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; }
}
}
}