VC.
On-Device Activity Recognition — Care Wristband logo
AI/MLHardware

On-Device Activity Recognition — Care Wristband

Real-time activity classification running entirely on a 64 KB RAM wristband — no cloud round-trip, no added latency.

0+

Model Iterations

0 Classes

Classified On-Device

0%+

Field Accuracy

Tech Stack

Edge ImpulseTensorFlow Lite MicroCnRF5 SDKNordic nRF52LIS3DH IMUPythonDSP

The Challenge

Watcherr's wristband needed to classify caregiver activities (brushing, combing, mopping, walking, idle) in real time to automatically log care interactions without manual input. The entire ML inference pipeline had to run on a Nordic nRF52832/nRF52840 MCU with 64 KB RAM and no OS — sharing cycles with BLE radio scheduling, I²C sensor polling, OTA firmware updates, and battery management. Streaming raw accelerometer data to the cloud for inference was not viable: latency was too high, BLE bandwidth too constrained, and care environments too connectivity-unreliable. The model had to live entirely on the device. Additionally, the LIS3DH accelerometer was fitted in different physical orientations across three hardware variants (B7, B8, W7), requiring axis-flip compensation so a single trained model worked across all variants.

Architecture & System Design

On-Device Activity Recognition — Care Wristband system architecture
Full system schematic available upon request

The pipeline runs in four stages. First, raw 50 Hz accelerometer data is collected via a custom IMU streamer firmware on the nRF52, streamed over BLE to a PC, and manually labelled per activity class into CSV training files. Second, the dataset is loaded into Edge Impulse where DSP blocks (spectral power, RMS, zero-crossing rate) extract features from sliding windows, and a neural network classifier is trained and evaluated — iterated 23+ times from v3 to v23 as new data was collected and edge cases surfaced. Third, the trained model is exported as a C++ library and integrated into the production firmware: the LIS3DH is read via I²C FIFO burst at 50 Hz, feeding 96 floats per burst into the Edge Impulse ring buffer via `ei_wrapper_add_data()`; when a full classifier window accumulates, `ei_wrapper_get_data()` runs DSP + TFLite Micro inference fully in-place. Confidence thresholding (90%) and anomaly scoring gate which classifications are accepted. Fourth, a local Python WebSocket + HTML activity viewer streams live classified activities from the BLE gateway for field debugging and demo.

Code Walkthrough

3-step walk-through of the production implementation — file paths and intent shown above each block.

  1. Step 1 of 3

    LIS3DH FIFO burst → Edge Impulse ring buffer

    firmware/imu.c

    Polling the LIS3DH one sample at a time at 50 Hz would fire an I²C interrupt every 20 ms and starve the BLE radio scheduler. Instead the accelerometer is configured in FIFO mode: 32 samples accumulate in hardware, a single burst read pulls all 196 bytes, and the axis-flip table handles the three different PCB mounting orientations (B7, B8, W7) without separate firmware builds.

    c
    static void twi_callback_acceleration(ret_code_t result, void *p_user_data) {
        /* Burst-read 32 FIFO samples (196 bytes) over I²C */
        for (uint8_t rx_bit = 0; (rx_bit + 5) < IMU_RX_BUFFER_SIZE; rx_bit += 6) {
            /* Axis-flip compensation: B7/B8/W7 mount differently */
            float acc[3] = {
                (float)(int8_t)(m_rx_buffer[rx_bit + 1]) * 32 * FLIP_X,
                (float)(int8_t)(m_rx_buffer[rx_bit + 3]) * 32 * FLIP_Y,
                (float)(int8_t)(m_rx_buffer[rx_bit + 5]) * 32 * FLIP_Z
            };
            ei_wrapper_add_data(acc, 3);  /* Feed ring buffer */
            acceleration_data_count++;
            activity_detection_data_ready = true;
        }
    }
    Takeaway

    FIFO burst-read reduces I²C interrupts 32× and frees the BLE scheduler; a compile-time axis-flip table keeps a single firmware binary across all hardware variants.

  2. Step 2 of 3

    Edge Impulse inference + confidence gating

    firmware/activity.c

    ei_wrapper_get_data runs the full DSP feature extraction plus TFLite Micro inference in-place inside the main loop — no heap allocation, no OS. Two gates protect against junk classifications: a 90% confidence threshold and a separate anomaly score for out-of-distribution motion (device removed, thrown, etc.). Only transitions that persist for more than 2 seconds are accepted, eliminating micro-transitions between identical classes.

    c
    void activity_detection(void) {
        if (!activity_detection_data_ready) return;
        activity_detection_data_ready = false;
    
        ei_impulse_result_t result = { 0 };
        ei_wrapper_get_data(&result);  /* DSP blocks + TFLite Micro — fully in-place */
    
        uint8_t main_activity = 0;
        uint8_t strongest     = 0;
        for (size_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
            uint8_t p = (uint8_t)(result.classification[i].value * 100);
            if (p > strongest) { strongest = p; main_activity = i + 1; }
        }
    
        /* Gate 1 — confidence below threshold or anomalous motion */
        if (strongest < CONFIDENCE_THRESHOLD_PERCENTAGE ||
            result.anomaly > ANOMALY_THRESHOLD)
            main_activity = ACTIVITY_ANOMALY;
    
        /* Gate 2 — only accept transitions sustained for > 2 s */
        if (current_activity != main_activity &&
            (get_uptime() - previous_activity_change_time) > MINIMUM_ACTIVITY_LENGTH) {
            current_activity             = main_activity;
            previous_activity_change_time = get_uptime();
            activity_queue_packet();
        }
    
        ei_wrapper_start_prediction(0, acceleration_data_count);
        acceleration_data_count = 0;
    }
    Takeaway

    Two independent gates — confidence threshold and anomaly score — are cheaper than a larger model and cleanly handle the device-removal and out-of-class scenarios that caused false positives in earlier iterations.

  3. Step 3 of 3

    BLE activity packet encoding

    firmware/ble_activity_service.c

    The gateway never needs a 50 Hz stream — it only needs to know when the activity changes. Each 7-byte BLE packet encodes the class index, confidence, and a millisecond uptime timestamp so the gateway can reconstruct a timestamped activity log without any raw sensor traffic. The uptime is correlated to wall clock at the gateway on first heartbeat.

    c
    /* Packet layout: [type:1][activity_class:1][confidence:1][uptime_ms:4] */
    #define PKT_TYPE_ACTIVITY  0x0A
    
    void activity_queue_packet(void) {
        uint8_t buf[7];
        buf[0] = PKT_TYPE_ACTIVITY;
        buf[1] = current_activity;   /* 1–5 (brush/comb/mop/walk/idle), 0 = anomaly */
        buf[2] = strongest;          /* Confidence 0–100 */
    
        /* Little-endian uptime ms — gateway correlates to wall clock on heartbeat */
        uint32_t now = get_uptime();
        memcpy(&buf[3], &now, 4);
    
        ble_queue_alloc_buffer(buf, sizeof(buf));
        NRF_LOG_INFO("activity: class=%d conf=%d%%", current_activity, strongest);
    }
    Takeaway

    Send transitions, not samples — 7 bytes per activity change vs. 6 bytes × 50 Hz × 60 s = 18 KB/min. The timestamp lets the gateway reconstruct the full log without the raw stream.

Results

The on-device activity recognition pipeline ran reliably across three hardware variants (Minew B7, B8, KKM W7) in live care-facility deployments across Belgium and the Netherlands. Over 23 Edge Impulse model iterations, classification confidence on the 5 target activities exceeded 90% in field conditions. The 2-second minimum activity duration filter eliminated spurious short transitions, and the anomaly score threshold cleanly rejected out-of-distribution motion. The local activity viewer made field debugging immediate — real-time classification visible in a browser without any cloud dependency. Watcherr subsequently rebranded as ixicare; the platform and devices continue operating under that brand.

Gallery & Demos

On-Device Activity Recognition — Care Wristband screenshot
On-Device Activity Recognition — Care Wristband screenshot
On-Device Activity Recognition — Care Wristband screenshot
On-Device Activity Recognition — Care Wristband screenshot
On-Device Activity Recognition — Care Wristband screenshot
Demo video 1
Click to expand
Demo video 2
Click to expand
Demo video 3
Click to expand
Demo video 4
Click to expand

Click any image or video to expand · ← → keys navigate

Interested in this work?

Full architecture walkthrough and code review available during interviews.