Skip to content
Open
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
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Release

on:
push:
tags:
- 'v*'

permissions:
contents: write

jobs:
release:
name: Create Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: gyronics_protobuf.proto
generate_release_notes: true
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## [0.1.0] - 2026-01-25

### Added
- Initial release of the Gyronics Wearable Protocol definitions (`gyronics_protobuf.proto`).
- GitHub Actions workflow for automated releases.
- README documentation.
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Gyronics Wearable Protocol

This repository contains the Protocol Buffers definition (`gyronics_protobuf.proto`) for the Gyronics Wearable communication protocol. This protocol defines the message structure for communication between the wearable device and a host application (e.g., mobile app, desktop SDK).

## Overview

The protocol is designed around a single `Envelope` message that encapsulates all communication. It supports:
- **Handshake**: Version negotiation and device identification.
- **Control Plane**: Request/Response pattern for system commands, sensor configuration, and device settings.
- **Data Plane**: High-frequency streaming of sensor data (IMU, Gestures).

## File Structure

- `gyronics_protobuf.proto`: The core protocol definition.

## Protocol Details

### Transport Layer

Messages are framed with a 4-byte big-endian length prefix followed by the serialized `Envelope` protobuf message.

```
[uint32_be length][Envelope bytes]
```

### Envelope

The `Envelope` message is the top-level container.

- `sequence_number`: Monotonic counter for debugging/tracing.
- `sent_at_unix_ms`: Sender timestamp for observability.
- `payload`: One of `hello`, `hello_ack`, `request`, `response`, or `event`.

### Handshake

1. **Client** sends `Hello` with `schema_version` and `app_id`.
2. **Server** responds with `HelloAck` containing `schema_version`, `server_version`, and `device_info`.

*Note: No other messages are valid until the handshake is complete.*

### Control Plane

Requests and Responses are correlated via a `request_id`.

**Requests (`Request`):**
- **System**: `Ping`, `GetBattery`, `GetDeviceInfo`.
- **Sensor**: `Subscribe`, `Unsubscribe`.
- **Config**: `UseBLE`.

**Responses (`Response`):**
- Matches the `request_id` of the request.
- Contains either a specific result (System, Sensor, Config) or an `Error`.

### Data Plane

Streaming data is sent as `StreamEvent` messages.

- `stream_id`: Identifies the subscription.
- `uptime_us`: Device uptime in microseconds (for alignment).
- `seq`: Per-stream sequence number.
- `data`:
- `ImuSample`: Accelerometer, Gyroscope, Magnetometer, and fused orientation (Roll, Pitch, Yaw).
- `GestureUpdate`: Detected gesture name and confidence.

## Usage

To generate code for your language of choice, use `protoc`:

```bash
# Example for Python
protoc --python_out=. gyronics_protobuf.proto

# Example for Go
protoc --go_out=. gyronics_protobuf.proto
```

## License

See [LICENSE](LICENSE) file.
273 changes: 273 additions & 0 deletions gyronics_protobuf.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
syntax = "proto3";

package gyronics;

// ============================================================================
// Transport Envelope
// ============================================================================
// Framing (outside protobuf):
// [uint32_be length][Envelope bytes]
//
// Contract:
// - First message on every connection MUST be Hello
// - Server responds with HelloAck
// - All other messages are invalid before handshake completes
message Envelope {
// Monotonic per-connection sequence number.
// Debugging / tracing only.
uint64 sequence_number = 1;

// Absolute wall-clock send time (Unix epoch, ms).
// Logging / observability only.
int64 sent_at_unix_ms = 2;

oneof payload {
Hello hello = 10;
HelloAck hello_ack = 11;

Request request = 20;
Response response = 21;

StreamEvent event = 30;
}
}

// ============================================================================
// Handshake
// ============================================================================

message Hello {
// Major protocol version expected by the client.
uint32 schema_version = 1;

// Identifies the external application (label only).
// Recommended: reverse-DNS (e.g. "com.example.myapp").
// Used for Logging / Observability.
string app_id = 2;
}

message HelloAck {
// Protocol version selected by the server.
uint32 schema_version = 1;

// Server implementation version (informational).
string server_version = 2;

// Device metadata as known to the server from config.
// Not necessarily a live probe of a currently connected device.
DeviceInfo device_info = 3;
}

// ============================================================================
// Control Plane — Requests
// ============================================================================

message Request {
// Client-chosen ID for correlating responses.
// Must be unique among in-flight requests on this connection.
uint64 id = 1;

oneof target {
SystemRequest system = 10;
SensorRequest sensor = 11;
ConfigRequest config = 12;
}
}

// --------------------
// System Requests
// --------------------

message SystemRequest {
oneof command {
Ping ping = 1;
GetBattery get_battery = 2;
GetDeviceInfo get_device_info = 3;
}

message Ping {
uint64 nonce = 1;
}

message GetBattery {}

message GetDeviceInfo {}
}

// --------------------
// Sensor Requests
// --------------------

message SensorRequest {
oneof command {
Subscribe subscribe = 1;
Unsubscribe unsubscribe = 2;
}

message Subscribe {
SensorType type = 1;
}

message Unsubscribe {
uint64 stream_id = 1;
}
}

// --------------------
// Config Requests
// --------------------

message ConfigRequest {
oneof command {
UseBLE use_ble = 1;
}

message UseBLE {
bool enabled = 1;
}
}

// ============================================================================
// Control Plane — Responses
// ============================================================================

message Response {
// Matches Request.id
uint64 request_id = 1;

oneof body {
Error error = 2;

SystemResponse system = 10;
SensorResponse sensor = 11;
ConfigResponse config = 12;
}
}

message Error {
enum Code {
CODE_UNSPECIFIED = 0;

INVALID_ARGUMENT = 1;
NOT_FOUND = 2;
UNSUPPORTED = 3;

INTERNAL = 10;
TIMEOUT = 11;
NOT_READY = 13;
}

Code code = 1;
string message = 2;
map<string, string> details = 3;
}

// --------------------
// System Responses
// --------------------

message SystemResponse {
oneof result {
Pong pong = 1;
BatteryStatus battery = 2;
DeviceInfo device_info = 3;
}

message Pong {
uint64 nonce = 1;
}
}

message BatteryStatus {
uint32 percentage = 1; // 0–100
bool is_charging = 2;
}

message DeviceInfo {
string device_name = 1;
string firmware_version = 2;
bool has_mag = 3;
}

// --------------------
// Sensor Responses
// --------------------

message SensorResponse {
oneof result {
SubscribeResult subscribe = 1;
UnsubscribeResult unsubscribe = 2;
}
}

message SubscribeResult {
uint64 stream_id = 1;
SensorType type = 2;
}

message UnsubscribeResult {
uint64 stream_id = 1;
}

// --------------------
// Config Responses
// --------------------

message ConfigResponse {
oneof result {
BLESet ble_set = 1;
}

message BLESet {}
}

// ============================================================================
// Data Plane — Streaming
// ============================================================================

enum SensorType {
SENSOR_TYPE_UNSPECIFIED = 0;
SENSOR_TYPE_IMU_RAW = 1; // Uses ImuSample with roll, pitch, yaw set to 0
SENSOR_TYPE_IMU_ORIENTATION = 2; // Uses ImuSample with acc, gyr and mag set to 0
SENSOR_TYPE_IMU_ALL = 3; // All fields valid
SENSOR_TYPE_AI_PROCESSED_IMU = 4; // Derived IMU stream from AI pipeline; same ImuSample layout
SENSOR_TYPE_AI_GESTURE = 5;
}

message StreamEvent {
// Identifies the subscription instance.
uint64 stream_id = 1;

// Monotonic timestamp relative to device/server start.
// Use for ordering, alignment, latency.
uint64 uptime_us = 2;

// Per-stream sequence number (detect drops).
uint32 seq = 3;

oneof data {
ImuSample imu = 10;
GestureUpdate gesture = 11;
}
}

message Vector3 {
float x = 1;
float y = 2;
float z = 3;
}

message ImuSample {
Vector3 acc = 1;
Vector3 gyr = 2;
Vector3 mag = 3; // May be unavailable. See DeviceInfo.has_mag
float roll = 4;
float pitch = 5;
float yaw = 6;
}

message GestureUpdate {
string name = 1;
float confidence = 2;
}