CameraService.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. using MvCamCtrl.NET;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. using TeamAAS_VP.Core.Cameras;
  8. using TeamAAS_VP.Interfaces;
  9. using TeamAAS_VP.Models;
  10. namespace TeamAAS_VP.Services
  11. {
  12. /// <summary>
  13. /// 提供相机对象的创建、管理、查找和释放功能的服务类。
  14. /// 线程安全:内部使用 <see cref="_sync"/> 进行并发访问同步。
  15. /// 资源管理:实现 <see cref="IDisposable"/>,在释放时会关闭并清理所有已注册相机。
  16. /// </summary>
  17. public class CameraService : ICameraService, IDisposable
  18. {
  19. /// <summary>
  20. /// 保存相机实例的字典,键为相机的唯一标识 <see cref="Guid"/>。
  21. /// </summary>
  22. private readonly Dictionary<Guid, ICamera> _CameraCollection;
  23. /// <summary>
  24. /// 用于保证对 <see cref="_CameraCollection"/> 并发访问的同步对象。
  25. /// </summary>
  26. private readonly object _sync = new object();
  27. /// <summary>
  28. /// 初始化一个新的 <see cref="CameraService"/> 实例。
  29. /// </summary>
  30. public CameraService()
  31. {
  32. _CameraCollection = new Dictionary<Guid, ICamera>();
  33. }
  34. /// <summary>
  35. /// 创建并注册一个相机实例。如果同一 <paramref name="id"/> 已存在,则会先尝试关闭并替换之。
  36. /// </summary>
  37. /// <param name="id">要注册的相机唯一标识符。</param>
  38. /// <param name="cameraInfo">用于创建相机的配置信息,不能为空。</param>
  39. /// <returns>始终返回 <c>true</c>(当前实现不表示创建失败)。</returns>
  40. /// <exception cref="ArgumentNullException"><paramref name="cameraInfo"/> 为 null 时抛出。</exception>
  41. public bool CreateCamera(Guid id, CameraInfo cameraInfo)
  42. {
  43. if (cameraInfo == null) throw new ArgumentNullException(nameof(cameraInfo));
  44. var camera = CreateCameraInstance(id, cameraInfo);
  45. lock (_sync)
  46. {
  47. if (_CameraCollection.ContainsKey(id))
  48. {
  49. var currenCamera = _CameraCollection[id];
  50. try
  51. {
  52. currenCamera.CloseDevice();
  53. }
  54. catch
  55. {
  56. // 忽略关闭异常
  57. }
  58. }
  59. _CameraCollection[id] = camera;
  60. }
  61. return true;
  62. }
  63. /// <summary>
  64. /// 异步创建并注册一个相机实例(内部使用 Task.Run 包装同步方法)。
  65. /// </summary>
  66. /// <param name="id">相机唯一标识符。</param>
  67. /// <param name="cameraInfo">相机配置信息。</param>
  68. /// <returns>表示创建结果的任务,结果为布尔值。</returns>
  69. public Task<bool> CreateCameraAsync(Guid id, CameraInfo cameraInfo)
  70. {
  71. return Task.Run(() => CreateCamera(id, cameraInfo));
  72. }
  73. /// <summary>
  74. /// 根据 <paramref name="id"/> 获取已注册的相机实例,如果未找到则返回 null。
  75. /// </summary>
  76. /// <param name="id">相机唯一标识符。</param>
  77. /// <returns>找到的 <see cref="ICamera"/> 实例或 null。</returns>
  78. public ICamera GetCamera(Guid id)
  79. {
  80. lock (_sync)
  81. {
  82. if (_CameraCollection.TryGetValue(id, out var cam))
  83. return cam;
  84. }
  85. return null;
  86. }
  87. /// <summary>
  88. /// 异步获取相机实例(包装同步调用)。
  89. /// </summary>
  90. /// <param name="id">相机唯一标识符。</param>
  91. /// <returns>表示查找结果的任务,任务结果为 <see cref="ICamera"/> 或 null。</returns>
  92. public Task<ICamera> GetCameraAsync(Guid id)
  93. {
  94. return Task.Run(() =>
  95. {
  96. return GetCamera(id);
  97. });
  98. }
  99. /// <summary>
  100. /// 取消注册指定 <paramref name="id"/> 的相机并尝试关闭其设备资源。
  101. /// 如果不存在则无操作。
  102. /// </summary>
  103. /// <param name="id">要取消注册的相机标识符。</param>
  104. public void UnRegisterCamera(Guid id)
  105. {
  106. ICamera camera = null;
  107. lock (_sync)
  108. {
  109. if (_CameraCollection.TryGetValue(id, out camera))
  110. {
  111. _CameraCollection.Remove(id);
  112. }
  113. }
  114. if (camera != null)
  115. {
  116. try
  117. {
  118. camera.CloseDevice();
  119. }
  120. catch
  121. {
  122. // 忽略关闭异常
  123. }
  124. }
  125. }
  126. /// <summary>
  127. /// 异步取消注册相机(包装同步调用)。
  128. /// </summary>
  129. /// <param name="id">相机唯一标识符。</param>
  130. /// <returns>表示操作完成的任务。</returns>
  131. public Task UnRegisterCameraAsync(Guid id)
  132. {
  133. return Task.Run(() => UnRegisterCamera(id));
  134. }
  135. /// <summary>
  136. /// 获取当前已注册的所有相机的只读集合快照。
  137. /// </summary>
  138. /// <returns>只读集合,包含当前注册的所有相机实例。</returns>
  139. public IReadOnlyCollection<ICamera> GetAllCameras()
  140. {
  141. lock (_sync)
  142. {
  143. return _CameraCollection.Values.ToList().AsReadOnly();
  144. }
  145. }
  146. /// <summary>
  147. /// 异步获取所有已注册相机(包装同步调用)。
  148. /// </summary>
  149. /// <returns>表示只读相机集合的任务。</returns>
  150. public Task<IReadOnlyCollection<ICamera>> GetAllCamerasAsync()
  151. {
  152. return Task.Run(() =>
  153. {
  154. return GetAllCameras();
  155. });
  156. }
  157. /// <summary>
  158. /// 尝试根据 <paramref name="id"/> 获取相机实例。
  159. /// </summary>
  160. /// <param name="id">相机唯一标识符。</param>
  161. /// <param name="camera">若找到则输出相机实例,否则为 null。</param>
  162. /// <returns>找到返回 <c>true</c>,否则返回 <c>false</c>。</returns>
  163. public bool TryGetCamera(Guid id, out ICamera camera)
  164. {
  165. lock (_sync)
  166. {
  167. return _CameraCollection.TryGetValue(id, out camera);
  168. }
  169. }
  170. /// <summary>
  171. /// 异步尝试获取相机实例,返回一个包含是否找到及相机实例的元组。
  172. /// </summary>
  173. /// <param name="id">相机唯一标识符。</param>
  174. /// <returns>任务结果为 (<c>found</c>, <c>camera</c>) 元组。</returns>
  175. public Task<(bool found, ICamera camera)> TryGetCameraAsync(Guid id)
  176. {
  177. return Task.Run(() =>
  178. {
  179. ICamera camera;
  180. bool found;
  181. lock (_sync)
  182. {
  183. found = _CameraCollection.TryGetValue(id, out camera);
  184. }
  185. return (found, camera);
  186. });
  187. }
  188. /// <summary>
  189. /// 判断是否包含指定 <paramref name="id"/> 的相机注册项。
  190. /// </summary>
  191. /// <param name="id">相机唯一标识符。</param>
  192. /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
  193. public bool ContainsCamera(Guid id)
  194. {
  195. lock (_sync)
  196. {
  197. return _CameraCollection.ContainsKey(id);
  198. }
  199. }
  200. /// <summary>
  201. /// 异步判断相机是否存在(包装同步调用)。
  202. /// </summary>
  203. /// <param name="id">相机唯一标识符。</param>
  204. /// <returns>表示存在性的任务结果(布尔值)。</returns>
  205. public Task<bool> ContainsCameraAsync(Guid id)
  206. {
  207. return Task.Run(() =>
  208. {
  209. return ContainsCamera(id);
  210. });
  211. }
  212. /// <summary>
  213. /// 移除指定 <paramref name="id"/> 的相机并关闭其设备资源(如存在)。
  214. /// </summary>
  215. /// <param name="id">要移除的相机标识符。</param>
  216. /// <returns>如果存在并已移除返回 <c>true</c>,否则返回 <c>false</c>。</returns>
  217. public bool RemoveCamera(Guid id)
  218. {
  219. ICamera camera = null;
  220. lock (_sync)
  221. {
  222. if (_CameraCollection.TryGetValue(id, out camera))
  223. {
  224. _CameraCollection.Remove(id);
  225. }
  226. }
  227. if (camera != null)
  228. {
  229. try
  230. {
  231. camera.CloseDevice();
  232. }
  233. catch
  234. {
  235. // 忽略关闭异常
  236. }
  237. return true;
  238. }
  239. return false;
  240. }
  241. /// <summary>
  242. /// 异步移除相机(包装同步调用)。
  243. /// </summary>
  244. /// <param name="id">相机唯一标识符。</param>
  245. /// <returns>表示操作是否成功的任务。</returns>
  246. public Task<bool> RemoveCameraAsync(Guid id)
  247. {
  248. return Task.Run(() =>
  249. {
  250. return RemoveCamera(id);
  251. });
  252. }
  253. /// <summary>
  254. /// 根据相机编号查找相机实例,找不到返回 null。
  255. /// 该方法内部使用反射来兼容不同相机实现中编号属性的命名差异。
  256. /// </summary>
  257. /// <param name="number">相机的编号(业务编号、索引等)。</param>
  258. /// <returns>找到的 <see cref="ICamera"/> 实例或 null。</returns>
  259. public ICamera GetCameraByNumber(int number)
  260. {
  261. if (TryFindCameraByNumber(number, out _, out var camera))
  262. return camera;
  263. return null;
  264. }
  265. /// <summary>
  266. /// 异步根据编号获取相机实例(包装同步调用)。
  267. /// </summary>
  268. /// <param name="number">相机编号。</param>
  269. /// <returns>表示相机实例的任务,未找到为 null。</returns>
  270. public Task<ICamera> GetCameraByNumberAsync(int number)
  271. {
  272. return Task.Run(() =>
  273. {
  274. return GetCameraByNumber(number);
  275. });
  276. }
  277. /// <summary>
  278. /// 异步根据编号取消注册相机(包装同步实现)。
  279. /// </summary>
  280. /// <param name="number">相机编号。</param>
  281. /// <returns>表示操作完成的任务。</returns>
  282. public Task UnRegisterCameraByNumberAsync(int number)
  283. {
  284. return Task.Run(() =>
  285. {
  286. UnRegisterCameraByNumber(number);
  287. });
  288. }
  289. /// <summary>
  290. /// 根据编号查找并取消注册相机(如果找到则调用 <see cref="UnRegisterCamera(Guid)"/>)。
  291. /// </summary>
  292. /// <param name="number">相机编号。</param>
  293. public void UnRegisterCameraByNumber(int number)
  294. {
  295. if (TryFindCameraByNumber(number, out var id, out var camera))
  296. {
  297. UnRegisterCamera(id);
  298. }
  299. }
  300. /// <summary>
  301. /// 移除并关闭所有已注册的相机,逐个移除以确保资源被正确释放。
  302. /// </summary>
  303. public void RemoveAllCameras()
  304. {
  305. //依次移除每一个相机,确保资源释放
  306. List<Guid> cameraIds;
  307. lock (_sync)
  308. {
  309. cameraIds = _CameraCollection.Keys.ToList();
  310. }
  311. foreach (var id in cameraIds)
  312. {
  313. RemoveCamera(id);
  314. }
  315. }
  316. /// <summary>
  317. /// 使用提供的相机配置信息数组初始化并尝试打开所有相机。
  318. /// 该方法会先清除已有相机,再为每个配置创建实例并尝试打开设备。
  319. /// </summary>
  320. /// <param name="cameras">要初始化的相机信息数组。</param>
  321. /// <returns>
  322. /// 返回元组,<c>IsSucceed</c> 表示是否所有相机均打开成功;<c>Message</c> 为失败信息或成功消息。
  323. /// </returns>
  324. public (bool IsSucceed, string Message) InitializeAllCameras(CameraInfo[] cameras)
  325. {
  326. //清除现有相机
  327. RemoveAllCameras();
  328. foreach (var camInfo in cameras)
  329. {
  330. var camera = CreateCameraInstance(camInfo.Id, camInfo);
  331. lock (_sync)
  332. {
  333. _CameraCollection[camInfo.Id] = camera;
  334. }
  335. }
  336. bool allConnected = true;
  337. StringBuilder errorMessages = new StringBuilder();
  338. //获取所有相机并尝试打开设备
  339. foreach (var camInfo in cameras)
  340. {
  341. if (TryGetCamera(camInfo.Id, out var camera))
  342. {
  343. bool opened = camera.OpenDevice();
  344. if (!opened)
  345. {
  346. allConnected = false;
  347. errorMessages.AppendLine($"相机 {camInfo.CameraName}(编号: {camInfo.CameraNo})打开失败: {camera.ErrorMessage}");
  348. }
  349. }
  350. else
  351. {
  352. allConnected = false;
  353. errorMessages.AppendLine($"未找到相机 {camInfo.CameraName}(编号: {camInfo.CameraNo})的实例。");
  354. }
  355. }
  356. if (allConnected)
  357. {
  358. return (true, "所有相机均已成功初始化并连接。");
  359. }
  360. else
  361. {
  362. return (false, errorMessages.ToString());
  363. }
  364. }
  365. /// <summary>
  366. /// 异步初始化所有相机(包装同步实现)。
  367. /// </summary>
  368. /// <param name="cameras">相机信息数组。</param>
  369. /// <returns>返回包含是否成功及消息的任务。</returns>
  370. public Task<(bool IsSucceed, string Message)> InitializeAllCamerasAsync(CameraInfo[] cameras)
  371. {
  372. return Task.Run(() => InitializeAllCameras(cameras));
  373. }
  374. /// <summary>
  375. /// 更新指定相机的业务编号(Index)。若未找到对应相机则无操作。
  376. /// </summary>
  377. /// <param name="id">相机唯一标识符。</param>
  378. /// <param name="newNumber">新的编号值。</param>
  379. public void UpdateCameraNumber(Guid id, int newNumber)
  380. {
  381. if (TryGetCamera(id, out var camera))
  382. {
  383. camera.Index = newNumber;
  384. }
  385. }
  386. /// <summary>
  387. /// 根据提供的 <see cref="CameraInfo"/> 创建具体相机实现的实例。
  388. /// 支持不同品牌的相机类型分发;默认使用 <see cref="MvCamera"/>。
  389. /// </summary>
  390. /// <param name="id">相机唯一标识符。</param>
  391. /// <param name="cameraInfo">相机配置信息。</param>
  392. /// <returns>创建的 <see cref="ICamera"/> 实例(未打开设备)。</returns>
  393. private ICamera CreateCameraInstance(Guid id, CameraInfo cameraInfo)
  394. {
  395. ICamera camera;
  396. if (cameraInfo.CameraBrand == Enums.CameraBrand.Basler_Pylon)
  397. {
  398. camera = new BaslerCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  399. }
  400. else if (cameraInfo.CameraBrand == Enums.CameraBrand.HikRobot_MVS)
  401. {
  402. camera = new MvCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  403. }
  404. else if (cameraInfo.CameraBrand == Enums.CameraBrand.IRayple)
  405. {
  406. camera = new AHuaCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  407. }
  408. else if (cameraInfo.CameraBrand == Enums.CameraBrand.Cognex)
  409. {
  410. camera = new CognexCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  411. }
  412. else if (cameraInfo.CameraBrand == Enums.CameraBrand.Galaxy)
  413. {
  414. camera = new GalaxyCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  415. }
  416. else if (cameraInfo.CameraBrand == Enums.CameraBrand.Opt)
  417. {
  418. camera = new OptCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  419. }
  420. else if (cameraInfo.CameraBrand == Enums.CameraBrand.SSZN)
  421. {
  422. camera = new SSZNCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  423. }
  424. else
  425. {
  426. camera = new MvCamera(id, cameraInfo.CameraNo, cameraInfo.CameraName, cameraInfo.SerialNumber, cameraInfo.CameraIp, cameraInfo.IsFindByIp);
  427. }
  428. return camera;
  429. }
  430. /// <summary>
  431. /// 根据业务编号在已注册相机中查找匹配项。
  432. /// 实现要点:
  433. /// - 对每个相机尝试通过常见属性名("CameraNo", "Number", "No")读取编号;
  434. /// - 若未直接在相机对象找到编号,则尝试从其可能的 <c>CameraInfo</c> / <c>Info</c> 属性中读取;
  435. /// - 使用 Convert.ToInt32 进行通用转换,忽略无法转换或异常的项;
  436. /// - 在找到匹配编号时返回 true,并输出相机 id 与实例;否则返回 false。
  437. /// </summary>
  438. /// <param name="number">要匹配的编号。</param>
  439. /// <param name="id">若找到则输出相机的 <see cref="Guid"/> 标识符,未找到为 <see cref="Guid.Empty"/>。</param>
  440. /// <param name="camera">若找到则输出对应的 <see cref="ICamera"/> 实例,否则为 null。</param>
  441. /// <returns>找到匹配项返回 <c>true</c>,否则返回 <c>false</c>。</returns>
  442. private bool TryFindCameraByNumber(int number, out Guid id, out ICamera camera)
  443. {
  444. lock (_sync)
  445. {
  446. foreach (var kvp in _CameraCollection)
  447. {
  448. var c = kvp.Value;
  449. if (c == null) continue;
  450. object rawValue = null;
  451. var type = c.GetType();
  452. var prop = type.GetProperty("CameraNo") ?? type.GetProperty("Number") ?? type.GetProperty("No") ;
  453. if (prop != null)
  454. {
  455. try
  456. {
  457. rawValue = prop.GetValue(c);
  458. }
  459. catch
  460. {
  461. rawValue = null;
  462. }
  463. }
  464. if (rawValue == null)
  465. {
  466. var infoProp = type.GetProperty("CameraInfo") ?? type.GetProperty("Info");
  467. if (infoProp != null)
  468. {
  469. try
  470. {
  471. var infoObj = infoProp.GetValue(c);
  472. if (infoObj != null)
  473. {
  474. var infoType = infoObj.GetType();
  475. var numberProp = infoType.GetProperty("CameraNo") ?? infoType.GetProperty("CameraNumber") ?? infoType.GetProperty("Number") ?? infoType.GetProperty("No");
  476. if (numberProp != null)
  477. {
  478. try
  479. {
  480. rawValue = numberProp.GetValue(infoObj);
  481. }
  482. catch
  483. {
  484. rawValue = null;
  485. }
  486. }
  487. }
  488. }
  489. catch
  490. {
  491. rawValue = null;
  492. }
  493. }
  494. }
  495. if (rawValue != null)
  496. {
  497. int parsed;
  498. try
  499. {
  500. if (rawValue is int) parsed = (int)rawValue;
  501. else parsed = Convert.ToInt32(rawValue);
  502. }
  503. catch
  504. {
  505. continue;
  506. }
  507. if (parsed == number)
  508. {
  509. id = kvp.Key;
  510. camera = c;
  511. return true;
  512. }
  513. }
  514. if(c.Index== number)
  515. {
  516. id = kvp.Key;
  517. camera = c;
  518. return true;
  519. }
  520. }
  521. }
  522. id = Guid.Empty;
  523. camera = null;
  524. return false;
  525. }
  526. /// <summary>
  527. /// 释放服务占用的资源:关闭所有相机设备并清空内部集合。
  528. /// </summary>
  529. public void Dispose()
  530. {
  531. List<ICamera> cameras;
  532. lock (_sync)
  533. {
  534. cameras = _CameraCollection.Values.ToList();
  535. _CameraCollection.Clear();
  536. }
  537. foreach (var c in cameras)
  538. {
  539. try
  540. {
  541. c.CloseDevice();
  542. }
  543. catch
  544. {
  545. // 忽略单个相机关闭异常
  546. }
  547. }
  548. GC.SuppressFinalize(this);
  549. }
  550. }
  551. }