Przeglądaj źródła

feat(d2-02-t3): 调试页手动电机/光源/阀/EEPROM/曝光命令切DebugSessionClient.CommandAsync(operate HAL空壳,真机经control执行)+补SetExposure op

operate 进程内 HAL 是空壳(从不 ScanDevices),调试页手动命令走进程内 Serial.XxxWait()/Cam.SetExposure
在真机上失效。改走 control 的 /debug/command(DebugSessionManager.Execute op 字典):
- VM 新增统一 RunDebugCommandAsync(op,args):Begin scope 埋点 + await CommandAsync + Ok判定 + 失败AddMessageInfo + SESSION_EXPIRED提示重进; 读命令值从 r.Result 回显
- 切的命令: 读温度/压力/舱门/排气阀时间;开关LED/进气阀/排气阀/补气/排气;写进气阀时间/排气阀时间/扫描间隔/well水平位(EEPROM4个语义不变);电机 水平复位/正反向/到well + 垂直复位/正反向/绝对; 一键电机准备; 进入调试握手+电机归位序列
- 曝光: control 端 Execute 新增 SetExposure op(走 s.Lease.Camera.SetExposure),operate 曝光改走 CommandAsync("SetExposure",{value})
- VM 手动 handler 改 async Task; View 端 Click 内 Task.Run lambda 改 async + await(保证后续依赖语句顺序)
- 未动: 标定协作(Task3.4 HTTP/CalibrationClient)、抓图/预览(ShuiPingZhuaPai/AutoFocusPic/GetPicData/StartPreview 相机像素流)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie 1 dzień temu
rodzic
commit
cb2f82d21a

+ 8 - 0
ivf_tl_operate_2.0/control/ivf_tl_ControlHost/Debug/DebugSessionManager.cs

@@ -89,6 +89,14 @@ namespace IvfTl.ControlHost.Debug
             {
                 switch (op)
                 {
+                    // 相机曝光:operate 进程相机是空壳,调试页设曝光改走 control 借用的真相机(Task3.4b)。
+                    case "SetExposure":
+                    {
+                        var cam = s.Lease.Camera;
+                        if (cam == null) return DebugCommandResult.Fail("NO_HANDLE", "借用相机句柄为空");
+                        int e = args?["value"] != null ? args["value"].Value<int>() : 0;
+                        return DebugCommandResult.Okay(cam.SetExposure(e));
+                    }
                     case "ReadTemp": return DebugCommandResult.Okay(ser.TemperatureWait());
                     case "ReadPressure": return DebugCommandResult.Okay(ser.PressureWait());
                     case "ReadDoor": return DebugCommandResult.Okay(ser.DoorStatusWait().ToString());

+ 53 - 56
ivf_tl_operate_2.0/ivf_tl_Operate/View/HouseDebugPageView.xaml.cs

@@ -375,10 +375,7 @@ namespace ivf_tl_Operate.View
         private void RedTem_Click(object sender, RoutedEventArgs e)
         {
 
-            Task.Run(() =>
-            {
-                vm.RedTem();
-            });
+            Task.Run(async () => await vm.RedTem());
         }
 
         /// <summary>
@@ -388,9 +385,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void RedPre_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.RedPre();
+                await vm.RedPre();
 
             });
         }
@@ -402,9 +399,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void RedDoor_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.RedDoor();
+                await vm.RedDoor();
 
             });
         }
@@ -416,9 +413,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void OpenLed_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.OpenLed();
+                await vm.OpenLed();
             });
         }
 
@@ -429,9 +426,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void CloseLed_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.CloseLed();
+                await vm.CloseLed();
             });
         }
 
@@ -478,9 +475,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void OpenIntake_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.OpenIntake();
+                await vm.OpenIntake();
             });
         }
 
@@ -491,9 +488,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void CloseIntake_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.CloseIntake();
+                await vm.CloseIntake();
             });
         }
 
@@ -504,9 +501,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void OpenExhaust_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.OpenExhaust();
+                await vm.OpenExhaust();
             });
         }
 
@@ -517,17 +514,17 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void CloseExhaust_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.CloseExhaust();
+                await vm.CloseExhaust();
             });
         }
 
         private void Aeration_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.HouseAeration();
+                await vm.HouseAeration();
 
             });
         }
@@ -555,9 +552,9 @@ namespace ivf_tl_Operate.View
                 }
                 if (int.TryParse(timeString, out int value) && value >= 0)
                 {
-                    Task.Run(() =>
+                    Task.Run(async () =>
                     {
-                        vm.WriteOpenIntakeTime(value);
+                        await vm.WriteOpenIntakeTime(value);
                         if (vm.CurrentHouse != null)
                         {
                             vm.CurrentHouse.inletValveOpeningTime = value;
@@ -582,7 +579,7 @@ namespace ivf_tl_Operate.View
         /// </summary>
         /// <param name="sender"></param>
         /// <param name="e"></param>
-        private void SetExposure_Click(object sender, RoutedEventArgs e)
+        private async void SetExposure_Click(object sender, RoutedEventArgs e)
         {
             try
             {
@@ -594,7 +591,7 @@ namespace ivf_tl_Operate.View
 
                 if (int.TryParse(this._exposure_TextBox.Text.Trim(), out int newExposure) && newExposure >= 0)
                 {
-                    vm.SetExposure(newExposure);
+                    await vm.SetExposure(newExposure);
                     if (vm.CurrentHouse != null)
                     {
                         vm.CurrentHouse.ccdExposure = newExposure;
@@ -619,9 +616,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void HorizontalMotorReset_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.HorizontalMotorReset();
+                await vm.HorizontalMotorReset();
                 RefshInfo();
             });
 
@@ -639,11 +636,11 @@ namespace ivf_tl_Operate.View
                 ShowMessageDefeat("未获取到对应的舱室信息");
                 return;
             }
-            Task.Run(() =>
+            Task.Run(async () =>
             {
                 try
                 {
-                    vm.SaveWellHor();
+                    await vm.SaveWellHor();
                     if (!vm.SetWellMotorPosition())
                     {
                         ShowMessageDefeat("数据库保存失败");
@@ -686,9 +683,9 @@ namespace ivf_tl_Operate.View
             }
             if (int.TryParse(forwardString, out int value))
             {
-                Task.Run(() =>
+                Task.Run(async () =>
                 {
-                    vm.HorizontalMotorForward(value);
+                    await vm.HorizontalMotorForward(value);
                 });
             }
             else
@@ -713,9 +710,9 @@ namespace ivf_tl_Operate.View
             }
             if (int.TryParse(backwardString, out int value))
             {
-                Task.Run(() =>
+                Task.Run(async () =>
                 {
-                    vm.HorizontalMotorBackward(value);
+                    await vm.HorizontalMotorBackward(value);
                 });
             }
             else
@@ -740,9 +737,9 @@ namespace ivf_tl_Operate.View
             }
             if (int.TryParse(wellString, out int value) && value >= 1 && value <= 16)
             {
-                Task.Run(() =>
+                Task.Run(async () =>
                 {
-                    vm.HorizontalMotorToWell(value);
+                    await vm.HorizontalMotorToWell(value);
                     RefshInfo();
                 });
             }
@@ -760,9 +757,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void HorizontalMotorNextWell_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.HorizontalMotorToWell(vm.CurrentWell + 1);
+                await vm.HorizontalMotorToWell(vm.CurrentWell + 1);
                 RefshInfo();
             });
 
@@ -775,9 +772,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void VerticalMotorResetWait_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.VerticalMotorReset();
+                await vm.VerticalMotorReset();
                 vm.CurrentFocal = 0;
             });
 
@@ -798,9 +795,9 @@ namespace ivf_tl_Operate.View
             }
             if (int.TryParse(forwardString, out int value))
             {
-                Task.Run(() =>
+                Task.Run(async () =>
                 {
-                    vm.VerticalMotorForward(value);
+                    await vm.VerticalMotorForward(value);
                 });
 
             }
@@ -826,9 +823,9 @@ namespace ivf_tl_Operate.View
             }
             if (int.TryParse(backwardString, out int value))
             {
-                Task.Run(() =>
+                Task.Run(async () =>
                 {
-                    vm.VerticalMotorBackward(value);
+                    await vm.VerticalMotorBackward(value);
                 });
             }
             else
@@ -853,9 +850,9 @@ namespace ivf_tl_Operate.View
             }
             if (int.TryParse(absoluteString, out int value) && value >= 0 && value <= vm.tLSetting.verticalMotorPulseMax)
             {
-                Task.Run(() =>
+                Task.Run(async () =>
                 {
-                    vm.VerticalMotorAbsolute(value);
+                    await vm.VerticalMotorAbsolute(value);
                 });
             }
             else
@@ -882,9 +879,9 @@ namespace ivf_tl_Operate.View
                 }
                 if (int.TryParse(this._verticalMotorForward_TextBox.Text.Trim(), out int newValue) && newValue > 0 && newValue <= vm.tLSetting.verticalMotorPulseMax)
                 {
-                    Task.Run(() =>
+                    Task.Run(async () =>
                     {
-                        vm.WriteOVerSpace(newValue);
+                        await vm.WriteOVerSpace(newValue);
                         vm.CurrentHouse.verticalMotorSpacePulse = newValue;
                         vm.SetHouseInfo();
                     });
@@ -919,9 +916,9 @@ namespace ivf_tl_Operate.View
         /// <param name="e"></param>
         private void Motor_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.MototReady();
+                await vm.MototReady();
             });
 
         }
@@ -1303,9 +1300,9 @@ namespace ivf_tl_Operate.View
 
         private void Paiqi_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.HouseVent();
+                await vm.HouseVent();
             });
         }
 
@@ -1327,9 +1324,9 @@ namespace ivf_tl_Operate.View
                 }
                 if (int.TryParse(timeString, out int value) && value >= 0)
                 {
-                    Task.Run(() =>
+                    Task.Run(async () =>
                     {
-                        vm.WriteOpenVentTime(value);
+                        await vm.WriteOpenVentTime(value);
                         //if (vm.CurrentHouse != null)
                         //{
                         //    vm.CurrentHouse.inletValveOpeningTime = value;
@@ -1351,9 +1348,9 @@ namespace ivf_tl_Operate.View
 
         private void ReadOpenVentTime_Click(object sender, RoutedEventArgs e)
         {
-            Task.Run(() =>
+            Task.Run(async () =>
             {
-                vm.RedVentTime();
+                await vm.RedVentTime();
             });
         }
     }

+ 158 - 165
ivf_tl_operate_2.0/ivf_tl_Operate/ViewModel/HouseDebugPageViewModel.cs

@@ -347,30 +347,33 @@ namespace ivf_tl_Operate.ViewModel
                         verNewValue = cc.clearestPosition;
                     }
 
-                    Serial.ShakeHandsWait();
+                    // Task3.4b:进入调试的握手/电机归位/读数序列同走 control(operate Serial 空壳)。
+                    await RunDebugCommandAsync("进入调试-握手", "ShakeHands", null, new { houseSn = currentHouse.houseSn });
 
-                    OpenLed();
+                    await OpenLed();
 
-                    Serial.HorizontalResetWait(tLSetting.motorDelay);
+                    await RunDebugCommandAsync("进入调试-水平复位", "HorizontalReset", new { motorDelay = tLSetting.motorDelay }, new { houseSn = currentHouse.houseSn });
                     CurrentHor = 0;
                     CurrentWell = 0;
 
-                    Serial.HorizontalMoveToWait(currentHorSetting.horizontalMotorPosition, tLSetting.motorDelay);
+                    await RunDebugCommandAsync("进入调试-水平到位", "HorizontalMoveTo",
+                        new { pos = currentHorSetting.horizontalMotorPosition, motorDelay = tLSetting.motorDelay }, new { houseSn = currentHouse.houseSn });
                     CurrentHor = currentHorSetting.horizontalMotorPosition;
                     CurrentWell = 1;
 
-                    Serial.VerticalResetWait(tLSetting.motorDelay);
+                    await RunDebugCommandAsync("进入调试-垂直复位", "VerticalReset", new { motorDelay = tLSetting.motorDelay }, new { houseSn = currentHouse.houseSn });
                     CurrentVer = 0;
                     CurrentFocal = 0;
 
-                    Serial.VerticalMoveToWait(verNewValue, tLSetting.motorDelay);
+                    await RunDebugCommandAsync("进入调试-垂直到位", "VerticalMoveTo",
+                        new { pos = verNewValue, motorDelay = tLSetting.motorDelay }, new { houseSn = currentHouse.houseSn });
                     CurrentVer = verNewValue;
                     CurrentFocal = 1;
 
-                    RedTem();
-                    RedPre();
-                    RedDoor();
-                    RedVentTime();
+                    await RedTem();
+                    await RedPre();
+                    await RedDoor();
+                    await RedVentTime();
                     return;
                 }
                 catch (Exception ex)
@@ -477,31 +480,73 @@ namespace ivf_tl_Operate.ViewModel
         #endregion
 
 
+        // ── 自动对焦重构 D2-02-T3.4b:调试页手动命令统一走 control 的 /debug/command ──
+        // operate 进程内 HAL 是空壳(从不 ScanDevices)、Serial/Cam 在真机上取不到真硬件;
+        // control 独立进程持真硬件,故手动电机/光源/阀/EEPROM/曝光全经 _sessionClient.CommandAsync 下发。
+        // 埋点不丢:沿用 OperationScope(同 M8-G3-1),据 CommandAsync 返回 Ok 记成功/失败。
+        /// <summary>
+        /// 统一执行一个调试手动命令:经 control HTTP /debug/command 下发 + 操作日志埋点 + 失败/会话失效提示。
+        /// 返回 control 的 AcquireResult(读命令的值从 r.Result 取);会话未建立或失败返回 null/!Ok。
+        /// </summary>
+        private async Task<AcquireResult> RunDebugCommandAsync(string operation, string op, object args, object input)
+        {
+            if (_sessionClient == null || string.IsNullOrEmpty(CurrentSessionId))
+            {
+                AddMessageInfo($"[{operation}]未持调试会话,请重新进入调试页");
+                return null;
+            }
+            using var _op = Aivfo.OperationLog.OperationLogger.Begin("对焦调试", operation, houseSn: CurrentHouseId);
+            try { _op.Input(input); } catch { }
+            AcquireResult r;
+            try
+            {
+                r = await _sessionClient.CommandAsync(op, args);
+            }
+            catch (Exception ex)
+            {
+                _op.Fail(ex.GetType().Name + ": " + ex.Message);
+                AddMessageInfo($"[{operation}]下发异常:{ex.Message}");
+                return null;
+            }
+            if (r == null) { _op.Fail("空响应"); AddMessageInfo($"[{operation}]无响应"); return null; }
+            if (r.Ok) { _op.Success(r.Result); }
+            else
+            {
+                _op.Fail(r.Code + ": " + r.Error);
+                if (r.Code == "SESSION_EXPIRED")
+                    AddMessageInfo($"[{operation}]调试会话已失效,请重新进入调试页");
+                else
+                    AddMessageInfo($"[{operation}]失败:{r.Error}");
+            }
+            return r;
+        }
+
         /// <summary>
         /// 读温度
         /// </summary>
-        public void RedTem()
+        public async Task RedTem()
         {
-            if (Serial == null) return;
-            Temperature = Serial.TemperatureWait();
+            var r = await RunDebugCommandAsync("读温度", "ReadTemp", null, new { houseSn = CurrentHouseId });
+            if (r != null && r.Ok && r.Result != null && decimal.TryParse(r.Result.ToString(), out var t)) Temperature = t;
         }
 
         /// <summary>
         /// 读压力
         /// </summary>
-        public void RedPre()
+        public async Task RedPre()
         {
-            if (Serial == null) return;
-            Pressure = Serial.PressureWait();
+            var r = await RunDebugCommandAsync("读压力", "ReadPressure", null, new { houseSn = CurrentHouseId });
+            if (r != null && r.Ok && r.Result != null && decimal.TryParse(r.Result.ToString(), out var p)) Pressure = p;
         }
 
         /// <summary>
         /// 读舱门
         /// </summary>
-        public void RedDoor()
+        public async Task RedDoor()
         {
-            if (Serial == null) return;
-            string DoorStateString = Serial.DoorStatusWait().ToString();
+            var r = await RunDebugCommandAsync("读舱门", "ReadDoor", null, new { houseSn = CurrentHouseId });
+            if (r == null || !r.Ok) return;
+            string DoorStateString = r.Result?.ToString();
             if(DoorStateString == "关闭")
             {
                 DoorState = KeyToStringConvert.GetLanguageStringByKey("C0305");
@@ -517,13 +562,10 @@ namespace ivf_tl_Operate.ViewModel
         /// <summary>
         /// 打开Led灯
         /// </summary>
-        public void OpenLed()
+        public async Task OpenLed()
         {
-            if (Serial == null) return;
-            // M8-G3-1:LED 开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "打开LED灯",
-                () => Serial.OpenLedWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:LED 开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("打开LED灯", "OpenLed", null, new { houseSn = CurrentHouseId });
             LedState = KeyToStringConvert.GetLanguageStringByKey("C0306");
             //LedState = "开启";
         }
@@ -531,13 +573,10 @@ namespace ivf_tl_Operate.ViewModel
         /// <summary>
         /// 关闭Led灯
         /// </summary>
-        public void CloseLed()
+        public async Task CloseLed()
         {
-            if (Serial == null) return;
-            // M8-G3-1:LED 开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "关闭LED灯",
-                () => Serial.CloseLedWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:LED 开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("关闭LED灯", "CloseLed", null, new { houseSn = CurrentHouseId });
             //LedState = "关闭";
             LedState = KeyToStringConvert.GetLanguageStringByKey("C0305");
         }
@@ -545,74 +584,56 @@ namespace ivf_tl_Operate.ViewModel
         /// <summary>
         /// 打开进气阀
         /// </summary>
-        public void OpenIntake()
+        public async Task OpenIntake()
         {
-            if (Serial == null) return;
-            // M8-G3-1:进气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "打开进气阀",
-                () => Serial.OpenIntakeValveWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:进气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("打开进气阀", "OpenIntake", null, new { houseSn = CurrentHouseId });
         }
 
         /// <summary>
         /// 关闭进气阀
         /// </summary>
-        public void CloseIntake()
+        public async Task CloseIntake()
         {
-            if (Serial == null) return;
-            // M8-G3-1:进气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "关闭进气阀",
-                () => Serial.CloseIntakeValveWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:进气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("关闭进气阀", "CloseIntake", null, new { houseSn = CurrentHouseId });
         }
 
         /// <summary>
         /// 打开排气阀
         /// </summary>
-        public void OpenExhaust()
+        public async Task OpenExhaust()
         {
-            if (Serial == null) return;
-            // M8-G3-1:排气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "打开排气阀",
-                () => Serial.OpenExhaustValveWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:排气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("打开排气阀", "OpenExhaust", null, new { houseSn = CurrentHouseId });
         }
 
 
         /// <summary>
         /// 关闭排气阀
         /// </summary>
-        public void CloseExhaust()
+        public async Task CloseExhaust()
         {
-            if (Serial == null) return;
-            // M8-G3-1:排气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "关闭排气阀",
-                () => Serial.CloseExhaustValveWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:排气阀开关为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("关闭排气阀", "CloseExhaust", null, new { houseSn = CurrentHouseId });
         }
 
         /// <summary>
         /// 舱室补气
         /// </summary>
-        public void HouseAeration()
+        public async Task HouseAeration()
         {
-            if (Serial == null) return;
-            // M8-G3-1:舱室补气为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "舱室补气",
-                () => Serial.HouseAerationWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:舱室补气为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("舱室补气", "HouseAeration", null, new { houseSn = CurrentHouseId });
         }
 
         /// <summary>
         /// 舱室排气
         /// </summary>
-        public void HouseVent()
+        public async Task HouseVent()
         {
-            if (Serial == null) return;
-            // M8-G3-1:舱室排气为串口设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "舱室排气",
-                () => Serial.HouseVentWait(),
-                input: new { houseSn = CurrentHouseId });
+            // M8-G3-1:舱室排气为串口设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("舱室排气", "HouseVent", null, new { houseSn = CurrentHouseId });
         }
 
 
@@ -620,32 +641,25 @@ namespace ivf_tl_Operate.ViewModel
         /// 写舱室进气阀时间
         /// </summary>
         /// <param name="newValue"></param>
-        public void WriteOpenIntakeTime(int newValue)
+        public async Task WriteOpenIntakeTime(int newValue)
         {
-            if (Serial == null) return;
-            // M1-B2:走 lease.Serial→control ComBin(builder 与 operate 逐字节一致)。⚠ 待真机 V-010(写E方回包判定)。
-            // M8-G3-1:写舱室进气阀时间为写 EEPROM 设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "写舱室进气阀时间",
-                () => Serial.WriteOpenIntakeTimeWait(newValue, isBuffer: false),
-                input: new { houseSn = CurrentHouseId, newValue });
+            // M8-G3-1:写舱室进气阀时间为写 EEPROM 设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("写舱室进气阀时间", "WriteOpenIntakeTime", new { value = newValue },
+                new { houseSn = CurrentHouseId, newValue });
         }
 
         /// <summary>
         /// 写舱室排气阀时间
         /// </summary>
         /// <param name="newValue"></param>
-        public void WriteOpenVentTime(int newValue)
-        {
-            if (Serial == null) return;
-            // 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)
+        public async Task WriteOpenVentTime(int newValue)
+        {
+            // M8-G3-1:写舱室排气阀时间为写 EEPROM 设备命令入口,补操作日志埋点(module=对焦调试)。
+            var r = await RunDebugCommandAsync("写舱室排气阀时间", "WriteOpenVentTime", new { value = newValue },
+                new { houseSn = CurrentHouseId, newValue });
+            if (r == null || !r.Ok)
             {
-                AddMessageInfo($"[排气阀时间]下发失败仅本地暂存{newValue}");
+                AddMessageInfo($"[排气阀时间]下发失败,仅本地暂存{newValue}");
             }
             HouseVentTimeE = newValue;
         }
@@ -653,53 +667,45 @@ namespace ivf_tl_Operate.ViewModel
         /// <summary>
         /// 读舱室排气阀打开时间
         /// </summary>
-        public void RedVentTime()
+        public async Task RedVentTime()
         {
-            if (Serial == null) return;
-            // M-02 已补:control 端补齐读排气阀时间 E方命令(地址 00 03 08);读不到(-1)则保持原值不覆盖。
-            int v = Serial.ReadOpenVentTimeWait();
-            if (v >= 0) HouseVentTimeE = v;
+            var r = await RunDebugCommandAsync("读排气阀时间", "ReadVentTime", null, new { houseSn = CurrentHouseId });
+            if (r != null && r.Ok && r.Result != null && int.TryParse(r.Result.ToString(), out var v) && v >= 0)
+                HouseVentTimeE = v;
         }
 
         /// <summary>
         /// 写垂直电机间隔脉冲
         /// </summary>
         /// <param name="newValue"></param>
-        public void WriteOVerSpace(int newValue)
+        public async Task WriteOVerSpace(int newValue)
         {
-            if (Serial == null) return;
-            // M1-B2:走 lease.Serial→control ComBin(builder 与 operate 逐字节一致)。⚠ 待真机 V-010。
-            // M8-G3-1:写垂直电机间隔脉冲为写 EEPROM 设备命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "写垂直电机间隔脉冲",
-                () => Serial.WriteScanStepWait(newValue),
-                input: new { houseSn = CurrentHouseId, newValue });
+            // M8-G3-1:写垂直电机间隔脉冲为写 EEPROM 设备命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("写垂直电机间隔脉冲", "WriteScanStep", new { value = newValue },
+                new { houseSn = CurrentHouseId, newValue });
         }
 
         /// <summary>
         /// 保存水平电机位置
         /// </summary>
         /// <param name="newValue"></param>
-        public void SaveWellHor()
+        public async Task SaveWellHor()
         {
-            if (Serial == null) return;
-            // M8-P3b:手调保存水平电机位置(关键命令入口,module=对焦调试)。
-            // M1-B2:走 lease.Serial.WriteWellHorizontalPosWait→control ComBin(builder 逐字节一致)。⚠ 待真机 V-010。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "手调保存水平电机位置",
-                () => Serial.WriteWellHorizontalPosWait(CurrentWell, CurrentHor),
-                input: new { houseSn = CurrentHouseId, well = CurrentWell, hor = CurrentHor });
+            // M8-P3b:手调保存水平电机位置(关键命令入口,module=对焦调试)。
+            await RunDebugCommandAsync("手调保存水平电机位置", "WriteWellHorizontalPos",
+                new { well = CurrentWell, hor = CurrentHor },
+                new { houseSn = CurrentHouseId, well = CurrentWell, hor = CurrentHor });
         }
 
         #region 电机运动
         /// <summary>
         /// 水平电机复位
         /// </summary>
-        public void HorizontalMotorReset()
+        public async Task HorizontalMotorReset()
         {
-            if (Serial == null) return;
             // M8-P3b:电机控制命令埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "水平电机复位",
-                () => Serial.HorizontalResetWait(tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId });
+            await RunDebugCommandAsync("水平电机复位", "HorizontalReset", new { motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId });
             CurrentHor = 0;
             CurrentWell = 0;
         }
@@ -707,26 +713,22 @@ namespace ivf_tl_Operate.ViewModel
         /// <summary>
         /// 水平电机正向
         /// </summary>
-        public void HorizontalMotorForward(int value)
+        public async Task HorizontalMotorForward(int value)
         {
-            if (Serial == null) return;
-            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "水平电机正向",
-                () => Serial.HorizontalForwardWait(value, tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId, value });
+            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("水平电机正向", "HorizontalForward", new { value, motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId, value });
             CurrentHor += value;
         }
 
         /// <summary>
         /// 水平电机反向
         /// </summary>
-        public void HorizontalMotorBackward(int value)
+        public async Task HorizontalMotorBackward(int value)
         {
-            if (Serial == null) return;
-            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "水平电机反向",
-                () => Serial.HorizontalBackwardWait(value, tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId, value });
+            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("水平电机反向", "HorizontalBackward", new { value, motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId, value });
             CurrentHor -= value;
         }
 
@@ -734,19 +736,18 @@ namespace ivf_tl_Operate.ViewModel
         /// 水平电机到目标well
         /// </summary>
         /// <param name="newWell"></param>
-        public void HorizontalMotorToWell(int newWell)
+        public async Task HorizontalMotorToWell(int newWell)
         {
-            if (Serial == null) return;
             var currentHorSetting = houseWellSettingList.FirstOrDefault(x => x.houseSn == CurrentHouseId && x.wellSn == newWell);
             if (currentHorSetting == null)
             {
                 AddMessageInfo($"[未获取到{newWell}号well的水平电机位置]");
                 return;
             }
-            // M8-G3-1:电机控制命令入口补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "水平电机到well",
-                () => Serial.HorizontalMoveToWait(currentHorSetting.horizontalMotorPosition, tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId, well = newWell, hor = currentHorSetting.horizontalMotorPosition });
+            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("水平电机到well", "HorizontalMoveTo",
+                new { pos = currentHorSetting.horizontalMotorPosition, motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId, well = newWell, hor = currentHorSetting.horizontalMotorPosition });
             CurrentHor = currentHorSetting.horizontalMotorPosition;
             CurrentWell = newWell;
         }
@@ -754,61 +755,52 @@ namespace ivf_tl_Operate.ViewModel
         /// <summary>
         /// 垂直电机复位
         /// </summary>
-        public void VerticalMotorReset()
+        public async Task VerticalMotorReset()
         {
-            if (Serial == null) return;
             // M8-P3b:电机控制命令埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "垂直电机复位",
-                () => Serial.VerticalResetWait(tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId });
+            await RunDebugCommandAsync("垂直电机复位", "VerticalReset", new { motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId });
             CurrentVer = 0;
         }
 
         /// <summary>
         /// 垂直电机正向运动
         /// </summary>
-        public void VerticalMotorForward(int newValue)
+        public async Task VerticalMotorForward(int newValue)
         {
-            if (Serial == null) return;
-            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "垂直电机正向",
-                () => Serial.VerticalForwardWait(newValue, tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId, newValue });
+            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("垂直电机正向", "VerticalForward", new { value = newValue, motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId, newValue });
             CurrentVer += newValue;
         }
 
         /// <summary>
         /// 垂直电机反向运动
         /// </summary>
-        public void VerticalMotorBackward(int newValue)
+        public async Task VerticalMotorBackward(int newValue)
         {
-            if (Serial == null) return;
-            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "垂直电机反向",
-                () => Serial.VerticalBackwardWait(newValue, tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId, newValue });
+            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("垂直电机反向", "VerticalBackward", new { value = newValue, motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId, newValue });
             CurrentVer -= newValue;
         }
 
         /// <summary>
         /// 垂直电机绝对运动
         /// </summary>
-        public void VerticalMotorAbsolute(int newValue)
+        public async Task VerticalMotorAbsolute(int newValue)
         {
-            if (Serial == null) return;
-            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
-            Aivfo.OperationLog.OperationLogger.Run("对焦调试", "垂直电机绝对运动",
-                () => Serial.VerticalMoveToWait(newValue, tLSetting.motorDelay),
-                input: new { houseSn = CurrentHouseId, newValue });
+            // M8-G3-1:电机控制命令入口,补操作日志埋点(module=对焦调试)。
+            await RunDebugCommandAsync("垂直电机绝对运动", "VerticalMoveTo", new { pos = newValue, motorDelay = tLSetting.motorDelay },
+                new { houseSn = CurrentHouseId, newValue });
             CurrentVer = newValue;
         }
 
         /// <summary>
         /// 一键电机准备
         /// </summary>
-        public void MototReady()
+        public async Task MototReady()
         {
-            if (Serial == null) return;
             var currentHorSetting = houseWellSettingList.FirstOrDefault(x => x.houseSn == CurrentHouseId && x.wellSn == 1);
             if (currentHorSetting == null)
             {
@@ -823,24 +815,26 @@ namespace ivf_tl_Operate.ViewModel
                 verNewValue = cc.clearestPosition;
             }
 
-            // M8-G3-1:一键电机准备为多步串口动作流程,用 Begin scope 串联各子动作(module=对焦调试)。
+            // M8-G3-1:一键电机准备为多步串口动作流程,各子动作经 control 顺序下发(module=对焦调试)。
             using var _op = Aivfo.OperationLog.OperationLogger.Begin("对焦调试", "一键电机准备",
                 houseSn: CurrentHouseId);
             try { _op.Input(new { houseSn = CurrentHouseId, targetHor = currentHorSetting.horizontalMotorPosition, targetVer = verNewValue }); } catch { }
 
-            Serial.HorizontalResetWait(tLSetting.motorDelay);
+            await RunDebugCommandAsync("一键电机准备-水平复位", "HorizontalReset", new { motorDelay = tLSetting.motorDelay }, new { houseSn = CurrentHouseId });
             CurrentHor = 0;
             CurrentWell = 0;
 
-            Serial.HorizontalMoveToWait(currentHorSetting.horizontalMotorPosition, tLSetting.motorDelay);
+            await RunDebugCommandAsync("一键电机准备-水平到位", "HorizontalMoveTo",
+                new { pos = currentHorSetting.horizontalMotorPosition, motorDelay = tLSetting.motorDelay }, new { houseSn = CurrentHouseId });
             CurrentHor = currentHorSetting.horizontalMotorPosition;
             CurrentWell = 1;
 
-            Serial.VerticalResetWait(tLSetting.motorDelay);
+            await RunDebugCommandAsync("一键电机准备-垂直复位", "VerticalReset", new { motorDelay = tLSetting.motorDelay }, new { houseSn = CurrentHouseId });
             CurrentVer = 0;
             CurrentFocal = 0;
 
-            Serial.VerticalMoveToWait(verNewValue, tLSetting.motorDelay);
+            await RunDebugCommandAsync("一键电机准备-垂直到位", "VerticalMoveTo",
+                new { pos = verNewValue, motorDelay = tLSetting.motorDelay }, new { houseSn = CurrentHouseId });
             CurrentVer = verNewValue;
             CurrentFocal = 1;
         }
@@ -1041,14 +1035,13 @@ namespace ivf_tl_Operate.ViewModel
         /// 设置曝光值
         /// </summary>
         /// <param name="newExporuse"></param>
-        public void SetExposure(int newExporuse)
-        {
-            if (Cam == null) { AddMessageInfo("设置曝光失败:相机句柄为空"); return; }
-            // M8-G3-1:设置相机曝光为相机设备命令入口,补操作日志埋点(module=对焦调试)。
-            var ret = Aivfo.OperationLog.OperationLogger.Run("对焦调试", "设置相机曝光值",
-                () => Cam.SetExposure(newExporuse),
-                input: new { houseSn = CurrentHouseId, ccdId = CurrentCCDId, newExporuse });
-            AddMessageInfo($"设置曝光结果:{ret}");
+        public async Task SetExposure(int newExporuse)
+        {
+            // M8-G3-1:设置相机曝光为相机设备命令入口,补操作日志埋点(module=对焦调试)。
+            // Task3.4b:operate 相机空壳,曝光改走 control 借用的真相机(control 端 Execute 新增 SetExposure op)。
+            var r = await RunDebugCommandAsync("设置相机曝光值", "SetExposure", new { value = newExporuse },
+                new { houseSn = CurrentHouseId, ccdId = CurrentCCDId, newExporuse });
+            if (r != null && r.Ok) AddMessageInfo($"设置曝光结果:{r.Result}");
         }
 
         /// <summary>