ConfigService.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. using Cognex.VisionPro;
  2. using Cognex.VisionPro.ToolBlock;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Collections.ObjectModel;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Threading.Tasks;
  9. using TeamAAS_VP.Core;
  10. using TeamAAS_VP.Interfaces;
  11. using TeamAAS_VP.Models;
  12. using TeamAAS_VP.Models.Calibration;
  13. using TeamAAS_VP.Models.Feeder;
  14. using TeamAAS_VP.Models.Lights;
  15. namespace TeamAAS_VP.Services
  16. {
  17. /// <summary>
  18. /// 配置参数服务实现类
  19. /// </summary>
  20. public class ConfigService : IConfigService
  21. {
  22. private static class ConfigPaths
  23. {
  24. // 将路径集中管理于本类,当前使用现有 FilePath 值以保持兼容
  25. public static readonly string CamerasConfigurationPath = "..//Config//CameraConfiguration.cfg";
  26. public static readonly string RobotsConfigurationPath = "..//Config//RobotsConfiguration.cfg";
  27. public static readonly string FeedersConfigurationPath = "..//Config//FeedersConfiguration.cfg";
  28. public static readonly string PlcConfigurationPath = "..//Config//PlcConfiguration.cfg";
  29. public static readonly string BgTcpIpConfigurationPath = "..//Config//BgTcpIpConfiguration.cfg";
  30. public static readonly string BgModbusTcpConfigurationPath = "..//Config//BgModbusTcpConfiguration.cfg";
  31. public static readonly string SystemConfigurationPath = "..//Config//SystemConfiguration.cfg";
  32. // Feeder清料任务配置路径
  33. public static readonly string FeederClearanceTasksConfigurationPath = "..//Config//FeederClearanceTasksConfiguration.cfg";
  34. // 光源控制器配置路径
  35. public static readonly string LightControllersConfigurationPath = "..//Config//LightControllersConfiguration.cfg";
  36. }
  37. private readonly object _sync = new object();
  38. private ObservableCollection<CameraInfo> Cameras { get; set; }
  39. private ObservableCollection<RobotInfo> Robots { get; set; }
  40. private ObservableCollection<FeederInfo> Feeders { get; set; }
  41. private ObservableCollection<PlcInfo> Plcs { get; set; }
  42. private BgTcpIP BgCommunicate { get; set; }
  43. private BgModbusTcp BgModbusCommunicate { get; set; }
  44. private ObservableCollection<FeederClearWork> FeederClearanceTasks { get; set; }
  45. // 光源控制器配置
  46. private ObservableCollection<LightControllerConfig> LightControllers { get; set; }
  47. public ConfigService()
  48. {
  49. //LoadAll();
  50. }
  51. public void LoadAll()
  52. {
  53. lock (_sync)
  54. {
  55. // 读取各配置,如果文件不存在,则保持 DeviceConfig 默认值
  56. if (File.Exists(ConfigPaths.CamerasConfigurationPath))
  57. Cameras = FileHelper.ReadJsonFile<ObservableCollection<CameraInfo>>(ConfigPaths.CamerasConfigurationPath);
  58. else
  59. {
  60. Cameras = new ObservableCollection<CameraInfo>();
  61. FileHelper.WriteJsonFile(Cameras, ConfigPaths.CamerasConfigurationPath);
  62. }
  63. if (File.Exists(ConfigPaths.RobotsConfigurationPath))
  64. Robots = FileHelper.ReadJsonFile<ObservableCollection<RobotInfo>>(ConfigPaths.RobotsConfigurationPath);
  65. else
  66. {
  67. Robots = new ObservableCollection<RobotInfo>();
  68. FileHelper.WriteJsonFile(Robots, ConfigPaths.RobotsConfigurationPath);
  69. }
  70. if (File.Exists(ConfigPaths.FeedersConfigurationPath))
  71. Feeders = FileHelper.ReadJsonFile<ObservableCollection<FeederInfo>>(ConfigPaths.FeedersConfigurationPath);
  72. else
  73. {
  74. Feeders = new ObservableCollection<FeederInfo>();
  75. FileHelper.WriteJsonFile(Feeders, ConfigPaths.FeedersConfigurationPath);
  76. }
  77. if (File.Exists(ConfigPaths.PlcConfigurationPath))
  78. Plcs = FileHelper.ReadJsonFile<ObservableCollection<PlcInfo>>(ConfigPaths.PlcConfigurationPath);
  79. else
  80. {
  81. Plcs = new ObservableCollection<PlcInfo>();
  82. FileHelper.WriteJsonFile(Plcs, ConfigPaths.PlcConfigurationPath);
  83. }
  84. if (File.Exists(ConfigPaths.BgTcpIpConfigurationPath))
  85. BgCommunicate = FileHelper.ReadJsonFile<BgTcpIP>(ConfigPaths.BgTcpIpConfigurationPath);
  86. else
  87. {
  88. BgCommunicate = new BgTcpIP();
  89. FileHelper.WriteJsonFile(BgCommunicate, ConfigPaths.BgTcpIpConfigurationPath);
  90. }
  91. if (File.Exists(ConfigPaths.BgModbusTcpConfigurationPath))
  92. BgModbusCommunicate = FileHelper.ReadJsonFile<BgModbusTcp>(ConfigPaths.BgModbusTcpConfigurationPath);
  93. else
  94. {
  95. BgModbusCommunicate = new BgModbusTcp();
  96. FileHelper.WriteJsonFile(BgModbusCommunicate, ConfigPaths.BgModbusTcpConfigurationPath);
  97. }
  98. if (!File.Exists(ConfigPaths.FeederClearanceTasksConfigurationPath))
  99. {
  100. FeederClearanceTasks = new ObservableCollection<FeederClearWork>();
  101. FileHelper.WriteJsonFile(FeederClearanceTasks, ConfigPaths.FeederClearanceTasksConfigurationPath);
  102. }
  103. else
  104. {
  105. // 读取 Feeder清料任务 配置
  106. FeederClearanceTasks = FileHelper.ReadJsonFile<ObservableCollection<FeederClearWork>>(ConfigPaths.FeederClearanceTasksConfigurationPath);
  107. }
  108. // 读取光源控制器配置
  109. if (!File.Exists(ConfigPaths.LightControllersConfigurationPath))
  110. {
  111. LightControllers = new ObservableCollection<LightControllerConfig>();
  112. FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
  113. }
  114. else
  115. {
  116. LightControllers = FileHelper.ReadJsonFile<ObservableCollection<LightControllerConfig>>(ConfigPaths.LightControllersConfigurationPath);
  117. }
  118. }
  119. }
  120. public Task LoadAllAsync()
  121. {
  122. return Task.Run(() => LoadAll());
  123. }
  124. public void SaveAll()
  125. {
  126. lock (_sync)
  127. {
  128. FileHelper.WriteJsonFile(Cameras, ConfigPaths.CamerasConfigurationPath);
  129. FileHelper.WriteJsonFile(Robots, ConfigPaths.RobotsConfigurationPath);
  130. FileHelper.WriteJsonFile(Feeders, ConfigPaths.FeedersConfigurationPath);
  131. FileHelper.WriteJsonFile(Plcs, ConfigPaths.PlcConfigurationPath);
  132. FileHelper.WriteJsonFile(BgCommunicate, ConfigPaths.BgTcpIpConfigurationPath);
  133. FileHelper.WriteJsonFile(BgModbusCommunicate, ConfigPaths.BgModbusTcpConfigurationPath);
  134. FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
  135. }
  136. }
  137. public Task SaveAllAsync()
  138. {
  139. return Task.Run(() => SaveAll());
  140. }
  141. #region Cameras
  142. public IReadOnlyCollection<CameraInfo> GetAllCameras()
  143. {
  144. lock (_sync) { return Cameras.ToList().AsReadOnly(); }
  145. }
  146. public CameraInfo GetCamera(Guid id)
  147. {
  148. lock (_sync) { return Cameras.FirstOrDefault(c => c.Id == id); }
  149. }
  150. public CameraInfo AddOrUpdateCamera(CameraInfo camera)
  151. {
  152. if (camera == null) return null;
  153. lock (_sync)
  154. {
  155. var exist = Cameras.FirstOrDefault(c => c.Id == camera.Id);
  156. if (exist != null)
  157. {
  158. var idx = Cameras.IndexOf(exist);
  159. Cameras[idx] = camera;
  160. }
  161. else
  162. {
  163. camera.CameraNo = Cameras.Count + 1;
  164. Cameras.Add(camera);
  165. }
  166. }
  167. FileHelper.WriteJsonFile(Cameras, ConfigPaths.CamerasConfigurationPath);
  168. return camera;
  169. }
  170. public bool RemoveCamera(Guid id)
  171. {
  172. bool result = false;
  173. lock (_sync)
  174. {
  175. var exist = Cameras.FirstOrDefault(c => c.Id == id);
  176. if (exist != null) result = Cameras.Remove(exist);
  177. //重新编号
  178. int no = 1;
  179. //对所有相机按CameraNo排序
  180. var sortedCameras = Cameras.OrderBy(c => c.CameraNo).ToList();
  181. foreach (var cam in sortedCameras)
  182. {
  183. cam.CameraNo = no;
  184. no++;
  185. }
  186. Cameras.Clear();
  187. foreach (var cam in sortedCameras)
  188. {
  189. Cameras.Add(cam);
  190. }
  191. }
  192. FileHelper.WriteJsonFile(Cameras, ConfigPaths.CamerasConfigurationPath);
  193. return result;
  194. }
  195. public bool ContainsCamera(Guid id) { lock (_sync) { return Cameras.Any(c => c.Id == id); } }
  196. #endregion
  197. #region Robots
  198. public IReadOnlyCollection<RobotInfo> GetAllRobots() { lock (_sync) { return Robots.ToList().AsReadOnly(); } }
  199. public RobotInfo GetRobot(Guid id) { lock (_sync) { return Robots.FirstOrDefault(r => r.Id == id); } }
  200. public RobotInfo AddOrUpdateRobot(RobotInfo robot)
  201. {
  202. if (robot == null) return null;
  203. lock (_sync)
  204. {
  205. var exist = Robots.FirstOrDefault(r => r.Id == robot.Id);
  206. if (exist != null)
  207. {
  208. var idx = Robots.IndexOf(exist);
  209. Robots[idx] = robot;
  210. }
  211. else
  212. {
  213. robot.RobotNo = Robots.Count + 1;
  214. Robots.Add(robot);
  215. }
  216. }
  217. FileHelper.WriteJsonFile(Robots, ConfigPaths.RobotsConfigurationPath);
  218. return robot;
  219. }
  220. public bool RemoveRobot(Guid id)
  221. {
  222. bool result = false;
  223. lock (_sync)
  224. {
  225. var e = Robots.FirstOrDefault(r => r.Id == id);
  226. if (e != null)
  227. result = Robots.Remove(e);
  228. //重新编号
  229. int no = 1;
  230. //对所有机器人按RobotNo排序
  231. var sortedRobots = Robots.OrderBy(r => r.RobotNo).ToList();
  232. foreach (var r in sortedRobots)
  233. {
  234. r.RobotNo = no;
  235. no++;
  236. }
  237. Robots.Clear();
  238. foreach (var r in sortedRobots)
  239. {
  240. Robots.Add(r);
  241. }
  242. }
  243. FileHelper.WriteJsonFile(Robots, ConfigPaths.RobotsConfigurationPath);
  244. return result;
  245. }
  246. public bool ContainsRobot(Guid id) { lock (_sync) { return Robots.Any(r => r.Id == id); } }
  247. /// <summary>
  248. /// 修改指定机器人id的步进距离
  249. /// </summary>
  250. /// <param name="id">机器人唯一标识符。</param>
  251. /// <param name="stepDistance">步进距离</param>
  252. /// <returns></returns>
  253. public bool UpdateRobotStepDistance(Guid id, double stepDistance)
  254. {
  255. lock (_sync)
  256. {
  257. var robot = Robots.FirstOrDefault(r => r.Id == id);
  258. if (robot != null)
  259. {
  260. robot.StepDistance = stepDistance;
  261. FileHelper.WriteJsonFile(Robots, ConfigPaths.RobotsConfigurationPath);
  262. return true;
  263. }
  264. else
  265. {
  266. return false;
  267. }
  268. }
  269. }
  270. /// <summary>
  271. /// 获取指定机器人Id的步进距离
  272. /// </summary>
  273. /// <param name="id"></param>
  274. /// <returns></returns>
  275. public double GetRobotStepDistance(Guid id)
  276. {
  277. lock (_sync)
  278. {
  279. var robot = Robots.FirstOrDefault(r => r.Id == id);
  280. if (robot != null)
  281. {
  282. return robot.StepDistance;
  283. }
  284. else
  285. {
  286. return 1.0;
  287. }
  288. }
  289. }
  290. #endregion
  291. #region Feeders
  292. public IReadOnlyCollection<FeederInfo> GetAllFeeders() { lock (_sync) { return Feeders.ToList().AsReadOnly(); } }
  293. public FeederInfo GetFeeder(Guid id) { lock (_sync) { return Feeders.FirstOrDefault(f => f.Id == id); } }
  294. public FeederInfo AddOrUpdateFeeder(FeederInfo feeder)
  295. {
  296. if (feeder == null) return null;
  297. lock (_sync)
  298. {
  299. var exist = Feeders.FirstOrDefault(f => f.Id == feeder.Id);
  300. if (exist != null)
  301. {
  302. var idx = Feeders.IndexOf(exist);
  303. Feeders[idx] = feeder;
  304. }
  305. else
  306. {
  307. feeder.FeederNo = Feeders.Count + 1;
  308. Feeders.Add(feeder);
  309. }
  310. }
  311. FileHelper.WriteJsonFile(Feeders, ConfigPaths.FeedersConfigurationPath);
  312. return feeder;
  313. }
  314. public bool RemoveFeeder(Guid id)
  315. {
  316. bool result = false;
  317. lock (_sync)
  318. {
  319. var e = Feeders.FirstOrDefault(f => f.Id == id);
  320. if (e != null) result = Feeders.Remove(e);
  321. //重新编号
  322. int no = 1;
  323. //对所有供料器按FeederNo排序
  324. var sortedFeeders = Feeders.OrderBy(f => f.FeederNo).ToList();
  325. foreach (var f in sortedFeeders)
  326. {
  327. f.FeederNo = no;
  328. no++;
  329. }
  330. Feeders.Clear();
  331. foreach (var f in sortedFeeders)
  332. {
  333. Feeders.Add(f);
  334. }
  335. }
  336. FileHelper.WriteJsonFile(Feeders, ConfigPaths.FeedersConfigurationPath);
  337. return result;
  338. }
  339. public bool ContainsFeeder(Guid id) { lock (_sync) { return Feeders.Any(f => f.Id == id); } }
  340. #endregion
  341. #region PLC
  342. public IReadOnlyCollection<PlcInfo> GetAllPlcs() { lock (_sync) { return Plcs.ToList().AsReadOnly(); } }
  343. public PlcInfo GetPlc(Guid id) { lock (_sync) { return Plcs.FirstOrDefault(p => p.Id == id); } }
  344. public PlcInfo AddOrUpdatePlc(PlcInfo plc)
  345. {
  346. if (plc == null) return null;
  347. lock (_sync)
  348. {
  349. var exist = Plcs.FirstOrDefault(p => p.Id == plc.Id);
  350. if (exist != null)
  351. {
  352. var idx = Plcs.IndexOf(exist);
  353. Plcs[idx] = plc;
  354. }
  355. else
  356. {
  357. plc.PlcNo = Plcs.Count + 1;
  358. Plcs.Add(plc);
  359. }
  360. }
  361. FileHelper.WriteJsonFile(Plcs, ConfigPaths.PlcConfigurationPath);
  362. return plc;
  363. }
  364. public bool RemovePlc(Guid id)
  365. {
  366. bool result = false;
  367. lock (_sync)
  368. {
  369. var e = Plcs.FirstOrDefault(p => p.Id == id);
  370. if (e != null) result = Plcs.Remove(e);
  371. //重新编号
  372. int no = 1;
  373. //对所有PLC按PlcNo排序
  374. var sortedPlcs = Plcs.OrderBy(p => p.PlcNo).ToList();
  375. foreach (var p in sortedPlcs)
  376. {
  377. p.PlcNo = no;
  378. no++;
  379. }
  380. Plcs.Clear();
  381. foreach (var p in sortedPlcs)
  382. {
  383. Plcs.Add(p);
  384. }
  385. }
  386. FileHelper.WriteJsonFile(Plcs, ConfigPaths.PlcConfigurationPath);
  387. return result;
  388. }
  389. public bool ContainsPlc(Guid id) { lock (_sync) { return Plcs.Any(p => p.Id == id); } }
  390. #endregion
  391. #region BgTcp
  392. public BgTcpIP GetBgTcp() { lock (_sync) { return BgCommunicate; } }
  393. public void SetBgTcp(BgTcpIP cfg) { lock (_sync) { BgCommunicate = cfg; } }
  394. public void SaveBgTcp() { lock (_sync) { FileHelper.WriteJsonFile(BgCommunicate, ConfigPaths.BgTcpIpConfigurationPath); } }
  395. #endregion
  396. #region BgModbus
  397. public BgModbusTcp GetBgModbusTcp() { lock (_sync) { return BgModbusCommunicate; } }
  398. public void SetBgModbusTcp(BgModbusTcp cfg) { lock (_sync) { BgModbusCommunicate = cfg; } }
  399. public void SaveBgModbusTcp() { lock (_sync) { FileHelper.WriteJsonFile(BgModbusCommunicate, ConfigPaths.BgModbusTcpConfigurationPath); } }
  400. #endregion
  401. #region 系统参数
  402. /// <summary>
  403. /// 获取系统设置
  404. /// </summary>
  405. /// <returns></returns>
  406. public SystemConfiguration GetSystemConfiguration()
  407. {
  408. lock (_sync)
  409. {
  410. var config = new SystemConfiguration();
  411. if (File.Exists(ConfigPaths.SystemConfigurationPath))
  412. {
  413. config = FileHelper.ReadJsonFile<SystemConfiguration>(ConfigPaths.SystemConfigurationPath);
  414. }
  415. return config;
  416. }
  417. }
  418. /// <summary>
  419. /// 保存系统设置
  420. /// </summary>
  421. public void SaveSystemConfiguration(SystemConfiguration systemConfiguration)
  422. {
  423. lock (_sync)
  424. {
  425. FileHelper.WriteJsonFile(systemConfiguration, ConfigPaths.SystemConfigurationPath);
  426. }
  427. }
  428. #endregion
  429. #region Feeder清料任务
  430. /// <summary>
  431. /// 获取所有清料任务
  432. /// </summary>
  433. /// <returns></returns>
  434. public IReadOnlyCollection<FeederClearWork> GetAllClearanceTasks() { lock (_sync) { return FeederClearanceTasks.ToList().AsReadOnly(); } }
  435. /// <summary>
  436. /// 新增或更新清料任务
  437. /// </summary>
  438. /// <param name="task"></param>
  439. /// <returns></returns>
  440. public FeederClearWork AddOrUpdateClearanceTask(FeederClearWork task)
  441. {
  442. if (task == null) return null;
  443. lock (_sync)
  444. {
  445. var exist = FeederClearanceTasks.FirstOrDefault(t => t.Id == task.Id);
  446. if (exist != null)
  447. {
  448. var idx = FeederClearanceTasks.IndexOf(exist);
  449. FeederClearanceTasks[idx] = task;
  450. }
  451. else
  452. {
  453. // 设置任务编号为当前最大编号加1
  454. task.TaskCode = FeederClearanceTasks.Count > 0 ? FeederClearanceTasks.Max(t => t.TaskCode) + 1 : 1;
  455. FeederClearanceTasks.Add(task);
  456. }
  457. }
  458. FileHelper.WriteJsonFile(FeederClearanceTasks, ConfigPaths.FeederClearanceTasksConfigurationPath);
  459. return task;
  460. }
  461. /// <summary>
  462. /// 移除清料任务
  463. /// </summary>
  464. /// <param name="id"></param>
  465. /// <returns></returns>
  466. public bool RemoveClearanceTask(Guid id)
  467. {
  468. lock (_sync)
  469. {
  470. var exist = FeederClearanceTasks.FirstOrDefault(t => t.Id == id);
  471. if (exist != null)
  472. {
  473. FileHelper.WriteJsonFile(FeederClearanceTasks, ConfigPaths.FeederClearanceTasksConfigurationPath);
  474. return FeederClearanceTasks.Remove(exist);
  475. }
  476. else
  477. return false;
  478. }
  479. }
  480. /// <summary>
  481. /// 清除所有清料任务
  482. /// </summary>
  483. public void ClearAllClearanceTasks()
  484. {
  485. lock (_sync)
  486. {
  487. FeederClearanceTasks.Clear();
  488. }
  489. FileHelper.WriteJsonFile(FeederClearanceTasks, ConfigPaths.FeederClearanceTasksConfigurationPath);
  490. }
  491. /// <summary>
  492. /// 判断是否包含指定标识的清料任务
  493. /// </summary>
  494. /// <param name="id"></param>
  495. /// <returns></returns>
  496. public bool ContainsClearanceTask(Guid id)
  497. {
  498. lock (_sync) { return FeederClearanceTasks.Any(t => t.Id == id); }
  499. }
  500. /// <summary>
  501. /// 根据标识获取单个清料任务信息
  502. /// </summary>
  503. /// <param name="id"></param>
  504. /// <returns></returns>
  505. public FeederClearWork GetClearanceTask(Guid id)
  506. {
  507. lock (_sync) { return FeederClearanceTasks.FirstOrDefault(t => t.Id == id); }
  508. }
  509. /// <summary>
  510. /// 判断是否包含指定编号的清料任务
  511. /// </summary>
  512. /// <param name="taskNumber"></param>
  513. /// <returns></returns>
  514. public bool ContainsClearanceTaskByNumber(int taskNumber)
  515. {
  516. lock (_sync) { return FeederClearanceTasks.Any(t => t.TaskCode == taskNumber); }
  517. }
  518. /// <summary>
  519. /// 根据编号获取单个清料任务信息
  520. /// </summary>
  521. /// <param name="taskNumber"></param>
  522. /// <returns></returns>
  523. public FeederClearWork GetClearanceTaskByNumber(int taskNumber)
  524. {
  525. lock (_sync) { return FeederClearanceTasks.FirstOrDefault(t => t.TaskCode == taskNumber); }
  526. }
  527. #endregion
  528. #region Light controllers and channels
  529. /// <summary>
  530. /// 获取所有光源控制器配置的只读集合。
  531. /// 线程安全:在内部使用 <see cref="_sync"/> 进行锁定以保证并发访问安全。
  532. /// </summary>
  533. /// <returns>按当前内存中存储顺序返回的只读 <see cref="LightControllerConfig"/> 集合。</returns>
  534. public IReadOnlyCollection<LightControllerConfig> GetAllLightControllers()
  535. {
  536. lock (_sync) { return LightControllers.ToList().AsReadOnly(); }
  537. }
  538. /// <summary>
  539. /// 根据控制器标识获取单个光源控制器配置。
  540. /// 线程安全:该方法对内存集合进行只读访问并在内部加锁。
  541. /// </summary>
  542. /// <param name="id">控制器的整数标识(Id)。</param>
  543. /// <returns>匹配的 <see cref="LightControllerConfig"/> 实例,找不到则返回 <c>null</c>。</returns>
  544. public LightControllerConfig GetLightController(int id)
  545. {
  546. lock (_sync) { return LightControllers.FirstOrDefault(c => c.Id == id); }
  547. }
  548. /// <summary>
  549. /// 新增或更新光源控制器配置。
  550. /// - 若 <paramref name="controller"/> 为 <c>null</c> 返回 <c>null</c>。
  551. /// - 更新时以 Id 匹配现有项并替换。
  552. /// - 新增时分配连续的控制器 Id,并为控制器内的每个通道分配全局通道编号(GlobalIndex)。
  553. /// 线程安全:在内部使用 <see cref="_sync"/> 锁定集合。
  554. /// </summary>
  555. /// <param name="controller">要添加或更新的 <see cref="LightControllerConfig"/> 实例。</param>
  556. /// <returns>已添加或更新的 <see cref="LightControllerConfig"/> 实例;输入为 <c>null</c> 时返回 <c>null</c>。</returns>
  557. /// <exception cref="ArgumentException">当要添加/更新的控制器名称与现有其他控制器重复时抛出。</exception>
  558. public LightControllerConfig AddOrUpdateLightController(LightControllerConfig controller)
  559. {
  560. if (controller == null) return null;
  561. lock (_sync)
  562. {
  563. // 名称不得与其他控制器重复(同名但不同 Id 不允许)
  564. if (LightControllers.Any(c => c.Name == controller.Name && c.Id != controller.Id))
  565. throw new ArgumentException("Controller name must be unique");
  566. var exist = LightControllers.FirstOrDefault(c => c.Id == controller.Id);
  567. if (exist != null)
  568. {
  569. // 更新现有控制器(按索引替换)
  570. var idx = LightControllers.IndexOf(exist);
  571. LightControllers[idx] = controller;
  572. }
  573. else
  574. {
  575. // 新增控制器
  576. // 分配控制器编号为当前最大 Id + 1(若集合为空则为 1)
  577. controller.Id = LightControllers.Count > 0 ? LightControllers.Max(c => c.Id) + 1 : 1;
  578. // 计算下一个全局通道索引:在所有已存在控制器的通道 GlobalIndex 上取最大值并加1
  579. int nextGlobal = 0;
  580. if (LightControllers.SelectMany(c => c.ChannelConfigs).Any())
  581. {
  582. nextGlobal = LightControllers.SelectMany(c => c.ChannelConfigs).Max(ch => ch.GlobalIndex) + 1;
  583. }
  584. // 遍历新增控制器的通道,若通道的 GlobalIndex 无效(<0)或与现有通道冲突,则重新分配
  585. for (int i = 0; i < controller.ChannelConfigs.Count; i++)
  586. {
  587. var ch = controller.ChannelConfigs[i];
  588. if (ch.GlobalIndex < 0 || LightControllers.SelectMany(c => c.ChannelConfigs).Any(existingCh => existingCh.GlobalIndex == ch.GlobalIndex))
  589. {
  590. ch.GlobalIndex = nextGlobal;
  591. nextGlobal++;
  592. }
  593. }
  594. LightControllers.Add(controller);
  595. }
  596. }
  597. // 持久化更新后的光源控制器配置
  598. FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
  599. return controller;
  600. }
  601. /// <summary>
  602. /// 根据 Id 移除光源控制器配置。
  603. /// - 移除后会对剩余控制器重新编号(从1开始连续编号)。
  604. /// 线程安全:在方法体内对集合进行锁定。
  605. /// </summary>
  606. /// <param name="id">要移除的控制器 Id。</param>
  607. /// <returns>如果成功移除返回 <c>true</c>,否则返回 <c>false</c>(比如未找到该 Id)。</returns>
  608. public bool RemoveLightController(int id)
  609. {
  610. bool result = false;
  611. lock (_sync)
  612. {
  613. var e = LightControllers.FirstOrDefault(c => c.Id == id);
  614. if (e != null)
  615. result = LightControllers.Remove(e);
  616. // 重新按 Id 排序并从 1 开始重新编号,以保持连续性
  617. int no = 1;
  618. var sorted = LightControllers.OrderBy(c => c.Id).ToList();
  619. foreach (var c in sorted)
  620. {
  621. c.Id = no;
  622. no++;
  623. }
  624. LightControllers.Clear();
  625. foreach (var c in sorted)
  626. {
  627. LightControllers.Add(c);
  628. }
  629. }
  630. FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
  631. return result;
  632. }
  633. /// <summary>
  634. /// 判断是否包含指定 Id 的光源控制器。
  635. /// 线程安全:在内部使用锁定进行检查。
  636. /// </summary>
  637. /// <param name="id">控制器 Id。</param>
  638. /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
  639. public bool ContainsLightController(int id) { lock (_sync) { return LightControllers.Any(c => c.Id == id); } }
  640. /// <summary>
  641. /// 获取所有光源通道的只读集合,按 GlobalIndex 升序返回。
  642. /// 线程安全:方法内部加锁以保证集合读取的一致性。
  643. /// </summary>
  644. /// <returns>按 GlobalIndex 排序的 <see cref="ChannelConfig"/> 只读集合。</returns>
  645. public IReadOnlyCollection<ChannelConfig> GetAllLightChannels()
  646. {
  647. lock (_sync) { return LightControllers.SelectMany(c => c.ChannelConfigs).OrderBy(ch => ch.GlobalIndex).ToList().AsReadOnly(); }
  648. }
  649. /// <summary>
  650. /// 根据全局通道索引获取单个通道配置。
  651. /// 线程安全:在内部加锁。
  652. /// </summary>
  653. /// <param name="globalIndex">通道的全局索引(GlobalIndex)。</param>
  654. /// <returns>匹配的 <see cref="ChannelConfig"/> 实例,找不到则返回 <c>null</c>。</returns>
  655. public ChannelConfig GetLightChannelByGlobalIndex(int globalIndex)
  656. {
  657. lock (_sync) { return LightControllers.SelectMany(c => c.ChannelConfigs).FirstOrDefault(ch => ch.GlobalIndex == globalIndex); }
  658. }
  659. /// <summary>
  660. /// 判断是否存在指定全局通道索引的通道配置。
  661. /// 线程安全:在内部使用锁定。
  662. /// </summary>
  663. /// <param name="globalIndex">全局通道索引。</param>
  664. /// <returns>存在返回 <c>true</c>,否则返回 <c>false</c>。</returns>
  665. public bool ContainsLightChannelByGlobalIndex(int globalIndex)
  666. {
  667. lock (_sync) { return LightControllers.SelectMany(c => c.ChannelConfigs).Any(ch => ch.GlobalIndex == globalIndex); }
  668. }
  669. /// <summary>
  670. /// 更新指定全局通道的默认亮度(DefaultBrightness)。
  671. /// 线程安全:在内部锁定集合并在更新后持久化配置。
  672. /// </summary>
  673. /// <param name="globalIndex">目标通道的全局索引(GlobalIndex)。</param>
  674. /// <param name="brightness">要设置的亮度值(单位由 <see cref="ChannelConfig"/> 定义)。</param>
  675. /// <returns>更新成功返回 <c>true</c>;找不到对应通道返回 <c>false</c>。</returns>
  676. public bool UpdateChannelDefaultBrightness(int globalIndex, int brightness)
  677. {
  678. lock (_sync)
  679. {
  680. var ch = LightControllers.SelectMany(c => c.ChannelConfigs).FirstOrDefault(x => x.GlobalIndex == globalIndex);
  681. if (ch == null) return false;
  682. ch.DefaultBrightness = brightness;
  683. FileHelper.WriteJsonFile(LightControllers, ConfigPaths.LightControllersConfigurationPath);
  684. return true;
  685. }
  686. }
  687. #endregion
  688. public void Dispose()
  689. {
  690. SaveAll();
  691. GC.SuppressFinalize(this);
  692. }
  693. }
  694. }