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; } 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; } } 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]; } 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]; }