HardwareAccessLayer.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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. // 默认分辨率重载(SN 枚举等过渡用途):已建则原样返回,绝不把已按正确分辨率建好的实例降级重建。
  50. public ICamera GetCamera(int cameraIndex)
  51. {
  52. lock (_lock)
  53. {
  54. if (_cameras.TryGetValue(cameraIndex, out var cam)) return cam;
  55. cam = new CameraImpl(cameraIndex, 1600, 1200, 400, _cameraGate);
  56. _cameras[cameraIndex] = cam;
  57. return cam;
  58. }
  59. }
  60. public ICamera GetCamera(int cameraIndex, int width, int height, int exposure)
  61. {
  62. lock (_lock)
  63. {
  64. if (_cameras.TryGetValue(cameraIndex, out var cam))
  65. {
  66. // D2-02 多舱拍照崩溃根因修复:缓存相机分辨率与本次请求不一致时,丢弃旧实例按正确分辨率重建。
  67. // 背景:开机 SN 枚举经默认重载先把相机缓存成 1600×1200,采集端实际需 ccdWidth×ccdHeight(如 2592×1944);
  68. // HAL 原"首建为准"会返回那台 1600×1200 → SourceBuffer 仅出 5.76MB 缓冲,而 Save2 按 2592×1944 读 15MB
  69. // → 越界读砸坏托管堆 → coreclr 固定偏移 AV(createdump 实证:byte[]=5,760,000 而 width*height*3=15,116,544)。
  70. if (cam.Width == width && cam.Height == height) return cam;
  71. Log?.Invoke($"[HAL] 相机 {cameraIndex} 缓存分辨率 {cam.Width}x{cam.Height} 与请求 {width}x{height} 不符,按正确分辨率重建。");
  72. try { cam.Dispose(); } catch { }
  73. _cameras.Remove(cameraIndex);
  74. }
  75. cam = new CameraImpl(cameraIndex, width, height, exposure, _cameraGate);
  76. _cameras[cameraIndex] = cam;
  77. return cam;
  78. }
  79. }
  80. // ── 按舱取句柄组 ──
  81. public IHouseHandle GetHouse(int houseSn)
  82. {
  83. lock (_lock)
  84. {
  85. if (!_houses.TryGetValue(houseSn, out var info))
  86. {
  87. throw new InvalidOperationException($"舱 {houseSn} 未在 ScanDevices 中发现,无法取句柄组。请先 ScanDevices()。");
  88. }
  89. ISerialChannel serial = GetSerialNoLock(info.PortName);
  90. ICamera camera = info.CcdIndex >= 0 ? GetCameraNoLock(info.CcdIndex) : null;
  91. IHouseGate gate = GetHouseGateNoLock(houseSn, info);
  92. return new HouseHandle(houseSn, serial, camera, gate);
  93. }
  94. }
  95. public IHouseGate GetHouseGate(int houseSn)
  96. {
  97. lock (_lock)
  98. {
  99. _houses.TryGetValue(houseSn, out var info);
  100. return GetHouseGateNoLock(houseSn, info);
  101. }
  102. }
  103. private ISerialChannel GetSerialNoLock(string portName)
  104. {
  105. if (!_serials.TryGetValue(portName, out var ch))
  106. {
  107. ch = new SerialChannelImpl(0, portName);
  108. _serials[portName] = ch;
  109. }
  110. return ch;
  111. }
  112. private ICamera GetCameraNoLock(int cameraIndex)
  113. {
  114. if (!_cameras.TryGetValue(cameraIndex, out var cam))
  115. {
  116. cam = new CameraImpl(cameraIndex, 1600, 1200, 400, _cameraGate);
  117. _cameras[cameraIndex] = cam;
  118. }
  119. return cam;
  120. }
  121. private IHouseGate GetHouseGateNoLock(int houseSn, HouseDeviceInfo info)
  122. {
  123. if (!_gates.TryGetValue(houseSn, out var gate))
  124. {
  125. string port = info?.PortName;
  126. int idx = info?.CcdIndex ?? -1;
  127. gate = new HouseGateImpl(
  128. houseSn,
  129. () => port != null ? GetSerialNoLock(port) : null,
  130. () => idx >= 0 ? GetCameraNoLock(idx) : null);
  131. _gates[houseSn] = gate;
  132. }
  133. return gate;
  134. }
  135. // ── 设备发现 ──
  136. public IReadOnlyList<HouseDeviceInfo> ScanDevices()
  137. {
  138. // 复刻 af DeviceScanner.ScanAll / control SerialBin.UpdataCamera+Start:
  139. // ① 枚举相机 0..9 读 SN,得 ccdSn → cameraIndex;
  140. // ② 扫描 COM 口握手得 houseSn + 读 EEPROM 得该舱 CCDSN;
  141. // ③ 按 CCDSN 把舱与相机 index 配对。
  142. // ⚠ 13 §⑤ V7 / V-009:index/COM 每次开机可变,必须现场重扫,禁止写死。
  143. var result = new List<HouseDeviceInfo>();
  144. // ① 相机 SN → index
  145. // 用临时相机实例读 SN(不进 HAL 缓存):避免以默认分辨率缓存相机,
  146. // 采集端随后用各舱实际 ccdWidth/ccdHeight 经 GetCamera(index,w,h,exp) 惰性创建正确实例。
  147. var snToIndex = new Dictionary<string, int>();
  148. for (int i = 0; i < 10; i++)
  149. {
  150. ICamera cam = new CameraImpl(i, 100, 100, 100, _cameraGate);
  151. try
  152. {
  153. if (cam.Init() == 0)
  154. {
  155. cam.ReadSerial();
  156. string sn = cam.SerialNumber;
  157. if (!string.IsNullOrEmpty(sn) && !snToIndex.ContainsKey(sn))
  158. snToIndex[sn] = i;
  159. cam.UnInit();
  160. }
  161. }
  162. catch (Exception ex)
  163. {
  164. Log?.Invoke($"[HAL] 枚举相机 {i} 异常: {ex.Message}");
  165. }
  166. finally
  167. {
  168. try { cam.Dispose(); } catch { }
  169. }
  170. }
  171. // ② / ③ 扫 COM 口握手 + 配对
  172. string[] ports;
  173. try { ports = SerialPort.GetPortNames(); }
  174. catch { ports = Array.Empty<string>(); }
  175. foreach (var port in ports)
  176. {
  177. if (port == "COM1" || port == "COM2" || !port.Contains("COM")) continue;
  178. ISerialChannel ch = GetSerial(port);
  179. try
  180. {
  181. if (!ch.Open()) { Log?.Invoke($"[HAL] {port} 打开失败"); continue; }
  182. int houseSn = ch.ShakeHandsWait();
  183. if (houseSn < 0) { Log?.Invoke($"[HAL] {port} 握手失败"); continue; }
  184. string ccdSn = null;
  185. int ccdIndex = -1;
  186. if (houseSn != 11) // 11 = 缓冲瓶/总控模块,无相机
  187. {
  188. int ccd = ch.ReadCcdSnWait();
  189. if (ccd >= 0)
  190. {
  191. ccdSn = ccd.ToString();
  192. if (snToIndex.TryGetValue(ccdSn, out int idx)) ccdIndex = idx;
  193. }
  194. }
  195. var info = new HouseDeviceInfo
  196. {
  197. HouseSn = houseSn,
  198. PortName = port,
  199. CcdSn = ccdSn,
  200. CcdIndex = ccdIndex
  201. };
  202. lock (_lock) { _houses[houseSn] = info; }
  203. result.Add(info);
  204. Log?.Invoke($"[HAL] 发现舱 {houseSn} @ {port}, CCDSN={ccdSn}, index={ccdIndex}");
  205. }
  206. catch (Exception ex)
  207. {
  208. Log?.Invoke($"[HAL] 扫描 {port} 异常: {ex.Message}");
  209. }
  210. finally
  211. {
  212. try { ch.Close(); } catch { }
  213. }
  214. }
  215. return result;
  216. }
  217. // ── 采集端补登记设备表(ScanDevices 现场漏扫某舱时的双保险;T1.4)──
  218. public void RegisterHouse(int houseSn, string portName, int ccdIndex)
  219. {
  220. if (string.IsNullOrEmpty(portName)) return;
  221. lock (_lock)
  222. {
  223. // ScanDevices(登录后由 MainWindow 调)若已发现本舱,以其现场实时枚举结果为准、不覆盖;
  224. // 仅补 ScanDevices 漏扫(如该舱握手失败)但采集端有服务器配置的舱,使其借用仍可用。
  225. if (_houses.ContainsKey(houseSn)) return;
  226. _houses[houseSn] = new HouseDeviceInfo
  227. {
  228. HouseSn = houseSn,
  229. PortName = portName,
  230. CcdSn = null,
  231. CcdIndex = ccdIndex
  232. };
  233. _gates.Remove(houseSn);
  234. }
  235. }
  236. // ── 统一关闭 ──
  237. public void ShutdownAll()
  238. {
  239. lock (_lock)
  240. {
  241. foreach (var c in _cameras.Values) { try { c.Dispose(); } catch { } }
  242. foreach (var s in _serials.Values) { try { s.Dispose(); } catch { } }
  243. _cameras.Clear();
  244. _serials.Clear();
  245. _gates.Clear();
  246. _houses.Clear();
  247. }
  248. }
  249. private sealed class HouseHandle : IHouseHandle
  250. {
  251. public HouseHandle(int sn, ISerialChannel serial, ICamera camera, IHouseGate gate)
  252. {
  253. HouseSn = sn; Serial = serial; Camera = camera; Gate = gate;
  254. }
  255. public int HouseSn { get; }
  256. public ISerialChannel Serial { get; }
  257. public ICamera Camera { get; }
  258. public IHouseGate Gate { get; }
  259. }
  260. }
  261. }