Skip to content

kl527/bangle_firmware_biosignal_research

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

Bangle.js 2 Biosignal Research Firmware

Biosignal collection firmware for the Bangle.js 2 smartwatch. Captures PPG (heart rate), accelerometer, and temperature data in time-synchronized chunks, stores them on-device, and transmits to the phone via BLE.

Project Structure

bangle/
├── README.md                    # This file
├── biosignalBangle.js           # Production bundle (upload this to watch)
└── segmentedBangle/             # Modular source code
    ├── package.json
    ├── src/
    │   ├── 00_prelude.js        # Globals, CONFIG, constants
    │   ├── 01_logging.js        # Log helpers, debug persistence
    │   ├── 02_state.js          # MODE enum, state object, queue
    │   ├── 03_buffers.js        # Binary buffer allocation/writers
    │   ├── 04_sensors.js        # HRM, accel, temp handlers
    │   ├── 05_window.js         # Recording lifecycle
    │   ├── 06_ui.js             # Display, menus, buttons
    │   ├── 07_ble.js            # BLE protocol, commands, transfers
    │   └── 08_init.js           # Initialization, event binding
    ├── tools/
    │   └── bundle.js            # Simple concatenation bundler
    └── dist/
        └── biosignal.bundle.js  # Local bundle output

Prerequisites

  • Node.js (v14 or later) - Download
  • Chrome or Edge browser - Required for Web Bluetooth support
  • Bangle.js 2 watch - Buy here

Quick Start

Step 1: Install Dependencies

cd segmentedBangle
npm install

Step 2: Build the Bundle

npm run bundle

This creates biosignalBangle.js in the parent folder.

Step 3: Flash to Watch

  1. Open the Espruino Web IDE in Chrome/Edge (requires Web Bluetooth)
  2. Click the Connect button (yellow plug icon, top-left)
  3. Select Web Bluetooth from the popup
  4. Choose your Bangle.js 2 from the device list and click Pair
  5. Once connected, click the Open File button (folder icon, middle of toolbar)
  6. Navigate to and select biosignalBangle.js
  7. Click the Send to Espruino button (chip/upload icon, top-middle) to flash

The watch will buzz and display "BIOSIG" when the firmware is running.

Step 4: Verify Installation

In the Espruino IDE console (left panel), type:

X({type: "get_status"})

You should see a JSON response with the current watch status.

Hardware Target

  • Device: Bangle.js 2 (BANGLEJS2)
  • Firmware: Espruino 2v28+
  • SoC: Nordic nRF52840 (64MHz ARM Cortex-M4F)
  • RAM: 256KB (JS heap ~30-50KB)
  • Flash: 8MB (user storage ~4MB)
  • BLE: Bluetooth 5.0, Nordic UART Service (NUS)

Data Collection

Recording Mode

The firmware uses continuous recording mode which captures biosignals in 5-minute chunks. When recording starts, the watch continuously collects data and saves each chunk to storage. Chunks are automatically queued for upload to the phone.

Parameter Value
Chunk duration 5 minutes
Trigger BLE command or button long-press

Sensor Configuration

Sensor Rate Sync Interval Binary Format
PPG (HRM) 25 Hz 1 second uint32 timestamp + 25×uint16
Accelerometer 12 Hz 1 second uint32 timestamp + 12×(int16 x,y,z)
Temperature 0.1 Hz 10 seconds uint32 timestamp + float32

Storage Format

Each recording chunk creates 4 files:

biosig_<windowId>_m.json     # Manifest (metadata)
biosig_<windowId>_ppg.bin    # PPG binary data
biosig_<windowId>_acc.bin    # Accelerometer binary data
biosig_<windowId>_tmp.bin    # Temperature binary data

Note: Filenames use short suffixes to stay within Bangle.js Storage's 28-character limit.

BLE Protocol

Communication

  • Service: Nordic UART Service (NUS)
  • Format: JSON commands over BLE UART, newline-delimited
  • Binary: Chunked transfers with JSON header/end markers

Commands (Phone → Watch)

Command Description
start Start recording
stop Stop recording
get_queue Get list of pending uploads
get_manifest Get chunk metadata {windowId}
get_ppg Request PPG binary data {windowId}
get_accel Request accelerometer data {windowId}
get_temp Request temperature data {windowId}
next_chunk Request next binary chunk {dataType, windowId}
binary_ack Acknowledge binary receipt {dataType, windowId}
confirm_upload Delete chunk after successful upload {windowId}
cancel_transfer Cancel active binary transfer
get_status Get current watch status
get_health Get sensor health during recording
delete_all_windows Clear all stored data
dev_console Enable/disable BLE console {enabled}

Binary Transfer Flow

1. Phone sends: {"type":"get_ppg","windowId":"123"}
2. Watch sends: {"type":"ppg","windowId":"123","length":1024,"chunked":true}
3. Watch sends: [binary chunks, auto-pumped]
4. Phone sends: {"type":"binary_ack","dataType":"ppg","windowId":"123"}
5. Watch sends: {"type":"end","dataType":"ppg","windowId":"123"}

BLE Configuration

BLE_CHUNK_SIZE: 244      // MTU 247 - 3 byte header
BLE_MTU: 247             // Negotiated MTU
BLE_CONN_INTERVAL: 7.5-15ms
BLE_TX_POWER: 4          // dBm

Source Modules

00_prelude.js

  • Storage require
  • APP_ID for sensor power management
  • CONFIG object with all tunables
  • Global BLE transfer state variables
  • FILE_SUFFIXES for storage filenames
  • Button detection (HAS_BTN2, MENU_BTN)
  • DEV_MODE detection (hold BTN1 at startup)

01_logging.js

  • LOG enum (ERROR, WARN, INFO, DEBUG)
  • logE(), logW(), logI(), logD() helpers
  • dbg() - Persistent debug log for BLE transfer diagnosis
  • flushDebugLog(), clearDebugLog(), readDebugLog()
  • BLE console management (enableBleConsole, disableBleConsole)
  • applyBleTuning() for MTU/interval negotiation

02_state.js

  • MODE enum (IDLE, CONTINUOUS)
  • state object with all runtime tracking
  • Upload queue functions: getUploadQueue(), addToUploadQueue(), removeFromUploadQueue()

03_buffers.js

  • allocatePpgBuffer(), allocateAccelBuffer(), allocateTempBuffer()
  • Binary writers: writeUint32(), writeInt16(), writeUint16(), writeFloat32()
  • flushPpgBuffer(), flushAccelBuffer(), flushTempBuffer()
  • safeStorageWrite(), safeStorageWriteJSON()

04_sensors.js

  • onHRM(hrm) - PPG event handler
  • onAccel(acc) - Accelerometer event handler
  • sampleTemperature() - Temperature sampling

05_window.js

  • startWindow(mode, duration, label) - Begin recording
  • stopWindow() - End recording, save data
  • saveWindow(endTime) - Persist to storage
  • startContinuous(), stopContinuous(), startContinuousChunk()

06_ui.js

  • updateDisplay() - Main status screen
  • getConnectionStatus() - BLE connection check
  • Button handlers: onButton1Down(), onButton1Up(), onButton2()
  • Menus: showMenu(), showRecordingMenu(), showQueueInfo(), showStorageInfo()
  • Temperature timer: startTempSampling(), stopTempSampling()

07_ble.js

  • sendReady(), sendStatus() - Handshake and status
  • handleBleCommand(cmd) - Command dispatcher
  • sendBleData(type, windowId, data) - Binary transfer
  • sendNextChunk() - Chunked transfer pump
  • handleBinaryAck() - ACK handling
  • cancelBleTransfer() - Transfer cleanup
  • deleteWindow() - File cleanup
  • bleHandler - UART event handler object

08_init.js

  • init() - Main initialization
  • Event handler registration (HRM, accel, buttons)
  • BLE UART setup with JSON command parsing
  • NRF connect/disconnect handlers
  • Calls init() at end of file

Development

DEV_MODE

Hold BTN1 during startup to enable developer mode:

  • Keeps BLE console/REPL available for IDE uploads
  • Enables verbose logging
  • Prevents console disable on BLE connect

Re-enable console via BLE: {"type":"dev_console","enabled":true}

Debug Logging

The debug build includes persistent logging for diagnosing BLE transfer issues:

// In Espruino IDE console:
readDebugLog()   // Print saved debug log
clearDebugLog()  // Clear log buffer and file
flushDebugLog()  // Force write buffer to Storage

Debug log tags:

  • [XFER] - Transfer start events
  • [SNC] - sendNextChunk() execution flow
  • [CANCEL] - Transfer cancellation
  • [CMD] - BLE command handling
  • [ACK] - Binary acknowledgment flow

Testing Commands

From Espruino IDE console:

// Inject BLE commands locally
X({type: "get_status"})
X({type: "start"})
X({type: "stop"})
X({type: "get_queue"})

// Check state
state
getUploadQueue()

// Manual sensor test
Bangle.setHRMPower(1, "test")

Configuration Reference

Key CONFIG values in 00_prelude.js:

CONFIG = {
  // Sampling
  PPG_HZ: 25,
  ACCEL_HZ: 12,
  TEMP_HZ: 0.1,

  // Recording
  CONTINUOUS_CHUNK: 300000,  // 5 minute chunks

  // BLE
  BLE_CHUNKED_MODE: true,
  BLE_CHUNK_SIZE: 244,
  BLE_MTU: 247,

  // Behavior
  COMMAND_ONLY_STARTS: true,  // Require BLE commands to start recording

  // Logging
  DEBUG_LOG: true,
  DEBUG_LEVEL: 2,  // 0=error, 1=warn, 2=info, 3=debug
}

Troubleshooting

No PPG Data

  • Check HRM is enabled: Bangle.setHRMPower(1, "biosig")
  • Verify watch is on wrist with good skin contact
  • Look for "No PPG samples after 5s" warning in logs

BLE Transfer Stalls

  • Check readDebugLog() for [SNC] entries
  • Look for timer_fired not appearing after scheduling_next
  • Phone may need to send cancel_transfer and retry

Storage Full

  • Use get_queue_details to see pending uploads
  • Use delete_all_windows to clear all data
  • Check Storage.getStats() for space info

Button Controls

  • BTN1 long press: Toggle recording (if COMMAND_ONLY_STARTS=false)
  • BTN2: Menu button
  • If BTN2 unavailable, BTN1 handles menu

Version History

  • v1.0 - Initial release with modular source structure
    • PPG, accelerometer, temperature collection
    • Chunked BLE binary transfers
    • Continuous recording mode
    • Persistent debug logging

About

Firmware for raw biosignal data collection on Bangle devices for research use

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors