using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Threading; namespace IvfTl.Hardware.Impl { /// /// 统一硬件访问层单例(13 §3.1)。全进程唯一持有每个 COM 口与每台相机句柄。 /// 合并后由宿主(operate 主进程)在启动时初始化一次(Instance),并在 control StartRun 之前 ScanDevices()。 /// 字典缓存 + 锁保证:GetSerial(port)/GetCamera(index) 重复调用返回同一实例——杜绝同口重复 Open / 同相机重复 Init。 /// 代码隔离原则(01 §5):内部包装旧 ComBin/Camera/Channel,不改其内部逻辑。 /// ⚠ 线程安全见 V-009:所有字典访问经 _lock;单例用 Lazy 双检。 /// public sealed class HardwareAccessLayer : IHardwareAccessLayer { private static readonly Lazy _instance = new Lazy(() => new HardwareAccessLayer(), LazyThreadSafetyMode.ExecutionAndPublication); /// 进程级单例。 public static HardwareAccessLayer Instance => _instance.Value; private readonly object _lock = new object(); private readonly Dictionary _serials = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _cameras = new Dictionary(); private readonly Dictionary _gates = new Dictionary(); // 设备发现结果:houseSn → 设备信息(port + 配对相机 index)。 private readonly Dictionary _houses = new Dictionary(); private readonly CameraGateImpl _cameraGate = new CameraGateImpl(); /// 日志回调(宿主可注入到 LogService)。 public Action Log { get; set; } private HardwareAccessLayer() { } public ICameraGate CameraGate => _cameraGate; // ── 唯一持有:串口 ── public ISerialChannel GetSerial(string portName) => GetSerial(0, portName); public ISerialChannel GetSerial(int houseId, string portName) { if (string.IsNullOrEmpty(portName)) throw new ArgumentNullException(nameof(portName)); lock (_lock) { if (!_serials.TryGetValue(portName, out var ch)) { // houseId 仅用于 ComBin 内部日志;唯一持有以 portName 为键。 ch = new SerialChannelImpl(houseId, portName); _serials[portName] = ch; } return ch; } } // ── 唯一持有:相机 ── public ICamera GetCamera(int cameraIndex) => GetCamera(cameraIndex, 1600, 1200, 400); public ICamera GetCamera(int cameraIndex, int width, int height, int exposure) { lock (_lock) { if (!_cameras.TryGetValue(cameraIndex, out var cam)) { cam = new CameraImpl(cameraIndex, width, height, exposure, _cameraGate); _cameras[cameraIndex] = cam; } return cam; } } // ── 按舱取句柄组 ── public IHouseHandle GetHouse(int houseSn) { lock (_lock) { if (!_houses.TryGetValue(houseSn, out var info)) { throw new InvalidOperationException($"舱 {houseSn} 未在 ScanDevices 中发现,无法取句柄组。请先 ScanDevices()。"); } ISerialChannel serial = GetSerialNoLock(info.PortName); ICamera camera = info.CcdIndex >= 0 ? GetCameraNoLock(info.CcdIndex) : null; IHouseGate gate = GetHouseGateNoLock(houseSn, info); return new HouseHandle(houseSn, serial, camera, gate); } } public IHouseGate GetHouseGate(int houseSn) { lock (_lock) { _houses.TryGetValue(houseSn, out var info); return GetHouseGateNoLock(houseSn, info); } } private ISerialChannel GetSerialNoLock(string portName) { if (!_serials.TryGetValue(portName, out var ch)) { ch = new SerialChannelImpl(0, portName); _serials[portName] = ch; } return ch; } private ICamera GetCameraNoLock(int cameraIndex) { if (!_cameras.TryGetValue(cameraIndex, out var cam)) { cam = new CameraImpl(cameraIndex, 1600, 1200, 400, _cameraGate); _cameras[cameraIndex] = cam; } return cam; } private IHouseGate GetHouseGateNoLock(int houseSn, HouseDeviceInfo info) { if (!_gates.TryGetValue(houseSn, out var gate)) { string port = info?.PortName; int idx = info?.CcdIndex ?? -1; gate = new HouseGateImpl( houseSn, () => port != null ? GetSerialNoLock(port) : null, () => idx >= 0 ? GetCameraNoLock(idx) : null); _gates[houseSn] = gate; } return gate; } // ── 设备发现 ── public IReadOnlyList ScanDevices() { // 复刻 af DeviceScanner.ScanAll / control SerialBin.UpdataCamera+Start: // ① 枚举相机 0..9 读 SN,得 ccdSn → cameraIndex; // ② 扫描 COM 口握手得 houseSn + 读 EEPROM 得该舱 CCDSN; // ③ 按 CCDSN 把舱与相机 index 配对。 // ⚠ 13 §⑤ V7 / V-009:index/COM 每次开机可变,必须现场重扫,禁止写死。 var result = new List(); // ① 相机 SN → index // 用临时相机实例读 SN(不进 HAL 缓存):避免以默认分辨率缓存相机, // 采集端随后用各舱实际 ccdWidth/ccdHeight 经 GetCamera(index,w,h,exp) 惰性创建正确实例。 var snToIndex = new Dictionary(); for (int i = 0; i < 10; i++) { ICamera cam = new CameraImpl(i, 100, 100, 100, _cameraGate); try { if (cam.Init() == 0) { cam.ReadSerial(); string sn = cam.SerialNumber; if (!string.IsNullOrEmpty(sn) && !snToIndex.ContainsKey(sn)) snToIndex[sn] = i; cam.UnInit(); } } catch (Exception ex) { Log?.Invoke($"[HAL] 枚举相机 {i} 异常: {ex.Message}"); } finally { try { cam.Dispose(); } catch { } } } // ② / ③ 扫 COM 口握手 + 配对 string[] ports; try { ports = SerialPort.GetPortNames(); } catch { ports = Array.Empty(); } foreach (var port in ports) { if (port == "COM1" || port == "COM2" || !port.Contains("COM")) continue; ISerialChannel ch = GetSerial(port); try { if (!ch.Open()) { Log?.Invoke($"[HAL] {port} 打开失败"); continue; } int houseSn = ch.ShakeHandsWait(); if (houseSn < 0) { Log?.Invoke($"[HAL] {port} 握手失败"); continue; } string ccdSn = null; int ccdIndex = -1; if (houseSn != 11) // 11 = 缓冲瓶/总控模块,无相机 { int ccd = ch.ReadCcdSnWait(); if (ccd >= 0) { ccdSn = ccd.ToString(); if (snToIndex.TryGetValue(ccdSn, out int idx)) ccdIndex = idx; } } var info = new HouseDeviceInfo { HouseSn = houseSn, PortName = port, CcdSn = ccdSn, CcdIndex = ccdIndex }; lock (_lock) { _houses[houseSn] = info; } result.Add(info); Log?.Invoke($"[HAL] 发现舱 {houseSn} @ {port}, CCDSN={ccdSn}, index={ccdIndex}"); } catch (Exception ex) { Log?.Invoke($"[HAL] 扫描 {port} 异常: {ex.Message}"); } finally { try { ch.Close(); } catch { } } } return result; } // ── 采集端补登记设备表(ScanDevices 现场漏扫某舱时的双保险;T1.4)── public void RegisterHouse(int houseSn, string portName, int ccdIndex) { if (string.IsNullOrEmpty(portName)) return; lock (_lock) { // ScanDevices(登录后由 MainWindow 调)若已发现本舱,以其现场实时枚举结果为准、不覆盖; // 仅补 ScanDevices 漏扫(如该舱握手失败)但采集端有服务器配置的舱,使其借用仍可用。 if (_houses.ContainsKey(houseSn)) return; _houses[houseSn] = new HouseDeviceInfo { HouseSn = houseSn, PortName = portName, CcdSn = null, CcdIndex = ccdIndex }; _gates.Remove(houseSn); } } // ── 统一关闭 ── public void ShutdownAll() { lock (_lock) { foreach (var c in _cameras.Values) { try { c.Dispose(); } catch { } } foreach (var s in _serials.Values) { try { s.Dispose(); } catch { } } _cameras.Clear(); _serials.Clear(); _gates.Clear(); _houses.Clear(); } } private sealed class HouseHandle : IHouseHandle { public HouseHandle(int sn, ISerialChannel serial, ICamera camera, IHouseGate gate) { HouseSn = sn; Serial = serial; Camera = camera; Gate = gate; } public int HouseSn { get; } public ISerialChannel Serial { get; } public ICamera Camera { get; } public IHouseGate Gate { get; } } } }