# CodeWindows **Repository Path**: gautohank/CodeWindows ## Basic Information - **Project Name**: CodeWindows - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-10 - **Last Updated**: 2026-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Code_Windows 板子服务 板子夹在 **APP、扫描仪、算法** 三者中间:接楼栋 zip、管房间任务、调度扫描与 fls→ply 转换、 调用算法计算、对外提供结果查询。 **部署形态**:板子 + 算法桥 + 墙面识别跑在 **Docker(一键 compose)**;Windows 侧只留一个小代理 `win_agent`,承担容器做不到的两件事——fls 转换(exe 是 Windows 程序)、连扫描仪热点的 wifi 扫描/连接 (容器没有无线网卡与 netsh)。Android 由**迷你路由器**接入(见 §6),不经过板子。 ``` Android APP ─HTTP:8000─▶ board-app (FastAPI, 容器) 经迷你路由器 LAN │ 192.168.8.100:8000 ├─HTTP:8100─▶ algo-bridge (容器, import 算法 .so) │ └─HTTP:9093─▶ detector (容器, 墙面识别 yolo, 来自离线 tar) │ ↕ 共享卷 detector_save / board_data ├─HTTP:8200─▶ win_agent (Windows 裸机): fls→ply 转换 + 连扫描仪 wifi │ └─直连扫描仪: FARO → HTTP 192.168.43.1:8888 BLK360 → Linux SDK(libBLK360G2.so) → 10.10.1.1 ``` --- ## 1. 三条通讯链路 ### 板子 ↔ 算法(HTTP,进程隔离) - 算法不是被当函数调用,而是跑在 **algo-bridge 容器**里:容器内 `import RoomLocalInterface`,算法源码由 compose 以只读卷挂入(`../code_algorithm_commute_local:/algo:ro`)。板子经 `http://algo-bridge:8100` 调用 (`app/algo_client.py`)。 - **桥内严格单线程**:全板一个算法对象、一把锁。长任务(compute/wrap_up)后台线程执行,板子**轮询** `/room-task/result` 取结果,不依赖长连接。 - **共享卷 + 相对路径**:板子与桥共用 Docker 卷 `board_data`(板子挂 `/board/data`,桥挂 `/data`);桥只认相对 `/data` 的 POSIX 路径如 `work_tasks//3/<房间>`。用卷而非 Windows 目录——9p 跨 WSL2 边界极慢 (同一房间计算 929s→220s)。 - **detector(墙面识别)由算法调用,不是板子**:算法算测站时把投影图写进共享卷 `detector_save`,POST `http://detector:9093/detect` 发绝对路径,detector 按同路径读图返回结果。因此 `detector_save` 必须两容器挂在 **同一路径**。detector 镜像在阿里云私有仓库、现场拉不到,**离线 tar 交付**: `docker load -i D:\doc\detector\detector_image.tar` 后再 `up`(`pull_policy: never`,不会联网)。 ### 板子 ↔ Android(HTTP REST) Android 是客户端,板子只是局域网里一台主机(**不开热点、不主动连手机**)。经迷你路由器同处 `192.168.8.0/24`, APP 访问 `http://192.168.8.100:8000`。主要端点见 §4。 ### 板子 ↔ 扫描仪(板子直连) - FARO:纯 HTTP(LsWebAPI)。BLK360:容器内经 Linux SDK `libBLK360G2.so`(卷挂载,与 Windows 版头文件一致)。 - **连扫描仪热点**这一步经 win_agent(netsh/wlanapi),扫描本身在容器内直连。 - **扫描仪清单在手机本地**(APP 的「扫描仪管理」录型号/SSID/密码);板子不存任何设备信息,连设备时 APP 带 `{ssid, password, device}` 过来(`app/routes/wifi.py`)。 ## 2. 数据流转(用户数据从哪来到哪去) ``` ① APP zip ─POST /sync─▶ download_tasks// (解析 download_room_info.json 入 SQLite) ② POST /buildings {sync_id} ─▶ 按 房间×阶段 复制成 work_tasks//... 可计算副本 ③ 扫描: BLK360 直出 ply / FARO 下 fls 包 → 转换 → work_tasks/.../<测站>/scan_pcd.ply ④ 计算: 入队 → algo-bridge → 结果落 work_tasks/.../room_res.json、station_res/ 等 ⑤ 导出: 结果由 APP sync 上云;或 docker cp / GET /results/work/... ``` `data/` 是**临时工作区**(在 `.gitignore` 里,存 Docker 卷),不长期留档,见 §7。 ## 3. 部署 ### 3.1 前提 | 组件 | 要求 | |---|---| | OS | Windows 10/11 x64 | | Docker | Docker Desktop(承载板子 + 算法桥 + detector) | | Python | 3.10+(仅 win_agent 需要) | | 转换 exe 依赖 | `SCENE Redistributable 2.1.4` + `vcredist_x64_2022`(安装包在 `convert_fls/`) | **目录布局**(compose 用相对路径挂载,层级不能变): ``` <部署根>/ Code_Windows/ # 本项目(含 docker-compose.yml / algo_bridge/ / win_agent/) code_algorithm_commute_local/ # 算法仓库(与 Code_Windows 平级,挂 /algo:ro) convert_fls/Code_WindowsEXE_FlsFileTransferV9_YYZS.exe BLK360G2_v1.6.6_Linux/lib/libBLK360G2.so # 容器内 BLK360(挂 /blk360) BLK360G2_v1.6.6_Windows/bin/BLK360G2.dll # 仅本机模式/CLI 自测 D:\doc\detector\detector_image.tar # 墙面识别镜像(离线 tar) ``` ### 3.2 启动 ```powershell docker load -i D:\doc\detector\detector_image.tar # detector 只能离线 load cd Code_Windows docker compose up -d --build # 首次约 5-10 分钟 pip install -r requirements.txt # win_agent 首次 python win_agent\agent_app.py # 监听 0.0.0.0:8200 ``` 自检: ```powershell curl http://127.0.0.1:8000/health # 板子 → {"status":"ok"} curl http://127.0.0.1:8100/health # 算法桥 → {"status":"ok","import_error":null,...} curl http://127.0.0.1:8200/health # win_agent ``` - 桥 `import_failed` 时看 `import_error` 堆栈;缺系统库在 `algo_bridge/Dockerfile` apt 层补装后重建。 - 代码是卷挂载,改代码只需 `docker restart board-app` / `board-algo-bridge`,无需重建镜像。 - **测试台**:浏览器开 `http://:8000/webtest/`(全流程图形化);API 文档 `/docs`。 ### 3.3 mock 模式(无扫描仪开发) compose 里 board 服务放开 `BOARD_SCANNER_MOCK: "1"` 后 `docker compose up -d`:扫描复制样例点云不连设备; `/wifi/*` 也变成一组假 SSID(见 `win_agent/wifi.py` 的 `MOCK_SSIDS`),把这几个 SSID 加进 APP 扫描仪清单即可 走完流程。**真机联调记得关掉**。`MOCK_SCAN_DIR` 须是容器内路径。 > 板子也可不进 Docker 直接 `python main.py`(旧模式):`WIN_AGENT_URL` 留空,转换/BLK360 本机直调;算法桥仍需 Docker。 ## 4. API 调用流程(APP 视角) ``` 1 POST /sync 上传楼栋 zip → sync_building_id 2 POST /buildings {sync_id} 创建任务楼栋 → compute_building_id 3 GET /buildings/{cid} 楼层→房间树,选 room_uid 4 POST .../rooms/{ruid}/enter 进入房间(占 duty,桥 open 算法对象) 5 POST .../stations/{sn}/scan 逐测站扫描;轮询 GET /scan-tasks/{id} 6 POST .../compute {station_ids:[...]} 计算入队 → compute_task_id 7 POST .../wrap-up 收尾入队(完成后房间置 done) GET .../compute-tasks/{id} 轮询单个计算/收尾任务 GET .../live 房间 + 各测站实时状态(APP 主轮询端点) 8 GET .../result / .../stations/{sn}/result 房间/测站结果 GET /results/work/{cid}/rooms/{ruid}/<路径> 结果文件(png/ply/zip) 9 POST .../leave 离开房间(done 房间无需调用,为 noop) DELETE .../task 删房间结果(含点云则回未开始需重扫) ``` **扫描 body**(`device_info/scanner_loc/crop_dist/mode_table` 全由 APP 提供,板子按 `device` 路由): ```json {"device_info":{"device":"faro_new","scanning_mode":"1/16"}, "scanner_loc":["0","0","0"], "crop_dist":["8","8","3"], "mode_table":{"masonry_mode":0,"tradition_map_mode":1,"sjg_map_mode":0}, "color":false} ``` 支持三种设备: | APP 传 `device` | 显示名 | 链路 | |---|---|---| | `faro_new` | Topcon-Faro | FARO HTTP,`POST /lswebapi/jobs` | | `faro_old` | Faro-PC | FARO HTTP,`POST /lswebapi/operations/scan-operation` | | `leica_blk360_g2` | Leica-BLK360-G2 | 容器内 Linux SDK 直连 | - 新老 FARO 是两种独立设备,端点与分辨率映射由 `app/scanner/faro_modes.json` 按设备名分流,表里没配的档位**明确报错**不静默默认。 - `faro_new/faro_old` 算法不认:落 `scan_info.json` 时按 `algo_device` 译成 `topcon_faro`/`faro_pc`(唯一不原样透传的字段)。 - `/wifi/connect` 成功后自动把 `scanner_config` 切到该设备,APP 不必再 `POST /scanner/config`。 - **profileId(仅 Focus S70 / S Plus 350)**:建任务须带,只能从设备已有 job 反查,而 job 列表重启即空;板子按序列号 缓存到 `data/faro_profiles.json`,一台设备只需手动补扫一次。相关:`GET /faro/profile-status`、 `POST /faro/wait-manual-profile`。 ### 关键规则 - **全板同一时刻只有一个房间 duty**;其他房间 enter 得 409 + 占用者信息(含房间名/阶段)。 - **计算严格单线程,但已异步**:`/compute`、`/wrap-up` **入队即返回 task_id**,全板一条队列单消费者 (`app/queue_service.py`);房间正在计算**不再 409**,排队即可,并发合批由 `ALGO_MAX_WORKERS` 决定。 APP 轮询 `/compute-tasks/{id}` 或 `/live`。 - **wrap_up 成功 = 房间结束**:板子随即**纯释放**算法对象(`/room-task/release`,只丢引用不删结果)。done 房间退出 是 noop。**done 房间不能再 enter**,需先 `DELETE .../task` 删结果。 - **leave 按状态分三种**(`app/duty_service.py`):`done`→什么都不做;duty 且算过测站→`leave_without_wrap_up` (房间级结果丢弃、测站结果保留)后销毁;duty 但没算过→纯释放。只有桥上活动对象确实是本房间时才动算法。 - 卡住兜底:`POST /duty/release` 强制释放当前占用者;重启时 `startup_recover` 自愈残留状态。 ## 5. 配置项(环境变量) 容器内关键项由 `docker-compose.yml` 设置;win_agent / 本机模式用 `.env`(复制 `.env.example`,详细注释见该文件)。 | 变量 | 默认 | 用在哪 | 说明 | |---|---|---|---| | `ALGO_BRIDGE_URL` | `http://127.0.0.1:8100` | 板子 | compose 内为 `http://algo-bridge:8100` | | `ALGO_CALL_TIMEOUT` | `3600` | 板子 | 单次算法调用超时(秒),compute 分钟级勿设小 | | `BOARD_ALGO_MAX_WORKERS` | `1` | 板子 | 并发算的测站数 = 队列合批上限 | | `WIN_AGENT_URL` | 空 | 板子 | compose 内 `http://host.docker.internal:8200`;本机模式留空 | | `BLK360_IP` / `FARO_BASE_URL` | `10.10.1.1` / `192.168.43.1:8888` | 板子 | 扫描仪地址 | | `BLK360_DLL` | Windows dll | 板子 | compose 内 `/blk360/lib/libBLK360G2.so` | | `BOARD_SCANNER_MOCK` | `0` | 板子 | `1`=mock 扫描,同时让 `/wifi/*` 变假 | | `SCANNER_WIFI_INTERFACE` | 空 | 板子 | 连扫描仪的**无线网卡名**,现场配 `WLAN`(见 §6) | | `SCANNER_BIND_IP` | 空 | 本机模式 | 扫描仪侧网卡本机 IP | | `BOARD_BIND_HOST` | `0.0.0.0` | 本机模式 | **保持 `0.0.0.0`**;Docker 看 compose 端口映射 | > compose 的 `environment` 优先级高于 `.env`(`load_dotenv` 不覆盖已有环境变量);本块出现过的变量只能在这里改, > 改完 `docker compose up -d` 重建容器(`docker restart` 不重读 environment)。 ## 6. 现场网络(迷你路由器接 Android + 板载网卡连扫描仪) ``` [FARO / BLK360] ~wifi~▶ WLAN(Intel AX201) ─┐ ├─ 这台电脑(板子 + win_agent + docker) [Android 手机] ~wifi~▶ 迷你路由器 ─网线───┘ 以太网(I226-V) 固定 192.168.8.100 192.168.8.1 ``` - 手机与板子经路由器同处 `192.168.8.0/24`,APP 访问 `http://192.168.8.100:8000`(**这是电脑自己的 IP,不是网关**)。 - AX201 空出来专职连扫描仪(一块网卡同时只能连一个 wifi,点云传输量大要独占射频);USB 无线网卡已退役。 - **别再试「板子自己开热点」**——2026-08 实测 softap 每 120s 踢客户端、tethering 需已连通上行,工地无网可用,相关代码已删。 **接线与网卡**: - 网线接路由器 **LAN 口**(非 WAN,否则手机访问不到电脑);`以太网` 设静态 `192.168.8.100/24`,**不填网关/DNS** (路由器无外网,填网关会与扫描仪链路抢默认路由)。 - 路由器给该 MAC 做 DHCP 静态绑定到 `192.168.8.100`(双保险)。 - 关掉 AX201 对路由器 wifi 的自动连接:`netsh wlan set profileparameter name="" connectionmode=manual`, 否则开机自动连回去抢走扫描仪网卡。 **路由器侧**:**AP 隔离必须关**(第一大坑,开着手机访问不到电脑);2.4G SSID(5G 穿钢筋衰减大);网段保持 `192.168.8.0/24`(与 FARO/BLK360/docker 不冲突);USB 用持续供电口。 **三处配置对齐**(同一地址写三处):`Code_Windows/.env` 的 `SCANNER_WIFI_INTERFACE=WLAN`;`CodeFronted/.env` 的 `HOST_LAN_IP=192.168.8.100`;`CodeAndroid/.../board_config.json` 的 `board_ip=192.168.8.100`。Android 改 json 即可, Kotlin 不动,改完重新出包。 **端口映射**:compose 保持 `"8000:8000"`,**别写 `"192.168.8.100:8000:8000"`**(开机网卡拿地址比 docker 晚,会 `bind: cannot assign requested address`)。 **扫描仪侧路由**(真出现歧义时,管理员运行):`.\tools\setup_scanner_route.ps1 -ScannerInterfaceAlias "WLAN"` 把 `10.10.1.0/24`(BLK360)与 `192.168.43.0/24`(FARO)钉到 WLAN。 **验证**:`curl http://192.168.8.100:8000/health` 本机通、手机不通 → 八成是 AP 隔离没关。上线前手机连 APP 静置 10 分钟确认不掉线。 ## 7. 数据存放与导出 运行时数据在 Docker 卷 `code_windows_board_data`(板子 `/board/data`、桥 `/data` 共享),**Windows 侧无对应目录**。 ``` /board/data/ board.db # SQLite:楼栋/房间/扫描任务/计算任务索引 scanner_config.json # APP 下发的扫描仪连接参数 download_tasks// # 同步 zip 解包(户型图、测站鸟瞰图) work_tasks//<楼层>/<房间>/ layout_info.json layout_bk.png mesh_logo.png <测站ID>/ scan_pcd.ply scan_info.json station_res.json station_res/ room_task.json room_res.json room_res/ panorama_res/ registers/ ``` 计算峰值约 1 GB,wrap_up 后约 290 MB,leave 后约 115 MB。导出: ```powershell docker cp board-app:/board/data D:\导出目录 # 全部(容器 Exited 也可,卷在即可) docker cp board-app:/board/data/work_tasks/ ... # 单次计算 docker exec board-app du -sh /board/data/work_tasks/* # 只看占用(需运行中) ``` 也可走 HTTP:`GET /results/work/{cid}/rooms/{ruid}/<相对路径>`。 ⚠️ `docker compose down -v` 或 Docker Desktop 重装/factory-reset 会**清空卷**(数据全丢);WSL2 虚拟盘只增不减, 删数据不还空间,需回收时 `down -v` 后重建卷。留档请先 `docker cp` 或依赖云端同步。 ## 8. fls→ply 转换(板子 → win_agent) 只有 **FARO 系**需要(BLK360 直出 ply)。入口:`scan` 后板子从 LsWebAPI 下 fls 包自动进队列,或 `POST .../stations/{sn}/fls-package` 上传 zip。板子容器与 win_agent **不共享目录**,整包走 HTTP: ``` 板子: FARO 包 zip ─POST /convert-zip─▶ win_agent 临时目录解压→重组 .fls/→调 exe─▶ 响应体=ply 落盘 <测站>/scan_pcd.ply(先写 .part 收完整再改名) ``` 单线程队列,多测站排队;单次超时 1800s。实测:134.9 MB zip → 649.2 MB ply(4328 万点)约 254s。 前置:win_agent 已启动、8200 未被防火墙拦、已装 SCENE+vcredist、`CONVERT_FLS_EXE` 指向真实 exe。 ## 9. 实机自测 CLI(Windows 侧) ```powershell python tools\blk360_cli.py info python tools\blk360_cli.py scan out.ply --density 4 python tools\faro_cli.py info python tools\faro_cli.py scan out.zip --mode 1/16 --kind faro_new ``` ## 10. 常见问题 | 现象 | 处理 | |---|---| | 桥 `import_failed` | 看 `import_error`;缺 `.so` → `algo_bridge/Dockerfile` apt 补装重建 | | 板子 503 `algo bridge unreachable` | 桥没起/未就绪:`docker logs board-algo-bridge` | | `image ... not found locally` | detector tar 没 load:`docker load -i ...detector_image.tar` | | fls 转换 `win_agent unreachable` | win_agent 没启动或 8200 被拦(§8) | | 找不到数据目录 | 数据在 Docker 卷,非 `Code_Windows/data/`;`docker cp` 导出(§7) | | 升级后房间全空 | 卷是否还在:`docker volume ls`;`down -v`/重装会清空 | | enter 返回 409 | 有其他房间在 duty(响应带占用者),或该房间已 done | | 重启后卡在 duty | 启动自动恢复;仍异常先重启桥再 `docker restart board-app`,或 `POST /duty/release` | | 改代码不生效 | 卷挂载:`docker restart board-app`/`board-algo-bridge`;win_agent 重跑脚本 | ## 11. 相关文档 - `PLAN.md` / `plan_2.md` — v1/v2 设计与算法接口实测结论 - `step.md` — 里程碑执行日志 - `.env.example` — 全部环境变量说明 - `README.full.bak.md` — 精简前的完整旧版(含各处踩坑详述)