Skip to content

BLE / USB CDC Command Protocol

This page describes the binary command protocol used to communicate with the iCartridge 2.0 over BLE and USB CDC interfaces. Both interfaces share the same protocol and command set.

Overview

The protocol supports three types of commands:

  • READ (? / 0x3F): Request data from the device
  • WRITE (! / 0x21): Send data to the device (payload up to 255 bytes)
  • WRITE_EXTENDED (# / 0x23): Send data to the device (extended payload, up to 3000 bytes)

Responses from the device use the same packet format.


Packet Structure

Standard Commands (READ / WRITE)

| Type (1) | Group (1) | ID (1) | Payload Len (1) | Payload (0-255) | CRC (2) |

Total size: 4 bytes (header) + payload length + 2 bytes (CRC)

Extended Commands (WRITE_EXTENDED)

| Type (1) | Group (1) | ID (1) | 0x00 (1) | Payload Len (8) | Payload (variable) | CRC (2) |

Total size: 4 bytes (header) + 8 bytes (length) + payload length + 2 bytes (CRC)


Field Details

Type (1 byte)

ValueASCIIDescription
0x3F?READ - request data from the device
0x21!WRITE - send data to the device
0x23#WRITE_EXTENDED - send large data (> 255 bytes)

Group (1 byte)

ValueGroupDescription
0x01APPApplication commands (ping, reboot)
0x02COILSModbus coils (read/write booleans)
0x03INPUTModbus input registers (read-only 16-bit)
0x04HOLDINGModbus holding registers (read/write 16-bit)
0x05DISCRETEModbus discrete inputs (read-only booleans)
0x06LOGGINGData logging control

ID (1 byte)

Command identifier within a group. See Command Reference below.

Payload Length

  • Standard commands: 1 byte (0-255)
  • Extended commands: Byte 3 must be 0x00 (marker), followed by 8 bytes (uint64_t, little-endian) specifying the payload size (including the 8-byte length field itself)

CRC (2 bytes)

CRC16 checksum in little-endian format, calculated over the header and payload.


CRC Calculation

Algorithm

  • Standard: CRC16-CCITT (table-based)
  • Initial value: 0x0000
  • Polynomial: 0x1021

What to Include

  1. Header bytes: [type, group, id, payload_len]
  2. Extended header (if applicable): 8-byte payload length
  3. Payload data

Reference Implementations

CRC16 C Implementation

c
const uint16_t crc16_tab[] = {
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
    0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
    /* ... full 256-entry table ... */
};

uint16_t calc_crc16(const uint8_t *buf, uint32_t len, uint16_t cksum) {
    for (uint32_t i = 0; i < len; i++) {
        cksum = crc16_tab[(((cksum >> 8) ^ *buf++) & 0xFF)] ^ (cksum << 8);
    }
    return cksum;
}

Byte Order

All multi-byte integers (uint16_t, uint64_t) use little-endian byte order, including the CRC.


Timeout

The device implements a 2000ms timeout between bytes. If more than 2 seconds elapse between consecutive bytes, the command parser resets and discards the incomplete command.


Command Reference

APP Commands (Group 0x01)

IDNameTypePayloadDescription
0x01PingWRITENoneBLE keep-alive. Must be sent at least every 5 seconds to maintain the BLE session.
0x02RebootWRITENoneTriggers an immediate MCU reset.

BLE Connection Management

The device tracks BLE activity via the Ping command. If no Ping is received within 5 seconds, the BLE session is marked inactive and automatic logging is disabled. Sending a Ping re-activates the session and re-enables logging.

Modbus Register Commands (Groups 0x02-0x05)

All modbus commands use ID = 0x00.

These commands provide direct access to the same register data available via the Modbus RTU interface, but over BLE or USB CDC.

Reading Registers

Send a READ (0x3F) command with a 3-byte payload:

| offset (uint16_t LE) | len (uint8_t) |
  • For 16-bit groups (Input Registers 0x03, Holding Registers 0x04): offset and len are in register units (each register = 2 bytes). Offset 0, len 2 reads registers 0 and 1 (4 bytes).
  • For boolean groups (Coils 0x02, Discrete Inputs 0x05): offset and len are in byte units. Each byte represents one boolean.

The device responds with a packet containing the requested data as the payload (same type/group/id header).

Writing Registers

Send a WRITE (0x21) command with a variable-length payload:

| offset (uint16_t LE) | data (variable) |
  • For 16-bit groups: offset is in register units.
  • For boolean groups: offset is in byte units.

The data bytes are written directly to the register memory starting at the given offset. After a successful write, register values are automatically saved to flash.

WARNING

Writes are silently ignored if offset + data length exceeds the register group size. No error response is sent.

Register Group Memory Layout

Coils (Group 0x02) - 4 bytes
OffsetFieldDefault
0temperature_autotrue
1process_barrier_pressure_autotrue
2solenoid_valve_1false (forced on boot)
3solenoid_valve_2false (forced on boot)
Discrete Inputs (Group 0x05) - 12 bytes, read-only
OffsetField
0status_icartridge_error
1status_icartridge_state
2status_process_pressure_ready
3status_barrier_fluid_pressure_ready
4status_pump_feedback_ready
5status_temperature_ready
6status_pump_standby
7status_pump_fault_blocked
8status_pump_fault_electrical
9status_pump_fault_warning
10status_accelerometer_external_ready
11status_accelerometer_onboard_ready
Input Registers (Group 0x03) - 17 registers (34 bytes), read-only
Reg OffsetFieldUnit
0temperature_deci_cdeci °C
1process_pressure_centi_barcentibar
2barrier_fluid_cbarcentibar
3d_pBarrier_pProcess_centi_barcentibar
4pump_power_centi_wattscentiwatts
5time_yeare.g. 2025
6time_month1-12
7time_day1-31
8time_hour0-23
9time_minute0-59
10time_second0-59
11accelerometer_external_x_mgmg
12accelerometer_external_y_mgmg
13accelerometer_external_z_mgmg
14accelerometer_onboard_x_mgmg
15accelerometer_onboard_y_mgmg
16accelerometer_onboard_z_mgmg
Holding Registers (Group 0x04) - 15 registers (30 bytes)
Reg OffsetFieldUnit
0set_point_temperature_deci_cdeci °C
1set_point_d_process_barrier_centi_barcentibar
2pid_temperature_p×100
3pid_temperature_i×1000/s
4pid_temperature_d×1000 s
5pid_temperature_output_percentdeci %
6deadband_process_barrier_centi_barcentibar
7minimum_pulse_time_centi_secondscentiseconds
8set_time_year-1 = skip
9set_time_month-1 = skip
10set_time_day-1 = skip
11set_time_hour-1 = skip
12set_time_minute-1 = skip
13set_time_second-1 = skip
14pressure_hysteresis_centi_barcentibar

Logging Commands (Group 0x06)

IDNameTypeDescription
0x01EnableWRITEEnable automatic logging (200ms interval)
0x02DisableWRITEDisable automatic logging
0x03Log DataREADDevice-initiated log data snapshot

When logging is enabled and the BLE session is active, the device automatically sends Log Data packets every 200ms. Each packet contains a snapshot of all register data.

Log Data Payload Structure

| version (uint16_t LE) | coils (4 bytes) | discrete (12 bytes) | input (34 bytes) | holding (30 bytes) |
  • Version: Currently 1
  • Total payload size: 2 + 4 + 12 + 34 + 30 = 82 bytes

The data is a raw memory copy of each register group, in the order listed above. Parse it using the memory layouts defined in Register Group Memory Layout.


Examples

Example 1: Ping (BLE Keep-Alive)

21 01 01 00 [CRC_LO] [CRC_HI]
ByteValueDescription
00x21WRITE
10x01APP group
20x01Ping
30x00No payload
4-5CRCCRC16 of [0x21, 0x01, 0x01, 0x00]

Example 2: Read All Input Registers

Read all 17 input registers (temperature, pressure, time, accelerometers):

3F 03 00 03 00 00 11 [CRC_LO] [CRC_HI]
ByteValueDescription
00x3FREAD
10x03Input Registers group
20x00cmd_all
30x03Payload length = 3
4-50x00 0x00Offset = 0 (register units, LE)
60x11Length = 17 (register units = 34 bytes)
7-8CRCCRC16 of bytes 0-6

Response: Device sends back a packet with type=0x3F, group=0x03, id=0x00, and 34 bytes of payload containing the raw int16_t register values in little-endian.

Example 3: Read Temperature Only

Read just the first input register (temperature):

3F 03 00 03 00 00 01 [CRC_LO] [CRC_HI]
ByteValueDescription
4-50x00 0x00Offset = 0
60x01Length = 1 register (2 bytes)

Response payload: 2 bytes, int16_t little-endian. Example: 0xFA 0x00 = 250 = 25.0 °C.

Example 4: Write Temperature Setpoint

Set temperature setpoint to 30.0 °C (= 300 in deci °C = 0x012C):

21 04 00 04 00 00 2C 01 [CRC_LO] [CRC_HI]
ByteValueDescription
00x21WRITE
10x04Holding Registers group
20x00cmd_all
30x04Payload length = 4
4-50x00 0x00Offset = 0 (register 40001)
6-70x2C 0x01Value = 300 (LE) = 30.0 °C

Example 5: Write Coil (Enable Auto Temperature)

Set coil 0 (temperature_auto) to true:

21 02 00 03 00 00 01 [CRC_LO] [CRC_HI]
ByteValueDescription
00x21WRITE
10x02Coils group
20x00cmd_all
30x03Payload length = 3 (2 offset + 1 data)
4-50x00 0x00Offset = 0 (byte units)
60x01Value = true

Example 6: Read Discrete Inputs (All Status Flags)

3F 05 00 03 00 00 0C [CRC_LO] [CRC_HI]
ByteValueDescription
00x3FREAD
10x05Discrete Inputs group
20x00cmd_all
30x03Payload length = 3
4-50x00 0x00Offset = 0
60x0CLength = 12 bytes

Response payload: 12 bytes, each byte is a boolean (0 or 1).

Example 7: Enable Logging

21 06 01 00 [CRC_LO] [CRC_HI]
ByteValueDescription
00x21WRITE
10x06Logging group
20x01Enable
30x00No payload

After this command (and while the BLE session is active via Ping), the device will begin sending Log Data packets every 200ms.

Example 8: Reboot Device

21 01 02 00 [CRC_LO] [CRC_HI]
ByteValueDescription
00x21WRITE
10x01APP group
20x02Reboot
30x00No payload

Triggers an immediate MCU reset. The device will disconnect and restart.


Communication Flow

Typical BLE Session

  1. Connect to iCartridge 2.0 BLE device
  2. Send Ping every 2-3 seconds to maintain the session
  3. Send Enable Logging to start receiving automatic data
  4. Receive Log Data packets every 200ms
  5. Send Read / Write commands as needed for configuration
  6. Send Disable Logging or simply stop sending Pings to end the session

USB CDC Session

  1. Connect via USB (device enumerates as CDC virtual COM port)
  2. Send Read / Write commands directly (no Ping required for USB)
  3. Responses are sent back over the same USB connection

TIP

Both BLE and USB CDC share the same command parser. Any command received on either interface produces a response sent to both interfaces simultaneously.


Data Persistence

  • Holding Registers and Coils are saved to internal flash after every write operation
  • Values persist across power cycles and reboots
  • Exception: Solenoid valve coils (00003, 00004) are forced to false on every boot for safety

Maximum Payload Sizes

Command TypeMax Payload
Standard (READ/WRITE)255 bytes
Extended (WRITE_EXTENDED)3000 bytes