Browse Source

feat(control): D1-10 control侧oplog审计埋点迁移到活栈 + 真机red→green

control子树原零OperationLogger埋点(都在operate不驱动硬件的死栈)→设备物理动作审计缺失(合规级)。
与D3-04删operate死栈解耦,本轮纯新增埋点(不改串口字节/时序/电机·全try兜底):
- 4 control程序集引Aivfo.OperationLog;ControlHost InitOperationLog(Project=control)+生命周期审计(进程启动/采集启动成功/安全停机flush)
- Camera Init/UnInit(镜像operate P3b)、HouseBin换气/补气(每会话一行·远离串口热循环)、SerialChannelImpl 5个EEPROM写(日志置_ioLock外)
- 新增oplog-config.json(模块开关·随exe部署热加载)

真机RED(operation_log project=control=0,operate=883证管道通)→GREEN(control=70:生命周期3+相机68结构化input/output);
/shutdown口令tl13579安全停机行flush落库+7COM释放。换气/补气/写EEPROM同已证机制·放皿培养/调试接通时触发。
control sln+operate Release双编译0错;既有40单测过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie 2 ngày trước cách đây
mục cha
commit
f77bc042c7

+ 14 - 5
ivf_tl_operate_2.0/control/IvfTl.Hardware/Impl/SerialChannelImpl.cs

@@ -110,12 +110,16 @@ namespace IvfTl.Hardware.Impl
             // T1.1 子代理逐字节核实与 operate 完全一致:0x12 + 同 well 地址表 + 小端 pulse + 同 CreateORC)。
             // 写命令无显式成功出口(旧栈 void),阻塞收到回复即视为已下发,返回 true。
             // ⚠ 待真机 V-010:写E方破坏性,0x12 回包长度 control/operate 有差异,"写成功"判定真机核对。
-            lock (_ioLock) { _com.WriteEEPROMhoriMtWellHorHorWait(new CustomProtocol(), well, pulseValue); return true; }
+            lock (_ioLock) { _com.WriteEEPROMhoriMtWellHorHorWait(new CustomProtocol(), well, pulseValue); }
+            try { Aivfo.OperationLog.OperationLogger.Log("写EEPROM", "写水平电机well位置", input: new { port = _portName, well, pulseValue }, result: "成功"); } catch { }
+            return true;
         }
 
         public bool WriteScanStepWait(int pulseValue)
         {
-            lock (_ioLock) { _com.WriteEEPROMvertMtScanPluseWait(new CustomProtocol(), pulseValue); return true; }
+            lock (_ioLock) { _com.WriteEEPROMvertMtScanPluseWait(new CustomProtocol(), pulseValue); }
+            try { Aivfo.OperationLog.OperationLogger.Log("写EEPROM", "写垂直扫描间隔", input: new { port = _portName, pulseValue }, result: "成功"); } catch { }
+            return true;
         }
 
         public bool WriteOpenIntakeTimeWait(int newValue, bool isBuffer = false)
@@ -124,8 +128,9 @@ namespace IvfTl.Hardware.Impl
             {
                 if (isBuffer) _com.WriteEEPROOpenIntakeTimeBufferWait(new CustomProtocol(), newValue);
                 else _com.WriteEEPROOpenIntakeTimeWait(new CustomProtocol(), newValue);
-                return true;
             }
+            try { Aivfo.OperationLog.OperationLogger.Log("写EEPROM", isBuffer ? "写缓冲瓶进气阀时间" : "写舱室进气阀时间", input: new { port = _portName, newValue, isBuffer }, result: "成功"); } catch { }
+            return true;
         }
 
         public bool WriteOpenVentTimeWait(int newValue)
@@ -133,7 +138,9 @@ namespace IvfTl.Hardware.Impl
             // M-01 已补:control Commander/ComBin 补齐 CreateWriteEEPROOpenVentTimeCommand + WriteEEPROOpenVentTimeWait
             // (builder 地址 00 03 08,与合并前 operate 逐字节一致)。写命令旧栈无显式成功出口(void),
             // 阻塞收到回复即视为已下发,返回 true。
-            lock (_ioLock) { _com.WriteEEPROOpenVentTimeWait(new CustomProtocol(), newValue); return true; }
+            lock (_ioLock) { _com.WriteEEPROOpenVentTimeWait(new CustomProtocol(), newValue); }
+            try { Aivfo.OperationLog.OperationLogger.Log("写EEPROM", "写舱室排气阀时间", input: new { port = _portName, newValue }, result: "成功"); } catch { }
+            return true;
         }
 
         public int ReadOpenVentTimeWait()
@@ -145,7 +152,9 @@ namespace IvfTl.Hardware.Impl
         public bool WriteLightBrightnessWait(int newValue)
         {
             // M-03 已补:control 补齐 CreateWriteEEPROMLightNum + WriteEEPROMLightNumWait(地址 00 05 34)。
-            lock (_ioLock) { _com.WriteEEPROMLightNumWait(new CustomProtocol(), newValue); return true; }
+            lock (_ioLock) { _com.WriteEEPROMLightNumWait(new CustomProtocol(), newValue); }
+            try { Aivfo.OperationLog.OperationLogger.Log("写EEPROM", "写灯光亮度", input: new { port = _portName, newValue }, result: "成功"); } catch { }
+            return true;
         }
 
         // ── 电机:垂直 ──

+ 2 - 0
ivf_tl_operate_2.0/control/IvfTl.Hardware/IvfTl.Hardware.csproj

@@ -19,6 +19,8 @@
     <ProjectReference Include="..\ivf_tl_SerialHelper\ivf_tl_SerialHelper.csproj" />
     <ProjectReference Include="..\ivf_tl_Entity\ivf_tl_Entity.csproj" />
     <ProjectReference Include="..\ivf_tl_UtilHelper\ivf_tl_UtilHelper.csproj" />
+    <!-- D1-10/D3-04:control 侧操作日志(审计埋点)——SerialChannelImpl 写E方/配置变更收口。 -->
+    <ProjectReference Include="..\..\..\Aivfo.OperationLog\Aivfo.OperationLog.csproj" />
   </ItemGroup>
 
 </Project>

+ 19 - 0
ivf_tl_operate_2.0/control/ivf_tl_CameraHelper/Camera.cs

@@ -191,6 +191,16 @@ namespace ivf_tl_CameraHelper
 
                 var result = InitCamera(this);
                 if (result == 0) this.IsInit = true;
+                // D1-10:相机初始化边界审计埋点(module=相机)。全 try 兜底,镜像 operate 死栈同款 P3b。
+                try
+                {
+                    Aivfo.OperationLog.OperationLogger.Log("相机", "初始化",
+                        input: new { index, width, height, exposure },
+                        output: new { result },
+                        result: result == 0 ? "成功" : "失败",
+                        error: result == 0 ? null : $"InitCamera 返回 {result}");
+                }
+                catch { }
                 return result;
             }
             catch (Exception ex)
@@ -289,6 +299,15 @@ namespace ivf_tl_CameraHelper
             {
                 this.IsInit = false;
                 var result = UnInit(this);
+                // D1-10:相机卸载边界审计埋点(module=相机)。全 try 兜底。
+                try
+                {
+                    Aivfo.OperationLog.OperationLogger.Log("相机", "卸载",
+                        input: new { index },
+                        output: new { result },
+                        result: result == 0 ? "成功" : "失败");
+                }
+                catch { }
                 return result;
             }
             catch (Exception ex)

+ 2 - 0
ivf_tl_operate_2.0/control/ivf_tl_CameraHelper/ivf_tl_CameraHelper.csproj

@@ -13,6 +13,8 @@
 
 	<ItemGroup>
 	  <ProjectReference Include="..\ivf_tl_Entity\ivf_tl_Entity.csproj" />
+	  <!-- D1-10/D3-04:control 侧操作日志(审计埋点)——相机 Init/UnInit 边界收口。 -->
+	  <ProjectReference Include="..\..\..\Aivfo.OperationLog\Aivfo.OperationLog.csproj" />
 	</ItemGroup>
 
 </Project>

+ 37 - 0
ivf_tl_operate_2.0/control/ivf_tl_Com/HouseBin.cs

@@ -1029,10 +1029,28 @@ namespace ivf_tl_Com
                 HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][换气结束时间:{DateTime.Now}][换气耗时:{StringHelper.TimeToString(AirThreadStopwatch.Elapsed)}]", LogEnum.HouseInfo);
                 AirThreadStopwatch.Stop();
                 WorkingType = WorkingType.DoNothing;
+                // D1-10:换气物理动作审计(每次换气会话一行,非每次阀动作;远离串口字节循环)。全 try 兜底。
+                try
+                {
+                    Aivfo.OperationLog.OperationLogger.Log("换气", "周期换气",
+                        input: new { houseSn = House.houseSn, port = PortName, airSwapTime = TLSetting.airSwapTime },
+                        result: "成功",
+                        elapsedMs: (long)AirThreadStopwatch.Elapsed.TotalMilliseconds,
+                        houseSn: House.houseSn, tlSn: TLSetting?.tlSn);
+                }
+                catch { }
             }
             catch (Exception ex)
             {
                 ExceptionLogEvent?.Invoke(ex, $"[{House.houseSn}][{PortName}]换气", null, LogEnum.RunException);
+                try
+                {
+                    Aivfo.OperationLog.OperationLogger.Log("换气", "周期换气",
+                        input: new { houseSn = House.houseSn, port = PortName },
+                        result: "失败", error: ex.GetType().Name + ": " + ex.Message,
+                        houseSn: House.houseSn, tlSn: TLSetting?.tlSn);
+                }
+                catch { }
             }
         }
 
@@ -1326,10 +1344,29 @@ namespace ivf_tl_Com
                 if (Pressure < TLSetting.pressureAlarmMin) this.ValveState = State.待补气;
                 HouseLogEvent?.Invoke(House.houseSn, DateTime.Now, $"[{House.houseSn}][{this.PortName}][补气结束时间:{DateTime.Now}][耗时:{StringHelper.TimeToString(AerationStopwatch.Elapsed)}]", LogEnum.HouseInfo);
                 AerationStopwatch.Stop();
+                // D1-10:补气物理动作审计(每次补气会话一行)。全 try 兜底。
+                try
+                {
+                    Aivfo.OperationLog.OperationLogger.Log("补气", "缺压补气",
+                        input: new { houseSn = House.houseSn, port = PortName, pressureAlarmMin = TLSetting.pressureAlarmMin },
+                        output: new { pressure = Pressure, valveState = ValveState.ToString() },
+                        result: "成功",
+                        elapsedMs: (long)AerationStopwatch.Elapsed.TotalMilliseconds,
+                        houseSn: House.houseSn, tlSn: TLSetting?.tlSn);
+                }
+                catch { }
             }
             catch (Exception ex)
             {
                 ExceptionLogEvent?.Invoke(ex, $"[{House.houseSn}][{PortName}]补气", null, LogEnum.RunException);
+                try
+                {
+                    Aivfo.OperationLog.OperationLogger.Log("补气", "缺压补气",
+                        input: new { houseSn = House.houseSn, port = PortName },
+                        result: "失败", error: ex.GetType().Name + ": " + ex.Message,
+                        houseSn: House.houseSn, tlSn: TLSetting?.tlSn);
+                }
+                catch { }
             }
         }
 

+ 2 - 0
ivf_tl_operate_2.0/control/ivf_tl_Com/ivf_tl_Com.csproj

@@ -13,6 +13,8 @@
     <ProjectReference Include="..\ivf_tl_SerialHelper\ivf_tl_SerialHelper.csproj" />
     <ProjectReference Include="..\IvfTl.Hardware\IvfTl.Hardware.csproj" />
     <ProjectReference Include="..\IvfTl.AutoFocus\IvfTl.AutoFocus.csproj" />
+    <!-- D1-10/D3-04:control 侧操作日志(审计埋点)——换气/补气等业务物理动作收口。 -->
+    <ProjectReference Include="..\..\..\Aivfo.OperationLog\Aivfo.OperationLog.csproj" />
   </ItemGroup>
 
 </Project>

+ 50 - 0
ivf_tl_operate_2.0/control/ivf_tl_ControlHost/Program.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Diagnostics;
 using System.Threading;
+using Aivfo.OperationLog;             // D1-10/D3-04:control 侧操作日志(审计埋点)
 using ivf_tl_Control;                 // AppData, StartMain
 using IvfTl.Control.Services;         // Log4netHelper(程序集 ivf_tl_Control_Services;切勿写 operate 端 ivf_tl_Services)
 
@@ -30,6 +31,17 @@ namespace IvfTl.ControlHost
                 var hostArgs = HostArgs.Parse(args);
                 Log4netHelper.WriteLog($"ControlHost 启动 port={hostArgs.Port} account={hostArgs.Account}");
 
+                // D1-10/D3-04:先起操作日志组件(审计埋点),Project=control。紧接记一条"进程启动"生命周期审计
+                // (即刻入库,与硬件埋点共同构成 control 真实物理动作的合规审计)。全 try 兜底,绝不影响启动。
+                InitControlOperationLog();
+                try
+                {
+                    OperationLogger.Log("生命周期", "control 进程启动",
+                        input: new { port = hostArgs.Port, account = hostArgs.Account, pid = Process.GetCurrentProcess().Id },
+                        result: "成功");
+                }
+                catch { }
+
                 // 2) 先起 HTTP(让 operate 能尽早探到"在启动中"),started=false。
                 //    阶段2:/status rich + /serial/pause|resume + /shutdown。
                 _http = new ControlHttpServer(
@@ -112,6 +124,15 @@ namespace IvfTl.ControlHost
                 {
                     _started = true;
                     Log4netHelper.WriteLog("ControlHost: control 启动成功,常驻运行");
+                    // D1-10:采集启动成功生命周期审计(带 tlSn)。
+                    try
+                    {
+                        string tlSn = null;
+                        try { tlSn = AppData.Instance.TLSetting?.tlSn; } catch { }
+                        OperationLogger.Log("生命周期", "control 采集启动成功",
+                            output: new { tlSn, started = true }, result: "成功", tlSn: tlSn);
+                    }
+                    catch { }
                 }
             }
             catch (Exception ex)
@@ -178,6 +199,9 @@ namespace IvfTl.ControlHost
         /// <summary>统一安全停机:关相机/串口句柄(ShutdownAll)→ Set 退出事件(主线程 finally 停 HTTP+释放 Mutex)。</summary>
         private static void SafeShutdown()
         {
+            // D1-10:停机生命周期审计 + flush 操作日志(确保停机前在途审计落库),全 try 兜底。
+            try { OperationLogger.Log("生命周期", "control 安全停机", result: "成功"); } catch { }
+            try { OperationLogger.Shutdown(); } catch { }
             try
             {
                 IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ShutdownAll();
@@ -187,6 +211,32 @@ namespace IvfTl.ControlHost
             _exitEvent?.Set();
         }
 
+        /// <summary>
+        /// D1-10/D3-04:初始化 control 侧操作日志组件。Project=control,Kafka 取 App.config kfkaIP/kfkaPort,topic=tl-oplog
+        /// (与 operate/front 同管道);ConfigFilePath=exe 同目录 oplog-config.json(≤15s 热加载,按模块开关)。
+        /// 全 try 兜底:日志初始化失败绝不影响 control 驱动机器。
+        /// </summary>
+        private static void InitControlOperationLog()
+        {
+            try
+            {
+                string kafkaIp = System.Configuration.ConfigurationManager.AppSettings["kfkaIP"]?.ToString() ?? "127.0.0.1";
+                string kafkaPort = System.Configuration.ConfigurationManager.AppSettings["kfkaPort"]?.ToString() ?? "9092";
+                OperationLogger.Init(o =>
+                {
+                    o.Project = "control";
+                    o.KafkaBootstrapServers = $"{kafkaIp}:{kafkaPort}";
+                    o.Topic = "tl-oplog";
+                    o.ConfigFilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "oplog-config.json");
+                });
+                Log4netHelper.WriteLog($"[D1-10]control 操作日志组件已初始化 kafka={kafkaIp}:{kafkaPort} topic=tl-oplog project=control");
+            }
+            catch (Exception ex)
+            {
+                try { Log4netHelper.WriteLog($"[D1-10]control 操作日志组件初始化失败(已忽略):{ex.Message}"); } catch { }
+            }
+        }
+
         /// <summary>/serial/pause 借串口:置该舱 HouseGate 暂停标志,control 采集节拍据此让路(不驱动电机)。</summary>
         private static bool HandleSerialPause(int houseSn)
         {

+ 8 - 0
ivf_tl_operate_2.0/control/ivf_tl_ControlHost/ivf_tl_ControlHost.csproj

@@ -13,5 +13,13 @@
   <ItemGroup>
     <ProjectReference Include="..\ivf_tl_Control\ivf_tl_Control.csproj" />
     <ProjectReference Include="..\ivf_tl_ServicesImpl\ivf_tl_ServicesImpl.csproj" />
+    <!-- D1-10/D3-04:control 侧操作日志(审计埋点)。Project=control,与 operate 共用同一 OperationLogger 组件/Kafka topic。 -->
+    <ProjectReference Include="..\..\..\Aivfo.OperationLog\Aivfo.OperationLog.csproj" />
+  </ItemGroup>
+  <ItemGroup>
+    <!-- D1-10:操作日志按模块开关配置(随 exe 部署到输出目录,≤15s 热加载;改输出目录那份即生效)。 -->
+    <None Update="oplog-config.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
   </ItemGroup>
 </Project>

+ 11 - 0
ivf_tl_operate_2.0/control/ivf_tl_ControlHost/oplog-config.json

@@ -0,0 +1,11 @@
+{
+  "enabled": true,
+  "globalLevel": "Info",
+  "modules": {
+    "生命周期": { "enabled": true },
+    "相机": { "enabled": true },
+    "换气": { "enabled": true },
+    "补气": { "enabled": true },
+    "写EEPROM": { "enabled": true }
+  }
+}

+ 20 - 0
项目文档/进度/交接卡.md

@@ -296,3 +296,23 @@
 - **核实**:SaveBmpPic/af ImageConverter 源码逐行比对;相机 width=1600 实读;基线调试页 `camera.SavePic(name,ccdWidth,ccdHeight)` 调用核对。文档:待验证清单 M-04 ☑ + §8.1 表 + 表头注。
 - **里程碑**:合并遗留 **M 区 M-01~M-07 全部闭合**(M-01/02/03 去桩 + M-04 存图代码定论 + M-05 帧长回归 + M-06 按well零点 + M-07 网关)。
 - **下一步**:剩**延后专项**(均超无监督自主范围):D2-02 调试页借串口完整驱动(需设计串口/相机命令代理大改面)、D3-04 ComBin 两栈去重(有风险重构)、整机开机自启复测(需真重启中断在跑 control)。建议按用户优先级开专项。
+
+---
+
+## 2026-06-23 · D1-10 control 侧 oplog 审计埋点迁移(= 昨日建议第3条,与 D3-04 删死栈解耦先做)+ 真机 red→green
+
+- **背景**:用户 `/goal`「按工作计划把活先干完,再出昨日建议」。M 区已全闭合,工作计划剩三延后专项(D3-04 两栈去重含 D1-10 oplog / D2-02 命令代理 / 整机自启复测)。**关键现实核查**:当前**无 control 在跑、无活体培养**(唯一存活=3天前僵尸 operate 20268,不驱动硬件)→ 自由启停 control 真机验证不中断任何生物过程。故先攻**价值最高且可自主安全闭环**的 D1-10(control 审计埋点,正是昨日建议第3条·医疗合规级,纯新增不碰电机)。**把 D1-10 审计埋点从 D3-04 解耦**:本轮只做新增埋点,删 operate 死栈(有风险删除)仍属 D3-04。
+- **缺口**:拆分后真正驱动硬件的是 control,但 control 子树**零 OperationLogger 埋点**(埋点都在 operate 已不驱动硬件的另一套 ComBin/Camera 死栈)→ 设备对培养环境真实物理动作**审计缺失**。
+- **改动(8 改 + 1 新增,纯新增埋点·全 try 兜底·不改串口字节/时序/电机)**:
+  - csproj×4:ControlHost/ivf_tl_Com/ivf_tl_CameraHelper/IvfTl.Hardware 加 `..\..\..\Aivfo.OperationLog`(net6.0,兼容)ProjectReference。
+  - `ControlHost/Program.cs`:`InitControlOperationLog`(Project=**control**,Kafka 取 App.config kfkaIP/kfkaPort=192.168.0.108:9092,topic=tl-oplog,ConfigFilePath=exe 同目录 oplog-config.json 热加载)+ 生命周期审计(进程启动**即刻产**·账号无关 / 采集启动成功·带 tlSn / 安全停机)+ `SafeShutdown` 先 Log 再 `OperationLogger.Shutdown()` flush。
+  - `ivf_tl_CameraHelper/Camera.cs`:Init/UnInit 边界埋点(逐字镜像 operate 死栈已验 P3b,module=相机;字段仅用确定存在的 index/width/height/exposure/result,不臆造 NumBer)。
+  - `ivf_tl_Com/HouseBin.cs`:换气 `AirSwapOldFun` / 补气 `AerationNew` 业务动作埋点(成功+失败两路,**每会话一行、远离串口字节热循环**,带 houseSn/tlSn/elapsed,module=换气/补气)。⚠ 踩坑:首次换气编辑的 old_string 贪婪吞下了下个方法签名 `private void AirSwapNewFun()` 致花括号失衡(204 错 CS0106/CS1022),补回签名即修复。
+  - `IvfTl.Hardware/Impl/SerialChannelImpl.cs`:5 个 EEPROM 写(写排气阀/进气阀/灯光/扫描间隔/水平well位置)埋点,**日志置 `_ioLock` 外**(绝不持串口 IO 锁记日志),module=写EEPROM。
+  - 新增 `ivf_tl_ControlHost/oplog-config.json`(模块 生命周期/相机/换气/补气/写EEPROM 默认全开,csproj `<None CopyToOutputDirectory=PreserveNewest>` 随 exe 部署)。
+- **真机 red→green(集成,108 oplog 管道在线:消费端 jar/Kafka9092/MySQL3306/gateway 全连通)**:
+  - **RED**:直查 108 `log.operation_log` 按 project 分组——`operate`=883、`control`=**0**(管道通但 control 零埋点=功能缺失,RED 原因正确)。
+  - **GREEN**:提权静默启动 control(pid=6488,tlSn=NEO-1-20230411,**started:true**,MQTT 连,舱7=37.31/舱9=37.25℃ 真温)→ `project=control`=**70 行**:`生命周期`3(进程启动/采集启动成功 `output={"started":true}`/安全停机)+ `相机`68(结构化 `input={"index":5,"width":1600,"height":1200,"exposure":400}` `output={"result":0}`)。`/shutdown` 口令 tl13579→200`{ok:true}`、安全停机行 flush 落库、/ping 不可达=干净退出+7 COM 释放。oplog-error 日志仅"配置已热加载"无错。
+  - **未在本窗口触发(非缺陷·机制已证)**:换气/补气/写EEPROM 用与相机**同一已证 OperationLogger.Log 机制**且编译通过,但 `HouseBin:670` 注释「舱有培养记录才换气/拍照/对焦」——当前**无皿空闲态**不跑换气循环;写EEPROM 需调试页接通(D2-02)。放皿/调试接通即产行。
+- **核实**:RED(control=0)/GREEN(control=70)真机直查;control sln(ControlHost)+ operate Release **双编译 0 错**;既有 40 单测过;codegraph sync 已跑;临时 Q*.java/harness 在 gitignore `临时文件/`。
+- **下一步**:工作计划续做 D3-04 删 operate 死串口栈(有风险删除,谨慎双验)、D2-02 调试页命令代理;之后回到昨日建议(control 看门狗 / HIL 回归套件 / 配置收敛 / 验证清零)。

+ 1 - 1
项目文档/进度/工作计划表.md

@@ -27,7 +27,7 @@
 |------|------|------|----------|
 | **阶段1** | control 独立进程骨架 | 🟢 代码完成·真机闭环打通(待并 main) | control 独立 exe 能起✓、HTTP探活/读状态✓、续命✓、单实例✓、硬件获取✓、**真机自控环运行✓**;阻塞闭环的 D1-08 串口握手死锁已修复 |
 | **阶段2** | 监控补全 + 调试借串口 + 受护栏停止 | 🟢 监控/受护栏停止/借串口让路 已实现+真机验;调试页完整驱动待设计 | 监控页跨进程 /status 显示完整✓;受护栏 /shutdown 安全停✓;/serial 让路✓(调试页完整借串口需命令代理设计+受监督真机) |
-| **阶段3** | 清理老壳 + 装机收尾 | 🟢 退役删ControlTest+部署文档+开机自启 已做;ComBin两栈去重延后 | 退役删 ivf_tl_ControlTest✓(两编译0错);双进程部署指南✓;开机自启注册表方案✓;ComBin两栈去重(G1-2,含oplog)延后专项 |
+| **阶段3** | 清理老壳 + 装机收尾 | 🟢 退役删ControlTest+部署文档+开机自启 已做;**D1-10 control oplog审计埋点已迁移+真机验证(2026-06-23)**;删operate死栈延后 | 退役删 ivf_tl_ControlTest✓(两编译0错);双进程部署指南✓;开机自启注册表方案✓;**D1-10 oplog审计埋点迁移到control活栈✓(与删死栈解耦先做,project=control真机入库)**;ComBin删operate死栈(D3-04)仍延后专项 |
 
 ---
 

+ 8 - 1
项目文档/进度/待验证清单.md

@@ -19,7 +19,7 @@
 | D1-07 | **control 完整采集闭环 + 数据入库(started:true / 真机自控环 / 落库)** | **真机** | ☑(修死锁后·DB实证) |
 | D1-08 | **serialBin/HAL 借用ComBin重开不复活发送线程致串口握手死锁** 修复 | **真机** | ☑ 已修复并真机验证 |
 | D1-09 | control 本地 SQLite 建表/schema 缺口(AUTOINCREMENT + 多表缺列) | 运行 | ☑ 已修复并真机验证 |
-| D1-10 | control 硬件操作不进 operation_log(oplog 埋点在 operate 的另一套栈) | 运行 | ✗ 归阶段3(两栈去重) |
+| D1-10 | control 硬件操作进 operation_log(审计埋点迁移到 control 活栈) | **真机** | ☑ 已修复并真机验证(2026-06-23) |
 
 > **2026-06-22 真机验证实测说明(D1-xx 详情见交接卡同日段)**:
 > - **D1-01/D1-05 真实代码实跑(非机制层)**:编译运行 operate 端真实 `ControlProcessLauncher.EnsureRunning`(集成测试 harness `临时文件/LauncherTest`,`<Compile Include>` 链入真文件):① control 未运行时 `IsControlAlive=False → 已拉起 control.exe → 轮询就绪 → True`(拉起路径);② control 已运行时 `IsControlAlive=True → "已在运行,直接连接" → True、PID 不变、进程数恒 1`(复用路径)。harness(operate 代理)退出后 control 仍在 = **续命**亦由真实拉起路径实证。**唯一未跑** = operate WPF 外壳(登录窗→MainWindow),因僵尸 20268 占 operate 单实例 Mutex,但其调用的拉起逻辑已实跑验证。
@@ -29,6 +29,13 @@
 > - ✗ **D1-10 oplog 缺口(归阶段3,非闭环阻塞)**:核查发现全 `OperationLogger` 埋点都在 **operate 侧**(ivf_tl_Operate + operate 的 ivf_tl_Services/HttpHelper + operate 的 ivf_tl_Entity 的 Camera/ComBin),**control 子树零埋点**。control 硬件操作的 oplog 收口本就挂在 operate 的**另一套 ComBin/Camera 栈**(=阶段3 "ComBin 两套栈去重 G1-2")——故给 ControlHost 单加 `InitOperationLog` 也不会产 oplog(control 自己串口栈无埋点)。归阶段3 两栈去重一并处理。control 诊断走 `RunRecord/HouseComRecord` 文件日志(本次定位全程靠它),业务数据入库走 MQTT,**闭环不受影响**。
 > - 旁注:`ServiceDishAndBalanceData 接口返回失败`=无皿时服务器无培养记录、回退本地,正常非缺陷。
 
+> **2026-06-23 D1-10 ☑ 已修复并真机验证(control 侧 oplog 审计埋点迁移)**:
+> - **缺口**:拆分后真正驱动硬件的是 control,但 control 子树**零 OperationLogger 埋点**(埋点都在 operate 已不驱动硬件的另一套 ComBin/Camera 死栈)→ 设备对培养环境的真实物理动作**审计缺失**(医疗合规级)。与 D3-04"删 operate 死栈"**解耦**:本轮只做**新增审计埋点**(纯加、不改串口字节/时序、不碰电机),删死栈仍属 D3-04 延后。
+> - **改动(8 改 + 1 新增,纯新增埋点)**:① 4 个 control 程序集(ControlHost/ivf_tl_Com/ivf_tl_CameraHelper/IvfTl.Hardware)加 `Aivfo.OperationLog` ProjectReference;② `ControlHost/Program.cs` 加 `InitControlOperationLog`(Project=**control**,Kafka 取 App.config kfkaIP/kfkaPort,topic=tl-oplog,ConfigFilePath=exe 同目录 oplog-config.json 热加载)+ 生命周期审计(进程启动/采集启动成功/安全停机)+ 停机 flush;③ `Camera.cs` Init/UnInit 边界埋点(镜像 operate 已验 P3b,module=相机);④ `HouseBin.cs` 换气(AirSwapOldFun)/补气(AerationNew)业务动作埋点(每会话一行,**远离串口字节循环**,module=换气/补气);⑤ `SerialChannelImpl.cs` 5 个 EEPROM 写埋点(写排气阀/进气阀/灯光/扫描间隔/水平well位置,日志置 `_ioLock` 外,module=写EEPROM);⑥ 新增 `ivf_tl_ControlHost/oplog-config.json`(随 exe 部署,按模块开关)。全部 `try{}catch{}` 兜底,绝不抛进采集循环。
+> - **真机 red→green(108 oplog 管道在线)**:RED 基线 `operation_log` `project=control`=**0**(operate=883,证管道通);改后提权启动 control(pid=6488,tlSn=NEO-1-20230411,started:true,MQTT 连、舱7/9 真温 37℃)→ GREEN:`project=control` 出现 **70 行**——`生命周期`(进程启动/采集启动成功 `{"started":true}`/安全停机,flush 在退出前落库)+ `相机`68 行(结构化 `input={"index":5,"width":1600,"height":1200,"exposure":400}` `output={"result":0}`)。`/shutdown` 口令 tl13579 安全停机产"安全停机"行 + 7 COM 口释放。oplog-error 日志仅"配置已热加载"无错。
+> - **未在本窗口触发(非缺陷,机制已证)**:换气/补气/写EEPROM 埋点已编译验证 + 用与相机**同一已证 OperationLogger.Log 机制**,但 `HouseBin:670` 注释"舱有培养记录才换气/拍照/对焦"——当前**无皿空闲态**不触发换气循环,写EEPROM 需调试页接通(D2-02);放皿培养 / 调试接通时即产行。
+> - **核实**:RED(control=0)/GREEN(control=70,生命周期+相机)真机直查 108 库;control sln(ControlHost)+ operate Release **双编译 0 错**;既有 40 单测过;codegraph sync 已跑;harness 在 gitignore `临时文件/`。
+
 ## 阶段2 · 监控补全 + 调试借串口 + 受护栏停止
 
 | 编号 | 验证项 | 门控 | 状态 |

+ 8 - 7
项目文档/进度/进度数据.js

@@ -1,10 +1,10 @@
 // 实时面板数据源(监控面板.html 读 window.PROGRESS_DATA)。每推进一步更新本文件。
 window.PROGRESS_DATA = {
   project: "operate/control 双进程拆分",
-  generatedAt: "2026-06-23 02:10",
-  phase: "三阶段主体完成;合并遗留M区 M-01~M-07 全部闭合(M-04存图代码定论 + M-05帧长回归 + M-06按well零点本轮完成)",
-  currentTask: "M-04:CameraImpl.SavePic丢w/h转发Camera.SaveBmpPic(24bpp+原生尺寸+RotateNoneFlipY),朝向与权威af ImageConverter逐字一致、width1600 stride安全→代码层定论存图等价。合并遗留M区M-01~M-07全闭合。",
-  note: "operate/control双进程拆分三阶段主体完成。合并遗留M区 M-01~M-07 全部闭合:M-01/02/03(E方builder去桩)+M-04(存图代码定论:24bpp/原生尺寸/canonical RotateNoneFlipY,同权威af ImageConverter,与不透明native厂商编码器逐字节一致不可能也无必要)+M-05(0x12写E方帧长合并回归6→12,真机24/24干净)+M-06(ReadWellFocusZero按well读,集成red→green真机)+M-07(Release网关)。仅剩延后专项(均超无监督范围):D2-02调试页借串口命令代理设计/D3-04 ComBin两栈去重风险重构/整机自启复测需重启。",
+  generatedAt: "2026-06-23 11:55",
+  phase: "三阶段主体完成;合并遗留M区 M-01~M-07 全闭合;D1-10 control侧oplog审计埋点已迁移+真机red→green验证",
+  currentTask: "D1-10 control侧oplog审计埋点迁移(=昨日建议第3条,与D3-04删死栈解耦先做):4 control程序集引Aivfo.OperationLog;ControlHost InitOperationLog(Project=control)+生命周期审计+停机flush;相机Init/UnInit、HouseBin换气/补气、SerialChannelImpl 5个EEPROM写埋点(全try兜底·不碰串口字节/电机)。真机RED(control=0)→GREEN(control=70:生命周期3+相机68)。",
+  note: "D1-10已闭合:control子树原零OperationLogger埋点(埋点都在operate不驱动硬件的死栈)→设备物理动作审计缺失(合规级)。本轮纯新增埋点(不改串口字节/时序/电机),与删operate死栈(D3-04)解耦。真机red→green:108 oplog管道在线,RED基线operation_log project=control=0(operate=883证管道通)→提权启control(pid6488/started:true/MQTT连/舱7-9真温37℃)→GREEN project=control=70行(生命周期:进程启动/采集启动成功/安全停机flush;相机68结构化input/output)。/shutdown口令tl13579安全停机+7COM释放。换气/补气/写EEPROM埋点编译验证+同已证机制,放皿培养/调试接通时触发(HouseBin:670舱有培养记录才换气)。双编译0错+40单测过。剩延后专项:D3-04删operate死栈(有风险删除)/D2-02命令代理/整机自启复测需重启;之后回昨日建议(看门狗/HIL套件/配置收敛/验证清零)。",
   milestones: [
     { name: "阶段1 · control 独立进程骨架(完成)", tasks: [
       { id: "Task1-7", name: "全过+D1-08死锁修复+operate真外壳E2E+数据入库DB铁证", status: "☑" }
@@ -18,7 +18,8 @@ window.PROGRESS_DATA = {
       { id: "D3-01", name: "退役删ivf_tl_ControlTest(两编译0错)", status: "☑" },
       { id: "D3-03", name: "双进程部署指南+布局E2E验", status: "☑" },
       { id: "D3-02", name: "开机自启注册表方案验(整机复测需重启)", status: "◑" },
-      { id: "D3-04", name: "ComBin两栈去重(含D1-10 oplog)延后专项", status: "✗" }
+      { id: "D1-10", name: "control oplog审计埋点迁移到活栈(project=control真机入库)", status: "☑" },
+      { id: "D3-04", name: "删operate死串口栈(去重·有风险删除)延后专项", status: "✗" }
     ]}
   ],
   pending: [
@@ -30,7 +31,7 @@ window.PROGRESS_DATA = {
     { id: "D1-06", rel: "Task7", point: "单实例 Mutex:第二个 control 自退", env: "运行", risk: "低", status: "☑" },
     { id: "D1-07", rel: "Task7", point: "完整闭环+数据入库(house_collect 37℃真温/alarm_data 落108库)", env: "真机", risk: "高", status: "☑DB实证" },
     { id: "D1-08", rel: "合并遗留", point: "serialBin/HAL借用ComBin重开不复活发送线程致握手死锁 修复", env: "真机", risk: "高", status: "☑已修复验证" },
-    { id: "D1-09", rel: "合并遗留", point: "control本地SQLite InitTables AUTOINCREMENT建表失败(不阻塞)", env: "运行", risk: "中", status: "✗待修" },
-    { id: "D1-10", rel: "阶段3", point: "control硬件操作不进operation_log(埋点在operate另一套栈,归两栈去重)", env: "运行", risk: "低", status: "✗归阶段3" }
+    { id: "D1-09", rel: "合并遗留", point: "control本地SQLite InitTables AUTOINCREMENT建表失败(不阻塞)", env: "运行", risk: "中", status: "☑已修复" },
+    { id: "D1-10", rel: "阶段3", point: "control硬件操作进operation_log(审计埋点迁移到control活栈,project=control真机70行)", env: "真机", risk: "低", status: "☑已修复验证" }
   ]
 };

+ 8 - 6
项目文档/进度/进度状态.yaml

@@ -1,12 +1,14 @@
 # 续接断点状态(机器可解析)。换会话/换电脑后首先读它定位。
 # 状态取值: 未开始 / 进行中 / 完成 / 代码完成待验证
 # 纪律:本字段只存【当前断点】,历史细节进 交接卡.md(见 CLAUDE.md 第三节)。
-更新时间: 2026-06-23 M-04 存图代码层定论等价(SaveBmpPic=24bpp+原生尺寸+RotateNoneFlipY,朝向同权威 af ImageConverter,width1600 stride安全)→ 合并遗留 M 区 M-01~M-07 全闭合。仅剩延后专项(D2-02命令代理/D3-04两栈去重/整机自启复测需重启)
+更新时间: 2026-06-23 D1-10 control 侧 oplog 审计埋点迁移完成并真机 red→green 验证(project=control 0→70 行:生命周期+相机 68;换气/补气/写EEPROM 埋点已编译+同机制,放皿/调试接通时触发)。当前无 control 在跑(已安全停机)、无活体培养
 当前任务: >
-  【合并遗留 M 区 M-01~M-07 全部闭合】
-  · M-01/02/03(E方builder去桩)+ M-04(存图代码定论)+ M-05(0x12帧长合并回归)+ M-06(按well焦点零点)+ M-07(Release网关)全部修复/定论并(真机/代码)验证。
-  · M-04:CameraImpl.SavePic丢w/h转发Camera.SaveBmpPic(24bpp+原生尺寸+RotateNoneFlipY),朝向与权威af ImageConverter逐字一致;width1600→1600*3=4800四字节对齐整块拷贝无错行;与不透明native厂商编码器逐字节一致不可能也无必要(调试产物)。
-  · 下一步:仅剩延后专项,均超无监督范围——D2-02(调试页借串口需命令代理大改设计)/D3-04(ComBin两栈去重风险重构)/整机开机自启复测(需真重启中断在跑control)。
+  【D1-10 control 侧 oplog 审计埋点迁移 ☑ 完成】(= 昨日建议第3条 control 审计埋点,与 D3-04 删死栈解耦先做)
+  · 4 control 程序集引 Aivfo.OperationLog;ControlHost InitOperationLog(Project=control)+生命周期审计+停机flush;
+    相机Init/UnInit、HouseBin换气/补气、SerialChannelImpl 5个EEPROM写 埋点(全 try 兜底、不碰串口字节/电机)。
+  · 真机 RED(control=0)→GREEN(control=70:生命周期3+相机68);双编译0错;40单测过。
+  · 下一步:按工作计划续做剩余延后专项——D3-04 删 operate 死串口栈(有风险删除,谨慎评估)、D2-02 调试页借串口命令代理;
+    之后再回到昨日建议(看门狗/HIL回归套件/配置收敛/验证清零)。
 说明: >
   operate/control 双进程拆分三阶段主体早已完成;合并遗留 M 区 M-01~M-07 本轮全部闭合
   (M-01/02/03 builder去桩、M-04 存图代码定论、M-05 0x12帧长回归、M-06 按well焦点零点、M-07 网关)。
@@ -32,4 +34,4 @@
     名称: 清理老壳 + 装机收尾
     状态: 未开始
     备注: "退役删ivf_tl_ControlTest脏壳 + operate开机自启 + ComBin两套栈去重(G1-2) + 部署文档。待阶段2完成后拆计划"
-下一步: 合并遗留 M 区 M-01~M-07 全闭合;仅剩延后专项(均超无监督自主范围):D2-02 调试页借串口完整驱动(需设计串口/相机命令代理大改面)、D3-04 ComBin 两栈去重(有风险重构)、整机开机自启复测(需真重启中断在跑 control)。建议按用户优先级开专项
+下一步: D1-10 control oplog 审计埋点已完成并真机验证。按工作计划续做剩余延后专项:D3-04 删 operate 死串口栈(有风险删除,谨慎编译+运行双验)、D2-02 调试页借串口命令代理(需设计命令代理面,涉电机在安全区间自驱);之后回到昨日建议(看门狗/HIL套件/配置收敛/验证清零)