feat(test): BLE test mode for validating alerts, CAN speed, and buzzer
Add TEST_ENTER/TEST_EXIT commands via BLE to enter/exit test mode. In test mode, inference results are suppressed; testers can directly trigger BLE JSON (TEST_BLE *), CAN+Buzzer actions (TEST_CAN *), or run sequenced full-coverage tests (TEST_BLE_ALL, TEST_CAN_ALL, TEST_ALL). Auto-exits after 60s idle. Routes unrecognised BLE commands through new bt_uart_set_extra_cmd_cb() hook.
This commit is contained in:
parent
286291fcfd
commit
953b7a348a
@ -43,6 +43,10 @@ void bt_uart_set_rent_status(int status);
|
||||
typedef int (*bt_intervention_fn)(void);
|
||||
void bt_uart_set_intervention_cb(bt_intervention_fn fn);
|
||||
|
||||
/* Extra command callback — called for unrecognised BLE commands. */
|
||||
typedef void (*bt_extra_cmd_fn)(const char *cmd);
|
||||
void bt_uart_set_extra_cmd_cb(bt_extra_cmd_fn fn);
|
||||
|
||||
/* Reset handshake and notify peer; DX-BT24 is passthrough — no HW disconnect API. */
|
||||
void bt_uart_disconnect(void);
|
||||
|
||||
|
||||
@ -40,4 +40,7 @@ void event_recorder_provide_frame(void);
|
||||
* level=0 時 type 傳 NULL */
|
||||
void fire_collision_warning(int level, const char *type);
|
||||
|
||||
/* Test mode: 1=test (inference results skipped), 0=normal. */
|
||||
extern volatile int g_test_mode;
|
||||
|
||||
#endif /* EVENT_RECORDER_H */
|
||||
|
||||
@ -144,6 +144,7 @@ static void print_yolo_result(kp_app_yolo_result_t *yolo_data)
|
||||
yolo_data->boxes[i].x2, yolo_data->boxes[i].y2, yolo_data->boxes[i].score, yolo_data->boxes[i].class_num);
|
||||
}
|
||||
#else
|
||||
if (g_test_mode) return;
|
||||
/* ── [YOLO] collision_warning(2.2.5)─────────────────────────────
|
||||
* 偵測到 vehicle(class=2) 時送 BLE notify。
|
||||
* 狀態有變化(進入/離開)才送,debounce 2 秒,避免頻繁發送。
|
||||
@ -504,6 +505,7 @@ int app_header_recv_inference(uint32_t buf_addr, bool *bl_run_next_inference)
|
||||
printf("[%s] STDC inference error %u\n", __func__, header_stamp->status_code);
|
||||
return header_stamp->status_code;
|
||||
}
|
||||
if (g_test_mode) return KP_SUCCESS;
|
||||
stdc_inf_result_t *stdc_res = (stdc_inf_result_t *)header_stamp;
|
||||
stdc_analysis_t *ana = &stdc_res->stdc_result.analysis;
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@ static pthread_mutex_t s_rent_mtx = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
/* ── Cart-control intervention callback ──────────────────────────────────── */
|
||||
static bt_intervention_fn s_intervention_cb = NULL;
|
||||
static bt_extra_cmd_fn s_extra_cmd_cb = NULL;
|
||||
|
||||
/* ── BLE Handshake context ───────────────────────────────────────────────── */
|
||||
static handshake_ctx_t s_handshake_ctx;
|
||||
@ -150,6 +151,8 @@ static void handle_command(const char *json)
|
||||
pthread_mutex_unlock(&s_rent_mtx);
|
||||
printf("[BT CMD] rent processed\n");
|
||||
reply_rent_status();
|
||||
} else if (s_extra_cmd_cb) {
|
||||
s_extra_cmd_cb(json);
|
||||
} else {
|
||||
printf("[BT RX] unrecognised command\n");
|
||||
}
|
||||
@ -530,3 +533,8 @@ void bt_uart_set_intervention_cb(bt_intervention_fn fn)
|
||||
{
|
||||
s_intervention_cb = fn;
|
||||
}
|
||||
|
||||
void bt_uart_set_extra_cmd_cb(bt_extra_cmd_fn fn)
|
||||
{
|
||||
s_extra_cmd_cb = fn;
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include <MemBroker/mem_broker.h>
|
||||
#include <vmf/video_snapshot_mechanism.h>
|
||||
@ -186,6 +187,13 @@ static int g_last_left_alert = 0;
|
||||
static int g_last_right_alert = 0;
|
||||
static int g_last_collision_warning = 0;
|
||||
|
||||
volatile int g_test_mode = 0;
|
||||
|
||||
static time_t s_test_mode_entered = 0;
|
||||
static volatile int s_test_all_running = 0;
|
||||
#define TEST_MODE_TIMEOUT_SEC 60
|
||||
#define TEST_OBS_DEFAULT_SEC 10
|
||||
|
||||
/* *URL / network helpers (Channel B only)**/
|
||||
|
||||
static void parse_url_into(const char *url,
|
||||
@ -422,6 +430,264 @@ static void fire_json_async(const char *event_id, const char *type, int level)
|
||||
bt_uart_send_json(json);
|
||||
}
|
||||
|
||||
/* ── Test mode helpers ───────────────────────────────────────────────────── */
|
||||
|
||||
static void str_toupper(char *s)
|
||||
{
|
||||
for (; *s; s++) *s = (char)toupper((unsigned char)*s);
|
||||
}
|
||||
|
||||
static void test_reply(const char *msg)
|
||||
{
|
||||
bt_uart_send_json(msg);
|
||||
printf("[TEST] reply: %s\n", msg);
|
||||
}
|
||||
|
||||
static void test_mode_exit_internal(const char *reason)
|
||||
{
|
||||
g_test_mode = 0;
|
||||
s_test_mode_entered = 0;
|
||||
can_ctrl_cmd_t ctrl = { .speed = SPEED_LIMIT_NORMAL,
|
||||
.led_gpio = 0, .led_enable = 0,
|
||||
.keepalive_interval_ms = 0 };
|
||||
can_bus_send_control_cmd(&ctrl);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_OFF);
|
||||
printf("[TEST] exit test mode (%s)\n", reason);
|
||||
test_reply("TEST_EXIT_OK");
|
||||
}
|
||||
|
||||
static void test_ble_violation(int level)
|
||||
{
|
||||
char event_id[24], date[32];
|
||||
snprintf(event_id, sizeof(event_id), "TEST%ld", (long)time(NULL));
|
||||
now_iso(date, sizeof(date));
|
||||
char json[192];
|
||||
snprintf(json, sizeof(json),
|
||||
"{\"response_type\":\"violation\","
|
||||
"\"content\":{\"id\":\"%s\",\"date\":\"%s\","
|
||||
"\"type\":\"lane\",\"level\":%d}}",
|
||||
event_id, date, level);
|
||||
bt_uart_send_json(json);
|
||||
char ack[64];
|
||||
snprintf(ack, sizeof(ack), "TEST_ACK BLE VIOLATION %d OK", level);
|
||||
test_reply(ack);
|
||||
}
|
||||
|
||||
static void test_ble_collision(int level, const char *type)
|
||||
{
|
||||
char type_str[24];
|
||||
if (level && type && type[0])
|
||||
snprintf(type_str, sizeof(type_str), "\"%s\"", type);
|
||||
else
|
||||
snprintf(type_str, sizeof(type_str), "null");
|
||||
char json[128];
|
||||
snprintf(json, sizeof(json),
|
||||
"{\"response_type\":\"collision_warning\","
|
||||
"\"content\":{\"level\":%d,\"type\":%s}}",
|
||||
level, type_str);
|
||||
bt_uart_send_json(json);
|
||||
char ack[64];
|
||||
snprintf(ack, sizeof(ack), "TEST_ACK BLE COLLISION %d OK", level);
|
||||
test_reply(ack);
|
||||
}
|
||||
|
||||
static void test_ble_alert(int ll, const char *lt, int rl, const char *rt)
|
||||
{
|
||||
char ls[24], rs[24];
|
||||
if (ll && lt && lt[0]) snprintf(ls, sizeof(ls), "\"%s\"", lt);
|
||||
else snprintf(ls, sizeof(ls), "null");
|
||||
if (rl && rt && rt[0]) snprintf(rs, sizeof(rs), "\"%s\"", rt);
|
||||
else snprintf(rs, sizeof(rs), "null");
|
||||
char json[256];
|
||||
snprintf(json, sizeof(json),
|
||||
"{\"response_type\":\"alert\","
|
||||
"\"content\":{"
|
||||
"\"left\":{\"level\":%d,\"type\":%s},"
|
||||
"\"right\":{\"level\":%d,\"type\":%s}}}",
|
||||
ll, ls, rl, rs);
|
||||
bt_uart_send_json(json);
|
||||
char ack[96];
|
||||
snprintf(ack, sizeof(ack),
|
||||
"TEST_ACK BLE ALERT left=%d right=%d OK", ll, rl);
|
||||
test_reply(ack);
|
||||
}
|
||||
|
||||
static void test_can_event(const char *event, int level,
|
||||
uint8_t speed, buzzer_pattern_t pat)
|
||||
{
|
||||
can_ctrl_cmd_t ctrl = { .speed = speed, .led_gpio = 0,
|
||||
.led_enable = 0, .keepalive_interval_ms = 0 };
|
||||
can_bus_send_control_cmd(&ctrl);
|
||||
buzzer_set_pattern(pat);
|
||||
char ack[96];
|
||||
snprintf(ack, sizeof(ack),
|
||||
"TEST_ACK CAN %s %d speed=%u buzzer=%s",
|
||||
event, level, (unsigned)speed,
|
||||
pat == BUZZER_PATTERN_COLLISION ? "collision" :
|
||||
pat == BUZZER_PATTERN_ALERT ? "alert" :
|
||||
pat == BUZZER_PATTERN_GRASS ? "grass" : "off");
|
||||
test_reply(ack);
|
||||
}
|
||||
|
||||
static void *test_ble_all_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
test_reply("TEST_BLE_ALL_START");
|
||||
test_ble_violation(1); sleep(3);
|
||||
test_ble_violation(0); sleep(2);
|
||||
test_ble_collision(1, "vehicle"); sleep(3);
|
||||
test_ble_collision(0, NULL); sleep(2);
|
||||
test_ble_alert(1, "tree", 0, NULL); sleep(3);
|
||||
test_ble_alert(0, NULL, 0, NULL); sleep(2);
|
||||
test_ble_alert(1, "tree", 1, "vehicle"); sleep(3);
|
||||
test_ble_alert(0, NULL, 0, NULL); sleep(2);
|
||||
test_reply("TEST_BLE_ALL_DONE");
|
||||
test_mode_exit_internal("BLE_ALL complete");
|
||||
s_test_all_running = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *test_can_all_thread(void *arg)
|
||||
{
|
||||
int obs = arg ? (int)(intptr_t)arg : TEST_OBS_DEFAULT_SEC;
|
||||
test_reply("TEST_CAN_ALL_START");
|
||||
test_can_event("VIOLATION", 1, SPEED_LIMIT_ALERT, BUZZER_PATTERN_GRASS); sleep(obs);
|
||||
test_can_event("VIOLATION", 0, SPEED_LIMIT_NORMAL, BUZZER_PATTERN_OFF); sleep(3);
|
||||
test_can_event("COLLISION", 1, SPEED_LIMIT_STOP, BUZZER_PATTERN_COLLISION); sleep(obs);
|
||||
test_can_event("COLLISION", 0, SPEED_LIMIT_NORMAL, BUZZER_PATTERN_OFF); sleep(3);
|
||||
test_can_event("ALERT", 1, SPEED_LIMIT_ALERT, BUZZER_PATTERN_ALERT); sleep(obs);
|
||||
test_can_event("ALERT", 0, SPEED_LIMIT_NORMAL, BUZZER_PATTERN_OFF); sleep(3);
|
||||
test_reply("TEST_CAN_BUZZER_ONLY_START");
|
||||
buzzer_set_pattern(BUZZER_PATTERN_GRASS); sleep(4);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_ALERT); sleep(4);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_COLLISION); sleep(4);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_OFF);
|
||||
test_reply("TEST_CAN_ALL_DONE");
|
||||
test_mode_exit_internal("CAN_ALL complete");
|
||||
s_test_all_running = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *test_all_thread(void *arg)
|
||||
{
|
||||
int obs = arg ? (int)(intptr_t)arg : TEST_OBS_DEFAULT_SEC;
|
||||
test_reply("TEST_ALL_START");
|
||||
test_reply("TEST_ALL_BLE_GROUP_START");
|
||||
test_ble_violation(1); sleep(3);
|
||||
test_ble_violation(0); sleep(2);
|
||||
test_ble_collision(1, "vehicle"); sleep(3);
|
||||
test_ble_collision(0, NULL); sleep(2);
|
||||
test_ble_alert(1, "tree", 0, NULL); sleep(3);
|
||||
test_ble_alert(0, NULL, 0, NULL); sleep(2);
|
||||
test_ble_alert(1, "tree", 1, "vehicle"); sleep(3);
|
||||
test_ble_alert(0, NULL, 0, NULL); sleep(2);
|
||||
test_reply("TEST_ALL_BLE_GROUP_DONE");
|
||||
sleep(3);
|
||||
test_reply("TEST_ALL_CAN_GROUP_START");
|
||||
test_can_event("VIOLATION", 1, SPEED_LIMIT_ALERT, BUZZER_PATTERN_GRASS); sleep(obs);
|
||||
test_can_event("VIOLATION", 0, SPEED_LIMIT_NORMAL, BUZZER_PATTERN_OFF); sleep(3);
|
||||
test_can_event("COLLISION", 1, SPEED_LIMIT_STOP, BUZZER_PATTERN_COLLISION); sleep(obs);
|
||||
test_can_event("COLLISION", 0, SPEED_LIMIT_NORMAL, BUZZER_PATTERN_OFF); sleep(3);
|
||||
test_can_event("ALERT", 1, SPEED_LIMIT_ALERT, BUZZER_PATTERN_ALERT); sleep(obs);
|
||||
test_can_event("ALERT", 0, SPEED_LIMIT_NORMAL, BUZZER_PATTERN_OFF); sleep(3);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_GRASS); sleep(4);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_ALERT); sleep(4);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_COLLISION); sleep(4);
|
||||
buzzer_set_pattern(BUZZER_PATTERN_OFF);
|
||||
test_reply("TEST_ALL_CAN_GROUP_DONE");
|
||||
test_reply("TEST_ALL_DONE");
|
||||
test_mode_exit_internal("ALL complete");
|
||||
s_test_all_running = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void handle_test_cmd(const char *raw)
|
||||
{
|
||||
char cmd[128];
|
||||
strncpy(cmd, raw, sizeof(cmd) - 1);
|
||||
cmd[sizeof(cmd) - 1] = '\0';
|
||||
str_toupper(cmd);
|
||||
printf("[TEST CMD] %s\n", cmd);
|
||||
|
||||
if (g_test_mode) s_test_mode_entered = time(NULL);
|
||||
|
||||
if (strncmp(cmd, "TEST_ENTER", 10) == 0) {
|
||||
if (g_test_mode) { test_reply("TEST_ACK ALREADY_IN_TEST"); return; }
|
||||
g_test_mode = 1;
|
||||
s_test_mode_entered = time(NULL);
|
||||
test_reply("TEST_ACK ENTER");
|
||||
return;
|
||||
}
|
||||
if (strncmp(cmd, "TEST_EXIT", 9) == 0) {
|
||||
s_test_all_running = 0;
|
||||
test_mode_exit_internal("user request");
|
||||
return;
|
||||
}
|
||||
if (!g_test_mode) { test_reply("TEST_ERR NOT_IN_TEST_MODE"); return; }
|
||||
|
||||
if (strncmp(cmd, "TEST_BLE_ALL", 12) == 0) {
|
||||
if (s_test_all_running) { test_reply("TEST_ERR ALL_ALREADY_RUNNING"); return; }
|
||||
s_test_all_running = 1;
|
||||
pthread_t tid; pthread_create(&tid, NULL, test_ble_all_thread, NULL); pthread_detach(tid);
|
||||
} else if (strncmp(cmd, "TEST_BLE VIOLATION", 18) == 0) {
|
||||
int lv = atoi(cmd + 18);
|
||||
test_ble_violation(lv ? 1 : 0);
|
||||
} else if (strncmp(cmd, "TEST_BLE COLLISION", 18) == 0) {
|
||||
int lv = atoi(cmd + 18);
|
||||
test_ble_collision(lv ? 1 : 0, lv ? "vehicle" : NULL);
|
||||
} else if (strncmp(cmd, "TEST_BLE ALERT", 14) == 0) {
|
||||
int ll = 0, rl = 0;
|
||||
char lt[16] = "null", rt[16] = "null";
|
||||
sscanf(cmd + 14, " %d %15s %d %15s", &ll, lt, &rl, rt);
|
||||
test_ble_alert(ll, strcmp(lt, "NULL") == 0 ? NULL : lt,
|
||||
rl, strcmp(rt, "NULL") == 0 ? NULL : rt);
|
||||
} else if (strncmp(cmd, "TEST_CAN_ALL", 12) == 0) {
|
||||
if (s_test_all_running) { test_reply("TEST_ERR ALL_ALREADY_RUNNING"); return; }
|
||||
int obs = TEST_OBS_DEFAULT_SEC;
|
||||
sscanf(cmd + 12, " %d", &obs);
|
||||
if (obs <= 0 || obs > 60) obs = TEST_OBS_DEFAULT_SEC;
|
||||
s_test_all_running = 1;
|
||||
pthread_t tid; pthread_create(&tid, NULL, test_can_all_thread, (void*)(intptr_t)obs); pthread_detach(tid);
|
||||
} else if (strncmp(cmd, "TEST_CAN VIOLATION", 18) == 0) {
|
||||
int lv = atoi(cmd + 18);
|
||||
test_can_event("VIOLATION", lv,
|
||||
lv ? SPEED_LIMIT_ALERT : SPEED_LIMIT_NORMAL,
|
||||
lv ? BUZZER_PATTERN_GRASS : BUZZER_PATTERN_OFF);
|
||||
} else if (strncmp(cmd, "TEST_CAN COLLISION", 18) == 0) {
|
||||
int lv = atoi(cmd + 18);
|
||||
test_can_event("COLLISION", lv,
|
||||
lv ? SPEED_LIMIT_STOP : SPEED_LIMIT_NORMAL,
|
||||
lv ? BUZZER_PATTERN_COLLISION : BUZZER_PATTERN_OFF);
|
||||
} else if (strncmp(cmd, "TEST_CAN ALERT", 14) == 0) {
|
||||
int ll = 0, rl = 0;
|
||||
sscanf(cmd + 14, " %d %d", &ll, &rl);
|
||||
int active = ll || rl;
|
||||
test_can_event("ALERT", active,
|
||||
active ? SPEED_LIMIT_ALERT : SPEED_LIMIT_NORMAL,
|
||||
active ? BUZZER_PATTERN_ALERT : BUZZER_PATTERN_OFF);
|
||||
} else if (strncmp(cmd, "TEST_BUZZER", 11) == 0) {
|
||||
char pat[16] = "OFF";
|
||||
sscanf(cmd + 11, " %15s", pat);
|
||||
buzzer_pattern_t p = BUZZER_PATTERN_OFF;
|
||||
if (strcmp(pat, "COLLISION") == 0) p = BUZZER_PATTERN_COLLISION;
|
||||
else if (strcmp(pat, "ALERT") == 0) p = BUZZER_PATTERN_ALERT;
|
||||
else if (strcmp(pat, "GRASS") == 0) p = BUZZER_PATTERN_GRASS;
|
||||
buzzer_set_pattern(p);
|
||||
char ack[48];
|
||||
snprintf(ack, sizeof(ack), "TEST_ACK BUZZER %s OK", pat);
|
||||
test_reply(ack);
|
||||
} else if (strncmp(cmd, "TEST_ALL", 8) == 0) {
|
||||
if (s_test_all_running) { test_reply("TEST_ERR ALL_ALREADY_RUNNING"); return; }
|
||||
int obs = TEST_OBS_DEFAULT_SEC;
|
||||
sscanf(cmd + 8, " %d", &obs);
|
||||
if (obs <= 0 || obs > 60) obs = TEST_OBS_DEFAULT_SEC;
|
||||
s_test_all_running = 1;
|
||||
pthread_t tid; pthread_create(&tid, NULL, test_all_thread, (void*)(intptr_t)obs); pthread_detach(tid);
|
||||
} else {
|
||||
test_reply("TEST_ERR UNKNOWN_CMD");
|
||||
}
|
||||
}
|
||||
|
||||
/* collision_warning notify (§2.2.5) */
|
||||
void fire_collision_warning(int level, const char *type)
|
||||
{
|
||||
@ -666,6 +932,7 @@ void event_recorder_init(const char *upload_url,
|
||||
s_enabled,
|
||||
s_up_host, s_up_port, s_up_path,
|
||||
s_sd_path, sd_max_mb, s_upload_delay_ms);
|
||||
bt_uart_set_extra_cmd_cb(handle_test_cmd);
|
||||
}
|
||||
|
||||
/* Recv callback: drives state machine */
|
||||
@ -674,6 +941,14 @@ void event_recorder_update(const stdc_analysis_t *ana)
|
||||
#if 1
|
||||
if (!s_enabled) return;
|
||||
|
||||
if (g_test_mode) {
|
||||
if (s_test_mode_entered > 0 && !s_test_all_running) {
|
||||
if (time(NULL) - s_test_mode_entered >= TEST_MODE_TIMEOUT_SEC)
|
||||
test_mode_exit_internal("timeout");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int on_grass = ana->on_grass;
|
||||
int grass_trigger = on_grass && ana->is_moving;
|
||||
uint8_t speed_val = 0;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user