Skip to content
20 changes: 15 additions & 5 deletions jacdacSensorMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
2 changes: 1 addition & 1 deletion pxt.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sensors",
"version": "0.0.21",
"version": "0.0.22",
"dependencies": {
"core": "*",
"radio": "*",
Expand Down
13 changes: 13 additions & 0 deletions ui/.github/workflows/makecode.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions ui/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"arrowParens":"avoid","semi":false,"tabWidth":4}
152 changes: 152 additions & 0 deletions ui/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
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?
// - 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()

// wait until the client is bound then create the widget
input.onButtonPressed(Button.A, () => {
console.log(`starting servers...`)
servers.start({
touchP0: true,
touchP1: true,
touchP2: true,
soundLevel: true,
lightLevel: true,
temperature: true,
forceSimulators: true
})
})


namespace microgui {
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: [
convertToPercent(sensor),
`min: ${sensor.minimum.toString()}`,
`max: ${sensor.maximum.toString()}`,
`role: ${role}`
], // optional arg
colour: 6, // optional arg
xScaling: 1.7, // optional arg
})
return simpleTextComponent
}

let sensorsToProcess: string[] = []
let devicesServiceFound: string[] = []

type RoleInfo = {
rName: string,
deviceId: string,
serviceIndex: number
}

type ServiceToRoleInfo = {
[serviceClass: number]: RoleInfo[]
}

type ServiceToCount = {
[serviceClass: number]: number
}

const serviceToRoleInfo: ServiceToRoleInfo = {}
const serviceToCount: ServiceToCount = {}
const roleToSensor: { [rName: string]: sensors.Sensor } = {}

function getRoleInfoForService(serviceClass: number, deviceId: string, serviceIndex: number) : RoleInfo | undefined{
try {
const sensorData = sensors.getSimpleSensorMetaData(serviceClass)
if (!serviceToRoleInfo[serviceClass]) {
serviceToRoleInfo[serviceClass] = []
serviceToCount[serviceClass] = 0
}
const rName = `${sensorData.rName}${serviceToCount[serviceClass]}`
const roleInfo = {
rName,
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++) {
const serviceClass = dev.serviceClassAt(i)
const devService = `${dev.deviceId}:${serviceClass}:${i}`
if (devicesServiceFound.find(d => d === devService)) {
continue
}
devicesServiceFound.push(devService)
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)
roleToSensor[roleInfo.rName] = sensor
sensorsToProcess.push(roleInfo.rName)
}
})

let currentRole: string | undefined= undefined
let gcs : GUIComponentScene | undefined = undefined

context.onEvent(ControllerButtonEvent.Pressed, controller.B.id,
() => {
if (gcs) {
app.popScene()
gcs = undefined
currentRole = undefined
}
}
)


basic.forever(() => {
if (sensorsToProcess.length > 0 && !gcs) {
console.log(`processing sensor ${sensorsToProcess[0]}...`)
currentRole = sensorsToProcess.pop()
}
if (currentRole) {
const textComponent = getTextComponent(currentRole, roleToSensor[currentRole])
app.popScene()
gcs = new GUIComponentScene({ app,
components: [textComponent] })
gcs.showAllComponents()
app.pushScene(gcs)
}
})

}
4 changes: 4 additions & 0 deletions ui/mkc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"targetWebsite": "https://makecode.microbit.org/beta",
"links": {}
}
19 changes: 19 additions & 0 deletions ui/pxt.json
Original file line number Diff line number Diff line change
@@ -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": "github:microbit-apps/sensors#198ddbe594c953d81499e164dce58d457dfda325",
"jacdac-microbit": "github:jacdac/pxt-jacdac/devices/microbit"
},
"testDependencies": {}
}
15 changes: 15 additions & 0 deletions ui/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES5",
"noImplicitAny": true,
"outDir": "built",
"rootDir": "."
},
"include": [
"**/*.ts"
],
"exclude": [
"built/**",
"pxt_modules/**/*test.ts"
]
}