Bluetooth Low Energy (BLE)

The cosinuss° sensors comply to the official BLE specification as published by the Bluetooth SIG (

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:

  • client: A device that initiates GATT commands and requests, and accepts responses, for example, a computer or smartphone.
  • server: A device that receives GATT commands and requests, and returns responses, for example, a temperature sensor (cosinuss° One, °Two, …).
  • characteristic: A data value transferred between client and server, for example, the current battery voltage.
  • service: A collection of related characteristics, which operate together to perform a particular function. For instance, the Health Thermometer service includes characteristics for a temperature measurement value and a time interval between measurements.

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:

  • Discover UUIDs for all primary services
  • Find a service with a given UUID
  • Discover all characteristics for a given service
  • Find characteristics matching a given UUID

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

  • A value may be read either by specifying the characteristic's UUID, or by a handle value (which is returned by the information discovery commands above).
  • Write operations always identify the characteristic by handle, but have a choice of whether or not a response from the server is required.
  • 'Long read' and 'Long write' operations can be used when the length of the characteristic's data exceeds the Maximum Transmission Unit (MTU) of the radio link.
  • Finally, GATT offers notifications and indications. The client may request a notification for a particular characteristic from the server. The server can then send the value/values to the client whenever it becomes available. For instance, a temperature sensor server may notify its client every time it takes a measurement. This avoids the need for the client to poll the server, which would require the server's radio circuitry to be constantly operational.
  • An indication is similar to a notification, except that it requires a response from the client, as confirmation that it has received the message.

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

“Base UUID” for standard services/characteristics from the Bluetooth SIG: XXXXXXXX-0000-1000-8000-00805F9B34FB

The only exception is the custom service with a base UUID format: 0000-XXXX-1212-EFDE-1523-785F-EABC-D123

In the following only the short 16 bit version of the UUID is shown:

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.


  • Heart Rate Measurement (UUID:0x2A37) notify. Descriptor: Client Characteristic Configuration (UUID:0x2902) (heart rate and rr-intervals)
  • Body Sensor Location (UUID:0x2A38) read (not used at cosinuss°)

Bluetooth Specifications:


# 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: 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.


  • Temperature Measurement (UUID:0x2A1C) indicate. Descriptor: Client Characteristic Configuration (UUID:0x2902)

Bluetooth Specifications:


# 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: 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.


  • PLX Continuous Measurement (UUID:0x2A5F) indicate. Descriptor: Client Characteristic Configuration (UUID:0x2902)

Bluetooth Specifications:


# 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
    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: 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.


  • Battery Level (UUID:0x2A19) notify, read. Descriptor: Client Characteristic Configuration (UUID:0x2902)


# 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: 0x180A see also org.bluetooth.service.device_information.

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


  • Manufacturer Name String (UUID:0x2A29) read, e.g. cosinuss
  • Model Number String (UUID:0x2A24) read, e.g. one3
  • Hardware Revision String (UUID:0x2A27) read, e.g. 3
  • Firmware Revision String (UUID:0x2A26) read, e.g. 83f45dae4e0a (hg commit hash)
  • Software Revision String (UUID:0x2A28) read, e.g. 6-1
  • System ID (UUID:2A23) read

UUID: 0x1800


  • Device Name (UUID: 0x2A00) read, write
  • Appearance (UUID: 0x2A01) read
  • Peripheral Preferred Connection Parameters (UUID: 0x2A04) read

UUID: 0x1801


  • Service Changed (UUID:0x2A05) indicate. Descriptor: Client Characteristic Configuration (UUID:0x2902)

UUID: 0x1530

This service is used to perform a device firmware update (DFU) via Bluetooth LE.


  • DFU Packet (UUID:0x1532) write no response
  • DFU Control Point (UUID:0x1531) notify, write. Descriptor: Client Characteristic Configuration (UUID:0x2902)
  • DFU Version (UUID:0x1534) read

The custom service has a different “Base UUID” for services/characteristics: 0000-XXXX-1212-EFDE-1523-785F-EABC-D123. It's purpose is to send/receive sent proprietary data/commands.

UUID: 0xA000 so full length 0000-A000-1212-EFDE-1523-785F-EABC-D123


The characteristics may be specified differently from sensor to sensor.

  • rawdata (UUID:0xA001) notify, read. Descriptor: Client Characteristic Configuration (UUID:0x2902)
  • signal quality and error codes (UUID:0xA002) notify, read. Descriptor: Client Characteristic Configuration (UUID:0x2902)

Signal quality: This is a custom quality indicator for the heart rate values. Values of 30 and above can be considered as good quality. Heart rate values with lower quality are likely to be incorrect. You can receive these values when you enable notifications on 0xA002. The first byte (at index 0) must be the identifier 06 (hex value) to indicate that quality values are in this packet. From this packet the 9th byte (at index 8) needs to be converted to uint8 and then yields the quality value. All other bytes in the packet can be ignored.

Example signal quality:

Let's assume the following packet in hex values was received:

Assign an index to all of the 20 bytes:
Bytes(hex): 0x 06  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 = 0x06                 Quality value = 0x31

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

rawdata: This is a data channel used for debugging purposes and to send the raw data (accelerations, ppg, etc.). And to send coommands from client to server. You need a key to use this data stream. This channel is not meant to be public and should be confidential.

The sensor may send error codes on 0xA002 when it notices unexpected behavior. The first byte of the package must contain the indicator 0x07, the second byte represents the error code which must be converted to uint8.

Two error codes

Value of byte[1] (uint8) Meaning
0 Infrared or red PPG signal too low (only firmware 3.0.0 and lower)
Errors below are only for firmware versions 3.1.0 and higher
10 Infrared PPG signal too low
11 Red PPG singal too low
12 Accelerometer error
13 Battery curve missing (may lead to wrong battery life estimation)
60 Temperature measurement defect
61 Temperature measurement unrealistic. Sensor might be out of the ear.

Example error code:

Let's assume the following packet in hex values was received:

Byte[0] with value 07 indicates that it is a package with an error code
Byte[1] is the error code and can be converted from hex to uint8
         0x3C is then 3x16 + 12 = 60 (uint8)
         Which means that the temperature measurement is defect

<font 20px/inherit;;#ff0000;;inherit>!</font> 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.

  • A detailed description on GATT can be found on O'Reilly
  • Many BLE Questions can be found here Nordic Semiconductor's DevZone.
  • One of the most helpful tools for working with BLE is the Nordic nRF Connect Mobile App (iOS, Android). For example you can read and write every service and characteristic of any BLE device or see the logging messages of the nRF app to get a better understanding of how the app communicates.

  • public/ble.1688549073.txt.gz
  • Last modified: 2023/07/05 11:24
  • by felix.koneberg