Compare commits
2 Commits
9d7d9073a5
...
548cec2d4f
| Author | SHA1 | Date | |
|---|---|---|---|
| 548cec2d4f | |||
| a3f3b2f8fc |
6
.web_config.json
Normal file
6
.web_config.json
Normal 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
BIN
build/app_header_init.o
Normal file
Binary file not shown.
BIN
build/application_init.o
Normal file
BIN
build/application_init.o
Normal file
Binary file not shown.
BIN
build/bt_uart.o
Normal file
BIN
build/bt_uart.o
Normal file
Binary file not shown.
46
build/demo_hdmi.sh
Normal file
46
build/demo_hdmi.sh
Normal 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
BIN
build/demo_post_utils.o
Normal file
Binary file not shown.
56
build/demo_rtsp.sh
Normal file
56
build/demo_rtsp.sh
Normal 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
58
build/demo_rtsp_hdmi.sh
Normal 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
68
build/deploy.sh
Normal 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
BIN
build/event_recorder.o
Normal file
Binary file not shown.
BIN
build/fec_api.o
Normal file
BIN
build/fec_api.o
Normal file
Binary file not shown.
BIN
build/glibc_shim.o
Normal file
BIN
build/glibc_shim.o
Normal file
Binary file not shown.
99
build/host_stream.ini
Normal file
99
build/host_stream.ini
Normal 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
BIN
build/kCurl
Executable file
Binary file not shown.
BIN
build/kdp2_host_stream.o
Normal file
BIN
build/kdp2_host_stream.o
Normal file
Binary file not shown.
BIN
build/kp_firmware.o
Normal file
BIN
build/kp_firmware.o
Normal file
Binary file not shown.
BIN
build/kp_firmware_host_stream
Executable file
BIN
build/kp_firmware_host_stream
Executable file
Binary file not shown.
BIN
build/nef/STDC03302026_models_630.nef
Normal file
BIN
build/nef/STDC03302026_models_630.nef
Normal file
Binary file not shown.
BIN
build/nef/STDC04012026_models_630.nef
Normal file
BIN
build/nef/STDC04012026_models_630.nef
Normal file
Binary file not shown.
BIN
build/stat_shim.o
Normal file
BIN
build/stat_shim.o
Normal file
Binary file not shown.
BIN
build/stdc_inf_single_model.o
Normal file
BIN
build/stdc_inf_single_model.o
Normal file
Binary file not shown.
BIN
build/stdc_post_process.o
Normal file
BIN
build/stdc_post_process.o
Normal file
Binary file not shown.
@ -65,7 +65,7 @@ fi
|
|||||||
ALL_SRCS=$(ls \
|
ALL_SRCS=$(ls \
|
||||||
$WORKSPACE/src/host_stream/*.c \
|
$WORKSPACE/src/host_stream/*.c \
|
||||||
$WORKSPACE/src/app_flow/*.c \
|
$WORKSPACE/src/app_flow/*.c \
|
||||||
$WORKSPACE/src/pre_post_proc/*.c \
|
$WORKSPACE/src/pre_post_proc/*.c \
|
||||||
2>/dev/null)
|
2>/dev/null)
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
33
include/host_stream/bt_uart.h
Normal file
33
include/host_stream/bt_uart.h
Normal 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 */
|
||||||
@ -7,7 +7,7 @@
|
|||||||
* event_recorder — golf cart violation event logger
|
* event_recorder — golf cart violation event logger
|
||||||
*
|
*
|
||||||
* Two channels:
|
* 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)
|
* B) tar.gz archive → POST <upload_url> (OOB / cloud path)
|
||||||
* Simulation: http://192.168.0.114:8081/api/upload
|
* Simulation: http://192.168.0.114:8081/api/upload
|
||||||
* Production: http://192.168.0.99/api/golf.cgi
|
* Production: http://192.168.0.99/api/golf.cgi
|
||||||
@ -21,9 +21,9 @@
|
|||||||
* event_recorder_provide_frame() — every frame, from app_header_send_inference
|
* event_recorder_provide_frame() — every frame, from app_header_send_inference
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Call once after INI is loaded (before VMF starts). */
|
/* Call once after INI is loaded (before VMF starts).
|
||||||
void event_recorder_init(const char *pc_url,
|
* bt_uart_init() must be called before this. */
|
||||||
const char *upload_url,
|
void event_recorder_init(const char *upload_url,
|
||||||
const char *sd_path,
|
const char *sd_path,
|
||||||
int sd_max_mb,
|
int sd_max_mb,
|
||||||
int upload_delay_ms,
|
int upload_delay_ms,
|
||||||
|
|||||||
@ -88,10 +88,11 @@ http_url = http://192.168.0.114:8081/api/upload
|
|||||||
cooldown_ms = 1000
|
cooldown_ms = 1000
|
||||||
|
|
||||||
[event]
|
[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
|
enable = 1
|
||||||
pc_url = http://192.168.0.114:8081 # Channel A: PC mock server (iPad/BLE path)
|
bt_uart_dev = /dev/ttyS1 # Channel A: UART device for DX-BT24 BLE module → iPad
|
||||||
upload_url = http://192.168.0.114:8081/api/upload # Channel B: tar.gz upload (OOB/cloud path)
|
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
|
# Production: http://192.168.0.99/api/golf.cgi
|
||||||
sd_path = /tmp/sdcard/events # SD card event archive path
|
sd_path = /tmp/sdcard/events # SD card event archive path
|
||||||
sd_max_mb = 7168 # 7 GB — delete oldest when exceeded
|
sd_max_mb = 7168 # 7 GB — delete oldest when exceeded
|
||||||
|
|||||||
279
src/host_stream/bt_uart.c
Normal file
279
src/host_stream/bt_uart.c
Normal 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 9600→115200 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;
|
||||||
|
}
|
||||||
@ -4,7 +4,8 @@
|
|||||||
* Golf cart violation event recorder for KL630.
|
* Golf cart violation event recorder for KL630.
|
||||||
*
|
*
|
||||||
* Channel A (iPad / BLE path):
|
* 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):
|
* Channel B (OOB / cloud path):
|
||||||
* After event ends, wait upload_delay_ms, then:
|
* After event ends, wait upload_delay_ms, then:
|
||||||
@ -36,23 +37,23 @@
|
|||||||
#include <vmf/video_source.h>
|
#include <vmf/video_source.h>
|
||||||
|
|
||||||
#include "event_recorder.h"
|
#include "event_recorder.h"
|
||||||
|
#include "bt_uart.h"
|
||||||
#include "stdc_post_process.h" /* THR_*_COLLISION constants */
|
#include "stdc_post_process.h" /* THR_*_COLLISION constants */
|
||||||
|
|
||||||
/* ── External (from kdp2_host_stream.c) ──────────────────────────────────── */
|
/* ── External (from kdp2_host_stream.c) ──────────────────────────────────── */
|
||||||
extern VMF_VSRC_HANDLE_T *g_ptVsrcHandle;
|
extern VMF_VSRC_HANDLE_T *g_ptVsrcHandle;
|
||||||
|
|
||||||
/* ── Config ──────────────────────────────────────────────────────────────── */
|
/* ── Config ──────────────────────────────────────────────────────────────── */
|
||||||
/* Channel A (JSON events → iPad path) */
|
/* Channel A: JSON events → BT UART → iPad (via DX-BT24 BLE module)
|
||||||
static char s_pc_host[64] = "192.168.0.114";
|
* Initialized by bt_uart_init() in kp_firmware.c. No local config needed. */
|
||||||
static int s_pc_port = 8081;
|
|
||||||
|
|
||||||
/* Channel B (tar.gz upload → OOB / cloud path)
|
/* Channel B (tar.gz upload → OOB / cloud path)
|
||||||
* Same endpoint as capture JPG upload (golf.cgi), just posting tar.gz.
|
* Same endpoint as capture JPG upload (golf.cgi), just posting tar.gz.
|
||||||
* Simulation: http://192.168.0.114:8081/api/upload
|
* Simulation: http://192.168.0.114:8081/api/upload
|
||||||
* Production: http://192.168.0.99/api/golf.cgi */
|
* Production: http://192.168.0.99/api/golf.cgi */
|
||||||
static char s_up_host[64] = "192.168.0.114";
|
static char s_up_host[64] = "192.168.0.99";
|
||||||
static int s_up_port = 8081;
|
static int s_up_port = 80;
|
||||||
static char s_up_path[128] = "/api/upload";
|
static char s_up_path[128] = "/api/golf.cgi";
|
||||||
|
|
||||||
static char s_sd_path[256] = "/tmp/sdcard/events";
|
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 */
|
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;
|
static int g_last_tree = 0;
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
* URL / network helpers
|
* URL / network helpers (Channel B only)
|
||||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
static void parse_url_into(const char *url,
|
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);
|
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)
|
static int open_socket_to(const char *host, int port)
|
||||||
{
|
{
|
||||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
@ -167,31 +163,6 @@ static int open_socket_to(const char *host, int port)
|
|||||||
return sock;
|
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) ── */
|
/* ── 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 */
|
/* 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"
|
#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);
|
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
|
* 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) */
|
/* UTC+8 offset for Taiwan time (TZ env may not be set on embedded device) */
|
||||||
#define TZ_OFFSET_SEC (8 * 3600)
|
#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)
|
static void now_iso_utc(char *buf, size_t n)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
@ -307,7 +241,7 @@ static void write_event_json(const char *work_dir, const char *event_id,
|
|||||||
char path[320];
|
char path[320];
|
||||||
snprintf(path, sizeof(path), "%s/event.json", work_dir);
|
snprintf(path, sizeof(path), "%s/event.json", work_dir);
|
||||||
FILE *f = fopen(path, "w");
|
FILE *f = fopen(path, "w");
|
||||||
if (!f) return;
|
if (!f) { printf("[EVT] write_event_json: fopen failed: %s\n", path); return; }
|
||||||
|
|
||||||
char ts[32];
|
char ts[32];
|
||||||
now_iso(ts, sizeof(ts));
|
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)
|
static void fire_json_async(const char *event_id, const char *type, int level)
|
||||||
{
|
{
|
||||||
char ts[32];
|
(void)event_id; /* not included in BT payload — kept for caller compatibility */
|
||||||
now_iso_utc(ts, sizeof(ts)); /* spec: ISO 8601 UTC with Z suffix */
|
|
||||||
|
|
||||||
JsonPostArg *a = (JsonPostArg *)malloc(sizeof(JsonPostArg));
|
if (!type) type = "unknown";
|
||||||
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);
|
|
||||||
|
|
||||||
pthread_t t;
|
char json[64];
|
||||||
pthread_attr_t attr;
|
/* Compact format fits in a single BLE packet:
|
||||||
pthread_attr_init(&attr);
|
* e.g. {"class":"lane","level":1} = 26 bytes */
|
||||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
snprintf(json, sizeof(json), "{\"class\":\"%s\",\"level\":%d}", type, level);
|
||||||
if (pthread_create(&t, &attr, json_post_thread, a) != 0) free(a);
|
bt_uart_send_json(json); /* enqueues and returns immediately */
|
||||||
pthread_attr_destroy(&attr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════════
|
||||||
@ -539,7 +455,10 @@ static void launch_upload(const char *work_dir, const char *event_id,
|
|||||||
pthread_attr_t attr;
|
pthread_attr_t attr;
|
||||||
pthread_attr_init(&attr);
|
pthread_attr_init(&attr);
|
||||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
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);
|
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.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_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);
|
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);
|
pthread_mutex_unlock(&g_snap_mtx);
|
||||||
}
|
}
|
||||||
@ -623,8 +545,7 @@ static void grass_enter_level(int level)
|
|||||||
* Public API
|
* Public API
|
||||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
void event_recorder_init(const char *pc_url,
|
void event_recorder_init(const char *upload_url,
|
||||||
const char *upload_url,
|
|
||||||
const char *sd_path,
|
const char *sd_path,
|
||||||
int sd_max_mb,
|
int sd_max_mb,
|
||||||
int upload_delay_ms,
|
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_upload_delay_ms = (upload_delay_ms >= 0) ? upload_delay_ms : 60000;
|
||||||
s_sd_max_bytes = (long long)sd_max_mb * 1024 * 1024;
|
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,
|
if (upload_url && *upload_url) parse_url_into(upload_url,
|
||||||
s_up_host, sizeof(s_up_host),
|
s_up_host, sizeof(s_up_host),
|
||||||
&s_up_port,
|
&s_up_port,
|
||||||
s_up_path, sizeof(s_up_path));
|
s_up_path, sizeof(s_up_path));
|
||||||
if (sd_path && *sd_path) snprintf(s_sd_path, sizeof(s_sd_path), "%s", sd_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",
|
printf("[EVT] init: enable=%d ch_a=bt_uart ch_b=%s:%d%s sd=%s max=%dMB delay=%dms\n",
|
||||||
s_enabled, s_pc_host, s_pc_port,
|
s_enabled,
|
||||||
s_up_host, s_up_port, s_up_path,
|
s_up_host, s_up_port, s_up_path,
|
||||||
s_sd_path, sd_max_mb, s_upload_delay_ms);
|
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 ─────────────────────────────────── */
|
/* ── Recv callback: drives state machine ─────────────────────────────────── */
|
||||||
|
|||||||
@ -1655,6 +1655,13 @@ void *kdp2_host_stream_image_thread(void *arg)
|
|||||||
release_video_source(g_ptVsrcHandle);
|
release_video_source(g_ptVsrcHandle);
|
||||||
goto EXIT_MIPI_IMAGE_THREAD;
|
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;
|
g_dwInitBind = 1;
|
||||||
|
|
||||||
if (((dwInferenceWidth == g_tLayout.dwVideoWidth) && (dwInferenceHeight == g_tLayout.dwVideoHeight)) || (pHostStreamInit->bRoiEnable)) {
|
if (((dwInferenceWidth == g_tLayout.dwVideoWidth) && (dwInferenceHeight == g_tLayout.dwVideoHeight)) || (pHostStreamInit->bRoiEnable)) {
|
||||||
|
|||||||
@ -41,6 +41,7 @@
|
|||||||
#include "kdp2_host_stream.h"
|
#include "kdp2_host_stream.h"
|
||||||
#include "fec_api.h"
|
#include "fec_api.h"
|
||||||
#include "event_recorder.h"
|
#include "event_recorder.h"
|
||||||
|
#include "bt_uart.h"
|
||||||
|
|
||||||
//fifo queue buffer setting
|
//fifo queue buffer setting
|
||||||
#define IMAGE_BUFFER_COUNT 3
|
#define IMAGE_BUFFER_COUNT 3
|
||||||
@ -224,12 +225,14 @@ int loadConfig(HOST_STREAM_INIT_OPT_T* pHostStreamInit)
|
|||||||
/* --- [event] section: violation event recording + upload --- */
|
/* --- [event] section: violation event recording + upload --- */
|
||||||
{
|
{
|
||||||
int ev_enable = iniparser_getint(ini, "event:enable", 0);
|
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_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");
|
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_max_mb = iniparser_getint(ini, "event:sd_max_mb", 7168);
|
||||||
int ev_delay = iniparser_getint(ini, "event:upload_delay_ms", 60000);
|
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);
|
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);
|
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("[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_stream_image_handle, NULL, kdp2_host_stream_image_thread, &HostStreamInit);
|
||||||
pthread_create(&task_update_result_handle, NULL, kdp2_host_update_result_thread, &HostStreamInit);
|
pthread_create(&task_update_result_handle, NULL, kdp2_host_update_result_thread, &HostStreamInit);
|
||||||
if(!HostStreamInit.dwNnmSource && HostStreamInit.dwEncodeStreamCount > 0)
|
if(!HostStreamInit.dwNnmSource && HostStreamInit.dwEncodeStreamCount > 0)
|
||||||
|
|||||||
9
tools/bolt/time_cgi/content/time
Normal file
9
tools/bolt/time_cgi/content/time
Normal 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)"
|
||||||
7
tools/bolt/time_cgi/install.sh
Normal file
7
tools/bolt/time_cgi/install.sh
Normal 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"
|
||||||
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
set -e
|
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
|
BIN_DIR=/mnt/flash/plus/kp_firmware/kp_firmware_0/kp_firmware/bin
|
||||||
FW=/mnt/flash/vienna/kp_firmware_host_stream
|
FW=/mnt/flash/vienna/kp_firmware_host_stream
|
||||||
INI=$BIN_DIR/ini/host_stream.ini
|
INI=$BIN_DIR/ini/host_stream.ini
|
||||||
|
|||||||
@ -184,7 +184,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
self.send_error(400, str(e))
|
self.send_error(400, str(e))
|
||||||
|
|
||||||
# ── Channel B: tar.gz archive upload ─────────────────────────
|
# ── 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
|
# 1) kCurl sends filename as query param: /api/upload?filename=xxx
|
||||||
qs = parse_qs(urlparse(self.path).query)
|
qs = parse_qs(urlparse(self.path).query)
|
||||||
filename = (qs.get('filename') or [None])[0]
|
filename = (qs.get('filename') or [None])[0]
|
||||||
@ -205,8 +205,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
fp = os.path.join(UPLOAD_DIR, filename)
|
fp = os.path.join(UPLOAD_DIR, filename)
|
||||||
with open(fp, 'wb') as f:
|
with open(fp, 'wb') as f:
|
||||||
f.write(body)
|
f.write(body)
|
||||||
print(f" [CHANNEL-B] saved {filename} ({len(body):,} bytes)")
|
print(f" [CHANNEL-B] {path} saved {filename} ({len(body):,} bytes)")
|
||||||
self._json({'ok': True, 'filename': filename, 'bytes': len(body)})
|
self._json({'ok': True, 'path': path, 'filename': filename, 'bytes': len(body)})
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.send_error(404)
|
self.send_error(404)
|
||||||
@ -250,6 +250,7 @@ if __name__ == '__main__':
|
|||||||
print(f" Time API : GET http://localhost:{PORT}/api/time")
|
print(f" Time API : GET http://localhost:{PORT}/api/time")
|
||||||
print(f" Event API : POST http://localhost:{PORT}/api/event")
|
print(f" Event API : POST http://localhost:{PORT}/api/event")
|
||||||
print(f" Upload API : POST http://localhost:{PORT}/api/upload")
|
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(f" Uploads : {UPLOAD_DIR}")
|
||||||
print("=" * 55)
|
print("=" * 55)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -147,7 +147,7 @@ def build_targz(event_id: str, event_type: str, max_level: int,
|
|||||||
|
|
||||||
# ── Scenarios ─────────────────────────────────────────────────────────
|
# ── 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."""
|
"""Full grass violation: L1 → L2 → L3 → L0, then upload tar.gz."""
|
||||||
print("\n[Grass Scenario]")
|
print("\n[Grass Scenario]")
|
||||||
print("Step 0: get server time (simulates NTP from OOB Enabler)")
|
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')
|
ts_fn = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||||||
filename = f'event_{event_id}_{ts_fn}.tar.gz'
|
filename = f'event_{event_id}_{ts_fn}.tar.gz'
|
||||||
print(f"\n→ [CHANNEL B] uploading {filename} ({len(tgz):,} bytes)")
|
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."""
|
"""Single-shot hazard event — no level, no tar.gz wait."""
|
||||||
labels = {'bunker': '沙坑', 'pond': '水池', 'tree': '樹木'}
|
labels = {'bunker': '沙坑', 'pond': '水池', 'tree': '樹木'}
|
||||||
label = labels.get(hazard_type, hazard_type)
|
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')
|
ts_fn = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||||||
filename = f'event_{event_id}_{ts_fn}.tar.gz'
|
filename = f'event_{event_id}_{ts_fn}.tar.gz'
|
||||||
print(f"→ [CHANNEL B] uploading {filename} ({len(tgz):,} bytes)")
|
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."""
|
"""Person detection — single-shot."""
|
||||||
print("\n[Person Scenario]")
|
print("\n[Person Scenario]")
|
||||||
get_server_time(server)
|
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')
|
ts_fn = datetime.datetime.utcnow().strftime('%Y%m%d_%H%M%S')
|
||||||
filename = f'event_{event_id}_{ts_fn}.tar.gz'
|
filename = f'event_{event_id}_{ts_fn}.tar.gz'
|
||||||
print(f"→ [CHANNEL B] uploading {filename} ({len(tgz):,} bytes)")
|
print(f"→ [CHANNEL B] uploading {filename} ({len(tgz):,} bytes)")
|
||||||
post_targz(server, '/api/upload', filename, tgz)
|
post_targz(server, upload_path, filename, tgz)
|
||||||
|
|
||||||
|
|
||||||
# ── Main ───────────────────────────────────────────────────────────────
|
# ── Main ───────────────────────────────────────────────────────────────
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--server', default=DEFAULT_SERVER)
|
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('--hazard', choices=['bunker', 'pond', 'tree'])
|
||||||
parser.add_argument('--person', action='store_true')
|
parser.add_argument('--person', action='store_true')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@ -250,8 +252,8 @@ if __name__ == '__main__':
|
|||||||
event_id = str(int(time.time()))
|
event_id = str(int(time.time()))
|
||||||
|
|
||||||
if args.hazard:
|
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:
|
elif args.person:
|
||||||
scenario_person(args.server, event_id)
|
scenario_person(args.server, event_id, args.upload_path)
|
||||||
else:
|
else:
|
||||||
scenario_grass(args.server, event_id)
|
scenario_grass(args.server, event_id, args.upload_path)
|
||||||
|
|||||||
113
web_serve.py
113
web_serve.py
@ -506,36 +506,101 @@ def api_deploy_run():
|
|||||||
return Response(generate(), mimetype="text/event-stream",
|
return Response(generate(), mimetype="text/event-stream",
|
||||||
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
|
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
|
||||||
|
|
||||||
# ── API: first-time ISP setup via Telnet (SSE) ────────────────────────────────
|
# ── API: BT UART one-time baud setup via Telnet (SSE) ────────────────────────
|
||||||
@app.route("/api/setup/run")
|
@app.route("/api/bt_setup/run")
|
||||||
def api_setup_run():
|
def api_bt_setup_run():
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
kl_ip = request.args.get("ip", cfg["kl630_ip"])
|
kl_ip = request.args.get("ip", cfg["kl630_ip"])
|
||||||
bd = BIN_DIR_DEVICE
|
bd = BIN_DIR_DEVICE
|
||||||
awb = f"{bd}/Resource/AWB/AutoWhiteBalance.ini"
|
fw = FW_PATH_DEVICE
|
||||||
isp0 = f"{bd}/Resource/ISP/0/pqtable_ispe_Config.cfg"
|
restart_cmd = (f"cd {bd} && rm -f /dev/shm/* && "
|
||||||
isp1 = f"{bd}/Resource/ISP/1/pqtable_ispe_Config.cfg"
|
f"nohup sh ./ini/demo_rtsp.sh > /tmp/fw.log 2>&1 &")
|
||||||
|
|
||||||
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}",
|
|
||||||
]
|
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
|
yield sse("⚠ 請先關閉手機 nRF Connect 並確保 DX-BT24 未連線,再繼續!", "warn")
|
||||||
yield sse(f"Connecting to {kl_ip}:23 via Telnet...")
|
yield sse(f"Connecting to {kl_ip}:23 via Telnet...")
|
||||||
try:
|
try:
|
||||||
tn = _telnet_connect(kl_ip)
|
tn = _telnet_connect(kl_ip)
|
||||||
_drain_shell(tn)
|
_drain_shell(tn)
|
||||||
yield sse("Connected.", "ok")
|
yield sse("Connected.", "ok")
|
||||||
for cmd in commands:
|
|
||||||
yield sse(f"$ {cmd}", "prompt")
|
# Step 1: kill firmware so UART is free
|
||||||
for ln in _telnet_run(tn, cmd):
|
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")
|
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()
|
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:
|
except Exception as e:
|
||||||
yield sse(f"Telnet error: {e}", "error")
|
yield sse(f"Telnet error: {e}", "error")
|
||||||
yield sse_done()
|
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>
|
<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
|
Deploy to KL630
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-warn" id="btn-setup" onclick="runAction('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"><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>
|
<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>
|
||||||
First-time ISP Setup
|
BT 初始化
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-ghost" onclick="clearLog()">Clear Log</button>
|
<button class="btn btn-ghost" onclick="clearLog()">Clear Log</button>
|
||||||
<button class="btn btn-ghost" id="btn-autostart" onclick="runAction('autostart')">
|
<button class="btn btn-ghost" id="btn-autostart" onclick="runAction('autostart')">
|
||||||
@ -1707,14 +1772,14 @@ function runAction(action) {
|
|||||||
'&port=' + encodeURIComponent(cfg.port) +
|
'&port=' + encodeURIComponent(cfg.port) +
|
||||||
'&out_rtsp=' + (document.getElementById('out-rtsp').checked ? 1 : 0) +
|
'&out_rtsp=' + (document.getElementById('out-rtsp').checked ? 1 : 0) +
|
||||||
'&out_hdmi=' + (document.getElementById('out-hdmi').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) +
|
autostart: '/api/autostart/write?ip=' + encodeURIComponent(cfg.kl630_ip) +
|
||||||
'&out_rtsp=' + (document.getElementById('out-rtsp').checked ? 1 : 0) +
|
'&out_rtsp=' + (document.getElementById('out-rtsp').checked ? 1 : 0) +
|
||||||
'&out_hdmi=' + (document.getElementById('out-hdmi').checked ? 1 : 0),
|
'&out_hdmi=' + (document.getElementById('out-hdmi').checked ? 1 : 0),
|
||||||
autostart_read: '/api/autostart/read?ip=' + encodeURIComponent(cfg.kl630_ip),
|
autostart_read: '/api/autostart/read?ip=' + encodeURIComponent(cfg.kl630_ip),
|
||||||
mount_sd: '/api/mount_sd?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');
|
appendLog('\\n── ' + labels[action] + ' ' + '─'.repeat(40), 'prompt');
|
||||||
setLogStatus('Running...');
|
setLogStatus('Running...');
|
||||||
@ -1810,7 +1875,7 @@ function appendLog(text, kind) {
|
|||||||
}
|
}
|
||||||
function clearLog() { document.getElementById('log').textContent = ''; setLogStatus(''); }
|
function clearLog() { document.getElementById('log').textContent = ''; setLogStatus(''); }
|
||||||
function setLogStatus(s) { document.getElementById('log-status').textContent = s; }
|
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 ──────────────────────────────────────────────────────────────────
|
// ── Terminal ──────────────────────────────────────────────────────────────────
|
||||||
// by mars
|
// by mars
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user