VC.
BLE Wearable System Architecture & Protocol Design logo
HardwareBackend

BLE Wearable System Architecture & Protocol Design

7 custom protocols to make a BLE wearable reliable in connectivity-unreliable care homes.

0

Custom Protocols Designed

0 Layers

Device · Gateway · Cloud

Fail-Safe

Alarm Delivery Guarantee

Tech Stack

BLEWebSocketNordic DFUOTARPCState Machinesdraw.io

The Challenge

Off-the-shelf BLE profiles (GATT, HID, etc.) were not designed for the alarm-delivery guarantees required in a care-home safety context. A distress alarm triggered by a resident had to reach the caregiver app even if the BLE connection dropped mid-transmission, the gateway rebooted, or the device went out of range and reconnected minutes later. The system also needed to support OTA firmware updates over BLE without interrupting alarm monitoring, and allow the cloud to push configuration changes and messages to devices that may be offline for hours. Each of these scenarios required a custom protocol layer on top of raw BLE.

Architecture & System Design

Full system schematic available upon request

Seven protocols were designed to cover the full communication surface. The BLE Packet Definition specified the byte-level framing for all packet types (alarm, heartbeat, ILT scan, DFU trigger, sticky message) with version fields for forward compatibility. The Alarm State Protocol defined a state machine with states (Idle, Alarming, Acknowledged, Cancelled) and the exact transition triggers, retry timings (10 s resend until ack, 60 s reduced rate post-ack), and counter-matching rules to prevent replays. The Sticky Notification Protocol guaranteed delivery of cloud-to-device messages across connectivity gaps using a counter handshake: the device requests pending messages on heartbeat, the gateway responds with a counter, the device ACKs and requests the actual payload, repeating until counter reaches 255 (no pending). The OTA Protocol and DFU Trigger Sequence specified the dual-bank Nordic bootloader handoff, the 5-button DFU entry sequence, and the abort-to-alarm fallback. The RPC Protocol and WebSocket Protocol defined cloud-to-gateway and gateway-to-app communication respectively. The Fail-Safe Design document specified system behaviour under power loss, connectivity loss, and firmware corruption scenarios.

Code Walkthrough

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

  1. Step 1 of 3

    Sticky message counter handshake

    firmware/sticky_messages.c

    Care homes have frequent short BLE disconnections — a device moves out of gateway range for 30 seconds, reconnects, and any cloud-to-device config message sent during that window would be silently lost with a naive push model. The counter handshake flips the responsibility: on every heartbeat the device asks 'do you have anything for me?' The gateway responds with a counter; the device ACKs and repeats until the counter reaches 255 (nothing pending), guaranteeing delivery even after hours of disconnection.

    c
    /* sticky_messages.c — Guaranteed cloud-to-device delivery
     * Counter handshake ensures no message is lost across BLE gaps.
     * Device polls on every heartbeat; gateway drains its queue per counter.
     */
    
    void sticky_message_request_send(uint8_t sticky_counter) {
        ble_packet_t pkt = {
            .type    = PKT_STICKY_MSG_REQUEST,
            .counter = sticky_counter,
        };
        ble_queue_alloc_buffer(&pkt, sizeof(pkt));
    }
    
    void sticky_message_response_handle(uint8_t received_counter) {
        m_sticky_counter = received_counter;
        if (received_counter != STICKY_NO_PENDING) {
            /* ACK this message, schedule next poll in 10 s */
            sticky_message_request_send(m_sticky_counter);
            app_timer_start(m_sticky_timer, STICKY_RETRY_TICKS, NULL);
        }
        /* received_counter == 255 → nothing pending, stop polling */
    }
    Takeaway

    Pull beats push for unreliable links — the device drives the drain loop so the gateway never needs to know when a device is back online.

  2. Step 2 of 3

    Alarm state machine with counter-matched ACK

    firmware/alarm_state.c

    A missed alarm in a care home is a patient safety event. The state machine's monotonic counter prevents a stale ACK from a previous alarm silencing a new one, and the 10-second retry loop keeps the alarm alive until an authorised caregiver explicitly acknowledges it — not just until the BLE connection drops and recovers.

    c
    typedef enum {
        ALARM_STATE_IDLE      = 0,
        ALARM_STATE_ALARMING  = 1,
        ALARM_STATE_ACKED     = 2,
    } alarm_state_t;
    
    static alarm_state_t m_alarm_state   = ALARM_STATE_IDLE;
    static uint32_t      m_alarm_counter = 0;   /* Monotonic — prevents stale-ACK replay */
    
    void alarm_trigger(uint8_t source) {
        if (m_alarm_state == ALARM_STATE_ALARMING) return;  /* Already firing */
        m_alarm_state = ALARM_STATE_ALARMING;
        m_alarm_counter++;
        led_set(LED_RED_BLINK);
        vibrate_pulse(3);
    
        alarm_send_packet(source, m_alarm_counter);
        /* Retry every 10 s until ACK received */
        app_timer_start(m_alarm_retry_timer, ALARM_RETRY_TICKS, NULL);
    }
    
    void alarm_ack_received(uint32_t counter) {
        if (counter != m_alarm_counter) return;   /* Stale ACK — ignore */
        m_alarm_state = ALARM_STATE_ACKED;
        app_timer_stop(m_alarm_retry_timer);
        led_set(LED_GREEN_SOLID);
    }
    
    static void alarm_retry_timeout(void *ctx) {
        if (m_alarm_state == ALARM_STATE_ALARMING)
            alarm_send_packet(ALARM_SOURCE_RETRY, m_alarm_counter);
    }
    Takeaway

    Counter-matching the ACK costs 4 bytes and prevents the worst failure mode: a delayed ACK from a previous alarm silencing a live emergency.

  3. Step 3 of 3

    BLE chunk DFU — dual-bank flash write

    firmware/dfu_transport.c

    Nordic's dual-bank DFU keeps the running firmware in bank A while the new image is streamed chunk-by-chunk into bank B. If the BLE connection drops mid-update, the device stays on bank A — it never bricks. Only after a CRC-matched finalise call does the bootloader mark bank B valid and trigger the swap on next reset, leaving alarm monitoring uninterrupted throughout.

    c
    /* dfu_transport.c — BLE chunk delivery, dual-bank Nordic DFU */
    #define DFU_CHUNK_SIZE  20   /* MTU-limited BLE payload */
    
    static uint32_t m_offset       = 0;
    static uint32_t m_expected_crc = 0;
    
    void dfu_start(uint32_t image_size, uint32_t crc) {
        m_offset       = 0;
        m_expected_crc = crc;
        if (nrf_dfu_bank_prepare(image_size) != NRF_SUCCESS) {
            dfu_notify_error(DFU_ERR_NO_SPACE);
            return;
        }
        dfu_notify_ready();
    }
    
    void dfu_chunk_received(const uint8_t *data, uint16_t len) {
        nrf_dfu_flash_write(DFU_BANK_B_ADDR + m_offset, data, len);
        m_offset += len;
        dfu_notify_offset(m_offset);  /* Sender can resume after drop */
    }
    
    void dfu_finalise(void) {
        uint32_t actual_crc = crc32_compute(DFU_BANK_B_ADDR, m_offset);
        if (actual_crc != m_expected_crc) {
            nrf_dfu_bank_invalidate(DFU_BANK_B);   /* Discard — stay on bank A */
            dfu_notify_error(DFU_ERR_CRC_MISMATCH);
            return;
        }
        nrf_dfu_bank_validate(DFU_BANK_B);
        NVIC_SystemReset();   /* Bootloader swaps banks on restart */
    }
    Takeaway

    Offset-ACK on every chunk means a reconnect resumes where it left off; CRC validation before bank swap is the last line of defence against a corrupt image bricking a production device.

Results

The sticky-message protocol proved critical in production: care-home BLE environments had frequent short disconnections (device moved out of gateway range, gateway rebooted for updates) and the counter-handshake mechanism ensured no cloud-to-device configuration messages or alarm acknowledgements were lost. The alarm state machine's retry logic sustained alarm visibility in the caregiver app across all connectivity gap scenarios observed in field deployments. The dual-bank DFU protocol allowed firmware updates to 40+ device revisions (v1.0.4 → v2.1.0) without a single device bricking in production. Watcherr subsequently rebranded as ixicare; the protocol designs and firmware architecture described here underpin the current ixicare platform.

Gallery & Demos

BLE Wearable System Architecture & Protocol Design screenshot
BLE Wearable System Architecture & Protocol Design screenshot

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

Interested in this work?

Full architecture walkthrough and code review available during interviews.