LightManualViewModel.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. using Basler.Pylon;
  2. using Prism.Commands;
  3. using Prism.Events;
  4. using Prism.Ioc;
  5. using Prism.Mvvm;
  6. using Prism.Regions;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Collections.ObjectModel;
  10. using System.Linq;
  11. using System.Threading.Tasks;
  12. using TeamAAS_VP.Core;
  13. using TeamAAS_VP.Core.Lights;
  14. using TeamAAS_VP.Data;
  15. using TeamAAS_VP.Events;
  16. using TeamAAS_VP.Interfaces;
  17. using TeamAAS_VP.Models.Lights;
  18. using TeamAAS_VP.Resources.Languages;
  19. namespace TeamAAS_VP.ViewModels.DebugMod
  20. {
  21. public class LightManualViewModel : BindableBase, INavigationAware
  22. {
  23. IContainerProvider _container;
  24. IEventAggregator _eventAggregator;
  25. ILightManagerService _light_manager_service; // fallback
  26. ILightManagerService _lightManagerService;
  27. IConfigService _configService;
  28. ISystemDatabaseService _systemDatabaseService;
  29. #region 属性
  30. private ObservableCollection<ChannelItem> _Channels = new ObservableCollection<ChannelItem>();
  31. public ObservableCollection<ChannelItem> Channels
  32. {
  33. get => _Channels; set => SetProperty(ref _Channels, value);
  34. }
  35. #endregion
  36. #region 命令
  37. private DelegateCommand _LoadedCommand;
  38. public DelegateCommand LoadedCommand =>
  39. _LoadedCommand ?? (_LoadedCommand = new DelegateCommand(ExecuteLoadedCommand));
  40. #endregion
  41. #region 事件
  42. #endregion
  43. public LightManualViewModel(IContainerProvider container, IEventAggregator ea, ILightManagerService lightManagerService, IConfigService configService, ISystemDatabaseService systemDatabaseService)
  44. {
  45. _container = container;
  46. _eventAggregator = ea;
  47. _light_manager_service = lightManagerService;
  48. _lightManagerService = lightManagerService;
  49. _configService = configService;
  50. _systemDatabaseService = systemDatabaseService;
  51. //订阅权限登录事件
  52. _eventAggregator.GetEvent<UserLoginNotification>().Subscribe(LoginChange);
  53. }
  54. #region 方法
  55. void ExecuteLoadedCommand()
  56. {
  57. if (_systemDatabaseService.GetCurrentUser().userPart == Enums.UserPart.Operator)
  58. {
  59. IsAllowEdit = false;
  60. }
  61. else
  62. {
  63. IsAllowEdit = true;
  64. }
  65. }
  66. #endregion
  67. #region 继承
  68. public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
  69. {
  70. continuationCallback(true);
  71. }
  72. public async void OnNavigatedTo(NavigationContext navigationContext)
  73. {
  74. // load channels from light manager service
  75. Channels.Clear();
  76. try
  77. {
  78. var globals = _lightManagerService.GlobalChannels.ToList();
  79. foreach (var kv in globals)
  80. {
  81. var item = new ChannelItem(kv.Key, (ILightChannel)kv.Value, _lightManagerService, _configService, _eventAggregator);
  82. Channels.Add(item);
  83. await item.InitializeAsync();
  84. }
  85. }
  86. catch (Exception ex)
  87. {
  88. // ignore or notify
  89. }
  90. }
  91. public bool IsNavigationTarget(NavigationContext navigationContext)
  92. {
  93. return true;
  94. }
  95. public void OnNavigatedFrom(NavigationContext navigationContext)
  96. {
  97. // nothing
  98. }
  99. #endregion
  100. private bool _IsAllowEdit = false;
  101. public bool IsAllowEdit
  102. {
  103. get { return _IsAllowEdit; }
  104. set { SetProperty(ref _IsAllowEdit, value); }
  105. }
  106. private void LoginChange(Models.User user)
  107. {
  108. if (user.userPart == Enums.UserPart.Operator)
  109. {
  110. IsAllowEdit = false;
  111. }
  112. else
  113. {
  114. IsAllowEdit = true;
  115. }
  116. }
  117. public class ChannelItem : BindableBase
  118. {
  119. private readonly int _globalId;
  120. private readonly ILightChannel _channel;
  121. private readonly ILightManagerService _lightManagerService;
  122. private readonly IConfigService _configService;
  123. private readonly IEventAggregator _eventAggregator;
  124. public int GlobalId => _globalId;
  125. private string _Name;
  126. public string Name { get => _Name; set => SetProperty(ref _Name, value); }
  127. private int _PendingBrightness;
  128. public int PendingBrightness { get => _PendingBrightness; set => SetProperty(ref _PendingBrightness, value); }
  129. private int _CurrentBrightness;
  130. public int CurrentBrightness { get => _CurrentBrightness; set => SetProperty(ref _CurrentBrightness, value); }
  131. private bool _IsOn;
  132. public bool IsOn { get => _IsOn; set => SetProperty(ref _IsOn, value); }
  133. public DelegateCommand SaveCommand { get; }
  134. public DelegateCommand TurnOnCommand { get; }
  135. public DelegateCommand TurnOffCommand { get; }
  136. private DelegateCommand<object> _BrightnessChangedCommand;
  137. public DelegateCommand<object> BrightnessChangedCommand =>
  138. _BrightnessChangedCommand ?? (_BrightnessChangedCommand = new DelegateCommand<object>(ExecuteBrightnessChangedCommand));
  139. // 去抖与写入控制字段
  140. private readonly object _writeLock = new object();
  141. private int? _latestQueuedBrightness = null;
  142. private bool _isWriting = false;
  143. private System.Threading.CancellationTokenSource _debounceCts;
  144. public ChannelItem(int globalId, ILightChannel channel, ILightManagerService lightManagerService, IConfigService configService, IEventAggregator eventAggregator)
  145. {
  146. _globalId = globalId;
  147. _channel = channel;
  148. _lightManagerService = lightManagerService;
  149. _configService = configService;
  150. _eventAggregator = eventAggregator;
  151. Name = channel?.ChannelName ?? $"Channel {_globalId}";
  152. PendingBrightness = 0;
  153. CurrentBrightness = 0;
  154. IsOn = false;
  155. SaveCommand = new DelegateCommand(async () => await SaveAsync());
  156. TurnOnCommand = new DelegateCommand(async () => await TurnOnAsync());
  157. TurnOffCommand = new DelegateCommand(async () => await TurnOffAsync());
  158. }
  159. public async Task InitializeAsync()
  160. {
  161. try
  162. {
  163. if (_channel != null)
  164. {
  165. var b = await _channel.GetBrightnessAsync();
  166. CurrentBrightness = b;
  167. PendingBrightness = b;
  168. IsOn = await _channel.GetStatusAsync();
  169. }
  170. }
  171. catch
  172. {
  173. }
  174. }
  175. // 当滑块变化时调用:采用去抖 + 只写最新值的策略
  176. async void ExecuteBrightnessChangedCommand(object obj)
  177. {
  178. try
  179. {
  180. var parameter = obj as Tuple<object, double, double>;
  181. if (parameter == null) return;
  182. int newValue = (int)parameter.Item2;
  183. // 更新 PendingBrightness 可即时反映到 UI(根据需要)
  184. PendingBrightness = newValue;
  185. QueueBrightnessChange(newValue);
  186. }
  187. catch (Exception)
  188. {
  189. // ignore
  190. }
  191. }
  192. // 将新的亮度值入队(覆盖之前未写入的值),并启动去抖延迟任务
  193. private void QueueBrightnessChange(int brightness)
  194. {
  195. lock (_writeLock)
  196. {
  197. _latestQueuedBrightness = brightness;
  198. // 取消之前的去抖任务
  199. try
  200. {
  201. _debounceCts?.Cancel();
  202. _debounceCts?.Dispose();
  203. }
  204. catch { }
  205. _debounceCts = new System.Threading.CancellationTokenSource();
  206. var token = _debounceCts.Token;
  207. // 启动去抖后处理(不等待)
  208. System.Threading.Tasks.Task.Run(async () =>
  209. {
  210. try
  211. {
  212. // 去抖延迟,可调整为 150-300ms
  213. await System.Threading.Tasks.Task.Delay(200, token);
  214. await ProcessWritesAsync();
  215. }
  216. catch (System.OperationCanceledException)
  217. {
  218. // 被取消,忽略
  219. }
  220. catch
  221. {
  222. // 忽略其它异常防止未观测异常
  223. }
  224. }, System.Threading.CancellationToken.None);
  225. }
  226. }
  227. // 串行化写入循环:写入当前最新值,若在写入过程中又有新值则继续写入,直到没有新值
  228. private async System.Threading.Tasks.Task ProcessWritesAsync()
  229. {
  230. // 快速检查与设置写入标志,保证只有一个写入循环执行
  231. lock (_writeLock)
  232. {
  233. if (_isWriting) return;
  234. _isWriting = true;
  235. }
  236. try
  237. {
  238. while (true)
  239. {
  240. int? toWrite = null;
  241. lock (_writeLock)
  242. {
  243. if (_latestQueuedBrightness.HasValue)
  244. {
  245. toWrite = _latestQueuedBrightness.Value;
  246. _latestQueuedBrightness = null;
  247. }
  248. }
  249. if (!toWrite.HasValue)
  250. {
  251. // 没有需要写入的值,退出循环
  252. break;
  253. }
  254. try
  255. {
  256. // 实际写入(底层方法可能不支持取消)
  257. var success = await _lightManagerService.SetGlobalChannelBrightnessAsync(_globalId, toWrite.Value);
  258. if (success)
  259. {
  260. CurrentBrightness = toWrite.Value;
  261. PendingBrightness = toWrite.Value;
  262. IsOn = toWrite.Value > 0;
  263. }
  264. }
  265. catch
  266. {
  267. // 忽略写入异常,继续检查是否有新值需要写入
  268. }
  269. }
  270. }
  271. finally
  272. {
  273. lock (_writeLock)
  274. {
  275. _isWriting = false;
  276. }
  277. }
  278. }
  279. private async Task SaveAsync()
  280. {
  281. try
  282. {
  283. var success = await _lightManagerService.SetGlobalChannelBrightnessAsync(_globalId, PendingBrightness);
  284. if (success)
  285. {
  286. CurrentBrightness = PendingBrightness;
  287. IsOn = PendingBrightness > 0;
  288. // also save as default in config
  289. _configService.UpdateChannelDefaultBrightness(_globalId, PendingBrightness);
  290. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = string.Format(Lang.保存完成01, Name, PendingBrightness), Duration = 1 });
  291. }
  292. else
  293. {
  294. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = string.Format(Lang.保存失败0, Name), Duration = 1 });
  295. }
  296. }
  297. catch (Exception ex)
  298. {
  299. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = string.Format(Lang.保存异常0, ex.Message), Duration = 1 });
  300. }
  301. }
  302. private async Task TurnOnAsync()
  303. {
  304. try
  305. {
  306. var success = await _lightManagerService.TurnOnGlobalChannelAsync(_globalId);
  307. if (success)
  308. {
  309. IsOn = true;
  310. // refresh brightness
  311. var b = await _channel.GetBrightnessAsync();
  312. PendingBrightness = b;
  313. CurrentBrightness = b;
  314. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = string.Format(Lang.打开通道0, Name), Duration = 1 });
  315. }
  316. }
  317. catch (Exception ex)
  318. {
  319. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = string.Format(Lang.打开异常0,ex.Message), Duration = 1 });
  320. }
  321. }
  322. private async Task TurnOffAsync()
  323. {
  324. try
  325. {
  326. var success = await _lightManagerService.TurnOffGlobalChannelAsync(_globalId);
  327. if (success)
  328. {
  329. IsOn = false;
  330. PendingBrightness = 0;
  331. CurrentBrightness = 0;
  332. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = string.Format(Lang.关闭通道0,Name), Duration = 1 });
  333. }
  334. }
  335. catch (Exception ex)
  336. {
  337. _eventAggregator.GetEvent<SnackbarMessageNotification>().Publish(new MessageParameter() { Msg = string.Format(Lang.关闭异常0, ex.Message), Duration = 1 });
  338. }
  339. }
  340. }
  341. }
  342. }