| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- using System;
- using System.Collections.Generic;
- using System.IO.Ports;
- using System.Linq;
- using System.Threading;
- namespace IvfTl.Hardware.Impl
- {
- /// <summary>
- /// 统一硬件访问层单例(13 §3.1)。全进程唯一持有每个 COM 口与每台相机句柄。
- /// 合并后由宿主(operate 主进程)在启动时初始化一次(Instance),并在 control StartRun 之前 ScanDevices()。
- /// 字典缓存 + 锁保证:GetSerial(port)/GetCamera(index) 重复调用返回同一实例——杜绝同口重复 Open / 同相机重复 Init。
- /// 代码隔离原则(01 §5):内部包装旧 ComBin/Camera/Channel,不改其内部逻辑。
- /// ⚠ 线程安全见 V-009:所有字典访问经 _lock;单例用 Lazy 双检。
- /// </summary>
- public sealed class HardwareAccessLayer : IHardwareAccessLayer
- {
- private static readonly Lazy<HardwareAccessLayer> _instance =
- new Lazy<HardwareAccessLayer>(() => new HardwareAccessLayer(), LazyThreadSafetyMode.ExecutionAndPublication);
- /// <summary>进程级单例。</summary>
- public static HardwareAccessLayer Instance => _instance.Value;
- private readonly object _lock = new object();
- private readonly Dictionary<string, ISerialChannel> _serials = new Dictionary<string, ISerialChannel>(StringComparer.OrdinalIgnoreCase);
- private readonly Dictionary<int, ICamera> _cameras = new Dictionary<int, ICamera>();
- private readonly Dictionary<int, IHouseGate> _gates = new Dictionary<int, IHouseGate>();
- // 设备发现结果:houseSn → 设备信息(port + 配对相机 index)。
- private readonly Dictionary<int, HouseDeviceInfo> _houses = new Dictionary<int, HouseDeviceInfo>();
- private readonly CameraGateImpl _cameraGate = new CameraGateImpl();
- /// <summary>日志回调(宿主可注入到 LogService)。</summary>
- public Action<string> 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<HouseDeviceInfo> 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<HouseDeviceInfo>();
- // ① 相机 SN → index
- // 用临时相机实例读 SN(不进 HAL 缓存):避免以默认分辨率缓存相机,
- // 采集端随后用各舱实际 ccdWidth/ccdHeight 经 GetCamera(index,w,h,exp) 惰性创建正确实例。
- var snToIndex = new Dictionary<string, int>();
- 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<string>(); }
- 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;
- }
- // ── 统一关闭 ──
- 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; }
- }
- }
- }
|