diff --git a/include/host_stream/bt_uart.h b/include/host_stream/bt_uart.h index a9d85e7..f025c99 100644 --- a/include/host_stream/bt_uart.h +++ b/include/host_stream/bt_uart.h @@ -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); diff --git a/include/host_stream/event_recorder.h b/include/host_stream/event_recorder.h index 6e189e6..db14936 100644 --- a/include/host_stream/event_recorder.h +++ b/include/host_stream/event_recorder.h @@ -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 */ diff --git a/src/host_stream/app_header_init.c b/src/host_stream/app_header_init.c index 2a3def0..23e3131 100644 --- a/src/host_stream/app_header_init.c +++ b/src/host_stream/app_header_init.c @@ -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; diff --git a/src/host_stream/bt_uart.c b/src/host_stream/bt_uart.c index d240868..57cb0f3 100644 --- a/src/host_stream/bt_uart.c +++ b/src/host_stream/bt_uart.c @@ -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; +} diff --git a/src/host_stream/event_recorder.c b/src/host_stream/event_recorder.c index 065ed11..958e060 100644 --- a/src/host_stream/event_recorder.c +++ b/src/host_stream/event_recorder.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -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;