Describe a device

Candeo learns a device from a JSON file: how to find it, the bytes it expects, where its lights sit, and the effects its own firmware runs. No code and no rebuild — save the file, choose it on the device's card, and the device obeys it.

Start from one that works

  1. In Devices, under Every device Candeo knows, Open the device closest to yours. A built-in definition opens read only.
  2. Click Copy to yours. The copy lands in Documents\candeo\devices, opens in its place, and becomes the one the device uses.
  3. Edit it. The editor checks every field as you type and shows what each one means on hover; Ctrl+S puts it in shape and saves it.
  4. Saved, it takes effect at once: the device opens again on it, and the effect it was showing starts again.

For a device Candeo does not know, change match to its ids: the file then adds a device rather than replacing one. Several files may describe the same device — a variant each — and the card's Definition list says which one drives it. The built-in one is the default, and a file never takes over by being in the folder.

A file that does not load is listed under Your definitions with the reason, the same one the editor underlines, and the device keeps the definition it had.

A whole definition

The lights around the Alienware m18 R1's keyboard, as Candeo ships them: three zones, a report to select lights, another to colour them, and two images checked byte for byte.

alienware-m18-r1-zones.json
{
  "$schema": "https://o2csi.github.io/candeo/device-definition.schema.json",
  "name": "Alienware m18 R1 zones",
  "notes": [
    "Survey: docs/protocol/alienware-m18-r1.md, section 2.",
    "Matched on the machine: SMBIOS names it “Alienware m18 R1”, as Candeo read it at startup on the surveyed machine on 2026-10-16. The AW-ELC, 187c:0551, also lights the m16 R1, the m16 R2 and the m18 R2, each wiring its ids its own way (alienfx-tools' maps, #328).",
    "The AMD edition, “Alienware m18 R1 AMD”, is another machine: not matched until someone confirms its ids.",
    "The ring's two halves make one region: one half lit alone spreads through the whole ring, and the halves show only when both are lit (docs/protocol/alienware-m18-r1.md §2)."
  ],
  "match": { "vid": "187c", "pid": "0551", "usagePage": "ff00", "usage": "0001", "machine": "Alienware m18 R1" },
  "report": { "wire": "output", "length": 33, "prefix": "00" },
  "frame": {
    "pace": { "atLeastMs": 100, "skipUnchanged": true },
    "open": ["03 21 00 01 ff ff"],
    "each": {
      "group": "colour",
      "chunk": 4,
      "send": ["03 23 01 00 {count} {addresses}", "03 24 00 07 d0 00 fa {r} {g} {b}"]
    },
    "close": ["03 21 00 03 00 ff"]
  },
  "lights": {
    "kind": "zones",
    "items": [
      { "name": "ring, upper half", "address": 0, "shape": "archUp", "rect": [2, 7.9, 9, 1.1] },
      { "name": "ring, lower half", "address": 1, "shape": "archDown", "rect": [2, 9, 9, 1.1] },
      { "name": "logo on the lid", "address": 2, "shape": "disc", "rect": [5.5, 2.5, 2, 2] }
    ]
  },
  "outline": [
    { "rect": [0.5, 0, 12, 7], "r": 0.5 },
    { "rect": [0, 7.4, 13, 3.2], "r": 0.8 },
    { "rect": [3.2, 8.65, 0.8, 0.7], "r": 0.12 },
    { "rect": [4.4, 8.65, 1.4, 0.7], "r": 0.12 },
    { "rect": [6.2, 8.65, 0.6, 0.7], "r": 0.12 },
    { "rect": [7.2, 8.65, 1.4, 0.7], "r": 0.12 },
    { "rect": [9, 8.65, 0.8, 0.7], "r": 0.12 }
  ],
  "regions": [
    { "id": "ring", "name": { "en": "Ring", "fr": "Anneau" }, "lights": [0, 1] },
    { "id": "logo", "name": "Logo", "lights": [2] }
  ],
  "examples": [
    {
      "colours": ["ff0000", "00ff00", "0000ff"],
      "reports": [
        "03 21 00 01 ff ff",
        "03 23 01 00 01 00", "03 24 00 07 d0 00 fa ff 00 00",
        "03 23 01 00 01 01", "03 24 00 07 d0 00 fa 00 ff 00",
        "03 23 01 00 01 02", "03 24 00 07 d0 00 fa 00 00 ff",
        "03 21 00 03 00 ff"
      ]
    },
    {
      "colours": ["ff0000", "ff0000", "000000"],
      "reports": [
        "03 21 00 01 ff ff",
        "03 23 01 00 02 00 01", "03 24 00 07 d0 00 fa ff 00 00",
        "03 23 01 00 01 02", "03 24 00 07 d0 00 fa",
        "03 21 00 03 00 ff"
      ]
    }
  ]
}

The sections below take its parts in turn, and the fields the other devices add.

Finding the device: match

A device is found by its vendor and product ids, four hexadecimal digits each. A device with lights usually exposes several HID entries — keys, media keys, lighting — and only one of them takes the lighting reports, so the definition names which:

On Windows, the device's Hardware Ids in Device Manager read like HID\VID_1532&PID_0292&MI_03: vendor, product, interface. The wrong entry opens fine and silently drops every write, which is why it is named rather than guessed.

A laptop's lights are different: the maker fits the same controller in many machines and wires it differently in each, so the same ids light the keyboard on one model and the lid on another. There, machine names the model the definition was made on, as the system gives it — the machine line of the diagnostic Candeo copies: "machine": "Alienware 17 R5". It is compared exactly, and on any other machine the definition drives nothing. A keyboard or a mouse needs none.

The shape of a report: report

An image of every light: frame

Up to thirty times a second, Candeo sends an image: the open reports, then each for the lights, then the close reports — often what makes the device show it. Reports are written as a capture shows them, bytes in hexadecimal, and placeholders in braces that the image fills in.

By default, each.send goes out once per light. Two ways to send fewer:

Placeholder What it is
{r} {g} {b}A colour, one byte each.
{address}A light's address, as the device calls it.
{count} {addresses}How many lights the report carries, and their addresses.
{lights}Their entries, each written as each.light says.
{row} {start} {end}A row, and its first and last column.
{length:N}How many bytes the report holds after offset N, once filled.
{level}The brightness, in brightness.
{kind} {r2} {g2} {b2}A firmware effect's kind, and its second colour.

Two more fields, for devices with habits:

Where the lights are: lights

kind says what they are: keys, which can be pressed, or zones — a ring, a logo, a strip — which cannot, so effects reacting to typing are not offered there. Each item then gives:

outline draws, faintly, the parts that light nothing — a lid, a base, ports — so zones sit where they are on the device. A keyboard needs none.

Parts that run their own effect: regions

A laptop's controller often lights the keyboard, the lid and the base together, so the whole machine runs one effect. regions names its parts, and a device that declares them is offered Split into regions: each part then runs an effect of its own. Each region gives:

"regions": [
  { "id": "keyboard", "name": { "en": "Keyboard", "fr": "Clavier" }, "lights": [4, 5, 6, 7, 12] },
  { "id": "lid", "name": { "en": "Lid", "fr": "Capot" }, "lights": [0, 1, 10, 11] },
  { "id": "base", "name": { "en": "Base", "fr": "Base" }, "lights": [8, 9, 2] },
  { "id": "power", "name": { "en": "Power button", "fr": "Bouton d'alimentation" }, "lights": [3] }
]

Every light belongs to exactly one region, and there are at least two: a light in none would stay dark once the device is split, a light in two would have two effects fighting over it, and the file is refused, naming the address. An effect running on a region sees the region alone, measured where it sits on the device. Split only what the hardware keeps apart: the m18 R1's ring is one region, since one half lit alone spreads through the whole ring.

Brightness and the firmware's own effects

brightness lists the reports setting the overall level, {level} from 00 to ff. Without it, the device has no brightness slider.

firmware offers the effects the device runs by itself, in the gallery beside Candeo's: they cost the computer nothing, and keep going when Candeo is closed. send is the report selecting one, with {kind} and its colours; off the kind that shows nothing, when there is one. Each effect then says:

{
  "id": "hardware:m18-08",
  "kind": "08",
  "looks": "breathing",
  "name": { "en": "Breathing", "fr": "Respiration" },
  "summary": {
    "en": "Colours follow one another, fading through black in between.",
    "fr": "Les couleurs se succèdent, en passant par le noir entre chacune."
  }
}

A plain string is the same in every language, and English is shown where the interface's language is missing. looks picks the drawing the gallery shows as a legend of the effect — steady, pulse, breathing, morph, wave, scanner or spectrum. It is a legend, not a preview: the firmware draws the effect and shows nothing back.

Proving it: examples

A reference frame is an image and the reports it must give: colours in, bytes out, as a capture of the device showed them. Candeo's tests replay those of every built-in definition byte for byte — which is what lets someone without the device review one, and what keeps it right as Candeo changes. In a file of yours they document; in a contribution they are required.

surveyed records the firmware version the survey was made against, and notes what a reviewer should know. Candeo reads neither to drive the device; when a device reports another firmware version, its card says so.

Finding the bytes

The bytes come from watching the maker's own software: a USB capture — Wireshark with USBPcap on Windows — while it changes a colour, then another, then the brightness. What changes between two captures is the colour; what stays is the frame around it. Two worked surveys, with the method and every fact dated, are in docs/protocol.

Then try it on the device, carefully:

Making it built in

A definition that works for you can ship with Candeo, for everyone with that device. A pull request adds the file to crates/candeo-device/devices with its reference frames, and the survey behind it to docs/protocol: the firmware version, how each fact was established, and when. The tests replay the frames, and the schema checks the rest. #34 is where what a contribution carries is being settled.

Some definitions are waiting for someone with the device: the ones to verify were written from published facts, and one report that it lights is what they need.

Reference