Compare commits

..

2 Commits

Author SHA1 Message Date
548cec2d4f fix hdmi mode DrawBox amd BT output 2026-04-13 10:51:34 +08:00
a3f3b2f8fc fix hdmi mode DrawBox amd BT output 2026-04-13 10:41:38 +08:00
36 changed files with 818 additions and 170 deletions

6
.web_config.json Normal file
View File

@ -0,0 +1,6 @@
{
"host_ip": "192.168.0.114",
"kl630_ip": "192.168.0.201",
"port": 8080,
"docker_image": "kl630-dev"
}

BIN
build/app_header_init.o Normal file

Binary file not shown.

BIN
build/application_init.o Normal file

Binary file not shown.

BIN
build/bt_uart.o Normal file

Binary file not shown.

46
build/demo_hdmi.sh Normal file
View File

@ -0,0 +1,46 @@
#!/bin/sh
set -e
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin
FW=/mnt/flash/vienna/kp_firmware_host_stream
INI=$BIN_DIR/ini/host_stream.ini
cd $BIN_DIR
# Load hardware drivers if not already loaded (required for EDMC/NPU)
[ -e /dev/vpl_edmc ] || (cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null && sleep 1)
killall -9 kp_firmware_host_stream 2>/dev/null || true
killall -9 rtsps 2>/dev/null || true
sleep 1
rm -f /dev/shm/*
# Optional override: PIXFMT=NV21 MODEL_PATH=nef/other.nef MODEL_ID=xxxxx JOB_ID=yyy ./demo_hdmi.sh
PIXFMT=${PIXFMT:-NV12}
# Model settings — read from INI as default, env var overrides at runtime
_ini_val() { grep "^$1" "$INI" | sed 's/.*= *//' | tr -d '"' | awk '{print $1}'; }
MODEL_PATH=${MODEL_PATH:-$(_ini_val ModelPath)}
MODEL_ID=${MODEL_ID:-$(_ini_val ModelId)}
JOB_ID=${JOB_ID:-$(_ini_val JobId)}
# HDMI demo profile: inference on stream1 (724x362) + HDMI output from stream0 (1920x1080).
# InferenceStream=1: NPU receives 724x362 frames (model native res) — ~8x faster than InferenceStream=0.
# StreamCount=2: creates both stream0 (1920x1080, draw_box+HDMI) and stream1 (724x362, inference) SSMs.
sed -i 's/^NnmSource.*/NnmSource = 0/' $INI
sed -i 's/^GetImageBufMode.*/GetImageBufMode = 0/' $INI
sed -i 's/^InferenceStream.*/InferenceStream = 1/' $INI
sed -i 's/^StreamCount.*/StreamCount = 2/' $INI
sed -i 's/^voc_enable.*/voc_enable = 1/' $INI
sed -i "s/^PixFmt.*/PixFmt = ${PIXFMT}/" $INI
# DrawBoxEnable: respect INI value set by web UI (do not override)
echo "=== HDMI Demo INI ==="
grep -E "ModelPath|ModelId|JobId|NnmSource|GetImageBufMode|InferenceStream|StreamCount|voc_enable|PixFmt" $INI
echo "=== Model (runtime): MODEL_PATH=$MODEL_PATH MODEL_ID=$MODEL_ID JOB_ID=$JOB_ID ==="
echo "=== Start Firmware (HDMI demo) ==="
LD_LIBRARY_PATH=/mnt/flash/vienna/lib $FW -m "$MODEL_PATH" -i "$MODEL_ID" -j "$JOB_ID" &
FW_PID=$!
echo "Firmware PID: $FW_PID"
wait $FW_PID

BIN
build/demo_post_utils.o Normal file

Binary file not shown.

56
build/demo_rtsp.sh Normal file
View File

@ -0,0 +1,56 @@
#!/bin/sh
set -e
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin
FW=/mnt/flash/vienna/kp_firmware_host_stream
INI=$BIN_DIR/ini/host_stream.ini
cd $BIN_DIR
# Load hardware drivers if not already loaded (required for EDMC/NPU)
[ -e /dev/vpl_edmc ] || (cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null && sleep 1)
killall -9 kp_firmware_host_stream 2>/dev/null || true
killall -9 rtsps 2>/dev/null || true
sleep 1
rm -f /dev/shm/*
# Optional overrides:
# PIXFMT=NV21 INF_STREAM=0 MODEL_PATH=nef/other.nef MODEL_ID=xxxxx JOB_ID=yyy ./demo_rtsp.sh
PIXFMT=${PIXFMT:-NV12}
INF_STREAM=${INF_STREAM:-1}
# Model settings — read from INI as default, env var overrides at runtime
_ini_val() { grep "^$1" "$INI" | sed 's/.*= *//' | tr -d '"' | awk '{print $1}'; }
MODEL_PATH=${MODEL_PATH:-$(_ini_val ModelPath)}
MODEL_ID=${MODEL_ID:-$(_ini_val ModelId)}
JOB_ID=${JOB_ID:-$(_ini_val JobId)}
# RTSP demo profile: single-process source + one encoder stream + RTSP server.
sed -i 's/^NnmSource.*/NnmSource = 0/' $INI
sed -i 's/^GetImageBufMode.*/GetImageBufMode = 0/' $INI
sed -i "s/^InferenceStream.*/InferenceStream = ${INF_STREAM}/" $INI
sed -i 's/^StreamCount.*/StreamCount = 2/' $INI
sed -i "s/^PixFmt.*/PixFmt = ${PIXFMT}/" $INI
sed -i 's/^voc_enable.*/voc_enable = 0/' $INI
# DrawBoxEnable: respect INI value set by web UI (do not override)
echo "=== RTSP Demo INI ==="
grep -E "ModelPath|ModelId|JobId|NnmSource|GetImageBufMode|InferenceStream|StreamCount|voc_enable|PixFmt|DrawBoxEnable" $INI
echo "=== Model (runtime): MODEL_PATH=$MODEL_PATH MODEL_ID=$MODEL_ID JOB_ID=$JOB_ID ==="
echo "=== Start Firmware (RTSP demo) ==="
LD_LIBRARY_PATH=/mnt/flash/vienna/lib $FW -m "$MODEL_PATH" -i "$MODEL_ID" -j "$JOB_ID" &
FW_PID=$!
sleep 4
# Start RTSP server that reads venc_srb_* output.
LD_LIBRARY_PATH=/mnt/flash/vienna/lib ./rtsps -c stream_server_config.ini &
RTSP_PID=$!
echo "Firmware PID: $FW_PID"
echo "RTSP PID: $RTSP_PID"
echo "RTSP URL: rtsp://192.168.3.10/live1.sdp"
wait $FW_PID

58
build/demo_rtsp_hdmi.sh Normal file
View File

@ -0,0 +1,58 @@
#!/bin/sh
# by mars
set -e
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin
FW=/mnt/flash/vienna/kp_firmware_host_stream
INI=$BIN_DIR/ini/host_stream.ini
cd $BIN_DIR
# Load hardware drivers if not already loaded (required for EDMC/NPU)
[ -e /dev/vpl_edmc ] || (cd /mnt/flash/vienna/drivers && sh driver.sh 2>/dev/null && sleep 1)
killall -9 kp_firmware_host_stream 2>/dev/null || true
killall -9 rtsps 2>/dev/null || true
sleep 1
rm -f /dev/shm/*
# Optional override: PIXFMT=NV21 MODEL_PATH=nef/other.nef MODEL_ID=xxxxx JOB_ID=yyy ./demo_rtsp_hdmi.sh
PIXFMT=${PIXFMT:-NV12}
# Model settings — read from INI as default, env var overrides at runtime
_ini_val() { grep "^$1" "$INI" | sed 's/.*= *//' | tr -d '"' | awk '{print $1}'; }
MODEL_PATH=${MODEL_PATH:-$(_ini_val ModelPath)}
MODEL_ID=${MODEL_ID:-$(_ini_val ModelId)}
JOB_ID=${JOB_ID:-$(_ini_val JobId)}
# Combined RTSP + HDMI profile.
# InferenceStream=1 uses stream1 (724x362) for inference.
# DrawOnResize=1 so segmentation overlay is drawn on resize streams (RTSP streams).
sed -i 's/^NnmSource.*/NnmSource = 0/' $INI
sed -i 's/^GetImageBufMode.*/GetImageBufMode = 0/' $INI
sed -i 's/^InferenceStream.*/InferenceStream = 1/' $INI
sed -i 's/^StreamCount.*/StreamCount = 2/' $INI
sed -i 's/^voc_enable.*/voc_enable = 1/' $INI
sed -i "s/^PixFmt.*/PixFmt = ${PIXFMT}/" $INI
# DrawBoxEnable: respect INI value set by web UI (do not override)
# DrawOnResize: not needed — firmware auto-disables it when InferenceStream != 0
echo "=== RTSP+HDMI Demo INI ==="
grep -E "ModelPath|ModelId|JobId|NnmSource|GetImageBufMode|InferenceStream|StreamCount|voc_enable|PixFmt|DrawBoxEnable|DrawOnResize" $INI
echo "=== Model (runtime): MODEL_PATH=$MODEL_PATH MODEL_ID=$MODEL_ID JOB_ID=$JOB_ID ==="
echo "=== Start Firmware (RTSP+HDMI demo) ==="
LD_LIBRARY_PATH=/mnt/flash/vienna/lib $FW -m "$MODEL_PATH" -i "$MODEL_ID" -j "$JOB_ID" &
FW_PID=$!
sleep 4
# Start RTSP server that reads venc_srb_* output.
LD_LIBRARY_PATH=/mnt/flash/vienna/lib ./rtsps -c stream_server_config.ini &
RTSP_PID=$!
echo "Firmware PID: $FW_PID"
echo "RTSP PID: $RTSP_PID"
echo "RTSP URL: rtsp://192.168.3.10/live1.sdp"
wait $FW_PID

68
build/deploy.sh Normal file
View File

@ -0,0 +1,68 @@
#!/bin/sh
# deploy.sh — 部署新編譯的 firmware 到裝置並重啟 RTSP demo
#
# 使用方式(在裝置上執行):
# sh deploy.sh
#
# 前提host 192.168.3.1:8080 在提供以下檔案:
# /kp_firmware_host_stream (compile.sh 的輸出)
# /host_stream.ini (kl630_build/ini/host_stream.ini)
#
# 一次性設定(只需在新機器上執行一次,之後重開機不需要再跑):
# sh deploy.sh --setup
set -e
HOST_URL="http://192.168.0.114:8080"
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin
FW=/mnt/flash/vienna/kp_firmware_host_stream
INI=$BIN_DIR/ini/host_stream.ini
# ── 一次性 ISP 資源修正 ─────────────────────────────────────────────────────
# IMX662 DOL-HDR 需要 dwStatisticsSrcType=2 和 bGTREnable=1。
# 這些寫入 flash重開機後自動保留只需要執行一次。
one_time_setup() {
echo "=== 一次性 ISP resource 設定 ==="
sed -i 's/dwStatisticsSrcType = 0/dwStatisticsSrcType = 2/' \
$BIN_DIR/Resource/AWB/AutoWhiteBalance.ini
sed -i 's/bGTREnable = 0/bGTREnable = 1/' \
$BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg
sed -i 's/bGTREnable = 0/bGTREnable = 1/' \
$BIN_DIR/Resource/ISP/1/pqtable_ispe_Config.cfg
echo " dwStatisticsSrcType=$(grep dwStatisticsSrcType $BIN_DIR/Resource/AWB/AutoWhiteBalance.ini | head -1)"
echo " bGTREnable=$(grep bGTREnable $BIN_DIR/Resource/ISP/0/pqtable_ispe_Config.cfg)"
echo "=== 完成,之後重開機不需要再執行 ==="
}
if [ "$1" = "--setup" ]; then
one_time_setup
exit 0
fi
# ── 停止舊的 firmware ─────────────────────────────────────────────────────────
echo "=== 停止舊 firmware ==="
killall -9 kp_firmware_host_stream 2>/dev/null || true
killall -9 rtsps 2>/dev/null || true
sleep 1
rm -f /dev/shm/*
# ── 下載新 binary ─────────────────────────────────────────────────────────────
echo "=== 下載 firmware binary ==="
wget -q "$HOST_URL/kp_firmware_host_stream" -O $FW
chmod +x $FW
echo " $FW: $(ls -lh $FW | awk '{print $5, $6, $7, $8}')"
# ── 下載新 INI保留裝置端的 fusion_cfg / ISP 設定)─────────────────────────
echo "=== 下載 INI ==="
wget -q "$HOST_URL/host_stream.ini" -O $INI
echo " fusion_cfg: $(grep '^fusion_cfg' $INI || echo '(not set!)')"
# ── 下載 demo_rtsp.sh確保裝置上版本與 host 一致)────────────────────────────
echo "=== 下載 demo_rtsp.sh ==="
wget -q "$HOST_URL/demo_rtsp.sh" -O $BIN_DIR/ini/demo_rtsp.sh
chmod +x $BIN_DIR/ini/demo_rtsp.sh
# ── 啟動 RTSP demo ────────────────────────────────────────────────────────────
echo "=== 啟動 RTSP demo ==="
cd $BIN_DIR
sh ./ini/demo_rtsp.sh

BIN
build/event_recorder.o Normal file

Binary file not shown.

BIN
build/fec_api.o Normal file

Binary file not shown.

BIN
build/glibc_shim.o Normal file

Binary file not shown.

99
build/host_stream.ini Normal file
View File

@ -0,0 +1,99 @@
[sensor]
sensor_cfg = "./Resource/VIC/0/imx662_1920x1080_ch0.cfg"
fusion_cfg = "./Resource/VIC/1/imx662_1920x1080_ch1.cfg"
autoscene_config = "./Resource/AutoScene/autoscene_conf.cfg"
fec_calibrate_path = "./ini/fec_calibrate.ini"
fec_conf_path = "./ini/fec_conf.ini"
fec_mode = 4 # 0: Original, 1: 1 Region, 2: 180 all direction, 3: 180 one Direction, 4: 180 two direction, 5: PT Mode
initial_fec_app_type = 0 # 0: ceiling, 1: table, 2: wall
eis_enable = 1
[nnm]
ModelPath = "nef/STDC04012026_models_630.nef"
ModelId = 32769 # KNERON_YOLOV5S_COCO80_640_640_3 (YOLO v5s)
JobId = 200 # KDP2_INF_ID_APP_YOLO
InferenceStream = 1 # Inference stream index (use stream1: 640x640)
Threshold = 0.5 # for yolo only(JobId = 11)
Fps = 25 # Image input fps for NPU inference
GetImageBufMode = 0 # 0: block mode 1: non-block mode
RoiEnable = 0 # Enable ROI for nnm detect
RoiX = 0 # ROI start x
RoiY = 0 # ROI start y
DrawBoxEnable = 1 # draw object bounding box on stream0 (also enables STDC seg overlay)
OnlyPerson = 1 # only draw person bounding box when DrawBoxEnable
#(so far for yolo only, JobId = 11)
DrawOnResize = 0; # If InferenceStream is 0. This setting needs to be enabled. The box will be drawn on all resize streams.
verbose_log = 0 # 0: quiet (warnings only, ~30B/s), 1: per-frame class ratios every 20 frames (~1KB/s). Toggle from web UI.
NnmSource = 0 # 0: run host_stream independently, 1: run with streamer
ssm_name = "vsrc_ssm_ifp_0" # If NnmSource is 1. This setting needs to be set. The name is automaticlly create by streamer.
[voc]
voc_enable = 1 #enable Video Output Component
VocWidth = 1920 #video output width
VocHeight = 1080 #video output height
PixFmt = NV12 # HDMI input format for VOC: NV12 / NV21 / YM12
[streamer]
StreamCount = 2 #Stream amount
MemType = 0 #Encode buffer type, 0(SRB mode)/1(SCM mode)
#stream0 is the main stream which has the same resolution as the sensor.
[stream0]
Codec = 0 #VMF_VENC_CODEC_TYPE_H264
Width = 1920 #The image width of stream0
Height = 1080 #The image height of stream0
FPS = 25 #The frame rate of stream0
QP = 25 #The base value of the quantization parameter. Its range is from 0 to 51.
Bitrate = 2000000 #The average bitrate of the encoded stream. Its range is from 30,000 to 700,000,000 bps.
PIQ = 0 #PIQ setting. Reduce the difference of QP between intra and inter frame. Default: 0 (disable).
GOP = 50 #Group of pictures. It specifies the number of frames between two intra frames. The maximum GOP value is 600.
Virt_I_Interval = 0 #Virtual intra frame interval. 0:disable, range: 1 ~ (gop-1)
KeepFrameRatio = 0 # This option is valid only on resized stream.
EncodeBufferSize = 6291456 # 6M: 6*1024*1024 = 6291456
EncodeBufferAmount = 3 # (SRB only)
[stream1]
Codec = 0 #VMF_VENC_CODEC_TYPE_H264
Width = 724 #The image width of stream1 (STDC model training resolution)
Height = 362 #The image height of stream1 (STDC model training resolution)
FPS = 25 #The frame rate of stream0
QP = 25 #The base value of the quantization parameter. Its range is from 0 to 51.
Bitrate = 2000000 #The average bitrate of the encoded stream. Its range is from 30,000 to 700,000,000 bps.
PIQ = 0 #PIQ setting. Reduce the difference of QP between intra and inter frame. Default: 0 (disable).
GOP = 50 #Group of pictures. It specifies the number of frames between two intra frames. The maximum GOP value is 600.
Virt_I_Interval = 0 #Virtual intra frame interval. 0:disable, range: 1 ~ (gop-1)
KeepFrameRatio = 0 # This option is valid only on resized stream.
EncodeBufferSize = 2097152 # 2M: 2*1024*1024 = 2097152
EncodeBufferAmount = 3 # (SRB only)
[stream2]
Codec = 0 #VMF_VENC_CODEC_TYPE_H264
Width = 640 #The image width of stream0
Height = 480 #The image height of stream0
FPS = 25 #The frame rate of stream0
QP = 25 #The base value of the quantization parameter. Its range is from 0 to 51.
Bitrate = 2000000 #The average bitrate of the encoded stream. Its range is from 30,000 to 700,000,000 bps.
PIQ = 0 #PIQ setting. Reduce the difference of QP between intra and inter frame. Default: 0 (disable).
GOP = 50 #Group of pictures. It specifies the number of frames between two intra frames. The maximum GOP value is 600.
Virt_I_Interval = 0 #Virtual intra frame interval. 0:disable, range: 1 ~ (gop-1)
KeepFrameRatio = 0 # This option is valid only on resized stream.
EncodeBufferSize = 2097152 # 2M: 2*1024*1024 = 2097152
EncodeBufferAmount = 3 # (SRB only)
[capture]
# Legacy single-JPEG capture (kept for reference, not used by event_recorder)
enable = 0
http_url = http://192.168.0.114:8081/api/upload
cooldown_ms = 1000
[event]
# Violation event recorder — two-channel: JSON (BT/iPad) + tar.gz (Allxon OOB)
enable = 1
bt_uart_dev = /dev/ttyS1 # Channel A: UART device for DX-BT24 BLE module → iPad
bt_at_probe = 0 # 0: skip AT commands (normal); 1: one-time baud upgrade (factory/reset only)
upload_url = http://192.168.0.114:8081/api/golf.cgi # Channel B: tar.gz upload (OOB/cloud path)
# Production: http://192.168.0.99/api/golf.cgi
sd_path = /tmp/sdcard/events # SD card event archive path
sd_max_mb = 7168 # 7 GB — delete oldest when exceeded
upload_delay_ms = 0 # 0: upload immediately after tar.gz built (tar is built after event ends, no extra delay needed)

BIN
build/kCurl Executable file

Binary file not shown.

BIN
build/kdp2_host_stream.o Normal file

Binary file not shown.

BIN
build/kp_firmware.o Normal file

Binary file not shown.

BIN
build/kp_firmware_host_stream Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
build/stat_shim.o Normal file

Binary file not shown.

Binary file not shown.

BIN
build/stdc_post_process.o Normal file

Binary file not shown.

View File

@ -0,0 +1,33 @@
#ifndef BT_UART_H
#define BT_UART_H
/*
* bt_uart DX-BT24 Bluetooth module UART driver
*
* The DX-BT24 is a UART-transparent BLE module. Whatever is written to the
* serial port is forwarded verbatim over BLE to the connected iOS/Android app.
*
* Usage (normal operation module already at 115200):
* bt_uart_init("/dev/ttyS1", 0) do_at_probe=0: skip AT commands
* bt_uart_send_json(json_str) every event, thread-safe
* bt_uart_close() shutdown (optional)
*
* First-time / factory-reset setup (module at factory default 9600):
* bt_uart_init("/dev/ttyS1", 1) do_at_probe=1: negotiate baud via AT
* After success, set bt_at_probe = 0 in INI never probe again.
*/
/* Open and configure the UART to the BT module.
* dev: device path, e.g. "/dev/ttyS1" (NULL or "" = disabled).
* do_at_probe: 0 = open directly at 115200 (normal operation, no AT sent).
* 1 = run AT baud negotiation first (one-time setup only).
* Returns 0 on success, -1 on failure / disabled. */
int bt_uart_init(const char *dev, int do_at_probe);
/* Write JSON bytes to the BT module (no CRLF added). Thread-safe. No-op if not init'd. */
void bt_uart_send_json(const char *json);
/* Close the UART fd and release resources. */
void bt_uart_close(void);
#endif /* BT_UART_H */

View File

@ -7,7 +7,7 @@
* event_recorder golf cart violation event logger
*
* Two channels:
* A) Real-time JSON POST <pc_url>/api/event (iPad / BLE path)
* A) Real-time JSON UART DX-BT24 BLE iPad (bt_uart_send_json)
* B) tar.gz archive POST <upload_url> (OOB / cloud path)
* Simulation: http://192.168.0.114:8081/api/upload
* Production: http://192.168.0.99/api/golf.cgi
@ -21,9 +21,9 @@
* event_recorder_provide_frame() every frame, from app_header_send_inference
*/
/* Call once after INI is loaded (before VMF starts). */
void event_recorder_init(const char *pc_url,
const char *upload_url,
/* Call once after INI is loaded (before VMF starts).
* bt_uart_init() must be called before this. */
void event_recorder_init(const char *upload_url,
const char *sd_path,
int sd_max_mb,
int upload_delay_ms,

View File

@ -88,10 +88,11 @@ http_url = http://192.168.0.114:8081/api/upload
cooldown_ms = 1000
[event]
# Violation event recorder — two-channel: JSON (iPad) + tar.gz (Allxon OOB)
# Violation event recorder — two-channel: JSON (BT/iPad) + tar.gz (Allxon OOB)
enable = 1
pc_url = http://192.168.0.114:8081 # Channel A: PC mock server (iPad/BLE path)
upload_url = http://192.168.0.114:8081/api/upload # Channel B: tar.gz upload (OOB/cloud path)
bt_uart_dev = /dev/ttyS1 # Channel A: UART device for DX-BT24 BLE module → iPad
bt_at_probe = 0 # 0: skip AT commands (normal); 1: one-time baud upgrade (factory/reset only)
upload_url = http://192.168.0.114:8081/api/golf.cgi # Channel B: tar.gz upload (OOB/cloud path)
# Production: http://192.168.0.99/api/golf.cgi
sd_path = /tmp/sdcard/events # SD card event archive path
sd_max_mb = 7168 # 7 GB — delete oldest when exceeded

279
src/host_stream/bt_uart.c Normal file
View File

@ -0,0 +1,279 @@
/*
* bt_uart.c DX-BT24 Bluetooth module UART driver for KL630
*
* Normal operation (bt_at_probe = 0 in INI):
* Open directly at 115200 blocking (VMIN=1). No AT commands sent.
* The module's baud rate is persistent across power cycles once set.
*
* First-time / factory-reset setup (bt_at_probe = 1 in INI):
* Call bt_uart_probe_and_upgrade() which handles the 9600115200 upgrade:
* 1. Try AT+BAUD at 115200 if response, already configured.
* 2. If no response: try AT+BAUD at 9600 if response, send AT+BAUD7
* then AT+RESET to lock in the new baud rate.
* After this one-time step, set bt_at_probe = 0 in INI so AT commands are
* never sent again (avoids forwarding AT strings to connected BLE clients).
*
* bt_uart_send_json() is fully non-blocking: it enqueues the message into an
* internal FIFO and returns immediately. A dedicated writer thread drains the
* queue and performs the actual UART write, so callers are never stalled by IO.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <pthread.h>
#include "bt_uart.h"
/* ── UART fd ─────────────────────────────────────────────────────────────── */
static int s_bt_fd = -1;
/* ── Message queue ───────────────────────────────────────────────────────── */
typedef struct BtMsg {
char *json;
struct BtMsg *next;
} BtMsg;
static BtMsg *s_q_head = NULL;
static BtMsg *s_q_tail = NULL;
static pthread_mutex_t s_q_mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t s_q_cond = PTHREAD_COND_INITIALIZER;
static pthread_t s_writer_tid;
static volatile int s_running = 0;
/* ── Internal: write all bytes to fd ────────────────────────────────────── */
static void uart_write_all(const char *data, size_t len)
{
size_t off = 0;
while (off < len) {
ssize_t n = write(s_bt_fd, data + off, len - off);
if (n <= 0) { perror("[BT] write"); return; }
off += (size_t)n;
}
}
/* ── Writer thread: drains queue, does actual IO ─────────────────────────── */
static void *bt_writer_thread(void *arg)
{
(void)arg;
while (1) {
pthread_mutex_lock(&s_q_mtx);
while (!s_q_head && s_running)
pthread_cond_wait(&s_q_cond, &s_q_mtx);
if (!s_running && !s_q_head) {
pthread_mutex_unlock(&s_q_mtx);
break;
}
BtMsg *msg = s_q_head;
if (msg) {
s_q_head = msg->next;
if (!s_q_head) s_q_tail = NULL;
}
pthread_mutex_unlock(&s_q_mtx);
if (msg) {
if (s_bt_fd >= 0)
uart_write_all(msg->json, strlen(msg->json));
free(msg->json);
free(msg);
}
}
return NULL;
}
/* ── Internal: open + configure serial port ──────────────────────────────── */
static int open_uart(const char *dev, speed_t baud, int vmin, int vtime)
{
int fd = open(dev, O_RDWR | O_NOCTTY);
if (fd < 0) { perror("[BT] open uart"); return -1; }
struct termios opts;
if (tcgetattr(fd, &opts) != 0) {
perror("[BT] tcgetattr");
close(fd);
return -1;
}
cfmakeraw(&opts);
cfsetispeed(&opts, baud);
cfsetospeed(&opts, baud);
opts.c_cflag |= (CLOCAL | CREAD);
opts.c_cflag &= ~PARENB;
opts.c_cflag &= ~CSTOPB;
opts.c_cflag &= ~CSIZE;
opts.c_cflag |= CS8;
opts.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
opts.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL | INLCR);
opts.c_oflag &= ~OPOST;
opts.c_cc[VMIN] = (cc_t)vmin;
opts.c_cc[VTIME] = (cc_t)vtime;
if (tcsetattr(fd, TCSANOW, &opts) != 0) {
perror("[BT] tcsetattr");
close(fd);
return -1;
}
return fd;
}
/* ── One-time AT baud setup (call only when bt_at_probe = 1 in INI) ─────── */
static void bt_uart_probe_and_upgrade(const char *dev)
{
char buf[128] = {0};
int n;
/* Try AT+BAUD at 115200 first */
int fd = open_uart(dev, B115200, 0, 10); /* VTIME=10 → 1s timeout */
if (fd < 0) return;
write(fd, "AT+BAUD\r\n", 9);
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");
return;
}
/* No response at 115200 — try 9600 */
fd = open_uart(dev, B9600, 0, 10);
if (fd < 0) return;
memset(buf, 0, sizeof(buf));
write(fd, "AT+BAUD\r\n", 9);
n = read(fd, buf, sizeof(buf) - 1);
printf("[BT] AT probe @ 9600 => %d byte(s): %s\n", n, n > 0 ? buf : "(none)");
if (n == 0) {
/* No response at either baud — cannot upgrade; proceed anyway */
printf("[BT] WARNING: no AT response at 9600 or 115200\n");
close(fd);
return;
}
/* 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. */
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)");
close(fd);
usleep(300000); /* 300 ms: let module stabilise at 115200 */
/* Reopen at 115200 and reset so the new baud persists across power cycles */
fd = open_uart(dev, B115200, 0, 10);
if (fd < 0) return;
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 */
printf("[BT] upgrade complete — set bt_at_probe = 0 in INI to skip AT on next boot\n");
}
/* ── Public API ───────────────────────────────────────────────────────────── */
int bt_uart_init(const char *dev, int do_at_probe)
{
if (!dev || !*dev) {
printf("[BT] bt_uart_init: no device configured — BT disabled\n");
return -1;
}
if (do_at_probe) {
/* One-time factory setup: negotiate baud rate via AT commands */
bt_uart_probe_and_upgrade(dev);
}
/* Open at 115200, blocking (VMIN=1) for normal operation */
int fd = open_uart(dev, B115200, 1, 0);
if (fd < 0) {
printf("[BT] bt_uart_init: open at 115200 failed\n");
return -1;
}
/* Flush any residual bytes */
tcflush(fd, TCIOFLUSH);
s_bt_fd = fd;
/* Start dedicated writer thread */
s_running = 1;
if (pthread_create(&s_writer_tid, NULL, bt_writer_thread, NULL) != 0) {
perror("[BT] pthread_create writer");
close(fd);
s_bt_fd = -1;
s_running = 0;
return -1;
}
pthread_setname_np(s_writer_tid, "bt_writer");
printf("[BT] bt_uart_init OK: %s @ 115200 (at_probe=%d)\n", dev, do_at_probe);
/* Diagnostic ping — confirms UART→BLE channel is alive.
* No \r\n: BLE is packet-based; delimiter not needed and causes an extra
* empty notification in nRF Connect. */
bt_uart_send_json("{\"class\":\"boot\",\"level\":0}");
printf("[BT] boot ping queued\n");
return 0;
}
void bt_uart_send_json(const char *json)
{
if (s_bt_fd < 0 || !s_running || !json || !*json) return;
BtMsg *msg = (BtMsg *)malloc(sizeof(BtMsg));
if (!msg) return;
msg->json = strdup(json);
msg->next = NULL;
if (!msg->json) { free(msg); return; }
pthread_mutex_lock(&s_q_mtx);
if (s_q_tail) {
s_q_tail->next = msg;
s_q_tail = msg;
} else {
s_q_head = s_q_tail = msg;
}
pthread_cond_signal(&s_q_cond);
pthread_mutex_unlock(&s_q_mtx);
}
void bt_uart_close(void)
{
if (!s_running) return;
/* Signal writer thread to exit after draining remaining messages */
pthread_mutex_lock(&s_q_mtx);
s_running = 0;
pthread_cond_signal(&s_q_cond);
pthread_mutex_unlock(&s_q_mtx);
pthread_join(s_writer_tid, NULL);
if (s_bt_fd >= 0) {
close(s_bt_fd);
s_bt_fd = -1;
printf("[BT] uart closed\n");
}
/* Drain any leftover queue entries */
BtMsg *m = s_q_head;
while (m) {
BtMsg *next = m->next;
free(m->json);
free(m);
m = next;
}
s_q_head = s_q_tail = NULL;
}

View File

@ -4,7 +4,8 @@
* Golf cart violation event recorder for KL630.
*
* Channel A (iPad / BLE path):
* POST JSON to /api/event on every level change.
* Write JSON to UART DX-BT24 BLE module iPad on every level change.
* bt_uart_init() must be called before event_recorder_init().
*
* Channel B (OOB / cloud path):
* After event ends, wait upload_delay_ms, then:
@ -36,23 +37,23 @@
#include <vmf/video_source.h>
#include "event_recorder.h"
#include "bt_uart.h"
#include "stdc_post_process.h" /* THR_*_COLLISION constants */
/* ── External (from kdp2_host_stream.c) ──────────────────────────────────── */
extern VMF_VSRC_HANDLE_T *g_ptVsrcHandle;
/* ── Config ──────────────────────────────────────────────────────────────── */
/* Channel A (JSON events → iPad path) */
static char s_pc_host[64] = "192.168.0.114";
static int s_pc_port = 8081;
/* Channel A: JSON events → BT UART → iPad (via DX-BT24 BLE module)
* Initialized by bt_uart_init() in kp_firmware.c. No local config needed. */
/* Channel B (tar.gz upload → OOB / cloud path)
* Same endpoint as capture JPG upload (golf.cgi), just posting tar.gz.
* Simulation: http://192.168.0.114:8081/api/upload
* Production: http://192.168.0.99/api/golf.cgi */
static char s_up_host[64] = "192.168.0.114";
static int s_up_port = 8081;
static char s_up_path[128] = "/api/upload";
static char s_up_host[64] = "192.168.0.99";
static int s_up_port = 80;
static char s_up_path[128] = "/api/golf.cgi";
static char s_sd_path[256] = "/tmp/sdcard/events";
static long long s_sd_max_bytes = (long long)7 * 1024 * 1024 * 1024; /* 7 GB — must be long long on 32-bit ARM */
@ -113,7 +114,7 @@ static int g_last_pond = 0;
static int g_last_tree = 0;
/* ═══════════════════════════════════════════════════════════════════════════
* URL / network helpers
* URL / network helpers (Channel B only)
* */
static void parse_url_into(const char *url,
@ -144,11 +145,6 @@ static void parse_url_into(const char *url,
if (host) snprintf(host, host_n, "%s", hostport);
}
static void parse_url(const char *url)
{
parse_url_into(url, s_pc_host, sizeof(s_pc_host), &s_pc_port, NULL, 0);
}
static int open_socket_to(const char *host, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
@ -167,31 +163,6 @@ static int open_socket_to(const char *host, int port)
return sock;
}
static int open_socket(void) { return open_socket_to(s_pc_host, s_pc_port); }
/* ── Channel A: POST JSON to /api/event ──────────────────────────────────── */
static void http_post_json(const char *json)
{
int sock = open_socket();
if (sock < 0) {
printf("[EVT] JSON post: connect failed\n");
return;
}
char hdr[256];
int hdr_len = snprintf(hdr, sizeof(hdr),
"POST /api/event HTTP/1.0\r\n"
"Host: %s\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %zu\r\n"
"Connection: close\r\n\r\n",
s_pc_host, strlen(json));
send(sock, hdr, hdr_len, 0);
send(sock, json, strlen(json), 0);
char resp[128] = {0};
recv(sock, resp, sizeof(resp) - 1, 0);
close(sock);
}
/* ── Channel B: POST tar.gz via kCurl (same method as golf.cgi JPEG upload) ── */
/* kCurl lives in the firmware bin directory — use absolute path so CWD doesn't matter */
#define KCURL_PATH "/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin/kCurl"
@ -220,44 +191,6 @@ static void http_post_file(const char *filepath)
printf("[EVT] upload: %s sent OK\n", basename);
}
/* ── GET /api/time → settimeofday ────────────────────────────────────────── */
static void http_sync_time(void)
{
int sock = open_socket();
if (sock < 0) {
printf("[EVT] time sync: connect failed\n");
return;
}
char req[128];
int rlen = snprintf(req, sizeof(req),
"GET /api/time HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n",
s_pc_host);
send(sock, req, rlen, 0);
char resp[512] = {0};
recv(sock, resp, sizeof(resp) - 1, 0);
close(sock);
/* Find "unix": in response body */
char *p = strstr(resp, "\"unix\":");
if (!p) {
printf("[EVT] time sync: parse failed\n");
return;
}
p += 7;
while (*p == ' ') p++;
long unix_ts = atol(p);
if (unix_ts <= 0) {
printf("[EVT] time sync: bad timestamp %ld\n", unix_ts);
return;
}
struct timeval tv = { (time_t)unix_ts, 0 };
if (settimeofday(&tv, NULL) == 0)
printf("[EVT] time synced: unix=%ld\n", unix_ts);
else
printf("[EVT] time sync: settimeofday failed (errno=%d)\n", errno);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Helpers
* */
@ -265,7 +198,8 @@ static void http_sync_time(void)
/* UTC+8 offset for Taiwan time (TZ env may not be set on embedded device) */
#define TZ_OFFSET_SEC (8 * 3600)
/* UTC time with Z suffix — used in channel A JSON (spec requires UTC ISO 8601) */
/* UTC time with Z suffix — reserved for future use */
__attribute__((unused))
static void now_iso_utc(char *buf, size_t n)
{
struct timeval tv;
@ -307,7 +241,7 @@ static void write_event_json(const char *work_dir, const char *event_id,
char path[320];
snprintf(path, sizeof(path), "%s/event.json", work_dir);
FILE *f = fopen(path, "w");
if (!f) return;
if (!f) { printf("[EVT] write_event_json: fopen failed: %s\n", path); return; }
char ts[32];
now_iso(ts, sizeof(ts));
@ -417,39 +351,21 @@ static void rm_work_dir(const char *dir)
}
/* ═══════════════════════════════════════════════════════════════════════════
* JSON event post (detached thread doesn't block inference pipeline)
* JSON event fire bt_uart_send_json() is non-blocking (internal queue +
* dedicated writer thread), so we call it directly without a wrapper thread.
* */
typedef struct {
char json[512];
} JsonPostArg;
static void *json_post_thread(void *arg)
{
JsonPostArg *a = (JsonPostArg *)arg;
http_post_json(a->json);
free(a);
return NULL;
}
static void fire_json_async(const char *event_id, const char *type, int level)
{
char ts[32];
now_iso_utc(ts, sizeof(ts)); /* spec: ISO 8601 UTC with Z suffix */
(void)event_id; /* not included in BT payload — kept for caller compatibility */
JsonPostArg *a = (JsonPostArg *)malloc(sizeof(JsonPostArg));
if (!a) return;
snprintf(a->json, sizeof(a->json),
"{\"response_type\":\"violation\","
"\"content\":{\"id\":\"%s\",\"date\":\"%s\",\"type\":\"%s\",\"level\":%d}}",
event_id, ts, type, level);
if (!type) type = "unknown";
pthread_t t;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&t, &attr, json_post_thread, a) != 0) free(a);
pthread_attr_destroy(&attr);
char json[64];
/* Compact format fits in a single BLE packet:
* e.g. {"class":"lane","level":1} = 26 bytes */
snprintf(json, sizeof(json), "{\"class\":\"%s\",\"level\":%d}", type, level);
bt_uart_send_json(json); /* enqueues and returns immediately */
}
/* ═══════════════════════════════════════════════════════════════════════════
@ -539,7 +455,10 @@ static void launch_upload(const char *work_dir, const char *event_id,
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&t, &attr, upload_thread, a) != 0) free(a);
if (pthread_create(&t, &attr, upload_thread, a) != 0) {
printf("[EVT] launch_upload: pthread_create failed — upload dropped id=%s\n", a->event_id);
free(a);
}
pthread_attr_destroy(&attr);
}
@ -599,6 +518,9 @@ static void request_snap(const char *filename, const char *work_dir,
snprintf(g_snap_req.work_dir, sizeof(g_snap_req.work_dir), "%s", work_dir);
snprintf(g_snap_req.event_id, sizeof(g_snap_req.event_id), "%s", event_id);
snprintf(g_snap_req.event_type, sizeof(g_snap_req.event_type), "%s", event_type);
} else {
printf("[EVT] snap dropped: already pending %s (new: %s/%s)\n",
g_snap_req.filename, event_id, filename);
}
pthread_mutex_unlock(&g_snap_mtx);
}
@ -623,8 +545,7 @@ static void grass_enter_level(int level)
* Public API
* */
void event_recorder_init(const char *pc_url,
const char *upload_url,
void event_recorder_init(const char *upload_url,
const char *sd_path,
int sd_max_mb,
int upload_delay_ms,
@ -634,22 +555,16 @@ void event_recorder_init(const char *pc_url,
s_upload_delay_ms = (upload_delay_ms >= 0) ? upload_delay_ms : 60000;
s_sd_max_bytes = (long long)sd_max_mb * 1024 * 1024;
if (pc_url && *pc_url) parse_url(pc_url);
if (upload_url && *upload_url) parse_url_into(upload_url,
s_up_host, sizeof(s_up_host),
&s_up_port,
s_up_path, sizeof(s_up_path));
if (sd_path && *sd_path) snprintf(s_sd_path, sizeof(s_sd_path), "%s", sd_path);
printf("[EVT] init: enable=%d ch_a=%s:%d ch_b=%s:%d%s sd=%s max=%dMB delay=%dms\n",
s_enabled, s_pc_host, s_pc_port,
printf("[EVT] init: enable=%d ch_a=bt_uart ch_b=%s:%d%s sd=%s max=%dMB delay=%dms\n",
s_enabled,
s_up_host, s_up_port, s_up_path,
s_sd_path, sd_max_mb, s_upload_delay_ms);
if (!s_enabled) return;
/* Sync time from PC server */
http_sync_time();
}
/* ── Recv callback: drives state machine ─────────────────────────────────── */

View File

@ -1655,6 +1655,13 @@ void *kdp2_host_stream_image_thread(void *arg)
release_video_source(g_ptVsrcHandle);
goto EXIT_MIPI_IMAGE_THREAD;
}
/* Set g_dwDrawBoxType BEFORE g_dwInitBind=1.
* VOC thread unblocks on g_dwInitBind and immediately checks g_dwDrawBoxType
* to decide which SSM pin to read from. If draw box is enabled on stream 0,
* it must see g_dwDrawBoxType=1 so it reads from VENC_VSRC_B_PIN (overlay
* output) rather than the raw ISP SSM fixes intermittent HDMI no-overlay. */
if (pHostStreamInit->bDrawBoxEnable && pHostStreamInit->dwEncodeStreamCount > 0)
g_dwDrawBoxType = 1;
g_dwInitBind = 1;
if (((dwInferenceWidth == g_tLayout.dwVideoWidth) && (dwInferenceHeight == g_tLayout.dwVideoHeight)) || (pHostStreamInit->bRoiEnable)) {

View File

@ -41,6 +41,7 @@
#include "kdp2_host_stream.h"
#include "fec_api.h"
#include "event_recorder.h"
#include "bt_uart.h"
//fifo queue buffer setting
#define IMAGE_BUFFER_COUNT 3
@ -224,12 +225,14 @@ int loadConfig(HOST_STREAM_INIT_OPT_T* pHostStreamInit)
/* --- [event] section: violation event recording + upload --- */
{
int ev_enable = iniparser_getint(ini, "event:enable", 0);
const char *ev_url = iniparser_getstring(ini, "event:pc_url", "http://192.168.0.114:8081");
const char *bt_dev = iniparser_getstring(ini, "event:bt_uart_dev", "/dev/ttyS1");
int bt_at_probe = iniparser_getint(ini, "event:bt_at_probe", 0);
const char *ev_up = iniparser_getstring(ini, "event:upload_url", "http://192.168.0.114:8081/api/upload");
const char *ev_sd = iniparser_getstring(ini, "event:sd_path", "/tmp/sdcard/events");
int ev_max_mb = iniparser_getint(ini, "event:sd_max_mb", 7168);
int ev_delay = iniparser_getint(ini, "event:upload_delay_ms", 60000);
event_recorder_init(ev_url, ev_up, ev_sd, ev_max_mb, ev_delay, ev_enable);
bt_uart_init(bt_dev, bt_at_probe);
event_recorder_init(ev_up, ev_sd, ev_max_mb, ev_delay, ev_enable);
}
iniparser_freedict(ini);
@ -441,13 +444,6 @@ int main (int argc, char* argv[])
VMF_NNM_Fifoq_Manager_Allocate_Buffer(IMAGE_BUFFER_COUNT, ImageBufferSize, RESULT_BUFFER_COUNT, RESULT_BUFFER_SIZE);
printf("[DBG] After AllocateBuffer, ImageBufferSize=%u\n", ImageBufferSize);
printf("###################################################\n");
printf("###################################################\n");
printf("%s: func:%s,line:%d \n", __FILE__, __func__,__LINE__);
printf("###################################################\n");
printf("###################################################\n");
pthread_create(&task_stream_image_handle, NULL, kdp2_host_stream_image_thread, &HostStreamInit);
pthread_create(&task_update_result_handle, NULL, kdp2_host_update_result_thread, &HostStreamInit);
if(!HostStreamInit.dwNnmSource && HostStreamInit.dwEncodeStreamCount > 0)

View File

@ -0,0 +1,9 @@
#!/bin/sh
# /usr/local/sbin/www/api/time — Allxon Bolt time CGI
# Returns current Unix timestamp in JSON.
# Same response format as the KL630 mock server GET /api/time.
#
# Response: {"unix": <epoch_seconds>}
printf "Content-Type: application/json\r\n"
printf "\r\n"
printf '{"unix": %s}' "$(date +%s)"

View File

@ -0,0 +1,7 @@
#!/bin/sh
# 安裝 time CGI 到 Allxon Bolt HTTP server
# 在 Bolt 上執行sh install.sh
# 安裝後可用GET http://192.168.0.100/api/time → {"unix": <epoch>}
cp ./content/time /usr/local/sbin/www/api/time
chmod +x /usr/local/sbin/www/api/time
echo "time CGI installed: http://192.168.0.100/api/time"

View File

@ -13,7 +13,7 @@
set -e
HOST_URL="http://192.168.0.102:8080"
HOST_URL="http://192.168.0.114:8080"
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin
FW=/mnt/flash/vienna/kp_firmware_host_stream
INI=$BIN_DIR/ini/host_stream.ini

View File

@ -184,7 +184,7 @@ class Handler(BaseHTTPRequestHandler):
self.send_error(400, str(e))
# ── Channel B: tar.gz archive upload ─────────────────────────
elif path == '/api/upload':
elif path in ('/api/upload', '/api/golf.cgi'):
# 1) kCurl sends filename as query param: /api/upload?filename=xxx
qs = parse_qs(urlparse(self.path).query)
filename = (qs.get('filename') or [None])[0]
@ -205,8 +205,8 @@ class Handler(BaseHTTPRequestHandler):
fp = os.path.join(UPLOAD_DIR, filename)
with open(fp, 'wb') as f:
f.write(body)
print(f" [CHANNEL-B] saved {filename} ({len(body):,} bytes)")
self._json({'ok': True, 'filename': filename, 'bytes': len(body)})
print(f" [CHANNEL-B] {path} saved {filename} ({len(body):,} bytes)")
self._json({'ok': True, 'path': path, 'filename': filename, 'bytes': len(body)})
else:
self.send_error(404)
@ -250,6 +250,7 @@ if __name__ == '__main__':
print(f" Time API : GET http://localhost:{PORT}/api/time")
print(f" Event API : POST http://localhost:{PORT}/api/event")
print(f" Upload API : POST http://localhost:{PORT}/api/upload")
print(f" Golf API : POST http://localhost:{PORT}/api/golf.cgi")
print(f" Uploads : {UPLOAD_DIR}")
print("=" * 55)
try:

View File

@ -147,7 +147,7 @@ def build_targz(event_id: str, event_type: str, max_level: int,
# ── Scenarios ─────────────────────────────────────────────────────────
def scenario_grass(server, event_id):
def scenario_grass(server, event_id, upload_path):
"""Full grass violation: L1 → L2 → L3 → L0, then upload tar.gz."""
print("\n[Grass Scenario]")
print("Step 0: get server time (simulates NTP from OOB Enabler)")
@ -191,10 +191,10 @@ def scenario_grass(server, event_id):
ts_fn = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S')
filename = f'event_{event_id}_{ts_fn}.tar.gz'
print(f"\n→ [CHANNEL B] uploading {filename} ({len(tgz):,} bytes)")
post_targz(server, '/api/upload', filename, tgz)
post_targz(server, upload_path, filename, tgz)
def scenario_hazard(server, hazard_type, event_id):
def scenario_hazard(server, hazard_type, event_id, upload_path):
"""Single-shot hazard event — no level, no tar.gz wait."""
labels = {'bunker': '沙坑', 'pond': '水池', 'tree': '樹木'}
label = labels.get(hazard_type, hazard_type)
@ -215,10 +215,10 @@ def scenario_hazard(server, hazard_type, event_id):
ts_fn = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S')
filename = f'event_{event_id}_{ts_fn}.tar.gz'
print(f"→ [CHANNEL B] uploading {filename} ({len(tgz):,} bytes)")
post_targz(server, '/api/upload', filename, tgz)
post_targz(server, upload_path, filename, tgz)
def scenario_person(server, event_id):
def scenario_person(server, event_id, upload_path):
"""Person detection — single-shot."""
print("\n[Person Scenario]")
get_server_time(server)
@ -236,13 +236,15 @@ def scenario_person(server, event_id):
ts_fn = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S')
filename = f'event_{event_id}_{ts_fn}.tar.gz'
print(f"→ [CHANNEL B] uploading {filename} ({len(tgz):,} bytes)")
post_targz(server, '/api/upload', filename, tgz)
post_targz(server, upload_path, filename, tgz)
# ── Main ───────────────────────────────────────────────────────────────
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--server', default=DEFAULT_SERVER)
parser.add_argument('--upload-path', default='/api/upload',
choices=['/api/upload', '/api/golf.cgi'])
parser.add_argument('--hazard', choices=['bunker', 'pond', 'tree'])
parser.add_argument('--person', action='store_true')
args = parser.parse_args()
@ -250,8 +252,8 @@ if __name__ == '__main__':
event_id = str(int(time.time()))
if args.hazard:
scenario_hazard(args.server, args.hazard, event_id)
scenario_hazard(args.server, args.hazard, event_id, args.upload_path)
elif args.person:
scenario_person(args.server, event_id)
scenario_person(args.server, event_id, args.upload_path)
else:
scenario_grass(args.server, event_id)
scenario_grass(args.server, event_id, args.upload_path)

View File

@ -506,36 +506,101 @@ def api_deploy_run():
return Response(generate(), mimetype="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
# ── API: first-time ISP setup via Telnet (SSE) ────────────────────────────────
@app.route("/api/setup/run")
def api_setup_run():
# ── 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
awb = f"{bd}/Resource/AWB/AutoWhiteBalance.ini"
isp0 = f"{bd}/Resource/ISP/0/pqtable_ispe_Config.cfg"
isp1 = f"{bd}/Resource/ISP/1/pqtable_ispe_Config.cfg"
commands = [
f"sed -i 's/dwStatisticsSrcType = 0/dwStatisticsSrcType = 2/' {awb}",
f"sed -i 's/bGTREnable = 0/bGTREnable = 1/' {isp0}",
f"sed -i 's/bGTREnable = 0/bGTREnable = 1/' {isp1}",
f"grep dwStatisticsSrcType {awb}",
f"grep bGTREnable {isp0}",
]
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")
for cmd in commands:
yield sse(f"$ {cmd}", "prompt")
for ln in _telnet_run(tn, cmd):
# 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("One-time ISP setup complete. Settings written to flash.", "ok")
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()
@ -1548,9 +1613,9 @@ 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"><polyline points="5 12 12 5 19 12"/><line x1="12" y1="5" x2="12" y2="19"/></svg>
Deploy to KL630
</button>
<button class="btn btn-warn" id="btn-setup" onclick="runAction('setup')">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 0-14.14 0M4.93 19.07a10 10 0 0 0 14.14 0"/></svg>
First-time ISP Setup
<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" onclick="clearLog()">Clear Log</button>
<button class="btn btn-ghost" id="btn-autostart" onclick="runAction('autostart')">
@ -1707,14 +1772,14 @@ function runAction(action) {
'&port=' + encodeURIComponent(cfg.port) +
'&out_rtsp=' + (document.getElementById('out-rtsp').checked ? 1 : 0) +
'&out_hdmi=' + (document.getElementById('out-hdmi').checked ? 1 : 0),
setup: '/api/setup/run?ip=' + encodeURIComponent(cfg.kl630_ip),
bt_setup: '/api/bt_setup/run?ip=' + encodeURIComponent(cfg.kl630_ip),
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),
autostart_read: '/api/autostart/read?ip=' + encodeURIComponent(cfg.kl630_ip),
mount_sd: '/api/mount_sd?ip=' + encodeURIComponent(cfg.kl630_ip),
};
const labels = { compile:'Compile', deploy:'Deploy to KL630', setup:'First-time ISP Setup', autostart:'Write Autostart', autostart_read:'Read Autostart', mount_sd:'Mount SD' };
const labels = { compile:'Compile', deploy:'Deploy to KL630', bt_setup:'BT 初始化', autostart:'Write Autostart', autostart_read:'Read Autostart', mount_sd:'Mount SD' };
appendLog('\\n── ' + labels[action] + ' ' + ''.repeat(40), 'prompt');
setLogStatus('Running...');
@ -1810,7 +1875,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-setup','btn-autostart','btn-autostart-read','btn-mount-sd','btn-model-apply'].forEach(id => document.getElementById(id).disabled = disabled); }
function setBtns(disabled){ ['btn-compile','btn-deploy','btn-bt-setup','btn-autostart','btn-autostart-read','btn-mount-sd','btn-model-apply'].forEach(id => document.getElementById(id).disabled = disabled); }
// Terminal
// by mars