| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using System.Threading.Tasks;
- using TeamAAS.Camera.Attributes;
- using TeamAAS.Camera.Enums;
- using TeamAAS.Camera.Interfaces;
- using TeamAAS.Camera.Models;
- namespace TeamAAS.Camera
- {
- /// <summary>
- /// 相机生命周期和配置管理器。
- /// 负责相机实例的创建、注册、连接、采集控制、移除、JSON 配置持久化以及品牌/设备发现。
- /// 同时作为懒汉单例使用:<c>CameraManager.Instance</c>。
- /// </summary>
- public class CameraManager : ICameraManager
- {
- #region 单例
- private static readonly Lazy<CameraManager> _instance =
- new Lazy<CameraManager>(() => new CameraManager(), isThreadSafe: true);
- /// <summary>
- /// 懒汉单例入口。首次访问时初始化,线程安全。
- /// </summary>
- public static CameraManager Instance => _instance.Value;
- #endregion
- #region 字段
- private readonly Dictionary<Guid, ICamera> _cameras;
- private readonly object _sync = new object();
- // 设备发现 provider 缓存
- private List<CameraProviderInfo> _providers;
- private readonly object _providerLock = new object();
- // 反射扫描结果缓存
- private List<CameraTypeInfo> _scannedTypes;
- private readonly object _scanLock = new object();
- #endregion
- #region 构造
- /// <summary>
- /// 默认构造(供单例和 DI 容器使用)。
- /// 外部不应直接 new,请使用 <see cref="Instance"/> 或通过 DI 注入 <see cref="ICameraManager"/>。
- /// </summary>
- public CameraManager()
- {
- _cameras = new Dictionary<Guid, ICamera>();
- }
- #endregion
- #region 相机生命周期
- public bool CreateCamera(Guid id, CameraInfo cameraInfo)
- {
- if (cameraInfo == null) throw new ArgumentNullException(nameof(cameraInfo));
- var camera = CreateCameraInstance(id, cameraInfo);
- ICamera existing = null;
- lock (_sync)
- {
- _cameras.TryGetValue(id, out existing);
- _cameras[id] = camera;
- }
- if (existing != null && !ReferenceEquals(existing, camera))
- {
- try
- {
- if (existing.IsGrabbing)
- existing.StopGrabbing();
- }
- catch { }
- try { existing.CloseDevice(); } catch { }
- }
- return true;
- }
- public Task<bool> CreateCameraAsync(Guid id, CameraInfo cameraInfo)
- {
- return Task.Run(() => CreateCamera(id, cameraInfo));
- }
- public ICamera GetCamera(Guid id)
- {
- lock (_sync)
- {
- return _cameras.TryGetValue(id, out var cam) ? cam : null;
- }
- }
- public Task<ICamera> GetCameraAsync(Guid id)
- {
- return Task.Run(() => GetCamera(id));
- }
- public IReadOnlyCollection<ICamera> GetAllCameras()
- {
- lock (_sync)
- {
- return _cameras.Values.ToList().AsReadOnly();
- }
- }
- public Task<IReadOnlyCollection<ICamera>> GetAllCamerasAsync()
- {
- return Task.Run(() => GetAllCameras());
- }
- public bool TryGetCamera(Guid id, out ICamera camera)
- {
- lock (_sync)
- {
- return _cameras.TryGetValue(id, out camera);
- }
- }
- public IReadOnlyList<ICamera> Devices
- {
- get
- {
- lock (_sync)
- {
- return _cameras.Values.ToList().AsReadOnly();
- }
- }
- }
- public bool ContainsCamera(Guid id)
- {
- lock (_sync)
- {
- return _cameras.ContainsKey(id);
- }
- }
- public bool ContainsSN(string serialNumber)
- {
- lock (_sync)
- {
- return _cameras?.Values.Any(c => c.SerialNumber == serialNumber) ?? false;
- }
- }
- public bool RemoveCamera(Guid id)
- {
- ICamera camera = null;
- lock (_sync)
- {
- if (_cameras.TryGetValue(id, out camera))
- {
- _cameras.Remove(id);
- }
- }
- if (camera != null)
- {
- try
- {
- if (camera.IsGrabbing)
- camera.StopGrabbing();
- }
- catch { }
- try
- {
- camera.CloseDevice();
- }
- catch { }
- return true;
- }
- return false;
- }
- public Task<bool> RemoveCameraAsync(Guid id)
- {
- return Task.Run(() => RemoveCamera(id));
- }
- public void UnRegisterCamera(Guid id)
- {
- RemoveCamera(id);
- }
- /// <summary>
- /// 停止采集并关闭所有相机(程序退出前调用)。
- /// 幂等:已关闭的设备再调一次不会抛异常。
- /// </summary>
- public void CloseAll()
- {
- List<ICamera> list;
- lock (_sync)
- {
- list = _cameras.Values.ToList();
- _cameras.Clear();
- }
- foreach (var camera in list)
- {
- try { if (camera.IsGrabbing) camera.StopGrabbing(); } catch { }
- try { camera.CloseDevice(); } catch { }
- }
- }
- public Task UnRegisterCameraAsync(Guid id)
- {
- return Task.Run(() => RemoveCamera(id));
- }
- public ICamera GetCameraByNumber(int number)
- {
- lock (_sync)
- {
- foreach (var kvp in _cameras)
- {
- if (kvp.Value != null && kvp.Value.Index == number)
- return kvp.Value;
- }
- }
- return null;
- }
- public void RemoveAllCameras()
- {
- List<Guid> ids;
- lock (_sync)
- {
- ids = _cameras.Keys.ToList();
- }
- foreach (var id in ids)
- {
- RemoveCamera(id);
- }
- }
- public (bool IsSucceed, string Message) InitializeAllCameras(CameraInfo[] cameras)
- {
- RemoveAllCameras();
- foreach (var info in cameras)
- {
- var camera = CreateCameraInstance(info.Id, info);
- lock (_sync)
- {
- _cameras[info.Id] = camera;
- }
- }
- bool allConnected = true;
- StringBuilder errors = new StringBuilder();
- foreach (var info in cameras)
- {
- if (TryGetCamera(info.Id, out var camera))
- {
- bool opened = camera.OpenDevice();
- if (!opened)
- {
- allConnected = false;
- errors.AppendLine($"相机 {info.CameraName}(编号: {info.CameraNo})打开失败: {camera.ErrorMessage}");
- }
- else
- {
- // 应用配置里持久化的参数快照(曝光/增益/Gamma),null 表示沿用硬件默认值
- ApplySavedParameters(camera, info);
- }
- }
- else
- {
- allConnected = false;
- errors.AppendLine($"未找到相机 {info.CameraName}(编号: {info.CameraNo})的实例。");
- }
- }
- return allConnected ? (true, "所有相机均已成功初始化并连接。") : (false, errors.ToString());
- }
- public Task<(bool IsSucceed, string Message)> InitializeAllCamerasAsync(CameraInfo[] cameras)
- {
- return Task.Run(() => InitializeAllCameras(cameras));
- }
- public void UpdateCameraNumber(Guid id, int newNumber)
- {
- if (TryGetCamera(id, out var camera))
- {
- camera.Index = newNumber;
- }
- }
- #endregion
- #region 配置持久化
- public static string DefaultConfigPath
- {
- get
- {
- return Path.Combine(TeamAAS.PathHelper.ConfigDirectory, "Cameras.json");
- }
- }
- public bool SaveConfig(string path = null)
- {
- path = path ?? DefaultConfigPath;
- var infos = new List<CameraInfo>();
- lock (_sync)
- {
- foreach (var cam in _cameras.Values)
- {
- infos.Add(new CameraInfo
- {
- Id = cam.ID,
- CameraNo = cam.Index,
- CameraName = cam.Name,
- CameraBrand = cam.CameraBrand,
- CameraType = cam.CameraType,
- CameraIp = cam.CameraIp,
- IsFindByIp = cam.IsFindByIp,
- SerialNumber = cam.SerialNumber,
- Model = cam.ModelName,
- ManufacturerName = cam.ManufacturerName,
- ExposureTime = cam.ExposureTime,
- Gain = cam.Gain,
- Gamma = cam.Gamma,
- });
- }
- }
- return TeamAAS.JsonFileStore.Save(path, infos);
- }
- public IReadOnlyList<CameraInfo> GetAllCameraInfos()
- {
- var infos = new List<CameraInfo>();
- lock (_sync)
- {
- foreach (var cam in _cameras.Values)
- {
- infos.Add(new CameraInfo
- {
- Id = cam.ID,
- CameraNo = cam.Index,
- CameraName = cam.Name,
- CameraBrand = cam.CameraBrand,
- CameraType = cam.CameraType,
- CameraIp = cam.CameraIp,
- IsFindByIp = cam.IsFindByIp,
- SerialNumber = cam.SerialNumber,
- Model = cam.ModelName,
- ManufacturerName = cam.ManufacturerName,
- ExposureTime = cam.ExposureTime,
- Gain = cam.Gain,
- Gamma = cam.Gamma,
- });
- }
- }
- return infos.AsReadOnly();
- }
- public void LoadConfig(string path = null)
- {
- path = path ?? DefaultConfigPath;
- if (!File.Exists(path)) return;
- var infos = TeamAAS.JsonFileStore.Load<List<CameraInfo>>(path);
- if (infos == null || infos.Count == 0) return;
- InitializeAllCameras(infos.ToArray());
- }
- #endregion
- #region 品牌/设备发现(原 CameraFactory 职责)
- /// <summary>
- /// 获取所有已注册的相机品牌 provider(首次访问时反射扫描并缓存)。
- /// </summary>
- private List<CameraProviderInfo> GetProviders()
- {
- if (_providers != null) return _providers;
- lock (_providerLock)
- {
- if (_providers != null) return _providers;
- _providers = ScanProviders();
- return _providers;
- }
- }
- private List<CameraProviderInfo> ScanProviders()
- {
- var result = new List<CameraProviderInfo>();
- // 通用模块扫描:收集全部 ICamera 实现(含 Runtime\Plugins 里的插件 DLL)
- foreach (var type in TeamAAS.Modularity.AssemblyScanner.FindImplementations<ICamera>())
- {
- var attr = type.GetCustomAttribute<CameraAttribute>();
- if (attr == null) continue;
- var getDevicesMethod = type.GetMethod("GetDevices", BindingFlags.Public | BindingFlags.Static);
- var checkInstalledMethod = type.GetMethod("CheckSoftwareInstalled", BindingFlags.Public | BindingFlags.Static);
- if (getDevicesMethod == null) continue;
- result.Add(new CameraProviderInfo
- {
- CameraType = type,
- Brand = attr.Brand,
- DisplayName = attr.DisplayName,
- GetDevicesDelegate = () =>
- {
- try
- {
- var r = getDevicesMethod.Invoke(null, null);
- if (r is CameraInfo[] arr) return arr.ToList();
- if (r is IEnumerable<CameraInfo> list) return list.ToList();
- return new List<CameraInfo>();
- }
- catch { return new List<CameraInfo>(); }
- },
- CheckInstalledDelegate = () =>
- {
- if (checkInstalledMethod == null)
- return (false, "");
- try
- {
- var r = checkInstalledMethod.Invoke(null, null);
- if (r is ValueTuple<bool, string> tuple) return tuple;
- if (r is Tuple<bool, string> t2) return (t2.Item1, t2.Item2);
- return (false, "");
- }
- catch { return (false, ""); }
- }
- });
- }
- return result
- .GroupBy(p => p.CameraType.FullName)
- .Select(g => g.First())
- .OrderBy(p => p.DisplayName)
- .ToList();
- }
- /// <summary>
- /// 搜索所有品牌的可用相机设备
- /// </summary>
- public List<CameraInfo> DiscoverAllDevices()
- {
- var allDevices = new List<CameraInfo>();
- int index = 1;
- foreach (var provider in GetProviders())
- {
- try
- {
- var devices = provider.GetDevicesDelegate();
- foreach (var d in devices)
- {
- d.CameraNo = index++;
- allDevices.Add(d);
- }
- }
- catch { }
- }
- return allDevices;
- }
- /// <summary>
- /// 按品牌搜索可用相机设备
- /// </summary>
- public List<CameraInfo> DiscoverByBrand(string brand)
- {
- var provider = GetProviders().FirstOrDefault(p => p.Brand == brand);
- if (provider == null)
- throw new NotSupportedException($"品牌 {brand} 暂未实现设备发现功能");
- try
- {
- return provider.GetDevicesDelegate();
- }
- catch
- {
- return new List<CameraInfo>();
- }
- }
- /// <summary>
- /// 获取已安装的相机品牌列表(用于弹窗选择)
- /// </summary>
- public List<(string Brand, string DisplayName, bool Installed)> GetAvailableBrands()
- {
- var list = new List<(string, string, bool)>();
- foreach (var provider in GetProviders())
- {
- try
- {
- var (installed, _) = provider.CheckInstalledDelegate();
- list.Add((provider.Brand, provider.DisplayName, installed));
- }
- catch
- {
- list.Add((provider.Brand, provider.DisplayName, false));
- }
- }
- return list;
- }
- /// <summary>
- /// 获取相机品牌的显示名称
- /// </summary>
- public string GetBrandDisplayName(string brand)
- {
- var provider = GetProviders().FirstOrDefault(p => p.Brand == brand);
- return provider?.DisplayName ?? brand;
- }
- /// <summary>
- /// 获取已安装的相机SDK列表
- /// </summary>
- public List<(string Name, bool Installed, string Version)> GetSdkStatus()
- {
- var list = new List<(string, bool, string)>();
- foreach (var provider in GetProviders())
- {
- try
- {
- var (installed, version) = provider.CheckInstalledDelegate();
- list.Add((provider.DisplayName, installed, version));
- }
- catch
- {
- list.Add((provider.DisplayName, false, ""));
- }
- }
- return list;
- }
- #endregion
- #region 反射扫描(类型信息)
- /// <summary>
- /// 获取所有可用的相机类型信息(通过 [Camera] 特性自动发现)。
- /// </summary>
- public IReadOnlyList<CameraTypeInfo> GetAvailableTypes()
- {
- if (_scannedTypes != null) return _scannedTypes.AsReadOnly();
- lock (_scanLock)
- {
- if (_scannedTypes != null) return _scannedTypes.AsReadOnly();
- _scannedTypes = ScanTypes();
- return _scannedTypes.AsReadOnly();
- }
- }
- private List<CameraTypeInfo> ScanTypes()
- {
- var result = new List<CameraTypeInfo>();
- // 通用模块扫描:收集全部 ICamera 实现(含 Runtime\Plugins 里的插件 DLL)
- foreach (var type in TeamAAS.Modularity.AssemblyScanner.FindImplementations<ICamera>())
- {
- var attr = type.GetCustomAttribute<CameraAttribute>();
- if (attr == null) continue;
- result.Add(new CameraTypeInfo
- {
- TypeKey = type.FullName,
- DisplayName = attr.DisplayName,
- Brand = attr.Brand,
- Description = attr.Description,
- Type = type
- });
- }
- return result
- .GroupBy(t => t.TypeKey)
- .Select(g => g.First())
- .OrderBy(t => t.DisplayName)
- .ToList();
- }
- #endregion
- #region IDisposable
- public void Dispose()
- {
- RemoveAllCameras();
- GC.SuppressFinalize(this);
- }
- #endregion
- #region 私有辅助
- private ICamera CreateCameraInstance(Guid id, CameraInfo info)
- {
- var types = GetAvailableTypes();
- var match = types.FirstOrDefault(t => t.Brand == info.CameraBrand);
- if (match == null)
- throw new NotSupportedException("Unsupported camera brand: " + info.CameraBrand);
- return (ICamera)Activator.CreateInstance(
- match.Type,
- id, info.CameraNo, info.CameraName, info.SerialNumber, info.CameraIp, info.IsFindByIp);
- }
- /// <summary>
- /// 相机打开成功后,把 Cameras.json 中持久化的参数快照下发到硬件。
- /// 属性 setter 内部已做异常兜底(失败写入 ErrorMessage),此处再整体兜底保证初始化流程不被打断。
- /// </summary>
- private static void ApplySavedParameters(ICamera camera, CameraInfo info)
- {
- if (camera == null || info == null) return;
- try
- {
- // 自动曝光是相机品牌特有(UVC)的运行时开关,不在这层恢复;
- // 恢复手动参数即可——UVC 处于自动曝光时会自行忽略手动写入。
- if (info.ExposureTime.HasValue) camera.ExposureTime = info.ExposureTime.Value;
- if (info.Gain.HasValue) camera.Gain = info.Gain.Value;
- if (info.Gamma.HasValue) camera.Gamma = info.Gamma.Value;
- }
- catch { }
- }
- private class CameraProviderInfo
- {
- public Type CameraType { get; set; }
- public string Brand { get; set; }
- public string DisplayName { get; set; }
- public Func<List<CameraInfo>> GetDevicesDelegate { get; set; }
- public Func<(bool isInstalled, string version)> CheckInstalledDelegate { get; set; }
- }
- #endregion
- }
- public class CameraTypeInfo
- {
- public string TypeKey { get; set; }
- public string DisplayName { get; set; }
- public string Brand { get; set; }
- public string Description { get; set; }
- public Type Type { get; set; }
- }
- }
|