SettingViewModel.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. using Prism.Commands;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.Linq;
  6. using System.Runtime.InteropServices;
  7. using System.Threading.Tasks;
  8. using System.Windows;
  9. using TeamAAS.Camera.Interfaces;
  10. using TeamAAS.Communication;
  11. using TeamAAS.Communication.Base;
  12. using TeamAAS.Communication.Config;
  13. using TeamAAS.Communication.Interfaces;
  14. using TeamAAS.Communication.LightSources;
  15. using TeamAAS.Communication.Models;
  16. using TeamAAS.Feeder.Interfaces;
  17. using TeamAAS.Robot.Interfaces;
  18. using TeamAAS.Robot.Models;
  19. using TeamAAS.Views;
  20. namespace TeamAAS.ViewModels
  21. {
  22. /// <summary>
  23. /// 设置页 ViewModel(partial 拆分):
  24. /// 本文件 = 导航/通讯/调试 + 构造函数 + 全部命令声明;
  25. /// 相机/机器人/光源/全局变量/系统设置分别位于 SettingViewModel.*.cs。
  26. /// </summary>
  27. public partial class SettingViewModel : TeamAAS.BindableBase
  28. {
  29. private readonly ICameraManager _cameraManager;
  30. private readonly IRobotManager _robotManager;
  31. private readonly IFeederManager _feederManager;
  32. private readonly CommunicationManager _communicationManager;
  33. [DllImport("gdi32.dll")]
  34. private static extern bool DeleteObject(IntPtr hObject);
  35. #region 导航
  36. private string _selectedSection = "Camera";
  37. public string SelectedSection
  38. {
  39. get { return _selectedSection; }
  40. set
  41. {
  42. if (SetProperty(ref _selectedSection, value))
  43. {
  44. RaisePropertyChanged(nameof(IsBasicTab));
  45. RaisePropertyChanged(nameof(IsPlcTab));
  46. RaisePropertyChanged(nameof(IsCommSection));
  47. RaisePropertyChanged(nameof(CommTabDisplayText));
  48. UpdateDisplayedCommunications();
  49. RaisePropertyChanged(nameof(PropertyGridSelectedObject));
  50. UpdateDebugMode();
  51. if (value == "Light")
  52. {
  53. RefreshLightSerialDevices();
  54. }
  55. if (value == "ProductData")
  56. {
  57. RefreshDatabaseHost();
  58. }
  59. if (value == "GlobalVariable")
  60. {
  61. // 进入全局变量页:从 live 列表克隆 Global 变量到编辑器
  62. InitGlobalVarEditor();
  63. }
  64. }
  65. }
  66. }
  67. #endregion
  68. #region 通讯管理 - 基础属性
  69. public bool IsCommSection => SelectedSection == "BasicComm" || SelectedSection == "PlcComm";
  70. public bool IsBasicTab => SelectedSection == "BasicComm";
  71. public bool IsPlcTab => SelectedSection == "PlcComm";
  72. public string CommTabDisplayText => IsBasicTab ? "基础通讯设备" : "PLC通讯设备";
  73. public ObservableCollection<ICommunication> Communications { get; }
  74. public ObservableCollection<CommunicationTypeInfo> CommunicationTypes { get; }
  75. public ObservableCollection<CommunicationTypeInfo> PlcTypes { get; }
  76. /// <summary>当前Tab下显示的通讯列表</summary>
  77. public ObservableCollection<ICommunication> DisplayedCommunications { get; }
  78. private ICommunication _selectedCommunication;
  79. public ICommunication SelectedCommunication
  80. {
  81. get { return _selectedCommunication; }
  82. set
  83. {
  84. var old = _selectedCommunication as System.ComponentModel.INotifyPropertyChanged;
  85. if (old != null) old.PropertyChanged -= OnSelectedDevicePropertyChanged;
  86. SetProperty(ref _selectedCommunication, value);
  87. var now = _selectedCommunication as System.ComponentModel.INotifyPropertyChanged;
  88. if (now != null) now.PropertyChanged += OnSelectedDevicePropertyChanged;
  89. RaisePropertyChanged(nameof(IsTcpClientSelected));
  90. RaisePropertyChanged(nameof(IsTcpServerSelected));
  91. RaisePropertyChanged(nameof(IsSerialSelected));
  92. RaisePropertyChanged(nameof(IsUdpSelected));
  93. RaisePropertyChanged(nameof(IsWebSelected));
  94. RaisePropertyChanged(nameof(IsPlcSelected));
  95. RaisePropertyChanged(nameof(HasSelectedCommunication));
  96. RaisePropertyChanged(nameof(PropertyGridSelectedObject));
  97. RaisePropertyChanged(nameof(IsPropertyGridEnabled));
  98. UpdateDebugMode();
  99. }
  100. }
  101. private void OnSelectedDevicePropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
  102. {
  103. if (e.PropertyName == nameof(ICommunication.IsConnected))
  104. {
  105. RaisePropertyChanged(nameof(IsPropertyGridEnabled));
  106. UpdateDebugMode();
  107. }
  108. }
  109. public bool HasSelectedCommunication => _selectedCommunication != null;
  110. /// <summary>
  111. /// PropertyGrid 绑定对象 - 直接返回当前选中的配置对象
  112. /// 基础Tab 和 PLCTab 使用不同的 Info 子类,PropertyGrid 自动只显示对应属性
  113. /// </summary>
  114. public object PropertyGridSelectedObject => _selectedCommunication;
  115. /// <summary>
  116. /// PropertyGrid 是否可用(未选中或已连接时禁用)
  117. /// </summary>
  118. public bool IsPropertyGridEnabled => _selectedCommunication != null && !_selectedCommunication.IsConnected;
  119. private bool IsType(string typeKey) => SelectedCommunication?.TypeKey?.Contains(typeKey) ?? false;
  120. public bool IsTcpClientSelected => IsType("TcpClientCommunication");
  121. public bool IsTcpServerSelected => IsType("TcpServerCommunication");
  122. public bool IsSerialSelected => IsType("SerialCommunication");
  123. public bool IsUdpSelected => IsType("UdpCommunication");
  124. public bool IsWebSelected => IsType("WebCommunication");
  125. public bool IsPlcSelected => SelectedCommunication is PlcCommunicationBase;
  126. private string _addTypeKey = "";
  127. public string AddTypeKey
  128. {
  129. get { return _addTypeKey; }
  130. set { SetProperty(ref _addTypeKey, value); }
  131. }
  132. private string _commMessage = "";
  133. public string CommMessage
  134. {
  135. get { return _commMessage; }
  136. set { SetProperty(ref _commMessage, value); }
  137. }
  138. /// <summary>基础通讯类型列表(供基础Tab添加用)</summary>
  139. public ObservableCollection<CommunicationTypeInfo> BasicCommTypes { get; }
  140. private CommunicationTypeInfo _selectedPlcType;
  141. public CommunicationTypeInfo SelectedPlcType
  142. {
  143. get { return _selectedPlcType; }
  144. set { SetProperty(ref _selectedPlcType, value); }
  145. }
  146. #endregion
  147. #region 视觉引擎与主页画面
  148. /// <summary>可用视觉引擎(启动时反射探测:VisionPro / VM / Halcon ...)</summary>
  149. public System.Collections.Generic.IReadOnlyList<TeamAAS.Vision.IVisualEngine> AvailableEngines
  150. => TeamAAS.Vision.VisualEngineManager.Instance.Engines;
  151. private TeamAAS.Vision.IVisualEngine _selectedEngine;
  152. /// <summary>当前选中的视觉引擎(一键切换:主页画面/标定页随引擎变化)</summary>
  153. public TeamAAS.Vision.IVisualEngine SelectedEngine
  154. {
  155. get
  156. {
  157. return _selectedEngine
  158. ?? TeamAAS.Vision.VisualEngineManager.Instance.Current
  159. ?? TeamAAS.Vision.VisualEngineManager.Instance.GetOrLoad(App.SystemConfig?.Vision?.EngineName);
  160. }
  161. set
  162. {
  163. if (value == null) return;
  164. _selectedEngine = value;
  165. RaisePropertyChanged(nameof(SelectedEngine));
  166. if (!TeamAAS.Vision.VisualEngineManager.Instance.SetCurrent(value.EngineName)) return;
  167. App.SystemConfig.Vision.EngineName = value.EngineName;
  168. ApplyVisionLayout();
  169. App.SaveSystemConfig();
  170. }
  171. }
  172. /// <summary>按当前配置重建主页画面布局、重注册画面框并保存配置</summary>
  173. private void ApplyVisionLayout()
  174. {
  175. var vision = App.SystemConfig.Vision;
  176. var engine = TeamAAS.Vision.VisualEngineManager.Instance.Current;
  177. engine?.ApplyHomeLayout(vision.HomePageFrameCount, vision.HomeFrameArrange);
  178. TeamAAS.Vision.VisualFrameManager.Clear();
  179. for (int i = 0; i < vision.HomePageFrameCount; i++)
  180. TeamAAS.Vision.VisualFrameManager.Register($"home:{i + 1}", vision.GetFrameName(i), "Home");
  181. // 主页 VM 不一定存活,布局数据已持久化;主页下次导航时按配置重建
  182. App.SaveSystemConfig();
  183. }
  184. private DelegateCommand _applyVisionLayoutCommand;
  185. public DelegateCommand ApplyVisionLayoutCommand
  186. => _applyVisionLayoutCommand ?? (_applyVisionLayoutCommand = new DelegateCommand(ApplyVisionLayout));
  187. /// <summary>主页画面数可选规格:均能被最优行列算法精确铺满(无空白框)</summary>
  188. public int[] HomePageFrameOptions { get; } = { 1, 2, 4, 6, 9 };
  189. /// <summary>主页画面框水平排列(HomeFrameArrange=1)</summary>
  190. public bool IsHorizontalArrange
  191. {
  192. get => (App.SystemConfig?.Vision?.HomeFrameArrange ?? 1) == 1;
  193. set { if (value && App.SystemConfig?.Vision != null) App.SystemConfig.Vision.HomeFrameArrange = 1; }
  194. }
  195. /// <summary>主页画面框垂直排列(HomeFrameArrange=2)</summary>
  196. public bool IsVerticalArrange
  197. {
  198. get => (App.SystemConfig?.Vision?.HomeFrameArrange ?? 1) == 2;
  199. set { if (value && App.SystemConfig?.Vision != null) App.SystemConfig.Vision.HomeFrameArrange = 2; }
  200. }
  201. #endregion
  202. #region 通讯调试
  203. private ICommunication _subscribedDevice;
  204. private bool _isPlcDebugMode;
  205. public bool IsPlcDebugMode
  206. {
  207. get { return _isPlcDebugMode; }
  208. set { SetProperty(ref _isPlcDebugMode, value); }
  209. }
  210. private object _currentCommunicationDebugPage;
  211. /// <summary>品牌专属调试页(由 [Communication] 特性 DebugPageType 创建);null = 使用通用通讯测试面板</summary>
  212. public object CurrentCommunicationDebugPage
  213. {
  214. get { return _currentCommunicationDebugPage; }
  215. private set
  216. {
  217. SetProperty(ref _currentCommunicationDebugPage, value);
  218. }
  219. }
  220. private ICommunication _debugPageDevice;
  221. /// <summary>
  222. /// 按选中的设备刷新专属调试页;设备实例未变化时不重建(保留页面状态)。
  223. /// </summary>
  224. private void RefreshCommunicationDebugPage()
  225. {
  226. var comm = _selectedCommunication;
  227. if (comm == _debugPageDevice && (comm != null || _currentCommunicationDebugPage == null))
  228. return;
  229. (_currentCommunicationDebugPage as TeamAAS.Communication.UI.ICommunicationDebugPage)?.UnbindCommunication();
  230. _debugPageDevice = null;
  231. CurrentCommunicationDebugPage = null;
  232. if (comm == null) return;
  233. try
  234. {
  235. var page = TeamAAS.Communication.UI.CommunicationDebugPageFactory.Create(comm);
  236. if (page == null) return;
  237. _debugPageDevice = comm;
  238. CurrentCommunicationDebugPage = page;
  239. }
  240. catch (Exception ex)
  241. {
  242. System.Diagnostics.Debug.WriteLine($"加载通讯调试页失败: {ex.Message}");
  243. }
  244. }
  245. #endregion
  246. #region 相机命令
  247. public DelegateCommand SearchDevicesCommand { get; }
  248. public DelegateCommand DeleteCameraCommand { get; }
  249. public DelegateCommand ConnectCommand { get; }
  250. public DelegateCommand DisconnectCommand { get; }
  251. public DelegateCommand StartGrabbingCommand { get; }
  252. public DelegateCommand StopGrabbingCommand { get; }
  253. public DelegateCommand SaveCameraConfigCommand { get; }
  254. #endregion
  255. #region 机器人命令
  256. public DelegateCommand AddRobotCommand { get; }
  257. public DelegateCommand DeleteRobotCommand { get; }
  258. public DelegateCommand ConnectRobotCommand { get; }
  259. public DelegateCommand DisconnectRobotCommand { get; }
  260. public DelegateCommand SaveRobotConfigCommand { get; }
  261. #endregion
  262. #region 供料器命令
  263. public DelegateCommand AddFeederCommand { get; }
  264. public DelegateCommand DeleteFeederCommand { get; }
  265. public DelegateCommand SaveFeederConfigCommand { get; }
  266. public DelegateCommand ConnectFeederCommand { get; }
  267. public DelegateCommand DisconnectFeederCommand { get; }
  268. public DelegateCommand ToggleBacklightCommand { get; }
  269. public DelegateCommand<object> FeederDirectionCommand { get; }
  270. public DelegateCommand StopFeederActionCommand { get; }
  271. public DelegateCommand ExecuteActionCommand { get; }
  272. public DelegateCommand DownloadParamCommand { get; }
  273. #region 序列集命令
  274. public DelegateCommand AddSequenceCommand { get; }
  275. public DelegateCommand DeleteSequenceCommand { get; }
  276. public DelegateCommand RenameSequenceCommand { get; }
  277. public DelegateCommand<string> AddSequenceStepCommand { get; }
  278. public DelegateCommand MoveUpStepCommand { get; }
  279. public DelegateCommand MoveDownStepCommand { get; }
  280. public DelegateCommand DeleteStepCommand { get; }
  281. public DelegateCommand ExecuteSequenceCommand { get; }
  282. public DelegateCommand StopSequenceCommand { get; }
  283. #endregion
  284. #endregion
  285. #region 光源命令
  286. public DelegateCommand LightConnectCommand { get; }
  287. public DelegateCommand LightDisconnectCommand { get; }
  288. public DelegateCommand LightTurnOnAllCommand { get; }
  289. public DelegateCommand LightTurnOffAllCommand { get; }
  290. public DelegateCommand LightSetBrightnessCommand { get; }
  291. public DelegateCommand LightReadBrightnessCommand { get; }
  292. public DelegateCommand LightRefreshDevicesCommand { get; }
  293. #endregion
  294. #region 通讯命令
  295. public DelegateCommand AddCommunicationCommand { get; }
  296. public DelegateCommand DeleteCommunicationCommand { get; }
  297. public DelegateCommand ConnectCommunicationCommand { get; }
  298. public DelegateCommand DisconnectCommunicationCommand { get; }
  299. public DelegateCommand SaveCommunicationsCommand { get; }
  300. public DelegateCommand RefreshCommunicationCommand { get; }
  301. public DelegateCommand ConnectAllCommand { get; }
  302. public DelegateCommand DisconnectAllCommand { get; }
  303. #endregion
  304. public SettingViewModel(ICameraManager cameraManager, IRobotManager robotManager,
  305. CommunicationManager communicationManager, IFeederManager feederManager)
  306. {
  307. _cameraManager = cameraManager;
  308. _robotManager = robotManager;
  309. _communicationManager = communicationManager;
  310. _feederManager = feederManager;
  311. Cameras = new ObservableCollection<TeamAAS.Camera.Models.CameraInfo>();
  312. SdkStatus = new ObservableCollection<SdkStatusItem>();
  313. Robots = new ObservableCollection<RobotInfo>();
  314. Feeders = new ObservableCollection<TeamAAS.Feeder.Models.FeederInfo>();
  315. Communications = new ObservableCollection<ICommunication>();
  316. DisplayedCommunications = new ObservableCollection<ICommunication>();
  317. CommunicationTypes = new ObservableCollection<CommunicationTypeInfo>();
  318. PlcTypes = new ObservableCollection<CommunicationTypeInfo>();
  319. BasicCommTypes = new ObservableCollection<CommunicationTypeInfo>();
  320. // 相机命令
  321. SearchDevicesCommand = new DelegateCommand(OpenSearchDialog);
  322. DeleteCameraCommand = new DelegateCommand(DeleteCamera);
  323. ConnectCommand = new DelegateCommand(async () => await ConnectAsync());
  324. DisconnectCommand = new DelegateCommand(Disconnect);
  325. StartGrabbingCommand = new DelegateCommand(StartGrabbing);
  326. StopGrabbingCommand = new DelegateCommand(StopGrabbing);
  327. SaveCameraConfigCommand = new DelegateCommand(SaveCameraConfig);
  328. // 机器人命令
  329. AddRobotCommand = new DelegateCommand(AddRobot);
  330. DeleteRobotCommand = new DelegateCommand(DeleteRobot);
  331. ConnectRobotCommand = new DelegateCommand(async () => await ConnectRobotAsync());
  332. DisconnectRobotCommand = new DelegateCommand(DisconnectRobot);
  333. SaveRobotConfigCommand = new DelegateCommand(SaveRobotConfig);
  334. // 供料器命令
  335. AddFeederCommand = new DelegateCommand(AddFeeder);
  336. DeleteFeederCommand = new DelegateCommand(DeleteFeeder, () => HasSelectedFeeder);
  337. SaveFeederConfigCommand = new DelegateCommand(SaveFeederConfig);
  338. ConnectFeederCommand = new DelegateCommand(async () => await ConnectFeederAsync());
  339. DisconnectFeederCommand = new DelegateCommand(DisconnectFeeder);
  340. ToggleBacklightCommand = new DelegateCommand(ToggleBacklight);
  341. FeederDirectionCommand = new DelegateCommand<object>(async (p) => await ExecuteDirectionAsync(Convert.ToInt32(p)));
  342. StopFeederActionCommand = new DelegateCommand(async () => await StopFeederAsync());
  343. ExecuteActionCommand = new DelegateCommand(async () => await ExecuteCurrentActionAsync());
  344. DownloadParamCommand = new DelegateCommand(async () => await DownloadFeederParamsAsync());
  345. AddSequenceCommand = new DelegateCommand(AddSequence);
  346. DeleteSequenceCommand = new DelegateCommand(DeleteSequence);
  347. RenameSequenceCommand = new DelegateCommand(RenameSequence);
  348. AddSequenceStepCommand = new DelegateCommand<string>(AddSequenceStep);
  349. MoveUpStepCommand = new DelegateCommand(MoveUpStep);
  350. MoveDownStepCommand = new DelegateCommand(MoveDownStep);
  351. DeleteStepCommand = new DelegateCommand(DeleteStep);
  352. ExecuteSequenceCommand = new DelegateCommand(async () => await ExecuteSequenceAsync());
  353. StopSequenceCommand = new DelegateCommand(StopSequence);
  354. // 光源命令
  355. LightConnectCommand = new DelegateCommand(async () => await LightConnectAsync());
  356. LightDisconnectCommand = new DelegateCommand(LightDisconnect);
  357. LightTurnOnAllCommand = new DelegateCommand(async () => await LightTurnOnAllAsync());
  358. LightTurnOffAllCommand = new DelegateCommand(async () => await LightTurnOffAllAsync());
  359. LightSetBrightnessCommand = new DelegateCommand(async () => await LightSetBrightnessAsync());
  360. LightReadBrightnessCommand = new DelegateCommand(async () => await LightReadBrightnessAsync());
  361. LightRefreshDevicesCommand = new DelegateCommand(RefreshLightSerialDevices);
  362. // 通讯命令
  363. AddCommunicationCommand = new DelegateCommand(AddCommunication);
  364. DeleteCommunicationCommand = new DelegateCommand(DeleteCommunication);
  365. ConnectCommunicationCommand = new DelegateCommand(async () => await ConnectCommunicationAsync());
  366. DisconnectCommunicationCommand = new DelegateCommand(DisconnectCommunication);
  367. SaveCommunicationsCommand = new DelegateCommand(SaveCommunications);
  368. RefreshCommunicationCommand = new DelegateCommand(RefreshCommunications);
  369. ConnectAllCommand = new DelegateCommand(ConnectAll);
  370. DisconnectAllCommand = new DelegateCommand(DisconnectAll);
  371. // 全局变量命令
  372. SaveGlobalVarsCommand = new DelegateCommand(SaveGlobalVars);
  373. InitGlobalVarEditor();
  374. // 通用设置命令
  375. SaveSystemConfigCommand = new DelegateCommand(SaveSystemConfig);
  376. LoadSystemConfigCommand = new DelegateCommand(LoadSystemConfig);
  377. // 语言设置命令
  378. RefreshLanguagesCommand = new DelegateCommand(RefreshLanguages);
  379. // 全局事件配置命令
  380. SaveGlobalEventConfigCommand = new DelegateCommand(SaveGlobalEventConfig);
  381. LoadGlobalEventConfigCommand = new DelegateCommand(LoadGlobalEventConfig);
  382. AddHeartbeatCommand = new DelegateCommand(AddHeartbeat);
  383. RemoveHeartbeatCommand = new DelegateCommand<HeartbeatConfig>(RemoveHeartbeat);
  384. AddProductSwitchEventCommand = new DelegateCommand(AddProductSwitchEvent);
  385. RemoveProductSwitchEventCommand = new DelegateCommand<ProductSwitchEvent>(RemoveProductSwitchEvent);
  386. }
  387. /// <summary>
  388. /// 首次导航到设置页时延迟加载(UI 先渲染,数据后台加载)
  389. /// </summary>
  390. public void DelayedLoad()
  391. {
  392. try { LoadSdkStatus(); } catch { }
  393. try { LoadCommunicationTypes(); } catch { }
  394. try { LoadPlcTypes(); } catch { }
  395. try { LoadBasicCommTypes(); } catch { }
  396. try { RefreshCommunications(); } catch { }
  397. try { RefreshLightSerialDevices(); } catch { }
  398. try { InitLightModelOptions(); } catch { }
  399. try { RefreshCameraList(); } catch { }
  400. try { RefreshRobotList(); } catch { }
  401. try { RefreshFeederList(); } catch { }
  402. try { RefreshRecipeList(); } catch { }
  403. try { InitGlobalVarEditor(); } catch { }
  404. try { TeamAAS.FlowEditor.Execution.ResultRegistry.RefreshGlobals(); } catch { }
  405. try { LoadSystemConfig(); } catch { }
  406. try { LoadGlobalEventConfig(); } catch { }
  407. try { InitializeLanguageSettings(); } catch { }
  408. }
  409. private void InitLightModelOptions()
  410. {
  411. LightModelDisplayOptions.Clear();
  412. foreach (LightModel model in Enum.GetValues(typeof(LightModel)))
  413. {
  414. var field = typeof(LightModel).GetField(model.ToString());
  415. var attr = field?.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false)
  416. .OfType<System.ComponentModel.DescriptionAttribute>()
  417. .FirstOrDefault();
  418. string display = attr?.Description ?? model.ToString();
  419. LightModelDisplayOptions.Add(new LightModelDisplay { Model = model, Display = display });
  420. }
  421. SelectedLightModelItem = LightModelDisplayOptions.FirstOrDefault();
  422. }
  423. #region 通讯基础操作
  424. private void LoadCommunicationTypes()
  425. {
  426. CommunicationTypes.Clear();
  427. try
  428. {
  429. var types = _communicationManager.GetAvailableTypes();
  430. foreach (var t in types)
  431. {
  432. CommunicationTypes.Add(t);
  433. }
  434. AddTypeKey = CommunicationTypes.FirstOrDefault()?.TypeKey ?? "";
  435. }
  436. catch (Exception ex)
  437. {
  438. CommMessage = $"加载通讯类型失败: {ex.Message}";
  439. }
  440. }
  441. private void LoadPlcTypes()
  442. {
  443. PlcTypes.Clear();
  444. try
  445. {
  446. var types = _communicationManager.GetPlcTypes();
  447. foreach (var t in types)
  448. {
  449. PlcTypes.Add(t);
  450. }
  451. }
  452. catch (Exception ex)
  453. {
  454. CommMessage = $"加载 PLC 协议类型失败: {ex.Message}";
  455. }
  456. }
  457. private void LoadBasicCommTypes()
  458. {
  459. BasicCommTypes.Clear();
  460. try
  461. {
  462. var types = _communicationManager.GetBasicTypes();
  463. foreach (var t in types)
  464. {
  465. BasicCommTypes.Add(t);
  466. }
  467. }
  468. catch { }
  469. }
  470. private void RefreshCommunications()
  471. {
  472. Communications.Clear();
  473. try
  474. {
  475. foreach (var comm in _communicationManager.Devices)
  476. {
  477. Communications.Add(comm);
  478. }
  479. }
  480. catch (Exception ex)
  481. {
  482. CommMessage = $"加载通讯列表失败: {ex.Message}";
  483. }
  484. UpdateDisplayedCommunications();
  485. RefreshLightSerialDevices();
  486. }
  487. private void UpdateDisplayedCommunications()
  488. {
  489. DisplayedCommunications.Clear();
  490. var list = Communications.ToList();
  491. if (IsBasicTab)
  492. {
  493. foreach (var c in list)
  494. {
  495. if (c is PlcCommunicationBase) continue;
  496. DisplayedCommunications.Add(c);
  497. }
  498. }
  499. else if (IsPlcTab)
  500. {
  501. foreach (var c in list)
  502. {
  503. if (c is PlcCommunicationBase)
  504. DisplayedCommunications.Add(c);
  505. }
  506. }
  507. RaisePropertyChanged(nameof(DisplayedCommunications));
  508. }
  509. private void AddCommunication()
  510. {
  511. try
  512. {
  513. string typeKey;
  514. string baseName;
  515. if (IsPlcTab)
  516. {
  517. var plcType = SelectedPlcType;
  518. if (plcType == null)
  519. {
  520. DialogHelper.ShowWarning("请先选择 PLC 协议类型");
  521. return;
  522. }
  523. typeKey = plcType.TypeKey;
  524. baseName = "PLC" + (DisplayedCommunications.Count + 1);
  525. }
  526. else
  527. {
  528. if (string.IsNullOrWhiteSpace(AddTypeKey))
  529. {
  530. DialogHelper.ShowWarning("请先选择通讯类型");
  531. return;
  532. }
  533. typeKey = AddTypeKey;
  534. baseName = _communicationManager.GetTypeInfo(typeKey)?.DisplayName ?? "通讯" + (DisplayedCommunications.Count + 1);
  535. }
  536. var index = Communications.Count + 1;
  537. var name = GetUniqueCommunicationName(baseName + index);
  538. var device = _communicationManager.Create(typeKey, Guid.NewGuid(), name, index);
  539. _communicationManager.Register(device);
  540. Communications.Add(device);
  541. UpdateDisplayedCommunications();
  542. SelectedCommunication = device;
  543. CommMessage = $"已添加 {device.Name}";
  544. RefreshLightSerialDevices();
  545. }
  546. catch (Exception ex)
  547. {
  548. DialogHelper.ShowError("添加通讯失败", ex);
  549. }
  550. }
  551. private string GetUniqueCommunicationName(string baseName)
  552. {
  553. var names = new HashSet<string>(
  554. Communications.Select(c => c.Name),
  555. StringComparer.OrdinalIgnoreCase);
  556. if (!names.Contains(baseName)) return baseName;
  557. int n = 1;
  558. string candidate;
  559. do
  560. {
  561. candidate = $"{baseName}({n})";
  562. n++;
  563. } while (names.Contains(candidate));
  564. return candidate;
  565. }
  566. private void DeleteCommunication()
  567. {
  568. if (SelectedCommunication == null) return;
  569. if (!DialogHelper.ConfirmYesNo(
  570. $"确认删除 \"{SelectedCommunication.Name}\" 吗?",
  571. "确认删除"))
  572. {
  573. return;
  574. }
  575. var toRemove = SelectedCommunication;
  576. try
  577. {
  578. _communicationManager.Remove(toRemove.Id);
  579. }
  580. catch (Exception ex)
  581. {
  582. System.Diagnostics.Debug.WriteLine($"移除通讯设备异常: {ex.Message}");
  583. }
  584. Communications.Remove(toRemove);
  585. DisplayedCommunications.Remove(toRemove);
  586. for (int i = 0; i < Communications.Count; i++)
  587. {
  588. Communications[i].Index = i + 1;
  589. }
  590. SelectedCommunication = null;
  591. RefreshLightSerialDevices();
  592. }
  593. private async Task ConnectCommunicationAsync()
  594. {
  595. if (SelectedCommunication == null) return;
  596. try
  597. {
  598. await SelectedCommunication.ConnectAsync();
  599. UpdateDebugMode();
  600. RaisePropertyChanged(nameof(IsPropertyGridEnabled));
  601. CommMessage = SelectedCommunication.IsConnected ? $"已连接 {SelectedCommunication.Name}" : $"连接失败 {SelectedCommunication.Name}";
  602. }
  603. catch (Exception ex)
  604. {
  605. CommMessage = $"连接异常: {ex.Message}";
  606. }
  607. }
  608. private void DisconnectCommunication()
  609. {
  610. if (SelectedCommunication == null) return;
  611. try
  612. {
  613. SelectedCommunication.Disconnect();
  614. RaisePropertyChanged(nameof(IsPropertyGridEnabled));
  615. CommMessage = $"已断开 {SelectedCommunication.Name}";
  616. }
  617. catch (Exception ex)
  618. {
  619. CommMessage = $"断开异常: {ex.Message}";
  620. }
  621. }
  622. private void ConnectAll()
  623. {
  624. try
  625. {
  626. foreach (var device in Communications)
  627. {
  628. try { device.Connect(); } catch { }
  629. }
  630. CommMessage = "已连接所有设备";
  631. }
  632. catch (Exception ex)
  633. {
  634. CommMessage = $"批量连接异常: {ex.Message}";
  635. }
  636. }
  637. private void DisconnectAll()
  638. {
  639. try
  640. {
  641. foreach (var device in Communications)
  642. {
  643. try { device.Disconnect(); } catch { }
  644. }
  645. CommMessage = "已断开所有设备";
  646. }
  647. catch (Exception ex)
  648. {
  649. CommMessage = $"批量断开异常: {ex.Message}";
  650. }
  651. }
  652. private void SaveCommunications()
  653. {
  654. try
  655. {
  656. if (_communicationManager.SaveConfig())
  657. CommMessage = "通讯配置已保存";
  658. else
  659. DialogHelper.ShowError("通讯配置保存失败,请查看日志");
  660. }
  661. catch (Exception ex)
  662. {
  663. DialogHelper.ShowError("保存通讯配置失败", ex);
  664. }
  665. }
  666. #endregion
  667. #region 通讯调试
  668. private void UpdateDebugMode()
  669. {
  670. if (_subscribedDevice != null)
  671. {
  672. _subscribedDevice.DataReceivedEvent -= OnDataReceived;
  673. _subscribedDevice = null;
  674. }
  675. if (_selectedCommunication == null)
  676. {
  677. IsPlcDebugMode = false;
  678. return;
  679. }
  680. IsPlcDebugMode = _selectedCommunication is PlcCommunicationBase;
  681. var comm = _communicationManager.Get(_selectedCommunication.Id);
  682. if (comm != null)
  683. {
  684. _subscribedDevice = comm;
  685. _subscribedDevice.DataReceivedEvent += OnDataReceived;
  686. }
  687. RefreshCommunicationDebugPage();
  688. }
  689. private void OnDataReceived(object sender, string data)
  690. {
  691. var comm = _selectedCommunication as BindableCommunicationBase;
  692. if (comm == null) return;
  693. Application.Current.Dispatcher.Invoke(() =>
  694. {
  695. comm.AppendLog($"RX: {data}");
  696. });
  697. }
  698. #endregion
  699. }
  700. }