Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions generated/c/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ add_library(open_earable_protocols STATIC
"src/audio_configuration_protocol.c"
"src/audio_response_protocol.c"
"src/button_protocol.c"
"src/imu_protocol.c"
"src/led_protocol.c"
"src/ppg_protocol.c"
"src/wireless_audio_configuration_protocol.c"
Expand Down
44 changes: 44 additions & 0 deletions generated/c/include/imu_protocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Generated from schemas/imu/protocol.yml. Do not edit by hand.
#pragma once

#include "protocol_runtime.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
* BLE only from firmware 2.3.0; six signed raw motion readings followed by three unchanged
* compensated magnetometer floats. See README for exact scales and firmware selection.
*/
typedef struct imu_compact_sample_t imu_compact_sample_t;
struct imu_compact_sample_t {
int16_t accel_x;
int16_t accel_y;
int16_t accel_z;
int16_t gyro_x;
int16_t gyro_y;
int16_t gyro_z;
float mag_x;
float mag_y;
float mag_z;
};


/**
* Encode a binary representation of this message. BLE only from firmware 2.3.0; six signed
* raw motion readings followed by three unchanged compensated magnetometer floats. See
* README for exact scales and firmware selection.
*/
protocol_status_t imu_compact_sample_encode(const imu_compact_sample_t *message, uint8_t *buffer, size_t buffer_size, size_t *bytes_written);
/**
* Decode a binary representation into this message. BLE only from firmware 2.3.0; six
* signed raw motion readings followed by three unchanged compensated magnetometer floats.
* See README for exact scales and firmware selection.
*/
protocol_status_t imu_compact_sample_decode(imu_compact_sample_t *message, const uint8_t *buffer, size_t buffer_size, size_t *bytes_read);

#ifdef __cplusplus
}
#endif

73 changes: 73 additions & 0 deletions generated/c/src/imu_protocol.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Generated from schemas/imu/protocol.yml. Do not edit by hand.
#include "imu_protocol.h"

static protocol_status_t imu_compact_sample_write(protocol_writer_t *writer, const imu_compact_sample_t *message) {
protocol_status_t status;
status = protocol_write_int16(writer, message->accel_x);
if (status != PROTOCOL_OK) return status;
status = protocol_write_int16(writer, message->accel_y);
if (status != PROTOCOL_OK) return status;
status = protocol_write_int16(writer, message->accel_z);
if (status != PROTOCOL_OK) return status;
status = protocol_write_int16(writer, message->gyro_x);
if (status != PROTOCOL_OK) return status;
status = protocol_write_int16(writer, message->gyro_y);
if (status != PROTOCOL_OK) return status;
status = protocol_write_int16(writer, message->gyro_z);
if (status != PROTOCOL_OK) return status;
status = protocol_write_float(writer, message->mag_x);
if (status != PROTOCOL_OK) return status;
status = protocol_write_float(writer, message->mag_y);
if (status != PROTOCOL_OK) return status;
status = protocol_write_float(writer, message->mag_z);
if (status != PROTOCOL_OK) return status;
return PROTOCOL_OK;
}

static protocol_status_t imu_compact_sample_read(protocol_reader_t *reader, imu_compact_sample_t *message) {
protocol_status_t status;
status = protocol_read_int16(reader, &message->accel_x);
if (status != PROTOCOL_OK) return status;
status = protocol_read_int16(reader, &message->accel_y);
if (status != PROTOCOL_OK) return status;
status = protocol_read_int16(reader, &message->accel_z);
if (status != PROTOCOL_OK) return status;
status = protocol_read_int16(reader, &message->gyro_x);
if (status != PROTOCOL_OK) return status;
status = protocol_read_int16(reader, &message->gyro_y);
if (status != PROTOCOL_OK) return status;
status = protocol_read_int16(reader, &message->gyro_z);
if (status != PROTOCOL_OK) return status;
status = protocol_read_float(reader, &message->mag_x);
if (status != PROTOCOL_OK) return status;
status = protocol_read_float(reader, &message->mag_y);
if (status != PROTOCOL_OK) return status;
status = protocol_read_float(reader, &message->mag_z);
if (status != PROTOCOL_OK) return status;
return PROTOCOL_OK;
}

protocol_status_t imu_compact_sample_encode(const imu_compact_sample_t *message, uint8_t *buffer, size_t buffer_size, size_t *bytes_written) {
protocol_writer_t writer = { buffer, buffer_size, 0 };
protocol_status_t status = imu_compact_sample_write(&writer, message);
if (status != PROTOCOL_OK) {
return status;
}
if (bytes_written != NULL) {
*bytes_written = writer.offset;
}
return PROTOCOL_OK;
}

protocol_status_t imu_compact_sample_decode(imu_compact_sample_t *message, const uint8_t *buffer, size_t buffer_size, size_t *bytes_read) {
protocol_reader_t reader = { buffer, buffer_size, 0 };
protocol_status_t status = imu_compact_sample_read(&reader, message);
if (status != PROTOCOL_OK) {
return status;
}
if (bytes_read != NULL) {
*bytes_read = reader.offset;
}
return PROTOCOL_OK;
}

1 change: 1 addition & 0 deletions generated/dart/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 0.0.4

- Define 24-byte BLE IMU samples for firmware 2.3.x; retain float values/units and the 36-byte SD layout.
- Define lossless 10-byte BLE PPG samples for firmware 2.3.x alongside the unchanged 16-byte legacy/SD layout.

## 0.0.3
Expand Down
1 change: 1 addition & 0 deletions generated/dart/lib/open_earable_protocols.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export 'src/protocol_runtime.dart'
export 'src/audio_configuration_protocol.dart';
export 'src/audio_response_protocol.dart';
export 'src/button_protocol.dart';
export 'src/imu_protocol.dart';
export 'src/led_protocol.dart';
export 'src/ppg_protocol.dart';
export 'src/wireless_audio_configuration_protocol.dart';
61 changes: 61 additions & 0 deletions generated/dart/lib/src/imu_protocol.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Generated from schemas/imu/protocol.yml. Do not edit by hand.
import 'dart:typed_data';
import 'protocol_runtime.dart';

/// BLE only from firmware 2.3.0; six signed raw motion readings followed by three unchanged
/// compensated magnetometer floats. See README for exact scales and firmware selection.
class ImuCompactSample {
/// Creates a ImuCompactSample value.
ImuCompactSample({required this.accel_x, required this.accel_y, required this.accel_z, required this.gyro_x, required this.gyro_y, required this.gyro_z, required this.mag_x, required this.mag_y, required this.mag_z});

final int accel_x;
final int accel_y;
final int accel_z;
final int gyro_x;
final int gyro_y;
final int gyro_z;
final double mag_x;
final double mag_y;
final double mag_z;

/// Decodes a complete ImuCompactSample value from [bytes].
factory ImuCompactSample.fromBytes(Uint8List bytes) {
final reader = ProtocolReader(bytes);
final value = ImuCompactSample._read(reader);
reader.finish();
return value;
}

static ImuCompactSample _read(ProtocolReader reader) {
final accel_x = reader.int16();
final accel_y = reader.int16();
final accel_z = reader.int16();
final gyro_x = reader.int16();
final gyro_y = reader.int16();
final gyro_z = reader.int16();
final mag_x = reader.float32();
final mag_y = reader.float32();
final mag_z = reader.float32();
return ImuCompactSample(accel_x: accel_x, accel_y: accel_y, accel_z: accel_z, gyro_x: gyro_x, gyro_y: gyro_y, gyro_z: gyro_z, mag_x: mag_x, mag_y: mag_y, mag_z: mag_z);
}

/// Encodes this value to the protocol binary representation.
Uint8List toBytes() {
final writer = ProtocolWriter();
_write(writer);
return writer.takeBytes();
}

void _write(ProtocolWriter writer) {
writer.int16(accel_x);
writer.int16(accel_y);
writer.int16(accel_z);
writer.int16(gyro_x);
writer.int16(gyro_y);
writer.int16(gyro_z);
writer.float32(mag_x);
writer.float32(mag_y);
writer.float32(mag_z);
}
}

38 changes: 38 additions & 0 deletions schemas/imu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# IMU sensor data

Keep sensor ID **0**, the existing sensor-stream UUIDs, and the existing packet
header/timestamps. Read each device's firmware version before subscribing:
firmware 2.1.x/2.2.x sends nine little-endian float32 values (36 bytes/sample);
firmware 2.3.x, including prereleases, sends the 24-byte sample below. Select
per connection and refresh after reconnect/FOTA; do not infer from packet size.

| Byte offsets | Wire value |
|---|---|
| 0–5 | Accelerometer X/Y/Z, three little-endian signed int16 counts |
| 6–11 | Gyroscope X/Y/Z, three little-endian signed int16 counts |
| 12–23 | Magnetometer X/Y/Z, three unchanged little-endian float32 values in µT |

Restore the six motion values to float32 before applying the existing parsing
scheme or exposing values to applications:

- Acceleration in m/s²: `float32(raw * 0.0005985504249110818)`.
This is exactly the firmware's float32 `(2.0f * 9.80665f) / 32768.0f` scale.
- Angular velocity in degrees/s: `float32(raw * 0.06103515625)`.
This is exactly `2000.0f / 32768.0f`.

These scales are part of the 2.3.x wire contract, matching the existing ±2 g
and ±2000 degrees/s sensor configuration. Recover counts by rounding the
existing float divided by its scale; reject values outside int16 or values
that do not reconstruct the original float32 exactly. Do not round, rescale,
or remove magnetometer values: they already include factory compensation.

The advertised parsing scheme, library values/types/units and phone CSVs stay
unchanged. **All SD/.oe IMU samples remain nine float32 values (36 bytes)**,
including recordings from 2.3.x. File readers must use the legacy layout.

The packet envelope is unchanged: sensor ID (uint8), payload byte length
(uint8), first timestamp (uint64 microseconds), samples, then a uint16 period
in microseconds for multi-sample packets only. All integers are little-endian.
A 244-byte notification holds nine compact samples (228 bytes), versus six
legacy samples (228 bytes). Sample payload shrinks by one third; actual BLE
throughput still depends on radio scheduling and batching.
26 changes: 26 additions & 0 deletions schemas/imu/protocol.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
protocol: imu
version: 1
description: BLE IMU samples for firmware 2.3.x. Logical values and all SD records remain nine float32 values.

messages:
compact_sample:
description: BLE only from firmware 2.3.0; six signed raw motion readings followed by three unchanged compensated magnetometer floats. See README for exact scales and firmware selection.
fields:
- name: accel_x
type: int16
- name: accel_y
type: int16
- name: accel_z
type: int16
- name: gyro_x
type: int16
- name: gyro_y
type: int16
- name: gyro_z
type: int16
- name: mag_x
type: float
- name: mag_y
type: float
- name: mag_z
type: float
43 changes: 43 additions & 0 deletions tests/test_imu_codec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Verify signed IMU fields and unchanged float32 magnetometer bytes."""
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest


class ImuCodecTest(unittest.TestCase):
def test_c_wire_layout(self):
cc = shutil.which('cc')
if cc is None:
self.skipTest('C compiler unavailable')
source = r'''
#include <assert.h>
#include <string.h>
#include "imu_protocol.h"
int main(void) {
const uint8_t expected[24] = {
0,128,255,255,0,0,1,0,0,64,255,127,
0,0,160,63,0,0,32,192,0,0,0,128};
imu_compact_sample_t value = {-32768,-1,0,1,16384,32767,1.25f,-2.5f,-0.0f};
uint8_t out[24]; size_t size;
assert(imu_compact_sample_encode(&value, out, sizeof(out), &size) == PROTOCOL_OK);
assert(size == 24 && memcmp(out, expected, sizeof(out)) == 0);
imu_compact_sample_t decoded;
assert(imu_compact_sample_decode(&decoded, out, sizeof(out), &size) == PROTOCOL_OK);
assert(decoded.accel_x == -32768 && decoded.accel_y == -1 && decoded.accel_z == 0);
assert(decoded.gyro_x == 1 && decoded.gyro_y == 16384 && decoded.gyro_z == 32767);
assert(memcmp(&decoded.mag_x, &value.mag_x, 3 * sizeof(float)) == 0);
for (size_t n = 0; n < 24; ++n)
assert(imu_compact_sample_decode(&decoded, out, n, &size) != PROTOCOL_OK);
}
'''
with tempfile.TemporaryDirectory() as directory:
path = Path(directory)
(path / 'test.c').write_text(source)
subprocess.run([cc, '-std=c99', '-Wall', '-Wextra', '-Werror',
'-Igenerated/c/include', str(path / 'test.c'),
'generated/c/src/imu_protocol.c',
'generated/c/src/protocol_runtime.c',
'-o', str(path / 'test')], check=True)
subprocess.run([str(path / 'test')], check=True)
Loading