上傳檔案到「/」
This commit is contained in:
commit
7e40e52a0e
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
build-essential cmake wget curl \
|
||||||
|
python3 python3-pip \
|
||||||
|
gcc-arm-linux-gnueabihf \
|
||||||
|
g++-arm-linux-gnueabihf \
|
||||||
|
libc6-dev-armhf-cross \
|
||||||
|
pkg-config patchelf \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN pip3 install --no-cache-dir flask>=2.3 opencv-python-headless>=4.8
|
||||||
|
|
||||||
|
ENV CC=arm-linux-gnueabihf-gcc
|
||||||
|
ENV CXX=arm-linux-gnueabihf-g++
|
||||||
|
|
||||||
|
WORKDIR /workspace/kl630_build
|
||||||
|
|
||||||
|
EXPOSE 8080 5000
|
||||||
|
|
||||||
|
CMD ["bash"]
|
||||||
226
README.md
Normal file
226
README.md
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
# KL630 Host Stream Firmware
|
||||||
|
|
||||||
|
即時影像語義分割系統,運行於 Kneron KL630 (Cortex-A7, uClibc)。
|
||||||
|
透過 IMX662 DOL-HDR 雙曝光攝影機進行 STDC 語義分割,並將結果以 RTSP 串流或 HDMI 輸出。
|
||||||
|
|
||||||
|
**技術細節 → [`docs/technical_report.md`](docs/technical_report.md)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目錄結構
|
||||||
|
|
||||||
|
```
|
||||||
|
kl630_build/
|
||||||
|
├── web_serve.py # Web 控制台(主要使用)
|
||||||
|
├── build_and_serve.py # CLI 版本(不需要瀏覽器)
|
||||||
|
├── compile.sh # Docker 內 ARM 交叉編譯腳本
|
||||||
|
├── Dockerfile # kl630-dev image 定義
|
||||||
|
├── requirements.txt # Python 套件清單
|
||||||
|
│
|
||||||
|
├── src/
|
||||||
|
│ ├── host_stream/ # 主程式(初始化、推論迴圈、結果處理)
|
||||||
|
│ ├── app_flow/ # VMF pipeline 控制
|
||||||
|
│ ├── pre_post/ # YOLOv5 前後處理
|
||||||
|
│ └── stdc/ # STDC 語義分割後處理
|
||||||
|
│
|
||||||
|
├── include/
|
||||||
|
│ ├── stdc/ # stdc_post_process.h(分析結果結構)
|
||||||
|
│ └── fake/ # SDK 缺少時的 stub headers
|
||||||
|
│
|
||||||
|
├── ini/
|
||||||
|
│ └── host_stream.ini # 執行期設定(model、stream、ISP、FEC 參數)
|
||||||
|
│
|
||||||
|
│
|
||||||
|
├── lib/ # 裝置端 .so 函式庫(VMF SDK)
|
||||||
|
├── docs/
|
||||||
|
│ └── technical_report.md # 完整技術文件
|
||||||
|
└── build/ # 編譯輸出(由 compile.sh 產生)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速開始
|
||||||
|
|
||||||
|
### 前置條件
|
||||||
|
|
||||||
|
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) 已安裝並執行中
|
||||||
|
- Python 3.8+
|
||||||
|
|
||||||
|
### 安裝
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
| 套件 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `flask` | Web 控制台 + HTTP file server |
|
||||||
|
| `opencv-python` | RTSP 串流預覽(內建 FFmpeg,無需另外安裝)|
|
||||||
|
|
||||||
|
### 啟動
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python web_serve.py
|
||||||
|
```
|
||||||
|
|
||||||
|
瀏覽器開啟 `http://localhost:8080/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 新機器首次設定 SOP
|
||||||
|
|
||||||
|
全新 KL630 裝置依序執行以下步驟:
|
||||||
|
|
||||||
|
### Step 1 — Compile
|
||||||
|
|
||||||
|
在 Web 控制台按 **Compile**。
|
||||||
|
- 自動建立 Docker image(若不存在)
|
||||||
|
- 交叉編譯 ARM binary
|
||||||
|
- 複製 binary / INI / 腳本到 `build/`
|
||||||
|
|
||||||
|
> 若 binary 已是最新版本,可跳過此步驟直接從 Step 2 開始。
|
||||||
|
|
||||||
|
### Step 2 — 設定 Output Mode
|
||||||
|
|
||||||
|
在 **INI Settings** 面板選擇輸出模式(預設 RTSP):
|
||||||
|
|
||||||
|
| 組合 | RTSP | HDMI | 啟動腳本 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| RTSP 串流 | ON | OFF | `demo_rtsp.sh` |
|
||||||
|
| HDMI 顯示 | OFF | ON | `demo_hdmi.sh` |
|
||||||
|
| RTSP + HDMI 同時 | ON | ON | `demo_rtsp_hdmi.sh`(INI voc_enable=1)|
|
||||||
|
| 純推論(無輸出) | OFF | OFF | 直接啟動 binary |
|
||||||
|
|
||||||
|
### Step 3 — Deploy to KL630
|
||||||
|
|
||||||
|
按 **Deploy to KL630**,自動透過 Telnet 完成:
|
||||||
|
1. 停止舊 firmware
|
||||||
|
2. 下載 binary、INI、NEF 模型
|
||||||
|
3. 下載 `demo_rtsp.sh`、`demo_hdmi.sh`、`demo_rtsp_hdmi.sh`
|
||||||
|
4. 設定 VOC 輸出模式
|
||||||
|
5. 啟動 firmware
|
||||||
|
|
||||||
|
### Step 4 — First-time ISP Setup
|
||||||
|
|
||||||
|
按 **First-time ISP Setup**。
|
||||||
|
寫入 DOL-HDR 所需的 flash 參數(`dwStatisticsSrcType=2`、`bGTREnable=1`),
|
||||||
|
**只需執行一次**,設定永久保留在 flash,重新 Deploy 不會被覆蓋。
|
||||||
|
|
||||||
|
> 跳過此步驟影像會過暗或曝光不正確。
|
||||||
|
|
||||||
|
### Step 5 — 魚眼鏡頭校正(FEC)
|
||||||
|
|
||||||
|
在 **INI Settings** 面板設定 Fish-Eye Correction:
|
||||||
|
|
||||||
|
| 參數 | 推薦設定 | 說明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| FEC | ON | 開啟魚眼校正 |
|
||||||
|
| **Mode** | **4 — 180° Two Direction** | 天花板魚眼鏡頭推薦模式,同時展開水平與垂直方向 |
|
||||||
|
| Install Type | 0 — Ceiling | 天花板安裝 |
|
||||||
|
| EIS | 視需求 | 電子防手震 |
|
||||||
|
|
||||||
|
按 **Apply to Device + Restart** 套用並重啟 firmware。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Web 控制台功能說明
|
||||||
|
|
||||||
|
### Network Config
|
||||||
|
設定 **Host IP**(這台 PC)、**KL630 IP**、**HTTP Port**、**Docker Image**。
|
||||||
|
按 Save 後自動更新 `deploy.sh` 的 `HOST_URL`。
|
||||||
|
|
||||||
|
### HTTP Server Files
|
||||||
|
列出 `build/` 目錄下所有可下載的檔案,以及裝置端手動部署指令:
|
||||||
|
```sh
|
||||||
|
wget http://<host_ip>:8080/deploy.sh -O /tmp/deploy.sh && sh /tmp/deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### RTSP Stream Preview
|
||||||
|
直接在網頁預覽 KL630 串流,不需要開 VLC。
|
||||||
|
按 **▶ Start Stream** 即可,連線失敗時 6 秒內回報錯誤。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
| 按鈕 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| **Compile** | Docker 交叉編譯 ARM binary,複製到 `build/` |
|
||||||
|
| **Deploy to KL630** | Telnet 部署 binary / INI / NEF / 腳本,依 Output Mode 啟動 firmware |
|
||||||
|
| **First-time ISP Setup** | 新機器一次性 ISP flash 設定(DOL-HDR 參數) |
|
||||||
|
| **Write Autostart** | 寫入開機自動啟動腳本 `/etc/init.d/S99firmware` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## INI Settings
|
||||||
|
|
||||||
|
### Fish-Eye Correction (FEC)
|
||||||
|
|
||||||
|
| 參數 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| FEC ON/OFF | 開啟或關閉魚眼校正(`fec_mode = 0` 為關閉)|
|
||||||
|
| Mode 1 | Single Region |
|
||||||
|
| Mode 2 | 180° All Direction |
|
||||||
|
| Mode 3 | 180° One Direction |
|
||||||
|
| **Mode 4** | **180° Two Direction(推薦)** |
|
||||||
|
| Mode 5 | PT Mode |
|
||||||
|
| Install Type | Ceiling / Table / Wall(`initial_fec_app_type`)|
|
||||||
|
| EIS | 電子防手震(`eis_enable`)|
|
||||||
|
| DrawBox | H.264 burn-in 偵測框(`DrawBoxEnable`)|
|
||||||
|
|
||||||
|
### Output Mode
|
||||||
|
|
||||||
|
| 選項 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| RTSP | H.264 RTSP 串流輸出,由 `demo_rtsp.sh` 管理 |
|
||||||
|
| HDMI | VOC HDMI 顯示輸出,由 `demo_hdmi.sh` 管理 |
|
||||||
|
|
||||||
|
- **Save INI(disk only)** — 更新本地 `ini/host_stream.ini`,下次 Deploy 時推送至裝置
|
||||||
|
- **Apply to Device + Restart** — 立即 Telnet 更新裝置 INI 並重啟 firmware
|
||||||
|
|
||||||
|
### Model Settings
|
||||||
|
|
||||||
|
切換推論模型(NEF 檔)、ModelId、JobId,支援上傳新 NEF。
|
||||||
|
**Apply to Device + Restart** 會下載 NEF 到裝置並依目前 Output Mode 重啟。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Terminal
|
||||||
|
|
||||||
|
Output Log 下方有命令輸入列,可直接在網頁對 KL630 下指令:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cat /tmp/fw.log # 查看 firmware 啟動 log
|
||||||
|
cat /tmp/rtsp_demo.log # 查看 RTSP demo log
|
||||||
|
ps | grep firmware # 確認 firmware 是否在執行
|
||||||
|
killall kp_firmware_host_stream
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CLI 版本(不需要瀏覽器)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python build_and_serve.py # 編譯 + 檢查 + 啟動 HTTP server
|
||||||
|
python build_and_serve.py --no-build # 跳過編譯,直接 serve
|
||||||
|
python build_and_serve.py --port 9090
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Firmware 執行結果(console log)
|
||||||
|
|
||||||
|
```
|
||||||
|
[STDC] frame=42 mov=1 diff=4.2 bunker=0.0% car=8.3% grass=0.0% greenery=12.1% person=0.0% pond=0.0% road=71.4% tree=8.2%
|
||||||
|
[STDC] ON ROAD
|
||||||
|
[STDC WARN] CAR 8.3%
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 詳細說明
|
||||||
|
|
||||||
|
IMX662 DOL-HDR 雙曝光設定、STDC 語義分割架構、新裝置部署 SOP、故障排查:
|
||||||
|
|
||||||
|
**[`docs/technical_report.md`](docs/technical_report.md)**
|
||||||
302
build_and_serve.py
Normal file
302
build_and_serve.py
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
build_and_serve.py — KL630 一鍵編譯 + 檢查 + HTTP server
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
python build_and_serve.py # 編譯 + 檢查 + 啟動 server
|
||||||
|
python build_and_serve.py --no-build # 跳過編譯,只啟動 server
|
||||||
|
python build_and_serve.py --port 9090
|
||||||
|
|
||||||
|
裝置端:
|
||||||
|
wget http://192.168.3.1:<port>/deploy.sh -O /tmp/deploy.sh && sh /tmp/deploy.sh
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import shutil
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
# ── ANSI colors ─────────────────────────────────────────────────────────────
|
||||||
|
def _c(code, text): return f"\033[{code}m{text}\033[0m" if sys.stdout.isatty() else text
|
||||||
|
def ok(t): return _c("32;1", t)
|
||||||
|
def fail(t): return _c("31;1", t)
|
||||||
|
def info(t): return _c("36", t)
|
||||||
|
def warn(t): return _c("33", t)
|
||||||
|
def head(t): return _c("35;1", t)
|
||||||
|
|
||||||
|
# ── Paths ────────────────────────────────────────────────────────────────────
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
BUILD_DIR = SCRIPT_DIR / "build"
|
||||||
|
BINARY_NAME = "kp_firmware_host_stream"
|
||||||
|
BINARY_PATH = BUILD_DIR / BINARY_NAME
|
||||||
|
INI_SRC = SCRIPT_DIR / "ini" / "host_stream.ini"
|
||||||
|
DEPLOY_SRC = SCRIPT_DIR / "tools" / "device" / "deploy.sh"
|
||||||
|
DEMO_RTSP_SRC = SCRIPT_DIR / "tools" / "device" / "demo_rtsp.sh"
|
||||||
|
|
||||||
|
# Docker image name — change if yours is different
|
||||||
|
DOCKER_IMAGE = "kl630-dev"
|
||||||
|
|
||||||
|
# ── Step 0: Ensure Docker image exists ───────────────────────────────────────
|
||||||
|
def step_ensure_image() -> bool:
|
||||||
|
"""Build the Docker image from Dockerfile if it doesn't exist yet."""
|
||||||
|
print(head("\n=== [0/3] Docker Image Check ==="))
|
||||||
|
|
||||||
|
if shutil.which("docker") is None:
|
||||||
|
print(fail(" ERROR: 'docker' not found in PATH"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check if image already exists
|
||||||
|
r = subprocess.run(
|
||||||
|
["docker", "image", "inspect", DOCKER_IMAGE],
|
||||||
|
capture_output=True
|
||||||
|
)
|
||||||
|
if r.returncode == 0:
|
||||||
|
print(ok(f" Image '{DOCKER_IMAGE}' already exists, skipping build."))
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Image not found — build it from Dockerfile
|
||||||
|
dockerfile = SCRIPT_DIR / "Dockerfile"
|
||||||
|
if not dockerfile.exists():
|
||||||
|
print(fail(f" ERROR: Dockerfile not found at {dockerfile}"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(warn(f" Image '{DOCKER_IMAGE}' not found. Building from Dockerfile..."))
|
||||||
|
print(info(f" docker build -t {DOCKER_IMAGE} {SCRIPT_DIR}"))
|
||||||
|
print()
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
["docker", "build", "-t", DOCKER_IMAGE, str(SCRIPT_DIR)]
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(fail(f"\n IMAGE BUILD FAILED (exit {result.returncode})"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(ok(f"\n Image '{DOCKER_IMAGE}' built successfully."))
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ── Step 1: Docker build ──────────────────────────────────────────────────────
|
||||||
|
def step_build() -> bool:
|
||||||
|
print(head("\n=== [1/3] Docker Build ==="))
|
||||||
|
|
||||||
|
# Check docker available
|
||||||
|
if shutil.which("docker") is None:
|
||||||
|
print(fail(" ERROR: 'docker' not found in PATH"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Mount the full project directory — SDK/lib/include/src all come from here.
|
||||||
|
mount = str(SCRIPT_DIR).replace("\\", "/")
|
||||||
|
cmd = [
|
||||||
|
"docker", "run", "--rm",
|
||||||
|
"-v", f"{mount}:/workspace/kl630_build",
|
||||||
|
DOCKER_IMAGE,
|
||||||
|
"bash", "/workspace/kl630_build/compile.sh"
|
||||||
|
]
|
||||||
|
print(info(f" Image : {DOCKER_IMAGE}"))
|
||||||
|
print(info(f" Mount : {mount} -> /workspace/kl630_build"))
|
||||||
|
print()
|
||||||
|
|
||||||
|
result = subprocess.run(cmd)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(fail(f"\n BUILD FAILED (exit {result.returncode})"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(ok("\n Build succeeded."))
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ── Step 2: Check binary ──────────────────────────────────────────────────────
|
||||||
|
def _check_elf_arm(path: Path):
|
||||||
|
"""Return (is_arm_elf, description)."""
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
hdr = f.read(20)
|
||||||
|
if len(hdr) < 20:
|
||||||
|
return False, "file too small"
|
||||||
|
if hdr[:4] != b"\x7fELF":
|
||||||
|
return False, "not an ELF file"
|
||||||
|
e_machine = struct.unpack_from("<H", hdr, 18)[0]
|
||||||
|
arch_map = {0x28: "ARM (armv7)", 0xb7: "AArch64", 0x3e: "x86_64"}
|
||||||
|
arch = arch_map.get(e_machine, f"unknown machine 0x{e_machine:04x}")
|
||||||
|
return e_machine == 0x28, arch
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
def step_check() -> bool:
|
||||||
|
print(head("\n=== [2/3] Check Binary ==="))
|
||||||
|
|
||||||
|
if not BINARY_PATH.exists():
|
||||||
|
print(fail(f" FAIL: {BINARY_PATH} not found"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
size = BINARY_PATH.stat().st_size
|
||||||
|
is_arm, arch = _check_elf_arm(BINARY_PATH)
|
||||||
|
|
||||||
|
size_ok = size > 100_000
|
||||||
|
arch_ok = is_arm
|
||||||
|
|
||||||
|
print(f" File : {BINARY_PATH}")
|
||||||
|
print(f" Size : {size/1024:.1f} KB {'✓' if size_ok else '✗'}")
|
||||||
|
print(f" Arch : {arch} {'✓' if arch_ok else '✗'}")
|
||||||
|
|
||||||
|
# Try readelf if available (in Docker or WSL)
|
||||||
|
if shutil.which("readelf"):
|
||||||
|
r = subprocess.run(
|
||||||
|
["readelf", "-d", str(BINARY_PATH)],
|
||||||
|
capture_output=True, text=True
|
||||||
|
)
|
||||||
|
needed = [l.split("(NEEDED)")[1].strip() for l in r.stdout.splitlines() if "(NEEDED)" in l]
|
||||||
|
if needed:
|
||||||
|
print(f" NEEDED: {', '.join(needed)}")
|
||||||
|
if "libc.so.0" in needed:
|
||||||
|
print(ok(" uClibc patch: OK (libc.so.0 present)"))
|
||||||
|
elif "libc.so.6" in needed:
|
||||||
|
print(fail(" uClibc patch: MISSING (libc.so.6 still present)"))
|
||||||
|
|
||||||
|
if not (size_ok and arch_ok):
|
||||||
|
print(fail("\n CHECK FAILED"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(ok("\n Binary looks good."))
|
||||||
|
return True
|
||||||
|
# ── Step 3: Copy Binary To Network Share ──────────────────────
|
||||||
|
def step_copy_to_network(dst_dir: str) -> bool:
|
||||||
|
print(head("\n=== [copy] Copy Binary To Network Share ==="))
|
||||||
|
|
||||||
|
if not BINARY_PATH.exists():
|
||||||
|
print(fail(f" FAIL: source binary not found: {BINARY_PATH}"))
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
dst_path = Path(dst_dir)
|
||||||
|
dst_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
dst_file = dst_path / BINARY_NAME
|
||||||
|
shutil.copy2(BINARY_PATH, dst_file)
|
||||||
|
|
||||||
|
print(ok(f" Copied: {BINARY_PATH}"))
|
||||||
|
print(ok(f" -> {dst_file}"))
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(fail(f" COPY FAILED: {e}"))
|
||||||
|
return False
|
||||||
|
# ── Step 4: Prepare serve directory & start HTTP server ──────────────────────
|
||||||
|
def step_serve(port: int):
|
||||||
|
print(head("\n=== [3/3] Prepare & Serve ==="))
|
||||||
|
|
||||||
|
BUILD_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Copy INI
|
||||||
|
if INI_SRC.exists():
|
||||||
|
dst = BUILD_DIR / "host_stream.ini"
|
||||||
|
shutil.copy2(INI_SRC, dst)
|
||||||
|
print(ok(f" Copied : {INI_SRC.name} ({dst.stat().st_size} bytes)"))
|
||||||
|
else:
|
||||||
|
print(warn(f" WARN: {INI_SRC} not found, skipping"))
|
||||||
|
|
||||||
|
# Copy deploy.sh
|
||||||
|
if DEPLOY_SRC.exists():
|
||||||
|
dst = BUILD_DIR / "deploy.sh"
|
||||||
|
shutil.copy2(DEPLOY_SRC, dst)
|
||||||
|
print(ok(f" Copied : deploy.sh"))
|
||||||
|
else:
|
||||||
|
print(warn(f" WARN: {DEPLOY_SRC} not found, skipping"))
|
||||||
|
|
||||||
|
# Copy demo_rtsp.sh
|
||||||
|
if DEMO_RTSP_SRC.exists():
|
||||||
|
dst = BUILD_DIR / "demo_rtsp.sh"
|
||||||
|
shutil.copy2(DEMO_RTSP_SRC, dst)
|
||||||
|
print(ok(f" Copied : demo_rtsp.sh"))
|
||||||
|
else:
|
||||||
|
print(warn(f" WARN: {DEMO_RTSP_SRC} not found, skipping"))
|
||||||
|
|
||||||
|
# List what will be served
|
||||||
|
print(f"\n Serving from: {BUILD_DIR}")
|
||||||
|
for f in sorted(BUILD_DIR.iterdir()):
|
||||||
|
size_str = f"{f.stat().st_size/1024:.1f} KB"
|
||||||
|
print(f" {f.name:<40} {size_str:>10}")
|
||||||
|
|
||||||
|
# Start server
|
||||||
|
os.chdir(BUILD_DIR)
|
||||||
|
|
||||||
|
class _Handler(SimpleHTTPRequestHandler):
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
# Show only non-304 responses
|
||||||
|
code = args[1] if len(args) > 1 else ""
|
||||||
|
if code != "304":
|
||||||
|
print(info(f" [{self.address_string()}] {fmt % args}"))
|
||||||
|
|
||||||
|
httpd = HTTPServer(("", port), _Handler)
|
||||||
|
|
||||||
|
print(head(f"\n=== HTTP Server on port {port} ==="))
|
||||||
|
print(f" URL : http://0.0.0.0:{port}/")
|
||||||
|
print()
|
||||||
|
print(warn(" 裝置端執行:"))
|
||||||
|
print(f" wget http://192.168.3.1:{port}/deploy.sh -O /tmp/deploy.sh && sh /tmp/deploy.sh")
|
||||||
|
print()
|
||||||
|
print(warn(" 或分步驟:"))
|
||||||
|
print(f" wget http://192.168.3.1:{port}/{BINARY_NAME} -O /mnt/flash/vienna/{BINARY_NAME}")
|
||||||
|
print(f" wget http://192.168.3.1:{port}/host_stream.ini -O $BIN_DIR/ini/host_stream.ini")
|
||||||
|
print()
|
||||||
|
print(" Ctrl+C 停止\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
httpd.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(info("\n Server stopped."))
|
||||||
|
|
||||||
|
# ── Main ─────────────────────────────────────────────────────────────────────
|
||||||
|
def main():
|
||||||
|
global DOCKER_IMAGE
|
||||||
|
parser = argparse.ArgumentParser(description="KL630 build + check + serve")
|
||||||
|
parser.add_argument("--no-build", action="store_true",
|
||||||
|
help="跳過 Docker build,只 check + serve")
|
||||||
|
parser.add_argument("--no-check", action="store_true",
|
||||||
|
help="跳過 binary 檢查")
|
||||||
|
parser.add_argument("--copy-dst",default=r"\\192.168.0.122\public\nfs_share",
|
||||||
|
help="編譯成功後複製 binary 到指定網路資料夾")
|
||||||
|
parser.add_argument("--no-copy",action="store_true",
|
||||||
|
help="不要複製 binary 到網路資料夾")
|
||||||
|
parser.add_argument("--no-serve", action="store_true",
|
||||||
|
help="只 build + check,不啟動 HTTP server")
|
||||||
|
parser.add_argument("--port", type=int, default=8080,
|
||||||
|
help="HTTP server port (default: 8080)")
|
||||||
|
parser.add_argument("--image", default=DOCKER_IMAGE,
|
||||||
|
help=f"Docker image name (default: {DOCKER_IMAGE})")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
DOCKER_IMAGE = args.image
|
||||||
|
|
||||||
|
print(head("KL630 Build & Serve"))
|
||||||
|
print(f" Root : {SCRIPT_DIR}")
|
||||||
|
print(f" Binary : {BINARY_PATH}")
|
||||||
|
print(f" Port : {args.port}")
|
||||||
|
print(f" Copy : {args.copy_dst}")
|
||||||
|
|
||||||
|
# ===== Step 1: Build =====
|
||||||
|
if not args.no_build:
|
||||||
|
if not step_ensure_image():
|
||||||
|
sys.exit(1)
|
||||||
|
if not step_build():
|
||||||
|
sys.exit(1)
|
||||||
|
# ===== Step 2: Check =====
|
||||||
|
if not args.no_check:
|
||||||
|
if not step_check():
|
||||||
|
if args.no_build:
|
||||||
|
print(warn(" (使用 --no-build 時 binary 可能是舊的)"))
|
||||||
|
else:
|
||||||
|
sys.exit(1)
|
||||||
|
# ===== Step 3: Copy(重點)=====
|
||||||
|
if not args.no_copy:
|
||||||
|
if not step_copy_to_network(args.copy_dst):
|
||||||
|
sys.exit(1)
|
||||||
|
# ===== Step 4: Serve =====
|
||||||
|
if not args.no_serve:
|
||||||
|
step_serve(args.port)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
153
compile.sh
Normal file
153
compile.sh
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# compile.sh — KL630 cross-compile (ARM armv7-a)
|
||||||
|
# Run inside Docker: docker run --rm -v "<host_path>:/workspace/kl630_build" kl630-dev bash /workspace/kl630_build/compile.sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
WORKSPACE=/workspace/kl630_build
|
||||||
|
BUILD_DIR=$WORKSPACE/build
|
||||||
|
OUTPUT=$BUILD_DIR/kp_firmware_host_stream
|
||||||
|
CC=arm-linux-gnueabihf-gcc
|
||||||
|
LIB_DIR=$WORKSPACE/lib
|
||||||
|
|
||||||
|
echo "=== Checking compiler ==="
|
||||||
|
which $CC || { echo "ERROR: $CC not found"; exit 1; }
|
||||||
|
$CC --version | head -1
|
||||||
|
|
||||||
|
mkdir -p $BUILD_DIR
|
||||||
|
|
||||||
|
CFLAGS="-DVATICS_PLATFORM -DKL630 -D_GNU_SOURCE -U_FORTIFY_SOURCE"
|
||||||
|
CFLAGS="$CFLAGS -march=armv7-a -mfpu=neon -mfloat-abi=hard -Os"
|
||||||
|
CFLAGS="$CFLAGS -Wall -Wno-unused-variable -Wno-unused-function"
|
||||||
|
|
||||||
|
INCLUDES="-I$WORKSPACE/include/host_stream"
|
||||||
|
INCLUDES="$INCLUDES -I$WORKSPACE/include/app_flow"
|
||||||
|
INCLUDES="$INCLUDES -I$WORKSPACE/include/app_flow/pre_post_proc"
|
||||||
|
INCLUDES="$INCLUDES -I$WORKSPACE/include/common"
|
||||||
|
|
||||||
|
# Prefer real SDK headers when available; fallback to local fake headers.
|
||||||
|
SDK_INCLUDE_CANDIDATES=(
|
||||||
|
#"$WORKSPACE/SDK/sdk/vtcs_root_vienna/include"
|
||||||
|
"$WORKSPACE/include/vtcs_root_vienna/include"
|
||||||
|
"$WORKSPACE/third_party/kl630_sdk/include"
|
||||||
|
)
|
||||||
|
SDK_SYSROOT_INCLUDE="$WORKSPACE/third_party/kl630_sdk/sysroot/usr/include"
|
||||||
|
#SDK_MODULES_ROOT="$WORKSPACE/SDK/sdk/modules"
|
||||||
|
SDK_MODULES_ROOT="$WORKSPACE/include/modules"
|
||||||
|
USE_FAKE_HEADERS=1
|
||||||
|
for SDK_INCLUDE_ROOT in "${SDK_INCLUDE_CANDIDATES[@]}"; do
|
||||||
|
if [ -d "$SDK_INCLUDE_ROOT" ]; then
|
||||||
|
INCLUDES="$INCLUDES -I$SDK_INCLUDE_ROOT"
|
||||||
|
if [ -d "$SDK_INCLUDE_ROOT/vmf" ]; then
|
||||||
|
INCLUDES="$INCLUDES -I$SDK_INCLUDE_ROOT/vmf"
|
||||||
|
fi
|
||||||
|
USE_FAKE_HEADERS=0
|
||||||
|
echo "=== Using SDK include: $SDK_INCLUDE_ROOT ==="
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -d "$SDK_MODULES_ROOT" ]; then
|
||||||
|
INCLUDES="$INCLUDES -I$SDK_MODULES_ROOT"
|
||||||
|
fi
|
||||||
|
if [ -d "$SDK_SYSROOT_INCLUDE" ]; then
|
||||||
|
INCLUDES="$INCLUDES -I$SDK_SYSROOT_INCLUDE"
|
||||||
|
fi
|
||||||
|
if [ -d "$WORKSPACE/include/fake" ]; then
|
||||||
|
# Keep fake headers as the last fallback for components not shipped in this SDK pack.
|
||||||
|
INCLUDES="$INCLUDES -I$WORKSPACE/include/fake"
|
||||||
|
fi
|
||||||
|
if [ "$USE_FAKE_HEADERS" -eq 1 ]; then
|
||||||
|
echo "=== Header mode: fallback fake headers ==="
|
||||||
|
else
|
||||||
|
CFLAGS="$CFLAGS -DUSE_REAL_SDK_HEADERS=1"
|
||||||
|
echo "=== Header mode: real SDK headers + fake fallback ==="
|
||||||
|
fi
|
||||||
|
|
||||||
|
ALL_SRCS=$(ls \
|
||||||
|
$WORKSPACE/src/host_stream/*.c \
|
||||||
|
$WORKSPACE/src/app_flow/*.c \
|
||||||
|
$WORKSPACE/src/pre_post_proc/*.c \
|
||||||
|
2>/dev/null)
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Source files ==="
|
||||||
|
for s in $ALL_SRCS; do echo " $s"; done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Compiling ==="
|
||||||
|
OBJ_FILES=()
|
||||||
|
COMPILE_ERRORS=0
|
||||||
|
|
||||||
|
for src in $ALL_SRCS; do
|
||||||
|
base=$(basename $src .c)
|
||||||
|
obj=$BUILD_DIR/${base}.o
|
||||||
|
echo " CC $base.c"
|
||||||
|
if $CC $CFLAGS $INCLUDES -c "$src" -o "$obj" 2>&1; then
|
||||||
|
OBJ_FILES+=("$obj")
|
||||||
|
else
|
||||||
|
echo " *** COMPILE ERROR: $src ***"
|
||||||
|
COMPILE_ERRORS=$((COMPILE_ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $COMPILE_ERRORS -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "ERROR: $COMPILE_ERRORS file(s) failed to compile."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== All objects compiled ==="
|
||||||
|
|
||||||
|
# 建立 app_yolo symlink(versioned .so)
|
||||||
|
if [ ! -e "$LIB_DIR/libapp_yolo.so" ]; then
|
||||||
|
AYSO=$(ls $LIB_DIR/libapp_yolo.so* 2>/dev/null | head -1)
|
||||||
|
if [ -n "$AYSO" ]; then
|
||||||
|
ln -sf $(basename $AYSO) $LIB_DIR/libapp_yolo.so
|
||||||
|
echo " symlink: libapp_yolo.so -> $(basename $AYSO)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 建立 uClibc rpath-link symlinks
|
||||||
|
# 只建 libc.so.0/libc.so.1 供 rpath-link 解析 libvmf.so 的依賴
|
||||||
|
# 不建 libc.so 和 libpthread.so — 讓 -lc/-lpthread 繼續用 glibc startup
|
||||||
|
if [ -e "$LIB_DIR/libuClibc-1.0.34.so" ]; then
|
||||||
|
for name in libc.so.0 libc.so.1; do
|
||||||
|
[ ! -e "$LIB_DIR/$name" ] && ln -sf libuClibc-1.0.34.so $LIB_DIR/$name && echo " symlink: $name -> libuClibc-1.0.34.so"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "WARNING: libuClibc-1.0.34.so not found in $LIB_DIR"
|
||||||
|
echo " Transfer it from device: tftp -p -l /lib/libuClibc-1.0.34.so -r libuClibc-1.0.34.so <host_ip> 9069"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Linking ==="
|
||||||
|
|
||||||
|
$CC -march=armv7-a -mfpu=neon -mfloat-abi=hard \
|
||||||
|
"${OBJ_FILES[@]}" \
|
||||||
|
-L$LIB_DIR \
|
||||||
|
-Wl,-rpath-link,$LIB_DIR \
|
||||||
|
-Wl,-rpath,\$ORIGIN/lib \
|
||||||
|
-Wl,--dynamic-linker,/lib/ld-uClibc.so.1 \
|
||||||
|
-Wl,--allow-shlib-undefined \
|
||||||
|
-Wl,--allow-multiple-definition \
|
||||||
|
-lvmf -lmembroker -lmsgbroker -lsyncringbuffer -liniparser \
|
||||||
|
-lvmf_nnm -lkutils -laio -lpthread -lm \
|
||||||
|
-lapp_yolo \
|
||||||
|
-o $OUTPUT \
|
||||||
|
&& echo "" \
|
||||||
|
&& echo "=== SUCCESS: $OUTPUT ===" \
|
||||||
|
|| { echo ""; echo "=== LINK FAILED ==="; exit 1; }
|
||||||
|
|
||||||
|
ls -lh $OUTPUT
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Patching ELF for uClibc ==="
|
||||||
|
# Remove glibc dynamic linker (causes ld-uClibc to try loading itself as .so)
|
||||||
|
patchelf --remove-needed ld-linux-armhf.so.3 $OUTPUT
|
||||||
|
# Map glibc sonames to uClibc equivalents
|
||||||
|
patchelf --replace-needed libc.so.6 libc.so.0 $OUTPUT
|
||||||
|
patchelf --remove-needed libm.so.6 $OUTPUT
|
||||||
|
patchelf --remove-needed libpthread.so.0 $OUTPUT
|
||||||
|
|
||||||
|
echo "=== NEEDED after patch ==="
|
||||||
|
readelf -d $OUTPUT | grep NEEDED
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
flask>=2.3
|
||||||
|
opencv-python>=4.8
|
||||||
Loading…
x
Reference in New Issue
Block a user