/** * DreamStack ESP-NOW Transport — Ultra-Low Latency Binary Protocol * * Sub-1ms signal delivery over ESP-NOW (WiFi direct, no router). * Binary packed frames instead of JSON for minimal overhead. * * Transport strategy: * ESP-NOW: real-time signals + events (<1ms, ≤250 bytes) * UDP: initial IR push + large payloads (~2ms, ≤1472 bytes) */ #pragma once #include #include #include "esp_now.h" // ─── ESP-NOW Frame Types ─── #define DS_NOW_SIG 0x20 // Single signal update (hub → panel) #define DS_NOW_SIG_BATCH 0x21 // Batch signal update (hub → panel) #define DS_NOW_TOUCH 0x30 // Touch event (panel → hub) #define DS_NOW_ACTION 0x31 // Button/widget action (panel → hub) #define DS_NOW_PING 0xFE // Heartbeat (bidirectional) #define DS_NOW_PONG 0xFD // Heartbeat response // ─── UDP Frame Types ─── #define DS_UDP_IR_PUSH 0x40 // Full IR JSON push (hub → panel) #define DS_UDP_IR_FRAG 0x41 // IR fragment for payloads > MTU #define DS_UDP_DISCOVER 0x42 // Panel discovery broadcast // ─── UDP Port ─── #define DS_UDP_PORT 9200 // ─── ESP-NOW Channel ─── #define DS_ESPNOW_CHANNEL 1 // ─── Max ESP-NOW payload ─── #define DS_ESPNOW_MAX_DATA 250 // ─── Signal Update Frame (7 bytes) ─── // Hub → Panel: update a single signal value typedef struct __attribute__((packed)) { uint8_t type; // DS_NOW_SIG uint16_t signal_id; // which signal (0-65535) int32_t value; // new value } ds_sig_frame_t; // ─── Signal Batch Frame (3 + 6*N bytes) ─── // Hub → Panel: update multiple signals at once typedef struct __attribute__((packed)) { uint16_t id; int32_t val; } ds_sig_entry_t; typedef struct __attribute__((packed)) { uint8_t type; // DS_NOW_SIG_BATCH uint8_t count; // number of signals (max ~40 in 250B) uint8_t seq; // sequence number (wrapping u8) // followed by `count` ds_sig_entry_t entries } ds_sig_batch_t; // ─── Touch Event Frame (8 bytes) ─── // Panel → Hub: touch on the display typedef struct __attribute__((packed)) { uint8_t type; // DS_NOW_TOUCH uint8_t node_id; // which UI node (from IR) uint8_t event; // 0=click, 1=long_press, 2=release, 3=drag uint8_t seq; // sequence number uint16_t x; // touch X coordinate uint16_t y; // touch Y coordinate } ds_touch_now_t; // ─── Action Event Frame (4 bytes) ─── // Panel → Hub: widget action (button click, toggle, etc.) typedef struct __attribute__((packed)) { uint8_t type; // DS_NOW_ACTION uint8_t node_id; // which widget uint8_t action; // 0=click, 1=toggle, 2=slide_change uint8_t seq; // sequence number } ds_action_frame_t; // ─── Heartbeat Frame (2 bytes) ─── typedef struct __attribute__((packed)) { uint8_t type; // DS_NOW_PING or DS_NOW_PONG uint8_t seq; // echo back on pong } ds_heartbeat_t; // ─── UDP IR Push Header (4 bytes + payload) ─── typedef struct __attribute__((packed)) { uint8_t magic[2]; // 0xD5, 0x7A uint16_t length; // JSON payload length // followed by `length` bytes of IR JSON } ds_ir_push_t; // ─── UDP IR Fragment Header (6 bytes + payload) ─── typedef struct __attribute__((packed)) { uint8_t magic[2]; // 0xD5, 0x7A uint8_t type; // DS_UDP_IR_FRAG uint8_t frag_id; // fragment index (0-based) uint8_t frag_total; // total fragments uint8_t seq; // group sequence // followed by fragment data (up to 1466 bytes) } ds_ir_frag_t; // ─── Callbacks ─── typedef void (*ds_signal_cb_t)(uint16_t signal_id, int32_t value); typedef void (*ds_ir_cb_t)(const char *ir_json, size_t length); // ─── Configuration ─── typedef struct { uint8_t hub_mac[6]; // Hub MAC address (set to FF:FF:FF:FF:FF:FF for broadcast) uint8_t channel; // WiFi channel (default: DS_ESPNOW_CHANNEL) ds_signal_cb_t on_signal; // Called when a signal update arrives ds_ir_cb_t on_ir_push; // Called when a full IR JSON arrives } ds_espnow_config_t; /** * Initialize ESP-NOW transport. * Sets up ESP-NOW, registers peer, starts UDP listener. * WiFi must be initialized first (STA or AP mode, no connection needed). */ esp_err_t ds_espnow_init(const ds_espnow_config_t *config); /** * Send an action event to the hub (panel → hub). * Encodes as ds_action_frame_t and sends via ESP-NOW. */ esp_err_t ds_espnow_send_action(uint8_t node_id, uint8_t action); /** * Send a touch event to the hub (panel → hub). */ esp_err_t ds_espnow_send_touch(uint8_t node_id, uint8_t event, uint16_t x, uint16_t y); /** * Send a heartbeat ping. */ esp_err_t ds_espnow_send_ping(void); /** * Deinitialize ESP-NOW transport. */ void ds_espnow_deinit(void);