Ruuvi Firmware – Part 9: Bluetooth Broadcasting

Ruuvi Firmware series part 9 intro image

In this part of the tutorial we’ll add Bluetooth broadcast functionality. We also start defining interfaces for communication interfaces which are as independent as possible of the data transmission method, such as Bluetooth or NFC. Final code of this blog post can be downloaded at Ruuvi GitHub in the ruuviblog-branch, tag 3.9.0-alpha.

Please follow part 1 of the series for details on how to clone the repository and compile the code. Final hex of this tutorial can be downloaded from the Ruuvi Jenkins.

ruuvi.firmware.c architecture 3.9.0

Communication interface

In the future we’ll have many different ways of communicating with the outside world. These might include Bluetooth Low-Energy Generic ATTributes (BLE GATT), some sort of mesh network, Universal Asynchronous Receiver-Transmitter (UART), Real-Time Transfer (RTT) and Near-Field Communication (NFC). We want our application to use any communication backend as transparently as possible, so we’ll define a communication interface as we did define a sensor interface.

Any interface we implement is going to have some common functionality. We’ll have

  • Initialization
  • Uninitialization
  • Send data
  • Read data

Some data transmission methods, such as communication over Bluetooth is asynchronous by nature. We’ll have to wait until there is a slot for radio activity and only then we can transmit. Therefore our send means only that data has been buffered in the driver. Likewise our receive is an event which has to be handled by the driver, and our program will read the data from the receive buffer when the application has time.

Sometimes we might want to keep repeating the data over and over, for example on iBeacon transmissions. Other cases, such as sensor data, might be sensible to send only once. We’ll reserve space for a “repeat” attribute on our message format, but won’t implement it yet.

ruuvi_interface_communication.h
ruuvi_interface_communication.h

Other parameters, such as transmission intervals or baud rates can be considered implementation specific, and won’t be included in the communication interface but they will rather be unique for each communication channel.

Chunk size is different for each channel. On a BLE broadcast we can send 24 bytes as a manufacturer specific payload, on BLE GATT transmission we can send 20 bytes unless we adjust the Maximum Transmission Unit (MTU) settings and on BLE Mesh we’re limited to 3 byte opcode + 8 byte payload per single unsegmented packet. The send function will reject the message if the message is too large to fit the buffers of the channel. In the future we might implement segmenting the messages either on driver level or on application level.

We’ll define a few standard message formats, therefore any application can understand the messages even if they’re split over several chunks. This leads us to endpoints.

Ruuvi Endpoints

The Ruuvi endpoints have been in the works for a bit over a year now. Basic idea is simple: we define type of data, and use those fixed types over all applications and communication methods.

In this part of tutorial we’ll need only the broadcast types. We’ll use the first byte as a header and remaining bytes are defined by the header. We’ll implement only manufacturer specific data advertisements for now, which means the Eddystone URL format cannot be implemented yet. We’ll fix the header values 00 to 0F for the broadcasts to stay compatible with the existing Ruuvi Firmware.

While we’re writing specs, let us agree on a general 11-byte format for the data.

  • Ruuvi data is 11 bytes long unless otherwise specified
  • Ruuvi data is split in 3 bytes of a header and 8 bytes payload
  • Header has a destination, source and type on 11 byte data
  • Payload is defined by the type on 11 byte data
  • Endpoints 00 .. 0F are served for broadcasts, including current formats. Every format is “special”, and has only first byte as a header
  • Endpoints 10 .. 7F are reserved for standard data and must follow above 11-byte definition.
  • Endpoints C0 .. FF are reserved for communication channel specific transmissions and they don’t have to be interoperable
  • Endpoints 80 .. BF are application specific and can have any format, as long as the first byte is in proper range.

Reasoning for reserving C0FF is in BLE Mesh: The unsegmented packet has 3-byte opcode which has BLE SIG vendor ID at bytes 1 and 2, and values 0b11xx xxxx of the byte 0 are reserved for the actual opcode.

For the scope of this tutorial we’ll need to implement only endpoint 3 — also known as RAWv1. This is the manufacturer specific data format which is broadcast by RuuviTags on boot after they’re coming off the factory.

ruuvi_endpoint_3.h
ruuvi_endpoint_3.h

Our endpoint file has only one function, which takes in a buffer to write the data to, data which is needed by the format and a constant which is considered as invalid data as marked by the drivers. Since data format 3 does not have a specific value for “invalid” defined, we’ll only zero any values which cannot be read from the sensors.

Bluetooth

Bluetooth Interface

In addition to the send and read functions outlined above we have some BLE-specific considerations, such as transmission interval, transmission power, if the advertisement is scannable or connectable and so on. For now we’ll support only non-connectable, non-scannable manufacturer specific data transmissions, i.e. Eddystone is not supported.

To make things even more complex, scanning advertisements over BLE or using GATT uses the same radio peripheral. One of our guiding principles has been to keep things as simple as possible, therefore we allow only one mode of radio operation at a time. For now we don’t need to care about the interoperability with other radio uses, we only need to broadcast the data. We’ll make a reservation for the BLE advertisement scanning so we can implement the read function in the future. The above communication interface is finally implemented within our Bluetooth advertising driver.

ruuvi_interface_communication_radio.h
ruuvi_interface_communication_radio.h
ruuvi_interface_communication_ble4_advertising.h
ruuvi_interface_communication_ble4_advertising.h

Bluetooth driver

Our driver has some functionality which is going to be common for all Bluetooth communication. We’ll separate this into ruuvi_platform_communication_radio. Ideally the application doesn’t have to know anything about the underlying details, such as which clocks and timers are initialized.

ruuvi_platform_communication_radio.c
ruuvi_platform_communication_radio.c

Our Nordic Software development kit 15 (SDK) -specific radio code enables the softdevice for one module, and can disable the radio in case operation mode should be changed. Theoretically the softdevice might support GATT connection and advertising at the same time, but we’ll allow only one mode at a time.

Advertising itself benefits from somewhat complex interaction with SDK: We setup advertisement data structures, let the SDK encode the data into raw buffer and finally pass the buffer to the SDK. Later on we might implement a proper circular buffer that sends data on first-in-first-out basis and puts any messages with the “repeat” set back into the queue. Maybe we even support adjustable transmission rate by sending messages faster when transmit buffer is filled and stopping when transmit buffer is empty.

For now we’re happy with barebones implementation of init, uninit and send. Read returns RUUVI_DRIVER_ERROR_NOT_IMPLEMENTED for the time being.

ruuvi_platform_communication_ble4_advertising.c — initialization
ruuvi_platform_communication_ble4_advertising.c — initialization

Integrating to Application

As before, the button is our only input to the application. We change the button task to trigger sensor sampling and start broadcasting the data via task_advertisement.

ruuvi_driver_status_t task_button_on_press(void)
{
  static uint64_t last_press = 0;
  // returns UINT64_MAX if RTC is not running.
  uint64_t now = ruuvi_platform_rtc_millis();
  ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;

  // Debounce button
  if((now - last_press) > RUUVI_BOARD_BUTTON_DEBOUNCE_PERIOD_MS)
  {

    err_code |= task_led_write(RUUVI_BOARD_LED_RED, TASK_LED_ON);
    /*err_code |= task_acceleration_on_button();
    err_code |= task_environmental_on_button();
    err_code |= task_adc_on_button();*/
    err_code |= task_advertisement_send_3();
    err_code |= task_led_write(RUUVI_BOARD_LED_RED, TASK_LED_OFF);
    RUUVI_DRIVER_ERROR_CHECK(err_code, ~RUUVI_DRIVER_ERROR_FATAL);
  }

  // store time of press for debouncing if possible
  if(RUUVI_DRIVER_UINT64_INVALID != now) { last_press = now; }
  return err_code;
}

View raw

Button press calls task_advertisement_send_3()

task_advertisement.c
task_advertisement.c

We haven’t yet implemented the “repeat” functionality in our message format, but the Softdevice keeps happily broadcasting last given data until something else is commanded. However, updating the data occurs only on button press.

Data presented in the Ruuvi Station app
Our tag broadcasts in valid Ruuvi data format which is parsed by the Ruuvi Station

Power Profile

As always, power consumption is the driving factor in our code. As we get near to the official Ruuvi Firmware built on SDK12 features we can start benchmarking our progess against the official Ruuvi Firmware which consumes approximately 24 μA at 3 V at room temperature. We’re not yet at feature parity, as the NFC is offline and sensors are sampled only at button presses. Let’s see how we compare now.

Consumption while broadcasting
Consumption while broadcasting

One thing to catch our eye is the peak current consumption. Our BLE broadcasts consume 20 mA. This is because we haven’t enabled the internal Direct Current to Direct Current converter (DC/DC). We’ll add a interface to power peripherals in the next part of the blog posts to get the current down. Overall we’re somewhere near 27 μA now, which is pretty near the 24 μA we could expect in the final firmware. Let’s verify the base level:

Consumption between the broadcasts
Consumption between the broadcasts

Our idle consumption is now 6.6 μA, which is 0.2 μA up from 6.4 μA in our previous post. Maybe the softdevice increased the baseline consumption a little somehow, or maybe we’re only seeing variance in the measurement now.

Conclusion

In this part we have added Bluetooth broadcasts and support for Ruuvi data format 3. We have also sketched out data format for remotely configuring and reading the sensors over any communication method, including Bluetooth Mesh. There is a lot of fine tuning and extending to be done to the firmware, but we’re also nearing the original Ruuvi Firmware functionality.

Stay tuned and follow @ojousima and @ruuvicom on Twitter for #FirmwareFriday posts!

Featured Products

Find the right products for your measuring needs
  • On backorder
    RuuviTag Pro is an environmental sensor that fits perfectly...
    Read more

    RuuviTag Pro (2-in-1, breathable, estimated shipping in middle of December)

    49,90
  • RuuviTag temperature sensorIn stock
    This RuuviTag model is without an air pressure sensor....
    Read more

    RuuviTag – Wireless Temperature Sensor (3-in-1)

    38,90
  • In stock
    With Ruuvi Gateway, you can read your Ruuvi sensors...
    Read more

    Ruuvi Gateway

    199,00
  • On backorder
    RuuviTag Pro is an environmental sensor that fits perfectly...
    Read more

    RuuviTag Pro (3-in-1, breathable, estimated shipping in middle of December)

    49,90