Skip to content

The widget spec (mixw)

A device widget is the little live panel that renders on your Mixlar One’s screen — a mini dashboard, control surface, or watchface that lives on the device’s MULTI page. Widgets are data, not code: a JSON spec plus optional image assets, rendered by the firmware. There is nothing to compile, and nothing a widget author writes can crash or brick the device.

This page covers the shape of a widget — the folder, the panel, and the top-level widget.json fields. For the individual element types (label, bar, arc, btn, and so on), see Elements. For the serial protocol between the PC and the device, see Protocol. For pushing live data from a plugin, see Data & events.

Every widget draws into the same canvas: 240 × 140 px, origin top-left. The panel is a hard clip boundary — anything positioned or sized past the edge is cropped, never drawn over the rest of the screen, and scrolling is disabled. Leave a few pixels of inset around your content; anything flush to the border reads as cut off.

/widgets/
my_widget/ # widget id: 2-24 chars, a-z 0-9 _ - (no dots)
widget.json # the spec (required)
logo.png # any images the spec references
notes.txt # any data files (future use / plugin-owned)

The folder is self-contained — it installs, updates, and deletes as a unit. There are two ways it ends up on the device:

  • USB mass storage — the device exposes its FAT filesystem as a drive; drag the folder into /widgets/.
  • Serial upload — the app sends WIDGET_UPLOAD,<id>/<file>,<size> per file. Folder targets may overwrite, so iterating on a spec is just re-upload and re-select.
  • Bundled with a plugin — put the widget folder next to your plugin file (widgets/<widget_id>/widget.json), and the app auto-installs it onto the device a few seconds after connect, and re-uploads whenever the bundle changes. Installing the plugin installs the widget — see Data & events for the plugin-pairing side of this.

File rules, enforced on both the app and the firmware: filenames 5-32 characters, lowercase a-z 0-9 _ - ., extension .png / .json / .txt, no leading . or -, one folder level max, no path traversal. The spec file itself is capped at 16 KB.

field type meaning
mixw int spec version — currently 1 (required)
name string display name (picker / marketplace)
version string widget semver, e.g. "1.2.0". Read by the app’s update check when a bundled widget ships a newer version; the renderer itself ignores it. Optional but recommended.
bg hex string panel background color, without # (optional)
elements array the elements to draw, max 48, painted in order — later elements draw on top
pages array instead of elements, a multi-page widget — up to 6 pages, sharing the same 48-element budget. See Elements
menu object optional theming for the built-in page-menu overlay. See Elements

Use either elements (single page) or pages (multi-page), not both.

A single-page widget with a label, a progress bar, and a ring gauge:

{
"mixw": 1,
"name": "PC Stats Demo",
"bg": "16171B",
"elements": [
{ "t": "label", "x": 10, "y": 6, "w": 120, "s": 14, "c": "8E8E93", "text": "CPU" },
{ "t": "label", "x": 10, "y": 22, "w": 120, "s": 38, "c": "FFFFFF", "bind": "cpu_load", "fmt": "{v}%" },
{ "t": "bar", "x": 10, "y": 72, "w": 130, "h": 8, "c": "1DB954", "bgc": "2C2C2E",
"bind": "cpu_load", "min": 0, "max": 100 },
{ "t": "arc", "x": 156, "y": 12, "d": 86, "w": 8, "c": "FF9F0A", "bgc": "2C2C2E",
"bind": "mem_load", "min": 0, "max": 100, "fmt": "{v}%", "ls": 18 },
{ "t": "label", "x": 156, "y": 104, "w": 86, "s": 12, "c": "8E8E93", "text": "RAM", "a": "c" }
]
}

Every element has a type (t), a position (x, y), and — for anything live — a bind naming the data key it subscribes to. A companion PC plugin pushes values with a single call: self.push_widget_data("cpu_load", 62). See Data & events for the full binding model, cbind/obind/pulse, and the plugin-side API.

  1. Sketch the layout on the 240×140 canvas — decide what’s static (text) and what’s live (bind).

  2. Write the top-level fields (mixw, name, bg) and add elements one at a time.

  3. Drop the file at widgets/<id>/widget.json, either standalone or bundled next to your plugin.

  4. Preview it in the Plugin Builder’s Widget view, then install it via USB or serial upload.

Widgets can’t hurt the device, by construction:

  • Declarative only — no user code executes on the device; the firmware just interprets JSON.
  • Parse/IO errors degrade gracefully — a malformed spec shows placeholder text on the panel, never a crash.
  • {v} substitution is not printf — user strings passed through fmt are never treated as a format string.
  • Path traversal is blocked on widget ids, filenames, and image src, checked on both the app and the firmware.
  • Hard caps keep memory bounded: 16 KB spec file, 48 elements, 32 data-key slots, clamped value lengths.
  • FAT storage survives firmware OTA updates — installed widgets are untouched when the device firmware updates.
  • Elements — every element type (label, bar, arc, img, panel, led, line, qr, btn, toggle, slider), pages, layers, and menu theming.
  • Data & eventspush_widget_data, widget-plugin pairing, and handling on_widget_event.
  • Protocol — the raw serial lines exchanged between the PC and the device.
  • Widget builder — the visual tool for authoring and previewing specs.