2026-06-22-operate-control-双进程拆分-design.md 13 KB

operate / control 拆分为双进程 · 架构设计

立项:2026-06-22(经 brainstorming 逐项澄清,关键决策已与用户确认) 范围:只动 operate 与 control,front(管理端 ivf_tl_Manage)保持现状不动。 现状基线见 项目文档/control-逻辑与配置全景.md(control 全部逻辑 + 配置参数,标 file:line)。 实现方式:分阶段落地(见 §8)。


1. 背景与问题

1.1 合并的真相

原本 operate(本机操作界面)与 control(机器驱动:串口/拍照/换气/对焦/上传)是两个独立进程、独立软件,各自连服务器,本地互不通信。"合并"只为代码仓库/管理方便,把 control 逻辑塞进了 operate 进程内(operate MainWindow_Loaded → Task.Run → StartMain.StartRun())。

1.2 合并引入的三个问题(本设计要解决的)

  1. 生命周期冲突:control 必须 7×24 常驻驱动机器(胚胎培养不能中断);operate 只是 UI,用户随时想开就开、想关就关。塞进一个进程后——
    • operate 关 UI 进程不退(control 的 LongRunning 前台线程撑着)→ 残留无窗口进程(如曾出现的 PID 20268)。
    • 重开 operate 又起一套 control → 多实例抢串口(G1 "串口占用"的根之一)。
  2. 监控功能没写全:operate 设置里的"服务监控"页本是用来看 control 各功能运行状态的,但合并时���做了"同进程直读内存"(ServiceMonitorViewModel 直读 ControlAppData.Instance.GetMonitorSnapshot()),内容也不全。
  3. 调试串口占用:control 常驻占着串口,operate 进调试页/串口设置页要用串口时与 control 冲突(现有 HouseGate 闸门只是同进程内协调)。

1.3 目标

  • 运行时回归"两个独立进程":control.exe 常驻驱动机器,operate.exe 可随时开关。
  • 用户/装机视角仍是一个软件:只装、只启动 operate;control 由 operate 自动管理,用户无感。
  • operate 关闭 → control 继续驱动机器、采集、上传。
  • 补全监控页(跨进程读 control 真实状态)。
  • 解决调试串口占用(跨进程借用/归还)。

2. 已确认的关键决策

# 决策 选定方案
1 进程模型 operate / control 两个独立进程;代码仍在一个解决方案里管理,装机是一个软件;front 不动
2 进程间通信 control 开本地 HTTP 小服务(127.0.0.1:<端口>),operate 调用
3 谁拉起 control operate 按需拉起 + operate 开机自启 + control 用 Mutex 保证单实例
4 调试让串口(B) operate 调 /serial/pause+/serial/resume,复用现有 HouseGate 闸门,跨进程喊;control 不死、其他舱照常、调完自动恢复
5 整体停止 control(A) 监控页一个受护栏按钮(二次确认 + 工程师口令 tl13579)→ /shutdown 安全停机
6 监控页补全 补:各舱实时活动(拍照/对焦/换气)、后台线程健康/心跳、串口借用/占用状态
7 实现节奏 分阶段落地

3. 目标架构

3.1 进程拓扑

                    培养箱本机
┌──────────────────────────────────────────────────────┐
│  operate.exe(UI,可随时开关,管理员)                  │
│   - 本机操作界面:舱室/调试/对焦/配置                  │
│   - 服务监控页:轮询 control 的 /status               │
│   - 启动时:确保 control 在跑(不在则拉起)            │
│        │  本地 HTTP(127.0.0.1)                       │
│        │  GET /status  POST /serial/pause|resume      │
│        ▼  POST /shutdown                              │
│  control.exe(无界面常驻,管理员,Mutex 单实例)       │
│   - StartMain.StartRun():串口/相机/采集/对焦/换气     │
│   - 10×HouseBin 采集循环 + BufferBottleBin + 上报线程 │
│   - 内嵌轻量 HttpListener 提供上述接口                │
└──────────────────────────────────────────────────────┘
        │ control 各自连服务器          │ operate 各自连服务器
        ▼ (MQTT 指令 / Kafka 图片 / HTTP) ▼ (gateway HTTP / MQTT)
                       中央服务器

要点:operate 与 control 业务上仍各自连服务器(维持现状);本地新增的唯一通道是 control 的 HTTP 小服务,只承载"读状态/借串口/停止"三类本地协调。

3.2 control 独立进程的形态

  • 无界面后台常驻(control 本就无功能界面)。不弹登录窗(原 Window1 退役)。
  • 账号由 operate 拉起时传入(命令行参数,见 §5.2)。
  • 启动即 StartMain.StartRun() 跑起采集 + 起 HttpListener。
  • 基于干净的 ivf_tl_Control 类库新建启动器,不改造脏壳 ivf_tl_ControlTest(其唯一正式资产 Window1 启动骨架已被 operate 复刻;其余是测试/死代码,见 §7)。

4. 本地 HTTP 接口契约

control 进程内用 .NET 自带 HttpListener(轻量,无需 ASP.NET),只监听 127.0.0.1:<端口>。端口写进配置(默认如 38080,可改),避免占用冲突。control 是管理员运行,HttpListener 的 URL ACL 注册天然不受限。

方法 路径 入参 返回 用途
GET /status JSON 快照(见 §6) 监控页轮询(每 2s)
GET /ping {ok,pid,tlSn} operate 启动时探活,判断要不要拉起
POST /serial/pause {houseSn} {ok} 调试借串口:control 让出该舱串口(MarkPause→暂停采集)
POST /serial/resume {houseSn} {ok} 调试完归还:恢复该舱采集
POST /shutdown {token} {ok} 受护栏整体停机(token=工程师口令校验)

约定:

  • 全部 127.0.0.1 only,拒绝非本机请求(防外部调停机/借串口)。
  • 请求/响应 JSON;沿用项目现有 Newtonsoft.Json。
  • operate 侧调用复用现有 HttpService/HttpHelper 基础设施。

5. 生命周期管理

5.1 单实例(control)

control 启动时用命名 Mutex(如 Global\ivf_tl_control_singleton)判重:已存在则立即退出。这是"永远只有一个 control 驱动机器"的硬保证,根治多实例抢串口。(原 ivf_tl_ControlTest.OnStartup 已有 Mutex 范例可参考。)

5.2 operate 拉起 control

operate 启动流程(替换现有 MainWindow_LoadedTask.Run(StartRun) 那段):

  1. operate 登录成功 → 探 GET /ping
  2. 通 → control 已在跑,直接进入"已连接"状态,不拉起
  3. 不通 → Process.Start 拉起 control.exe,把账号/密码/缓存盘等通过命令行参数传入(替代原 Window1 读配置 + 透传账号);管理员静默(operate 已是管理员,不弹 UAC)。
  4. 轮询 /ping 直到 control 就绪(带超时与重试),再进入"已连接"。

5.3 operate 关闭

operate 关 UI 只退出自己,绝不动 control(去掉任何"退出时停 control / Environment.Exit 带走后台"的逻辑)。control 继续驱动机器。

修正合并期的缺陷:现 App_Exit 空、无对称停机——双进程后这反而是对的(operate 关闭不该影响 control)。

5.4 control 停止(仅受护栏入口)

只有监控页那个受护栏按钮(二次确认 + 工程师口令)→ POST /shutdown → control 执行安全停机:停采集主循环 → 停各 LongRunning 上报线程 → 关相机/串口句柄(HardwareAccessLayer.ShutdownAll())→ 释放 Mutex → 进程退出。

control 需新增一个统一 Shutdown() 入口(现状只有零散 CloseCamera/StopBlance/StopDish,无统一停机,见全景文档第九章)。

5.5 开机自启

装机时把 operate 设为开机自启(注册表 Run 或启动文件夹)。开机 → operate 自动起 → 拉起 control → 机器自动被驱动,用户无需点击。


6. 监控页补全(/status 返回内容)

/status 在现有 GetMonitorSnapshot() 基础上补三块(数据 control 内部已有,全景文档标了 file:line):

现有(保留):control 存活、MQTT 连接、HTTP/Kafka 链路最后成功时间、图片上传队列、磁盘、各舱(串口态/温度/压力/舱态/相机态)。

补充:

  1. 各舱实时活动:WorkingType(DoNothing/AirSwapWorking/CCDWorking/AutoFocusWorking)、RunStateValveStatecultureState(空闲/培养/平衡)、上次拍照时间。→ 一眼看出哪个舱在拍照/对焦/换气。
  2. 后台线程健康/心跳:采集主循环 LastRunTime(超 N 分钟未更新=卡死)、图片上传线程/上报线程存活、各舱采集线程状态。
  3. 串口借用/占用状态:每个舱串口当前被谁占(采集中/调试中/对焦中)、CapturePausedByGate 是否让路中。→ 排障"为什么调试进不去"。

operate 侧 ServiceMonitorViewModel.Refresh() 从"直读 ControlAppData.Instance"改为"调 GET /status 拿 JSON 映射"。其余展示逻辑基本不变。


7. 老壳 ivf_tl_ControlTest 的处置

逐文件鉴定(全景文档第九章已记):

  • Window1:正式启动骨架已被 operate 复刻;其余是测试按钮/shutdown /r 重启电脑/XML 改 config/已禁用的 StopProEvent/注释黑客代码。
  • MainWindow(命名空间 TLTest):100% 测试(硬编码登录、发测试 MQTT、造假 ImageDTO)。
  • StopWindow:死窗口(调用点已禁用)。

处置:不改造此脏壳。新 control 启动器基于干净的 ivf_tl_Control 类库新建;ivf_tl_ControlTest 整个项目在阶段3退役删除(确认新启动器跑通后)。


8. 分阶段实现计划

每阶段可独立编译/验证;真机已连,真机相关步骤由 Claude 自主跑完(无需用户在场/配合),仅「水平电机」「垂直 Z 电机」运动范围需守安全区间(参考 临时文件/相关参数.html),其余下位机控制无风险。

阶段 1:control 独立进程骨架(最关键)

  • 新建 control 启动器项目(WinExe/无界面 或 控制台+后台,管理员 manifest),引用 ivf_tl_Control 类库。
  • Mutex 单实例;命令行接账号参数;启动即 StartMain.StartRun()
  • control 内嵌 HttpListener,先实现 /ping + /status(返回现有快照)。
  • operate:移除"进程内跑 control"那段;改为登录后探 /ping→不在则 Process.Start 拉起 control(传参)。
  • 出口:operate 启动能拉起独立 control.exe、control 驱动机器、operate 关闭后 control 继续;监控页能通过 /status 看到 control 活着。(真机验证采集/握手)

阶段 2:监控补全 + 调试借串口 + 受护栏停止

  • /status 补 §6 三块内容;ServiceMonitorViewModel 改跨进程读。
  • 实现 /serial/pause+/serial/resume,接到现有 HouseGate 闸门;operate 调试页 ComHouseInit/ComHouseUnit 改为跨进程借/还(替代现同进程 gate.Acquire)。
  • 监控页加受护栏停止按钮 → /shutdown;control 实现统一 Shutdown() 安全停机。
  • 出口:监控页显示完整;调试页能借到串口(control 让路)、退出恢复采集;停止按钮能安全停 control。(真机验证调试借串口时序 = G1 的 V-012)

阶段 3:清理 + 收尾

  • 退役删除 ivf_tl_ControlTest;清理 operate 里残留的"内嵌 control"死代码。
  • 装机:operate 开机自启配置;部署文档更新(两个 exe、端口、开机自启)。
  • ComBin 两套栈去重(G1-2)收尾;回写需求文档/进度文档。
  • 出口:全新部署一次到位、文档与代码对齐。

9. 风险与注意

  1. HttpListener 端口冲突:端口可配,启动失败要有清晰日志 + operate 探活超时提示。
  2. control 拉起竞态:operate 探活到拉起之间若用户手快开了两个 operate,可能都想拉 control——靠 control Mutex 兜底(第二个 control 自退),operate 侧拉起也加锁/重试。
  3. 账号传参安全:命令行传密码有被进程列表看到的风险;可改为 operate 拉起后经 /login(127.0.0.1)post 账号,或加密传参。待实现阶段定(列为阶段1的细化点,不留 TBD:默认先用命令行加密串,阶段1 评审时确认)。
  4. control 安全停机时序:停采集→关相机/串口要按 HardwareAccessLayer.ShutdownAll() 的顺序,避免句柄泄漏(参考全景文档 §六)。真机验证
  5. 真机门控:真机已连,阶段1 的采集验证、阶段2 的调试借串口(V-012)均由 Claude 自主跑完(UAC 静默提权,无需用户在场/配合)。下位机均有可读默认值(项目内有读默认值的方法);唯一需守安全区间的是「水平电机(皿孔旋转)」与「垂直 Z 电机(对焦)」的运动范围,参考 临时文件/相关参数.html(用户自测值,仅供参考,实际安全范围比文档更宽):水平钳位 0,220000、垂直 Z 钳位 0,125000。除这两轴运动范围外,其余下位机控制无任何风险。
  6. 不动 front、不动业务逻辑:本次只动进程边界与本地通信,control 的采集/换气/对焦/上传逻辑零改动(降低回归风险)。