SettingViewModel.cs 33 KB

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