Combine cached accel and tap in one low-overhead master command for ~16 ms host polling. The dashboard uses a single live-stream toggle plus per-slave accel-stream controls; fix live_stream state so polling is not cleared every slow client refresh. Co-authored-by: Cursor <cursoragent@cursor.com>
357 lines
9.4 KiB
Protocol Buffer
357 lines
9.4 KiB
Protocol Buffer
syntax = "proto3";
|
||
|
||
import "nanopb.proto";
|
||
|
||
package alox;
|
||
|
||
enum MessageType {
|
||
UNKNOWN = 0;
|
||
ACK = 1;
|
||
ECHO = 2;
|
||
VERSION = 3;
|
||
CLIENT_INFO = 4;
|
||
CLIENT_INPUT = 5;
|
||
ACCEL_DEADZONE = 6;
|
||
ESPNOW_UNICAST_TEST = 7;
|
||
LED_RING = 8;
|
||
OTA_START = 16;
|
||
OTA_PAYLOAD = 17;
|
||
OTA_END = 18;
|
||
OTA_STATUS = 19;
|
||
OTA_START_ESPNOW = 20;
|
||
OTA_SLAVE_PROGRESS = 21;
|
||
FIND_ME = 22;
|
||
RESTART = 23;
|
||
ACCEL_SNAPSHOT = 24;
|
||
ACCEL_STREAM = 25;
|
||
BATTERY_STATUS = 26;
|
||
TAP_NOTIFY = 27;
|
||
TAP_SNAPSHOT = 28;
|
||
/** Combined cached accel + tap poll (one UART round-trip, ~16 ms cadence). */
|
||
CACHE_STATUS = 29;
|
||
}
|
||
|
||
message UartMessage {
|
||
MessageType type = 1;
|
||
oneof payload {
|
||
Ack ack_payload = 2;
|
||
EchoPayload echo_payload = 3;
|
||
VersionResponse version_response = 4;
|
||
ClientInfoResponse client_info_response = 5;
|
||
ClientInputResponse client_input_response = 6;
|
||
OtaStartPayload ota_start = 7;
|
||
OtaPayload ota_payload = 8;
|
||
OtaEndPayload ota_end = 9;
|
||
OtaStatusPayload ota_status = 10;
|
||
AccelDeadzoneRequest accel_deadzone_request = 11;
|
||
AccelDeadzoneResponse accel_deadzone_response = 12;
|
||
EspNowUnicastTestRequest espnow_unicast_test_request = 13;
|
||
EspNowUnicastTestResponse espnow_unicast_test_response = 14;
|
||
OtaSlaveProgressRequest ota_slave_progress_request = 15;
|
||
OtaSlaveProgressResponse ota_slave_progress_response = 16;
|
||
LedRingProgressRequest led_ring_progress_request = 17;
|
||
LedRingProgressResponse led_ring_progress_response = 18;
|
||
EspNowFindMeRequest espnow_find_me_request = 19;
|
||
EspNowFindMeResponse espnow_find_me_response = 20;
|
||
RestartRequest restart_request = 21;
|
||
RestartResponse restart_response = 22;
|
||
AccelSnapshotRequest accel_snapshot_request = 23;
|
||
AccelSnapshotResponse accel_snapshot_response = 24;
|
||
AccelStreamRequest accel_stream_request = 25;
|
||
AccelStreamResponse accel_stream_response = 26;
|
||
BatteryStatusRequest battery_status_request = 27;
|
||
BatteryStatusResponse battery_status_response = 28;
|
||
TapNotifyRequest tap_notify_request = 29;
|
||
TapNotifyResponse tap_notify_response = 30;
|
||
TapSnapshotRequest tap_snapshot_request = 31;
|
||
TapSnapshotResponse tap_snapshot_response = 32;
|
||
CacheStatusRequest cache_status_request = 33;
|
||
CacheStatusResponse cache_status_response = 34;
|
||
}
|
||
}
|
||
|
||
message Ack {}
|
||
|
||
message EchoPayload {
|
||
bytes data = 1;
|
||
}
|
||
|
||
message VersionResponse {
|
||
uint32 version = 1;
|
||
string git_hash = 2;
|
||
/** Active OTA app partition label, e.g. "ota_0" or "ota_1". */
|
||
string running_partition = 3;
|
||
}
|
||
|
||
message ClientInfo {
|
||
uint32 id = 1;
|
||
bool available = 2;
|
||
bool used = 3;
|
||
bytes mac = 4;
|
||
uint32 last_ping = 5;
|
||
uint32 last_success_ping = 6;
|
||
uint32 version = 7;
|
||
/** Master: ESP-NOW accel stream enabled for this slave. */
|
||
bool accel_stream_enabled = 8;
|
||
/** Master: ESP-NOW tap notify flags for this slave. */
|
||
bool tap_notify_single = 9;
|
||
bool tap_notify_double = 10;
|
||
bool tap_notify_triple = 11;
|
||
}
|
||
|
||
message ClientInfoResponse {
|
||
repeated ClientInfo clients = 1;
|
||
}
|
||
|
||
message ClientInput {
|
||
uint32 id = 1;
|
||
float lage_x = 2;
|
||
float lage_y = 3;
|
||
uint32 bitmask = 4;
|
||
}
|
||
|
||
message ClientInputResponse {
|
||
repeated ClientInput clients = 1;
|
||
}
|
||
|
||
// write=false: read deadzone; write=true: apply deadzone (LSB per axis, raw accel units).
|
||
// client_id 0 = local BMA456 on this node; >0 = slave id on master; ignored on slave.
|
||
// all_clients = true (master only): push deadzone to every registered slave via ESP-NOW.
|
||
message AccelDeadzoneRequest {
|
||
bool write = 1;
|
||
uint32 deadzone = 2;
|
||
uint32 client_id = 3;
|
||
bool all_clients = 4;
|
||
}
|
||
|
||
message AccelDeadzoneResponse {
|
||
uint32 deadzone = 1;
|
||
uint32 client_id = 2;
|
||
bool success = 3;
|
||
uint32 slaves_updated = 4;
|
||
}
|
||
|
||
// Host → master: enable/disable slave accel ESP-NOW stream (~16 ms per slave).
|
||
// write=false: read; write=true: apply. client_id 0 invalid for write (use >0 or all_clients).
|
||
message AccelStreamRequest {
|
||
bool write = 1;
|
||
bool enable = 2;
|
||
uint32 client_id = 3;
|
||
bool all_clients = 4;
|
||
}
|
||
|
||
message AccelStreamResponse {
|
||
bool enabled = 1;
|
||
uint32 client_id = 2;
|
||
bool success = 3;
|
||
uint32 slaves_updated = 4;
|
||
}
|
||
|
||
/** Host → master: read LiPo ADC voltages (master local and/or slaves via ESP-NOW). */
|
||
message BatteryStatusRequest {
|
||
/** 0 = master only; >0 = one slave; ignored when all_clients */
|
||
uint32 client_id = 1;
|
||
/** Master (client_id 0) plus every registered slave */
|
||
bool all_clients = 2;
|
||
}
|
||
|
||
message LipoReading {
|
||
bool valid = 1;
|
||
/** Estimated pack voltage in millivolts from ADC */
|
||
uint32 voltage_mv = 2;
|
||
}
|
||
|
||
message BatterySample {
|
||
uint32 client_id = 1;
|
||
LipoReading lipo1 = 2;
|
||
LipoReading lipo2 = 3;
|
||
/** Milliseconds since last ESP-NOW battery report from this pod. */
|
||
uint32 age_ms = 4;
|
||
}
|
||
|
||
message BatteryStatusResponse {
|
||
bool success = 1;
|
||
repeated BatterySample samples = 2 [(nanopb).max_count = 17];
|
||
}
|
||
|
||
// Host → master: read cached accel samples from slaves (only while stream enabled).
|
||
// client_id 0 = all registered slaves; otherwise one slave.
|
||
message AccelSnapshotRequest {
|
||
uint32 client_id = 1;
|
||
}
|
||
|
||
message AccelSample {
|
||
uint32 client_id = 1;
|
||
bool valid = 2;
|
||
sint32 x = 3;
|
||
sint32 y = 4;
|
||
sint32 z = 5;
|
||
/** Milliseconds since last ESP-NOW sample from this slave. */
|
||
uint32 age_ms = 6;
|
||
}
|
||
|
||
message AccelSnapshotResponse {
|
||
repeated AccelSample samples = 1 [(nanopb).max_count = 16];
|
||
}
|
||
|
||
/** Host → master: enable/disable tap ESP-NOW notify per slave (single/double/triple). */
|
||
message TapNotifyRequest {
|
||
bool write = 1;
|
||
uint32 client_id = 2;
|
||
bool all_clients = 3;
|
||
bool single = 4;
|
||
bool double_tap = 5;
|
||
bool triple = 6;
|
||
}
|
||
|
||
message TapNotifyResponse {
|
||
uint32 client_id = 1;
|
||
bool success = 2;
|
||
uint32 slaves_updated = 3;
|
||
bool single = 4;
|
||
bool double_tap = 5;
|
||
bool triple = 6;
|
||
}
|
||
|
||
enum TapKind {
|
||
TAP_NONE = 0;
|
||
TAP_SINGLE = 1;
|
||
TAP_DOUBLE = 2;
|
||
TAP_TRIPLE = 3;
|
||
}
|
||
|
||
/** Host → master: read cached tap events (discarded after reply or when age > 16 ms). */
|
||
message TapSnapshotRequest {
|
||
uint32 client_id = 1;
|
||
}
|
||
|
||
message TapEvent {
|
||
uint32 client_id = 1;
|
||
bool valid = 2;
|
||
TapKind kind = 3;
|
||
uint32 age_ms = 4;
|
||
}
|
||
|
||
message TapSnapshotResponse {
|
||
repeated TapEvent events = 1 [(nanopb).max_count = 16];
|
||
}
|
||
|
||
/** Host → master: one-shot read of subscribed cached slave data (no request body). */
|
||
message CacheStatusRequest {}
|
||
|
||
message CacheStatusResponse {
|
||
/** Slaves with accel_stream_enabled. */
|
||
repeated AccelSample accel = 1 [(nanopb).max_count = 16];
|
||
/** Slaves with any tap notify flag; pending taps are consumed (like TAP_SNAPSHOT). */
|
||
repeated TapEvent taps = 2 [(nanopb).max_count = 16];
|
||
}
|
||
|
||
message EspNowUnicastTestRequest {
|
||
uint32 client_id = 1;
|
||
uint32 seq = 2;
|
||
}
|
||
|
||
message EspNowUnicastTestResponse {
|
||
bool success = 1;
|
||
uint32 seq = 2;
|
||
}
|
||
|
||
// Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
|
||
// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink, 4=find-me, 5=all LEDs solid color.
|
||
message LedRingProgressRequest {
|
||
uint32 mode = 1;
|
||
/** 0–100: fraction of ring LEDs to light (mode=progress) */
|
||
uint32 progress = 2;
|
||
/** 0–10 (mode=digit) */
|
||
uint32 digit = 3;
|
||
uint32 r = 4;
|
||
uint32 g = 5;
|
||
uint32 b = 6;
|
||
/** 0–255 brightness scale; 0 = firmware default (~5 %) */
|
||
uint32 intensity = 7;
|
||
/** Pulse length in ms (mode=blink, default 350) */
|
||
uint32 blink_ms = 8;
|
||
/** Number of pulses (mode=blink, default 1) */
|
||
uint32 blink_count = 9;
|
||
/** 0 = master ring only; >0 = one slave; ignored when all_clients */
|
||
uint32 client_id = 10;
|
||
/** Broadcast to all registered slaves (and optionally master unless slaves_only) */
|
||
bool all_clients = 11;
|
||
/** With all_clients: do not change master ring */
|
||
bool slaves_only = 12;
|
||
}
|
||
|
||
message LedRingProgressResponse {
|
||
bool success = 1;
|
||
uint32 mode = 2;
|
||
uint32 progress = 3;
|
||
uint32 digit = 4;
|
||
uint32 client_id = 5;
|
||
uint32 slaves_updated = 6;
|
||
}
|
||
|
||
/** Host → master: find-me on local ring (client_id=0) or ESP-NOW unicast to one slave. */
|
||
message EspNowFindMeRequest {
|
||
uint32 client_id = 1;
|
||
}
|
||
|
||
message EspNowFindMeResponse {
|
||
bool success = 1;
|
||
uint32 client_id = 2;
|
||
}
|
||
|
||
/** Host → master: restart local node (client_id=0) or ESP-NOW unicast to one slave. */
|
||
message RestartRequest {
|
||
uint32 client_id = 1;
|
||
}
|
||
|
||
message RestartResponse {
|
||
bool success = 1;
|
||
uint32 client_id = 2;
|
||
}
|
||
|
||
// Host → device: begin UART OTA (erase inactive OTA slot; device replies OTA_STATUS).
|
||
message OtaStartPayload {
|
||
uint32 total_size = 1;
|
||
}
|
||
|
||
// Host → device: firmware chunk (up to 200 bytes); device buffers 4 KiB before flash write.
|
||
message OtaPayload {
|
||
uint32 seq = 1;
|
||
bytes data = 2 [(nanopb).max_size = 200];
|
||
}
|
||
|
||
// Host → device: no more payload; device flushes buffer and finalizes OTA.
|
||
message OtaEndPayload {}
|
||
|
||
// Device → host status (also used as ACK after each 4 KiB written).
|
||
// status: 1=preparing, 2=ready, 3=block_ack, 4=success, 5=failed, 6=distributing
|
||
message OtaStatusPayload {
|
||
uint32 status = 1;
|
||
uint32 bytes_written = 2;
|
||
uint32 target_slot = 3;
|
||
uint32 error = 4;
|
||
}
|
||
|
||
// Host → master: query ESP-NOW slave OTA progress (client_id 0 = all slaves in session).
|
||
message OtaSlaveProgressRequest {
|
||
uint32 client_id = 1;
|
||
}
|
||
|
||
message OtaSlaveProgressEntry {
|
||
uint32 client_id = 1;
|
||
uint32 bytes_written = 2;
|
||
uint32 total_bytes = 3;
|
||
/** 0=idle, 1=preparing, 2=ready, 3=distributing, 4=success, 5=failed */
|
||
uint32 status = 4;
|
||
uint32 error = 5;
|
||
}
|
||
|
||
message OtaSlaveProgressResponse {
|
||
bool active = 1;
|
||
uint32 total_bytes = 2;
|
||
uint32 aggregate_bytes = 3;
|
||
uint32 slave_count = 4;
|
||
repeated OtaSlaveProgressEntry slaves = 5 [(nanopb).max_count = 16];
|
||
}
|