Add UART ACCEL_READ command for on-demand BMA456 samples.

Expose MessageType 24 with protobuf response (success, x, y, z in raw LSB),
firmware handler with mutex-safe I2C read, goTool `accel` CLI, and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-28 19:55:02 +02:00
co-authored by Cursor
parent 16c521f71c
commit ba20544762
16 changed files with 479 additions and 99 deletions
+1
View File
@@ -18,6 +18,7 @@ idf_component_register(
"cmd/cmd_version.c"
"cmd/cmd_client_info.c"
"cmd/cmd_accel_deadzone.c"
"cmd/cmd_accel_read.c"
"cmd/cmd_espnow_unicast_test.c"
"cmd/cmd_espnow_find_me.c"
"cmd/cmd_restart.c"
+26 -1
View File
@@ -217,6 +217,7 @@ Host and master speak nanopb-encoded `UartMessage` inside UART frames (byte 0 =
| 21 | `OTA_SLAVE_PROGRESS` | Implemented (`cmd/cmd_ota_slave_progress.c`) — query per-slave ESP-NOW OTA progress |
| 22 | `FIND_ME` | Implemented (`cmd/cmd_espnow_find_me.c`) — `client_id=0` local ring, `>0` ESP-NOW to slave |
| 23 | `RESTART` | Implemented (`cmd/cmd_restart.c`) — `client_id=0` reboot master, `>0` ESP-NOW reboot slave |
| 24 | `ACCEL_READ` | Implemented (`cmd/cmd_accel_read.c`) — on-demand BMA456 XYZ (raw LSB) |
Regenerate C code:
@@ -310,6 +311,29 @@ Sets the **software** deadzone used by `bosch456.c` when logging accel (see [BMA
**Response:** `accel_deadzone_response` with applied `deadzone`, `success`, and `slaves_updated` (ESP-NOW count).
### ACCEL_READ command
Read the **current** BMA456 accelerometer sample on this node (master or slave with sensor). Values are raw LSB in the configured **±2g** range; they are **not** filtered by the software deadzone (unlike periodic `ACC X=…` logs in `bosch456.c`).
**Request:** framed `18` (`0x18`) only, or `18` + empty `accel_read_request`.
**Response:** `accel_read_response`:
| Field | Meaning |
|-------|---------|
| `success` | `true` if BMA456 is ready and I2C read succeeded |
| `x`, `y`, `z` | Raw accel LSB (`sint32`; meaningful only when `success`) |
If the sensor was not probed at boot (`bma456_is_ready()` false), `success` is `false` and axes are zero.
Host:
```bash
go run . -port /dev/ttyUSB0 accel
```
Implementation: `bma456_read_accel()` in `bosch456.c` (mutex with the 10 Hz poll task), handler in `cmd/cmd_accel_read.c`.
### ESPNOW_UNICAST_TEST command
Minimal master→slave ESP-NOW unicast check (no BMA456). Use this before debugging `ACCEL_DEADZONE` unicast.
@@ -454,7 +478,8 @@ Target: ESP32-S3. Close serial monitor on the UART adapter port before running `
| `cmd/cmd_version.c/h` | VERSION handler |
| `cmd/cmd_client_info.c/h` | CLIENT_INFO handler |
| `client_registry.c/h` | Registered slave table |
| `bosch456.c/h` | BMA456H I2C driver, accel poll, tap INT, deadzone filter |
| `bosch456.c/h` | BMA456H I2C driver, accel poll, on-demand read, tap INT, deadzone filter |
| `cmd/cmd_accel_read.c` | UART `ACCEL_READ` — current accel XYZ |
| `board_input.c/h` | Taster GPIO12, LiPo ADC on GPIO1 / GPIO12 |
| `pod_settings.c/h` | NVS persistence (accel deadzone, …) |
| `led_ring.c/h` | LED ring (digit display, progress bar) |
+45 -4
View File
@@ -14,6 +14,7 @@
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/idf_additions.h"
#include "freertos/semphr.h"
#include <rom/ets_sys.h>
#include <string.h>
@@ -34,6 +35,7 @@ static int16_t s_last_z;
static bool s_have_last_sample;
static volatile bool s_int_pending;
static SemaphoreHandle_t s_accel_mutex;
static esp_err_t check_bma4(const char *api_name, int8_t rslt);
@@ -121,6 +123,30 @@ void bma456_set_accel_deadzone(uint32_t deadzone_lsb) {
uint32_t bma456_get_accel_deadzone(void) { return s_accel_deadzone; }
esp_err_t bma456_read_accel(int16_t *x, int16_t *y, int16_t *z) {
if (!s_bma456_ready || x == NULL || y == NULL || z == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (s_accel_mutex == NULL ||
xSemaphoreTake(s_accel_mutex, pdMS_TO_TICKS(500)) != pdTRUE) {
return ESP_ERR_TIMEOUT;
}
struct bma4_accel sens_data = {0};
int8_t ret = bma4_read_accel_xyz(&sens_data, &s_bma456);
xSemaphoreGive(s_accel_mutex);
if (ret != BMA4_OK) {
bma4_error_codes_print_result("bma4_read_accel_xyz", ret);
return ESP_FAIL;
}
*x = sens_data.x;
*y = sens_data.y;
*z = sens_data.z;
return ESP_OK;
}
void bma456_report_accel_if_changed(int16_t x, int16_t y, int16_t z) {
if (!s_bma456_ready || !sample_exceeds_deadzone(x, y, z)) {
return;
@@ -187,11 +213,19 @@ static void read_sensor_task(void *param) {
struct bma4_accel sens_data = {0};
while (1) {
int8_t ret = bma4_read_accel_xyz(&sens_data, &s_bma456);
if (ret == BMA4_OK) {
bool got_sample = false;
if (s_accel_mutex != NULL &&
xSemaphoreTake(s_accel_mutex, pdMS_TO_TICKS(500)) == pdTRUE) {
int8_t ret = bma4_read_accel_xyz(&sens_data, &s_bma456);
xSemaphoreGive(s_accel_mutex);
if (ret == BMA4_OK) {
got_sample = true;
} else {
bma4_error_codes_print_result("bma4_read_accel_xyz", ret);
}
}
if (got_sample) {
bma456_report_accel_if_changed(sens_data.x, sens_data.y, sens_data.z);
} else {
bma4_error_codes_print_result("bma4_read_accel_xyz", ret);
}
if (s_int_pending) {
@@ -343,6 +377,13 @@ esp_err_t init_bma456(i2c_master_bus_handle_t bus_handle) {
goto fail;
}
if (s_accel_mutex == NULL) {
s_accel_mutex = xSemaphoreCreateMutex();
if (s_accel_mutex == NULL) {
goto fail;
}
}
if (xTaskCreate(read_sensor_task, "bma456_poll", 4096, NULL, 1, NULL) !=
pdPASS) {
goto fail;
+3
View File
@@ -35,4 +35,7 @@ uint32_t bma456_get_accel_deadzone(void);
/** Log accel when any axis moved more than deadzone since last reported sample. */
void bma456_report_accel_if_changed(int16_t x, int16_t y, int16_t z);
/** On-demand read of current accel XYZ (raw LSB). Returns ESP_ERR_INVALID_STATE if sensor not ready. */
esp_err_t bma456_read_accel(int16_t *x, int16_t *y, int16_t *z);
#endif
+34
View File
@@ -0,0 +1,34 @@
#include "bosch456.h"
#include "cmd_accel_read.h"
#include "uart_cmd.h"
static const char *TAG = "[ACCEL_READ]";
static void reply(bool success, int16_t x, int16_t y, int16_t z) {
alox_UartMessage response;
uart_cmd_init_response(&response, alox_MessageType_ACCEL_READ,
alox_UartMessage_accel_read_response_tag);
response.payload.accel_read_response.success = success;
response.payload.accel_read_response.x = x;
response.payload.accel_read_response.y = y;
response.payload.accel_read_response.z = z;
uart_cmd_send(&response, TAG);
}
static void handle_accel_read(const uint8_t *data, size_t len) {
(void)data;
(void)len;
int16_t x = 0;
int16_t y = 0;
int16_t z = 0;
if (bma456_read_accel(&x, &y, &z) == ESP_OK) {
reply(true, x, y, z);
return;
}
reply(false, 0, 0, 0);
}
void cmd_accel_read_register(void) {
uart_cmd_register(alox_MessageType_ACCEL_READ, handle_accel_read);
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef CMD_ACCEL_READ_H
#define CMD_ACCEL_READ_H
void cmd_accel_read_register(void);
#endif
+2
View File
@@ -48,6 +48,8 @@ static const char *message_type_name(uint16_t id) {
return "FIND_ME";
case alox_MessageType_RESTART:
return "RESTART";
case alox_MessageType_ACCEL_READ:
return "ACCEL_READ";
default:
return "UNKNOWN";
}
+2
View File
@@ -1,6 +1,7 @@
#include "app_config.h"
#include "cmd_handler.h"
#include "cmd_accel_deadzone.h"
#include "cmd_accel_read.h"
#include "cmd_espnow_unicast_test.h"
#include "cmd_espnow_find_me.h"
#include "cmd_restart.h"
@@ -177,6 +178,7 @@ void app_main(void) {
cmd_version_register();
cmd_client_info_register();
cmd_accel_deadzone_register();
cmd_accel_read_register();
cmd_espnow_unicast_test_register();
cmd_espnow_find_me_register();
cmd_restart_register();
+6
View File
@@ -36,6 +36,12 @@ PB_BIND(alox_AccelDeadzoneRequest, alox_AccelDeadzoneRequest, AUTO)
PB_BIND(alox_AccelDeadzoneResponse, alox_AccelDeadzoneResponse, AUTO)
PB_BIND(alox_AccelReadRequest, alox_AccelReadRequest, AUTO)
PB_BIND(alox_AccelReadResponse, alox_AccelReadResponse, AUTO)
PB_BIND(alox_EspNowUnicastTestRequest, alox_EspNowUnicastTestRequest, AUTO)
+54 -4
View File
@@ -27,7 +27,8 @@ typedef enum _alox_MessageType {
alox_MessageType_OTA_START_ESPNOW = 20,
alox_MessageType_OTA_SLAVE_PROGRESS = 21,
alox_MessageType_FIND_ME = 22,
alox_MessageType_RESTART = 23
alox_MessageType_RESTART = 23,
alox_MessageType_ACCEL_READ = 24
} alox_MessageType;
/* Struct definitions */
@@ -88,6 +89,18 @@ typedef struct _alox_AccelDeadzoneResponse {
uint32_t slaves_updated;
} alox_AccelDeadzoneResponse;
/* Host → device: read current BMA456 accelerometer sample (raw LSB, ±2g range). */
typedef struct _alox_AccelReadRequest {
char dummy_field;
} alox_AccelReadRequest;
typedef struct _alox_AccelReadResponse {
bool success;
int32_t x;
int32_t y;
int32_t z;
} alox_AccelReadResponse;
typedef struct _alox_EspNowUnicastTestRequest {
uint32_t client_id;
uint32_t seq;
@@ -218,6 +231,8 @@ typedef struct _alox_UartMessage {
alox_EspNowFindMeResponse espnow_find_me_response;
alox_RestartRequest restart_request;
alox_RestartResponse restart_response;
alox_AccelReadRequest accel_read_request;
alox_AccelReadResponse accel_read_response;
} payload;
} alox_UartMessage;
@@ -228,8 +243,8 @@ extern "C" {
/* Helper constants for enums */
#define _alox_MessageType_MIN alox_MessageType_UNKNOWN
#define _alox_MessageType_MAX alox_MessageType_RESTART
#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_RESTART+1))
#define _alox_MessageType_MAX alox_MessageType_ACCEL_READ
#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_ACCEL_READ+1))
#define alox_UartMessage_type_ENUMTYPE alox_MessageType
@@ -255,6 +270,8 @@ extern "C" {
@@ -269,6 +286,8 @@ extern "C" {
#define alox_ClientInputResponse_init_default {{{NULL}, NULL}}
#define alox_AccelDeadzoneRequest_init_default {0, 0, 0, 0}
#define alox_AccelDeadzoneResponse_init_default {0, 0, 0, 0}
#define alox_AccelReadRequest_init_default {0}
#define alox_AccelReadResponse_init_default {0, 0, 0, 0}
#define alox_EspNowUnicastTestRequest_init_default {0, 0}
#define alox_EspNowUnicastTestResponse_init_default {0, 0}
#define alox_LedRingProgressRequest_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
@@ -294,6 +313,8 @@ extern "C" {
#define alox_ClientInputResponse_init_zero {{{NULL}, NULL}}
#define alox_AccelDeadzoneRequest_init_zero {0, 0, 0, 0}
#define alox_AccelDeadzoneResponse_init_zero {0, 0, 0, 0}
#define alox_AccelReadRequest_init_zero {0}
#define alox_AccelReadResponse_init_zero {0, 0, 0, 0}
#define alox_EspNowUnicastTestRequest_init_zero {0, 0}
#define alox_EspNowUnicastTestResponse_init_zero {0, 0}
#define alox_LedRingProgressRequest_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
@@ -336,6 +357,10 @@ extern "C" {
#define alox_AccelDeadzoneResponse_client_id_tag 2
#define alox_AccelDeadzoneResponse_success_tag 3
#define alox_AccelDeadzoneResponse_slaves_updated_tag 4
#define alox_AccelReadResponse_success_tag 1
#define alox_AccelReadResponse_x_tag 2
#define alox_AccelReadResponse_y_tag 3
#define alox_AccelReadResponse_z_tag 4
#define alox_EspNowUnicastTestRequest_client_id_tag 1
#define alox_EspNowUnicastTestRequest_seq_tag 2
#define alox_EspNowUnicastTestResponse_success_tag 1
@@ -399,6 +424,8 @@ extern "C" {
#define alox_UartMessage_espnow_find_me_response_tag 20
#define alox_UartMessage_restart_request_tag 21
#define alox_UartMessage_restart_response_tag 22
#define alox_UartMessage_accel_read_request_tag 23
#define alox_UartMessage_accel_read_response_tag 24
/* Struct field encoding specification for nanopb */
#define alox_UartMessage_FIELDLIST(X, a) \
@@ -423,7 +450,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,led_ring_progress_response,payload.l
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_find_me_request,payload.espnow_find_me_request), 19) \
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_find_me_response,payload.espnow_find_me_response), 20) \
X(a, STATIC, ONEOF, MESSAGE, (payload,restart_request,payload.restart_request), 21) \
X(a, STATIC, ONEOF, MESSAGE, (payload,restart_response,payload.restart_response), 22)
X(a, STATIC, ONEOF, MESSAGE, (payload,restart_response,payload.restart_response), 22) \
X(a, STATIC, ONEOF, MESSAGE, (payload,accel_read_request,payload.accel_read_request), 23) \
X(a, STATIC, ONEOF, MESSAGE, (payload,accel_read_response,payload.accel_read_response), 24)
#define alox_UartMessage_CALLBACK NULL
#define alox_UartMessage_DEFAULT NULL
#define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack
@@ -447,6 +476,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,restart_response,payload.restart_res
#define alox_UartMessage_payload_espnow_find_me_response_MSGTYPE alox_EspNowFindMeResponse
#define alox_UartMessage_payload_restart_request_MSGTYPE alox_RestartRequest
#define alox_UartMessage_payload_restart_response_MSGTYPE alox_RestartResponse
#define alox_UartMessage_payload_accel_read_request_MSGTYPE alox_AccelReadRequest
#define alox_UartMessage_payload_accel_read_response_MSGTYPE alox_AccelReadResponse
#define alox_Ack_FIELDLIST(X, a) \
@@ -512,6 +543,19 @@ X(a, STATIC, SINGULAR, UINT32, slaves_updated, 4)
#define alox_AccelDeadzoneResponse_CALLBACK NULL
#define alox_AccelDeadzoneResponse_DEFAULT NULL
#define alox_AccelReadRequest_FIELDLIST(X, a) \
#define alox_AccelReadRequest_CALLBACK NULL
#define alox_AccelReadRequest_DEFAULT NULL
#define alox_AccelReadResponse_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, success, 1) \
X(a, STATIC, SINGULAR, SINT32, x, 2) \
X(a, STATIC, SINGULAR, SINT32, y, 3) \
X(a, STATIC, SINGULAR, SINT32, z, 4)
#define alox_AccelReadResponse_CALLBACK NULL
#define alox_AccelReadResponse_DEFAULT NULL
#define alox_EspNowUnicastTestRequest_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, client_id, 1) \
X(a, STATIC, SINGULAR, UINT32, seq, 2)
@@ -625,6 +669,8 @@ extern const pb_msgdesc_t alox_ClientInput_msg;
extern const pb_msgdesc_t alox_ClientInputResponse_msg;
extern const pb_msgdesc_t alox_AccelDeadzoneRequest_msg;
extern const pb_msgdesc_t alox_AccelDeadzoneResponse_msg;
extern const pb_msgdesc_t alox_AccelReadRequest_msg;
extern const pb_msgdesc_t alox_AccelReadResponse_msg;
extern const pb_msgdesc_t alox_EspNowUnicastTestRequest_msg;
extern const pb_msgdesc_t alox_EspNowUnicastTestResponse_msg;
extern const pb_msgdesc_t alox_LedRingProgressRequest_msg;
@@ -652,6 +698,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_ClientInputResponse_fields &alox_ClientInputResponse_msg
#define alox_AccelDeadzoneRequest_fields &alox_AccelDeadzoneRequest_msg
#define alox_AccelDeadzoneResponse_fields &alox_AccelDeadzoneResponse_msg
#define alox_AccelReadRequest_fields &alox_AccelReadRequest_msg
#define alox_AccelReadResponse_fields &alox_AccelReadResponse_msg
#define alox_EspNowUnicastTestRequest_fields &alox_EspNowUnicastTestRequest_msg
#define alox_EspNowUnicastTestResponse_fields &alox_EspNowUnicastTestResponse_msg
#define alox_LedRingProgressRequest_fields &alox_LedRingProgressRequest_msg
@@ -678,6 +726,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define ALOX_UART_MESSAGES_PB_H_MAX_SIZE alox_OtaSlaveProgressResponse_size
#define alox_AccelDeadzoneRequest_size 16
#define alox_AccelDeadzoneResponse_size 20
#define alox_AccelReadRequest_size 0
#define alox_AccelReadResponse_size 20
#define alox_Ack_size 0
#define alox_ClientInput_size 22
#define alox_EspNowFindMeRequest_size 6
+13
View File
@@ -22,6 +22,7 @@ enum MessageType {
OTA_SLAVE_PROGRESS = 21;
FIND_ME = 22;
RESTART = 23;
ACCEL_READ = 24;
}
message UartMessage {
@@ -48,6 +49,8 @@ message UartMessage {
EspNowFindMeResponse espnow_find_me_response = 20;
RestartRequest restart_request = 21;
RestartResponse restart_response = 22;
AccelReadRequest accel_read_request = 23;
AccelReadResponse accel_read_response = 24;
}
}
@@ -106,6 +109,16 @@ message AccelDeadzoneResponse {
uint32 slaves_updated = 4;
}
// Host → device: read current BMA456 accelerometer sample (raw LSB, ±2g range).
message AccelReadRequest {}
message AccelReadResponse {
bool success = 1;
sint32 x = 2;
sint32 y = 3;
sint32 z = 4;
}
message EspNowUnicastTestRequest {
uint32 client_id = 1;
uint32 seq = 2;