BLE Serial Protocol Description

From Mooshim Engineering Wiki
Jump to: navigation, search

Introduction

The Mooshimeter communicates over BLE using a custom GATT profile. There are several software layers above the GATT profile as well, documented below in order from lowest to highest.

  • BLE GATT Profile
  • Serial layer
  • Configuration Tree Layer
  • Application Layer

BLE GATT Profile

There are 2 characteristics in the Mooshimeter profile:

Name UUID Permissions Data format
Meter Serial In D4DB05E0-54F2-11E4-AB62-0002A1FFC51B W Binary Stream Format
Meter Serial Out D4DB05E0-54F2-11E4-AB62-0002A2FFC51B R/N Binary Stream Format

Data is written to the Serial In characteristic, processed by the Mooshimeter, and responses written to the Serial Out characteristic as 1 or several notifications.

Serial Layer

This layer is responsible for reconstructing a stream of data, in order, on the other side.

Serial In (Master->Mooshimeter) Format

In the serial input, an X byte packet of data is processed as follows:

  • While data remains in the source, pop up to 20 bytes from the data source and send it

20 bytes is the size limit for a single BLE GATT payload.

Serial Out (Mooshimeter->Master) Format

In the serial input, an X byte packet of data is processed as follows:

  • At the beginning of a BLE connection, a static sequence number (8 bit unsigned int) is reset to 0
  • While data remains in the source, pop up to 19 bytes from the data source and send it preceded by the sequence number
  • Increment the sequence number, allowing it to wrap to 0 after 255

Why add a sequence number?

In theory the BLE protocol guarantees packets will be delivered in order. But in practice many implementations scramble the order of the packets being delivered to the application layer (I'm looking at you Android). Adding a sequence number to the payload is a simple way to guarantee the data can be reconstructed in order.

Why isn't there a sequence number on the Serial In?

Two reasons:

  • I (jwhong) haven't observed any BLE Master implementations jumbling the order in which packets are sent, and the firmware on the Mooshimeter doesn't reorder received packets either.
  • Commands sent to the Mooshimeter are always less than 20 bytes long, so there is never a reason to reconstruct longer packets on the Mooshimeter side

Configuration Tree Layer

The Configuration Tree is a self describing protocol that runs on top of the serial layer. The tree is a way of grouping the functionality offered by the meter so that every function has an obvious and intuitive syntax and command format. The config tree also provides a way for the Mooshimeter (the slave device) to describe its features to the smartphone or PC (the master).

Node Types

Every node in the tree has a type. The type determines the format of data that can be written and/or read from the node.

  • PLAIN: This node describes some aspect of functionality, but offers no value to be read or written
  • LINK: This is also a descriptive node, indicating a connection to another part of the tree. This node offers no value to be read or written.
  • CHOOSER: This node offers a multiple choice, with each choice described by the child nodes.
  • VAL_U8/U16/U32/S8/S16/S32/FLT: These nodes offer a value that can be read or written, with type as described (U8 = unsigned 8 bit integer, U16 = unsigned 16 bit integer, FLT=float, etc).
  • VAL_STR/BIN: These nodes offer a string or binary blob to be read or written from the device.

Every node that offers a value to be read or written has a unique 7 bit code assigned to it, called the "command code".

Command Format

A configtree packet going from either M->S or S->M always has a 1 byte header consisting of a single write bit and a 7 bit command code. This is followed by an optional payload. There are 3 types of transaction in this system.

Read request (M->S)

Field Name Write bit Command code
Size 1 bit 7 bits
Value 0 Command Code

Write request (M->S)

A write request is sent by the master to the slave to update the value at a node.

Field Name Write bit Command code Payload
Size 1 bit 7 bits Varies
Value 1 Command Code Value to be written

Value Update (S->M)

A value update is sent as a response by the slave to a successful read request or write request. It may also be sent without prompting from the master. For example, the Mooshimeter occasionally sends an updated battery status without prompting from the master.

Field Name Write bit Command code Payload
Size 1 bit 7 bits Varies
Value 0 Command Code Updated value

Value Formats

All multi-byte values are packed least-significant-byte first.

Variable length values (STR and BIN types) are preceded by an unsigned 16 bit integer indicating the number of bytes to follow.

Field Name Blob length (X) Blob
Size 2 bytes X bytes

Initialization/Tree Discovery

After the BLE connection is established, the master must obtain a copy of the slave's command tree. To make this possible, there are 3 nodes with fixed command codes guaranteed by the protocol.

Node Name Command Code Type Description
ADMIN:CRC32 0 U32 Write the CRC32 of the packed config tree obtained via the ADMIN:TREE command. Writing this value correctly indicates to the Mooshimeter that the master device is ready to send and receive commands from this Mooshimeter's config tree. Prior to writing this value, only the 3 ADMIN: nodes of the tree are available.
ADMIN:TREE 1 BIN This is a serialized and bzipped version of the config tree. After unzipping, the tree is described in a binary format detailed in Tree Packing Format.
ADMIN:DIAGNOSTIC 2 STR The Mooshimeter will send diagnostic strings to the master device using this node. Writing strings from the master has no effect.

Example Initialization Transaction

After the BLE connection has been completed and all lower layers initialized, the config tree initialization transaction looks like this:

Direction Write Bit Command Code Payload Description
M->S 0 1 None Read request ADMIN:TREE
S->M 0 1 Zipped tree data Value Update for ADMIN:TREE
M->S 1 0 4 byte CRC32 value of the zipped tree data Write request for ADMIN:CRC32
S->M 0 0 4 byte CRC32 value of the zipped tree data Value update for ADMIN:CRC32, confirming that the rest of the commands have been unlocked and the Mooshimeter is ready to receive commands relating to the rest of the command tree.

Isn't this repeating the work that the GATT layer should be doing?

If you think it repeats a lot of work done on the GATT and BLE layers, you are right. In practice I ran in to a host of problems with the Android implementations of BLE that I could only circumvent by keeping the GATT layer as simple as possible and building on top of it. With the configuration tree running through a barebones serial link, I can add features without having to touch the GATT profile.

Application Layer

Name Code Type Description
ADMIN:CRC32 0 U32
ADMIN:TREE 1 BIN
ADMIN:DIAGNOSTIC 2 BIN
PCB_VERSION 3 U8 The hardware version of this Mooshimeter. May be 7 or 8. Cannot be written.
NAME 4 STR The name this Mooshimeter advertises with over BLE. May be overwritten with a value up to 20 characters long.
TIME_UTC 5 U32 UTC seconds since 1970 (unix/epoch time). Can be written.
TIME_UTC_MS 6 U16 Millisecond component of epoch time.
BAT_V 7 FLOAT Mooshimeter AA battery voltage in volts. Sent periodically.
REBOOT 8 CHOOSER
REBOOT:NORMAL X X Choice 0 for the REBOOT command: reboot normally
REBOOT:SHIPMODE X X Choice 1 for the REBOOT command: reboot in to shipping mode (radio off)
SAMPLING:RATE 9 CHOOSER Sets the sampling rate in Hz. Choices in order: 125,250,500,1000,2000,4000,8000
SAMPLING:DEPTH 10 CHOOSER Sets the buffer depth in # of samples. Choices in order: 32,64,128,256
SAMPLING:TRIGGER 11 CHOOSER Sets the sampling mode. Choices in order: OFF, SINGLE, CONTINUOUS
LOG:ON 12 U8 Writing to this boolean value enables logging (if SD card present)
LOG:INTERVAL 13 U16 Writing to this sets the logging interval in seconds
LOG:STATUS 14 U8 The Mooshimeter will notify the master of the logging status code here. Writing to this value has no effect.
LOG:POLLDIR 15 U8 Writing anything to this node will trigger the meter to enumerate every file in the logging directory, reading out their information through the LOGGING:INFO characteristics
LOG:INFO:INDEX 16 U16 Index of the log being described. Immediately followed by a LOG:INFO:END_TIME value update.
LOG:INFO:END_TIME 17 U32 End time (epoch/Unix time) of the log being described. Immediately followed by a LOG:INFO:N_BYTES value update.
LOG:INFO:N_BYTES 18 U32 Number of bytes in the log being described.
LOG:STREAM:INDEX 19 U16 Index of the log being streamed. Immediately followed by a LOG:STREAM:OFFSET value update.
LOG:STREAM:OFFSET 20 U32 Offset of the next chunk in the file. Immediately followed by a LOG:STREAM:DATA value update.
LOG:STREAM:DATA 21 BIN Chunk of the file being sent, in plaintext.
CH1:MAPPING 22 CHOOSER Sets the mapping of channel 1. Options in order: CURRENT, TEMP, SHARED.
CH1:RANGE_I 23 U8 Range selection index for channel 1.
CH1:ANALYSIS 24 CHOOSER Analysis selection for channel 1. Options in order: MEAN, RMS, BUFFER. Mean and RMS are self explanatory, buffer sends the entire sample buffer over the air through the CH1:BUF node.
CH1:VALUE 25 FLOAT This is the numerical value of channel 1. Units depend on how the channel is mapped (if the channel is measuring current, value is in amps. If measuring resistance, value is in Ωs, etc). Writing to this value has no effect.
CH1:OFFSET 26 FLOAT This is the offset value being applied to CH1:VALUE. Writing to this value will add the value to all subsequent readings on CH1:VALUE. Changing the mapping of CH1 will clear the value.
CH1:BUF 27 BINARY This node directly streams out the sample buffer of the Mooshimeter. Values are signed 24 bit integers. The number of values streamed is the configured buffer depth of the meter. Converting from the integer value to native units is accomplished by multiplying the integer value by the floating point value at CH1:BUF_LSB2NATIVE. Writing to this node has no effect.
CH1:BUF_BPS 28 U8 Number of bits per sample in CH1:BUF. Always 24. Writing to this node has no effect.
CH1:BUF_LSB2NATIVE 29 FLOAT The multiplier to apply to values from CH1:BUF to get values in native units (amps, volts, etc). Writing to this node has no effect.
CH2:MAPPING 30 CHOOSER Sets the mapping of channel 2. Options in order: VOLTAGE, TEMP, SHARED.
CH2:RANGE_I 31 U8 See explanation for CH1
CH2:ANALYSIS 32 CHOOSER See explanation for CH1
CH2:VALUE 33 FLOAT See explanation for CH1
CH2:OFFSET 34 FLOAT See explanation for CH1
CH2:BUF 35 BINARY See explanation for CH1
CH2:BUF_BPS 36 U8 See explanation for CH1
CH2:BUF_LSB2NATIVE 37 FLOAT See explanation for CH1
SHARED 38 CHOOSER Sets the mapping of the shared (auxiliary) input on the Mooshimeter (physically labeled with Ω). Options are AUX_V, RESISTANCE, DIODE. Each of these options carries its own set of available ranges, selected by the RANGE_I node of the channel mapped to SHARED. AUX_V:[0.1,0.3,1.2], RESISTANCE:[1E3,1E4,1E5,1E6,1E7], DIODE:[1.2].
REAL_PWR 39 FLOAT This node provides the real power (as opposed to apparent power) measured during the last sample. The real power is calculated by multiplying every value in the channel 1 buffer by its counterpart in the channel 2 buffer, summing those results and square rooting the sum.

Session data examples

The device respond slightly different in different operational modes. Continuous mode requires higher refresh rate and channel bandwidth, so data are streamed as a single data stream, multiplexing several values in a single response (although split by several transport layer packets)


Examples for such data exchange:

Simple command request/response

Continuous triggering mode