diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..660db47 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,96 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +KL630 Host Stream Firmware — a real-time semantic segmentation system running on a Kneron KL630 AI SoC. It processes video from a Sony IMX662 DOL-HDR dual-exposure camera for golf course monitoring (pedestrian/vehicle detection via STDC semantic segmentation). The binary runs on embedded Linux (ARMv7-A, uClibc 1.0.34) and outputs via RTSP streaming, HDMI display, BLE (iPad app), and CAN bus (golf cart control). + +## Build + +Cross-compilation is Docker-based (Ubuntu 20.04 + `arm-linux-gnueabihf-gcc`). **Docker Desktop must be running.** + +```bash +# Install Python dependencies (one-time) +pip install -r requirements.txt + +# Compile via web UI (recommended) +python web_serve.py # then open http://localhost:8080/ and click "Compile" + +# Compile via CLI +python build_and_serve.py # outputs build/kp_firmware_host_stream + build/host_stream.ini +``` + +`compile.sh` is the script executed inside the Docker container — it performs the actual `arm-linux-gnueabihf-gcc` invocation. The `Dockerfile` defines the build image. + +## Deploy + +```bash +# Via web UI: click "Deploy to KL630" (requires device IP configured) + +# Manual: telnet into device, then pull from HTTP server on host: +wget http://:8080/deploy.sh -O /tmp/deploy.sh && sh /tmp/deploy.sh +``` + +Device paths: +- Binary: `/mnt/flash/vienna/kp_firmware_host_stream` +- Config: `/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin/ini/host_stream.ini` +- Model (NEF): `/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin/nef/` + +## Testing + +No automated test framework. Manual verification: +- **Web UI** (`web_serve.py`): live RTSP preview, terminal, compile/deploy +- **Mock server** (`tools/mock_server/server.py`): simulates device events without hardware +- **On-device**: `ps | grep firmware`, `cat /tmp/fw.log`, `curl http://localhost:8080/api/stdc/stats` + +## Architecture + +**Video pipeline:** +``` +IMX662 (1920×1080, DOL-HDR) + → KL630 ISP (YUV420) + → VMF (Vatics Media Framework) + ├─ Stream 0 (1920×1080) → H.264 encoder → RTSP + └─ Stream 1 (724×362) → NPU (STDC inference) + → Post-processing + → HDMI overlay / BLE / CAN events +``` + +**Threading model** (all threads in `kp_firmware.c`): +- **Image thread**: reads ISP output from SSM → enqueues into FifoQ +- **Inference thread**: FifoQ dispatcher → NPU (STDC model, ModelId=32769) +- **Results thread**: FifoQ dequeue → post-processing → event dispatch +- **Video/VOC thread**: H.264 encoding + HDMI draw-box overlay + +**Key source directories:** +- `src/host_stream/` — main firmware logic, BLE/UART (`bt_uart.c`), CAN bus (`can_bus.c`, `mcp2515.c`), event recording, FEC API, GPIO +- `src/app_flow/` — NPU inference orchestration (`stdc_inf_single_model.c`) +- `src/pre_post_proc/` — STDC post-processing: motion detection, class distribution, ROI analysis +- `include/common/` — shared data structures (KP_STRUCT, model types) +- `include/fake/` — stub headers for SDK components unavailable at compile time +- `ini/host_stream.ini` — runtime config template (model, ISP, FEC, RTSP, event thresholds) +- `tools/` — deploy scripts, mock server, Python control scripts + +**Communication interfaces:** +- **BLE**: JSON over UART at 115200 baud (`/dev/ttyS1`) via DX-BT24 module → iPad app +- **CAN**: Standard CAN frames at 250 kbps via MCP2515 SPI → golf cart +- **RTSP**: H.264 from VMF encoder +- **Web API**: Flask HTTP server (`web_serve.py`) exposes compile/deploy/stats endpoints + +## Code Conventions + +- Global variables: `g_` prefix (e.g., `g_dwVocEnable`, `g_stdc_seg_map`) +- Boolean flags: `_bl` suffix; uint32_t: `_dw` suffix; struct pointers: `pt` prefix +- Config driven by INI file (iniparser library); sections: `[output]`, `[event]`, `[isp]`, `[fec]` +- Return codes: `KP_SUCCESS = 0`, negative values for errors +- Thread safety: mutex `g_stdc_seg_mutex` guards the shared segmentation map +- Logging: stdout/stderr + `/tmp/fw.log` on device; set `verbose_log=1` in INI for per-frame output + +## Important Runtime Notes + +- **ISP one-time setup**: DOL-HDR requires running firmware with `--setup` flag on first boot +- **Host IP**: configured in `.web_config.json` or via web UI; device uses it for `wget` downloads +- **FEC mode**: fish-eye correction mode 0–5 in INI; Mode 4 recommended for ceiling-mount +- **Model switching**: change `ModelId`/`JobId` in INI and redeploy +- **Startup mode**: `demo_rtsp.sh`, `demo_hdmi.sh`, or `demo_rtsp_hdmi.sh` on device sets output mode diff --git a/GF_AI_Box_Test_Guide.docx b/GF_AI_Box_Test_Guide.docx new file mode 100644 index 0000000..b987555 Binary files /dev/null and b/GF_AI_Box_Test_Guide.docx differ diff --git a/GF_AI_Box_Test_Guide.md b/GF_AI_Box_Test_Guide.md new file mode 100644 index 0000000..78ef561 --- /dev/null +++ b/GF_AI_Box_Test_Guide.md @@ -0,0 +1,309 @@ +# GF AI Box — 測試人員操作指南 + +> 文件版本:v2.0 +> 適用韌體:gf_ai_box_v4 +> 更新日期:2026-06-14 + +--- + +## 一、測試環境準備 + +### 所需工具 +- 藍芽串口工具(iOS:LightBlue / Serial Bluetooth Terminal;Android:Serial Bluetooth Terminal) +- CAN bus 分析儀(選配,用於驗證 CAN 訊號) +- 油門踏板(CAN 測試時需要) + +### 連線方式 +1. 開啟藍芽工具,掃描並連線到 AI Box 的 BLE 裝置 +2. 完成握手後即可送出指令 +3. 指令為**純文字**,大小寫不分,直接輸入後送出 + +### 重要說明 +- **硬體 Reset 後預設為正常模式**,必須先送 `test_enter` 才能執行測試指令 +- 進入測試模式後,AI Box 的 AI 推論結果暫停處理(NPU 繼續運算但不派發事件) +- 測試模式下如果 **60 秒無任何指令**,系統會自動退出測試模式並恢復正常 +- 若 TEST_ALL 系列正在執行,請勿重複送出測試指令 + +--- + +## 二、基本控制指令 + +| 指令 | 說明 | 預期回覆 | +|------|------|---------| +| `test_enter` | 進入測試模式 | `TEST_ACK ENTER` | +| `test_exit` | 退出測試模式,恢復正常 | `TEST_EXIT_OK` | + +--- + +## 三、大項一:BLE JSON 測試 + +**測試目的**:驗證各警示事件觸發時,藍芽送出的 JSON 格式與內容正確。 +**注意**:只送出 BLE JSON,**不觸發** CAN bus 指令與 Buzzer。 + +### 3.1 單項測試指令 + +#### Violation(草地違規) + +| 指令 | 說明 | 預期 BLE 回傳 JSON | +|------|------|-----------------| +| `test_ble violation 1` | 觸發 violation level=1 | `{"response_type":"violation","content":{"id":"...","date":"...","type":"lane","level":1}}` | +| `test_ble violation 0` | 解除 violation | `{"response_type":"violation","content":{"id":"...","date":"...","type":"lane","level":0}}` | + +#### Collision(碰撞警示) + +| 指令 | 說明 | 預期 BLE 回傳 JSON | +|------|------|-----------------| +| `test_ble collision 1` | 觸發 collision level=1(type=vehicle)| `{"response_type":"collision_warning","content":{"level":1,"type":"vehicle"}}` | +| `test_ble collision 0` | 解除 collision | `{"response_type":"collision_warning","content":{"level":0,"type":null}}` | + +#### Alert(方向警示) + +| 指令 | 說明 | 預期 BLE 回傳 JSON | +|------|------|-----------------| +| `test_ble alert 1 tree 0 null` | 左側 tree 警示,右側無 | `{"response_type":"alert","content":{"left":{"level":1,"type":"tree"},"right":{"level":0,"type":null}}}` | +| `test_ble alert 0 null 1 person` | 左側無,右側 person | `{"response_type":"alert","content":{"left":{"level":0,"type":null},"right":{"level":1,"type":"person"}}}` | +| `test_ble alert 1 tree 1 vehicle` | 左右都有警示 | `{"response_type":"alert","content":{"left":{"level":1,"type":"tree"},"right":{"level":1,"type":"vehicle"}}}` | +| `test_ble alert 0 null 0 null` | 解除所有 alert | `{"response_type":"alert","content":{"left":{"level":0,"type":null},"right":{"level":0,"type":null}}}` | + +> **Alert 指令格式**:`test_ble alert <左level> <左type> <右level> <右type>` +> type 可以是:`tree` / `person` / `vehicle` / `null` + +### 3.2 BLE 全項自動測試 + +| 指令 | 說明 | +|------|------| +| `test_ble_all` | 自動跑完所有 BLE 測項(約 20 秒)| + +**TEST_BLE_ALL 執行順序:** + +``` +[00s] violation level=1 → 等 3 秒 +[03s] violation level=0 → 等 2 秒 +[05s] collision level=1 → 等 3 秒 +[08s] collision level=0 → 等 2 秒 +[10s] alert left=tree, right=無 → 等 3 秒 +[13s] alert clear → 等 2 秒 +[15s] alert left=tree, right=vehicle → 等 3 秒 +[18s] alert clear → 等 2 秒 +[20s] TEST_BLE_ALL_DONE,自動退出測試模式 +``` + +--- + +## 四、大項二:CAN bus 測試 + +**測試目的**:驗證各警示事件觸發時,CAN bus 送出正確速度指令。 +**注意**:只觸發 CAN bus 速度指令,**不送出** BLE JSON,**不啟動** Buzzer。 + +### 速度對照表 + +| 事件 | CAN 速度值 | +|------|-----------| +| 正常行駛 | 240 | +| Violation / Alert | 35 | +| Collision | 10 | + +### 4.1 單項測試指令 + +#### Violation CAN 測試 + +| 指令 | CAN 速度 | 預期回覆 | +|------|---------|---------| +| `test_can violation 1` | 35 | `TEST_ACK CAN VIOLATION 1 speed=35` | +| `test_can violation 0` | 240 | `TEST_ACK CAN VIOLATION 0 speed=240` | + +#### Collision CAN 測試 + +| 指令 | CAN 速度 | 預期回覆 | +|------|---------|---------| +| `test_can collision 1` | 10 | `TEST_ACK CAN COLLISION 1 speed=10` | +| `test_can collision 0` | 240 | `TEST_ACK CAN COLLISION 0 speed=240` | + +#### Alert CAN 測試 + +| 指令 | CAN 速度 | 預期回覆 | +|------|---------|---------| +| `test_can alert 1` | 35 | `TEST_ACK CAN ALERT 1 speed=35` | +| `test_can alert 0` | 240 | `TEST_ACK CAN ALERT 0 speed=240` | + +### 4.2 CAN 全項自動測試 + +| 指令 | 說明 | +|------|------| +| `test_can_all` | 自動跑完所有 CAN 測項(預設觀察 10 秒,約 39 秒)| +| `test_can_all 15` | 每個測項觀察時間改為 15 秒 | + +**TEST_CAN_ALL 執行順序(預設觀察 10 秒):** + +``` +[00s] violation level=1 → speed=35 + ↓ 觀察 10 秒(踩油門,確認轉速被限制) +[10s] violation level=0 → speed=240 + ↓ 等 3 秒(確認恢復正常) +[13s] collision level=1 → speed=10 + ↓ 觀察 10 秒(踩油門,確認轉速被限制到最低) +[23s] collision level=0 → speed=240 + ↓ 等 3 秒 +[26s] alert level=1 → speed=35 + ↓ 觀察 10 秒 +[36s] alert level=0 → speed=240 + ↓ 等 3 秒 +[39s] TEST_CAN_ALL_DONE,自動退出測試模式 +``` + +--- + +## 五、大項三:Buzzer 測試 + +**測試目的**:驗證各警示事件觸發時,Buzzer 發出正確嗶聲節奏。 +**注意**:只觸發 Buzzer,**不送出** BLE JSON,**不發** CAN bus 指令。 + +### Buzzer Pattern 對照表 + +| Pattern | 節奏 | +|---------|------| +| OFF(靜音)| 無聲 | +| GRASS(Violation 草地)| 慢速:500ms 響 / 500ms 停 | +| ALERT(Alert 方向警示)| 中速:300ms 響 / 200ms 停 | +| COLLISION(碰撞)| 急促:100ms 響 / 100ms 停 | + +### 5.1 單項測試指令 + +| 指令 | Buzzer 動作 | 預期回覆 | +|------|-----------|---------| +| `test_buzzer grass` | 慢速嗶(500ms 響 / 500ms 停)| `TEST_ACK BUZZER GRASS OK` | +| `test_buzzer alert` | 中速嗶(300ms 響 / 200ms 停)| `TEST_ACK BUZZER ALERT OK` | +| `test_buzzer collision` | 急促嗶(100ms 響 / 100ms 停)| `TEST_ACK BUZZER COLLISION OK` | +| `test_buzzer off` | 靜音 | `TEST_ACK BUZZER OFF OK` | + +> Buzzer 指令送出後立即生效,持續到下一個指令為止。 + +### 5.2 Buzzer 全項自動測試 + +| 指令 | 說明 | +|------|------| +| `test_buzzer_all` | 自動跑完所有 Buzzer 測項(約 12 秒)| + +**TEST_BUZZER_ALL 執行順序:** + +``` +[00s] GRASS 慢速嗶 → 等 4 秒 +[04s] ALERT 中速嗶 → 等 4 秒 +[08s] COLLISION 急促嗶 → 等 4 秒 +[12s] OFF 靜音 +[12s] TEST_BUZZER_ALL_DONE,自動退出測試模式 +``` + +--- + +## 六、全功能自動測試 + +| 指令 | 說明 | +|------|------| +| `test_all` | 依序執行 BLE → CAN → Buzzer 三大群組(預設觀察 10 秒,約 77 秒)| +| `test_all 15` | CAN 觀察時間改為 15 秒(約 92 秒)| + +**執行順序(預設觀察 10 秒):** + +1. **BLE 群組**(約 20 秒)— 驗證 BLE JSON +2. 休息 3 秒 +3. **CAN 群組**(約 39 秒)— 驗證 CAN bus 速度指令 +4. 休息 3 秒 +5. **Buzzer 群組**(約 12 秒)— 驗證 Buzzer 嗶聲節奏 +6. 送出 `TEST_ALL_DONE`,自動退出測試模式 + +**進度訊息:** + +| 回傳訊息 | 說明 | +|---------|------| +| `TEST_ALL_START` | 全功能測試開始 | +| `TEST_ALL_BLE_GROUP_START` | BLE 群組開始 | +| `TEST_ALL_BLE_GROUP_DONE` | BLE 群組完成 | +| `TEST_ALL_CAN_GROUP_START` | CAN 群組開始 | +| `TEST_ALL_CAN_GROUP_DONE` | CAN 群組完成 | +| `TEST_ALL_BUZZER_GROUP_START` | Buzzer 群組開始 | +| `TEST_ALL_BUZZER_GROUP_DONE` | Buzzer 群組完成 | +| `TEST_ALL_DONE` | 全部完成 | + +--- + +## 七、錯誤回覆說明 + +| 回傳訊息 | 說明 | +|---------|------| +| `TEST_ERR NOT_IN_TEST_MODE` | 尚未執行 `test_enter` | +| `TEST_ERR ALL_ALREADY_RUNNING` | TEST_ALL 系列仍在執行中,請等待完成 | +| `TEST_ERR UNKNOWN_CMD` | 指令不正確,請確認格式 | +| `TEST_ACK ALREADY_IN_TEST` | 已在測試模式,不需重複 `test_enter` | +| `TEST_TIMEOUT_EXIT` | 60 秒逾時,自動退出測試模式 | + +--- + +## 八、快速測試流程範例 + +### 範例 A:只測試藍芽 JSON + +``` +test_enter +test_ble collision 1 +(確認收到 collision_warning JSON) +test_ble collision 0 +test_exit +``` + +### 範例 B:只測試 CAN 限速 + +``` +test_enter +test_can collision 1 +(踩油門 → 確認轉速被限制 + 確認 ACK speed=10) +test_can collision 0 +(確認轉速恢復正常) +test_exit +``` + +### 範例 C:只測試 Buzzer + +``` +test_enter +test_buzzer collision +(聽到急促嗶聲) +test_buzzer off +(靜音) +test_exit +``` + +### 範例 D:全自動測試(觀察 15 秒) + +``` +test_enter +test_all 15 +(等待約 92 秒,觀察各測項) +(收到 TEST_ALL_DONE 後自動退出測試模式) +``` + +--- + +## 附錄:指令速查表 + +``` +test_enter +test_exit + +test_ble violation level = 0 或 1 +test_ble collision level = 0 或 1 +test_ble alert <左level> <左type> <右level> <右type> +test_ble_all + +test_can violation level = 0 或 1 +test_can collision level = 0 或 1 +test_can alert level = 0 或 1 +test_can_all [觀察秒數] 預設 10 秒 + +test_buzzer grass +test_buzzer alert +test_buzzer collision +test_buzzer off +test_buzzer_all + +te \ No newline at end of file diff --git a/GF_AI_Box_Test_Guide.pdf b/GF_AI_Box_Test_Guide.pdf new file mode 100644 index 0000000..588b823 Binary files /dev/null and b/GF_AI_Box_Test_Guide.pdf differ diff --git a/src/host_stream/bt_uart.c b/src/host_stream/bt_uart.c index 57cb0f3..43c21d3 100644 --- a/src/host_stream/bt_uart.c +++ b/src/host_stream/bt_uart.c @@ -165,21 +165,73 @@ static void *bt_reader_thread(void *arg) char buf[512]; int pos = 0, depth = 0; int in_str = 0, escaped = 0; + int is_text = 0; /* 1 = accumulating plain-text (non-JSON) line */ while (s_running) { if (s_bt_fd < 0) { usleep(100000); continue; } fd_set fds; - struct timeval tv = {0, 500000}; /* 500 ms — lets us recheck s_running */ + struct timeval tv = {0, 200000}; /* 200 ms — flush plain-text if no newline */ FD_ZERO(&fds); FD_SET(s_bt_fd, &fds); - if (select(s_bt_fd + 1, &fds, NULL, NULL, &tv) <= 0) + if (select(s_bt_fd + 1, &fds, NULL, NULL, &tv) <= 0) { + /* timeout: flush any pending plain-text (no newline from app) */ + if (is_text && pos > 0) { + buf[pos] = '\0'; + printf("[BT RX TEXT timeout-flush] %s\n", buf); + if (s_extra_cmd_cb) s_extra_cmd_cb(buf); + pos = 0; is_text = 0; + } continue; + } unsigned char c; - if (read(s_bt_fd, &c, 1) != 1) continue; + int n = (int)read(s_bt_fd, &c, 1); + if (n != 1) { + /* read timeout / error: flush any pending plain-text (app sent no newline) */ + if (is_text && pos > 0) { + buf[pos] = '\0'; + printf("[BT RX TEXT flush] %s\n", buf); + if (s_extra_cmd_cb) s_extra_cmd_cb(buf); + pos = 0; is_text = 0; + } + continue; + } - /* JSON brace / string-literal state machine */ + printf("[BT RX BYTE] 0x%02X '%c'\n", c, (c >= 0x20 && c < 0x7F) ? c : '.'); + + /* ── Plain-text line mode ────────────────────────────────────────── + * Entered when the first non-whitespace character at depth==0 is not '{'. + * Used for test-mode commands (test_enter, test_exit, etc.). + * Routes directly to s_extra_cmd_cb, bypassing JSON parser and + * handshake so test commands work with any BLE app without pairing. + * ─────────────────────────────────────────────────────────────── */ + if (is_text) { + if (c == '\r' || c == '\n') { + if (pos > 0) { + buf[pos] = '\0'; + printf("[BT RX TEXT] %s\n", buf); + if (s_extra_cmd_cb) s_extra_cmd_cb(buf); + pos = 0; + } + is_text = 0; + } else if (pos < (int)sizeof(buf) - 1) { + buf[pos++] = (char)c; + } else { + printf("[BT RX] text overflow — discarding\n"); + pos = 0; is_text = 0; + } + continue; + } + + /* ── Detect start of plain-text line (not JSON) ───────────────── */ + if (depth == 0 && pos == 0 && c != '{' && c != '\r' && c != '\n' && c > ' ') { + is_text = 1; + buf[pos++] = (char)c; + continue; + } + + /* ── JSON brace / string-literal state machine ────────────────── */ if (!in_str) { if (c == '"') { in_str = 1; } else if (c == '{') { depth++; } @@ -206,7 +258,7 @@ static void *bt_reader_thread(void *arg) /* Discard overlong garbage */ if (pos >= (int)sizeof(buf) - 1) { printf("[BT RX] buffer overflow — discarding\n"); - pos = 0; depth = 0; in_str = 0; escaped = 0; + pos = 0; depth = 0; in_str = 0; escaped = 0; is_text = 0; } } return NULL; @@ -310,10 +362,13 @@ static void bt_set_module_name(int fd, const char *name) int n; snprintf(cmd, sizeof(cmd), "AT+NAME%s\r\n", name); write(fd, cmd, strlen(cmd)); - usleep(100000); /* 100 ms — module needs time to process */ + usleep(300000); /* 300 ms — module needs time to process (longer after baud switch) */ n = read(fd, buf, sizeof(buf) - 1); buf[n > 0 ? n : 0] = '\0'; printf("[BT] AT+NAME%s => %d byte(s): %s\n", name, n, n > 0 ? buf : "(none)"); + /* Note: some DX-BT24 firmware versions accept AT+NAME silently (no response). + * Success is confirmed by scanning BLE after AT+RESET — the broadcast name + * should reflect the new value. */ } /* ── One-time AT baud setup (call only when bt_at_probe = 1 in INI) ─────── */ @@ -329,12 +384,20 @@ static void bt_uart_probe_and_upgrade(const char *dev) n = read(fd, buf, sizeof(buf) - 1); buf[n > 0 ? n : 0] = '\0'; printf("[BT] AT probe @ 115200 => %d byte(s): %s\n", n, n > 0 ? buf : "(none)"); - close(fd); if (n > 0) { - printf("[BT] module already at 115200 — no upgrade needed\n"); + /* Module already at 115200 — still set the BT name in case it changed */ + printf("[BT] module already at 115200 — setting BT name\n"); + bt_set_module_name(fd, s_bt_name); + write(fd, "AT+RESET\r\n", 10); + usleep(100000); + read(fd, buf, sizeof(buf) - 1); + close(fd); + usleep(500000); /* 500 ms: wait for module to reboot */ + usleep(300000); /* 300 ms: let module stabilise */ return; } + close(fd); /* No response at 115200 — try 9600 */ fd = open_uart(dev, B9600, 0, 10); @@ -353,25 +416,30 @@ static void bt_uart_probe_and_upgrade(const char *dev) /* Module is at 9600 — send AT+BAUD7 to switch to 115200. * The module switches baud immediately after responding; close this fd - * before sending any further commands. */ + * then reopen at 115200 before sending any further AT commands. */ printf("[BT] upgrading module from 9600 to 115200\n"); memset(buf, 0, sizeof(buf)); write(fd, "AT+BAUD7\r\n", 10); usleep(100000); /* 100 ms: wait for ack + baud switch */ n = read(fd, buf, sizeof(buf) - 1); printf("[BT] AT+BAUD7 => %d byte(s): %s\n", n, n > 0 ? buf : "(none)"); - bt_set_module_name(fd, s_bt_name); /* set BT broadcast name before reset */ + close(fd); + usleep(300000); /* 300 ms: let module stabilise at 115200 */ + + /* Reopen at 115200 to send AT+NAME and AT+RESET at the correct baud */ + fd = open_uart(dev, B115200, 0, 10); + if (fd < 0) { + printf("[BT] WARNING: reopen at 115200 failed after BAUD7\n"); + return; + } + bt_set_module_name(fd, s_bt_name); /* set BT broadcast name */ write(fd, "AT+RESET\r\n", 10); usleep(100000); read(fd, buf, sizeof(buf) - 1); close(fd); usleep(500000); /* 500 ms: wait for module to reboot */ - close(fd); - usleep(300000); /* 300 ms: let module stabilise at 115200 */ + usleep(300000); /* 300 ms: let module stabilise */ - /* Reopen at 115200 and reset so the new baud persists across power cycles */ - fd = open_uart(dev, B115200, 0, 10); - if (fd < 0) return; printf("[BT] upgrade complete — set bt_at_probe = 0 in INI to skip AT on next boot\n"); } @@ -453,7 +521,25 @@ void bt_uart_send_json(const char *json) msg->next = NULL; if (!msg->json) { free(msg); return; } - pthread_mutex_lock(&s_q_mtx); + /* Use trylock with retry instead of blocking lock. + * The bt_writer_thread may have died while holding s_q_mtx (e.g. due to + * SIGPIPE / SIGSEGV on a bad UART write), leaving the mutex permanently + * locked. A blocking pthread_mutex_lock would then stall the bt_reader + * thread forever. We retry for up to 200 ms; if the lock is still + * unavailable after that, we drop the message and log a warning so the + * reader thread can continue handling subsequent commands. */ + int locked = 0; + for (int i = 0; i < 200; i++) { + if (pthread_mutex_trylock(&s_q_mtx) == 0) { locked = 1; break; } + usleep(1000); /* 1 ms per retry */ + } + if (!locked) { + printf("[BT] WARN: send_json: s_q_mtx stuck (200 ms timeout), dropping: %.48s\n", json); + free(msg->json); + free(msg); + return; + } + if (s_q_tail) { s_q_tail->next = msg; s_q_tail = msg; diff --git a/src/host_stream/buzzer.c b/src/host_stream/buzzer.c index 7cd0a72..9852f7b 100644 --- a/src/host_stream/buzzer.c +++ b/src/host_stream/buzzer.c @@ -132,12 +132,16 @@ int buzzer_init(void) void buzzer_set_pattern(buzzer_pattern_t pattern) { - pthread_mutex_lock(&s_mtx); - if (s_pattern != pattern) { - s_pattern = pattern; + /* Write s_pattern directly (volatile, 32-bit ARM = atomic word write). + * Use trylock so callers never block even if the buzzer thread holds + * s_mtx briefly between its own pthread_mutex_lock and cond_wait. + * The thread will pick up the new pattern on its next cycle even + * without the signal, because s_pattern is checked after every sleep. */ + s_pattern = pattern; + if (pthread_mutex_trylock(&s_mtx) == 0) { pthread_cond_signal(&s_cond); + pthread_mutex_unlock(&s_mtx); } - pthread_mutex_unlock(&s_mtx); } void buzzer_close(void) diff --git a/src/host_stream/event_recorder.c b/src/host_stream/event_recorder.c index dfe8c00..2a39230 100644 --- a/src/host_stream/event_recorder.c +++ b/src/host_stream/event_recorder.c @@ -665,7 +665,7 @@ static void handle_test_cmd(const char *raw) } 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); + int lv = atoi(obs + 12); 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); diff --git a/src/host_stream/kp_firmware.c b/src/host_stream/kp_firmware.c index 6729aa4..6678455 100644 --- a/src/host_stream/kp_firmware.c +++ b/src/host_stream/kp_firmware.c @@ -275,15 +275,43 @@ int loadConfig(HOST_STREAM_INIT_OPT_T* pHostStreamInit) } if (need_ini_save) { - FILE *fini = fopen(HOST_STREAM_CONFIG_PATH, "w"); - if (fini) { - iniparser_dump_ini(ini, fini); - fclose(fini); - printf("[BT] INI persisted: bt_name=%s device_id=%s bt_at_probe=%d\n", - bt_name, device_id, bt_at_probe ? 0 : bt_at_probe); - } else { - printf("[BT] WARNING: cannot write INI back (%s)\n", HOST_STREAM_CONFIG_PATH); + /* Use sed in-place replacement to update ONLY the specific keys we need to + * persist. This preserves the original file structure, all other sections, + * inline comments, and avoids iniparser_dump_ini stripping all comments and + * potentially producing an unreadable flat-format output on some iniparser + * versions. + * + * Pattern: match the key at the start of a line (with optional spaces around + * '='), replace the value part. The sed expression targets the [event] block + * values by their key names — safe because these keys only appear once. */ + char cmd[512]; + int rc = 0; + + /* device_id */ + snprintf(cmd, sizeof(cmd), + "sed -i 's|^device_id[[:space:]]*=.*|device_id = %s|' %s", + device_id, HOST_STREAM_CONFIG_PATH); + rc |= system(cmd); + + /* bt_name */ + snprintf(cmd, sizeof(cmd), + "sed -i 's|^bt_name[[:space:]]*=.*|bt_name = %s|' %s", + bt_name, HOST_STREAM_CONFIG_PATH); + rc |= system(cmd); + + /* bt_at_probe → 0 (only when we just ran the AT probe) */ + if (bt_at_probe) { + snprintf(cmd, sizeof(cmd), + "sed -i 's|^bt_at_probe[[:space:]]*=.*|bt_at_probe = 0|' %s", + HOST_STREAM_CONFIG_PATH); + rc |= system(cmd); } + + if (rc == 0) + printf("[BT] INI persisted via sed: bt_name=%s device_id=%s bt_at_probe=%d\n", + bt_name, device_id, bt_at_probe ? 0 : bt_at_probe); + else + printf("[BT] WARNING: sed INI update returned %d (%s)\n", rc, HOST_STREAM_CONFIG_PATH); } event_recorder_init(ev_up, ev_sd, ev_max_mb, ev_delay, ev_enable); @@ -440,6 +468,12 @@ void free_stream_init(HOST_STREAM_INIT_OPT_T* pHostStreamInit) { int main (int argc, char* argv[]) { + /* Force line-buffered stdout so printf output appears immediately even + * when piped to tee (e.g. ./firmware 2>&1 | tee /tmp/fw.log). + * Without this, the pipe triggers full-buffering and log appears in chunks. */ + setvbuf(stdout, NULL, _IOLBF, 0); + setvbuf(stderr, NULL, _IOLBF, 0); + uint32_t major, minor, patch, build; pthread_t thread_f = 0; diff --git a/src/host_stream/uclibc_compat.c b/src/host_stream/uclibc_compat.c new file mode 100644 index 0000000..5728a93 --- /dev/null +++ b/src/host_stream/uclibc_compat.c @@ -0,0 +1,72 @@ +/* + * uclibc_compat.c — uClibc 相容性 shim + * + * 問題:cross-compiler 使用 glibc 標頭,-D_GNU_SOURCE 加上 C99/C11 模式會讓 + * glibc stdio.h 把所有 scanf 系列呼叫改成 __isoc99_* 版本。 + * 但目標裝置 (KL630) 跑的是 uClibc 1.0.34,沒有這些符號, + * 導致執行時出現 "can't resolve symbol" 錯誤。 + * + * 解法:在 binary 本身提供全部 __isoc99_* scanf 系列符號, + * 轉呼叫 uClibc 本身有的底層函數。 + * 由於符號定義在 binary 裡,執行時不需要 uClibc 提供。 + */ + +#include +#include + +/* 移除 glibc 所有 scanf 相關 macro,避免遞迴展開 */ +#undef scanf +#undef fscanf +#undef sscanf +#undef vscanf +#undef vfscanf +#undef vsscanf + +/* ---- 非 v 系列 ---- */ + +int __isoc99_scanf(const char *fmt, ...) +{ + va_list ap; + int ret; + va_start(ap, fmt); + ret = vscanf(fmt, ap); + va_end(ap); + return ret; +} + +int __isoc99_fscanf(FILE *stream, const char *fmt, ...) +{ + va_list ap; + int ret; + va_start(ap, fmt); + ret = vfscanf(stream, fmt, ap); + va_end(ap); + return ret; +} + +int __isoc99_sscanf(const char *s, const char *fmt, ...) +{ + va_list ap; + int ret; + va_start(ap, fmt); + ret = vsscanf(s, fmt, ap); + va_end(ap); + return ret; +} + +/* ---- v 系列(直接轉呼叫 uClibc 底層) ---- */ + +int __isoc99_vscanf(const char *fmt, va_list ap) +{ + return vscanf(fmt, ap); +} + +int __isoc99_vfscanf(FILE *stream, const char *fmt, va_list ap) +{ + return vfscanf(stream, fmt, ap); +} + +int __isoc99_vsscanf(const char *s, const char *fmt, va_list ap) +{ + return vsscanf(s, fmt, ap); +}