The cosinuss° sensors comply to the official BLE specification as published by the Bluetooth SIG (www.bluetooth.com).
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.
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:
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:
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
Stop Notifications/Indications
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).
Service Name | 16-bit UUID (0x) |
---|---|
Generic Access | 1800 |
Generic Attribute | 1801 |
Health Thermometer | 1809 |
Device Information | 180A |
Heart Rate | 180D |
Battery | 180F |
Pulse Oximeter | 1822 |
Secure DFU Service | FE591) |
cosinuss° Custom Service | A0002) |
UUID: 0x1800
Characteristics:
UUID: 0x1801
Characteristics:
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
UUID: 0x180A see also org.bluetooth.service.device_information.
The cosinuss° sensors use this service to provide information about the current software version.
Characteristics:
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
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 %
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 %
UUID: 0000FE59-0000-1000-8000-00805F9B34FB
A propietary service of Nordic Semiconductor used to perform a device firmware update (DFU) via Bluetooth LE.
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:
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.
UUID: 0xA002
When notifications on 0xA002 are enabled, the sensor will send additional info like signal quality and device error codes.
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)
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.