Optimize SPI driver & web_server UIUX

This commit is contained in:
miketsai 2026-06-29 15:54:32 +08:00
parent 49cdb5bbae
commit 8278fae0ee
4 changed files with 17 additions and 204 deletions

View File

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

View File

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

View File

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

View File

@ -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}
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
Update start.sh
</button>
<button class="btn btn-warn" id="btn-bt-setup" onclick="runAction('bt_setup')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M6.5 6.5l11 11M17.5 6.5l-11 11"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/></svg>
BT 初始化
</button>
<button class="btn btn-ghost" id="btn-spi-setup" onclick="runAction('spi_setup')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M7 7h10v10H7z"/><path d="M3 10h4M17 10h4M10 3v4M10 17v4"/></svg>
SPI 初始化
</button>
<button class="btn btn-ghost" onclick="clearLog()">Clear Log</button>
<button class="btn btn-ghost" id="btn-autostart" onclick="runAction('autostart')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>
@ -1950,11 +1762,8 @@ function runAction(action) {
deploy: '/api/deploy/run?ip=' + encodeURIComponent(cfg.kl630_ip) +
'&host_ip=' + encodeURIComponent(cfg.host_ip) +
'&port=' + encodeURIComponent(cfg.port) +
'&spi_fix=1' +
'&out_rtsp=' + (document.getElementById('out-rtsp').checked ? 1 : 0) +
'&out_hdmi=' + (document.getElementById('out-hdmi').checked ? 1 : 0),
bt_setup: '/api/bt_setup/run?ip=' + encodeURIComponent(cfg.kl630_ip),
spi_setup: '/api/spi_setup/run?ip=' + encodeURIComponent(cfg.kl630_ip) + '&node=spi1.0',
autostart: '/api/autostart/write?ip=' + encodeURIComponent(cfg.kl630_ip) +
'&out_rtsp=' + (document.getElementById('out-rtsp').checked ? 1 : 0) +
'&out_hdmi=' + (document.getElementById('out-hdmi').checked ? 1 : 0),
@ -1969,7 +1778,7 @@ function runAction(action) {
'&host_ip=' + encodeURIComponent(cfg.host_ip) +
'&port=' + encodeURIComponent(cfg.port),
};
const labels = { compile:'Compile', deploy:'Deploy to KL630', bt_setup:'BT 初始化', spi_setup:'SPI 初始化', autostart:'Write Autostart', autostart_read:'Read Autostart', mount_sd:'Mount SD', deploy_binary:'Update Binary', deploy_startsh:'Update start.sh' };
const labels = { compile:'Compile', deploy:'Deploy to KL630', autostart:'Write Autostart', autostart_read:'Read Autostart', mount_sd:'Mount SD', deploy_binary:'Update Binary', deploy_startsh:'Update start.sh' };
appendLog('\\n── ' + labels[action] + ' ' + ''.repeat(40), 'prompt');
setLogStatus('Running...');
@ -2065,7 +1874,7 @@ function appendLog(text, kind) {
}
function clearLog() { document.getElementById('log').textContent = ''; setLogStatus(''); }
function setLogStatus(s) { document.getElementById('log-status').textContent = s; }
function setBtns(disabled){ ['btn-compile','btn-deploy','btn-bt-setup','btn-spi-setup','btn-autostart','btn-autostart-read','btn-mount-sd','btn-model-apply','btn-deploy-binary','btn-deploy-startsh'].forEach(id => document.getElementById(id).disabled = disabled); }
function setBtns(disabled){ ['btn-compile','btn-deploy','btn-autostart','btn-autostart-read','btn-mount-sd','btn-model-apply','btn-deploy-binary','btn-deploy-startsh'].forEach(id => document.getElementById(id).disabled = disabled); }
// Terminal
// by mars