diff --git a/include/host_stream/mcp2515.h b/include/host_stream/mcp2515.h
index 8e50acf..4e9939a 100644
--- a/include/host_stream/mcp2515.h
+++ b/include/host_stream/mcp2515.h
@@ -476,7 +476,7 @@ typedef struct mcp2515_dev
typedef struct spi_description
{
char *spidev_path;
- uint8_t fd;
+ int fd; /* spidev file descriptor; -1 = not open */
uint8_t mode;
uint8_t bits;
uint32_t speed;
diff --git a/src/host_stream/event_recorder.c b/src/host_stream/event_recorder.c
index 7f9aeb9..dcd28aa 100644
--- a/src/host_stream/event_recorder.c
+++ b/src/host_stream/event_recorder.c
@@ -266,9 +266,9 @@ static void http_post_file(const char *filepath)
printf("[EVT] upload: %s sent OK\n", basename);
}
-/* ═══════════════════════════════════════════════════════════════════════════
+/*
* Helpers
- * ═══════════════════════════════════════════════════════════════════════════ */
+ * */
#define TZ_OFFSET_SEC (8 * 3600)
diff --git a/src/host_stream/mcp2515.c b/src/host_stream/mcp2515.c
index d2981ac..ae30f9d 100644
--- a/src/host_stream/mcp2515.c
+++ b/src/host_stream/mcp2515.c
@@ -337,11 +337,14 @@ static void spi_autoprobe(mcp2515_dev *mcp2515_device)
int spi_initial(mcp2515_dev *mcp2515_device)
{
int ret = 0;
- int bind_ok = 0;
- bind_ok = ensure_spidev_binding(mcp2515_device->spi_dev->spidev_path);
- if (bind_ok < 0) {
- return -8;
+ /* Close existing fd before reopening to prevent fd leak.
+ * ensure_spidev_binding() is intentionally skipped here: the kernel
+ * already exposes /dev/spidev1.0 at boot, so rebinding is unnecessary
+ * and causes ESHUTDOWN errors on the old fd during CAN error recovery. */
+ if (mcp2515_device->spi_dev->fd >= 0) {
+ close(mcp2515_device->spi_dev->fd);
+ mcp2515_device->spi_dev->fd = -1;
}
int fd = open(mcp2515_device->spi_dev->spidev_path, O_RDWR);
@@ -349,7 +352,7 @@ int spi_initial(mcp2515_dev *mcp2515_device)
printf("[SPI] failed to open %s (errno=%d)\n", mcp2515_device->spi_dev->spidev_path, errno);
return -1;
}
- mcp2515_device->spi_dev->fd = (uint8_t)fd;
+ mcp2515_device->spi_dev->fd = fd;
ret = ioctl(fd, SPI_IOC_WR_MODE, &(mcp2515_device->spi_dev->mode));
if (ret == -1) { printf("[SPI] WR_MODE failed (errno=%d)\n", errno); return -2; }
@@ -446,8 +449,9 @@ mcp2515_dev* new_mcp2515_dev(char *spidev_path)
mcp2515_dev* mcp2515_device = (mcp2515_dev*)malloc(sizeof(mcp2515_dev));
mcp2515_device->spi_dev = (spi_description*)malloc(sizeof(spi_description));
mcp2515_device->spi_dev->spidev_path = spidev_path;
- mcp2515_device->spi_dev->mode = 0;
- mcp2515_device->spi_dev->bits = 8;
+ mcp2515_device->spi_dev->fd = -1; /* not open yet */
+ mcp2515_device->spi_dev->mode = 0;
+ mcp2515_device->spi_dev->bits = 8;
mcp2515_device->spi_dev->speed = 1000000;
return mcp2515_device;
}
diff --git a/web_serve.py b/web_serve.py
index 0ae4b55..9d64acd 100644
--- a/web_serve.py
+++ b/web_serve.py
@@ -415,55 +415,6 @@ def _verify_firmware_running(tn):
ok = any("FW PID:" in ln for ln in lines)
return ok, lines
-def _spi_setup_on_device(tn, spi_node="spi1.0"):
- """Try to switch SPI node from display driver to spidev and report status."""
- steps = [
- ("SPI preflight: listing SPI devices...",
- "ls -l /sys/bus/spi/devices 2>/dev/null || true", 10),
- ("SPI preflight: current modalias...",
- f"cat /sys/bus/spi/devices/{spi_node}/modalias 2>/dev/null || echo 'modalias missing'", 10),
- ("SPI preflight: current driver link...",
- f"readlink /sys/bus/spi/devices/{spi_node}/driver 2>/dev/null || echo 'driver link missing'", 10),
- ("SPI setup: unbind current driver...",
- f"DRV=$(basename $(readlink /sys/bus/spi/devices/{spi_node}/driver 2>/dev/null) 2>/dev/null || true); "
- f"echo DRIVER=$DRV; "
- f"[ -n \"$DRV\" ] && echo {spi_node} > /sys/bus/spi/drivers/$DRV/unbind 2>/dev/null || true; "
- "echo 'unbind done'", 12),
- ("SPI setup: set driver_override=spidev (if supported)...",
- f"if [ -e /sys/bus/spi/devices/{spi_node}/driver_override ]; then "
- f"echo spidev > /sys/bus/spi/devices/{spi_node}/driver_override && echo 'override OK'; "
- "else echo 'driver_override missing'; fi", 10),
- ("SPI setup: bind spidev...",
- f"echo {spi_node} > /sys/bus/spi/drivers/spidev/bind 2>/dev/null || true; echo 'bind done'", 10),
- ("SPI verify: modalias + driver + /dev/spidev*",
- f"echo 'modalias:'; cat /sys/bus/spi/devices/{spi_node}/modalias 2>/dev/null || true; "
- f"echo 'driver:'; readlink /sys/bus/spi/devices/{spi_node}/driver 2>/dev/null || true; "
- "echo '/dev:'; ls -l /dev/spidev* 2>/dev/null || echo 'no /dev/spidev'", 12),
- ]
-
- out = []
- for label, cmd, timeout in steps:
- out.append(("log", label))
- out.append(("prompt", f"$ {cmd}"))
- for ln in _telnet_run(tn, cmd, timeout=timeout):
- kind = "ok"
- low = ln.lower()
- if "missing" in low or "no /dev/spidev" in low:
- kind = "warn"
- out.append((kind, ln))
-
- # Final health hint
- final = _telnet_run(
- tn,
- f"M=$(cat /sys/bus/spi/devices/{spi_node}/modalias 2>/dev/null || true); "
- "echo FINAL_MODALIAS=$M; "
- "echo $M | grep -q dh2228fv && echo 'SPI_BINDING_BLOCKED' || echo 'SPI_BINDING_OK'",
- timeout=10,
- )
- for ln in final:
- out.append(("warn" if "BLOCKED" in ln else "ok", ln))
- return out
-
# ── API: deploy via Telnet (SSE) ──────────────────────────────────────────────
@app.route("/api/deploy/run")
def api_deploy_run():
@@ -473,7 +424,6 @@ def api_deploy_run():
port = int(request.args.get("port", cfg["port"]))
out_rtsp = request.args.get("out_rtsp", "1") == "1"
out_hdmi = request.args.get("out_hdmi", "0") == "1"
- spi_fix = request.args.get("spi_fix", "1") == "1"
base = f"http://{h_ip}:{port}"
bd = BIN_DIR_DEVICE
fw = FW_PATH_DEVICE
@@ -539,12 +489,7 @@ def api_deploy_run():
tn = _telnet_connect(kl_ip)
_drain_shell(tn)
yield sse(f"Connected to {kl_ip}", "ok")
-
- if spi_fix:
- yield sse("Running SPI setup preflight (spi1.0 -> spidev)...")
- for kind, text in _spi_setup_on_device(tn, "spi1.0"):
- yield sse(text, kind)
-
+
for label, cmd, bg, timeout in steps:
yield sse(label)
yield sse(f"$ {cmd}", "prompt")
@@ -651,131 +596,6 @@ def api_deploy_startsh():
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
# ── API: SPI setup via Telnet (SSE) ─────────────────────────────────────────
-@app.route("/api/spi_setup/run")
-def api_spi_setup_run():
- cfg = load_config()
- kl_ip = request.args.get("ip", cfg["kl630_ip"])
- spi_node = request.args.get("node", "spi1.0")
-
- def generate():
- yield sse(f"Connecting to {kl_ip}:23 via Telnet...")
- try:
- tn = _telnet_connect(kl_ip)
- _drain_shell(tn)
- yield sse("Connected.", "ok")
- for kind, text in _spi_setup_on_device(tn, spi_node):
- yield sse(text, kind)
- tn.close()
- yield sse("SPI setup flow complete.", "ok")
- except Exception as e:
- yield sse(f"Telnet error: {e}", "error")
- yield sse_done()
-
- return Response(generate(), mimetype="text/event-stream",
- headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
-
-# ── API: BT UART one-time baud setup via Telnet (SSE) ────────────────────────
-@app.route("/api/bt_setup/run")
-def api_bt_setup_run():
- cfg = load_config()
- kl_ip = request.args.get("ip", cfg["kl630_ip"])
- bd = BIN_DIR_DEVICE
- fw = FW_PATH_DEVICE
- restart_cmd = (f"cd {bd} && rm -f /dev/shm/* && "
- f"nohup sh ./ini/demo_rtsp.sh > /tmp/fw.log 2>&1 &")
-
- def generate():
- yield sse("⚠ 請先關閉手機 nRF Connect 並確保 DX-BT24 未連線,再繼續!", "warn")
- yield sse(f"Connecting to {kl_ip}:23 via Telnet...")
- try:
- tn = _telnet_connect(kl_ip)
- _drain_shell(tn)
- yield sse("Connected.", "ok")
-
- # Step 1: kill firmware so UART is free
- yield sse("Step 1: stopping firmware...")
- _telnet_run(tn, "killall -9 kp_firmware_host_stream 2>/dev/null; "
- "killall -9 rtsps 2>/dev/null; sleep 1; echo stopped", timeout=10)
-
- # Step 2: probe current baud by reading UART response
- yield sse("Step 2: probing module baud rate...")
- probe_115200 = (
- "stty -F /dev/ttyS1 115200 raw cs8 -parenb -cstopb -echo; "
- "cat /dev/ttyS1 > /tmp/_bt_resp.bin & CPID=$!; "
- "printf 'AT+BAUD\\r\\n' > /dev/ttyS1; sleep 1; "
- "kill $CPID 2>/dev/null; "
- "BYTES=$(wc -c < /tmp/_bt_resp.bin 2>/dev/null || echo 0); "
- "echo \"115200_bytes=$BYTES\"; "
- "[ \"$BYTES\" -gt 0 ] && cat /tmp/_bt_resp.bin || true"
- )
- resp_115200 = _telnet_run(tn, probe_115200, timeout=8)
- for ln in resp_115200:
- yield sse(ln, "ok")
-
- got_115200 = any("115200_bytes=" in ln and not ln.endswith("=0") for ln in resp_115200)
-
- if got_115200:
- yield sse("Module responded at 115200 — already configured!", "ok")
- else:
- yield sse("No response at 115200 — trying 9600...", "warn")
- probe_9600 = (
- "stty -F /dev/ttyS1 9600 raw cs8 -parenb -cstopb -echo; "
- "cat /dev/ttyS1 > /tmp/_bt_resp.bin & CPID=$!; "
- "printf 'AT+BAUD\\r\\n' > /dev/ttyS1; sleep 1; "
- "kill $CPID 2>/dev/null; "
- "BYTES=$(wc -c < /tmp/_bt_resp.bin 2>/dev/null || echo 0); "
- "echo \"9600_bytes=$BYTES\"; "
- "[ \"$BYTES\" -gt 0 ] && cat /tmp/_bt_resp.bin || true"
- )
- resp_9600 = _telnet_run(tn, probe_9600, timeout=8)
- for ln in resp_9600:
- yield sse(ln, "ok")
-
- got_9600 = any("9600_bytes=" in ln and not ln.endswith("=0") for ln in resp_9600)
-
- if got_9600:
- yield sse("Module at 9600 — sending AT+BAUD7 to upgrade...", "ok")
- upgrade = (
- "stty -F /dev/ttyS1 9600 raw cs8 -parenb -cstopb -echo; "
- "printf 'AT+BAUD7\\r\\n' > /dev/ttyS1; sleep 0.3; "
- "stty -F /dev/ttyS1 115200 raw cs8 -parenb -cstopb -echo; "
- "printf 'AT+RESET\\r\\n' > /dev/ttyS1; sleep 1.5; "
- "echo 'upgrade_sent'"
- )
- for ln in _telnet_run(tn, upgrade, timeout=8):
- yield sse(ln, "ok")
- yield sse("AT+BAUD7 + AT+RESET sent. Module rebooting...", "ok")
- else:
- yield sse("No response at 9600 either.", "warn")
- yield sse("Can't probe module — it may be in BLE transparent mode or disconnected.", "warn")
- yield sse("Make sure phone is DISCONNECTED from DX-BT24, then run this again.", "error")
-
- # Step 3: send test string at 115200 to verify
- yield sse("Step 3: sending test ping at 115200...")
- test_cmd = (
- "stty -F /dev/ttyS1 115200 raw cs8 -parenb -cstopb -echo; "
- "printf '{\"class\":\"test\",\"level\":0}' > /dev/ttyS1; "
- "echo 'ping_sent'"
- )
- for ln in _telnet_run(tn, test_cmd, timeout=5):
- yield sse(ln, "ok")
-
- # Step 4: restart firmware normally (bt_at_probe stays 0)
- yield sse("Step 4: restarting firmware...")
- _telnet_run(tn, "killall -9 kp_firmware_host_stream 2>/dev/null; "
- "killall -9 rtsps 2>/dev/null; sleep 1; rm -f /dev/shm/*", timeout=10)
- _telnet_run_bg(tn, restart_cmd)
- yield sse("Firmware restarted.", "ok")
-
- tn.close()
- yield sse('完成!現在連上手機 → 訂閱 Notify → 如果看到 {"class":"test","level":0} 或 {"class":"boot","level":0} 表示成功', "ok")
- except Exception as e:
- yield sse(f"Telnet error: {e}", "error")
- yield sse_done()
-
- return Response(generate(), mimetype="text/event-stream",
- headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
-
# ── API: RTSP → MJPEG proxy (via opencv-python, no system ffmpeg needed) ──────
_stream_active = False
@@ -1789,14 +1609,6 @@ input:checked+.slider:before{transform:translateX(16px);background:#fff}
Update start.sh
-
-