#ifndef CLIENT_REGISTRY_H #define CLIENT_REGISTRY_H #include "board_input.h" #include "esp_err.h" #include #include #include #define CLIENT_REGISTRY_MAX 16 #define CLIENT_MAC_LEN 6 typedef struct { uint32_t id; bool available; bool used; uint8_t mac[CLIENT_MAC_LEN]; /** Milliseconds since boot when last packet was received from this client. */ uint32_t last_ping_at; /** Milliseconds since boot when last heartbeat / SLAVE_INFO was accepted. */ uint32_t last_success_ping_at; uint32_t version; /** Accel deadzone in raw LSB per axis (master copy for ESP-NOW config). */ uint32_t accel_deadzone; /** Latest accel from slave ESP-NOW stream (master only). */ bool accel_valid; int16_t accel_x; int16_t accel_y; int16_t accel_z; uint32_t accel_updated_at; /** Host-enabled ESP-NOW accel stream to master. */ bool accel_stream_enabled; /** Host-enabled ESP-NOW tap notify flags. */ bool tap_notify_single; bool tap_notify_double; bool tap_notify_triple; /** Latest tap from slave ESP-NOW (master only, short-lived cache). */ bool tap_valid; uint32_t tap_kind; uint32_t tap_updated_at; /** Latest LiPo ADC from slave ESP-NOW battery report (~30 s). */ bool lipo1_valid; bool lipo2_valid; uint32_t lipo1_mv; uint32_t lipo2_mv; uint32_t battery_updated_at; } client_info_t; #define CLIENT_REGISTRY_DEFAULT_ACCEL_DEADZONE 100u /** Tap events older than this are discarded (matches accel stream interval). */ #define CLIENT_REGISTRY_TAP_MAX_AGE_MS 16u void client_registry_init(void); /** Milliseconds since boot (same clock as stored ping timestamps). */ uint32_t client_registry_now_ms(void); /** Ms elapsed since timestamp; 0 if timestamp is 0. */ uint32_t client_registry_ms_since(uint32_t timestamp); /** Register or refresh a client; updates both ping timestamps. */ esp_err_t client_registry_upsert(const uint8_t mac[CLIENT_MAC_LEN], uint32_t id, uint32_t version, bool available, bool used, bool *out_is_new); /** * Record a successful heartbeat (or initial slave info). * Sets available=true and updates last_success_ping_at (and last_ping_at). * If client was inactive, sets *out_reactivated=true. */ esp_err_t client_registry_heartbeat(const uint8_t mac[CLIENT_MAC_LEN], uint32_t id, uint32_t version, bool used, bool *out_is_new, bool *out_reactivated); /** Mark clients inactive when last_success_ping_at is older than timeout_ms. */ void client_registry_check_timeouts(uint32_t timeout_ms); size_t client_registry_count(void); const client_info_t *client_registry_at(size_t index); const client_info_t *client_registry_find_by_mac(const uint8_t mac[CLIENT_MAC_LEN]); const client_info_t *client_registry_find_by_id(uint32_t id); esp_err_t client_registry_set_accel_deadzone(uint32_t client_id, uint32_t deadzone); esp_err_t client_registry_get_accel_deadzone(uint32_t client_id, uint32_t *deadzone_out); /** Push deadzone to all active registry entries; returns count updated. */ size_t client_registry_set_accel_deadzone_all(uint32_t deadzone); /** Store latest accel sample from a slave (matched by sender MAC). */ esp_err_t client_registry_update_accel(const uint8_t mac[CLIENT_MAC_LEN], uint32_t slave_id, int16_t x, int16_t y, int16_t z); esp_err_t client_registry_set_accel_stream(uint32_t client_id, bool enabled); esp_err_t client_registry_get_accel_stream(uint32_t client_id, bool *enabled_out); size_t client_registry_set_accel_stream_all(bool enabled); esp_err_t client_registry_set_tap_notify(uint32_t client_id, bool single, bool double_tap, bool triple); esp_err_t client_registry_get_tap_notify(uint32_t client_id, bool *single_out, bool *double_tap_out, bool *triple_out); size_t client_registry_set_tap_notify_all(bool single, bool double_tap, bool triple); /** Store tap event from slave (matched by sender MAC). kind: 1=single, 2=double, 3=triple. */ esp_err_t client_registry_update_tap(const uint8_t mac[CLIENT_MAC_LEN], uint32_t slave_id, uint32_t kind); /** Drop cached tap if older than CLIENT_REGISTRY_TAP_MAX_AGE_MS. */ void client_registry_expire_tap(client_info_t *info); /** Clear cached tap after UART snapshot or expiry. */ void client_registry_clear_tap(client_info_t *info); /** * If client has a fresh tap (age <= CLIENT_REGISTRY_TAP_MAX_AGE_MS), copy it out * and clear the cache. Returns true when an event was returned. */ bool client_registry_take_tap(uint32_t client_id, uint32_t *kind_out, uint32_t *age_ms_out); /** Master local LiPo (client_id 0 in UART battery responses). */ void client_registry_set_master_battery(const board_lipo_reading_t *reading); bool client_registry_get_master_battery(board_lipo_reading_t *reading_out, uint32_t *age_ms_out); /** Store latest battery report from a slave (matched by sender MAC). */ esp_err_t client_registry_update_battery(const uint8_t mac[CLIENT_MAC_LEN], uint32_t slave_id, bool lipo1_valid, uint32_t lipo1_mv, bool lipo2_valid, uint32_t lipo2_mv); #endif