validate & emulate
You don’t need an M1X plugged in to know your plugin is going to work. mixlar-sdk validate lints your manifest and widgets, and mixlar-sdk emulate runs the whole thing against a headless mock device — pressing buttons, sliding sliders, and rendering widgets to a PNG — all from the terminal.
validate
Section titled “validate”Lints plugin.json and every bundled widgets/<id>/widget.json.
mixlar-sdk validate [pkg]pkg is optional and defaults to the current directory — every CLI command resolves it by looking for plugin.json, so you’ll get a clear error rather than a silent wrong-folder operation if you run it somewhere else.
$ mixlar-sdk validate[warn] no 'author' — shown on the plugin card[ok] package valid (Weather Widget v0.1.0)Run it any time, but it’s especially worth running before link, dev, or pack — catching a malformed widget.json here is a lot faster than debugging it on-device.
emulate
Section titled “emulate”Runs a package against a headless mock device: no app, no hardware, no serial connection.
mixlar-sdk emulate [pkg] --widget ID --render OUT.png --data k=v --interactive| flag | meaning |
|---|---|
--widget |
widget id to load (default: the package’s first declared widget) |
--render OUT.png |
render the current widget state to a PNG (needs the [render] extra) |
--data k=v |
seed a WDATA value before rendering/inspecting (repeatable) |
--interactive |
drop into a REPL for pressing buttons, sliding sliders, and paging live |
Under the hood, emulate loads your package’s plugin instance(s), attaches a MockDevice, binds the first MixlarPlugin subclass it finds, loads the requested widget (or the manifest’s first declared one), applies any --data seeds, and prints the data table and interactive-element list — then optionally renders and/or starts the REPL.
$ mixlar-sdk emulate --data pomodoro.pct=40 --data pomodoro.remaining=12:34 --render preview.png[..] loaded widget 'pomodoro' from C:\...\widgets\pomodoro\widget.jsonData snapshot: pomodoro.pct = 40 pomodoro.remaining = 12:34Interactive elements: reset start_pause[ok] rendered preview to preview.pngRendering
Section titled “Rendering”--render needs Pillow, installed via the [render] extra:
pip install "mixlar-sdk[render]"The renderer is a best-effort approximation of what custom_widget.ino draws on the device — not pixel-perfect, but enough to eyeball layout, colors, and live data bindings while authoring. It supports label, bar, arc (with its centered fmt label), panel, led, line, img (loads the real asset relative to the spec’s folder), qr (drawn as a placeholder box, not an actual scannable code), and btn/toggle/slider as styled boxes. Layer visibility (show) and bind/fmt templating are both honored against whatever data you seed. Unknown element types are skipped silently, matching the firmware’s own forward-compatible behavior — see the element reference in Elements for the full set.
Interactive REPL
Section titled “Interactive REPL”--interactive drops you into a small command loop for poking at the widget live:
mixlar> press start_pause[ok] log: <- WEVENT,pomodoro,start_pause,pressmixlar> data pomodoro.status Working[ok] log: -> WDATA,pomodoro.status,Workingmixlar> toggle power onmixlar> slide brightness 75mixlar> page Controlsmixlar> snapshotmixlar> quit| command | does |
|---|---|
press <id> |
simulate a tap on a btn |
hold <id> |
simulate a long-press |
toggle <id> on|off |
flip a toggle element |
slide <id> <n> |
move a slider to value n |
page <name> |
switch the mock device’s current page |
data <key> <val> |
push a WDATA update from the plugin side |
snapshot |
print the current live data table |
help |
list commands |
quit |
exit the REPL |
Every one of these mirrors a real device→PC or PC→device protocol line — see Protocol for the wire format each command produces.
Scripting against MockDevice directly
Section titled “Scripting against MockDevice directly”For anything the CLI’s flags and REPL don’t cover — assertions in a test suite, scripted sequences, inspecting _bind_keys or _element_ids — use mixlar.emulator.MockDevice directly in Python:
from mixlar.emulator import MockDevice
device = MockDevice()device.attach() # installs itself as PluginRegistry's senderdevice.bind_plugin(my_plugin) # routes events to my_plugin.on_widget_event, etc.device.load_widget("widgets/pomodoro/widget.json")
device.press("start_pause") # simulates a tap -> WEVENT,...,pressdevice.toggle("power", on=True) # -> WEVENT,...,ondevice.slide("brightness", 80) # -> WEVENT,...,value=80device.switch_page("Controls") # -> WEVENT,...,_page,Controls
device.data("pomodoro.remaining") # read back a value the plugin pusheddevice.snapshot() # the whole live data table, as a dictdevice.render_to("preview.png") # needs Pillow (the [render] extra)
device.detach()-
attach()installs the device asPluginRegistry’s data/image sender — the exact seampush_widget_data/push_widget_imagecall through. Once attached, any plugin instance’s pushes land in this device, whether or not you explicitly bound it.detach()restores both toNoneand is idempotent. -
bind_plugin(plugin)additionally routes simulated device→PC traffic (press/hold/toggle/slide) toplugin.on_widget_event, and fireson_widget_shown/on_widget_hiddenaroundload_widgetcalls. If the plugin has no active widget id set yet, it adoptsplugin.widget_id— mirroring how the app selects a plugin’s bundled widget viaCUSTOMWIDGET,<id>on connect. -
load_widget(path_or_dict)accepts a path or an already-parsed dict, scans every element forbindkeys (populating_bind_keys) and interactiveids onbtn/toggle/sliderelements (populating_element_ids— whatpress/toggle/slidewill accept). For multi-page specs,current_pageseeds to the first non-hidden page, matching the device’s own startup behavior. -
Every call is logged chronologically to
device.log("-> WDATA,..."/"<- WEVENT,...") — useful for asserting exact wire traffic in a test. All dispatch into plugin code is crash-isolated, so a broken hook never raises out of apress()/toggle()/load_widget()call — matching how the real firmware/app boundary behaves.
feed(line) — acting as the PC→device sender
Section titled “feed(line) — acting as the PC→device sender”feed is what gets installed as the data sender; it parses one or more newline-joined protocol lines and applies them: WDATA updates the data table (rejecting a new key past the 32-key budget with a logged WDATA_ERR, exactly like the firmware), WPAGE switches the current page, CUSTOMWIDGET updates the tracked widget id. Feed it manually to simulate the PC side without a plugin at all — handy for testing a hand-written widget’s bind/show wiring in isolation:
device.feed("WDATA,pomodoro.pct,40")device.feed("WDATA,pomodoro.status,Work - Running")Rendering from Python
Section titled “Rendering from Python”from mixlar.emulator.render import render_widget, save_preview
img = render_widget("widgets/pomodoro/widget.json", data={"pomodoro.pct": "40"})save_preview(spec_dict, "preview.png", data=device.snapshot())This is the same renderer the emulate --render flag calls — it needs Pillow, but everything else in the emulator works without it.
Where this fits
Section titled “Where this fits”mixlar-sdk createscaffolds the package.mixlar-sdk validatecatches manifest and widget spec mistakes early.mixlar-sdk emulate --render preview.pnggives you a fast visual feedback loop with no hardware.mixlar-sdk link/mixlar-sdk devget it into the real app for end-to-end testing.mixlar-sdk pack/mixlar-sdk signship it — see pack, sign & publish.
For the full widget spec these commands lint and render against, see Widget Spec and Elements. For everything else the CLI can do, see the CLI intro.