监控配置保存

This commit is contained in:
hsc
2026-07-09 11:34:03 +08:00
parent f5145d10e4
commit b21c1901e1
9 changed files with 146 additions and 99 deletions

View File

@@ -1,93 +0,0 @@
using OxyPlot.Series;
using OxyPlot;
using System.Collections.Concurrent;
using Prism.Mvvm;
using NCalc;
namespace MonitorModule.ViewModels
{
/// <summary>
/// 监测通道:一个设备方法对应一个通道。
/// 数据记录DataPoints与图表显示Series完全分离
/// - IsMonitored=true 时始终记录 DataPoints
/// - IsDisplayed=true 时才创建 LineSeries 并绘制
/// </summary>
public class MonitorChannel : BindableBase
{
// ===== 标识 =====
public string DeviceName { get; init; } = string.Empty;
/// <summary>硬件指纹(物理设备唯一标识,用于匹配广播事件)</summary>
public string Fingerprint { get; init; } = string.Empty;
public string MethodName { get; init; } = string.Empty;
public string DisplayName { get; init; } = string.Empty;
// ===== 状态 =====
private bool _isMonitored = true;
/// <summary>是否在记录数据点(添加后始终为 true</summary>
public bool IsMonitored
{
get => _isMonitored;
set => SetProperty(ref _isMonitored, value);
}
private bool _isDisplayed = true;
/// <summary>是否在 OxyPlot 上显示</summary>
public bool IsDisplayed
{
get => _isDisplayed;
set => SetProperty(ref _isDisplayed, value);
}
// ===== 数学变换 =====
private string? _mathExpression;
/// <summary>可选数学变换表达式,如 "x*0.001"。为空则 DisplayValue=RawValue</summary>
public string? MathExpression
{
get => _mathExpression;
set => SetProperty(ref _mathExpression, value);
}
// ===== 颜色 =====
public OxyColor Color { get; set; } = OxyColors.SteelBlue;
// ===== 数据存储 =====
/// <summary>所有采样数据点(线程安全),与 OxyPlot 无关</summary>
public ConcurrentQueue<(double Time, double RawValue, double DisplayValue)> DataPoints { get; } = new();
/// <summary>最大缓冲数据点数,超出则丢弃最旧的</summary>
public int MaxBuffer { get; set; } = 5000;
// ===== OxyPlot Series仅 IsDisplayed=true 时存在)=====
private LineSeries? _series;
public LineSeries? Series
{
get => _series;
set => SetProperty(ref _series, value);
}
// ===== 辅助方法 =====
/// <summary>记录一个数据点RawValue 经数学变换后得到 DisplayValue一并入队</summary>
public void Record(double time, double rawValue)
{
double displayValue = rawValue;
DataPoints.Enqueue((time, rawValue, displayValue));
// 超出缓冲上限时批量丢弃旧数据
while (DataPoints.Count > MaxBuffer)
{
DataPoints.TryDequeue(out _);
}
// 如果正在显示,同步到 Series
if (_series != null)
{
_series.Points.Add(new DataPoint(time, displayValue));
while (_series.Points.Count > 200)
{
_series.Points.RemoveAt(0);
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
using Common.Attributes;
using Model.Entity;
using Model.Models;
using NLog;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
@@ -19,6 +20,7 @@ using System.Windows.Input;
using System.Windows.Threading;
using UIShare.GlobalVariable;
using UIShare.PubEvent;
using UIShare.UIViewModel;
using UIShare.ViewModelBase;
@@ -42,13 +44,25 @@ namespace MonitorModule.ViewModels
public PlotModel Plot { get; }
/// <summary>已添加的监测通道列表</summary>
public ObservableCollection<MonitorChannel> Channels { get; } = new();
public ObservableCollection<MonitorChannelVM> Channels
{
get => _systemConfig?.Channels ?? new ObservableCollection<MonitorChannelVM>();
set
{
if (_systemConfig != null && _systemConfig.Channels != value)
{
_systemConfig.Channels = value;
RaisePropertyChanged();
}
}
}
/// <summary>可添加的设备方法列表(供用户选择)</summary>
public ObservableCollection<AvailableMethodItem> AvailableMethods { get; } = new();
private MonitorChannel? _selectedChannel;
public MonitorChannel? SelectedChannel
private MonitorChannelVM? _selectedChannel;
public MonitorChannelVM? SelectedChannel
{
get => _selectedChannel;
set => SetProperty(ref _selectedChannel, value);
@@ -75,12 +89,14 @@ namespace MonitorModule.ViewModels
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();
// 颜色调色盘
@@ -125,6 +141,7 @@ namespace MonitorModule.ViewModels
ResetViewCommand = new DelegateCommand(OnResetView);
RefreshDataCommand = new DelegateCommand(OnRefreshData);
RefreshCommand = new DelegateCommand(OnExpand);
SaveCommand = new DelegateCommand(OnSave);
// OxyPlot 刷新定时器1000ms只负责视觉更新
_plotRefreshTimer = new DispatcherTimer(DispatcherPriority.Background)
@@ -134,6 +151,8 @@ namespace MonitorModule.ViewModels
_plotRefreshTimer.Tick += OnPlotRefreshTick;
}
public void Dispose()
{
_monitorCTS?.Cancel();
@@ -159,7 +178,21 @@ namespace MonitorModule.ViewModels
Channels.Clear();
AvailableMethods.Clear();
}
private void OnSave()
{
// 将当前 Channels 序列化为 MonitorChannelConfig 列表保存到 MonitorChannels
if (_systemConfig != null)
{
_systemConfig.MonitorChannels = new ObservableCollection<MonitorChannelConfig>(Channels
.Select(c => new MonitorChannelConfig
{
Fingerprint = c.Fingerprint,
MethodName = c.MethodName,
IsDisplayed = c.IsDisplayed
}));
}
ConfigService.Save(_systemConfig);
}
#region 广
/// <summary>
/// 由 HardwareDataBroadcaster 通过 EventAggregator 广播触发。
@@ -275,6 +308,8 @@ namespace MonitorModule.ViewModels
_scope = _globalInfo.ScopeDic[TestStatus];
_scopedContext = _scope.Resolve<ScopedContext>();
_deviceManager = _scope.Resolve<DeviceManager>();
_systemConfig = _scope.Resolve<SystemConfig>();
RaisePropertyChanged(nameof(Channels));
_broadcaster = _scope.Resolve<HardwareDataBroadcaster>();
IsInitiated = true;
@@ -286,15 +321,18 @@ namespace MonitorModule.ViewModels
// 2. 扫描可监测方法(供 UI 选择)
DiscoverAvailableMethods();
// 3. 启动广播器Discover + Start
// 3. 从配置自动恢复已保存的监测通道
RestoreChannelsFromConfig();
// 4. 启动广播器Discover + Start
_broadcaster.Discover();
_broadcaster.Start();
// 4. 订阅 HardwareDataReportedEventUI 线程回调)
// 5. 订阅 HardwareDataReportedEventUI 线程回调)
_subscriptionToken = _eventAggregator.GetEvent<HardwareDataReportedEvent>()
.Subscribe(OnHardwareDataReceived, ThreadOption.UIThread);
// 5. 启动 OxyPlot 视觉刷新定时器
// 6. 启动 OxyPlot 视觉刷新定时器
_stopwatch.Start();
_plotRefreshTimer.Start();
}
@@ -404,6 +442,72 @@ namespace MonitorModule.ViewModels
? $"发现 {AvailableMethods.Count} 个可监测项"
: "未发现可监测的设备方法";
}
/// <summary>
/// 从 SystemConfig.MonitorChannels 持久化列表自动还原监测通道。
/// </summary>
private void RestoreChannelsFromConfig()
{
if (_systemConfig?.MonitorChannels == null || _systemConfig.MonitorChannels.Count == 0) return;
if (AvailableMethods.Count == 0) return;
int restored = 0;
foreach (var config in _systemConfig.MonitorChannels)
{
if (string.IsNullOrWhiteSpace(config.Fingerprint) ||
string.IsNullOrWhiteSpace(config.MethodName)) continue;
// 已在 Channels 中存在则跳过
if (Channels.Any(c => c.Fingerprint == config.Fingerprint && c.MethodName == config.MethodName))
continue;
// 在 AvailableMethods 中查找匹配项
var method = AvailableMethods.FirstOrDefault(m =>
m.Fingerprint == config.Fingerprint && m.MethodName == config.MethodName);
if (method == null) continue;
var color = _palette[_colorIndex % _palette.Length];
_colorIndex++;
var channel = new MonitorChannelVM
{
DeviceName = method.DeviceName,
Fingerprint = method.Fingerprint,
MethodName = method.MethodName,
DisplayName = method.DisplayName,
Color = color,
IsMonitored = true,
IsDisplayed = config.IsDisplayed
};
// 仅当 IsDisplayed=true 时才创建 LineSeries
if (channel.IsDisplayed)
{
channel.Series = new LineSeries
{
Title = channel.DisplayName,
Color = color,
StrokeThickness = 1.5
};
Plot.Series.Add(channel.Series);
}
channel.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(MonitorChannelVM.IsDisplayed))
OnChannelDisplayChanged(channel);
};
Channels.Add(channel);
restored++;
}
if (restored > 0)
{
Plot.InvalidatePlot(true);
StatusMessage = $"已从配置自动恢复 {restored} 个监测通道";
}
}
#endregion
#region
@@ -426,7 +530,7 @@ namespace MonitorModule.ViewModels
var color = _palette[_colorIndex % _palette.Length];
_colorIndex++;
var channel = new MonitorChannel
var channel = new MonitorChannelVM
{
DeviceName = method.DeviceName,
Fingerprint = method.Fingerprint,
@@ -447,7 +551,7 @@ namespace MonitorModule.ViewModels
channel.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(MonitorChannel.IsDisplayed))
if (e.PropertyName == nameof(MonitorChannelVM.IsDisplayed))
OnChannelDisplayChanged(channel);
};
@@ -497,7 +601,7 @@ namespace MonitorModule.ViewModels
#endregion
#region /
public void OnChannelDisplayChanged(MonitorChannel channel)
public void OnChannelDisplayChanged(MonitorChannelVM channel)
{
if (channel.IsDisplayed)
{
@@ -530,7 +634,7 @@ namespace MonitorModule.ViewModels
}
}
public void OnChannelMathChanged(MonitorChannel channel)
public void OnChannelMathChanged(MonitorChannelVM channel)
{
if (channel.Series == null) return;
var points = channel.DataPoints.ToArray();

View File

@@ -89,6 +89,10 @@
Command="{Binding RefreshDataCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="重新绘制图表"/>
<Button Content=" 保存"
Command="{Binding SaveCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="保存到配置文件"/>
</StackPanel>
</Border>