Design Pattern: Constant Streaming

Design Pattern: Constant Streaming
Photo by Stephen Dawson / Unsplash

Constant streaming apps need to collect data for long periods of time while at the same time minimizing packet loss.

Example: Sleep monitoring
A medical device that generates high amounts of critical signal data (think EEG, EKG) needs to get data consistently - over a long period of time - to a gateway app for analysis and uploading to the cloud.

On the device side: aside from maximizing the packet size, choose to send data as notifications rather than indications. (Since indications require an ack back from the phone and will slow down the processing time). If data is sent out almost constantly, the peripheral firmware will need to have adequate buffers to store the queued-up data.

To ensure that your data packets are not split by connection intervals, you will need to calculate the maximum packet size that fits within your chosen connection interval. (click for more info)

The formula for this calculation is

Max Packet Size = Connection Interval / (1 + Slave Latency) * Max Bytes Per Interval

Where:

  • Connection Interval: The time interval in milliseconds between each data exchange between the central and peripheral devices. This value is set by the central device.
  • Slave Latency: The number of connection intervals that the peripheral can skip before responding to a request from the central device. This value is also set by the central device.
  • Max Bytes Per Interval: The maximum number of bytes that can be sent per connection interval. This value is determined by the BLE protocol. On iOS with Data Length Extensions enabled this is 255. But we have to allow some header information per the BLE spec, so assume 244 bytes.

For example, if your connection interval is set to 15 milliseconds (iOS default) and your slave latency is 0 (meaning the peripheral must respond to every request), the maximum packet size would be:

Max Packet Size = 15 / (1 + 0) * 244 = 3660 bytes

On the phone side: make sure to request the appropriate connection interval.

On Android, this is called "Connection Priority".  For the LOW, BALANCED, and HIGH intervals, the min/max values can be found here.

On iOS, CoreBluetooth will not allow direct configuration. Rather, it relies on the rules outlined in Apple's Accessory Guidelines.

The other thing to consider on the phone side is

💡
Notifications should be handled as quickly as possible. Delays in handling the updates could lead to slower response times, missed packets, or even errors


On iOS, this means it's good to specify the queue when creating a CBCentralManager

On Android, it's good to not sit in onCharacteristicUpdated long - and rather pass off processing to another thread.

There are a few other ways you can optimize the handling of incoming data:

  1. Reduce processing time: Try to minimize the amount of time it takes to process the incoming data. You can achieve this by keeping the processing logic as simple as possible, reducing the number of unnecessary operations, and using optimized data structures and algorithms.
  2. Batch processing: Rather than processing every incoming update individually, you can consider batching updates and processing them in groups. This can reduce the amount of processing overhead and improve performance.
  3. Implement data filtering: If the characteristic is sending a large amount of data, implementing data filtering can help reduce the amount of data that needs to be processed. For example, you could filter out data that is not relevant to your application.