using Common.Attributes;
using Model.Entity;
using Model.Models;
using NLog;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;
using Service.Interface;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Threading;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
using ZLGUSBCANFD;
namespace MonitorModule.ViewModels
{
public class MonitorViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
{
#region 属性
private IMonitorValueService _monitorValueService;
public bool KeepAlive => true;
public ScopedContext _scopedContext { get; set; } = null!;
public GlobalInfo _globalInfo { get; set; }
private string _testStatus = string.Empty;
public string TestStatus
{
get => _testStatus;
set => SetProperty(ref _testStatus, value);
}
public PlotModel Plot { get; }
/// 已添加的监测通道列表
public ObservableCollection Channels
{
get => _systemConfig?.Channels ?? new ObservableCollection();
set
{
if (_systemConfig != null && _systemConfig.Channels != value)
{
_systemConfig.Channels = value;
RaisePropertyChanged();
}
}
}
/// 可添加的设备方法列表(供用户选择)
public ObservableCollection AvailableMethods { get; } = new();
private MonitorChannelVM? _selectedChannel;
public MonitorChannelVM? SelectedChannel
{
get => _selectedChannel;
set => SetProperty(ref _selectedChannel, value);
}
private AvailableMethodItem? _selectedAvailableMethod;
public AvailableMethodItem? SelectedAvailableMethod
{
get => _selectedAvailableMethod;
set => SetProperty(ref _selectedAvailableMethod, value);
}
private string _statusMessage = "图表已就绪,暂无监测项";
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
#endregion
#region 命令
public ICommand AddChannelCommand { get; }
public ICommand DeleteChannelCommand { get; }
public ICommand ResetViewCommand { get; }
public ICommand RefreshDataCommand { get; }
public ICommand RefreshCommand { get; }
public ICommand SaveCommand { get; }
#endregion
#region 私有字段
private bool IsInitiated = false;
private IScopedProvider? _scope;
private DeviceManager? _deviceManager;
private SystemConfig? _systemConfig;
private CancellationTokenSource? _monitorCTS = new();
// 颜色调色盘
private static readonly OxyColor[] _palette =
{
OxyColors.SteelBlue, OxyColors.IndianRed, OxyColors.SeaGreen,
OxyColors.DarkOrange, OxyColors.MediumPurple, OxyColors.Goldenrod,
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
};
private int _colorIndex;
/// 设备数据广播器(全局单例)
private HardwareDataBroadcaster? _broadcaster;
/// CAN 信号广播器(全局单例)
private CANSignalBroadcaster? _canSignalBroadcaster;
/// 用于 OxyPlot X 轴相对秒数
private readonly Stopwatch _stopwatch = new();
/// OxyPlot 刷新定时器(仅负责滚动 X 轴 + InvalidatePlot)
private readonly DispatcherTimer _plotRefreshTimer;
/// 事件订阅令牌,用于 Dispose 时取消订阅
private SubscriptionToken? _subscriptionToken;
private SubscriptionToken? _dbcLoadedToken;
private SubscriptionToken? _dbcUnloadedToken;
// ==========================================
// 批量入库核心结构
// ==========================================
private readonly ConcurrentQueue _insertQueue = new();
private Task? _dbFlushTask;
private const int BulkInsertThreshold = 50;
private int _isFlushing = 0;
#endregion
public MonitorViewModel(IContainerExtension container) : base(container)
{
_globalInfo = container.Resolve();
_monitorValueService = container.Resolve();
Plot = BuildEmptyPlot();
AddChannelCommand = new DelegateCommand(OnAddChannel);
DeleteChannelCommand = new DelegateCommand(OnDeleteChannel);
ResetViewCommand = new DelegateCommand(OnResetView);
RefreshDataCommand = new DelegateCommand(OnRefreshData);
RefreshCommand = new DelegateCommand(OnExpand);
SaveCommand = new DelegateCommand(OnSave);
// OxyPlot 刷新定时器(1000ms,只负责视觉更新)
_plotRefreshTimer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval = TimeSpan.FromMilliseconds(1000)
};
_plotRefreshTimer.Tick += OnPlotRefreshTick;
}
public void Dispose()
{
_monitorCTS?.Cancel();
_monitorCTS?.Dispose();
_monitorCTS = null;
_plotRefreshTimer.Stop();
_plotRefreshTimer.Tick -= OnPlotRefreshTick;
_stopwatch.Stop();
// 取消 EventAggregator 订阅
if (_subscriptionToken != null)
_eventAggregator.GetEvent().Unsubscribe(_subscriptionToken);
if (_dbcLoadedToken != null)
_eventAggregator.GetEvent().Unsubscribe(_dbcLoadedToken);
if (_dbcUnloadedToken != null)
_eventAggregator.GetEvent().Unsubscribe(_dbcUnloadedToken);
if (_dbFlushTask != null)
{
try { _dbFlushTask.Wait(TimeSpan.FromSeconds(1.5)); } catch { }
}
// 3. 释放容器作用域
_scope?.Dispose();
Channels.Clear();
AvailableMethods.Clear();
}
private void OnSave()
{
// 将当前 Channels 序列化为 MonitorChannelConfig 列表保存到 MonitorChannels
if (_systemConfig != null)
{
_systemConfig.MonitorChannels = new ObservableCollection(Channels
.Select(c => new MonitorChannelConfig
{
Fingerprint = c.Fingerprint,
MethodName = c.MethodName,
IsDisplayed = c.IsDisplayed
}));
}
ConfigService.Save(_systemConfig);
}
#region 事件驱动:接收广播数据
///
/// 由 HardwareDataBroadcaster 通过 EventAggregator 广播触发。
/// 已通过 ThreadOption.UIThread 确保在 UI 线程执行。
/// 通过 HardwareFingerprint + MethodName 匹配监测通道。
///
private void OnHardwareDataReceived(HardwareReportArgs args)
{
// 仅处理属于当前 Scope 的事件
if (string.IsNullOrEmpty(TestStatus) || args.Scope != TestStatus) return;
// 以硬件指纹 + 方法名唯一匹配通道(与逻辑名解耦)
var channel = Channels.FirstOrDefault(c =>
c.Fingerprint == args.HardwareFingerprint &&
c.MethodName == args.MethodName &&
c.IsMonitored);
if (channel == null) return;
// 记录数据点(线程安全:Record 内部 ConcurrentQueue + Series 操作)
channel.Record(args.Time, args.Value);
// 入队数据库批量写入
// CreateTime 使用 args.Time(采样触发时刻),而不是 DateTime.Now(设备响应到达时刻),
// 这样即使设备响应有延迟,数据库里的时间戳仍然按设定采样间隔分布。
var entity = new MonitorValueEntity
{
MonitorName = channel.DisplayName,
MonitorValue = args.Value,
Scope = args.Scope,
CreateTime = args.Time,
IsDel = 0
};
_insertQueue.Enqueue(entity);
if (_insertQueue.Count >= BulkInsertThreshold)
{
_ = Task.Run(async () => await DoFlushWorkAsync().ConfigureAwait(false));
}
}
#endregion
#region OxyPlot 视觉刷新定时器
private void OnPlotRefreshTick(object? sender, EventArgs e)
{
if (!_stopwatch.IsRunning) return;
var now = DateTime.Now;
var xAxis = Plot.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom);
if (xAxis != null)
{
xAxis.Minimum = DateTimeAxis.ToDouble(now.AddSeconds(-20));
xAxis.Maximum = DateTimeAxis.ToDouble(now.AddSeconds(0.5));
}
Plot.InvalidatePlot(true);
}
#endregion
#region 后台数据库批量入库
private void StartDbFlushWorker(CancellationToken token)
{
_dbFlushTask = Task.Run(async () =>
{
while (!token.IsCancellationRequested)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
await DoFlushWorkAsync().ConfigureAwait(false);
}
catch (OperationCanceledException) { break; }
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DB Worker Error] {ex.Message}");
}
}
await DoFlushWorkAsync().ConfigureAwait(false);
}, token);
}
private async Task DoFlushWorkAsync()
{
if (_insertQueue.IsEmpty) return;
if (Interlocked.CompareExchange(ref _isFlushing, 1, 0) != 0) return;
try
{
var listToInsert = new List();
while (_insertQueue.TryDequeue(out var entity)) listToInsert.Add(entity);
if (listToInsert.Count > 0)
{
var result = await _monitorValueService.InsertRangeAsync(listToInsert).ConfigureAwait(false);
}
}
finally { Interlocked.Exchange(ref _isFlushing, 0); }
}
#endregion
#region Navigation 导航
public override void OnNavigatedTo(NavigationContext navigationContext)
{
base.OnNavigatedTo(navigationContext);
if (!IsInitiated && navigationContext.Parameters.ContainsKey("Name"))
{
TestStatus = navigationContext.Parameters.GetValue("Name");
Plot.Title = $"监控 - {TestStatus}";
Plot.InvalidatePlot(false);
_scope = _globalInfo.ScopeDic[TestStatus];
_scopedContext = _scope.Resolve();
_deviceManager = _scope.Resolve();
_systemConfig = _scope.Resolve();
RaisePropertyChanged(nameof(Channels));
_broadcaster = _scope.Resolve();
_canSignalBroadcaster = _scope.Resolve();
IsInitiated = true;
if (_monitorCTS == null) _monitorCTS = new CancellationTokenSource();
// 1. 启动后台数据库消费
StartDbFlushWorker(_monitorCTS.Token);
// 2. 扫描可监测方法(供 UI 选择)
DiscoverAvailableMethods();
// 3. 从配置自动恢复已保存的监测通道
RestoreChannelsFromConfig();
// 4. 启动广播器(Discover + Start)
_broadcaster.Discover();
_broadcaster.Start();
_canSignalBroadcaster.Discover();
_canSignalBroadcaster.Start();
// 5. 订阅 HardwareDataReportedEvent(UI 线程回调)
_subscriptionToken = _eventAggregator.GetEvent()
.Subscribe(OnHardwareDataReceived, ThreadOption.UIThread);
// 5b. 订阅 DBC 加载/卸载事件,动态发现或清除 CAN 信号
_dbcLoadedToken = _eventAggregator.GetEvent()
.Subscribe(OnDbcLoaded, ThreadOption.UIThread);
_dbcUnloadedToken = _eventAggregator.GetEvent()
.Subscribe(OnDbcUnloaded, ThreadOption.UIThread);
// 5c. 根据 SystemConfig.ConfigurationList 刷新已加载 DBC 中的 CAN 信号
RefreshConfiguredCanSignals();
// 6. 启动 OxyPlot 视觉刷新定时器
_stopwatch.Start();
_plotRefreshTimer.Start();
}
}
#endregion
#region PlotModel 构建
private static PlotModel BuildEmptyPlot()
{
var pm = new PlotModel
{
Title = string.Empty,
PlotAreaBorderColor = OxyColors.LightGray,
Background = OxyColors.White
};
pm.Axes.Add(new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = "时间",
StringFormat = "HH:mm:ss",
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None
});
pm.Axes.Add(new LinearAxis
{
Position = AxisPosition.Left,
Title = "值",
MajorGridlineStyle = LineStyle.Dot,
MinorGridlineStyle = LineStyle.None
});
pm.Legends.Add(new Legend
{
LegendPosition = LegendPosition.RightTop,
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
LegendBorder = OxyColors.LightGray
});
return pm;
}
#endregion
#region 设备方法发现(仅用于 UI 展示可选项)
///
/// 通过 GlobalInfo.HardwarePool 构建 设备实例引用→硬件指纹 的反向查找表。
///
private Dictionary