Add BMA456 tap detection with ESP-NOW notify and host snapshot API.

Slaves forward configured tap kinds to the master; goTool exposes CLI, dashboard, REST, and WebSocket with separate notify vs receive and 2s display cache.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-29 20:42:57 +02:00
co-authored by Cursor
parent 3cb0b5bbe9
commit a8d4d42920
34 changed files with 3138 additions and 241 deletions
+143
View File
@@ -257,6 +257,31 @@ static void clear_client_accel(client_slot_t *slot) {
slot->info.accel_updated_at = 0;
}
static void clear_client_tap(client_slot_t *slot) {
if (slot == NULL) {
return;
}
slot->info.tap_valid = false;
slot->info.tap_kind = 0;
slot->info.tap_updated_at = 0;
}
static bool tap_kind_enabled(const client_info_t *info, uint32_t kind) {
if (info == NULL) {
return false;
}
switch (kind) {
case 1:
return info->tap_notify_single;
case 2:
return info->tap_notify_double;
case 3:
return info->tap_notify_triple;
default:
return false;
}
}
esp_err_t client_registry_set_accel_stream(uint32_t client_id, bool enabled) {
for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) {
if (!s_clients[i].active || s_clients[i].info.id != client_id) {
@@ -325,6 +350,124 @@ esp_err_t client_registry_update_accel(const uint8_t mac[CLIENT_MAC_LEN],
return ESP_OK;
}
esp_err_t client_registry_set_tap_notify(uint32_t client_id, bool single,
bool double_tap, bool triple) {
for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) {
if (!s_clients[i].active || s_clients[i].info.id != client_id) {
continue;
}
s_clients[i].info.tap_notify_single = single;
s_clients[i].info.tap_notify_double = double_tap;
s_clients[i].info.tap_notify_triple = triple;
if (!single && !double_tap && !triple) {
clear_client_tap(&s_clients[i]);
}
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}
esp_err_t client_registry_get_tap_notify(uint32_t client_id, bool *single_out,
bool *double_tap_out,
bool *triple_out) {
if (single_out == NULL || double_tap_out == NULL || triple_out == NULL) {
return ESP_ERR_INVALID_ARG;
}
const client_info_t *info = client_registry_find_by_id(client_id);
if (info == NULL) {
return ESP_ERR_NOT_FOUND;
}
*single_out = info->tap_notify_single;
*double_tap_out = info->tap_notify_double;
*triple_out = info->tap_notify_triple;
return ESP_OK;
}
size_t client_registry_set_tap_notify_all(bool single, bool double_tap,
bool triple) {
size_t n = 0;
for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) {
if (!s_clients[i].active) {
continue;
}
s_clients[i].info.tap_notify_single = single;
s_clients[i].info.tap_notify_double = double_tap;
s_clients[i].info.tap_notify_triple = triple;
if (!single && !double_tap && !triple) {
clear_client_tap(&s_clients[i]);
}
n++;
}
return n;
}
esp_err_t client_registry_update_tap(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t slave_id, uint32_t kind) {
if (mac == NULL || kind < 1 || kind > 3) {
return ESP_ERR_INVALID_ARG;
}
client_slot_t *slot = find_slot(mac);
if (slot == NULL) {
return ESP_ERR_NOT_FOUND;
}
if (slot->info.id != slave_id) {
return ESP_ERR_INVALID_ARG;
}
if (!tap_kind_enabled(&slot->info, kind)) {
return ESP_ERR_INVALID_STATE;
}
slot->info.tap_kind = kind;
slot->info.tap_valid = true;
slot->info.tap_updated_at = now_ms();
return ESP_OK;
}
void client_registry_expire_tap(client_info_t *info) {
if (info == NULL || !info->tap_valid) {
return;
}
if (client_registry_ms_since(info->tap_updated_at) >
CLIENT_REGISTRY_TAP_MAX_AGE_MS) {
info->tap_valid = false;
info->tap_kind = 0;
info->tap_updated_at = 0;
}
}
void client_registry_clear_tap(client_info_t *info) {
if (info == NULL) {
return;
}
info->tap_valid = false;
info->tap_kind = 0;
info->tap_updated_at = 0;
}
bool client_registry_take_tap(uint32_t client_id, uint32_t *kind_out,
uint32_t *age_ms_out) {
for (size_t i = 0; i < CLIENT_REGISTRY_MAX; i++) {
if (!s_clients[i].active || s_clients[i].info.id != client_id) {
continue;
}
client_info_t *info = &s_clients[i].info;
client_registry_expire_tap(info);
if (!info->tap_valid) {
return false;
}
if (kind_out != NULL) {
*kind_out = info->tap_kind;
}
if (age_ms_out != NULL) {
*age_ms_out = client_registry_ms_since(info->tap_updated_at);
}
client_registry_clear_tap(info);
return true;
}
return false;
}
void client_registry_set_master_battery(const board_lipo_reading_t *reading) {
if (reading == NULL) {
return;