HardwareAccessLayer.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO.Ports;
  4. using System.Linq;
  5. using System.Threading;
  6. namespace IvfTl.Hardware.Impl
  7. {
  8. /// <summary>
  9. /// 统一硬件访问层单例(13 §3.1)。全进程唯一持有每个 COM 口与每台相机句柄。
  10. /// 合并后由宿主(operate 主进程)在启动时初始化一次(Instance),并在 control StartRun 之前 ScanDevices()。
  11. /// 字典缓存 + 锁保证:GetSerial(port)/GetCamera(index) 重复调用返回同一实例——杜绝同口重复 Open / 同相机重复 Init。
  12. /// 代码隔离原则(01 §5):内部包装旧 ComBin/Camera/Channel,不改其内部逻辑。
  13. /// ⚠ 线程安全见 V-009:所有字典访问经 _lock;单例用 Lazy 双检。
  14. /// </summary>
  15. public sealed class HardwareAccessLayer : IHardwareAccessLayer
  16. {
  17. private static readonly Lazy<HardwareAccessLayer> _instance =
  18. new Lazy<HardwareAccessLayer>(() => new HardwareAccessLayer(), LazyThreadSafetyMode.ExecutionAndPublication);
  19. /// <summary>进程级单例。</summary>
  20. public static HardwareAccessLayer Instance => _instance.Value;
  21. private readonly object _lock = new object();
  22. private readonly Dictionary<string, ISerialChannel> _serials = new Dictionary<string, ISerialChannel>(StringComparer.OrdinalIgnoreCase);
  23. private readonly Dictionary<int, ICamera> _cameras = new Dictionary<int, ICamera>();
  24. private readonly Dictionary<int, IHouseGate> _gates = new Dictionary<int, IHouseGate>();
  25. // 设备发现结果:houseSn → 设备信息(port + 配对相机 index)。
  26. private readonly Dictionary<int, HouseDeviceInfo> _houses = new Dictionary<int, HouseDeviceInfo>();
  27. private readonly CameraGateImpl _cameraGate = new CameraGateImpl();
  28. /// <summary>日志回调(宿主可注入到 LogService)。</summary>
  29. public Action<string> Log { get; set; }
  30. private HardwareAccessLayer() { }
  31. public ICameraGate CameraGate => _cameraGate;
  32. // ── 唯一持有:串口 ──
  33. public ISerialChannel GetSerial(string portName) => GetSerial(0, portName);
  34. public ISerialChannel GetSerial(int houseId, string portName)
  35. {
  36. if (string.IsNullOrEmpty(portName)) throw new ArgumentNullException(nameof(portName));
  37. lock (_lock)
  38. {
  39. if (!_serials.TryGetValue(portName, out var ch))
  40. {
  41. // houseId 仅用于 ComBin 内部日志;唯一持有以 portName 为键。
  42. ch = new SerialChannelImpl(houseId, portName);
  43. _serials[portName] = ch;
  44. }
  45. return ch;
  46. }
  47. }
  48. // ── 唯一持有:相机 ──
  49. public ICamera GetCamera(int cameraIndex) => GetCamera(cameraIndex, 1600, 1200, 400);
  50. public ICamera GetCamera(int cameraIndex, int width, int height, int exposure)
  51. {
  52. lock (_lock)
  53. {
  54. if (!_cameras.TryGetValue(cameraIndex, out var cam))
  55. {
  56. cam = new CameraImpl(cameraIndex, width, height, exposure, _cameraGate);
  57. _cameras[cameraIndex] = cam;
  58. }
  59. return cam;
  60. }
  61. }
  62. // ── 按舱取句柄组 ──
  63. public IHouseHandle GetHouse(int houseSn)
  64. {
  65. lock (_lock)
  66. {
  67. if (!_houses.TryGetValue(houseSn, out var info))
  68. {
  69. throw new InvalidOperationException($"舱 {houseSn} 未在 ScanDevices 中发现,无法取句柄组。请先 ScanDevices()。");
  70. }
  71. ISerialChannel serial = GetSerialNoLock(info.PortName);
  72. ICamera camera = info.CcdIndex >= 0 ? GetCameraNoLock(info.CcdIndex) : null;
  73. IHouseGate gate = GetHouseGateNoLock(houseSn, info);
  74. return new HouseHandle(houseSn, serial, camera, gate);
  75. }
  76. }
  77. public IHouseGate GetHouseGate(int houseSn)
  78. {
  79. lock (_lock)
  80. {
  81. _houses.TryGetValue(houseSn, out var info);
  82. return GetHouseGateNoLock(houseSn, info);
  83. }
  84. }
  85. private ISerialChannel GetSerialNoLock(string portName)
  86. {
  87. if (!_serials.TryGetValue(portName, out var ch))
  88. {
  89. ch = new SerialChannelImpl(0, portName);
  90. _serials[portName] = ch;
  91. }
  92. return ch;
  93. }
  94. private ICamera GetCameraNoLock(int cameraIndex)
  95. {
  96. if (!_cameras.TryGetValue(cameraIndex, out var cam))
  97. {
  98. cam = new CameraImpl(cameraIndex, 1600, 1200, 400, _cameraGate);
  99. _cameras[cameraIndex] = cam;
  100. }
  101. return cam;
  102. }
  103. private IHouseGate GetHouseGateNoLock(int houseSn, HouseDeviceInfo info)
  104. {
  105. if (!_gates.TryGetValue(houseSn, out var gate))
  106. {
  107. string port = info?.PortName;
  108. int idx = info?.CcdIndex ?? -1;
  109. gate = new HouseGateImpl(
  110. houseSn,
  111. () => port != null ? GetSerialNoLock(port) : null,
  112. () => idx >= 0 ? GetCameraNoLock(idx) : null);
  113. _gates[houseSn] = gate;
  114. }
  115. return gate;
  116. }
  117. // ── 设备发现 ──
  118. public IReadOnlyList<HouseDeviceInfo> ScanDevices()
  119. {
  120. // 复刻 af DeviceScanner.ScanAll / control SerialBin.UpdataCamera+Start:
  121. // ① 枚举相机 0..9 读 SN,得 ccdSn → cameraIndex;
  122. // ② 扫描 COM 口握手得 houseSn + 读 EEPROM 得该舱 CCDSN;
  123. // ③ 按 CCDSN 把舱与相机 index 配对。
  124. // ⚠ 13 §⑤ V7 / V-009:index/COM 每次开机可变,必须现场重扫,禁止写死。
  125. var result = new List<HouseDeviceInfo>();
  126. // ① 相机 SN → index
  127. // 用临时相机实例读 SN(不进 HAL 缓存):避免以默认分辨率缓存相机,
  128. // 采集端随后用各舱实际 ccdWidth/ccdHeight 经 GetCamera(index,w,h,exp) 惰性创建正确实例。
  129. var snToIndex = new Dictionary<string, int>();
  130. for (int i = 0; i < 10; i++)
  131. {
  132. ICamera cam = new CameraImpl(i, 100, 100, 100, _cameraGate);
  133. try
  134. {
  135. if (cam.Init() == 0)
  136. {
  137. cam.ReadSerial();
  138. string sn = cam.SerialNumber;
  139. if (!string.IsNullOrEmpty(sn) && !snToIndex.ContainsKey(sn))
  140. snToIndex[sn] = i;
  141. cam.UnInit();
  142. }
  143. }
  144. catch (Exception ex)
  145. {
  146. Log?.Invoke($"[HAL] 枚举相机 {i} 异常: {ex.Message}");
  147. }
  148. finally
  149. {
  150. try { cam.Dispose(); } catch { }
  151. }
  152. }
  153. // ② / ③ 扫 COM 口握手 + 配对
  154. string[] ports;
  155. try { ports = SerialPort.GetPortNames(); }
  156. catch { ports = Array.Empty<string>(); }
  157. foreach (var port in ports)
  158. {
  159. if (port == "COM1" || port == "COM2" || !port.Contains("COM")) continue;
  160. ISerialChannel ch = GetSerial(port);
  161. try
  162. {
  163. if (!ch.Open()) { Log?.Invoke($"[HAL] {port} 打开失败"); continue; }
  164. int houseSn = ch.ShakeHandsWait();
  165. if (houseSn < 0) { Log?.Invoke($"[HAL] {port} 握手失败"); continue; }
  166. string ccdSn = null;
  167. int ccdIndex = -1;
  168. if (houseSn != 11) // 11 = 缓冲瓶/总控模块,无相机
  169. {
  170. int ccd = ch.ReadCcdSnWait();
  171. if (ccd >= 0)
  172. {
  173. ccdSn = ccd.ToString();
  174. if (snToIndex.TryGetValue(ccdSn, out int idx)) ccdIndex = idx;
  175. }
  176. }
  177. var info = new HouseDeviceInfo
  178. {
  179. HouseSn = houseSn,
  180. PortName = port,
  181. CcdSn = ccdSn,
  182. CcdIndex = ccdIndex
  183. };
  184. lock (_lock) { _houses[houseSn] = info; }
  185. result.Add(info);
  186. Log?.Invoke($"[HAL] 发现舱 {houseSn} @ {port}, CCDSN={ccdSn}, index={ccdIndex}");
  187. }
  188. catch (Exception ex)
  189. {
  190. Log?.Invoke($"[HAL] 扫描 {port} 异常: {ex.Message}");
  191. }
  192. finally
  193. {
  194. try { ch.Close(); } catch { }
  195. }
  196. }
  197. return result;
  198. }
  199. // ── 采集端补登记设备表(ScanDevices 现场漏扫某舱时的双保险;T1.4)──
  200. public void RegisterHouse(int houseSn, string portName, int ccdIndex)
  201. {
  202. if (string.IsNullOrEmpty(portName)) return;
  203. lock (_lock)
  204. {
  205. // ScanDevices(登录后由 MainWindow 调)若已发现本舱,以其现场实时枚举结果为准、不覆盖;
  206. // 仅补 ScanDevices 漏扫(如该舱握手失败)但采集端有服务器配置的舱,使其借用仍可用。
  207. if (_houses.ContainsKey(houseSn)) return;
  208. _houses[houseSn] = new HouseDeviceInfo
  209. {
  210. HouseSn = houseSn,
  211. PortName = portName,
  212. CcdSn = null,
  213. CcdIndex = ccdIndex
  214. };
  215. _gates.Remove(houseSn);
  216. }
  217. }
  218. // ── 统一关闭 ──
  219. public void ShutdownAll()
  220. {
  221. lock (_lock)
  222. {
  223. foreach (var c in _cameras.Values) { try { c.Dispose(); } catch { } }
  224. foreach (var s in _serials.Values) { try { s.Dispose(); } catch { } }
  225. _cameras.Clear();
  226. _serials.Clear();
  227. _gates.Clear();
  228. _houses.Clear();
  229. }
  230. }
  231. private sealed class HouseHandle : IHouseHandle
  232. {
  233. public HouseHandle(int sn, ISerialChannel serial, ICamera camera, IHouseGate gate)
  234. {
  235. HouseSn = sn; Serial = serial; Camera = camera; Gate = gate;
  236. }
  237. public int HouseSn { get; }
  238. public ISerialChannel Serial { get; }
  239. public ICamera Camera { get; }
  240. public IHouseGate Gate { get; }
  241. }
  242. }
  243. }