Просмотр исходного кода

feat(control): ControlHost 自带 App.config + 启动序移入后台线程

阶段1-Task7 真机验证发现并修正:
1. ControlHost 必须自带 App.config(独立进程下 ivf_tl_Control.AppData 构造读
   gbTime/csTime/VentNum 等键,缺键 .ToString() NPE);内容=operate 业务键+连接键,
   构建后转为 ivf_tl_ControlHost.dll.config。
2. StartRun 改在后台线程跑(复刻 operate Task.Run 形态):原在主线程直跑 StartRun,
   其内部 serialBin 握手/StartAsync().Wait() 阻塞会占住主线程,使 _exitEvent.Wait()
   (阶段2 /shutdown 钩子)永不可达。改后 HTTP 即刻可达、主线程驻留 _exitEvent。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie 3 дней назад
Родитель
Сommit
600075e6ef

+ 70 - 0
ivf_tl_operate_2.0/control/ivf_tl_ControlHost/App.config

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+	<appSettings>
+		<!-- ================= ControlHost 独立进程自带 config =================
+		     control 的 ivf_tl_Control.AppData 构造期(AppData.cs:49-75)会读以下键;
+		     gbTime/csTime/VentNum/VentPre/VentWaitTimeB/VentWaitTimeD/AutoWaitTime/
+		     CCDAutoWaitTime/CCDError/CCDFailedNumber/CCDFailedWaitTime/QueuAir 用 .ToString()
+		     缺键直接 NPE(仅 url/kfka/mqtt 三组有 null 容错)。
+		     拆独立进程后入口程序集=ivf_tl_ControlHost.exe,必须自带此 config;
+		     内容与 operate ivf_tl_Operate/App.config 的 control 业务键 + 连接键保持同步。 -->
+		<!--内网接口的IP地址-->
+		<add key="urlIp" value="http://127.0.0.1" />
+		<!--内网接口的端口(本地网关 gateway 10010)-->
+		<add key="urlPort" value="10010" />
+		<!--内网mqtt的IP地址(broker 在 108 服务器)-->
+		<add key="mqttIp" value="192.168.0.108"/>
+		<!--内网mqtt的端口-->
+		<add key="mqttPort" value="1883"/>
+		<!--Kafka 地址(oplog 发送端,108 服务器)-->
+		<add key="kfkaIP" value="192.168.0.108"/>
+		<add key="kfkaPort" value="9092"/>
+		<!--上一次登录的用户名-->
+		<add key="userName" value="admin"/>
+		<!--上一次登录的密码-->
+		<add key="passWord" value="123456"/>
+		<!--工程师口令-->
+		<add key="engineerPwd" value="tl13579"/>
+		<!--上一次使用的TL设备编号-->
+		<add key="tlNum" value="20230411"/>
+		<!--不可用模块,英文逗号隔开-->
+		<add key="houseEnabled" value=""/>
+		<!--多语言配置文件名-->
+		<add key="Language" value="Chinese.xaml"/>
+		<!--1表示下位机烧录了换气,0表示没有-->
+		<add key="autoFocus" value="0"/>
+		<!--0表示内网,1表示外网-->
+		<add key="outInter" value="0"/>
+		<!-- ================= control 业务键(换气/CCD,缺键 NPE) ================= -->
+		<!--1直接拍照异常,0正常拍照-->
+		<add key="CCDError" value="0" />
+		<!--换气时冲刷时间,单位秒-->
+		<add key="csTime" value="15" />
+		<!--换气结束时,缓冲瓶关闭进气阀后等待时间,单位秒-->
+		<add key="gbTime" value="10" />
+		<!--最大排气次数-->
+		<add key="VentNum" value="10" />
+		<!--排气目标气压-->
+		<add key="VentPre" value="20" />
+		<!--平衡时排气阀打开间歇时间,单位毫秒-->
+		<add key="VentWaitTimeB" value="4000" />
+		<!--培养时排气阀打开间歇时间,单位毫秒-->
+		<add key="VentWaitTimeD" value="4000" />
+		<!--自动对焦前,漏气补气时间,单位分钟-->
+		<add key="AutoWaitTime" value="10" />
+		<!--拍照失败后,自动对焦等待时间,单位分钟-->
+		<add key="CCDAutoWaitTime" value="5" />
+		<!--拍照失败后,等待时间,单位秒-->
+		<add key="CCDFailedWaitTime" value="15" />
+		<!--拍照异常报警次数-->
+		<add key="CCDFailedNumber" value="3" />
+		<!--0表示15分钟定时换气,1表示排队换气-->
+		<add key="QueuAir" value="0"/>
+		<!--关闭软件倒计时,单位秒-->
+		<add key="StopPro" value="15"/>
+		<!--缓存盘符-->
+		<add key="cacheDisk" value="C" />
+		<!--control 本地HTTP端口(operate 探活/拉起用同一端口)-->
+		<add key="controlPort" value="38080" />
+	</appSettings>
+</configuration>

+ 53 - 35
ivf_tl_operate_2.0/control/ivf_tl_ControlHost/Program.cs

@@ -44,43 +44,14 @@ namespace IvfTl.ControlHost
                 }
                 else
                 {
-                    // 4) 复刻 operate MainWindow 启动序(顺序不可变)。
-                    if (!AppData.Instance.Login(hostArgs.Account, hostArgs.Password))
-                    {
-                        Log4netHelper.WriteLog("ControlHost: control 登录失败");
-                    }
-                    else
-                    {
-                        if (!string.IsNullOrEmpty(hostArgs.CacheDisk))
-                        {
-                            ivf_tl_UtilHelper.PathHelper.pan = hostArgs.CacheDisk;
-                            AppData.Instance.LogService.Pan = hostArgs.CacheDisk;
-                        }
-                        try
-                        {
-                            IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.Log =
-                                msg => Log4netHelper.WriteLog(msg);
-                            var devices = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ScanDevices();
-                            Log4netHelper.WriteLog($"ControlHost: HAL 发现 {devices.Count} 个舱");
-                        }
-                        catch (Exception hex)
-                        {
-                            Log4netHelper.WriteLog("ControlHost: HAL 发现异常(降级):" + hex.Message);
-                        }
-
-                        var startMain = new StartMain();
-                        string err = startMain.StartRun(); // 阻塞:InitTL→InitHouse→StartAsync
-                        if (!string.IsNullOrEmpty(err))
-                            Log4netHelper.WriteLog("ControlHost: control 启动失败:" + err);
-                        else
-                        {
-                            _started = true;
-                            Log4netHelper.WriteLog("ControlHost: control 启动成功,常驻运行");
-                        }
-                    }
+                    // 4) 启动序放后台线程(复刻 operate MainWindow 的 Task.Run 形态):
+                    //    StartRun 内部会阻塞(InitTL 串口握手 + StartAsync().Wait()),不能占住主线程,
+                    //    否则下面的 _exitEvent.Wait() 永不可达、阶段2 /shutdown 也无从优雅停。
+                    //    主线程只负责驻留;HTTP 在独立 Task 上,采集起没起都能被 operate 探活。
+                    System.Threading.Tasks.Task.Run(() => RunStartupSequence(hostArgs));
                 }
 
-                // 5) 驻留:control 后台线程已起(LongRunning),主线程阻塞等退出信号。
+                // 5) 驻留:主线程阻塞等退出信号(阶段2 的 /shutdown 会 Set 此事件)。
                 _exitEvent = new ManualResetEventSlim(false);
                 _exitEvent.Wait();
                 return 0;
@@ -97,6 +68,53 @@ namespace IvfTl.ControlHost
             }
         }
 
+        /// <summary>
+        /// control 启动序(后台线程跑,复刻 operate MainWindow 顺序,顺序不可变):
+        /// Login → 设缓存盘 → HAL.ScanDevices → StartMain.StartRun。
+        /// 任一步失败仅记日志降级,不退进程(HTTP 仍存活,operate 可探活到"未就绪")。
+        /// </summary>
+        private static void RunStartupSequence(HostArgs hostArgs)
+        {
+            try
+            {
+                if (!AppData.Instance.Login(hostArgs.Account, hostArgs.Password))
+                {
+                    Log4netHelper.WriteLog("ControlHost: control 登录失败");
+                    return;
+                }
+                if (!string.IsNullOrEmpty(hostArgs.CacheDisk))
+                {
+                    ivf_tl_UtilHelper.PathHelper.pan = hostArgs.CacheDisk;
+                    AppData.Instance.LogService.Pan = hostArgs.CacheDisk;
+                }
+                try
+                {
+                    IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.Log =
+                        msg => Log4netHelper.WriteLog(msg);
+                    var devices = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ScanDevices();
+                    Log4netHelper.WriteLog($"ControlHost: HAL 发现 {devices.Count} 个舱");
+                }
+                catch (Exception hex)
+                {
+                    Log4netHelper.WriteLog("ControlHost: HAL 发现异常(降级):" + hex.Message);
+                }
+
+                var startMain = new StartMain();
+                string err = startMain.StartRun(); // 阻塞:InitTL→InitHouse→StartAsync
+                if (!string.IsNullOrEmpty(err))
+                    Log4netHelper.WriteLog("ControlHost: control 启动失败:" + err);
+                else
+                {
+                    _started = true;
+                    Log4netHelper.WriteLog("ControlHost: control 启动成功,常驻运行");
+                }
+            }
+            catch (Exception ex)
+            {
+                Log4netHelper.WriteLog("ControlHost: 启动序异常(降级,HTTP 仍存活)", ex);
+            }
+        }
+
         /// <summary>提供给 HTTP /status 的快照(阶段1:基础存活;阶段2 接 GetMonitorSnapshot 补全)。</summary>
         private static StatusDto BuildStatus()
         {