|
@@ -0,0 +1,416 @@
|
|
|
|
|
+# HIL 硬件在环回归套件 实现计划
|
|
|
|
|
+
|
|
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development 或 superpowers:executing-plans 逐任务实现。步骤用 `- [ ]` 复选框跟踪。
|
|
|
|
|
+> 关联设计:`项目文档/需求文档/specs/2026-06-23-HIL硬件在环回归套件-design.md`。
|
|
|
|
|
+
|
|
|
|
|
+**Goal:** 新增永久入库的 xUnit HIL 测试工程 `IvfTl.Hardware.HilTests`,把 M-05/M-06/M-01-03 的真机检查固化为防回归用例;真机不在时优雅 Skip。
|
|
|
|
|
+
|
|
|
|
|
+**Architecture:** `net6.0-windows` xUnit 工程,经真实 `IvfTl.Hardware.Impl.SerialChannelImpl` 端到端连真舱。collection fixture 扫口握手收集响应真舱,禁并行串行跑;`[SkippableFact]` + `Skip.If` 在无硬件/control 占口时 Skip。默认零写入,写回环用 `HIL_ALLOW_WRITE=1` 显式开关。
|
|
|
|
|
+
|
|
|
|
|
+**Tech Stack:** C# / net6.0-windows / xUnit 2.4.2 / Xunit.SkippableFact / Microsoft.NET.Test.Sdk 17.6.0;`SerialChannelImpl`(System.IO.Ports)。
|
|
|
|
|
+
|
|
|
|
|
+**约定提醒:** operate.exe 在跑会锁 DLL(MSB3021),编译前先确保 operate 未跑;control 占串口会致测试 Skip(预期行为)。真机方法签名:`SerialChannelImpl(int index, string port)` / `bool Open()` / `int ShakeHandsWait()` / `int ReadOpenVentTimeWait()` / `bool WriteOpenVentTimeWait(int)` / `int ReadLightBrightnessWait()` / `bool WriteLightBrightnessWait(int)` / `int ReadWellFocusZeroWait(int well)` / `void Close()`。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 1: 工程骨架 + 加入 sln + 编译通过
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj`
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/AssemblyInfo.cs`
|
|
|
|
|
+- Modify: `ivf_tl_operate_2.0/control/ivf_tl_Control.sln`(加工程)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 写 csproj**
|
|
|
|
|
+
|
|
|
|
|
+```xml
|
|
|
|
|
+<Project Sdk="Microsoft.NET.Sdk">
|
|
|
|
|
+ <PropertyGroup>
|
|
|
|
|
+ <TargetFramework>net6.0-windows</TargetFramework>
|
|
|
|
|
+ <Nullable>disable</Nullable>
|
|
|
|
|
+ <IsPackable>false</IsPackable>
|
|
|
|
|
+ <Platforms>AnyCPU;x64</Platforms>
|
|
|
|
|
+ </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" />
|
|
|
|
|
+ <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
|
|
|
|
|
+ </ItemGroup>
|
|
|
|
|
+ <ItemGroup>
|
|
|
|
|
+ <ProjectReference Include="..\IvfTl.Hardware\IvfTl.Hardware.csproj" />
|
|
|
|
|
+ </ItemGroup>
|
|
|
|
|
+</Project>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 写 AssemblyInfo.cs(禁并行,防多测试抢 COM 口)**
|
|
|
|
|
+
|
|
|
|
|
+```csharp
|
|
|
|
|
+using Xunit;
|
|
|
|
|
+
|
|
|
|
|
+[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 加进 sln**
|
|
|
|
|
+
|
|
|
|
|
+Run: `cd ivf_tl_operate_2.0/control && dotnet sln ivf_tl_Control.sln add IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj`
|
|
|
|
|
+Expected: `Project ... added to the solution.`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 编译验证(含 NuGet restore SkippableFact)**
|
|
|
|
|
+
|
|
|
|
|
+Run: `dotnet build ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj -c Debug`
|
|
|
|
|
+Expected: `Build succeeded. 0 Error(s)`。
|
|
|
|
|
+**若 `Xunit.SkippableFact` 离线 restore 失败**:走回退——从 csproj 删该 PackageReference,Task 2 改用无依赖 `[HilFact]`(见 Task 2 回退分支)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 提交点(暂不单独 commit,按 §3.4 末尾合并提交)**
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 2: HardwareRigFixture(扫口握手收集响应真舱)+ collection
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/Infrastructure/HardwareRigFixture.cs`
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/Infrastructure/HilCollection.cs`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 写 HardwareRigFixture.cs**
|
|
|
|
|
+
|
|
|
|
|
+```csharp
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
|
+using System.IO.Ports;
|
|
|
|
|
+using IvfTl.Hardware.Impl;
|
|
|
|
|
+
|
|
|
|
|
+namespace IvfTl.Hardware.HilTests.Infrastructure
|
|
|
|
|
+{
|
|
|
|
|
+ /// <summary>一个有响应的真舱:串口 + 握手返回的舱号。</summary>
|
|
|
|
|
+ public sealed class ChamberInfo
|
|
|
|
|
+ {
|
|
|
|
|
+ public string Port { get; }
|
|
|
|
|
+ public int HouseSn { get; }
|
|
|
|
|
+ public ChamberInfo(string port, int houseSn) { Port = port; HouseSn = houseSn; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// 全套件构造一次:扫描 COM 口、逐口 Open+握手,收集有响应的真舱。
|
|
|
|
|
+ /// 探测完即 Close 释放;各测试用时各自重开端口。
|
|
|
|
|
+ /// 无硬件 / control 正占用串口 → Chambers 为空 → 测试 Skip。
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public sealed class HardwareRigFixture : IDisposable
|
|
|
|
|
+ {
|
|
|
|
|
+ public IReadOnlyList<ChamberInfo> Chambers { get; }
|
|
|
|
|
+
|
|
|
|
|
+ public HardwareRigFixture()
|
|
|
|
|
+ {
|
|
|
|
|
+ var found = new List<ChamberInfo>();
|
|
|
|
|
+ string[] ports;
|
|
|
|
|
+ try { ports = SerialPort.GetPortNames(); } catch { ports = Array.Empty<string>(); }
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var port in ports)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (port == "COM1" || port == "COM2") continue; // 非舱口
|
|
|
|
|
+ SerialChannelImpl ch = null;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ ch = new SerialChannelImpl(0, port);
|
|
|
|
|
+ if (!ch.Open()) continue;
|
|
|
|
|
+ int house = ch.ShakeHandsWait();
|
|
|
|
|
+ if (house < 0) continue;
|
|
|
|
|
+ found.Add(new ChamberInfo(port, house));
|
|
|
|
|
+ }
|
|
|
|
|
+ catch { /* 占口/无响应 → 不计入 */ }
|
|
|
|
|
+ finally { try { ch?.Close(); } catch { } }
|
|
|
|
|
+ }
|
|
|
|
|
+ Chambers = found;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// <summary>取第一个有舱室 E方(排气阀/灯光/焦点)的真舱,排除舱11(缓冲瓶/总控)。</summary>
|
|
|
|
|
+ public ChamberInfo FirstChamberWithWells()
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (var c in Chambers) if (c.HouseSn != 11) return c;
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Dispose() { }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 写 HilCollection.cs**
|
|
|
|
|
+
|
|
|
|
|
+```csharp
|
|
|
|
|
+using Xunit;
|
|
|
|
|
+
|
|
|
|
|
+namespace IvfTl.Hardware.HilTests.Infrastructure
|
|
|
|
|
+{
|
|
|
|
|
+ [CollectionDefinition("HIL")]
|
|
|
|
|
+ public sealed class HilCollection : ICollectionFixture<HardwareRigFixture> { }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `dotnet build ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj -c Debug`
|
|
|
|
|
+Expected: `Build succeeded. 0 Error(s)`
|
|
|
|
|
+
|
|
|
|
|
+> **回退分支(仅当 SkippableFact 不可用)**:在 Infrastructure 下加 `HilFactAttribute.cs`:`public sealed class HilFactAttribute : FactAttribute {}`(普通 Fact);各测试改用 `if (rig.Chambers.Count==0) { output.WriteLine("skip:无真舱"); return; }` 早返回(报 Passed)。后续 Task 的 `[SkippableFact]`/`Skip.If` 相应换成 `[HilFact]` + 早返回。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 3: FocusZeroHilTests(M-06 按 well 焦点零点,纯读)
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/FocusZeroHilTests.cs`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 写测试**
|
|
|
|
|
+
|
|
|
|
|
+```csharp
|
|
|
|
|
+using System.Linq;
|
|
|
|
|
+using System.Threading;
|
|
|
|
|
+using IvfTl.Hardware.HilTests.Infrastructure;
|
|
|
|
|
+using IvfTl.Hardware.Impl;
|
|
|
|
|
+using Xunit;
|
|
|
|
|
+using Xunit.Abstractions;
|
|
|
|
|
+
|
|
|
|
|
+namespace IvfTl.Hardware.HilTests
|
|
|
|
|
+{
|
|
|
|
|
+ [Collection("HIL")]
|
|
|
|
|
+ public class FocusZeroHilTests
|
|
|
|
|
+ {
|
|
|
|
|
+ private readonly HardwareRigFixture _rig;
|
|
|
|
|
+ private readonly ITestOutputHelper _out;
|
|
|
|
|
+ public FocusZeroHilTests(HardwareRigFixture rig, ITestOutputHelper o) { _rig = rig; _out = o; }
|
|
|
|
|
+
|
|
|
|
|
+ // M-06:按 well 读 Z 焦点零点。修前恒读 well-1(去重=1);修后各 well 分槽(去重>1)。纯 0x11 读,无电机。
|
|
|
|
|
+ [SkippableFact]
|
|
|
|
|
+ public void PerWellFocusZero_IsDistinctPerWell_AndWithinSafeZ()
|
|
|
|
|
+ {
|
|
|
|
|
+ var chamber = _rig.FirstChamberWithWells();
|
|
|
|
|
+ Skip.If(chamber == null, "无响应真舱:无硬件 / control 正占用串口 / 未连接");
|
|
|
|
|
+
|
|
|
|
|
+ var vals = new int[17];
|
|
|
|
|
+ SerialChannelImpl ch = null;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ ch = new SerialChannelImpl(0, chamber.Port);
|
|
|
|
|
+ Assert.True(ch.Open(), $"{chamber.Port} 打开失败");
|
|
|
|
|
+ for (int well = 1; well <= 16; well++)
|
|
|
|
|
+ {
|
|
|
|
|
+ vals[well] = ch.ReadWellFocusZeroWait(well);
|
|
|
|
|
+ Thread.Sleep(120);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ finally { try { ch?.Close(); } catch { } }
|
|
|
|
|
+
|
|
|
|
|
+ var read = vals.Skip(1).ToArray();
|
|
|
|
|
+ _out.WriteLine($"舱{chamber.HouseSn} 各 well 焦点零点: {string.Join(",", read)}");
|
|
|
|
|
+
|
|
|
|
|
+ int distinct = read.Distinct().Count();
|
|
|
|
|
+ Assert.True(distinct > 1, $"按 well 读应各 well 不同(去重>1),实得去重={distinct}(=1 说明退回恒读 well-1=M-06 回归)");
|
|
|
|
|
+ Assert.All(read, v => Assert.InRange(v, 0, 125000)); // 安全 Z 区间
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `dotnet build ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj -c Debug`
|
|
|
|
|
+Expected: `Build succeeded. 0 Error(s)`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 4: FrameLengthHilTests(M-05 连续读干净度,纯读)
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/FrameLengthHilTests.cs`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 写测试**
|
|
|
|
|
+
|
|
|
|
|
+```csharp
|
|
|
|
|
+using System.Threading;
|
|
|
|
|
+using IvfTl.Hardware.HilTests.Infrastructure;
|
|
|
|
|
+using IvfTl.Hardware.Impl;
|
|
|
|
|
+using Xunit;
|
|
|
|
|
+using Xunit.Abstractions;
|
|
|
|
|
+
|
|
|
|
|
+namespace IvfTl.Hardware.HilTests
|
|
|
|
|
+{
|
|
|
|
|
+ [Collection("HIL")]
|
|
|
|
|
+ public class FrameLengthHilTests
|
|
|
|
|
+ {
|
|
|
|
|
+ private readonly HardwareRigFixture _rig;
|
|
|
|
|
+ private readonly ITestOutputHelper _out;
|
|
|
|
|
+ public FrameLengthHilTests(HardwareRigFixture rig, ITestOutputHelper o) { _rig = rig; _out = o; }
|
|
|
|
|
+
|
|
|
|
|
+ // M-05:0x12 写 E方回包帧长修复为 12 后,读路径不被残留字节污染。
|
|
|
|
|
+ // 纯读变体:连续多轮读排气阀时间,断言全部 sane 非负(帧错位会出现 -1/垃圾)。
|
|
|
|
|
+ [SkippableFact]
|
|
|
|
|
+ public void RepeatedReads_AreClean_NoFrameCorruption()
|
|
|
|
|
+ {
|
|
|
|
|
+ var chamber = _rig.FirstChamberWithWells();
|
|
|
|
|
+ Skip.If(chamber == null, "无响应真舱:无硬件 / control 正占用串口 / 未连接");
|
|
|
|
|
+
|
|
|
|
|
+ const int rounds = 12;
|
|
|
|
|
+ int clean = 0;
|
|
|
|
|
+ SerialChannelImpl ch = null;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ ch = new SerialChannelImpl(0, chamber.Port);
|
|
|
|
|
+ Assert.True(ch.Open(), $"{chamber.Port} 打开失败");
|
|
|
|
|
+ for (int r = 0; r < rounds; r++)
|
|
|
|
|
+ {
|
|
|
|
|
+ int v = ch.ReadOpenVentTimeWait();
|
|
|
|
|
+ if (v >= 0) clean++;
|
|
|
|
|
+ else _out.WriteLine($"轮{r} 读={v}(脏/无响应)");
|
|
|
|
|
+ Thread.Sleep(80);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ finally { try { ch?.Close(); } catch { } }
|
|
|
|
|
+
|
|
|
|
|
+ _out.WriteLine($"舱{chamber.HouseSn} 连续读 {rounds} 轮,干净 {clean}/{rounds}");
|
|
|
|
|
+ Assert.Equal(rounds, clean); // 帧长正确则每轮都 sane;有错位则出现 -1
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `dotnet build ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj -c Debug`
|
|
|
|
|
+Expected: `Build succeeded. 0 Error(s)`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 5: EepromWriteHilTests(M-01/02/03 写回环,默认 Skip)
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/EepromWriteHilTests.cs`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 写测试**
|
|
|
|
|
+
|
|
|
|
|
+```csharp
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Threading;
|
|
|
|
|
+using IvfTl.Hardware.HilTests.Infrastructure;
|
|
|
|
|
+using IvfTl.Hardware.Impl;
|
|
|
|
|
+using Xunit;
|
|
|
|
|
+using Xunit.Abstractions;
|
|
|
|
|
+
|
|
|
|
|
+namespace IvfTl.Hardware.HilTests
|
|
|
|
|
+{
|
|
|
|
|
+ [Collection("HIL")]
|
|
|
|
|
+ public class EepromWriteHilTests
|
|
|
|
|
+ {
|
|
|
|
|
+ private readonly HardwareRigFixture _rig;
|
|
|
|
|
+ private readonly ITestOutputHelper _out;
|
|
|
|
|
+ public EepromWriteHilTests(HardwareRigFixture rig, ITestOutputHelper o) { _rig = rig; _out = o; }
|
|
|
|
|
+
|
|
|
|
|
+ private static bool WriteAllowed =>
|
|
|
|
|
+ Environment.GetEnvironmentVariable("HIL_ALLOW_WRITE") == "1";
|
|
|
|
|
+
|
|
|
|
|
+ // M-01/02/03:写命令真下发到下位机(读回随写变化),写后立即恢复原值(非破坏)。
|
|
|
|
|
+ // 默认 Skip(零写入);仅 HIL_ALLOW_WRITE=1 才跑。
|
|
|
|
|
+ [SkippableFact]
|
|
|
|
|
+ public void WriteVentTime_RoundTrips_AndRestoresOriginal()
|
|
|
|
|
+ {
|
|
|
|
|
+ Skip.IfNot(WriteAllowed, "写路径默认关闭;设 HIL_ALLOW_WRITE=1 才跑(写后立即恢复原值)");
|
|
|
|
|
+ RoundTrip("排气阀时间(M-01写/M-02读)",
|
|
|
|
|
+ ch => ch.ReadOpenVentTimeWait(), (ch, v) => ch.WriteOpenVentTimeWait(v));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [SkippableFact]
|
|
|
|
|
+ public void WriteLightBrightness_RoundTrips_AndRestoresOriginal()
|
|
|
|
|
+ {
|
|
|
|
|
+ Skip.IfNot(WriteAllowed, "写路径默认关闭;设 HIL_ALLOW_WRITE=1 才跑(写后立即恢复原值)");
|
|
|
|
|
+ RoundTrip("灯光亮度(M-03写)",
|
|
|
|
|
+ ch => ch.ReadLightBrightnessWait(), (ch, v) => ch.WriteLightBrightnessWait(v));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void RoundTrip(string label, Func<SerialChannelImpl, int> read, Action<SerialChannelImpl, int> write)
|
|
|
|
|
+ {
|
|
|
|
|
+ var chamber = _rig.FirstChamberWithWells();
|
|
|
|
|
+ Skip.If(chamber == null, "无响应真舱:无硬件 / control 正占用串口 / 未连接");
|
|
|
|
|
+
|
|
|
|
|
+ SerialChannelImpl ch = null;
|
|
|
|
|
+ try
|
|
|
|
|
+ {
|
|
|
|
|
+ ch = new SerialChannelImpl(0, chamber.Port);
|
|
|
|
|
+ Assert.True(ch.Open(), $"{chamber.Port} 打开失败");
|
|
|
|
|
+
|
|
|
|
|
+ // 读前丢弃一帧 + 间隔排空(隔离任何尾噪声),拿稳定原值
|
|
|
|
|
+ int ReadStable() { try { read(ch); } catch { } Thread.Sleep(700); int a = read(ch); Thread.Sleep(700); return a; }
|
|
|
|
|
+
|
|
|
|
|
+ int v0 = ReadStable();
|
|
|
|
|
+ _out.WriteLine($"[{label}] 舱{chamber.HouseSn} 原值 V0={v0}");
|
|
|
|
|
+ Skip.If(v0 < 0, $"[{label}] 原值读不到(该舱无此项),跳过");
|
|
|
|
|
+
|
|
|
|
|
+ int target = v0 + 1;
|
|
|
|
|
+ write(ch, target); Thread.Sleep(700);
|
|
|
|
|
+ int v1 = ReadStable();
|
|
|
|
|
+ _out.WriteLine($"[{label}] 写 {target} 后读回={v1}");
|
|
|
|
|
+
|
|
|
|
|
+ write(ch, v0); Thread.Sleep(700); // 恢复原值
|
|
|
|
|
+ int v2 = ReadStable();
|
|
|
|
|
+ _out.WriteLine($"[{label}] 写回 {v0} 后读回={v2}");
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Equal(target, v1); // 写真下发:读回随写变化
|
|
|
|
|
+ Assert.Equal(v0, v2); // 已恢复原值:非破坏
|
|
|
|
|
+ }
|
|
|
|
|
+ finally { try { ch?.Close(); } catch { } }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 编译验证**
|
|
|
|
|
+
|
|
|
|
|
+Run: `dotnet build ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj -c Debug`
|
|
|
|
|
+Expected: `Build succeeded. 0 Error(s)`
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 6: 真机跑 + README + 双编译 + 文档同步 + 提交
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/README.md`
|
|
|
|
|
+- Modify: 文档同步(见 Step 4)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 确认 control/operate 未占串口,真机跑 HIL(默认零写入)**
|
|
|
|
|
+
|
|
|
|
|
+Run: `dotnet test ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj -c Debug`
|
|
|
|
|
+Expected:`FocusZero`/`FrameLength` 两测试 **Passed**(真验证);3 个写测试 **Skipped**(默认关)。若真机不在/被占 → 全 Skipped。记录输出。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 开写开关重跑,验证写回环(写后恢复原值)**
|
|
|
|
|
+
|
|
|
|
|
+Run(bash):`HIL_ALLOW_WRITE=1 dotnet test ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests/IvfTl.Hardware.HilTests.csproj -c Debug`
|
|
|
|
|
+Expected:写回环 2 测试 Passed(读回随写变化、值已恢复)。记录输出。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 写 README.md**
|
|
|
|
|
+
|
|
|
|
|
+内容:套件用途(防 M-05/M-06/M-01-03 回归)、运行前提(control 未占串口、operate 未跑)、`dotnet test` 默认零写入、`HIL_ALLOW_WRITE=1` 开写回环、Skip 语义(无真舱即 Skip 非 Fail)、非破坏安全说明。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: 双编译 0 错 + 回归既有单测 + codegraph sync**
|
|
|
|
|
+
|
|
|
|
|
+Run: `dotnet build ivf_tl_operate_2.0/control/ivf_tl_Control.sln -c Debug`(control 全 sln)
|
|
|
|
|
+Run: `dotnet build ivf_tl_operate_2.0/ivf_tl_Operate.sln -c Release`(operate Release,先确保 operate.exe 未跑)
|
|
|
|
|
+Run: `dotnet test ivf_tl_operate_2.0/control/ivf_tl_SerialHelper.Tests/ivf_tl_SerialHelper.Tests.csproj`(既有 40 单测仍过)
|
|
|
|
|
+Run: `codegraph sync`
|
|
|
|
|
+Expected:全 0 错 / 40 过 / 索引同步。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 5: 文档同步(提交边界=文档已对齐)**
|
|
|
|
|
+
|
|
|
|
|
+更新:`工作计划表.md`(阶段3 加 HIL 套件入库行)、`进度状态.yaml`(覆盖断点)、`交接卡.md`(追加本次)、`待验证清单.md`(M-05/M-06/M-01-03 标注已有入库 HIL 守护)、`进度数据.js`(面板)。
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 6: 提交**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add ivf_tl_operate_2.0/control/IvfTl.Hardware.HilTests ivf_tl_operate_2.0/control/ivf_tl_Control.sln 项目文档/
|
|
|
|
|
+git commit -m "test(hil): 新增硬件在环回归套件 IvfTl.Hardware.HilTests(守护 M-05帧长/M-06按well焦点/M-01-03 EEPROM写;门控Skip+默认零写入)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 自检(Self-Review)
|
|
|
|
|
+
|
|
|
|
|
+- **Spec 覆盖**:形态(xUnit 门控)=Task1-2;M-06=Task3;M-05=Task4;M-01/02/03 写开关=Task5;不收录 FrameLenProbe=已述;README+验收+文档同步=Task6。✓ 全覆盖。
|
|
|
|
|
+- **占位扫描**:无 TBD;每个代码步骤含完整源码。✓
|
|
|
|
|
+- **类型一致**:`HardwareRigFixture.Chambers`/`FirstChamberWithWells()`/`ChamberInfo.{Port,HouseSn}` 在 Task2 定义,Task3-5 一致引用;`[SkippableFact]`+`Skip.If/IfNot` 一致;方法签名对齐头部清单。✓
|