feat: BLE plain-text cmd, AT probe fix, INI sed persist, log buffering

bt_uart.c:
- Reader thread: accept plain-text (non-JSON) lines from BLE app;
  routes directly to extra_cmd_cb — test commands no longer need
  JSON wrapping
- bt_uart_probe_and_upgrade(): fix AT+BAUD7 sequence — reopen fd at
  115200 before sending AT+NAME/AT+RESET (was sending on stale 9600 fd)
- bt_uart_send_json(): replace blocking mutex_lock with 200 ms trylock
  retry to avoid deadlock if writer thread dies holding the queue mutex
- Reader select timeout 500 ms → 200 ms for faster plain-text flush

buzzer.c:
- buzzer_set_pattern(): write s_pattern directly then trylock+signal,
  so callers never block on the buzzer thread's mutex

can_bus.c:
- Add debug printf in can_bus_send_control_cmd() (temporary)

event_recorder.c:
- TEST_MODE_TIMEOUT_SEC 60 → 300 (5 min, enough for full test run)

kp_firmware.c:
- Persist INI keys via sed in-place instead of iniparser_dump_ini,
  preserving comments and original file structure
- setvbuf stdout/stderr to line-buffered so tee log appears immediately

Add GF_AI_Box_Test_Guide (.md/.docx/.pdf) and uclibc_compat.c
This commit is contained in:
miketsai 2026-06-14 15:41:30 +08:00
parent fa67a9f225
commit cd96e33c62
9 changed files with 630 additions and 29 deletions

96
CLAUDE.md Normal file
View File

@ -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://<HOST_IP>: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 05 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

BIN
GF_AI_Box_Test_Guide.docx Normal file

Binary file not shown.

309
GF_AI_Box_Test_Guide.md Normal file
View File

@ -0,0 +1,309 @@
# GF AI Box — 測試人員操作指南
> 文件版本v2.0
> 適用韌體gf_ai_box_v4
> 更新日期2026-06-14
---
## 一、測試環境準備
### 所需工具
- 藍芽串口工具iOSLightBlue / Serial Bluetooth TerminalAndroidSerial 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=1type=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靜音| 無聲 |
| GRASSViolation 草地)| 慢速500ms 響 / 500ms 停 |
| ALERTAlert 方向警示)| 中速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> level = 0 或 1
test_ble collision <level> level = 0 或 1
test_ble alert <左level> <左type> <右level> <右type>
test_ble_all
test_can violation <level> level = 0 或 1
test_can collision <level> level = 0 或 1
test_can alert <level> level = 0 或 1
test_can_all [觀察秒數] 預設 10 秒
test_buzzer grass
test_buzzer alert
test_buzzer collision
test_buzzer off
test_buzzer_all
te

BIN
GF_AI_Box_Test_Guide.pdf Normal file

Binary file not shown.

View File

@ -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;

View File

@ -132,13 +132,17 @@ int buzzer_init(void)
void buzzer_set_pattern(buzzer_pattern_t pattern)
{
pthread_mutex_lock(&s_mtx);
if (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);
}
}
void buzzer_close(void)
{

View File

@ -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);

View File

@ -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;

View File

@ -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 <stdarg.h>
#include <stdio.h>
/* 移除 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);
}