From 9efe6be4b10d214d09a2d2459a962e7fdf4a12ef Mon Sep 17 00:00:00 2001 From: Tobias Roeddiger Date: Wed, 11 Feb 2026 08:14:55 +0100 Subject: [PATCH] Add OpenRing PPG sensor parsing and configuration --- lib/src/models/devices/open_ring_factory.dart | 17 +++++++ .../open_ring_value_parser.dart | 46 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/lib/src/models/devices/open_ring_factory.dart b/lib/src/models/devices/open_ring_factory.dart index f286411..2fb9f0d 100644 --- a/lib/src/models/devices/open_ring_factory.dart +++ b/lib/src/models/devices/open_ring_factory.dart @@ -36,6 +36,14 @@ class OpenRingFactory extends WearableFactory { ], sensorHandler: sensorHandler, ), + OpenRingSensorConfiguration( + name: "PPG", + values: [ + OpenRingSensorConfigurationValue(key: "On", cmd: OpenRingGatt.cmdPPGQ2, subOpcode: 0x01), + OpenRingSensorConfigurationValue(key: "Off", cmd: OpenRingGatt.cmdPPGQ2, subOpcode: 0x00), + ], + sensorHandler: sensorHandler, + ), ]; List sensors = [ OpenRingSensor( @@ -56,6 +64,15 @@ class OpenRingFactory extends WearableFactory { axisUnits: ["dps", "dps", "dps"], sensorHandler: sensorHandler, ), + OpenRingSensor( + sensorId: OpenRingGatt.cmdPPGQ2, + sensorName: "PPG", + chartTitle: "PPG", + shortChartTitle: "PPG", + axisNames: ["Green", "Red", "Infrared"], + axisUnits: ["raw", "raw", "raw"], + sensorHandler: sensorHandler, + ), ]; final w = OpenRing( diff --git a/lib/src/utils/sensor_value_parser/open_ring_value_parser.dart b/lib/src/utils/sensor_value_parser/open_ring_value_parser.dart index e77c42b..b6b393f 100644 --- a/lib/src/utils/sensor_value_parser/open_ring_value_parser.dart +++ b/lib/src/utils/sensor_value_parser/open_ring_value_parser.dart @@ -70,6 +70,19 @@ class OpenRingValueParser extends SensorValueParser { default: throw Exception("Unknown sub-opcode for sensor data: $subOpcode"); } + case 0x32: // PPG Q2 + switch (subOpcode) { + case 0x00: + result = const []; + case 0x01: + result = _parsePpg( + data: payload, + receiveTs: _lastTs, + baseHeader: baseHeader, + ); + default: + throw Exception("Unknown sub-opcode for PPG data: $subOpcode"); + } default: throw Exception("Unknown command: $cmd"); @@ -151,4 +164,37 @@ class OpenRingValueParser extends SensorValueParser { 'Z': data.getInt16(4, Endian.little), }; } + + List> _parsePpg({ + required ByteData data, + required int receiveTs, + required Map baseHeader, + }) { + if (data.lengthInBytes % 12 != 0) { + throw Exception("Invalid data length for PPG: ${data.lengthInBytes}"); + } + + final int nSamples = data.lengthInBytes ~/ 12; + if (nSamples == 0) return const []; + + final List> parsedData = []; + for (int i = 0; i < data.lengthInBytes; i += 12) { + final int sampleIndex = i ~/ 12; + final int ts = receiveTs + sampleIndex * _samplePeriodMs; + + final ByteData sample = ByteData.sublistView(data, i, i + 12); + + parsedData.add({ + ...baseHeader, + "timestamp": ts, + "PPG": { + "Green": sample.getInt32(0, Endian.little), + "Red": sample.getInt32(4, Endian.little), + "Infrared": sample.getInt32(8, Endian.little), + }, + }); + } + + return parsedData; + } }