SettingViewModel.cs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819
  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(DeleteRobot);
  385. ConnectMotionCardCommand = new DelegateCommand(async () => await ConnectRobotAsync());
  386. DisconnectMotionCardCommand = new DelegateCommand(DisconnectRobot);
  387. SaveMotionCardConfigCommand = new DelegateCommand(SaveRobotConfig);
  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 { RefreshFeederList(); } catch { }
  419. try { RefreshRecipeList(); } catch { }
  420. try { InitGlobalVarEditor(); } catch { }
  421. try { TeamAAS.FlowEditor.Execution.ResultRegistry.RefreshGlobals(); } catch { }
  422. try { LoadSystemConfig(); } catch { }
  423. try { LoadGlobalEventConfig(); } catch { }
  424. try { InitializeLanguageSettings(); } catch { }
  425. }
  426. private void InitLightModelOptions()
  427. {
  428. LightModelDisplayOptions.Clear();
  429. foreach (LightModel model in Enum.GetValues(typeof(LightModel)))
  430. {
  431. var field = typeof(LightModel).GetField(model.ToString());
  432. var attr = field?.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false)
  433. .OfType<System.ComponentModel.DescriptionAttribute>()
  434. .FirstOrDefault();
  435. string display = attr?.Description ?? model.ToString();
  436. LightModelDisplayOptions.Add(new LightModelDisplay { Model = model, Display = display });
  437. }
  438. SelectedLightModelItem = LightModelDisplayOptions.FirstOrDefault();
  439. }
  440. #region 通讯基础操作
  441. private void LoadCommunicationTypes()
  442. {
  443. CommunicationTypes.Clear();
  444. try
  445. {
  446. var types = _communicationManager.GetAvailableTypes();
  447. foreach (var t in types)
  448. {
  449. CommunicationTypes.Add(t);
  450. }
  451. AddTypeKey = CommunicationTypes.FirstOrDefault()?.TypeKey ?? "";
  452. }
  453. catch (Exception ex)
  454. {
  455. CommMessage = $"加载通讯类型失败: {ex.Message}";
  456. }
  457. }
  458. private void LoadPlcTypes()
  459. {
  460. PlcTypes.Clear();
  461. try
  462. {
  463. var types = _communicationManager.GetPlcTypes();
  464. foreach (var t in types)
  465. {
  466. PlcTypes.Add(t);
  467. }
  468. }
  469. catch (Exception ex)
  470. {
  471. CommMessage = $"加载 PLC 协议类型失败: {ex.Message}";
  472. }
  473. }
  474. private void LoadBasicCommTypes()
  475. {
  476. BasicCommTypes.Clear();
  477. try
  478. {
  479. var types = _communicationManager.GetBasicTypes();
  480. foreach (var t in types)
  481. {
  482. BasicCommTypes.Add(t);
  483. }
  484. }
  485. catch { }
  486. }
  487. private void RefreshCommunications()
  488. {
  489. Communications.Clear();
  490. try
  491. {
  492. foreach (var comm in _communicationManager.Devices)
  493. {
  494. Communications.Add(comm);
  495. }
  496. }
  497. catch (Exception ex)
  498. {
  499. CommMessage = $"加载通讯列表失败: {ex.Message}";
  500. }
  501. UpdateDisplayedCommunications();
  502. RefreshLightSerialDevices();
  503. }
  504. private void UpdateDisplayedCommunications()
  505. {
  506. DisplayedCommunications.Clear();
  507. var list = Communications.ToList();
  508. if (IsBasicTab)
  509. {
  510. foreach (var c in list)
  511. {
  512. if (c is PlcCommunicationBase) continue;
  513. DisplayedCommunications.Add(c);
  514. }
  515. }
  516. else if (IsPlcTab)
  517. {
  518. foreach (var c in list)
  519. {
  520. if (c is PlcCommunicationBase)
  521. DisplayedCommunications.Add(c);
  522. }
  523. }
  524. RaisePropertyChanged(nameof(DisplayedCommunications));
  525. }
  526. private void AddCommunication()
  527. {
  528. try
  529. {
  530. string typeKey;
  531. string baseName;
  532. if (IsPlcTab)
  533. {
  534. var plcType = SelectedPlcType;
  535. if (plcType == null)
  536. {
  537. DialogHelper.ShowWarning("请先选择 PLC 协议类型");
  538. return;
  539. }
  540. typeKey = plcType.TypeKey;
  541. baseName = "PLC" + (DisplayedCommunications.Count + 1);
  542. }
  543. else
  544. {
  545. if (string.IsNullOrWhiteSpace(AddTypeKey))
  546. {
  547. DialogHelper.ShowWarning("请先选择通讯类型");
  548. return;
  549. }
  550. typeKey = AddTypeKey;
  551. baseName = _communicationManager.GetTypeInfo(typeKey)?.DisplayName ?? "通讯" + (DisplayedCommunications.Count + 1);
  552. }
  553. var index = Communications.Count + 1;
  554. var name = GetUniqueCommunicationName(baseName + index);
  555. var device = _communicationManager.Create(typeKey, Guid.NewGuid(), name, index);
  556. _communicationManager.Register(device);
  557. Communications.Add(device);
  558. UpdateDisplayedCommunications();
  559. SelectedCommunication = device;
  560. CommMessage = $"已添加 {device.Name}";
  561. RefreshLightSerialDevices();
  562. }
  563. catch (Exception ex)
  564. {
  565. DialogHelper.ShowError("添加通讯失败", ex);
  566. }
  567. }
  568. private string GetUniqueCommunicationName(string baseName)
  569. {
  570. var names = new HashSet<string>(
  571. Communications.Select(c => c.Name),
  572. StringComparer.OrdinalIgnoreCase);
  573. if (!names.Contains(baseName)) return baseName;
  574. int n = 1;
  575. string candidate;
  576. do
  577. {
  578. candidate = $"{baseName}({n})";
  579. n++;
  580. } while (names.Contains(candidate));
  581. return candidate;
  582. }
  583. private void DeleteCommunication()
  584. {
  585. if (SelectedCommunication == null) return;
  586. if (!DialogHelper.ConfirmYesNo(
  587. $"确认删除 \"{SelectedCommunication.Name}\" 吗?",
  588. "确认删除"))
  589. {
  590. return;
  591. }
  592. var toRemove = SelectedCommunication;
  593. try
  594. {
  595. _communicationManager.Remove(toRemove.Id);
  596. }
  597. catch (Exception ex)
  598. {
  599. System.Diagnostics.Debug.WriteLine($"移除通讯设备异常: {ex.Message}");
  600. }
  601. Communications.Remove(toRemove);
  602. DisplayedCommunications.Remove(toRemove);
  603. for (int i = 0; i < Communications.Count; i++)
  604. {
  605. Communications[i].Index = i + 1;
  606. }
  607. SelectedCommunication = null;
  608. RefreshLightSerialDevices();
  609. }
  610. private async Task ConnectCommunicationAsync()
  611. {
  612. if (SelectedCommunication == null) return;
  613. try
  614. {
  615. await SelectedCommunication.ConnectAsync();
  616. UpdateDebugMode();
  617. RaisePropertyChanged(nameof(IsPropertyGridEnabled));
  618. CommMessage = SelectedCommunication.IsConnected ? $"已连接 {SelectedCommunication.Name}" : $"连接失败 {SelectedCommunication.Name}";
  619. }
  620. catch (Exception ex)
  621. {
  622. CommMessage = $"连接异常: {ex.Message}";
  623. }
  624. }
  625. private void DisconnectCommunication()
  626. {
  627. if (SelectedCommunication == null) return;
  628. try
  629. {
  630. SelectedCommunication.Disconnect();
  631. RaisePropertyChanged(nameof(IsPropertyGridEnabled));
  632. CommMessage = $"已断开 {SelectedCommunication.Name}";
  633. }
  634. catch (Exception ex)
  635. {
  636. CommMessage = $"断开异常: {ex.Message}";
  637. }
  638. }
  639. private void ConnectAll()
  640. {
  641. try
  642. {
  643. foreach (var device in Communications)
  644. {
  645. try { device.Connect(); } catch { }
  646. }
  647. CommMessage = "已连接所有设备";
  648. }
  649. catch (Exception ex)
  650. {
  651. CommMessage = $"批量连接异常: {ex.Message}";
  652. }
  653. }
  654. private void DisconnectAll()
  655. {
  656. try
  657. {
  658. foreach (var device in Communications)
  659. {
  660. try { device.Disconnect(); } catch { }
  661. }
  662. CommMessage = "已断开所有设备";
  663. }
  664. catch (Exception ex)
  665. {
  666. CommMessage = $"批量断开异常: {ex.Message}";
  667. }
  668. }
  669. private void SaveCommunications()
  670. {
  671. try
  672. {
  673. if (_communicationManager.SaveConfig())
  674. CommMessage = "通讯配置已保存";
  675. else
  676. DialogHelper.ShowError("通讯配置保存失败,请查看日志");
  677. }
  678. catch (Exception ex)
  679. {
  680. DialogHelper.ShowError("保存通讯配置失败", ex);
  681. }
  682. }
  683. #endregion
  684. #region 通讯调试
  685. private void UpdateDebugMode()
  686. {
  687. if (_subscribedDevice != null)
  688. {
  689. _subscribedDevice.DataReceivedEvent -= OnDataReceived;
  690. _subscribedDevice = null;
  691. }
  692. if (_selectedCommunication == null)
  693. {
  694. IsPlcDebugMode = false;
  695. return;
  696. }
  697. IsPlcDebugMode = _selectedCommunication is PlcCommunicationBase;
  698. var comm = _communicationManager.Get(_selectedCommunication.Id);
  699. if (comm != null)
  700. {
  701. _subscribedDevice = comm;
  702. _subscribedDevice.DataReceivedEvent += OnDataReceived;
  703. }
  704. RefreshCommunicationDebugPage();
  705. }
  706. private void OnDataReceived(object sender, string data)
  707. {
  708. var comm = _selectedCommunication as BindableCommunicationBase;
  709. if (comm == null) return;
  710. Application.Current.Dispatcher.Invoke(() =>
  711. {
  712. comm.AppendLog($"RX: {data}");
  713. });
  714. }
  715. #endregion
  716. }
  717. }