commit 7e40e52a0e566b60bfd48167d9910c6f09cb6cb8 Author: miketsai Date: Sun Apr 12 09:40:04 2026 +0000 上傳檔案到「/」 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..10e4f46 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..674cc54 --- /dev/null +++ b/README.md @@ -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://: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)** diff --git a/build_and_serve.py b/build_and_serve.py new file mode 100644 index 0000000..ca1f927 --- /dev/null +++ b/build_and_serve.py @@ -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:/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(" 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() diff --git a/compile.sh b/compile.sh new file mode 100644 index 0000000..7bb21db --- /dev/null +++ b/compile.sh @@ -0,0 +1,153 @@ +#!/bin/bash +# compile.sh — KL630 cross-compile (ARM armv7-a) +# Run inside Docker: docker run --rm -v ":/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 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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2463d3a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask>=2.3 +opencv-python>=4.8