Skip to main content

API reference

Complete API reference for the SiFi Bridge Python package (sifi_bridge_py), version 2.0.

Upgrading from 1.x

SiFi Bridge 2.0 reworked the Python API. The most common breaking changes:

1.x2.0
SifiBridge(publishers=...)Removed. Use the CLI's --tcp-out/--udp-out, or use_lsl=True.
sb.send_command(DeviceCommand.X)Removed, along with the DeviceCommand enum. Each command is now its own method (set_led(), set_motor(), power_off(), …).
sb.create_device(name) / delete_device()Removed. connect() creates a real session keyed by the device's BLE id; select_device() switches between them.
sb.show()sb.info()
sb.configure_ecg(state=True, ...)configure_sensors(ecg=True) turns the sensor on; configure_ecg() only sets parameters.
sb.configure_imu(gyro_range=...)Removed — see configure_imu().
sb.set_filters(...)set_onboard_filtering()
sb.configure_sampling_freqs(...)Removed. Use the per-sensor configure_* methods.
sb.get_data_with_key(...)Removed. Use get_data() or the per-sensor getters.
sb.start_memory_download() / is_memory_download_completed()download_memory_ble() / download_memory_serial(), which block until done and export for you.
packet field timestampreceived_at

SifiBridge class​

The main class for interacting with SiFi devices.

Constructor​

SifiBridge(use_lsl=False)

Spawn a sifibridge subprocess and connect to its data channel. The constructor launches the bundled CLI and starts background threads that read its responses and its data stream.

Parameters:

  • use_lsl (bool): If True, pass --lsl so sifibridge also streams data to Lab Streaming Layer outlets.

SiFi Bridge publishes one outlet per channel, not one per sensor — a fully-enabled BioPoint surfaces 15 outlets, because the IMU is split into seven (ax, ay, az, qw, qx, qy, qz) and the PPG into four (ir, r, g, b). Outlets are named <device id>:<channel>. See the CLI's transports page for the full stream layout.

Example:

import sifi_bridge_py as sbp

# Packets are read with sb.get_data().
sb = sbp.SifiBridge()

# Also publish the data stream to Lab Streaming Layer outlets.
sb = sbp.SifiBridge(use_lsl=True)

SifiBridge is a context manager, which is the recommended way to use it — the subprocess is shut down for you on exit:

with sbp.SifiBridge() as sb:
sb.connect()
...
info

The constructor uses the sifibridge binary bundled with the sifibridge_bin package. No separate installation is required.

Targeting multiple devices​

One SifiBridge instance can manage several connected devices. Call connect() once per device; each one gets a session keyed by its stable BLE id. Commands apply to the active device (the most recently connected, or whichever you pass to select_device()) unless you widen the target:

  • all=True — apply to every managed device
  • devices="handle" or devices=["h1", "h2"] — apply to a specific subset

Every method that acts on hardware accepts both. When you target several devices, the return value is a list of per-device responses instead of a single payload.

sb.connect("BioPoint_EF05")
sb.connect("BioPoint_AB12")

sb.start(all=True) # both
sb.set_led(1, True, devices="BioPoint_EF05") # just one

Connection methods​

connect()​

connect(handle=None, timeout=10.0) -> bool

Connect to a SiFi device. On success a session is created and becomes the active device.

Parameters:

  • handle (str | None): Device name, MAC address (Windows/Linux) or UUID (macOS). None auto-connects to the first device found.
  • timeout (float): Seconds to wait for the connection.

Returns: True if the connection succeeded.

Example:

sb.connect()                      # first available device
sb.connect("BioPoint_EF05") # by name
sb.connect("FA:31:9F:7E:6A:A9") # by MAC (Windows/Linux)

disconnect()​

disconnect() -> bool

Disconnect the active device and remove its session. Buffered acquisitions are retained — use buffer_clear() to drop them.

list_devices()​

list_devices(source) -> list[dict]

List devices from a given source.

Parameters:

  • source (ListSources | str): One of ListSources.BLE (scan for BLE devices), ListSources.SERIAL (serial ports), or ListSources.DEVICES (currently connected sessions).

Returns: A list of dicts with name and id keys.

Example:

for dev in sb.list_devices(sbp.ListSources.BLE):
print(dev["name"], dev["id"])

select_device()​

select_device(name) -> str

Choose which connected device subsequent commands target.

Parameters:

  • name (str): Device id or local name.

get_active_device()​

get_active_device() -> str

Return the id of the currently selected device.

rename_device()​

rename_device(name=None) -> dict

Rename the active device. Names are limited to 14 bytes of ASCII.

Parameters:

  • name (str | None): The new name. None resets the device to its factory default name.

Device information​

info()​

info() -> dict

Return everything the bridge knows about the active device: its id, name, model, connection state, and a nested configuration block with the full sensor configuration.

Example:

info = sb.info()
print(info["name"], info["device"])
print(info["configuration"]["ecg"]["fs"])

get_configuration()​

get_configuration() -> dict

Return just the configuration block from info() — the applied sampling rates, filters, memory mode, and so on.

get_sensors()​

get_sensors() -> dict

Return the enabled/disabled state of every sensor, including temperature.

get_sensor_states()​

get_sensor_states() -> dict

Return the enabled/disabled state of the five configurable biosignal sensors (ecg, emg, eda, imu, ppg).

get_device_state()​

get_device_state() -> str | None

Return the device's reported state string (e.g. "connected_memory_ble_working"), or None if it is not yet known.

get_battery()​

get_battery() -> int | None

Return the last reported battery percentage, or None if no status packet has arrived yet.

Battery requires status updates

Battery level arrives in status packets. Enable them with set_status_updates(True) and allow a packet to arrive before reading, otherwise this returns None.


Sensor configuration methods​

Sensors are turned on and off with configure_sensors(). The per-sensor configure_* methods only set parameters — they do not enable the sensor.

Every parameter of every configure_* method is optional and defaults to None, meaning "leave this setting as it is". configure_ecg(fs=1000) changes only the sampling rate and preserves the filters you set earlier.

Configuration is applied on start()

Configuration is staged while connected and pushed to the device when you call start(). Use info() to read back what was actually applied.

configure_sensors()​

configure_sensors(ecg=None, emg=None, eda=None, imu=None, ppg=None,
all=False, devices=None)

Enable or disable sensors. A sensor left as None keeps its current state, so configure_sensors(ppg=True) enables PPG without disturbing the others.

Example:

sb.configure_sensors(ecg=True, imu=True)
sb.configure_sensors(ppg=False) # turn PPG off, leave the rest alone

configure_ecg()​

configure_ecg(fs=None, dc_notch=None, mains_notch=None, bandpass=None,
flo=None, fhi=None, all=False, devices=None)

Parameters:

  • fs (int): Sampling rate in Hz — 250, 500, 1000, 2000.
  • dc_notch (bool): Enable the DC notch filter.
  • mains_notch (int | str): Mains notch frequency — 50 or 60. Pass "off" (or 0/False) to disable, None to leave unchanged.
  • bandpass (bool): Enable the bandpass filter.
  • flo (int): Bandpass lower cutoff in Hz.
  • fhi (int): Bandpass higher cutoff in Hz.

Example:

sb.configure_sensors(ecg=True)
sb.configure_ecg(fs=500, mains_notch=60, bandpass=True, flo=0, fhi=30)

configure_emg()​

configure_emg(fs=None, dc_notch=None, mains_notch=None, bandpass=None,
flo=None, fhi=None, all=False, devices=None)

Same parameters as configure_ecg(), except:

  • fs (int): Sampling rate in Hz — 500, 1000, 1600, 2000.

Example:

sb.configure_sensors(emg=True)
sb.configure_emg(fs=2000, mains_notch=60, bandpass=True, flo=20, fhi=450)

configure_eda()​

configure_eda(fs=None, dc_notch=None, mains_notch=None, bandpass=None,
flo=None, fhi=None, freq=None, all=False, devices=None)

Same parameters as configure_ecg(), plus:

  • fs (int): Sampling rate in Hz — 5, 25, 50, 100.
  • freq (int): Excitation signal frequency in Hz. 0 for DC.

Example:

sb.configure_sensors(eda=True)
sb.configure_eda(fs=50, freq=0)

configure_ppg()​

configure_ppg(sps=None, ir=None, red=None, green=None, blue=None,
sens=None, avg=None, all=False, devices=None)

Parameters:

  • sps (int): Raw AFE sample rate in Hz — 50, 100, 200, 400, 800.
  • ir, red, green, blue (int): LED drive current in mA (0–50).
  • sens (PpgSensitivity | str): Photodiode sensitivity — low, medium, high, max.
  • avg (int): Averaging factor — 1, 2, 4, 8, 16, 32.

The effective output rate is sps / avg — sps=400 with avg=4 yields 100 Hz — and is reported live in each packet's sample_rate.

Raises: ValueError if avg is not positive, or if sps / avg exceeds 200 Hz.

The Python wrapper caps PPG at 200 Hz

configure_ppg() rejects any sps/avg pair whose effective rate exceeds 200 Hz, even though the CLI itself accepts up to 800 Hz. Setting only one of the two is fine — the other is read back from the device so the cap can still be checked.

Example:

sb.configure_sensors(ppg=True)
sb.configure_ppg(sps=400, avg=4, ir=9, red=9, sens=sbp.PpgSensitivity.MEDIUM)

configure_ppg_fs()​

configure_ppg_fs(fs, avg=None, ir=None, red=None, green=None, blue=None,
sens=None, all=False, devices=None)

Configure PPG by the output rate you want, rather than by sps. This works out an sps/avg pair that delivers fs and hands it to configure_ppg().

By default it picks the pair with the most averaging — the highest sps that still divides down to fs — since more raw samples per delivered sample means a cleaner signal, at the cost of running the LEDs harder. Pass avg explicitly to choose the trade-off yourself.

Parameters:

  • fs (int): Wanted output rate in Hz, up to 200.
  • avg (int): Averaging factor to use. Defaults to the largest that can deliver fs.
  • Remaining parameters are as in configure_ppg().

Raises: ValueError if no sps/avg pair delivers fs, or if fs exceeds the 200 Hz cap. The message lists the reachable rates.

Example:

sb.configure_ppg_fs(100)          # picks sps=800, avg=8
sb.configure_ppg_fs(100, avg=1) # picks sps=100, avg=1

configure_imu()​

configure_imu(fs=None, accel_range=None, all=False, devices=None)

Parameters:

  • fs (int): Sampling rate in Hz — 25, 50, 100, 200.
  • accel_range (int): Accelerometer range in g — 8 or 16.

Raises: ValueError if accel_range is not 8 or 16.

Gyroscope range removed in 2.0

SiFi Bridge 2.0 removed the gyroscope range setting. The firmware reads the IMU through its 20-bit high-resolution FIFO, which pins the gyroscope full scale per IMU part, so the setting never changed the delivered range. The IMU part is reported as chip in info(), and the pinned full scale for each part is in the device spec sheet.

Example:

sb.configure_sensors(imu=True)
sb.configure_imu(fs=100, accel_range=8)

configure_temperature()​

configure_temperature(fs=None, all=False, devices=None)

Parameters:

  • fs (float): Sampling rate in Hz — 0.1, 1, 2, 10.

Device configuration methods​

set_onboard_filtering()​

set_onboard_filtering(enable, all=False, devices=None)

Enable or disable on-board filtering for all sensors.

set_high_gain()​

set_high_gain(enable, all=False, devices=None)

Enable high gain on the ECG and EMG ADC. High gain uses more of the dynamic range but saturates more easily.

set_ble_power()​

set_ble_power(power, all=False, devices=None)

Parameters:

  • power (BleTxPower | str): LOW, MEDIUM, or HIGH.

set_memory_mode()​

set_memory_mode(memory_config, all=False, devices=None)

Parameters:

  • memory_config (MemoryMode | str): STREAMING, DEVICE, or BOTH.
note

SiFiBand only supports STREAMING.

set_low_latency_mode()​

set_low_latency_mode(on, all=False, devices=None)

Pack data from multiple sensors into each BLE packet, reducing per-sensor latency at the cost of roughly 10% BLE bandwidth overhead. Enabled by default.

set_night_mode()​

set_night_mode(enable, all=False, devices=None)

Disable the device LEDs during an acquisition (e.g. for sleep studies).

set_status_updates()​

set_status_updates(on, all=False, devices=None)

Start or stop periodic status packets, which carry battery level, used memory, and device state.


Device control methods​

set_led()​

set_led(index, on, all=False, devices=None)

Parameters:

  • index (int): LED to set — 1 or 2.
  • on (bool): True to turn on.

Raises: ValueError if index is not 1 or 2.

set_motor()​

set_motor(on, all=False, devices=None)

Start or stop the vibration motor.

set_motor_intensity()​

set_motor_intensity(level, all=False, devices=None)

Set the vibration motor intensity. The motor only vibrates while it is running; the intensity persists until changed.

Parameters:

  • level (int): Intensity, 1 to 10.

Raises: ValueError if level is not between 1 and 10.

Example:

sb.set_motor_intensity(5)
sb.set_motor(True)
time.sleep(1)
sb.set_motor(False)

power_off()​

power_off(all=False, devices=None)

Power the device off. Connect it via USB to power it back on.

dfu()​

dfu(package, handle=None, resume=False, timeout=1800.0) -> dict

Update a device's firmware over BLE.

Parameters:

  • package (str): Path to the DFU zip package. Paths with spaces are fine; the wrapper quotes them.
  • handle (str | None): Device to update. Defaults to the active device.
  • resume (bool): Resume a DFU that was interrupted.
  • timeout (float): How long to wait for the update to finish.

Raises: SifiBridgeError if the bridge returns an error response.


Data acquisition methods​

start()​

start(all=False, devices=None, set_default=False)

Start data acquisition, pushing the staged configuration to the device.

Parameters:

  • set_default (bool): Also store the current configuration as the device's default, used for button-initiated acquisitions.

stop()​

stop(all=False, devices=None)

Stop data acquisition.

send_event()​

send_event(all=False, devices=None)

Generate a software event, inserting an event marker into the data stream.

get_data()​

get_data(timeout=None) -> dict

Pop the next sensor-data packet of any type.

Parameters:

  • timeout (float | None): Seconds to wait. None blocks indefinitely.

Returns: A packet dict, or {} if the timeout elapses.

Don't mix get_data() with the per-sensor getters

Each packet is queued both in the generic queue and in its sensor queue, so interleaving get_data() with get_ecg(), get_emg() and friends on the same instance returns duplicates. Pick one style per SifiBridge instance.

get_ecg() / get_emg() / get_eda() / get_imu() / get_ppg() / get_temperature() / get_event()​

get_ecg(timeout=None) -> dict

Pop the next packet for one specific sensor, ignoring all others. Each takes the same optional timeout and returns {} on expiry.

clear_data_buffer()​

clear_data_buffer() -> int

Discard any packets currently sitting in the bridge's internal queues. Useful right after start() to drop startup transients before the recording window begins.

Returns: The number of packets discarded.

close()​

close() -> None

Shut down the sifibridge subprocess and its worker threads. Called automatically when used as a context manager.


Buffer methods​

SiFi Bridge buffers every acquisition, so data can be queried after the fact and exported to disk. See the CLI's Buffering page for how buffers are filled and retained.

buffer_list()​

buffer_list(device=None) -> list[dict]

List acquisitions and their status.

buffer_info()​

buffer_info(device=None, acquisition_id=None) -> dict

Get acquisition details, including the device configuration used. acquisition_id defaults to the latest acquisition.

buffer_pull()​

buffer_pull(sensor, device=None, acquisition_id=None,
last_seconds=None, from_=None, to=None) -> list[dict]

Pull buffered sensor data out of an acquisition.

Parameters:

  • sensor (str | list[str]): Sensor type, or list of them — ecg, emg, emg_armband, eda, imu, ppg, temperature, event.
  • device (str | None): Device id or name. Defaults to the active device.
  • acquisition_id (int | None): Defaults to the latest acquisition.
  • last_seconds (float | None): Pull only the last N seconds. Mutually exclusive with from_/to.
  • from_ (float | None): Start of a relative time range, in seconds since recording start. Requires to.
  • to (float | None): End of that range. Requires from_.

Returns: A list of per-sensor dicts with keys sensor, timestamps and values (channel name → samples).

Raises: ValueError if the time-range arguments are combined illegally.

Example:

[ecg] = sb.buffer_pull("ecg", last_seconds=10)
print(len(ecg["timestamps"]), "samples")

both = sb.buffer_pull(["ecg", "imu"], from_=5.0, to=15.0)

buffer_export()​

buffer_export(fmt="csv", output_dir=".", device=None) -> dict

Export a device's buffered acquisitions to file.

Parameters:

  • fmt (str): "csv" or "hdf5".
  • output_dir (str): Directory to write to. Paths with spaces are fine; the wrapper quotes them.
  • device (str | None): Defaults to the active device.

buffer_clear()​

buffer_clear(device=None, acquisition_id=None, all=False) -> dict

Clear buffered data — one acquisition, one device's acquisitions, or everything with all=True.


Memory management methods​

download_memory_ble()​

download_memory_ble(output_dir, fmt="csv", timeout=43200) -> dict

Download the active device's onboard memory over BLE and export it.

In 2.0 the bridge's download-memory loads recordings into the buffering subsystem rather than writing files, so this blocks until the download completes and then runs buffer_export() for you.

Parameters:

  • output_dir (str): Directory to save the exported data.
  • fmt (str): "csv" or "hdf5".
  • timeout (float): How long to wait. Downloading a full flash over BLE takes hours, hence the very long default.

Raises: SifiBridgeTimeout if the download does not finish in time.

download_memory_serial()​

download_memory_serial(port, output_dir, fmt="csv", timeout=14400) -> dict

The same, over a serial port — significantly faster than BLE.

Parameters:

  • port (str): Serial port, e.g. "COM3" or "/dev/ttyACM0".

erase_onboard_memory()​

erase_onboard_memory(format=False, all=False, devices=None)

Erase the recordings stored in the device's on-board memory.

Parameters:

  • format (bool): Fully format the flash instead of only erasing stored recordings. Takes longer, but leaves the memory completely clear.

Exceptions​

SifiBridgeError​

class SifiBridgeError(RuntimeError)

Raised when sifibridge returns an error response.

SifiBridgeTimeout​

class SifiBridgeTimeout(SifiBridgeError)

Raised when a command or download does not complete within its timeout.


Enumerations​

DeviceType​

Device models reported in the device field.

class DeviceType(Enum):
BIOPOINT = "BioPoint"
SIFIBAND = "SiFiBand"
SIFIBAND_FOCUS = "SiFiBandFocus"
BIOPOINT_V1_1 = "BioPoint_v1_1"
BIOPOINT_V1_2 = "BioPoint_v1_2"
BIOPOINT_V1_3 = "BioPoint_v1_3"
INVALID = "Invalid"

PacketType​

Data packet types delivered by the bridge.

class PacketType(Enum):
ECG = "ecg"
EMG = "emg"
EDA = "eda"
IMU = "imu"
PPG = "ppg"
EMG_ARMBAND = "emg_armband"
TEMPERATURE = "temperature"
MEMORY = "memory"
STATUS = "status"
START_TIME = "start_time"
EVENT = "event"
INVALID = "invalid"
PacketType does not cover every value the bridge emits

The bridge's own packet-type schema declares three values this enum is missing — device_info, low_latency and start_packet — and device_info is emitted in practice, once per connection. So

PacketType(packet["packet_type"])   # ValueError on a device_info packet

will raise. Compare against the string, or guard the conversion:

try:
ptype = PacketType(packet["packet_type"])
except ValueError:
ptype = None # a packet type this wrapper version doesn't model

PacketStatus​

Packet status values.

class PacketStatus(Enum):
OK = "ok"
RECORDING = "recording"
MEMORY_DOWNLOAD_COMPLETED = "memory_download_completed"
MEMORY_ERASED = "memory_erased"
INVALID_DATETIME = "invalid_datetime"
INVALID = "invalid"

SensorChannel​

Sensor channel names used inside the data field of each packet.

class SensorChannel(Enum):
ECG = "ecg"
EMG = "emg"
EMG_ARMBAND = ("emg0", "emg1", "emg2", "emg3",
"emg4", "emg5", "emg6", "emg7")
EDA = "eda"
IMU = ("qw", "qx", "qy", "qz", "ax", "ay", "az")
PPG = ("ir", "r", "g", "b")
TEMPERATURE = "temperature"
EVENT = "event"

Example:

packet = sb.get_imu()
imu_data = packet["data"]

# Access specific IMU channels via the enum.
qw = imu_data[sbp.SensorChannel.IMU.value[0]] # quaternion W
ax = imu_data[sbp.SensorChannel.IMU.value[4]] # accel X (g)

BioChannel​

Every individual channel name the bridge can emit, including the status-packet fields. Useful when iterating over data generically.

MemberValueMemberValue
ECGecgEMG0–EMG7emg0–emg7
EMGemgTEMPERATUREtemperature
EDAedaEVENTevent
IMUimuBATTERYbattery_%
AX, AY, AZax, ay, azMEMORY_USED_KBYTESmemory_used_kbytes
QW, QX, QY, QZqw, qx, qy, qzYEAR, MONTH, DAYyear, month, day
PPGppgHOUR, MINUTE, SECONDhour, minute, second
IR, RED, GREEN, BLUEir, r, g, b

BleTxPower​

Bluetooth transmission power levels.

class BleTxPower(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"

MemoryMode​

Device memory configuration modes.

class MemoryMode(Enum):
STREAMING = "streaming" # Stream via BLE only
DEVICE = "device" # Save to device memory only
BOTH = "both" # Stream and save

PpgSensitivity​

PPG photodiode sensitivity levels.

class PpgSensitivity(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
MAX = "max"

ListSources​

Sources for list_devices().

class ListSources(Enum):
BLE = "ble" # Scan for Bluetooth devices
SERIAL = "serial" # Serial / USB ports
DEVICES = "devices" # Currently connected device sessions

Data packet structure​

All sensor packets follow the same general shape:

{
'device': str, # Device model, e.g. 'BioPoint'
'id': str, # Device id, e.g. 'BP_EF05'
'name': str, # BLE local name, e.g. 'BioPoint_EF05'
'mac': str, # Device MAC address or UUID
'packet_type': str, # One of PacketType
'status': str, # One of PacketStatus
'received_at': float, # Packet time of arrival (Unix epoch seconds)
'sample_rate': float, # Measured effective rate for this sensor (Hz)
'timestamps': [float], # Per-sample times, in seconds since acquisition start
'data': { # Sensor-specific samples
'<channel>': [float, ...],
...
},
}

timestamps and the per-channel sample arrays inside data always have the same length: one timestamp per sample.

device, id, name, mac, packet_type, status and received_at are always present. The rest are optional, and some packet types carry extra fields — start_time packets add start_time, status packets add device_state, memory-download packets add download_progress, and any packet may carry samples_lost. See the CLI's Data output page for the full field reference.

sample_rate is missing from the first packet

sample_rate is measured by SiFi Bridge from inter-packet timing, so the very first packet of each sensor arrives without it. Treat it as optional:

rate = packet.get("sample_rate")

It converges on the configured rate and may oscillate slightly around it because of Bluetooth jitter. status and start_time packets never carry one.

ECG packet​

{
'device': 'BioPoint', 'id': 'BP_EF05', 'name': 'BioPoint_EF05',
'mac': 'FA:31:9F:7E:6A:A9',
'packet_type': 'ecg', 'status': 'ok',
'received_at': 1790040045.074,
'sample_rate': 500.25,
'timestamps': [0.0, 0.002, 0.004, ...],
'data': {'ecg': [0.00123, 0.00119, ...]},
}

EMG packet (BioPoint)​

A single EMG channel.

{
'packet_type': 'emg', 'status': 'ok',
'sample_rate': 2000.9,
'timestamps': [0.0, 0.0005, 0.001, ...],
'data': {'emg': [0.00012, 0.00015, ...]},
}

EMG packet (SiFiBand)​

The 8-channel array, delivered as emg_armband.

{
'packet_type': 'emg_armband', 'status': 'ok',
'timestamps': [0.0, 0.000625, ...],
'data': {
'emg0': [...], 'emg1': [...], 'emg2': [...], 'emg3': [...],
'emg4': [...], 'emg5': [...], 'emg6': [...], 'emg7': [...],
},
}

EDA packet​

{
'packet_type': 'eda', 'status': 'ok',
'sample_rate': 50.03,
'timestamps': [0.0, 0.02],
'data': {'eda': [0.0, 0.0]},
}

IMU packet​

Quaternion orientation and acceleration in g.

{
'packet_type': 'imu', 'status': 'ok',
'sample_rate': 100.0,
'timestamps': [0.0, 0.01, 0.02],
'data': {
'qw': [0.892717, ...], 'qx': [-0.417280, ...],
'qy': [0.170099, ...], 'qz': [0.0, ...],
'ax': [-2.045347, ...], 'ay': [-5.017566, ...],
'az': [3.999683, ...],
},
}

PPG packet​

{
'packet_type': 'ppg', 'status': 'ok',
'sample_rate': 100.05,
'timestamps': [0.0, 0.01],
'data': {
'ir': [7243.27, ...], 'r': [8071.35, ...],
'g': [8063.50, ...], 'b': [8083.54, ...],
},
}

Temperature packet​

Skin temperature in °C.

{
'packet_type': 'temperature', 'status': 'ok',
'sample_rate': 1.0,
'timestamps': [0.0, 1.0],
'data': {'temperature': [24.281916, 24.281916]},
}

Status packet​

Emitted about once a second while set_status_updates(True) is on.

{
'packet_type': 'status', 'status': 'recording',
'received_at': 1790040045.433,
'timestamps': [1790040045.433],
'data': {
'temperature': [0.0],
'battery_%': [85.0],
'memory_used_kbytes': [37336.625],
},
'device_state': 'connected_memory_ble_working',
}

Start-time packet​

Sent once at the beginning of an acquisition, giving the wall-clock time the recording started.

{
'packet_type': 'start_time', 'status': 'ok',
'timestamps': [1790040045.044],
'data': {
'year': [2026.0], 'month': [9.0], 'day': [21.0],
'hour': [21.0], 'minute': [20.0], 'second': [45.0],
},
'start_time': 1790040045.0,
}

Event packet​

Emitted by send_event(), and readable with get_event().

{
'packet_type': 'event', 'status': 'ok',
'timestamps': [1.51],
'data': {'event': [2.0]},
}

The value in data['event'] is a counter that increments with each event in the acquisition, and timestamps places it relative to the recording start.