Data output
The data schema can be obtained with the sifibridge schema subcommand.
Data packet format
SiFi Bridge outputs sensor data as JSON packets (use -p flag for pretty-printed output). Each packet follows a standard structure defined by the DataPacket schema.
DataPacket structure
Each packet contains data from a single sensor at most.
Each data channel has the same number of samples, and the timestamps always has the same number of values too. Data values and timestamps are matched 1-to-1: (timestamps[0], data['ecg'][0]) form a pair.
{
"device": "BioPoint",
"id": "BP_7A1F",
"name": "my_bp",
"mac": "C2:EC:EF:30:0D:10",
"packet_type": "ecg",
"status": "ok",
"received_at": 1782573963.169,
"timestamps": [
17.826,
17.828,
17.83,
],
"data": {
"ecg": [
null,
0.0,
4.378609035326087e-7,
]
},
"sample_rate": 500.28058548806575,
"samples_lost": 1,
"download_progress": 90
}
Field descriptions
| Field | Type | Required | Description |
|---|---|---|---|
device | string | Yes | Device type, i.e. BioPoint or SiFiBand |
id | string | Yes | Device unique identifier |
name | string | Yes | Device name |
mac | string | Yes | Device MAC address |
packet_type | string | Yes | See Packet types |
status | string | Yes | See Packet Status |
received_at | double | Yes | Packet time of arrival (seconds since Unix epoch) |
data | HashMap[string, list[float]] | null | No | See Data channels |
timestamps | list[float] | null | No | Data timestamps (seconds since the start of the acquisition) |
sample_rate | double | null | No | Sampling rate (Hz). See Sampling rate |
samples_lost | uint | null | No | See Lost samples |
download_progress | uint8 | null | No | Memory download progress (percent) |
Packet types
The packet_type field indicates what kind of data is in the packet:
| Type | Description | Data channels |
|---|---|---|
ecg | Electrocardiography | ecg |
emg | Electromyography | emg |
eda | Electrodermal Activity | eda |
imu | Inertial Measurement Unit | ax, ay, az, qw, qx, qy, qz |
ppg | Photoplethysmography | ir, r, g, b |
emg_armband | EMG Armband (8 channels) | emg0-emg7 |
temperature | Temperature sensor | temperature |
status | Device status update | temperature, battery_%, memory_used_kbytes |
start_time | Acquisition start time | year, month, day, hour, minute, second |
event | Software event marker | event (a counter, incrementing per event) |
memory | Memory event | - |
device_info | Device information notice, emitted on connect | - (no data field) |
invalid | Invalid/unrecognised packet | - |
The status packet also carries a top-level device_state field, and
start_time a top-level start_time field, alongside the channels above.
The packet-type schema also declares low_latency ("extended" low
latency) and start_packet. Both are internal to the BLE framing and are
not normally surfaced as standalone packets on the data stream, so you are
unlikely to see them — but treat packet_type as an open set rather than
matching exhaustively. Export the authoritative list at any time with:
sifibridge schema -o ./schemas
Packet Status
The status field indicates the state of the packet:
| Status | Description |
|---|---|
ok | Data received successfully |
recording | Device is recording to memory |
memory_download_completed | Memory download finished |
memory_erased | Device memory was erased |
invalid_datetime | Invalid timestamp |
invalid | Invalid/corrupted packet |
Channel names
Available channel names that can appear in the data object:
Biosignal channels
ecg- Electrocardiography (volts)emg(BioPoint) /emg0-emg7(SiFiBand) - Electromyography (volts)eda- Electrodermal activity (siemens)temperature- Skin temperature (°C)
IMU channels
ax,ay,az- Accelerometer (m/s²)qw,qx,qy,qz- Normalized quaternions
PPG channels
PPG does not have a standardized unit since it mesures the intensity of the light reflected in the skin. Typical values are in the ~1000-10000 absolute units (AU) range.
ir- Infrared LEDr- Red LEDg- Green LEDb- Blue LED
Status channels
temperature- Temperature (°C). Redundant with dedicatedtemperaturepackets.battery_%- Battery level (percentage)
Device types
The device field can be:
BioPoint- BioPoint deviceSiFiBand- SiFiBand deviceInvalid- Unknown/invalid device
Sampling rate
The sample_rate field reports the effective sampling rate in Hz of the sensor as measured by SiFi Bridge. SiFi Bridge continually measures this value as each packet is received. Normally, it converges toward and perhaps slightly oscillates around the configured rate due to the jitter in Bluetooth communications.
The field is omitted from the packet entirely when there is no rate to
report — it is never serialized as null. Consumers must treat it as
optional (packet.get("sample_rate") in Python) rather than indexing it
directly. It is absent:
- on packet types that do not carry periodically sampled data, such as
status,start_time, andmemory; - on the first packet of each sensor in an acquisition, since the rate is measured from the interval between packets and there is nothing to measure against yet;
- during a memory download, since packets are streamed as fast as possible, making the measured sampling rate meaningless.
Lost samples
The samples_lost field reports how many samples were lost since the last packet. This can happen if the BLE link is unstable, causing the effective bandwidth of the BLE link to be lower than what the device requires to deliver all data in real-time.
To preserve the 1-to-1 alignment between timestamps and data values, each lost sample is kept as a null entry in the affected data channel(s), with its corresponding timestamp still present. In the DataPacket example above, samples_lost is 1 and the first ecg value is null. Its associated timestamp is 17.826 s.
If the key is missing, it means no samples were lost.
Lost samples only affect streamed data. The on-device data is not lost!
For instance, if an acquisition is started with memory mode of both, and some samples are lost, you can still obtain the real data by downloading the data from the device's onboard memory afterward.
Example packets
ECG packet
{
"device": "SiFiBand",
"id": "SB_EFFD",
"name": "my_sb",
"mac": "CD:92:AD:DA:B7:71",
"packet_type": "ecg",
"status": "ok",
"received_at": 1782598065.104,
"sample_rate": 996.8285003209548,
"timestamps": [
8.184
],
"data": {
"ecg": [
0.0
]
}
}
EMG packet
This only applies to BioPoint.
{
"device": "BioPoint",
"id": "BP_7A1F",
"name": "my_bp",
"mac": "C2:EC:EF:30:0D:10",
"packet_type": "emg",
"status": "ok",
"received_at": 1783259483.825,
"sample_rate": 1998.5506970149786,
"timestamps": [
3.448,
3.499
],
"data": {
"emg": [
-0.000020798392917798913,
-0.000021455184273097826
]
}
}
8-channel EMG packet
This only applies to SiFiBand.
{
"device": "SiFiBand",
"id": "SB_EFFD",
"name": "my_sb",
"mac": "CD:92:AD:DA:B7:71",
"packet_type": "emg_armband",
"status": "ok",
"received_at": 1783476224.947,
"sample_rate": 1597.1503517986785,
"timestamps": [
1.541875,
],
"data": {
"emg0": [
6.56791355298913e-7,
],
"emg1": [
2.1893045176630434e-7,
],
"emg2": [
-8.757218070652174e-7,
],
"emg3": [
-4.378609035326087e-6,
],
"emg4": [
2.1893045176630434e-7,
],
"emg5": [
5.254330842391304e-6,
],
"emg6": [
1.970374065896739e-6,
],
"emg7": [
1.0946522588315218e-6,
]
}
}
EDA packet
{
"device": "BioPoint",
"id": "BP_7A1F",
"name": "my_bp",
"mac": "C2:EC:EF:30:0D:10",
"packet_type": "eda",
"status": "ok",
"received_at": 1730370226.150,
"timestamps": [1730370225.123, 1730370225.163, 1730370225.203],
"data": {
"eda": [1.6323e-6, 1.64873e-6 , 1.72176e-6]
},
"sample_rate": 50.0
}
IMU packet
{
"device": "SiFiBand",
"id": "SB_EFFD",
"name": "my_sb",
"mac": "CD:92:AD:DA:B7:71",
"packet_type": "imu",
"status": "ok",
"received_at": 1782598065.101,
"sample_rate": 198.82754760640623,
"timestamps": [
8.15,
8.16
],
"data": {
"ax": [
9.889045000076294,
9.698618143200875
],
"ay": [
-0.09820778757333756,
-0.13653277784585954
],
"az": [
-0.17845073595643043,
-0.14012574568390848
],
"qw": [
0.6869235096042648,
0.6868557952294714
],
"qx": [
0.03461920498869666,
0.034735847377314714
],
"qy": [
-0.7243495047894222,
-0.7243973636306342
],
"qz": [
0.047491025575671385,
0.0476549790856847
]
}
}
PPG packet
{
"device": "BioPoint",
"id": "BP_7A1F",
"name": "my_bp",
"mac": "C2:EC:EF:30:0D:10",
"packet_type": "ppg",
"status": "ok",
"received_at": 1730370225.150,
"timestamps": [1730370225.123, 1730370225.133, 1730370225.143],
"data": {
"ir": [2738.0, 2753.0, 2765.0],
"r": [3141.0, 3154.0, 3168.0],
"g": [3465.0, 3474.0, 3482.0],
"b": [2890.0, 2903.0, 2915.0]
},
"sample_rate": 100.0
}
Status packet
{
"device": "BioPoint",
"id": "BP_7A1F",
"name": "my_bp",
"mac": "C2:EC:EF:30:0D:10",
"packet_type": "status",
"status": "ok",
"received_at": 1782574161.811,
"timestamps": [
1782574161.811
],
"data": {
"temperature": [
27.607770919799805
],
"battery_%": [
92.0
],
"memory_used_kbytes": [
370808.490234375
]
}
}
Processing Data Packets
For Python, we recommend using sifi-bridge-py.