Table of Contents

Bluetooth Low Energy (BLE)

The cosinuss° sensors comply to the official BLE specification as published by the Bluetooth SIG (www.bluetooth.com).

1. Advertising

BLE sensors that advertise, also called the server, (e.g. cosinuss° One, cosinuss° Two, …) are detected through a procedure based on broadcasting advertising packets on specified advertising channels (2.402 GHz, 2.426 GHz, 2.480 GHz). Different information, like device name or the mac address are included in the advertising packets. The advertising device sends a packet on at least one of these three channels, with a repetition period called the advertising interval. The scanner, also called the client, is a receiving device (e.g. Gateway, Lab App) that listens to each channel for a duration called the scan window. After finishing the scan on one channel, it listens to the next advertising channel. This is periodically repeated with an interval called scan interval. After the discovery of the sensor with the correct mac address the receiver can connect to the BLE sensor.

2. Services & characteristics

All Bluetooth Low Energy devices use the Generic Attribute Profile (GATT) specified by Bluetooth SIG:

GATT has the following terminology:

Some service and characteristic values are used for administrative purposes – for instance, the model name and serial number can be read as standard characteristics within the Generic Access service.

Services, characteristics are collectively referred to as attributes, and identified by UUIDs (128 bit). The Bluetooth SIG has reserved a range of UUIDs (of the form xxxxxxxx-0000-1000-8000-00805F9B34FB) for standard services/characteristics. For efficiency, these identifiers are represented as 16-bit or 32-bit values in the protocol, rather than the 128 bits required for a full UUID. For example, the Device Information service has the short 16-bit code 0x180A, rather than 0000180A-0000-1000-8000-00805F9B34FB. Note that the short UUIDs are only allowed for the services/characteristics of the SIG but not for custom ones. The full list of UUIDs is kept in the Bluetooth Assigned Numbers document online:

https://www.bluetooth.com/specifications/assigned-numbers/

3. GATT operations

The GATT protocol provides a number of commands for the client to discover information about the server. These include:

Commands are also provided to read (data transfer from server to client) and write (from client to server) the values of characteristics:

How to enable and disable notifications and indications

Note Most Librarys for working with BLE have methods for subscribing to characteristics (and by this enabling notifications/indications), so you won't have to write the CCCD manually.

In order to start and stop notifications and indications of a certain characteristic you have to write the two value bits of the Client Characteristic Configuration Descriptor (CCCD) that belongs to this characteristic. E.g. Battery Level characteristic (UUID 0x2A19) has notifications whereas the Temperature Measurement Characteristic (UUID 0x2A1C) has indications. The CCCDs have the UUID 0x2902. Their first bit represents the notifications (0=off, 1=on) and the second bit the indications (0=off, 1=on). If the appropriate bits are set, the server will send (heart rate, temperature, …) values to the client whenever it has new values.

Start Notifications/Indications

  1. Enable notifications/indications on the characteristic you are interested in on your local client. There is probably a method like this, e.g. for Android developers. Or you may have to define a callback function, that deals with the incoming data. (May be handled differently in different Bluetooth libraries and plattforms)
  2. Write the hex command on the CCCD of the characteristic you are interested in to your server (e.g. on Android):
    • 0x0100 to activate notifications
    • 0x0200 to activate indications
    • 0x0300 to activate both

Stop Notifications/Indications

  1. Write the hex command 0x0000 to the CCCD of the server
  2. Disable the notifications/indications on your client

4. Services and characteristics (used by cosinuss°)

Most cosinuss° sensors use the following services (of which most are standard services as defined by the Bluetooth SIG). Each comes with a UUID. The “Base UUID” for standard services/characteristics from the Bluetooth SIG is XXXXXXXX-0000-1000-8000-00805F9B34FB.

In the following sections only the short 16 bit version of the UUID is shown (i.e for standard services 0x180D is an abbreviation of 0000180D-0000-1000-8000-00805F9B34FB).

4.1 Generic Access

UUID: 0x1800

Characteristics:

4.2 Generic Attribute

UUID: 0x1801

Characteristics:

4.3 Health Thermometer

UUID: 0x1809 see also org.bluetooth.service.health_thermometer

The cosinuss° sensors use this service to provide the body core temperature. The data is sent with 1 Hz by indication.

Characteristics:

Bluetooth Specifications:

Example:

# Health Thermometer Float Conversion - Python Example
 
# Incoming byte input (hex) from the Bluetooth device:
# 0x 04 6A 08 00 FE 03
byte_data = bytearray(b'\x04\x6A\x08\x00\xFE\x03')
 
# These are the components of the input:
# byte 0       0x04:  Flag byte where each bit has a meaning (LSB first);
#                    0x04 = 00000100
#                    [0] data is in °C
#                    [1] time stamp field not present
#                    [2] temperature type field is included
#                    See Bluetooth specs for details
# byte 1...4   0x6A 0x08 0x00 0xFE: the data (in LSB first order)
# byte 5       0x03: Temperature type field;
#                    Measuring position is the ear
 
# The IEEE float calculation goes according to this formula:
# temperature_float = mantissa * 10 ^ exponent
# where
#   mantissa is an int24 from bytes 0x6A, 0x08, 0x00
#   exponent is an int8  from byte  0xFE
 
# convert exponent to int8 with two's complement
# exponent = 0xFE      (hex)
#          = 1111 1110 (bin)
#          = -2        (int8 two's complement representation)
exponent = 0x00 - ((byte_data[4] ^ 0xFF) + 1)
# note that this conversion works only with sign bit set
# but we are not expecting positive exponents (high temperatures)
 
# convert mantissa to int24
# mantissa = 0x00386A                 (hex)
#          = 00000000 111000 01101010 (bin)
#          = 2154                     (int24)
mantissa = ((byte_data[3] << 16) | (byte_data[2] << 8) | byte_data[1]) & 0xFFFFFF
# note that this conversion works only with sign bit not set
# but we are not expecting negative temperatures
 
temperature_float = mantissa * (10 ** exponent)
print('temperature =', temperature_float, '°C')
# temperature = 2154 * 10^(-2) °C
# temperature = 21.54 °C

4.4 Device Information

UUID: 0x180A see also org.bluetooth.service.device_information.

The cosinuss° sensors use this service to provide information about the current software version.

Characteristics:

4.5 Heart Rate

UUID: 0x180D see also org.bluetooth.service.heart_rate

The cosinuss° sensors use this service to provide the heart rate (bpm) and the rr-intervals (ms). The data is sent with 1 Hz by notification.

Characteristics:

Bluetooth Specifications:

Example:

# Heart Rate Measurement and rr-interval Conversion - Python Example
 
# Incoming byte input (hex) from the Bluetooth device:
# 0x 10 44 33 03 29 03
byte_data = bytearray(b'\x10\x44\x33\x03\x29\x03')
 
# These are the components of the input bytes:
# byte 0         0x10: Flag byte where each bit has a meaning (LSB first);
#                      0x10 = 00010000 as bits
#                      [0]   heart rate value data format is UINT8
#                      [1&2] contact feature status
#                      [3]   energy expended not present
#                      [4]   rr-intervals present
#                      See Bluetooth specs for details
# byte 1         0x44: heart rate value
# bytes 2..end   0x33, 0x03, ... : in this case rr-intervals
 
# heart rate value is just the uint8 representation of byte 1
# heart_rate = 0x44     (hex)
#            = 01000100 (bin)
#            = 68       (uint8)
heart_rate = byte_data[1]  # (python converts automatically to uint)
# (note that e.g. Java converts bytes automatically to signed int!)
print('heart rate =', heart_rate, 'bpm')
# heart rate = 68 bpm
 
# the remaining bytes are rr-intervals
# each rr interval comprises 2 bytes as uint16
# the rr-interval in milliseconds is then calculated with:
# rr_int = uint16_value / 1024 * 1000
# Note that the data comes LSB first, so the bytes have to be flipped
 
rr_int_1 = (byte_data[3] << 8 | byte_data[2]) / 1024 * 1000
print('rr-interval 1 =', rr_int_1, 'ms')
# rr interval 1 = 799.8046875 ms
 
rr_int_2 = (byte_data[5] << 8 | byte_data[4]) / 1024 * 1000
print('rr-interval 2 =', rr_int_2, 'ms')
# rr-interval 2 = 790.0390625 ms

4.6 Battery

UUID: 0x180F see also org.bluetooth.service.battery_service

The cosinuss° sensors indicate the current battery level with this service. The data is sent by notification.

Characteristics:

Example:

# Battery Conversion - Python Example
 
# Incoming byte input (hex) from the bluetooth device:
# 0x60
byte_data = bytearray(b'\x60')
# The battery percentage is simply the uint8 value of the first byte
# battery = 0x60     (hex)
#         = 01100000 (bin)
#         = 96       (uint8)
 
battery = byte_data[0]  # (Python converts automatically to uint)
print('battery =', battery, '%')
# battery = 96 %

4.7 Pulse Oximeter

UUID: 0x1822 see also org.bluetooth.service.pulse_oximeter

The cosinuss° sensors use this service to provide the peripheral oxygen saturation SpO2. The data is sent by notification.

Characteristics:

Bluetooth Specifications:

Example:

# SpO2 and Perfusion Conversion - Python Example
 
# Incoming byte input (hex) from the Bluetooth device:
# 0x 10 60 00 FF 07 23 E0
byte_data = bytearray(b'\x10\x60\x00\xFF\x07\x23\xE0')
 
# These are the components of the input bytes:
# byte 0        0x10: Flag byte where each bit has a meaning (LSB first);
#                     0x10 = 00010000 as bits
#                     [4] Pulse Amplitude ("perfusion") present
#                     See Bluetooth specs for details
# byte 1..2     0x60, 0x00: SpO2 value
# bytes 3..4    0xFF, 0x07: Pulse Rate but it's not used.
#                           0x07FF == NaN, see Bluetooth specs.
#                           (heart rate is sent separately)
# bytes 5..6    0x20, 0x34: perfusion index
#                     ("Pulse Amplitude Index" in Bluetooth Docs)
 
# Both, SpO2 and perfusion are SFLOAT values and are calculated as follows:
# float_value = mantissa * 10 ^ exponent
# where
#   exponent high 4 bit signed integer of the 2 data bytes
#   mantissa low 12 bit signed integer of the 2 data bytes
 
# SpO2
# exponent = 0x0  (hex) - taken from 0x0060
#          = 0000 (bin)
#          = 0    (int4, two's complement)
exponent_spo2 = byte_data[2]>> 4  # use only 4 high bits
# note that this conversion only works for positive exponents
# but the SpO2 precision is typically not in such small magnitudes
print('spo2 exponent =', exponent_spo2)
 
# mantissa
# mantissa = 0x060         (hex) - taken from 0x0060
#          = 0000 01010 0000 (bin)
#          = 96              (int12)
mantissa_spo2 = ((byte_data[2] & 0xF) << 8 | byte_data[1])
# note that this conversion only works for a positive mantissa
# but it would be unhealthy for a patient to have negative SpO2
print('spo2 mantissa =', mantissa_spo2)
 
spo2 = mantissa_spo2 * 10**exponent_spo2
print('spo2 =', spo2, '%')
# spo2 = 96 %
 
# perfusion
# exponent = 0xE  (hex) from 0xE023
#          = 1110 (bin)
#          = -2   (int4, two's complement)
 
# sign bit is set => two's complement on high 4 bits
if byte_data[6]>> 7 == 1:
    exponent_perf = 0x0 - (((byte_data[6]>>4) ^ 0xF) + 1)
# high 4 bits as unsigned integer
else:
    exponent_perf = byte_data[6]>> 4
print('perfusion exponent =', exponent_perf)
 
mantissa_perf = ((byte_data[6] & 0xF) << 8 | byte_data[5])
print('perfusion mantissa =', mantissa_perf)
# note that this works only for a positive mantissa
# but the perfusion should be positive anyway.
 
perfusion = mantissa_perf * 10**exponent_perf
print('perfusion =', perfusion, '%')
# perfusion = 0.35 %

4.8 Secure DFU Service

UUID: 0000FE59-0000-1000-8000-00805F9B34FB

A propietary service of Nordic Semiconductor used to perform a device firmware update (DFU) via Bluetooth LE.

5. cosinuss° Custom Service

UUID: 0000-A000-1212-EFDE-1523-785F-EABC-D123

The custom service has a different “Base UUID” for services/characteristics: 0000-XXXX-1212-EFDE-1523-785F-EABC-D123. It's purpose is to receive proprietary data (e.g. signal quality, device error codes, …) via the custom characteristics:

Characteristics:

5.1 cosinuss° Rawdata Characteristic

UUID: 0xA001

This characteristic is used for debugging purposes and to send the extended data (accelerations, ppg, etc.). You need a key to use this data stream. This channel is not meant to be public and should be confidential.

5.2 cosinuss° SOC characteristic

UUID: 0xA002

When notifications on 0xA002 are enabled, the sensor will send additional info like signal quality and device error codes.

5.2.1 Signal quality

Signal quality is a custom quality indicator for the heart rate values. Values of 30 and above can be considered as good quality. Heart rate values that come with a lower signal quality are likely to be incorrect.

You can receive these values when you enable notifications on the 0xA002 characteristic and listen for the right PacketID (the value of the first byte in the BLE packet). Then convert the value at the byte index 8 to uint8.

There are two variants of the signal quality depending on the firmware version of the sensor:

Example signal quality max:

Let's assume the following packet in hex values was received:
0x27-00-00-85-00-59-5B-2E-31-FF-EF-86-23-EF-F6-DB-FE-9d-23-BE

Assign an index to all of the 20 bytes:
Bytes(hex): 0x 27  00  00  85  00  59  5B  2E  31  FF  EF  86  23  EF  F6  DB  FE  9d  23  BE
Indices:      [00][01][02][03][04][05][06][07][08][09][10][11][12][13][14][15][16][17][18][19]
               ^                               ^
               Paket ID = 0x27                 Quality value = 0x31

Byte[00] with value 0x27 indicates that it is the correct package
Byte[08] is the quality_max value and can be converted from hex to uint8
         0x31 is then 3x16 + 1 = 49 (uint8)

5.2.2 Device error codes

The sensor may send error codes on 0xA002 when it notices unexpected behavior. The first byte of the package must contain the PacketID 0x07, the second byte represents the error code. The meanings are listed in the table below.

Note: Error codes of older firmware versions may differ and are not listed here.

error code (hex) Meaning
0A Infrared threshold
0B Red threshold
0C Acceleration axes
0D Unknown battery curve
0E Green threshold
11 Temperature defect
3C Temperature defect
3D Temperature unrealistic

* c-med° alpha only, °Two only

Example error code:

Let's assume the following packet in hex values was received:
0x07-0B-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00

Byte[0] with value 07 indicates that it is a package with an error code
Byte[1] contains the error code 0x0B which means that the red LED
        may be defective

Please note, that the sensor may send an error code on the first sign of a specific error, for example when the PPG signal was below a certain threshold for only a few samples but then returns to normal behavior. It is therefore necessary to implement a threshold for certain errors to avoid a false alarm.

6. Further reading and useful tools


1) , 2)
proprietary service