Gyroscope Accelerometer Magnetometer Baseline Inference Telemetry
Espruino Puck.js firmware for 9-DoF IMU data collection and BLE streaming.
GAMBIT is the active device firmware that collects sensor data from the Puck.js hardware and streams it via BLE to the web UI for visualization and storage.
graph LR
subgraph "Puck.js Device"
IMU[9-DoF IMU]
SENSORS[Light/Cap/Temp]
BTN[Button]
NFC[NFC]
BLE[BLE Radio]
end
subgraph "Data Flow"
IMU --> |50Hz| TELEMETRY[Telemetry Object]
SENSORS --> TELEMETRY
BTN --> |trigger| CAPTURE[30s Capture]
CAPTURE --> TELEMETRY
NFC --> |tap| PAIR[Pair to Web UI]
end
TELEMETRY --> |JSON| BLE
BLE --> |Nordic UART| WEBUI[Web UI]
Device: Espruino Puck.js v2
Each telemetry packet contains:
{
// Accelerometer (raw values, device coordinates)
ax: number, // X-axis acceleration
ay: number, // Y-axis acceleration
az: number, // Z-axis acceleration
// Gyroscope (raw values, degrees/second)
gx: number, // X-axis angular velocity
gy: number, // Y-axis angular velocity
gz: number, // Z-axis angular velocity
// Magnetometer (raw values, microtesla)
mx: number, // X-axis magnetic field
my: number, // Y-axis magnetic field
mz: number, // Z-axis magnetic field
// Additional sensors
l: number, // Light sensor (0-1)
t: number, // Temperature (magnetometer temp)
c: number, // Capacitive sense value
// State
s: number, // State (0=off, 1=on)
n: number, // Button press count
b: number // Battery percentage
}
sequenceDiagram
participant User
participant Button
participant Firmware
participant BLE
participant WebUI
User->>Button: Press
Button->>Firmware: setWatch trigger
Firmware->>Firmware: Toggle state
Firmware->>Firmware: Start 30s interval (50Hz)
loop Every 20ms for 30 seconds
Firmware->>Firmware: Read IMU sensors
Firmware->>Firmware: Read additional sensors
Firmware->>Firmware: Build telemetry JSON
Firmware->>BLE: console.log(JSON)
BLE->>WebUI: Nordic UART data
end
Firmware->>Firmware: Clear interval
The emit() function reads all sensors and constructs a telemetry object:
function emit() {
var mag = Puck.mag() // Magnetometer
var accel = Puck.accel() // Accelerometer + Gyroscope
telemetry.ax = accel.acc.x
// ... all sensor assignments
console.log("\nGAMBIT" + JSON.stringify(telemetry))
return telemetry;
}
Data collection runs for 30-second bursts to conserve battery:
function getData() {
if (interval) return emit();
setTimeout(function(){
clearInterval(interval)
interval = null
}, 30000) // 30 second capture
interval = setInterval(emit, 50) // 50Hz = 20ms
return(emit())
}
Tapping a phone to the device triggers the WebBLE pairing flow:
NRF.nfcURL("webble://christopherdebeer.github.io/simcap/src/web/GAMBIT/");
| LED | Color | Meaning |
|---|---|---|
| LED1 | Red | (unused in current impl) |
| LED2 | Green | NFC field detected |
| LED3 | Blue | Button press / state toggle |
stateDiagram-v2
[*] --> Idle: Power on
Idle --> Capturing: Button press (odd)
Capturing --> Idle: 30s timeout
Capturing --> Idle: Button press (even)
Idle: state = 0
Idle: LED3 short flash
Capturing: state = 1
Capturing: LED3 long flash
Capturing: 50Hz data emission
Transport: BLE Nordic UART Service (NUS)
6e400001-b5a3-f393-e0a9-e50e24dcca9e6e400002-b5a3-f393-e0a9-e50e24dcca9e6e400003-b5a3-f393-e0a9-e50e24dcca9eData Format: Newline-delimited JSON with GAMBIT prefix
\nGAMBIT{"ax":-464,"ay":-7949,"az":-2282,...}
app.jsThe following features are implemented but commented out:
These can be re-enabled by uncommenting the relevant code blocks.
Baseline data collected with this firmware is stored in:
data/GAMBIT/*.json
See the Web UI README for data collection workflow.