Explorar o código

fix(merge): 修复合并遗留 M-01/M-02/M-03 — control 补 3 个 E方 builder 去桩(TDD+真机验证)

根因:合并阶段 control 端 ivf_tl_SerialHelper/Commander 未搬运 3 条 E方命令,
SerialChannelImpl 对应方法返桩(false/-1),写排气阀时间/读排气阀时间/写灯光亮度未真下发下位机。

改动(TDD red→green):
- 新建 ivf_tl_SerialHelper.Tests(net6.0 xUnit,入 control sln):4 单测断言 3 builder 精确字节
  (期望=合并前 operate 黄金真值 + CreateORC 校验 + 写排气阀vs写进气阀交叉校验)。先 RED 后 GREEN。
- Commander 补 CreateWriteEEPROOpenVentTimeCommand(00 03 08)/CreateReadEEPROMVentNum/CreateWriteEEPROMLightNum(00 05 34)。
- Enums 补 ReadEEPROOpenVentTime/WriteEEPROOpenVentTime/WriteLightNum。
- ComBin 补 WriteEEPROOpenVentTimeWait/ReadEEPROMVentWait/WriteEEPROMLightNumWait(镜像既有 intake/light)。
- SerialChannelImpl 三方法去桩调真实 _com.*;operate 消费方(HouseDebugPageViewModel/BufferDebugViewModel)注释/提示更新。

真机验证(非破坏性回环,经真实 SerialChannelImpl,EEPROM 读写无电机风险):
- 舱9 排气阀 200→201→200 ✓、舱8 排气阀 90→91→90 ✓(M-01写/M-02读)、舱8 灯光 500→501→500 ✓(M-03写)。
- 旁注:无排空时写后读偶现帧错位(=既有 M-05,非本次字节错误),加间隔后 100% 干净。

control sln + operate Release 双编译 0 错;4 单测全绿。回写 待验证清单/进度状态.yaml/交接卡/进度数据.js/操作端全景§八。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie hai 2 días
pai
achega
f80c74f636

+ 8 - 7
ivf_tl_operate_2.0/control/IvfTl.Hardware/Impl/SerialChannelImpl.cs

@@ -129,21 +129,22 @@ namespace IvfTl.Hardware.Impl
 
         public bool WriteOpenVentTimeWait(int newValue)
         {
-            // control Commander 缺 CreateWriteEEPROOpenVentTimeCommand(T1.1 核实);不臆造字节 → 不下发。
-            // 返回 false:调试页据此保留旧路径并标注待真机(13 §⑤ / V-010)。
-            return false;
+            // M-01 已补:control Commander/ComBin 补齐 CreateWriteEEPROOpenVentTimeCommand + WriteEEPROOpenVentTimeWait
+            // (builder 地址 00 03 08,与合并前 operate 逐字节一致)。写命令旧栈无显式成功出口(void),
+            // 阻塞收到回复即视为已下发,返回 true。
+            lock (_ioLock) { _com.WriteEEPROOpenVentTimeWait(new CustomProtocol(), newValue); return true; }
         }
 
         public int ReadOpenVentTimeWait()
         {
-            // control Commander 缺 CreateReadEEPROMVentNum;不臆造 → 返回 -1(待真机)
-            return -1;
+            // M-02 已补:control 补齐 CreateReadEEPROMVentNum + ReadEEPROMVentWait(地址 00 03 08)。失败返回 -1
+            lock (_ioLock) { return _com.ReadEEPROMVentWait(new CustomProtocol()); }
         }
 
         public bool WriteLightBrightnessWait(int newValue)
         {
-            // control Commander 缺 CreateWriteEEPROMLightNum(仅有读 CreateReadEEPROMLightNum);不臆造 → 不下发
-            return false;
+            // M-03 已补:control 补齐 CreateWriteEEPROMLightNum + WriteEEPROMLightNumWait(地址 00 05 34)
+            lock (_ioLock) { _com.WriteEEPROMLightNumWait(new CustomProtocol(), newValue); return true; }
         }
 
         // ── 电机:垂直 ──

+ 10 - 0
ivf_tl_operate_2.0/control/ivf_tl_Control.sln

@@ -29,6 +29,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IvfTl.AutoFocus", "IvfTl.Au
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ivf_tl_ControlHost", "ivf_tl_ControlHost\ivf_tl_ControlHost.csproj", "{FB13358D-DA10-43F1-9F09-2A1F90EDB3B7}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ivf_tl_SerialHelper.Tests", "ivf_tl_SerialHelper.Tests\ivf_tl_SerialHelper.Tests.csproj", "{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -133,6 +135,14 @@ Global
 		{FB13358D-DA10-43F1-9F09-2A1F90EDB3B7}.Release|Any CPU.Build.0 = Release|Any CPU
 		{FB13358D-DA10-43F1-9F09-2A1F90EDB3B7}.Release|x64.ActiveCfg = Release|x64
 		{FB13358D-DA10-43F1-9F09-2A1F90EDB3B7}.Release|x64.Build.0 = Release|x64
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Debug|x64.Build.0 = Debug|Any CPU
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Release|x64.ActiveCfg = Release|Any CPU
+		{635D3540-E4CC-429A-B6B1-DF3BBF125F1C}.Release|x64.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 15 - 0
ivf_tl_operate_2.0/control/ivf_tl_Entity/GlobalEnums/Enums.cs

@@ -197,6 +197,21 @@ namespace IvfTl.Control.Entity.GlobalEnums
         /// </summary>
         WriteEEPROOpenIntakeTime,
 
+        /// <summary>
+        /// 读排气阀打开时间(M-02)
+        /// </summary>
+        ReadEEPROOpenVentTime,
+
+        /// <summary>
+        /// 写排气阀打开时间(M-01)
+        /// </summary>
+        WriteEEPROOpenVentTime,
+
+        /// <summary>
+        /// 写灯光亮度(M-03)
+        /// </summary>
+        WriteLightNum,
+
         /// <summary>
         /// 读垂直电机焦准脉冲数(零点)
         /// </summary>

+ 74 - 0
ivf_tl_operate_2.0/control/ivf_tl_SerialHelper.Tests/CommanderEepromTests.cs

@@ -0,0 +1,74 @@
+using IvfTl.Control.Entity;
+using ivf_tl_SerialHelper.Util;
+using Xunit;
+
+namespace ivf_tl_SerialHelper.Tests
+{
+    /// <summary>
+    /// M-01/M-02/M-03 合并遗留:control 端 Commander 缺 3 个 E方读写 builder。
+    /// 期望字节 = 合并前 operate 端 ivf_tl_Entity/ComEntitys/Commander.cs 同名方法的黄金真值
+    /// (临时文件/ivf_tl_operate_2.0,该实现真机已用),末位为 CreateORC 累加和校验(对前 n-1 字节求和取低字节)。
+    /// 字节编码是纯逻辑,这里 red→green;ComBin/SerialChannelImpl 接线 + EEPROM 读写往返走真机验证。
+    /// </summary>
+    public class CommanderEepromTests
+    {
+        /// <summary>M-02 读E方→舱室排气阀打开时间。地址 00 03 08,固定 9 字节读命令。</summary>
+        [Fact]
+        public void CreateReadEEPROMVentNum_产出与operate一致的读排气阀时间命令字节()
+        {
+            var custom = new CustomProtocol();
+            custom.CreateReadEEPROMVentNum();
+
+            // 5E 11(READ_EEPROM) 00 09(LTH) 00 03 08(地址) 04 00 → ORC=前8字节和=0x87
+            var expected = new byte[] { 0x5E, 0x11, 0x00, 0x09, 0x00, 0x03, 0x08, 0x04, 0x87 };
+            Assert.Equal(expected, custom.sendBuffer);
+        }
+
+        /// <summary>M-01 写E方→舱室排气阀打开时间。地址 00 03 08 + 小端 4 字节值 + 校验。</summary>
+        [Fact]
+        public void CreateWriteEEPROOpenVentTimeCommand_产出与operate一致的写排气阀时间命令字节()
+        {
+            var custom = new CustomProtocol();
+            custom.CreateWriteEEPROOpenVentTimeCommand(5);
+
+            // 5E 12(WRITE_EEPROM) 00 0C(LTH) 00 03 08(地址) 05 00 00 00(小端5) 00→ORC=前11字节和=0x8C
+            var expected = new byte[] { 0x5E, 0x12, 0x00, 0x0C, 0x00, 0x03, 0x08, 0x05, 0x00, 0x00, 0x00, 0x8C };
+            Assert.Equal(expected, custom.sendBuffer);
+        }
+
+        /// <summary>M-03 写E方→灯光亮度。地址 00 05 34 + 小端 4 字节值 + 校验。</summary>
+        [Fact]
+        public void CreateWriteEEPROMLightNum_产出与operate一致的写灯光亮度命令字节()
+        {
+            var custom = new CustomProtocol();
+            custom.CreateWriteEEPROMLightNum(80);
+
+            // 5E 12(WRITE_EEPROM) 00 0C(LTH) 00 05 34(地址) 50 00 00 00(小端80=0x50) 00→ORC=前11字节和=0x105&0xFF=0x05
+            var expected = new byte[] { 0x5E, 0x12, 0x00, 0x0C, 0x00, 0x05, 0x34, 0x50, 0x00, 0x00, 0x00, 0x05 };
+            Assert.Equal(expected, custom.sendBuffer);
+        }
+
+        /// <summary>
+        /// 交叉校验:写排气阀时间 与 control 已通过的写进气阀时间(地址 00 03 0c)仅"地址低字节"不同(0x08 vs 0x0c)。
+        /// 锚定到既有已验证 builder,防止地址表整体写错。
+        /// </summary>
+        [Fact]
+        public void 写排气阀与写进气阀命令仅地址低字节不同()
+        {
+            var vent = new CustomProtocol();
+            vent.CreateWriteEEPROOpenVentTimeCommand(5);
+            var intake = new CustomProtocol();
+            intake.CreateWriteEEPROOpenIntakeTimeCommand(5);
+
+            Assert.Equal(intake.sendBuffer.Length, vent.sendBuffer.Length);
+            Assert.Equal((byte)0x0c, intake.sendBuffer[6]); // 进气阀地址低字节
+            Assert.Equal((byte)0x08, vent.sendBuffer[6]);   // 排气阀地址低字节
+            // 除地址低字节(idx6)与末位校验外,其余字节应完全相同
+            for (int i = 0; i < vent.sendBuffer.Length - 1; i++)
+            {
+                if (i == 6) continue;
+                Assert.Equal(intake.sendBuffer[i], vent.sendBuffer[i]);
+            }
+        }
+    }
+}

+ 15 - 0
ivf_tl_operate_2.0/control/ivf_tl_SerialHelper.Tests/ivf_tl_SerialHelper.Tests.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net6.0-windows</TargetFramework>
+    <Nullable>disable</Nullable>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
+    <PackageReference Include="xunit" Version="2.4.2" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ivf_tl_SerialHelper\ivf_tl_SerialHelper.csproj" />
+  </ItemGroup>
+</Project>

+ 42 - 0
ivf_tl_operate_2.0/control/ivf_tl_SerialHelper/Util/ComBin.cs

@@ -1044,6 +1044,48 @@ namespace ivf_tl_SerialHelper.Util
             taskAutoResetEvent.WaitOne();
             return;
         }
+
+        // ── M-01/M-02/M-03 补齐:排气阀时间写/读、灯光亮度写(builder 与 operate 逐字节一致)──
+
+        /// <summary>写E方→舱室排气阀打开时间(M-01,builder 地址 00 03 08,与 operate 一致)。⚠ 待真机 V-010。</summary>
+        public void WriteEEPROOpenVentTimeWait(CustomProtocol custom, int newValue)
+        {
+            custom.commandType = Enums.WriteEEPROOpenVentTime;
+            custom.logDateTime = DateTime.Now;
+            custom.commandNumber = commandNumber++;
+            custom.IsWaitOne = true;
+            custom.CreateWriteEEPROOpenVentTimeCommand(newValue);
+            Enqueue(custom);
+            taskAutoResetEvent.WaitOne();
+            return;
+        }
+
+        /// <summary>读E方→舱室排气阀打开时间(M-02,builder 地址 00 03 08)。失败返回 -1。</summary>
+        public int ReadEEPROMVentWait(CustomProtocol custom)
+        {
+            custom.commandType = Enums.ReadEEPROOpenVentTime;
+            custom.logDateTime = DateTime.Now;
+            custom.commandNumber = commandNumber++;
+            custom.IsWaitOne = true;
+            custom.CreateReadEEPROMVentNum();
+            Enqueue(custom);
+            taskAutoResetEvent.WaitOne();
+            if (!custom.IsSuccess) return -1;
+            return Analysiser.ParseEEPROMPulse(custom.receivedBuffer);
+        }
+
+        /// <summary>写E方→灯光亮度(M-03,builder 地址 00 05 34,与 operate 一致)。⚠ 待真机 V-010。</summary>
+        public void WriteEEPROMLightNumWait(CustomProtocol custom, int newValue)
+        {
+            custom.commandType = Enums.WriteLightNum;
+            custom.logDateTime = DateTime.Now;
+            custom.commandNumber = commandNumber++;
+            custom.IsWaitOne = true;
+            custom.CreateWriteEEPROMLightNum(newValue);
+            Enqueue(custom);
+            taskAutoResetEvent.WaitOne();
+            return;
+        }
         #endregion
 
         #region 调试模式

+ 82 - 0
ivf_tl_operate_2.0/control/ivf_tl_SerialHelper/Util/Commander.cs

@@ -840,6 +840,88 @@ namespace ivf_tl_SerialHelper.Util
             CustomProtocolLength(custom);
         }
 
+        // ── M-01/M-02/M-03 合并遗留补齐:写/读舱室排气阀打开时间、写灯光亮度 ──
+        // 字节与合并前 operate 端 ivf_tl_Entity/ComEntitys/Commander.cs 同名方法逐字一致(地址表/小端/CreateORC),
+        // 该实现真机已用;补此三 builder 以解除 SerialChannelImpl 的 M-01/M-02/M-03 桩(此前 control Commander 缺对应 builder)。
+
+        /// <summary>
+        ///  写EEPROM->舱室排气阀打开时间(地址 00 03 08,与写进气阀 00 03 0c 仅地址低字节不同)
+        /// </summary>
+        public static void CreateWriteEEPROOpenVentTimeCommand(this CustomProtocol custom, int newValue)
+        {
+            byte[] pulses = BitConverter.GetBytes(newValue);
+            List<byte> command = new List<byte>();
+            command.Add(0x5E);//帧头
+            command.Add(0x12);//WRITE_EEPROM
+            command.Add(0x00);//CMDNO
+            command.Add(0x0C);//LTH
+
+            command.Add(0x00);
+            command.Add(0x03);
+            command.Add(0x08);
+
+            if (BitConverter.IsLittleEndian)
+            {
+                command.Add(pulses[0]);//参数1(低)
+                command.Add(pulses[1]);//参数2
+                command.Add(pulses[2]);//参数3
+                command.Add(pulses[3]);//参数4(高)
+            }
+            else
+            {
+                command.Add(pulses[3]);//参数1(低)
+                command.Add(pulses[2]);//参数2
+                command.Add(pulses[1]);//参数3
+                command.Add(pulses[0]);//参数4(高)
+            }
+            command.Add(0x00);//校验和CRC
+            custom.sendBuffer = CreateORC(command.ToArray());
+            CustomProtocolLength(custom);
+        }
+
+        /// <summary>
+        /// 读E方 -->舱室排气阀打开时间(地址 00 03 08)
+        /// </summary>
+        public static void CreateReadEEPROMVentNum(this CustomProtocol custom)
+        {
+            var array = new byte[] { 0x5E, 0x11, 0x00, 0x09, 0x00, 0x03, 0x08, 0x04, 0x00 };
+            custom.sendBuffer = CreateORC(array);
+            CustomProtocolLength(custom);
+        }
+
+        /// <summary>
+        /// 写E方 -->灯光亮度(地址 00 05 34,与读灯光亮度同地址)
+        /// </summary>
+        public static void CreateWriteEEPROMLightNum(this CustomProtocol custom, int num)
+        {
+            byte[] newValue = BitConverter.GetBytes(num);
+            List<byte> command = new List<byte>();
+            command.Add(0x5E);//帧头
+            command.Add(0x12);//WRITE_EEPROM
+            command.Add(0x00);//CMDNO
+            command.Add(0x0C);//LTH
+            command.Add(0x00);//地址(高)
+            command.Add(0x05);//地址(中)
+            command.Add(0x34);//地址(低)
+            if (BitConverter.IsLittleEndian)
+            {
+                command.Add(newValue[0]);//参数1(低)
+                command.Add(newValue[1]);//参数2
+                command.Add(newValue[2]);//参数3
+                command.Add(newValue[3]);//参数4(高)
+            }
+            else
+            {
+                command.Add(newValue[3]);//参数1(低)
+                command.Add(newValue[2]);//参数2
+                command.Add(newValue[1]);//参数3
+                command.Add(newValue[0]);//参数4(高)
+            }
+            command.Add(0x00);//校验和CRC
+            custom.sendBuffer = CreateORC(command.ToArray());
+            CustomProtocolLength(custom);
+        }
+
         /// <summary>
         /// 读EEPROM->CCDSN
         /// </summary>

+ 3 - 3
ivf_tl_operate_2.0/ivf_tl_Operate/ViewModel/BufferDebugViewModel.cs

@@ -268,15 +268,15 @@ namespace ivf_tl_Operate.ViewModel
         public void writeL(int newValue)
         {
             if (Serial == null) return;
-            // ⚠ M1-B2:control Commander 缺 CreateWriteEEPROMLightNum(T1.1 核实),不臆造字节 → 暂不下发到下位机。
-            // 待真机核对字节后在 control Commander 补 builder 再接入;当前仅本地+服务器记录,未真正写灯光EEPROM
+            // M-03 已补:control 端补齐写灯光亮度 E方命令(builder 地址 00 05 34,与合并前 operate 逐字节一致),
+            // 经 lease.Serial→control ComBin 真正写灯光 EEPROM;同时仍记录服务器
             // M8-G3-1:设置灯光亮度为"写 EEPROM 串口命令 + HTTP 持久化"打包操作,用 Begin scope 给父操作名串联子埋点(module=缓冲瓶调试)。
             using var _op = Aivfo.OperationLog.OperationLogger.Begin("缓冲瓶调试", "保存缓冲瓶灯光亮度",
                 houseSn: CurrentHouse?.houseSn);
             try { _op.Input(new { houseSn = CurrentHouse?.houseSn, newValue }); } catch { }
             if (!Serial.WriteLightBrightnessWait(newValue))
             {
-                AddMessageInfo($"[灯光亮度]control 端暂缺写E方命令(待真机补字节),本次未下发到下位机,仅本地暂存{newValue}");
+                AddMessageInfo($"[灯光亮度]下发失败,仅本地暂存{newValue}");
             }
             LedLight = newValue;
             AddMessageInfo($"灯光亮度保存服务器结果:{AppData.Instance.HttpHelper.LightsUpdateApi(tLSetting.tlSn, LedLight, CurrentHouse.inletValveOpeningTime)}");

+ 4 - 4
ivf_tl_operate_2.0/ivf_tl_Operate/ViewModel/HouseDebugPageViewModel.cs

@@ -534,15 +534,15 @@ namespace ivf_tl_Operate.ViewModel
         public void WriteOpenVentTime(int newValue)
         {
             if (Serial == null) return;
-            // ⚠ M1-B2:control Commander 缺 CreateWriteEEPROOpenVentTimeCommand(T1.1 核实),不臆造字节 → 暂不下发。
-            // 待真机核对字节后在 control Commander 补 builder 再接入。当前仅本地回显,未真正写入下位机。
+            // M-01 已补:control 端补齐写排气阀时间 E方命令(builder 地址 00 03 08,与合并前 operate 逐字节一致),
+            // 经 lease.Serial→control ComBin 真正下发下位机。
             // M8-G3-1:写舱室排气阀时间为写 EEPROM 设备命令入口,补操作日志埋点(module=对焦调试)。
             var written = Aivfo.OperationLog.OperationLogger.Run("对焦调试", "写舱室排气阀时间",
                 () => Serial.WriteOpenVentTimeWait(newValue),
                 input: new { houseSn = CurrentHouseId, newValue });
             if (!written)
             {
-                AddMessageInfo($"[排气阀时间]control 端暂缺写E方命令(待真机补字节),本次未下发到下位机,仅本地暂存{newValue}");
+                AddMessageInfo($"[排气阀时间]下发失败,仅本地暂存{newValue}");
             }
             HouseVentTimeE = newValue;
         }
@@ -553,7 +553,7 @@ namespace ivf_tl_Operate.ViewModel
         public void RedVentTime()
         {
             if (Serial == null) return;
-            // ⚠ M1-B2:control 缺读排气阀时间命令(待真机);读不到则保持原值不覆盖。
+            // M-02 已补:control 端补齐读排气阀时间 E方命令(地址 00 03 08);读不到(-1)则保持原值不覆盖。
             int v = Serial.ReadOpenVentTimeWait();
             if (v >= 0) HouseVentTimeE = v;
         }

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

@@ -216,3 +216,19 @@
 - **改动**:把全库"真机门控须用户在场 / 须用户在场连硬件 / 待用户在场 / 绝不无人值守驱动电机"的旧红线统一改为上述口径,共 6 文件 11 处:`待验证清单.md`(红线)、`specs/...双进程拆分-design.md`(§8 计划头 + §9.5 真机门控)、`开发计划/...阶段1...md`(关键现实约束 + Task7 头)、`项目背景与上手指南.md`(开工方式)、`工作计划表.md`(执行方式)、`系统业务流程详图.html`(§13 降级登记 + §14 路线图 lead + §14.1 阶段1说明 + 开工 li)。
 - **不覆盖历史**:本卡早前 21/38/167 行的"待用户在场/需用户在场"是当时会话的过往记录(且已被后续"自主完成"段落推翻),按回写协议 §3.2 只追加不覆盖,故保留原样,以本条为现行口径。
 - **下一步**:无新增。延后专项不变。
+
+---
+
+## 2026-06-23 · M-01/M-02/M-03 合并遗留降级修复(TDD red→green + 真机非破坏性回环验证)
+
+- **背景**:接 /goal 用 TDD 续做剩余工作。上次会话最后一项做到一半未回写——核查 git 工作区干净(无丢失代码),定位"半成品"是在调查 M 区。M-01/M-02/M-03 是合并阶段"做了一半、未真机验收"的真实功能降级,根因统一在 control 端 Commander 缺 builder、`SerialChannelImpl` 返桩,最适合 TDD(字节编码纯逻辑)。
+- **根因(逐文件坐实)**:合并前 operate(`临时文件/ivf_tl_operate_2.0/ivf_tl_Entity/ComEntitys/Commander.cs`)有 3 条 E方命令真机已用,合并时 **control 端 `ivf_tl_SerialHelper/Util/Commander.cs` 未搬运**:`CreateWriteEEPROOpenVentTimeCommand`(写排气阀时间,地址 00 03 08)、`CreateReadEEPROMVentNum`(读排气阀时间)、`CreateWriteEEPROMLightNum`(写灯光亮度,地址 00 05 34)。故 `SerialChannelImpl.WriteOpenVentTimeWait`/`ReadOpenVentTimeWait`/`WriteLightBrightnessWait` 只能返桩(false/-1),调试页写排气阀时间/读排气阀时间/缓冲瓶写灯光未真正下发下位机。
+- **TDD(改动)**:
+  - **RED**:新建 `ivf_tl_operate_2.0/control/ivf_tl_SerialHelper.Tests`(net6.0-windows xUnit,ProjectReference SerialHelper,已 `dotnet sln add` 入 control sln)+ `CommanderEepromTests.cs` 4 测试,断言 3 个 builder 的精确字节(期望=合并前 operate 黄金真值,末位 CreateORC 累加和校验我手算 0x87/0x8C/0x05)+ 1 条交叉校验(写排气阀 vs 已通过的写进气阀仅地址低字节 0x08/0x0c 不同)。`dotnet test` → CS1061 方法不存在 = RED(feature missing)。
+  - **GREEN**:`Commander.cs` 补 3 个 builder(逐字抄合并前 operate);`dotnet test` → **4 单测全绿**(手算校验位全对)。
+  - **接线**:`Enums.cs` 补 `ReadEEPROOpenVentTime`/`WriteEEPROOpenVentTime`/`WriteLightNum`;`ComBin.cs` 补 `WriteEEPROOpenVentTimeWait`/`ReadEEPROMVentWait`/`WriteEEPROMLightNumWait`(镜像既有 intake/light 的 commandType→Create*→Enqueue→WaitOne 模式);`SerialChannelImpl.cs` 三方法去桩调真实 `_com.*`;operate 消费方 `HouseDebugPageViewModel.WriteOpenVentTime/RedVentTime`、`BufferDebugViewModel.writeL` 的过时注释/兜底消息("control 端暂缺写E方命令")更新为"已补、真下发"。control sln(IvfTl.Hardware)+ operate Release 双编译 **0 错误**。
+- **真机验证(Claude 自主,UAC 静默提权;EEPROM 读写无电机风险,符合新口径)**:写一次性 harness `临时文件/EepromVerify`(console,ProjectReference IvfTl.Hardware,经**真实 `SerialChannelImpl`** 端到端,非走单测桩),对真机做**非破坏性回环**:读 V→写 V+1→读确认→写回 V→读确认。control 未跑时端口空闲、harness 直接 Open+ShakeHands(复刻 HAL.ScanDevices 模式)。
+  - **结果(加读前丢弃一帧+700ms 间隔后 100% 确定性干净)**:**舱9 排气阀** 200→写201读**201**→写回读**200** ✓;**舱8 排气阀** 90→91→90 ✓(M-01 写/M-02 读);**舱8 灯光** 500→501→500 ✓(M-03 写)。写入值真机读回随之变化且恢复原值 → 3 条新命令字节正确、真下发下位机。
+  - **旁注(顺带印证 M-05)**:无排空时"写后紧接读"偶现垃圾值/null=0x12 写回包长度(CustomProtocolLength 设 6)与 0x11 读(10)不同致残留字节帧错位,污染下一次读——这是既有 **M-05**"写EEPROM 成功判定/回包"可靠性问题,**非本次新命令字节错误**(字节错则干净回环不可能出现精确写入值)。加间隔即消失。M-05 帧错位/写成功校验列后续专项。
+- **核实**:4 单测真跑 red→green;harness 真编真跑经真实 SerialChannelImpl;两舱排气阀+一舱灯光回环实测、值已恢复;control+operate 双编译 0 错;`临时文件/` 已 gitignore(harness 不入库);codegraph sync 已跑。
+- **下一步**:提交(代码+测试+文档同步)。剩 M-04(调试页存图核对)/M-05(写成功判定·帧错位,本轮已定位现象)/M-06(ReadWellFocusZero 按 well)/M-07(Release 网关核对)+ 延后专项(D2-02 调试页借串口命令代理/D3-04 ComBin 两栈去重/整机开机自启复测需重启)。

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

@@ -66,10 +66,17 @@
 
 | 编号 | 验证项 | 现状(file:line) | 门控 | 状态 |
 |------|--------|------|------|------|
-| M-01 | 舱室排气阀时间**写** EEPROM 真下发下位机 | `SerialChannelImpl.cs:130` return false 不下发,仅本地暂存(HouseDebug:534);需补 control `CreateWriteEEPROOpenVentTimeCommand` + 真机核对字节(源码注释挂 V-010) | **真机** | ▼ |
-| M-02 | 舱室排气阀时间**读** 真读下位机 | `SerialChannelImpl.cs:137` return -1,静默保留旧值(**无 UI 提示**,最易误判正常) | **真机** | ▼ |
-| M-03 | 缓冲瓶灯光亮度**写** EEPROM 真下发 | `SerialChannelImpl.cs:143` return false,仅本地+服务器(Buffer:268);需补 control `CreateWriteEEPROMLightNum` | **真机** | ▼ |
+| M-01 | 舱室排气阀时间**写** EEPROM 真下发下位机 | `SerialChannelImpl.cs:130` 已去桩→调 `_com.WriteEEPROOpenVentTimeWait`(control 补 builder,地址 00 03 08,与 operate 逐字节一致) | **真机** | ☑ 已验通过 |
+| M-02 | 舱室排气阀时间**读** 真读下位机 | `SerialChannelImpl.cs:137` 已去桩→调 `_com.ReadEEPROMVentWait`(control 补 builder/Wait,地址 00 03 08) | **真机** | ☑ 已验通过 |
+| M-03 | 缓冲瓶灯光亮度**写** EEPROM 真下发 | `SerialChannelImpl.cs:143` 已去桩→调 `_com.WriteEEPROMLightNumWait`(control 补 builder,地址 00 05 34) | **真机** | ☑ 已验通过 |
 | M-04 | 调试页存图与基准一致 | `CameraImpl.cs:148` 丢弃宽高转 `SaveBmpPic`,自称等价**未验证**;核对落盘格式/旋转/位深(**无 UI 提示**) | **真机** | ☐ |
 | M-05 | 写 EEPROM 类"成功=true"可靠性 | `WriteWellHorizontalPos`/`WriteScanStep`/`WriteOpenIntakeTime` 阻塞收回复即 true,未校验真实成功(SerialChannelImpl.cs:106-118) | **真机** | ☐ |
 | M-06 | `ReadWellFocusZero` 按 well 区分 | control Z 对焦零点为整舱单值,`well` 入参被忽略,与 autofocus 按 well 读零点有差异(SerialChannelImpl.cs:94) | **真机** | ☐ |
 | M-07 | Release 连内网网关(非测试外网) | `AppData.cs:91-111` `#if DEBUG` 覆写 BaseUrl 到 test-gateway 外网;真机/排障必须用 Release + 现场核对 `urlIp` | 部署 | ☐ |
+
+> **2026-06-23 M-01/M-02/M-03 ☑ 已修复并真机验证(TDD)**:
+> - **根因**:合并阶段 control 端 Commander 缺 3 个 E方 builder(`CreateWriteEEPROOpenVentTimeCommand`/`CreateReadEEPROMVentNum`/`CreateWriteEEPROMLightNum`),`SerialChannelImpl` 对应方法返回桩值(false/-1),写排气阀时间/读排气阀时间/写灯光亮度未真正下发下位机。
+> - **TDD red→green**:新建 `ivf_tl_SerialHelper.Tests`(net6.0 xUnit,已入 control sln),对 3 个 builder 断言精确字节(期望=合并前 operate 同名方法黄金真值,末位 CreateORC 校验);先 RED(方法不存在 CS1061),补 builder 后 **4 单测全绿**(含"写排气阀 vs 写进气阀仅地址低字节 0x08/0x0c 不同"交叉校验)。
+> - **接线**:control `Enums` 补 3 枚举;`ComBin` 补 `WriteEEPROOpenVentTimeWait`/`ReadEEPROMVentWait`/`WriteEEPROMLightNumWait`(镜像既有 intake/light 模式);`SerialChannelImpl` 三方法去桩调真实方法;operate 消费方(`HouseDebugPageViewModel.WriteOpenVentTime/RedVentTime`、`BufferDebugViewModel.writeL`)注释/提示同步更新。control sln + operate Release 双编译 0 错。
+> - **真机验证(非破坏性回环,经真实 SerialChannelImpl 端到端,EEPROM 读写无电机风险)**:读 V→写 V+1→读确认=V+1→写回 V→读确认=V。**舱9 排气阀** 200→201→200 ✓、**舱8 排气阀** 90→91→90 ✓(M-01 写/M-02 读)、**舱8 灯光** 500→501→500 ✓(M-03 写)。写入值真机读回随之变化且已恢复原值→写命令字节正确、真下发下位机。
+> - **旁注**:无排空时"写后紧接读"偶现垃圾值/null=0x12 写回包长度与 0x11 读不同致帧错位(=既有 **M-05** 可靠性问题,非本次新命令字节错误——字节错则干净回环不可能出现写入值);加读前丢弃一帧+间隔后 100% 干净。M-05 帧错位/写成功判定列后续专项。

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

@@ -1,10 +1,10 @@
 // 实时面板数据源(监控面板.html 读 window.PROGRESS_DATA)。每推进一步更新本文件。
 window.PROGRESS_DATA = {
   project: "operate/control 双进程拆分",
-  generatedAt: "2026-06-22 23:00",
-  phase: "三阶段全推进到位(阶段1完成/阶段2三端点验/阶段3退役删+部署+自启);剩延后专项",
-  currentTask: "阶段1完成+operate真外壳E2E+数据入库;阶段2三端点真机验+operate客户端;阶段3退役删ControlTest+部署指南+开机自启方案验",
-  note: "operate/control双进程拆分三阶段主体完成+D1-09本地SQLite建表/schema缺口修复(启动0 DbException)。剩延后专项:调试页借串口命令代理(D2-02)/ComBin两栈去重(D3-04)/M区/整机自启复测(需重启)。建议feature合并main+推送。",
+  generatedAt: "2026-06-23 01:10",
+  phase: "三阶段主体完成;续做合并遗留M区——M-01/M-02/M-03(排气阀写/读、灯光写)已修复并真机验证",
+  currentTask: "M-01/02/03:control补3个E方builder+ComBin Wait+SerialChannelImpl去桩(TDD 4单测绿);真机非破坏性回环 舱9/舱8排气阀+舱8灯光 全PASS",
+  note: "operate/control双进程拆分三阶段主体完成。本轮续做合并遗留M区:M-01/M-02/M-03(写排气阀时间/读排气阀时间/写灯光亮度)三处降级已补control端builder去桩,TDD red→green 4单测+真机回环验证(写入值读回随变、已恢复原值)。剩M-04(存图)/M-05(写成功判定·帧错位,已定位现象)/M-06(按well零点)/M-07(Release网关)+延后专项(D2-02/D3-04/整机自启复测需重启)。",
   milestones: [
     { name: "阶段1 · control 独立进程骨架(完成)", tasks: [
       { id: "Task1-7", name: "全过+D1-08死锁修复+operate真外壳E2E+数据入库DB铁证", status: "☑" }

+ 9 - 11
项目文档/进度/进度状态.yaml

@@ -1,18 +1,16 @@
 # 续接断点状态(机器可解析)。换会话/换电脑后首先读它定位。
 # 状态取值: 未开始 / 进行中 / 完成 / 代码完成待验证
 # 纪律:本字段只存【当前断点】,历史细节进 交接卡.md(见 CLAUDE.md 第三节)。
-更新时间: 2026-06-22 三阶段全推进到位:阶段1完成(含operate真外壳E2E+数据入库)、阶段2三端点真机验+operate客户端、阶段3退役删ControlTest+部署指南+开机自启方案验。剩延后专项(调试页借串口命令代理/ComBin两栈去重/D1-09/M区/整机自启复测需重启)。
+更新时间: 2026-06-23 M-01/M-02/M-03 合并遗留降级修复(control 补 3 个 E方 builder + 去桩),TDD red→green 4 单测 + 真机非破坏性回环验证(排气阀写/读、灯光写 全 PASS)。
 当前任务: >
-  【三阶段全部推进到位;剩有依赖/受门控/有风险的延后专项】
-  · 阶段1 完成:Task1-7全过+D1-08死锁修复+operate真外壳E2E(真operate→拉起control→续命)+数据入库DB铁证。
-  · 阶段2 完成(代码+真机自验):/status监控补全+/shutdown受护栏停止+/serial借串口让路 三端点真机验;operate端ControlClient+监控页跨进程读+受护栏按钮(harness实跑)。调试页完整借串口=延后专项(需命令代理设计+受监督电机验)。
-  · 阶段3 推进到位:退役删ivf_tl_ControlTest(两编译0错)+双进程部署指南+开机自启注册表方案验;ComBin两栈去重(G1-2,含oplog)=延后专项。
-  · 延后专项清单:调试页借串口命令代理(D2-02)/ComBin两栈去重(D3-04)/D1-09 SQLite/M-01~M-07/整机开机自启复测(需重启)。建议feature合并main+推送。
-  续接读:本文件 + 交接卡末四段(阶段1/D1-08/operate外壳/阶段2/阶段3)+ 待验证清单 D1/D2/D3 + 双进程部署指南。
+  【续做合并遗留 M 区:M-01/M-02/M-03 已修复并真机验证】
+  · M-01/02/03 根因:合并时 control Commander 缺 3 个 E方 builder,SerialChannelImpl 返桩(写排气阀/读排气阀/写灯光未真下发)。
+  · 已做(TDD):新建 ivf_tl_SerialHelper.Tests(4 单测绿,断言字节=合并前 operate 黄金真值)+ control Enums/ComBin/Commander 补齐 + SerialChannelImpl 去桩 + operate 消费方注释更新;双编译 0 错。
+  · 真机验证(非破坏性回环,无电机风险):舱9排气阀200→201→200✓、舱8排气阀90→91→90✓、舱8灯光500→501→500✓。
+  · 下一步:提交 M-01~03;剩 M-04(存图)/M-05(写成功判定·帧错位)/M-06(按well零点)/M-07(Release网关) + 延后专项(D2-02调试页命令代理/D3-04两栈去重/整机自启复测)。
 说明: >
-  operate/control 双进程拆分三阶段主体完成:进程拆分骨架+真机闭环(阶段1)、跨进程监控/停止/借串口契约(阶段2)、
-  退役老壳+部署文档+开机自启(阶段3)。剩余延后专项均为有依赖(调试页命令代理)、受安全门控(电机验)、
-  有风险(ComBin去重)或预存缺陷(D1-09/M区),非主体阻塞。
+  operate/control 双进程拆分三阶段主体早已完成;本轮续做"合并遗留 M 区"——M-01/M-02/M-03(排气阀时间写/读、灯光亮度写)
+  三处降级已补 control 端 builder 去桩并真机验证。剩 M-04~M-07 与延后专项(调试页命令代理/两栈去重/整机自启复测需重启)。
 阶段概览:
   - id: 阶段1
     名称: control 独立进程骨架
@@ -34,4 +32,4 @@
     名称: 清理老壳 + 装机收尾
     状态: 未开始
     备注: "退役删ivf_tl_ControlTest脏壳 + operate开机自启 + ComBin两套栈去重(G1-2) + 部署文档。待阶段2完成后拆计划"
-下一步: 延后专项(调试页借串口命令代理/ComBin两栈去重D3-04/M-01~M-07/整机开机自启复测需重启);D1-09已修;建议feature合并main+推送
+下一步: 提交 M-01~03(代码+测试+文档);剩 M-04(存图)/M-05(写成功判定·帧错位)/M-06(按well零点)/M-07(Release网关) + 延后专项(D2-02/D3-04/整机自启复测需重启)

+ 6 - 4
项目文档/需求文档/操作端逻辑与配置全景.md

@@ -175,12 +175,14 @@
 
 ### 8.1 功能降级登记表(相对基准,真实功能缺失)
 
+> ✅ **2026-06-23 更新**:M-01/M-02/M-03 已修复并真机验证(TDD red→green + 非破坏性回环)——control 端补齐 3 个 E方 builder + ComBin Wait + SerialChannelImpl 去桩,见 `进度/待验证清单.md` 同日段。下表前三行标 ✅已修复。存图(M-04)仍待核对。
+
 | 模块 | 操作 | 基准行为 | 现状(file:line) | UI 提示 | 风险 |
 |---|---|---|---|---|---|
-| 对焦调试 | **写舱室排气阀时间** | `WriteEEPROOpenVentTimeWait` 真下发 | `Serial.WriteOpenVentTimeWait`→**`return false` 不下发**(SerialChannelImpl.cs:130-135);VM 仅本地暂存(HouseDebug:534-548) | ✅ 有("仅本地暂存") | **中**:工程师以为已写下位机,实际未生效。补 control `CreateWriteEEPROOpenVentTimeCommand` + 真机核对字节 |
-| 对焦调试 | **读舱室排气阀时间** | `ReadEEPROMVentWait` 真读 | `Serial.ReadOpenVentTimeWait`→**`return -1`**(SerialChannelImpl.cs:137-141);读到 -1 保持原值(HouseDebug:553-559) | ❌ **无提示(静默保留旧值)** | **中**:界面显示可能是陈旧值而非下位机实时值,最易被误判正常 |
-| 缓冲瓶调试 | **写灯光亮度 EEPROM** | `WriteEEPROMLightNumWait` 真写 | `Serial.WriteLightBrightnessWait`→**`return false` 不下发**(SerialChannelImpl.cs:143-147);VM 仅本地+HTTP(Buffer:268-283) | ✅ 有("仅本地暂存") | **中**:灯光只存服务器,未写下位机 EEPROM。补 control `CreateWriteEEPROMLightNum` |
-| 对焦调试 | **调试页存图** | `camera.SavePic(name,w,h)`→厂商 `MVCAPI.SavePic(buf,w,h,name)` | `Cam.SavePic`→`CameraImpl.SavePic` **丢弃宽高**→`SaveBmpPic(name)`(CameraImpl.cs:148-153) | ❌ **无提示(实现透明替换)** | **中**:存图链路换实现,语义"等价"但**未真机验证**,落盘格式/旋转/位深需核对 |
+| 对焦调试 | **写舱室排气阀时间** ✅已修复 | `WriteEEPROOpenVentTimeWait` 真下发 | `Serial.WriteOpenVentTimeWait`→`_com.WriteEEPROOpenVentTimeWait`(SerialChannelImpl.cs:130 已去桩;control 补 `CreateWriteEEPROOpenVentTimeCommand` 地址 00 03 08) | — | **已解除**:真机回环验证 舱9 200→201→200 / 舱8 90→91→90 PASS |
+| 对焦调试 | **读舱室排气阀时间** ✅已修复 | `ReadEEPROMVentWait` 真读 | `Serial.ReadOpenVentTimeWait`→`_com.ReadEEPROMVentWait`(SerialChannelImpl.cs:137 已去桩;control 补 `CreateReadEEPROMVentNum`) | — | **已解除**:真机读回真实值(200/90),非旧 -1 桩 |
+| 缓冲瓶调试 | **写灯光亮度 EEPROM** ✅已修复 | `WriteEEPROMLightNumWait` 真写 | `Serial.WriteLightBrightnessWait`→`_com.WriteEEPROMLightNumWait`(SerialChannelImpl.cs:143 已去桩;control 补 `CreateWriteEEPROMLightNum` 地址 00 05 34) | — | **已解除**:真机回环 舱8 灯光 500→501→500 PASS |
+| 对焦调试 | **调试页存图** | `camera.SavePic(name,w,h)`→厂商 `MVCAPI.SavePic(buf,w,h,name)` | `Cam.SavePic`→`CameraImpl.SavePic` **丢弃宽高**→`SaveBmpPic(name)`(CameraImpl.cs:148-153) | ❌ **无提示(实现透明替换)** | **中**:存图链路换实现,语义"等价"但**未真机验证**,落盘格式/旋转/位深需核对(=M-04) |
 
 ### 8.2 待验证 / 桩(非降级,但成功语义不可靠或未实现)