using System;
using System.Diagnostics;
using System.Threading;
using ivf_tl_Control; // AppData, StartMain
using IvfTl.Control.Services; // Log4netHelper(程序集 ivf_tl_Control_Services;切勿写 operate 端 ivf_tl_Services)
namespace IvfTl.ControlHost
{
public static class Program
{
private static Mutex _singleton;
private static ControlHttpServer _http;
private static volatile bool _started;
private static ManualResetEventSlim _exitEvent;
[STAThread]
public static int Main(string[] args)
{
// 1) 单实例:已有 control 在跑则立即退出(永远只有一个驱动机器)。
bool isNew;
_singleton = new Mutex(true, @"Global\ivf_tl_control_singleton", out isNew);
if (!isNew)
{
Log4netHelper.WriteLog("ControlHost: 已有实例在运行,本进程退出");
return 0;
}
try
{
var hostArgs = HostArgs.Parse(args);
Log4netHelper.WriteLog($"ControlHost 启动 port={hostArgs.Port} account={hostArgs.Account}");
// 2) 先起 HTTP(让 operate 能尽早探到"在启动中"),started=false。
// 阶段2:/status rich + /serial/pause|resume + /shutdown。
_http = new ControlHttpServer(
hostArgs.Port,
BuildStatus, // /ping 轻量
BuildRichStatus, // /status 完整快照(§6 三块)
HandleShutdown, // /shutdown 受护栏停机
HandleSerialPause, // /serial/pause 借串口让路
HandleSerialResume, // /serial/resume 归还恢复
msg => Log4netHelper.WriteLog(msg));
_http.Start();
// 3) 账号守卫(对齐 operate 空账号跳过逻辑)。
if (!hostArgs.IsValid)
{
Log4netHelper.WriteLog("ControlHost: 账号/密码为空,不启动采集(仅 HTTP 存活)");
}
else
{
// 4) 启动序放后台线程(复刻 operate MainWindow 的 Task.Run 形态):
// StartRun 内部会阻塞(InitTL 串口握手 + StartAsync().Wait()),不能占住主线程,
// 否则下面的 _exitEvent.Wait() 永不可达、阶段2 /shutdown 也无从优雅停。
// 主线程只负责驻留;HTTP 在独立 Task 上,采集起没起都能被 operate 探活。
System.Threading.Tasks.Task.Run(() => RunStartupSequence(hostArgs));
}
// 5) 驻留:主线程阻塞等退出信号(阶段2 的 /shutdown 会 Set 此事件)。
_exitEvent = new ManualResetEventSlim(false);
_exitEvent.Wait();
return 0;
}
catch (Exception ex)
{
Log4netHelper.WriteLog("ControlHost 致命异常", ex);
return 1;
}
finally
{
try { _http?.Stop(); } catch { }
try { _singleton?.ReleaseMutex(); } catch { }
}
}
///
/// control 启动序(后台线程跑,复刻 operate MainWindow 顺序,顺序不可变):
/// Login → 设缓存盘 → HAL.ScanDevices → StartMain.StartRun。
/// 任一步失败仅记日志降级,不退进程(HTTP 仍存活,operate 可探活到"未就绪")。
///
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);
}
}
/// 提供给 HTTP /status 的快照(阶段1:基础存活;阶段2 接 GetMonitorSnapshot 补全)。
private static StatusDto BuildStatus()
{
string tlSn = "";
try { tlSn = AppData.Instance.TLSetting?.tlSn ?? ""; } catch { }
return new StatusDto
{
Ok = true,
Pid = Process.GetCurrentProcess().Id,
TlSn = tlSn,
Started = _started
};
}
/// /status 完整快照:基础存活 + control 现有 MonitorSnapshot(阶段2 §6 三块已在快照内补全)。
private static object BuildRichStatus()
{
var head = BuildStatus();
object snapshot = null;
try { snapshot = AppData.Instance.GetMonitorSnapshot(); } catch { }
return new
{
ok = head.Ok,
pid = head.Pid,
tlSn = head.TlSn,
started = head.Started,
snapshot
};
}
/// /shutdown 受护栏停机:校验工程师口令(App.config engineerPwd,默认 tl13579),通过则安全停机。
private static bool HandleShutdown(string token)
{
string engineerPwd = "tl13579";
try
{
var cfg = System.Configuration.ConfigurationManager.AppSettings["engineerPwd"];
if (!string.IsNullOrEmpty(cfg)) engineerPwd = cfg;
}
catch { }
if (string.IsNullOrEmpty(token) || token != engineerPwd)
{
Log4netHelper.WriteLog("ControlHost: /shutdown 口令校验未通过,拒绝停机");
return false;
}
Log4netHelper.WriteLog("ControlHost: /shutdown 口令通过,执行安全停机");
// 异步触发,先把 HTTP 200 回给 operate,再停机退出。
System.Threading.Tasks.Task.Run(() =>
{
try { System.Threading.Thread.Sleep(300); SafeShutdown(); }
catch (Exception ex) { Log4netHelper.WriteLog("ControlHost: 停机异常", ex); _exitEvent?.Set(); }
});
return true;
}
/// 统一安全停机:关相机/串口句柄(ShutdownAll)→ Set 退出事件(主线程 finally 停 HTTP+释放 Mutex)。
private static void SafeShutdown()
{
try
{
IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.ShutdownAll();
Log4netHelper.WriteLog("ControlHost: HardwareAccessLayer.ShutdownAll 完成");
}
catch (Exception ex) { Log4netHelper.WriteLog("ControlHost: ShutdownAll 异常", ex); }
_exitEvent?.Set();
}
/// /serial/pause 借串口:置该舱 HouseGate 暂停标志,control 采集节拍据此让路(不驱动电机)。
private static bool HandleSerialPause(int houseSn)
{
try
{
var gate = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.GetHouseGate(houseSn);
if (gate == null) return false;
gate.PauseCapture();
Log4netHelper.WriteLog($"ControlHost: /serial/pause 舱{houseSn} 已让路(暂停采集)");
return true;
}
catch (Exception ex) { Log4netHelper.WriteLog($"ControlHost: /serial/pause 舱{houseSn} 异常", ex); return false; }
}
/// /serial/resume 归还:清该舱让路标志,恢复采集。
private static bool HandleSerialResume(int houseSn)
{
try
{
var gate = IvfTl.Hardware.Impl.HardwareAccessLayer.Instance.GetHouseGate(houseSn);
if (gate == null) return false;
gate.ResumeCapture();
Log4netHelper.WriteLog($"ControlHost: /serial/resume 舱{houseSn} 已恢复采集");
return true;
}
catch (Exception ex) { Log4netHelper.WriteLog($"ControlHost: /serial/resume 舱{houseSn} 异常", ex); return false; }
}
}
}