From 1ef06434f2de78ec70f9fd7604e365f3b9e725d6 Mon Sep 17 00:00:00 2001 From: greghulette Date: Tue, 21 Jul 2026 22:18:34 -0400 Subject: [PATCH 1/2] Add Maestro servo commands, WCB WLED board, and NaviCore Controller board - maestro.json: add the local Pololu servo command set (setTarget, goHome, setSpeed, setAccel, setSpeedAccel, setEasing, restartScript, stopScript) to the existing Maestro component, alongside the ;M sequence trigger; rename the component/manifest entry to "Maestro (Pololu)". - wcb-wled.json (new): WCB-relayed WLED lighting commands (";L,VERB") with a WLED-id param (power / brightness / preset / color / effect / palette / raw). - navicore.json (new): NaviCore controller-native sequence commands (record / play / stop). - manifest.json: register the two new boards. The servo / record / play / stop commands carry an optional "local" routing hint (and the servo position a "unit":"servoPulse" UI hint) used by the NaviCore consumer; they are additive and safely ignored by other consumers. Co-Authored-By: Claude Opus 4.8 (1M context) --- libraries/boards/maestro.json | 120 +++++++++++++++++++++++- libraries/boards/navicore.json | 62 +++++++++++++ libraries/boards/wcb-wled.json | 162 +++++++++++++++++++++++++++++++++ libraries/manifest.json | 14 ++- 4 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 libraries/boards/navicore.json create mode 100644 libraries/boards/wcb-wled.json diff --git a/libraries/boards/maestro.json b/libraries/boards/maestro.json index 5b9fac0..1e16bdc 100644 --- a/libraries/boards/maestro.json +++ b/libraries/boards/maestro.json @@ -22,7 +22,7 @@ "components": [ { "id": "maestro", - "name": "Maestro (sequence trigger)", + "name": "Maestro (Pololu)", "kind": "wcb-verb", "confidence": "high", "routing": { @@ -54,10 +54,126 @@ ], "commentLabel": "Maestro seq", "category": "Sequences" + }, + { + "id": "maestro.setTarget", + "name": "Set servo position", + "safety": "movement", + "local": "maestro", + "encoder": "template", + "template": "setTarget,{channel},{position}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "position", "label": "Position (¼µs; 6000 = 1.5 ms)", "type": "int", "unit": "servoPulse", "min": 0, "max": 16383, "default": 6000, "required": true } + ], + "examples": ["setTarget,0,6000"], + "commentLabel": "Maestro pos", + "category": "Servo" + }, + { + "id": "maestro.goHome", + "name": "Send all servos home", + "safety": "movement", + "local": "maestro", + "encoder": "template", + "template": "goHome", + "params": [], + "examples": ["goHome"], + "commentLabel": "Maestro home", + "category": "Servo" + }, + { + "id": "maestro.setSpeed", + "name": "Set speed limit", + "safety": "movement", + "local": "maestro", + "encoder": "template", + "template": "setSpeed,{channel},{value}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "value", "label": "Speed (0-16383, 0 = unlimited)", "type": "int", "min": 0, "max": 16383, "default": 0, "required": true } + ], + "examples": ["setSpeed,0,0"], + "commentLabel": "Maestro speed", + "category": "Motion" + }, + { + "id": "maestro.setAccel", + "name": "Set acceleration limit", + "safety": "movement", + "local": "maestro", + "encoder": "template", + "template": "setAccel,{channel},{value}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "value", "label": "Accel (0-255, 0 = unlimited)", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } + ], + "examples": ["setAccel,0,0"], + "commentLabel": "Maestro accel", + "category": "Motion" + }, + { + "id": "maestro.setSpeedAccel", + "name": "Set speed + acceleration", + "safety": "movement", + "local": "maestro", + "encoder": "template", + "template": "setSpeedAccel,{channel},{speed},{accel}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "speed", "label": "Speed (0-16383)", "type": "int", "min": 0, "max": 16383, "default": 0, "required": true }, + { "name": "accel", "label": "Accel (0-255)", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } + ], + "examples": ["setSpeedAccel,0,0,0"], + "commentLabel": "Maestro spd+acc", + "category": "Motion" + }, + { + "id": "maestro.setEasing", + "name": "Set active easing", + "safety": "cosmetic", + "local": "maestro", + "encoder": "template", + "template": "setEasing,{profile}", + "params": [ + { "name": "profile", "label": "Easing (release | off | p1..p8)", "type": "text", "default": "release", "required": true } + ], + "examples": ["setEasing,release"], + "commentLabel": "Maestro easing", + "category": "Motion" + }, + { + "id": "maestro.restartScript", + "name": "Run a script (subroutine)", + "safety": "movement", + "local": "maestro", + "encoder": "template", + "template": "restartScript,{sub}", + "params": [ + { "name": "sub", "label": "Subroutine # (0-127)", "type": "int", "min": 0, "max": 127, "default": 0, "required": true } + ], + "examples": ["restartScript,0"], + "commentLabel": "Maestro script", + "category": "Script" + }, + { + "id": "maestro.stopScript", + "name": "Stop the script", + "safety": "movement", + "local": "maestro", + "encoder": "template", + "template": "stopScript", + "params": [], + "examples": ["stopScript"], + "commentLabel": "Maestro stop", + "category": "Script" } ], "categories": [ - "Sequences" + "Sequences", + "Servo", + "Motion", + "Script" ] } ] diff --git a/libraries/boards/navicore.json b/libraries/boards/navicore.json new file mode 100644 index 0000000..550feb9 --- /dev/null +++ b/libraries/boards/navicore.json @@ -0,0 +1,62 @@ +{ + "$schema": "droidnet-command-library/library/v1", + "enums": {}, + "components": [ + { + "id": "navicore", + "name": "NaviCore Controller", + "kind": "controller-native", + "confidence": "high", + "firmware": "NaviCore", + "routing": { + "class": "local", + "nativeWrapper": "none", + "durationSuffix": { "supported": false } + }, + "commands": [ + { + "id": "nc.record", + "name": "Record clip (toggle)", + "safety": "cosmetic", + "local": "record", + "encoder": "template", + "template": "{clip}", + "params": [ + { "name": "clip", "label": "Clip name (optional — blank auto-saves rec_N)", "type": "text", "required": false } + ], + "examples": ["dome_spin", ""], + "commentLabel": "Record clip", + "category": "Sequences" + }, + { + "id": "nc.play", + "name": "Play clip", + "safety": "cosmetic", + "local": "play", + "encoder": "template", + "template": "{clip}", + "params": [ + { "name": "clip", "label": "Clip name", "type": "text", "required": true }, + { "name": "loop", "label": "Loop", "type": "bool", "default": false, "required": false } + ], + "examples": ["dome_spin"], + "commentLabel": "Play clip", + "category": "Sequences" + }, + { + "id": "nc.stop", + "name": "Stop record / playback", + "safety": "cosmetic", + "local": "stop", + "encoder": "none", + "template": "", + "params": [], + "examples": [], + "commentLabel": "Stop", + "category": "Sequences" + } + ], + "categories": ["Sequences"] + } + ] +} diff --git a/libraries/boards/wcb-wled.json b/libraries/boards/wcb-wled.json new file mode 100644 index 0000000..c3a3de1 --- /dev/null +++ b/libraries/boards/wcb-wled.json @@ -0,0 +1,162 @@ +{ + "$schema": "droidnet-command-library/library/v1", + "enums": {}, + "components": [ + { + "id": "wcb-wled", + "name": "WCB · WLED Lighting", + "kind": "wcb-verb", + "confidence": "high", + "firmware": "WCB WCB_WLED", + "routing": { + "class": "wcb-verb" + }, + "commands": [ + { + "id": "wled.on", + "name": "Lights on", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},ON", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true } + ], + "examples": [";L1,ON"], + "commentLabel": "WLED on", + "category": "Power" + }, + { + "id": "wled.off", + "name": "Lights off", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},OFF", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true } + ], + "examples": [";L1,OFF"], + "commentLabel": "WLED off", + "category": "Power" + }, + { + "id": "wled.toggle", + "name": "Toggle", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},TOGGLE", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true } + ], + "examples": [";L1,TOGGLE"], + "commentLabel": "WLED toggle", + "category": "Power" + }, + { + "id": "wled.bri", + "name": "Set brightness", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},BRI,{level}", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, + { "name": "level", "label": "Brightness (0-255)", "type": "int", "min": 0, "max": 255, "default": 128, "required": true } + ], + "examples": [";L1,BRI,128"], + "commentLabel": "WLED bri", + "category": "Lighting" + }, + { + "id": "wled.preset", + "name": "Recall preset", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},PS,{preset}", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, + { "name": "preset", "label": "Preset #", "type": "int", "min": 0, "max": 250, "default": 1, "required": true } + ], + "examples": [";L1,PS,3"], + "commentLabel": "WLED preset", + "category": "Lighting" + }, + { + "id": "wled.color", + "name": "Set color", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},COL,{color}", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, + { "name": "color", "label": "Color (RRGGBB or RRGGBBWW)", "type": "text", "default": "FF0000", "pattern": "^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$", "required": true } + ], + "examples": [";L1,COL,FF0000"], + "commentLabel": "WLED color", + "category": "Lighting" + }, + { + "id": "wled.fx", + "name": "Set effect", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},FX,{index}", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, + { "name": "index", "label": "Effect #", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } + ], + "examples": [";L1,FX,9"], + "commentLabel": "WLED fx", + "category": "Lighting" + }, + { + "id": "wled.fxfull", + "name": "Set effect + speed/intensity", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},FX,{index},{speed},{intensity}", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, + { "name": "index", "label": "Effect #", "type": "int", "min": 0, "max": 255, "default": 0, "required": true }, + { "name": "speed", "label": "Speed (0-255)", "type": "int", "min": 0, "max": 255, "default": 128, "required": true }, + { "name": "intensity", "label": "Intensity (0-255)", "type": "int", "min": 0, "max": 255, "default": 128, "required": true } + ], + "examples": [";L1,FX,9,128,200"], + "commentLabel": "WLED fx full", + "category": "Lighting" + }, + { + "id": "wled.pal", + "name": "Set palette", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},PAL,{index}", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, + { "name": "index", "label": "Palette #", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } + ], + "examples": [";L1,PAL,5"], + "commentLabel": "WLED palette", + "category": "Lighting" + }, + { + "id": "wled.json", + "name": "Raw JSON (escape hatch)", + "safety": "cosmetic", + "encoder": "template", + "template": ";L{wledId},JSON,{payload}", + "params": [ + { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, + { "name": "payload", "label": "WLED /json/state document", "type": "text", "default": "{\"bri\":128}", "required": true } + ], + "examples": [";L1,JSON,{\"bri\":128}"], + "commentLabel": "WLED json", + "category": "Advanced" + } + ], + "categories": [ + "Power", + "Lighting", + "Advanced" + ] + } + ] +} diff --git a/libraries/manifest.json b/libraries/manifest.json index a4f5b04..e3131f4 100644 --- a/libraries/manifest.json +++ b/libraries/manifest.json @@ -43,7 +43,7 @@ { "id": "maestro", "file": "boards/maestro.json", - "name": "Maestro (sequence trigger)", + "name": "Maestro (Pololu)", "confidence": "high" }, { @@ -117,6 +117,18 @@ "file": "boards/wcb-native.json", "name": "WCB (native config)", "confidence": "high" + }, + { + "id": "wcb-wled", + "file": "boards/wcb-wled.json", + "name": "WCB · WLED Lighting", + "confidence": "high" + }, + { + "id": "navicore", + "file": "boards/navicore.json", + "name": "NaviCore Controller", + "confidence": "high" } ] } From 77f59c3084c502fc323f20631c65de42abe4a26c Mon Sep 17 00:00:00 2001 From: greghulette Date: Wed, 22 Jul 2026 14:01:47 -0400 Subject: [PATCH 2/2] Address PR review: consumer-agnostic library, split Maestro transports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Pull the NaviCore board entirely. Its record/play/stop are controller-LOCAL actions with no wire grammar; forcing them into a serial-command book caused the universal {clip} matcher (swallowed any bare-numeric raw step), the duplicate record/play templates, the unsupported bool loop param, and the encoder:"none" throw. They stay in NaviCore's own local seed instead. - Split Maestro by transport (board identity): `maestro` now carries only the ;M sequence-trigger WCB verb; new `maestro-native` (device-native) holds the raw Pololu protocol — setTarget/goHome/setSpeed/setAccel/setSpeedAccel/ setEasing/restartScript/stopScript. Dropped the NaviCore-only `local`/`unit` hints so the board is consumer-agnostic. - Fix wled.color pattern -> [0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})? (anchor-free, non-capturing) so ;L1,COL,FF0000 round-trips instead of falling to raw. - Free-text params use `pattern` instead of the unsupported type:"text" (wled.json payload -> ".+", maestro.setEasing -> "[A-Za-z0-9]+"). - Bump libraryVersion 4.1.0 -> 4.2.0 (manifest + releases.json, byte-equal), update load-node/engine version assertions and component count 19 -> 21. Co-Authored-By: Claude Fable 5 --- libraries/boards/maestro-native.json | 131 +++++++++++++++++++++++++++ libraries/boards/maestro.json | 120 +----------------------- libraries/boards/navicore.json | 62 ------------- libraries/boards/wcb-wled.json | 4 +- libraries/manifest.json | 16 ++-- releases.json | 8 +- test/engine.test.js | 2 +- test/load-node.test.js | 8 +- 8 files changed, 152 insertions(+), 199 deletions(-) create mode 100644 libraries/boards/maestro-native.json delete mode 100644 libraries/boards/navicore.json diff --git a/libraries/boards/maestro-native.json b/libraries/boards/maestro-native.json new file mode 100644 index 0000000..72d008b --- /dev/null +++ b/libraries/boards/maestro-native.json @@ -0,0 +1,131 @@ +{ + "$schema": "droidnet-command-library/library/v1", + "enums": {}, + "components": [ + { + "id": "maestro-native", + "name": "Maestro (Pololu Protocol)", + "kind": "device-native", + "confidence": "high", + "routing": { + "class": "broadcast", + "nativeWrapper": "none", + "durationSuffix": { + "supported": false + } + }, + "commands": [ + { + "id": "maestro.setTarget", + "name": "Set servo position", + "safety": "movement", + "encoder": "template", + "template": "setTarget,{channel},{position}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "position", "label": "Position (¼µs; 6000 = 1.5 ms)", "type": "int", "min": 0, "max": 16383, "default": 6000, "required": true } + ], + "examples": ["setTarget,0,6000"], + "commentLabel": "Maestro pos", + "category": "Servo" + }, + { + "id": "maestro.goHome", + "name": "Send all servos home", + "safety": "movement", + "encoder": "template", + "template": "goHome", + "params": [], + "examples": ["goHome"], + "commentLabel": "Maestro home", + "category": "Servo" + }, + { + "id": "maestro.setSpeed", + "name": "Set speed limit", + "safety": "movement", + "encoder": "template", + "template": "setSpeed,{channel},{value}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "value", "label": "Speed (0-16383, 0 = unlimited)", "type": "int", "min": 0, "max": 16383, "default": 0, "required": true } + ], + "examples": ["setSpeed,0,0"], + "commentLabel": "Maestro speed", + "category": "Motion" + }, + { + "id": "maestro.setAccel", + "name": "Set acceleration limit", + "safety": "movement", + "encoder": "template", + "template": "setAccel,{channel},{value}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "value", "label": "Accel (0-255, 0 = unlimited)", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } + ], + "examples": ["setAccel,0,0"], + "commentLabel": "Maestro accel", + "category": "Motion" + }, + { + "id": "maestro.setSpeedAccel", + "name": "Set speed + acceleration", + "safety": "movement", + "encoder": "template", + "template": "setSpeedAccel,{channel},{speed},{accel}", + "params": [ + { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, + { "name": "speed", "label": "Speed (0-16383)", "type": "int", "min": 0, "max": 16383, "default": 0, "required": true }, + { "name": "accel", "label": "Accel (0-255)", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } + ], + "examples": ["setSpeedAccel,0,0,0"], + "commentLabel": "Maestro spd+acc", + "category": "Motion" + }, + { + "id": "maestro.setEasing", + "name": "Set active easing", + "safety": "cosmetic", + "encoder": "template", + "template": "setEasing,{profile}", + "params": [ + { "name": "profile", "label": "Easing (release | off | p1..p8)", "pattern": "[A-Za-z0-9]+", "default": "release", "required": true } + ], + "examples": ["setEasing,release"], + "commentLabel": "Maestro easing", + "category": "Motion" + }, + { + "id": "maestro.restartScript", + "name": "Run a script (subroutine)", + "safety": "movement", + "encoder": "template", + "template": "restartScript,{sub}", + "params": [ + { "name": "sub", "label": "Subroutine # (0-127)", "type": "int", "min": 0, "max": 127, "default": 0, "required": true } + ], + "examples": ["restartScript,0"], + "commentLabel": "Maestro script", + "category": "Script" + }, + { + "id": "maestro.stopScript", + "name": "Stop the script", + "safety": "movement", + "encoder": "template", + "template": "stopScript", + "params": [], + "examples": ["stopScript"], + "commentLabel": "Maestro stop", + "category": "Script" + } + ], + "categories": [ + "Servo", + "Motion", + "Script" + ] + } + ] +} diff --git a/libraries/boards/maestro.json b/libraries/boards/maestro.json index 1e16bdc..5b9fac0 100644 --- a/libraries/boards/maestro.json +++ b/libraries/boards/maestro.json @@ -22,7 +22,7 @@ "components": [ { "id": "maestro", - "name": "Maestro (Pololu)", + "name": "Maestro (sequence trigger)", "kind": "wcb-verb", "confidence": "high", "routing": { @@ -54,126 +54,10 @@ ], "commentLabel": "Maestro seq", "category": "Sequences" - }, - { - "id": "maestro.setTarget", - "name": "Set servo position", - "safety": "movement", - "local": "maestro", - "encoder": "template", - "template": "setTarget,{channel},{position}", - "params": [ - { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, - { "name": "position", "label": "Position (¼µs; 6000 = 1.5 ms)", "type": "int", "unit": "servoPulse", "min": 0, "max": 16383, "default": 6000, "required": true } - ], - "examples": ["setTarget,0,6000"], - "commentLabel": "Maestro pos", - "category": "Servo" - }, - { - "id": "maestro.goHome", - "name": "Send all servos home", - "safety": "movement", - "local": "maestro", - "encoder": "template", - "template": "goHome", - "params": [], - "examples": ["goHome"], - "commentLabel": "Maestro home", - "category": "Servo" - }, - { - "id": "maestro.setSpeed", - "name": "Set speed limit", - "safety": "movement", - "local": "maestro", - "encoder": "template", - "template": "setSpeed,{channel},{value}", - "params": [ - { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, - { "name": "value", "label": "Speed (0-16383, 0 = unlimited)", "type": "int", "min": 0, "max": 16383, "default": 0, "required": true } - ], - "examples": ["setSpeed,0,0"], - "commentLabel": "Maestro speed", - "category": "Motion" - }, - { - "id": "maestro.setAccel", - "name": "Set acceleration limit", - "safety": "movement", - "local": "maestro", - "encoder": "template", - "template": "setAccel,{channel},{value}", - "params": [ - { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, - { "name": "value", "label": "Accel (0-255, 0 = unlimited)", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } - ], - "examples": ["setAccel,0,0"], - "commentLabel": "Maestro accel", - "category": "Motion" - }, - { - "id": "maestro.setSpeedAccel", - "name": "Set speed + acceleration", - "safety": "movement", - "local": "maestro", - "encoder": "template", - "template": "setSpeedAccel,{channel},{speed},{accel}", - "params": [ - { "name": "channel", "label": "Channel (0-31)", "type": "int", "min": 0, "max": 31, "default": 0, "required": true }, - { "name": "speed", "label": "Speed (0-16383)", "type": "int", "min": 0, "max": 16383, "default": 0, "required": true }, - { "name": "accel", "label": "Accel (0-255)", "type": "int", "min": 0, "max": 255, "default": 0, "required": true } - ], - "examples": ["setSpeedAccel,0,0,0"], - "commentLabel": "Maestro spd+acc", - "category": "Motion" - }, - { - "id": "maestro.setEasing", - "name": "Set active easing", - "safety": "cosmetic", - "local": "maestro", - "encoder": "template", - "template": "setEasing,{profile}", - "params": [ - { "name": "profile", "label": "Easing (release | off | p1..p8)", "type": "text", "default": "release", "required": true } - ], - "examples": ["setEasing,release"], - "commentLabel": "Maestro easing", - "category": "Motion" - }, - { - "id": "maestro.restartScript", - "name": "Run a script (subroutine)", - "safety": "movement", - "local": "maestro", - "encoder": "template", - "template": "restartScript,{sub}", - "params": [ - { "name": "sub", "label": "Subroutine # (0-127)", "type": "int", "min": 0, "max": 127, "default": 0, "required": true } - ], - "examples": ["restartScript,0"], - "commentLabel": "Maestro script", - "category": "Script" - }, - { - "id": "maestro.stopScript", - "name": "Stop the script", - "safety": "movement", - "local": "maestro", - "encoder": "template", - "template": "stopScript", - "params": [], - "examples": ["stopScript"], - "commentLabel": "Maestro stop", - "category": "Script" } ], "categories": [ - "Sequences", - "Servo", - "Motion", - "Script" + "Sequences" ] } ] diff --git a/libraries/boards/navicore.json b/libraries/boards/navicore.json deleted file mode 100644 index 550feb9..0000000 --- a/libraries/boards/navicore.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "$schema": "droidnet-command-library/library/v1", - "enums": {}, - "components": [ - { - "id": "navicore", - "name": "NaviCore Controller", - "kind": "controller-native", - "confidence": "high", - "firmware": "NaviCore", - "routing": { - "class": "local", - "nativeWrapper": "none", - "durationSuffix": { "supported": false } - }, - "commands": [ - { - "id": "nc.record", - "name": "Record clip (toggle)", - "safety": "cosmetic", - "local": "record", - "encoder": "template", - "template": "{clip}", - "params": [ - { "name": "clip", "label": "Clip name (optional — blank auto-saves rec_N)", "type": "text", "required": false } - ], - "examples": ["dome_spin", ""], - "commentLabel": "Record clip", - "category": "Sequences" - }, - { - "id": "nc.play", - "name": "Play clip", - "safety": "cosmetic", - "local": "play", - "encoder": "template", - "template": "{clip}", - "params": [ - { "name": "clip", "label": "Clip name", "type": "text", "required": true }, - { "name": "loop", "label": "Loop", "type": "bool", "default": false, "required": false } - ], - "examples": ["dome_spin"], - "commentLabel": "Play clip", - "category": "Sequences" - }, - { - "id": "nc.stop", - "name": "Stop record / playback", - "safety": "cosmetic", - "local": "stop", - "encoder": "none", - "template": "", - "params": [], - "examples": [], - "commentLabel": "Stop", - "category": "Sequences" - } - ], - "categories": ["Sequences"] - } - ] -} diff --git a/libraries/boards/wcb-wled.json b/libraries/boards/wcb-wled.json index c3a3de1..fab1d06 100644 --- a/libraries/boards/wcb-wled.json +++ b/libraries/boards/wcb-wled.json @@ -87,7 +87,7 @@ "template": ";L{wledId},COL,{color}", "params": [ { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, - { "name": "color", "label": "Color (RRGGBB or RRGGBBWW)", "type": "text", "default": "FF0000", "pattern": "^[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$", "required": true } + { "name": "color", "label": "Color (RRGGBB or RRGGBBWW)", "default": "FF0000", "pattern": "[0-9A-Fa-f]{6}(?:[0-9A-Fa-f]{2})?", "required": true } ], "examples": [";L1,COL,FF0000"], "commentLabel": "WLED color", @@ -145,7 +145,7 @@ "template": ";L{wledId},JSON,{payload}", "params": [ { "name": "wledId", "label": "WLED ID (1-9)", "type": "int", "min": 1, "max": 9, "default": 1, "required": true }, - { "name": "payload", "label": "WLED /json/state document", "type": "text", "default": "{\"bri\":128}", "required": true } + { "name": "payload", "label": "WLED /json/state document", "default": "{\"bri\":128}", "pattern": ".+", "required": true } ], "examples": [";L1,JSON,{\"bri\":128}"], "commentLabel": "WLED json", diff --git a/libraries/manifest.json b/libraries/manifest.json index e3131f4..d28d669 100644 --- a/libraries/manifest.json +++ b/libraries/manifest.json @@ -1,6 +1,6 @@ { "$schema": "droidnet-command-library/catalog/v1", - "libraryVersion": "4.1.0", + "libraryVersion": "4.2.0", "schemaVersion": "v1", "generatedFrom": "C2B5 firmware + WIPDocs (2026-06); R2UppitySpinnerV3 ALT fw 3.5.3 (2026-07); Printed Droid Magic Panel 32 Wireless KB (2026-07); WCB 6.1.5_290119RJUN2026 (2026-07)", "boards": [ @@ -43,7 +43,13 @@ { "id": "maestro", "file": "boards/maestro.json", - "name": "Maestro (Pololu)", + "name": "Maestro (sequence trigger)", + "confidence": "high" + }, + { + "id": "maestro-native", + "file": "boards/maestro-native.json", + "name": "Maestro (Pololu Protocol)", "confidence": "high" }, { @@ -123,12 +129,6 @@ "file": "boards/wcb-wled.json", "name": "WCB · WLED Lighting", "confidence": "high" - }, - { - "id": "navicore", - "file": "boards/navicore.json", - "name": "NaviCore Controller", - "confidence": "high" } ] } diff --git a/releases.json b/releases.json index cad5544..5fed061 100644 --- a/releases.json +++ b/releases.json @@ -2,17 +2,17 @@ "$schema": "droidnet-command-library/releases/v1", "_comment": "Update pointer for the 'Update Library' flow. A device fetches this file, compares latest.libraryVersion to its installed catalog, and (if newer) downloads latest.url (the catalog manifest), then fetches each board listed in manifest.boards in order, validates them, and loads them via loadLibrary(boards, { libraryVersion }).", "latest": { - "libraryVersion": "4.1.0", + "libraryVersion": "4.2.0", "schemaVersion": "v1", - "releasedAt": "2026-07-09", + "releasedAt": "2026-07-22", "url": "https://raw.githubusercontent.com/travisccook/droidnet-command-library/main/libraries/manifest.json", - "notes": "Minor: WCB firmware 6.1.5 sweep. WCB · HCR Vocalizer (wcb-hcr) rounds out from 2 to 22 commands: per-channel volume (VOL/VOLUP/VOLDN with step defaults) and whole-board VOLUP/VOLDN, FADEIN/FADEOUT, and PLAY with fade-in, plus the remaining ;H, verbs — OVERLOAD, SETEMOTION, OVERRIDE, MUSE (on/off/toggle + gap range), STOPEMOTE, RESETEMOTIONS, STOPWAV, FN, and RAW passthrough. Adds WCB · MP3 Trigger board (wcb-mp3): 12 commands covering the ;A, verb set — play/play+callback (track order and filesystem order), stop, next/prev, volume set/up/down, track count, and firmware version. Adds WCB (native config) board (wcb-native): 77 commands covering the board's own ? grammar — hardware/board-number/MAC/baud/port-label setup, broadcast and serial/PWM port mapping, Kyber/Maestro/MP3/HCR sub-device config, ;S/;W routing and ;T timed dispatch, named sequences (?SEQ save/list/clear, ;C/;SEQ run), ETM tuning, debug toggles, and reboot/erase-NVS. Generated against WCB firmware 6.1.5_290119RJUN2026; board catalog 17 -> 19." + "notes": "Minor: Maestro servo control + WLED lighting. Adds Maestro (Pololu Protocol) board (maestro-native): the device-native Pololu servo grammar — setTarget, goHome, setSpeed/setAccel/setSpeedAccel, setEasing, and restartScript/stopScript. The existing Maestro board (maestro) is now scoped to just the ;M sequence-trigger WCB verb, so each board carries a single transport. Adds WCB · WLED Lighting board (wcb-wled): the ;L, verb set — on/off/toggle, brightness, preset, color, effect (+ speed/intensity), palette, and a raw /json/state escape hatch. Board catalog 19 -> 21." }, "libraries": [ { "id": "droidnet-astromech", "name": "DroidNet Astromech Boards", - "libraryVersion": "4.1.0", + "libraryVersion": "4.2.0", "schemaVersion": "v1", "url": "https://raw.githubusercontent.com/travisccook/droidnet-command-library/main/libraries/manifest.json" } diff --git a/test/engine.test.js b/test/engine.test.js index acd0052..6d559a6 100644 --- a/test/engine.test.js +++ b/test/engine.test.js @@ -19,7 +19,7 @@ describe('engine lookups', () => { expect(cb.getComponents().map(c => c.id)).toEqual(expect.arrayContaining(['flthy-hps', 'ia-magic-panel'])); }); test('getLibraryVersion reports the loaded version', () => { - expect(cb.getLibraryVersion()).toBe('4.1.0'); + expect(cb.getLibraryVersion()).toBe('4.2.0'); }); test('getCommand resolves and back-links its component', () => { const cmd = cb.getCommand('flthy.led.solid'); diff --git a/test/load-node.test.js b/test/load-node.test.js index 238e223..b605b05 100644 --- a/test/load-node.test.js +++ b/test/load-node.test.js @@ -3,20 +3,20 @@ const { readCatalog, loadCatalog } = require('../src/load-node.js'); test('readCatalog returns the manifest and every board, in order', () => { const { manifest, boards } = readCatalog(); - expect(manifest.libraryVersion).toBe('4.1.0'); + expect(manifest.libraryVersion).toBe('4.2.0'); expect(boards).toHaveLength(manifest.boards.length); expect(boards.map(b => b.components[0].id)).toEqual(manifest.boards.map(b => b.id)); }); test('loadCatalog({ load: false }) returns the merged catalog object', () => { const lib = loadCatalog({ load: false }); - expect(lib.libraryVersion).toBe('4.1.0'); - expect(lib.components.length).toBe(19); + expect(lib.libraryVersion).toBe('4.2.0'); + expect(lib.components.length).toBe(21); }); test('loadCatalog() loads the engine and resolves commands', () => { const engine = loadCatalog(); - expect(engine.getLibraryVersion()).toBe('4.1.0'); + expect(engine.getLibraryVersion()).toBe('4.2.0'); expect(engine.getCommand('flthy.led.solid')).not.toBeNull(); expect(engine.encode(engine.getCommand('flthy.led.solid'), { designator: 'A', color: '5' }, {})).toBe('A0065'); });