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/wcb-wled.json b/libraries/boards/wcb-wled.json new file mode 100644 index 0000000..fab1d06 --- /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)", "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", "default": "{\"bri\":128}", "pattern": ".+", "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..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": [ @@ -46,6 +46,12 @@ "name": "Maestro (sequence trigger)", "confidence": "high" }, + { + "id": "maestro-native", + "file": "boards/maestro-native.json", + "name": "Maestro (Pololu Protocol)", + "confidence": "high" + }, { "id": "psi-pro", "file": "boards/psi-pro.json", @@ -117,6 +123,12 @@ "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" } ] } 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'); });