From d0657b7622bdfa87f252adb03e890ffc211b34cb Mon Sep 17 00:00:00 2001 From: Todd Word Date: Sat, 24 Oct 2026 23:50:55 -0500 Subject: [PATCH] feat(boards): R2 Uppity Spinner ALT for firmware 4.0.0-beta.1 (library 4.7.0) The periscope board's entry for its v4 rewrite, which keeps every v3 :P/#P command: - adds #PRESUME (release a latched E-STOP), #PSTATE, #PCLEARFAULT, #PROTARYTEST, #PRANDOMLIGHT (new enum uppity.randomLight) and #PMESHHB, each marked (v4); - :PX and :PH keep their ids and templates, with names that say what they do on v4; - #PRC (RC/PPM mode) is kept and marked v3 only: v4 has no RC input and ignores it; - the routing note describes v4 and says the entry also drives v3.5.3+ boards. No ids removed or renamed; other boards unchanged. Library 4.6.0 -> 4.7.0 (minor), with the version pins in package.json, package-lock.json and the two tests that check it. npm run validate: ok (the Lifter/Rotary category warnings predate this change); npm test: 498 passed. Co-Authored-By: Claude Opus 5.5 --- libraries/boards/r2uppityspinner-alt.json | 161 +++++++++++++++++++++- libraries/manifest.json | 4 +- package-lock.json | 4 +- package.json | 2 +- releases.json | 8 +- test/engine.test.js | 2 +- test/load-node.test.js | 6 +- 7 files changed, 167 insertions(+), 20 deletions(-) diff --git a/libraries/boards/r2uppityspinner-alt.json b/libraries/boards/r2uppityspinner-alt.json index 59ea6b9..bc13a82 100644 --- a/libraries/boards/r2uppityspinner-alt.json +++ b/libraries/boards/r2uppityspinner-alt.json @@ -1,6 +1,6 @@ { "$schema": "droidnet-command-library/library/v1", - "generatedFrom": "R2UppitySpinnerV3 ALT fw 3.5.3 (highfalutintodd fork of Mimir Reynisson's original)", + "generatedFrom": "Uppity Spinner ALT v4.0.0-beta.1 (the clean-sheet rewrite, highfalutintodd/R2UppitySpinner_ALT); based on the community v3.5.3 entry", "enums": { "uppity.lightMode": { "label": "Light Kit Mode", @@ -128,6 +128,47 @@ "label": "Normally Open" } ] + }, + "uppity.randomLight": { + "label": "Light Kit in Random Mode", + "values": [ + { + "code": "0", + "label": "Leave alone" + }, + { + "code": "1", + "label": "Off" + }, + { + "code": "2", + "label": "Obi-Wan (blue)" + }, + { + "code": "3", + "label": "Yoda (green fade)" + }, + { + "code": "4", + "label": "Sith (red flash)" + }, + { + "code": "5", + "label": "Search Light (white)" + }, + { + "code": "6", + "label": "Dagobah (screen-accurate)" + }, + { + "code": "7", + "label": "Sparkle" + }, + { + "code": "8", + "label": "A new mode each action" + } + ] } }, "components": [ @@ -136,13 +177,14 @@ "name": "R2 Uppity Spinner ALT (Periscope)", "kind": "device-native", "confidence": "community", - "firmware": "v3.5.3", + "firmware": "v4.0.0-beta.1", "routing": { "class": "broadcast", "nativeWrapper": "none", "durationSuffix": { "supported": false - } + }, + "notes": "Every v3 :P/#P command keeps its syntax, so this entry also drives boards on v3.5.3 and later; the commands marked (v4) are new in 4.0, and RC mode is v3 only. v4: the same :P/#P grammar on the command serial (Marcduino bus), the USB console and, when the builder turns it on, the WCB mesh as its own client device (off by default). :PX, ?STOP and &ESTOP latch an E-STOP that only #PRESUME, the board's web page or &ESTOP,RESET releases. With the mesh on it sends &PERI,HB/POS/STATE/FAULT telemetry. Settings, unlocking and rescue commands are refused from the mesh; the bus refuses the rescue override." }, "commands": [ { @@ -238,7 +280,7 @@ }, { "id": "uppity.lifter.home", - "name": "Return Home", + "name": "Lower Safely (home the head first if needed)", "safety": "movement", "encoder": "template", "template": ":PH", @@ -274,7 +316,7 @@ }, { "id": "uppity.estop", - "name": "Emergency Stop", + "name": "Emergency Stop (on v4 it latches until released)", "safety": "power", "encoder": "template", "template": ":PX", @@ -865,7 +907,7 @@ }, { "id": "uppity.cfg.rc", - "name": "RC (PPM) Mode", + "name": "RC (PPM) Mode (v3 only)", "safety": "config", "encoder": "template", "template": "#PRC{state}", @@ -881,7 +923,7 @@ "#PRC0", "#PRC1" ], - "commentLabel": "Uppity RC mode", + "commentLabel": "Uppity RC mode (v3 only; v4 has no RC input and ignores it)", "category": "Setup" }, { @@ -961,6 +1003,111 @@ ], "commentLabel": "Uppity rotary limit", "category": "Setup" + }, + { + "id": "uppity.cfg.resume", + "name": "Release E-STOP (v4)", + "category": "Power", + "safety": "power", + "encoder": "template", + "template": "#PRESUME", + "params": [], + "examples": [ + "#PRESUME" + ], + "commentLabel": "Uppity release E-STOP" + }, + { + "id": "uppity.cfg.state", + "name": "Report State (v4)", + "category": "Config", + "safety": "cosmetic", + "encoder": "template", + "template": "#PSTATE", + "params": [], + "examples": [ + "#PSTATE" + ], + "commentLabel": "Uppity state" + }, + { + "id": "uppity.cfg.clearFault", + "name": "Clear Fault (v4)", + "category": "Power", + "safety": "config", + "encoder": "template", + "template": "#PCLEARFAULT", + "params": [], + "examples": [ + "#PCLEARFAULT" + ], + "commentLabel": "Uppity clear fault" + }, + { + "id": "uppity.cfg.rotaryTest", + "name": "Rotary Test (v4: measure ticks per turn, then home)", + "category": "Setup", + "safety": "movement", + "encoder": "template", + "template": "#PROTARYTEST{turns}", + "params": [ + { + "name": "turns", + "type": "int", + "min": 1, + "max": 10, + "required": true, + "default": 3 + } + ], + "examples": [ + "#PROTARYTEST3" + ], + "commentLabel": "Uppity rotary test" + }, + { + "id": "uppity.cfg.randomLight", + "name": "Light Kit During Random Mode (v4)", + "category": "Setup", + "safety": "config", + "encoder": "template", + "template": "#PRANDOMLIGHT{mode}", + "params": [ + { + "name": "mode", + "enum": "uppity.randomLight", + "required": true, + "default": "0" + } + ], + "examples": [ + "#PRANDOMLIGHT0", + "#PRANDOMLIGHT7" + ], + "commentLabel": "Uppity random light" + }, + { + "id": "uppity.cfg.meshHeartbeat", + "name": "Mesh Heartbeat Period (v4)", + "category": "Setup", + "safety": "config", + "encoder": "template", + "template": "#PMESHHB{seconds}", + "params": [ + { + "name": "seconds", + "type": "int", + "min": 5, + "max": 300, + "required": true, + "default": 10 + } + ], + "examples": [ + "#PMESHHB10", + "#PMESHHB30" + ], + "commentLabel": "Uppity mesh heartbeat" } ], "categories": [ diff --git a/libraries/manifest.json b/libraries/manifest.json index 530c1d3..2fbe723 100644 --- a/libraries/manifest.json +++ b/libraries/manifest.json @@ -1,8 +1,8 @@ { "$schema": "droidnet-command-library/catalog/v1", - "libraryVersion": "4.6.0", + "libraryVersion": "4.7.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); WCB 6.2.1_021242RSEP2026 + WcbCmd 0.8.0 (2026-09); DroidNet forks (2026-09)", + "generatedFrom": "C2B5 firmware + WIPDocs (2026-06); R2 Uppity Spinner ALT fw 4.0.0-beta.1 (2026-10); Printed Droid Magic Panel 32 Wireless KB (2026-07); WCB 6.1.5_290119RJUN2026 (2026-07); WCB 6.2.1_021242RSEP2026 + WcbCmd 0.8.0 (2026-09); DroidNet forks (2026-09)", "boards": [ { "id": "flthy-hps", diff --git a/package-lock.json b/package-lock.json index e5f6541..27b2c31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "droidnet-command-library", - "version": "4.6.0", + "version": "4.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "droidnet-command-library", - "version": "4.6.0", + "version": "4.7.0", "license": "MPL-2.0", "devDependencies": { "ajv": "^8.17.1", diff --git a/package.json b/package.json index 7708bd1..e75a736 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "droidnet-command-library", - "version": "4.6.0", + "version": "4.7.0", "description": "Schema-driven encoder/decoder and visual composer for building serial commands to control a vibrant community of Astromech droid components. Anyone can add their board by editing a JSON library — no code changes required.", "license": "MPL-2.0", "keywords": [ diff --git a/releases.json b/releases.json index 2a9d7d4..84f680b 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.6.0", + "libraryVersion": "4.7.0", "schemaVersion": "v1", - "releasedAt": "2026-10-02", + "releasedAt": "2026-10-24", "url": "https://raw.githubusercontent.com/travisccook/droidnet-command-library/main/libraries/manifest.json", - "notes": "Minor: comments keep their place, and the composer can follow a host's text field. Engine: a *** comment after an empty segment (^^***) is a standalone note step on its own line instead of the previous step's label, and buildWCBValue writes a note step that follows another step as ^^***text (a note first in the value stays ***text). A comment parsed from a single ^ after a delay, a labelled step or another note carries inline: true and is written back with one ^, so those values rebuild byte for byte. A comment directly after a command or raw step (^***) is still that step's label. The WCB skips empty segments and comments alike, so the firmware runs the same commands. + Note after a step now writes ^^*** (its own line) instead of ^*** (which read back as the step's label). UI: renderComposer returns { setValue(value) }: equal to the compiled steps it does nothing; otherwise it re-parses the steps, cancels a step being edited in the add bar and re-renders the step list, length and safety note, keeping a half-filled add bar. It never calls onChange; callers that ignore the return value are unaffected. No ids, templates or params change; board and command counts are unchanged." + "notes": "Minor: R2 Uppity Spinner ALT updated for firmware 4.0.0-beta.1 (the v4 rewrite; it keeps every v3 :P/#P command). Added: #PRESUME (release a latched E-STOP), #PSTATE, #PCLEARFAULT, #PROTARYTEST, #PRANDOMLIGHT (new enum uppity.randomLight) and #PMESHHB. :PX and :PH keep their ids and templates with names that say what they do on v4 (:PX latches; :PH lowers safely, homing the head first). The six new commands are marked (v4); #PRC (RC/PPM mode) is kept and marked v3 only: v4 has no RC input and ignores it. The board's routing note describes v4. No ids removed or renamed; other boards unchanged." }, "libraries": [ { "id": "droidnet-astromech", "name": "DroidNet Astromech Boards", - "libraryVersion": "4.6.0", + "libraryVersion": "4.7.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 5964811..9ac993c 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.6.0'); + expect(cb.getLibraryVersion()).toBe('4.7.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 a06b931..804f925 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.6.0'); + expect(manifest.libraryVersion).toBe('4.7.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.6.0'); + expect(lib.libraryVersion).toBe('4.7.0'); expect(lib.components.length).toBe(24); }); test('loadCatalog() loads the engine and resolves commands', () => { const engine = loadCatalog(); - expect(engine.getLibraryVersion()).toBe('4.6.0'); + expect(engine.getLibraryVersion()).toBe('4.7.0'); expect(engine.getCommand('flthy.led.solid')).not.toBeNull(); expect(engine.encode(engine.getCommand('flthy.led.solid'), { designator: 'A', color: '5' }, {})).toBe('A0055'); });