From 7389592a232db73b1ac224d0a81e06b5715f95d8 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Mon, 30 Mar 2026 14:21:24 -0700 Subject: [PATCH 01/16] 0.0.22 --- pxt.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pxt.json b/pxt.json index dcd6f4a..194bbde 100644 --- a/pxt.json +++ b/pxt.json @@ -1,6 +1,6 @@ { "name": "sensors", - "version": "0.0.21", + "version": "0.0.22", "dependencies": { "core": "*", "radio": "*", From 198ddbe594c953d81499e164dce58d457dfda325 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Mon, 30 Mar 2026 15:02:21 -0700 Subject: [PATCH 02/16] some refactoring --- jacdacSensorMap.ts | 20 +++++++++++++++----- ui/.github/workflows/makecode.yml | 13 +++++++++++++ ui/.prettierrc | 1 + ui/main.ts | 19 +++++++++++++++++++ ui/mkc.json | 4 ++++ ui/pxt.json | 19 +++++++++++++++++++ ui/tsconfig.json | 15 +++++++++++++++ 7 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 ui/.github/workflows/makecode.yml create mode 100644 ui/.prettierrc create mode 100644 ui/main.ts create mode 100644 ui/mkc.json create mode 100644 ui/pxt.json create mode 100644 ui/tsconfig.json diff --git a/jacdacSensorMap.ts b/jacdacSensorMap.ts index b45f496..5d44be1 100644 --- a/jacdacSensorMap.ts +++ b/jacdacSensorMap.ts @@ -5,9 +5,7 @@ namespace sensors { * This is looked up by a SimpleSensorClient.serviceClass: * The metadata therein can be used to make a Sensor(), see getJacdacSensor() and wrapJacdacSensor() */ - type SimpleSensorsMap = { - /** This service class key is in decimal, since that's what jacdac.SimpleSensorClient.serviceClass returns */ - [serviceClass: number]: { + type SimpleSensorMetaData = { /** A unique name starting with 'Jacdac', e.g: JacdacButton not the same as the client specific name 'button1'. */ name: string, /** A unique shortened name starting with 'JD', e.g: JDB. This is useful is you want to transmit over radio. */ @@ -28,9 +26,12 @@ namespace sensors { */ stateFormat: string } - } - + type SimpleSensorsMap = { + /** This service class key is in decimal, since that's what jacdac.SimpleSensorClient.serviceClass returns */ + [serviceClass: number]: SimpleSensorMetaData + } + /** * Supported Jacdac Simple Sensors. * Maps a Jacdac sensor name to its service class in decimal. @@ -517,5 +518,14 @@ namespace sensors { return Object.keys(__jacdacSensorMap).map(k => parseInt(k)) as JacdacSensorSrvs[]; } + export function getSimpleSensorMetaData(srv: JacdacSensorSrvs): SimpleSensorMetaData { + const s = __jacdacSensorMap[srv]; + + if (!s) + throw "Error: Invalid serviceClass: that Jacdac Client is not supported. Please use a SimpleSensorClient." + + return s; + } + export const numberOfSupportedJacdacSensors: number = Object.keys(__jacdacSensorMap).length; } diff --git a/ui/.github/workflows/makecode.yml b/ui/.github/workflows/makecode.yml new file mode 100644 index 0000000..9497b8f --- /dev/null +++ b/ui/.github/workflows/makecode.yml @@ -0,0 +1,13 @@ +name: MakeCode Build +on: + push: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + - run: npx makecode diff --git a/ui/.prettierrc b/ui/.prettierrc new file mode 100644 index 0000000..292912b --- /dev/null +++ b/ui/.prettierrc @@ -0,0 +1 @@ +{"arrowParens":"avoid","semi":false,"tabWidth":4} \ No newline at end of file diff --git a/ui/main.ts b/ui/main.ts new file mode 100644 index 0000000..64310af --- /dev/null +++ b/ui/main.ts @@ -0,0 +1,19 @@ +jacdac.firmwareVersion = jacdac.VERSION +console.log(`starting servers...`) +servers.start({ + // accelerometer: true, + lightLevel: true, + // temperature: true, +}) + +// now, we want to register a client for the light level service +// and create a dashboard widget for it + +const jdLightLevelClient = new jacdac.SimpleSensorClient(sensors.JacdacSensorSrvs.LightLevel, "ll1", ); + + +const lightLevelSensor = sensors.wrapJacdacSensor(jdLightLevelClient) + + + + diff --git a/ui/mkc.json b/ui/mkc.json new file mode 100644 index 0000000..20ce172 --- /dev/null +++ b/ui/mkc.json @@ -0,0 +1,4 @@ +{ + "targetWebsite": "https://makecode.microbit.org/beta", + "links": {} +} \ No newline at end of file diff --git a/ui/pxt.json b/ui/pxt.json new file mode 100644 index 0000000..ed34109 --- /dev/null +++ b/ui/pxt.json @@ -0,0 +1,19 @@ +{ + "name": "my-project", + "version": "0.0.22", + "files": [ + "main.ts" + ], + "supportedTargets": [ + "microbit" + ], + "dependencies": { + "core": "*", + "radio": "*", + "microphone": "*", + "microgui": "github:microbit-apps/microgui#v0.0.41", + "sensors": "file:../", + "jacdac-microbit": "github:jacdac/pxt-jacdac/devices/microbit" + }, + "testDependencies": {} +} diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 0000000..1f12644 --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES5", + "noImplicitAny": true, + "outDir": "built", + "rootDir": "." + }, + "include": [ + "**/*.ts" + ], + "exclude": [ + "built/**", + "pxt_modules/**/*test.ts" + ] +} \ No newline at end of file From 18b7510f64ec96f2c43c538ab29608a798ec6875 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Mon, 30 Mar 2026 17:32:54 -0700 Subject: [PATCH 03/16] print ouf info --- ui/main.ts | 31 ++++++++++++++++++++++++++++--- ui/pxt.json | 2 +- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 64310af..101eba1 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -9,11 +9,36 @@ servers.start({ // now, we want to register a client for the light level service // and create a dashboard widget for it -const jdLightLevelClient = new jacdac.SimpleSensorClient(sensors.JacdacSensorSrvs.LightLevel, "ll1", ); - - +const lightLevelData = sensors.getSimpleSensorMetaData(sensors.JacdacSensorSrvs.LightLevel); +const jdLightLevelClient = new jacdac.SimpleSensorClient(sensors.JacdacSensorSrvs.LightLevel, "ll1", lightLevelData.stateFormat); const lightLevelSensor = sensors.wrapJacdacSensor(jdLightLevelClient) +// wait until the client is bound then create the widget +jdLightLevelClient.onStateChanged(() => { + if (jdLightLevelClient.isConnected()) { + console.log("JD Light Level client connected, creating widget") + // microgui.createWidgetForSensor(lightLevelSensor, "light level") + } +}) +namespace microgui { + control.singleSimulator(); +const app = new App(); +const simpleTextComponent = new TextBox({ + alignment: GUIComponentAlignment.BOT, + isActive: false, + title: lightLevelSensor.name, + text: [`min: ${lightLevelSensor.minimum.toString()}`, + `max: ${lightLevelSensor.maximum.toString()}`, + `val: ${lightLevelSensor.reading.toString()}`], // optional arg + colour: 6, // optional arg + xScaling: 1.7, // optional arg + }) + + const gcs = new GUIComponentScene({ app, components: [simpleTextComponent] }) + app.popScene() + app.pushScene(gcs) + +} diff --git a/ui/pxt.json b/ui/pxt.json index ed34109..3416680 100644 --- a/ui/pxt.json +++ b/ui/pxt.json @@ -12,7 +12,7 @@ "radio": "*", "microphone": "*", "microgui": "github:microbit-apps/microgui#v0.0.41", - "sensors": "file:../", + "sensors": "github:microbit-apps/sensors#198ddbe594c953d81499e164dce58d457dfda325", "jacdac-microbit": "github:jacdac/pxt-jacdac/devices/microbit" }, "testDependencies": {} From 133b58b03845101cc9242f96d9eae27c6c771302 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Mon, 30 Mar 2026 19:10:40 -0700 Subject: [PATCH 04/16] working! --- ui/main.ts | 73 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 101eba1..84ecfd1 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -1,44 +1,59 @@ -jacdac.firmwareVersion = jacdac.VERSION -console.log(`starting servers...`) -servers.start({ - // accelerometer: true, - lightLevel: true, - // temperature: true, -}) - -// now, we want to register a client for the light level service -// and create a dashboard widget for it +jacdac.start() const lightLevelData = sensors.getSimpleSensorMetaData(sensors.JacdacSensorSrvs.LightLevel); const jdLightLevelClient = new jacdac.SimpleSensorClient(sensors.JacdacSensorSrvs.LightLevel, "ll1", lightLevelData.stateFormat); const lightLevelSensor = sensors.wrapJacdacSensor(jdLightLevelClient) +input.lightLevel() // wait until the client is bound then create the widget +input.onButtonPressed(Button.A, () => { + console.log(`starting servers...`) + servers.start({ + // accelerometer: true, + lightLevel: true, + // temperature: true, + forceSimulators: true + }) +}) + +namespace microgui { + +control.singleSimulator(); +const app = new App(); + +function getTextComponent() { + const simpleTextComponent = new TextBox({ + alignment: GUIComponentAlignment.CENTRE, + isActive: false, + title: lightLevelSensor.name, + text: [`min: ${lightLevelSensor.minimum.toString()}`, + `max: ${lightLevelSensor.maximum.toString()}`, + `val: ${Math.roundWithPrecision(lightLevelSensor.reading,3).toString()}`], // optional arg + colour: 6, // optional arg + xScaling: 1.7, // optional arg + }) + return simpleTextComponent +} + +let gcs: GUIComponentScene = undefined + jdLightLevelClient.onStateChanged(() => { if (jdLightLevelClient.isConnected()) { - console.log("JD Light Level client connected, creating widget") - // microgui.createWidgetForSensor(lightLevelSensor, "light level") + if (!gcs) { + console.log("JD Light Level client connected, creating widget") + gcs = new GUIComponentScene({ app, components: [getTextComponent()] }) + app.popScene() + app.pushScene(gcs) + } } }) -namespace microgui { - control.singleSimulator(); -const app = new App(); -const simpleTextComponent = new TextBox({ - alignment: GUIComponentAlignment.BOT, - isActive: false, - title: lightLevelSensor.name, - text: [`min: ${lightLevelSensor.minimum.toString()}`, - `max: ${lightLevelSensor.maximum.toString()}`, - `val: ${lightLevelSensor.reading.toString()}`], // optional arg - colour: 6, // optional arg - xScaling: 1.7, // optional arg - }) - - const gcs = new GUIComponentScene({ app, components: [simpleTextComponent] }) - app.popScene() - app.pushScene(gcs) +basic.forever(() => { + if (gcs) { + gcs.deactivate() + } +}) } From 37c9c58181bf49d6b15693ca58b188ca73de9ffe Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Mon, 30 Mar 2026 19:14:41 -0700 Subject: [PATCH 05/16] hack to get things working --- ui/main.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/main.ts b/ui/main.ts index 84ecfd1..41830f0 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -51,7 +51,9 @@ jdLightLevelClient.onStateChanged(() => { basic.forever(() => { if (gcs) { - gcs.deactivate() + gcs = new GUIComponentScene({ app, components: [getTextComponent()] }) + app.popScene() + app.pushScene(gcs) } }) From 846703c24622f3875eac39e75fc2bcdea9470f46 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Tue, 31 Mar 2026 14:53:42 -0700 Subject: [PATCH 06/16] debugging --- ui/main.ts | 65 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 41830f0..417c81f 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -1,8 +1,12 @@ jacdac.start() -const lightLevelData = sensors.getSimpleSensorMetaData(sensors.JacdacSensorSrvs.LightLevel); -const jdLightLevelClient = new jacdac.SimpleSensorClient(sensors.JacdacSensorSrvs.LightLevel, "ll1", lightLevelData.stateFormat); -const lightLevelSensor = sensors.wrapJacdacSensor(jdLightLevelClient) +// TODO: +// 1. make generic so that any sensor can be used, not just light level +// 2. make it so that the widget updates on sensor change, not just on a loop +// 3. make it so that the widget only updates the text, not the whole thing +// 4. handle multiple sensors being added at same time +// 5. need a way for user to dismiss the widget, maybe a button on the widget itself? +// 6. need to handle user inputs (buttons, etc), user outputs, and actuators (motors, etc) in general, not just sensors input.lightLevel() @@ -17,45 +21,68 @@ input.onButtonPressed(Button.A, () => { }) }) + namespace microgui { control.singleSimulator(); const app = new App(); -function getTextComponent() { +function getTextComponent(sensor: sensors.Sensor) { const simpleTextComponent = new TextBox({ alignment: GUIComponentAlignment.CENTRE, isActive: false, - title: lightLevelSensor.name, - text: [`min: ${lightLevelSensor.minimum.toString()}`, - `max: ${lightLevelSensor.maximum.toString()}`, - `val: ${Math.roundWithPrecision(lightLevelSensor.reading,3).toString()}`], // optional arg + title: sensor.name, + text: [`min: ${sensor.minimum.toString()}`, + `max: ${sensor.maximum.toString()}`, + `val: ${Math.roundWithPrecision(sensor.reading,3).toString()} ${sensor.unitName}`], // optional arg colour: 6, // optional arg xScaling: 1.7, // optional arg }) return simpleTextComponent } -let gcs: GUIComponentScene = undefined +let sensorRoleCount = 0 +let sensorsToProcess: sensors.Sensor[] = [] +let devicesServiceFound: string[] = [] + +jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { + console.log(`device connected: ${dev.deviceId} with ${dev.serviceClassLength} services `) -jdLightLevelClient.onStateChanged(() => { - if (jdLightLevelClient.isConnected()) { - if (!gcs) { - console.log("JD Light Level client connected, creating widget") - gcs = new GUIComponentScene({ app, components: [getTextComponent()] }) - app.popScene() - app.pushScene(gcs) + for (let i = 1; i < dev.serviceClassLength; i++) { + const serviceClass = dev.serviceClassAt(i) // skip service class 0 which is usually the control service + // print it as hex to make it easier to read + const devService = `${dev.deviceId}:${serviceClass}` + if (devicesServiceFound.find(d => d === devService)) { + console.log(`already found ${devService}, skipping...`) + return + } + devicesServiceFound.push(devService) + + console.log(`checking service class ${serviceClass} for device ${dev.deviceId}`) + try { + const sensor = sensors.getJacdacSensor(serviceClass, `sensor${sensorRoleCount}`) + sensorsToProcess.push(sensor) + sensorRoleCount++ + } catch (e) { + console.log(`error creating widget for device ${dev.deviceId}: ${e}`) } } }) +let currentSensor: sensors.Sensor = undefined +let gcs : GUIComponentScene = undefined + basic.forever(() => { - if (gcs) { - gcs = new GUIComponentScene({ app, components: [getTextComponent()] }) + if (sensorsToProcess.length > 0 && !gcs) { + console.log(`processing sensor ${sensorsToProcess[0].name}...`) + currentSensor = sensorsToProcess.pop() + } + if (currentSensor) { + const textComponent = getTextComponent(currentSensor) app.popScene() + gcs = new GUIComponentScene({ app, components: [textComponent] }) app.pushScene(gcs) } }) } - From 67ee392f77fd23697ed0b2fc6a9a72e449c9e573 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Tue, 31 Mar 2026 15:20:08 -0700 Subject: [PATCH 07/16] fix up error --- ui/main.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 417c81f..c93f6db 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -47,18 +47,14 @@ let devicesServiceFound: string[] = [] jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { console.log(`device connected: ${dev.deviceId} with ${dev.serviceClassLength} services `) - for (let i = 1; i < dev.serviceClassLength; i++) { const serviceClass = dev.serviceClassAt(i) // skip service class 0 which is usually the control service // print it as hex to make it easier to read const devService = `${dev.deviceId}:${serviceClass}` if (devicesServiceFound.find(d => d === devService)) { - console.log(`already found ${devService}, skipping...`) - return + continue } devicesServiceFound.push(devService) - - console.log(`checking service class ${serviceClass} for device ${dev.deviceId}`) try { const sensor = sensors.getJacdacSensor(serviceClass, `sensor${sensorRoleCount}`) sensorsToProcess.push(sensor) From cc978693ad643c189dd01d3e8209ff605c5d1f69 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Tue, 31 Mar 2026 15:23:48 -0700 Subject: [PATCH 08/16] start on multiple --- ui/main.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index c93f6db..9b272c7 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -9,6 +9,7 @@ jacdac.start() // 6. need to handle user inputs (buttons, etc), user outputs, and actuators (motors, etc) in general, not just sensors input.lightLevel() +input.temperature() // wait until the client is bound then create the widget input.onButtonPressed(Button.A, () => { @@ -16,7 +17,7 @@ input.onButtonPressed(Button.A, () => { servers.start({ // accelerometer: true, lightLevel: true, - // temperature: true, + temperature: true, forceSimulators: true }) }) @@ -34,7 +35,8 @@ function getTextComponent(sensor: sensors.Sensor) { title: sensor.name, text: [`min: ${sensor.minimum.toString()}`, `max: ${sensor.maximum.toString()}`, - `val: ${Math.roundWithPrecision(sensor.reading,3).toString()} ${sensor.unitName}`], // optional arg + `val: ${Math.roundWithPrecision(sensor.reading,3)}`, + `units: ${sensor.unitName}` ], // optional arg colour: 6, // optional arg xScaling: 1.7, // optional arg }) @@ -48,8 +50,7 @@ let devicesServiceFound: string[] = [] jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { console.log(`device connected: ${dev.deviceId} with ${dev.serviceClassLength} services `) for (let i = 1; i < dev.serviceClassLength; i++) { - const serviceClass = dev.serviceClassAt(i) // skip service class 0 which is usually the control service - // print it as hex to make it easier to read + const serviceClass = dev.serviceClassAt(i) const devService = `${dev.deviceId}:${serviceClass}` if (devicesServiceFound.find(d => d === devService)) { continue From 19310cf6c86b9d030874be24875d3b0dfdbba17f Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Tue, 31 Mar 2026 15:37:42 -0700 Subject: [PATCH 09/16] dismiss --- ui/main.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 9b272c7..b56f427 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -4,7 +4,6 @@ jacdac.start() // 1. make generic so that any sensor can be used, not just light level // 2. make it so that the widget updates on sensor change, not just on a loop // 3. make it so that the widget only updates the text, not the whole thing -// 4. handle multiple sensors being added at same time // 5. need a way for user to dismiss the widget, maybe a button on the widget itself? // 6. need to handle user inputs (buttons, etc), user outputs, and actuators (motors, etc) in general, not just sensors @@ -24,6 +23,7 @@ input.onButtonPressed(Button.A, () => { namespace microgui { +import Button = user_interface_base.Button control.singleSimulator(); const app = new App(); @@ -69,6 +69,17 @@ jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { let currentSensor: sensors.Sensor = undefined let gcs : GUIComponentScene = undefined +context.onEvent(ControllerButtonEvent.Pressed, controller.B.id, + () => { + if (gcs) { + app.popScene() + gcs = undefined + currentSensor = undefined + } + } +) + + basic.forever(() => { if (sensorsToProcess.length > 0 && !gcs) { console.log(`processing sensor ${sensorsToProcess[0].name}...`) @@ -77,7 +88,9 @@ basic.forever(() => { if (currentSensor) { const textComponent = getTextComponent(currentSensor) app.popScene() - gcs = new GUIComponentScene({ app, components: [textComponent] }) + gcs = new GUIComponentScene({ app, + components: [textComponent] }) + gcs.showAllComponents() app.pushScene(gcs) } }) From b25cb3e2a8eece123db62778e105df1a0a9ae563 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Wed, 1 Apr 2026 08:34:37 -0700 Subject: [PATCH 10/16] start role management --- ui/main.ts | 56 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index b56f427..9cacffa 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -1,7 +1,7 @@ jacdac.start() // TODO: -// 1. make generic so that any sensor can be used, not just light level +// 1. handle role management, per service class, and map to device id, service index // 2. make it so that the widget updates on sensor change, not just on a loop // 3. make it so that the widget only updates the text, not the whole thing // 5. need a way for user to dismiss the widget, maybe a button on the widget itself? @@ -47,6 +47,47 @@ let sensorRoleCount = 0 let sensorsToProcess: sensors.Sensor[] = [] let devicesServiceFound: string[] = [] +type RoleInfo = { + rName: string, + count: number, + deviceId: string, + serviceIndex: number +} + +type ServiceToRoleInfo = { + [serviceClass: number]: RoleInfo[] +} + +type ServiceToCount = { + [serviceClass: number]: number +} + +const serviceToRoleInfo: ServiceToRoleInfo = {} +const serviceToCount: ServiceToCount = {} + +function getRoleInfoForService(serviceClass: number, deviceId: string, serviceIndex: number): RoleInfo { + try { + const sensorData = sensors.getSimpleSensorMetaData(serviceClass) + if (!serviceToRoleInfo[serviceClass]) { + serviceToRoleInfo[serviceClass] = [] + serviceToCount[serviceClass] = 0 + } + const rName = sensorData.rName + const roleInfo = { + rName, + count: serviceToCount[serviceClass], + deviceId, + serviceIndex + } + serviceToRoleInfo[serviceClass].push(roleInfo) + serviceToCount[serviceClass]++ + return roleInfo + } catch (e) { + return undefined + } + +} + jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { console.log(`device connected: ${dev.deviceId} with ${dev.serviceClassLength} services `) for (let i = 1; i < dev.serviceClassLength; i++) { @@ -56,13 +97,14 @@ jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { continue } devicesServiceFound.push(devService) - try { - const sensor = sensors.getJacdacSensor(serviceClass, `sensor${sensorRoleCount}`) - sensorsToProcess.push(sensor) - sensorRoleCount++ - } catch (e) { - console.log(`error creating widget for device ${dev.deviceId}: ${e}`) + const roleInfo = getRoleInfoForService(serviceClass, dev.deviceId, i) + if (!roleInfo) { + console.log(`service class ${serviceClass} not recognized as a sensor, skipping`) + continue } + const sensor = sensors.getJacdacSensor(serviceClass, roleInfo.rName) + sensorsToProcess.push(sensor) + sensorRoleCount++ } }) From 65692d61d3c9f44ed6fa124c0c4bc8e5fab48a4a Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Wed, 1 Apr 2026 08:47:54 -0700 Subject: [PATCH 11/16] finish up roles --- ui/main.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 9cacffa..1e6ceab 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -28,15 +28,17 @@ import Button = user_interface_base.Button control.singleSimulator(); const app = new App(); -function getTextComponent(sensor: sensors.Sensor) { +function getTextComponent(role: string, sensor: sensors.Sensor) { const simpleTextComponent = new TextBox({ alignment: GUIComponentAlignment.CENTRE, isActive: false, title: sensor.name, - text: [`min: ${sensor.minimum.toString()}`, + text: [ + `${Math.roundWithPrecision(sensor.reading,3)} ${sensor.unitName}`, + `min: ${sensor.minimum.toString()}`, `max: ${sensor.maximum.toString()}`, - `val: ${Math.roundWithPrecision(sensor.reading,3)}`, - `units: ${sensor.unitName}` ], // optional arg + `role: ${role}` + ], // optional arg colour: 6, // optional arg xScaling: 1.7, // optional arg }) @@ -44,12 +46,11 @@ function getTextComponent(sensor: sensors.Sensor) { } let sensorRoleCount = 0 -let sensorsToProcess: sensors.Sensor[] = [] +let sensorsToProcess: string[] = [] let devicesServiceFound: string[] = [] type RoleInfo = { rName: string, - count: number, deviceId: string, serviceIndex: number } @@ -64,6 +65,7 @@ type ServiceToCount = { const serviceToRoleInfo: ServiceToRoleInfo = {} const serviceToCount: ServiceToCount = {} +const roleToSensor: { [rName: string]: sensors.Sensor } = {} function getRoleInfoForService(serviceClass: number, deviceId: string, serviceIndex: number): RoleInfo { try { @@ -72,10 +74,9 @@ function getRoleInfoForService(serviceClass: number, deviceId: string, serviceIn serviceToRoleInfo[serviceClass] = [] serviceToCount[serviceClass] = 0 } - const rName = sensorData.rName + const rName = `${sensorData.rName}${serviceToCount[serviceClass]}` const roleInfo = { rName, - count: serviceToCount[serviceClass], deviceId, serviceIndex } @@ -103,12 +104,13 @@ jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { continue } const sensor = sensors.getJacdacSensor(serviceClass, roleInfo.rName) - sensorsToProcess.push(sensor) + roleToSensor[roleInfo.rName] = sensor + sensorsToProcess.push(roleInfo.rName) sensorRoleCount++ } }) -let currentSensor: sensors.Sensor = undefined +let currentRole: string = undefined let gcs : GUIComponentScene = undefined context.onEvent(ControllerButtonEvent.Pressed, controller.B.id, @@ -116,7 +118,7 @@ context.onEvent(ControllerButtonEvent.Pressed, controller.B.id, if (gcs) { app.popScene() gcs = undefined - currentSensor = undefined + currentRole = undefined } } ) @@ -124,11 +126,11 @@ context.onEvent(ControllerButtonEvent.Pressed, controller.B.id, basic.forever(() => { if (sensorsToProcess.length > 0 && !gcs) { - console.log(`processing sensor ${sensorsToProcess[0].name}...`) - currentSensor = sensorsToProcess.pop() + console.log(`processing sensor ${sensorsToProcess[0]}...`) + currentRole = sensorsToProcess.pop() } - if (currentSensor) { - const textComponent = getTextComponent(currentSensor) + if (currentRole) { + const textComponent = getTextComponent(currentRole, roleToSensor[currentRole]) app.popScene() gcs = new GUIComponentScene({ app, components: [textComponent] }) From 2b0a0ac5ed10596e69854f2767969d7d49f8cac6 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Wed, 1 Apr 2026 09:16:39 -0700 Subject: [PATCH 12/16] multiples work --- ui/main.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 1e6ceab..c7772d7 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -14,7 +14,10 @@ input.temperature() input.onButtonPressed(Button.A, () => { console.log(`starting servers...`) servers.start({ - // accelerometer: true, + touchP0: true, + touchP1: true, + touchP2: true, + soundLevel: true, lightLevel: true, temperature: true, forceSimulators: true @@ -93,7 +96,7 @@ jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { console.log(`device connected: ${dev.deviceId} with ${dev.serviceClassLength} services `) for (let i = 1; i < dev.serviceClassLength; i++) { const serviceClass = dev.serviceClassAt(i) - const devService = `${dev.deviceId}:${serviceClass}` + const devService = `${dev.deviceId}:${serviceClass}:${i}` if (devicesServiceFound.find(d => d === devService)) { continue } From 3744e00690434cf03595922a0f0c878c7a65eb2e Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Wed, 1 Apr 2026 09:19:17 -0700 Subject: [PATCH 13/16] update comments --- ui/main.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index c7772d7..2e89f92 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -1,11 +1,12 @@ jacdac.start() // TODO: -// 1. handle role management, per service class, and map to device id, service index -// 2. make it so that the widget updates on sensor change, not just on a loop -// 3. make it so that the widget only updates the text, not the whole thing -// 5. need a way for user to dismiss the widget, maybe a button on the widget itself? -// 6. need to handle user inputs (buttons, etc), user outputs, and actuators (motors, etc) in general, not just sensors +// - make it so that the widget updates on sensor change, not just on a loop +// - make it so that the widget only updates the text, not the whole thing +// - need a way for user to dismiss the widget, maybe a button on the widget itself? +// - need to handle user inputs (buttons, etc), user outputs, and actuators (motors, etc) in general, not just sensors +// - what happens when devices disconnect? optional dialog for this? +// - storing the RoleInfo in settings input.lightLevel() input.temperature() From a279df5a4dee8510576d6bc150d0cb1e023ec816 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Thu, 2 Apr 2026 07:58:51 -0700 Subject: [PATCH 14/16] remove dead code --- ui/main.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index 2e89f92..cdcbb64 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -49,7 +49,6 @@ function getTextComponent(role: string, sensor: sensors.Sensor) { return simpleTextComponent } -let sensorRoleCount = 0 let sensorsToProcess: string[] = [] let devicesServiceFound: string[] = [] @@ -110,7 +109,6 @@ jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { const sensor = sensors.getJacdacSensor(serviceClass, roleInfo.rName) roleToSensor[roleInfo.rName] = sensor sensorsToProcess.push(roleInfo.rName) - sensorRoleCount++ } }) From c338a50a95b470556db443c045007937b11a0a41 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Thu, 2 Apr 2026 08:52:09 -0700 Subject: [PATCH 15/16] update typing --- ui/main.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ui/main.ts b/ui/main.ts index cdcbb64..44235bd 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -1,6 +1,7 @@ jacdac.start() // TODO: +// efficiency // - make it so that the widget updates on sensor change, not just on a loop // - make it so that the widget only updates the text, not the whole thing // - need a way for user to dismiss the widget, maybe a button on the widget itself? @@ -70,7 +71,7 @@ const serviceToRoleInfo: ServiceToRoleInfo = {} const serviceToCount: ServiceToCount = {} const roleToSensor: { [rName: string]: sensors.Sensor } = {} -function getRoleInfoForService(serviceClass: number, deviceId: string, serviceIndex: number): RoleInfo { +function getRoleInfoForService(serviceClass: number, deviceId: string, serviceIndex: number) : RoleInfo | undefined{ try { const sensorData = sensors.getSimpleSensorMetaData(serviceClass) if (!serviceToRoleInfo[serviceClass]) { @@ -112,8 +113,8 @@ jacdac.bus.on(jacdac.DEVICE_ANNOUNCE, (dev: jacdac.Device) => { } }) -let currentRole: string = undefined -let gcs : GUIComponentScene = undefined +let currentRole: string | undefined= undefined +let gcs : GUIComponentScene | undefined = undefined context.onEvent(ControllerButtonEvent.Pressed, controller.B.id, () => { From 925d0f52a44c9ced0c04f8eafa428b489ca25eb9 Mon Sep 17 00:00:00 2001 From: Thomas Ball Date: Thu, 2 Apr 2026 11:44:18 -0700 Subject: [PATCH 16/16] pretty print % --- ui/main.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/main.ts b/ui/main.ts index 44235bd..bd37ba3 100644 --- a/ui/main.ts +++ b/ui/main.ts @@ -33,13 +33,20 @@ import Button = user_interface_base.Button control.singleSimulator(); const app = new App(); +function convertToPercent(sensor: sensors.Sensor) { + if (sensor.unitName === "percent") { + return Math.roundWithPrecision(sensor.reading*100, 2)+" %" + } + return `${Math.roundWithPrecision(sensor.reading,3)} ${sensor.unitName}` +} + function getTextComponent(role: string, sensor: sensors.Sensor) { const simpleTextComponent = new TextBox({ alignment: GUIComponentAlignment.CENTRE, isActive: false, title: sensor.name, text: [ - `${Math.roundWithPrecision(sensor.reading,3)} ${sensor.unitName}`, + convertToPercent(sensor), `min: ${sensor.minimum.toString()}`, `max: ${sensor.maximum.toString()}`, `role: ${role}`