Compare commits

..

6 Commits

Author SHA1 Message Date
“hsc”
850156bfa2 回读图像功能优化 2026-07-30 16:33:32 +08:00
“hsc”
f350058f53 快捷键添加 2026-07-30 16:23:37 +08:00
“hsc”
156a04817c 添加机器码校验,删除加密狗 2026-07-30 14:06:33 +08:00
“hsc”
0e9f65ea21 加载配置文件优化 2026-07-30 13:37:49 +08:00
“hsc”
252c70ef05 设备单独控制界面 2026-07-29 16:54:56 +08:00
“hsc”
e4107c9997 示波器功率分析仪设备驱动添加 2026-07-29 16:23:43 +08:00
42 changed files with 3320 additions and 118 deletions

View File

@@ -37,8 +37,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceE
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CANModule", "CANModule\CANModule.csproj", "{AF2533DD-599D-49D7-942B-B0C26534D15F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "加密狗", "加密狗\加密狗.csproj", "{A35CA316-7B5B-4183-8636-4ECA532B1489}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurveModule", "CurveModule\CurveModule.csproj", "{84006890-9288-44C4-9D17-FA88F3FDECFA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportModule", "ExportModule\ExportModule.csproj", "{8923122F-F7D3-4AE7-9E86-9E78F1296A11}"
@@ -117,10 +115,6 @@ Global
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Release|Any CPU.Build.0 = Release|Any CPU
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Release|Any CPU.Build.0 = Release|Any CPU
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@@ -34,7 +34,6 @@
<ProjectReference Include="..\TSMasterCAN\TSMasterCAN.csproj" />
<ProjectReference Include="..\UIShare\UIShare.csproj" />
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
<ProjectReference Include="..\加密狗\加密狗.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -23,7 +23,7 @@ using DeviceCommand.Base;
using AutoMapper;
using Microsoft.Extensions.Logging.Abstractions;
using ACP.Profiles;
using ;
using UIShare.Helpers;
namespace ACP
{
@@ -83,44 +83,17 @@ namespace ACP
base.OnStartup(e);
return;
}
string strUserSn = "ACA89B2C1194650D4E93D97BE189FFCC";
string strApiKey = "C6D01BCD442347822876F8232245DE9422643C167B8C61CE4DF6049990E7395588675A8FC6AA4BF7AD353D669C993236BEA83F77551753885A47AF34185CC3A7";
Byte[] buffer = new Byte[256];
buffer = System.Text.Encoding.Default.GetBytes(strUserSn);
uint retcode;
retcode = .SN(buffer);
if (retcode != 0)
string myMachineCode = MachineCodeHelper.GetDeviceMachineCode();
if (MachineCodeHelper.VerifyMachineCode(myMachineCode))
{
MessageBox.Show(string.Format("设置UserSN失败 error code: 0x{0:x}", retcode));
base.OnStartup(e);
}
else
{
MessageBox.Show("机器码不匹配!");
return;
}
uint count = 0;
retcode = API类.VikeyFind(ref count);
if (retcode != 0 || count == 0)
{
MessageBox.Show("未找到加密狗");
return;
}
buffer = System.Text.Encoding.Default.GetBytes(strApiKey);
retcode = .APIkey(0, buffer);
if (retcode != 0)
{
MessageBox.Show(string.Format("设置api密钥失败 error code: 0x{0:x}", retcode));
return;
}
var remaining = .(0);
if (remaining <= TimeSpan.Zero)
{
MessageBox.Show("加密狗已到期或未能读取时间,请联系供应商。", "加密狗验证失败", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
base.OnStartup(e);
}
protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
{
@@ -133,6 +106,7 @@ namespace ACP
containerRegistry.RegisterSingleton<GlobalInfo>();
containerRegistry.RegisterSingleton<HardwareDataBroadcaster>();
containerRegistry.RegisterSingleton<CANSignalBroadcaster>();
containerRegistry.RegisterSingleton<GlobalConfig>();
//注册AutoMapper
var config = new MapperConfiguration(
cfg => cfg.AddProfile<AutoMapperProfile>(),

View File

@@ -37,6 +37,7 @@ namespace ACP.ViewModels
private readonly INotificationManager _notificationManager;
private readonly IModuleManager _moduleManager;
private readonly GlobalInfo _globalInfo;
private readonly GlobalConfig _globalConfig;
// 💡 新增UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间
private readonly DispatcherTimer _uiRefreshTimer;
@@ -165,6 +166,21 @@ namespace ACP.ViewModels
_regionManager = containerProvider.Resolve<IRegionManager>();
_notificationManager = containerProvider.Resolve<INotificationManager>();
_moduleManager = containerProvider.Resolve<IModuleManager>();
_globalConfig = containerProvider.Resolve<GlobalConfig>();
if (ConfigService.IsExit("GlobalConfig"))
{
string filePath = System.IO.Path.Combine(_globalConfig.SystemPath, "GlobalConfig.json");
if (System.IO.File.Exists(filePath))
{
string json = System.IO.File.ReadAllText(filePath);
Newtonsoft.Json.JsonConvert.PopulateObject(json, _globalConfig);
}
}
else
{
_globalConfig = new GlobalConfig();
ConfigService.SaveGlobalConfig(_globalConfig);
}
LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
ShowDialogManagerViewCommand = new DelegateCommand(ShowDialogManagerView);
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
@@ -841,10 +857,7 @@ namespace ACP.ViewModels
_regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
IsLeftDrawerOpen = false;
break;
case "台架移动界面":
if (_globalInfo.CurrentScope != "default") return;
_regionManager.RequestNavigate("ShellViewManager", "BenchMovementView");
break;
case "曲线回读界面":
if (_globalInfo.CurrentScope != "default") return;
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");

View File

@@ -28,6 +28,32 @@
<converter:InverseBooleanConverter x:Key="InverseBooleanConverter" />
<converter:TimeSpanToStringConverter x:Key="TimeSpanConverter" />
</Window.Resources>
<Window.InputBindings>
<!-- 工具菜单命令 (Ctrl + 1~0) -->
<KeyBinding Key="D1" Modifiers="Ctrl" Command="{Binding NewCommand}" />
<KeyBinding Key="D2" Modifiers="Ctrl" Command="{Binding OpenCommand}" />
<KeyBinding Key="D3" Modifiers="Ctrl" Command="{Binding SaveCommand}" />
<KeyBinding Key="D4" Modifiers="Ctrl" Command="{Binding SaveAsCommand}" />
<KeyBinding Key="D5" Modifiers="Ctrl" Command="{Binding SetDefaultCommand}" />
<KeyBinding Key="D6" Modifiers="Ctrl" Command="{Binding SelectCANSignalMonitorCommand}" />
<KeyBinding Key="D7" Modifiers="Ctrl" Command="{Binding MonitorValueSettingCommand}" />
<KeyBinding Key="D8" Modifiers="Ctrl" Command="{Binding SelectCANMessageCommand}" />
<KeyBinding Key="D9" Modifiers="Ctrl" Command="{Binding GetFileStringCommand}" />
<KeyBinding Key="D0" Modifiers="Ctrl" Command="{Binding ShowDialogManagerViewCommand}" />
<KeyBinding Key="B" Modifiers="Ctrl" Command="{Binding SilenceBuzzerCommand}" />
<!-- 流程执行命令 -->
<KeyBinding Key="G" Modifiers="Ctrl" Command="{Binding RefreshCommand}" />
<KeyBinding Key="D" Modifiers="Ctrl" Command="{Binding DestroyCommand}" />
<KeyBinding Key="E" Modifiers="Ctrl" Command="{Binding RunningCommand}" />
<KeyBinding Key="T" Modifiers="Ctrl" Command="{Binding RunSingleCommand}" />
<KeyBinding Key="H" Modifiers="Ctrl" Command="{Binding RunAbnormalStepsCommand}" />
<KeyBinding Key="R" Modifiers="Ctrl" Command="{Binding RestorationCommand}" />
<!-- 窗口操作命令 -->
<KeyBinding Key="M" Modifiers="Ctrl" Command="{Binding MinimizeCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}" />
<KeyBinding Key="Q" Modifiers="Ctrl" Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}" />
</Window.InputBindings>
<materialDesign:DrawerHost x:Name="MainDrawerHost"
IsLeftDrawerOpen="{Binding IsLeftDrawerOpen, Mode=TwoWay}">
@@ -61,11 +87,7 @@
Style="{StaticResource MaterialDesignFlatButton}"
Margin="8" />
<Button Content="台架移动界面"
Command="{Binding NavigateCommand}"
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
Style="{StaticResource MaterialDesignFlatButton}"
Margin="8" />
<Button Content="曲线回读界面"
Command="{Binding NavigateCommand}"
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"

View File

@@ -1,5 +1,6 @@
using Logger;
using OxyPlot;
using OxyPlot.Annotations;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;
@@ -11,7 +12,9 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Input;
using CurveModule.ViewModels.Dialogs;
using UIShare.ViewModelBase;
using UIShare.GlobalVariable;
namespace CurveModule.ViewModels
{
@@ -20,6 +23,9 @@ namespace CurveModule.ViewModels
/// </summary>
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
{
#region
private GlobalConfig _globalConfig;
#endregion
#region
public bool KeepAlive => true;
@@ -47,6 +53,11 @@ namespace CurveModule.ViewModels
public ICommand LoadCsvCommand { get; }
public ICommand ResetViewCommand { get; }
public ICommand ClearCommand { get; }
public ICommand StatisticsCommand { get; }
public ICommand SaveCommand { get; }
public ICommand LoadCommand { get; }
public ICommand CaptureCommand { get; }
#endregion
#region
@@ -58,39 +69,84 @@ namespace CurveModule.ViewModels
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
};
private int _colorIndex;
// 双光标截取
private LineAnnotation? _cursor1;
private LineAnnotation? _cursor2;
private LineAnnotation? _draggingCursor;
private List<MonitorRecord> _allRecords = new();
#endregion
/// <summary>截取按钮文本(随状态切换)</summary>
private string _captureButtonText = "开始截取图形";
public string CaptureButtonText
{
get => _captureButtonText;
set => SetProperty(ref _captureButtonText, value);
}
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_globalConfig = containerProvider.Resolve<GlobalConfig>();
Plot = BuildEmptyPlot();
LoadCsvCommand = new DelegateCommand(OnLoadCsv);
LoadCsvCommand = new DelegateCommand<string>(OnLoadCsv);
ResetViewCommand = new DelegateCommand(OnResetView);
ClearCommand = new DelegateCommand(OnClear);
StatisticsCommand = new DelegateCommand(OnStatistics);
SaveCommand = new DelegateCommand(OnSave);
LoadCommand = new DelegateCommand(Onload);
CaptureCommand = new DelegateCommand(OnToggleCapture);
}
#region
private void OnSave()
{
ConfigService.SaveGlobalConfig(_globalConfig);
}
private void Onload()
{
if (File.Exists(_globalConfig.DefaultCSVFilePath))
{
OnLoadCsv(_globalConfig.DefaultCSVFilePath);
}
}
/// <summary>
/// 打开 CSV 文件对话框,解析数据并按 (MonitorName, Scope) 分组绘制曲线。
/// </summary>
private void OnLoadCsv()
private void OnLoadCsv(string filename)
{
try
{
var dlg = new Microsoft.Win32.OpenFileDialog
// 1. 统一处理 filename
if (string.IsNullOrEmpty(filename))
{
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
Title = "选择要加载的监测数据 CSV 文件"
};
if (dlg.ShowDialog() != true) return;
var dlg = new Microsoft.Win32.OpenFileDialog
{
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
Title = "选择要加载的监测数据 CSV 文件"
};
if (dlg.ShowDialog() != true)
return;
// 将弹窗选中的路径统一赋给 filename
filename = dlg.FileName;
_globalConfig.DefaultCSVFilePath = filename;
}
// 2. 统一解析数据
List<MonitorRecord> records = ParseCsv(filename);
_allRecords = records;
var records = ParseCsv(dlg.FileName);
if (records.Count == 0)
{
StatusMessage = "CSV 文件为空或格式不正确";
return;
}
// 按 (MonitorName, Scope) 分组
// 3. 按 (MonitorName, Scope) 分组
var groups = records.GroupBy(r => (r.MonitorName, r.Scope))
.OrderBy(g => g.Key.MonitorName)
.ThenBy(g => g.Key.Scope);
@@ -138,8 +194,10 @@ namespace CurveModule.ViewModels
}
Plot.InvalidatePlot(true);
StatusMessage = $"已加载 {Path.GetFileName(dlg.FileName)},共 {Channels.Count} 条曲线,{records.Count} 个数据点";
LoggerHelper.Info($"曲线回读:加载 {dlg.FileName}{Channels.Count} 条曲线,{records.Count} 个数据点");
// 4. 统一使用 filename 变量进行日志和状态更新
StatusMessage = $"已加载 {Path.GetFileName(filename)},共 {Channels.Count} 条曲线,{records.Count} 个数据点";
LoggerHelper.Info($"曲线回读:加载 {filename}{Channels.Count} 条曲线,{records.Count} 个数据点");
}
catch (Exception ex)
{
@@ -157,10 +215,43 @@ namespace CurveModule.ViewModels
private void OnClear()
{
RemoveCursors();
CaptureButtonText = "开始截取图形";
ClearPlotAndChannels();
_allRecords.Clear();
Plot.InvalidatePlot(true);
StatusMessage = "已清空,请重新加载 CSV 文件";
}
/// <summary>
/// 对每条曲线计算数学统计(最大、最小、平均、斜率、积分),弹窗显示。
/// </summary>
private void OnStatistics()
{
if (Channels.Count == 0)
{
ShowInfoMessageBox("请先加载 CSV 文件", () => { });
return;
}
var results = new ObservableCollection<CurveStatisticsResult>();
foreach (var channel in Channels)
{
if (channel.Series == null || channel.Series.Points.Count == 0) continue;
results.Add(ComputeStatistics(channel));
}
if (results.Count == 0)
{
ShowInfoMessageBox("没有可统计的曲线数据", () => { });
return;
}
var parameters = new DialogParameters();
parameters.Add("Results", results);
_dialogService.ShowDialog("CurveStatistics", parameters, _ => { });
StatusMessage = $"已计算 {results.Count} 条曲线的数学统计";
}
#endregion
#region CSV
@@ -302,6 +393,79 @@ namespace CurveModule.ViewModels
#endregion
#region
/// <summary>
/// 对单条曲线计算数学统计:最大值、最小值、平均值、线性回归斜率、梯形积分。
/// 斜率单位:值/秒;积分单位:值×秒。
/// </summary>
private static CurveStatisticsResult ComputeStatistics(CurveRecallChannelVM channel)
{
var points = channel.Series!.Points;
int n = points.Count;
if (n == 0)
return new CurveStatisticsResult
{
DisplayName = channel.DisplayName,
PointCount = 0
};
double max = double.MinValue;
double min = double.MaxValue;
double sumY = 0;
// 线性回归累计量
double sumX = 0, sumXY = 0, sumX2 = 0;
// 梯形积分累计量
double integral = 0;
for (int i = 0; i < n; i++)
{
double x = points[i].X; // OADate 天数
double y = points[i].Y;
if (y > max) max = y;
if (y < min) min = y;
sumY += y;
sumX += x;
sumXY += x * y;
sumX2 += x * x;
if (i > 0)
{
double dx = x - points[i - 1].X;
integral += dx * (y + points[i - 1].Y) / 2.0;
}
}
double average = sumY / n;
// 线性回归斜率(单位:值 / OADate 天)
double slopePerDay = 0;
double denominator = n * sumX2 - sumX * sumX;
if (Math.Abs(denominator) > 1e-30)
{
slopePerDay = (n * sumXY - sumX * sumY) / denominator;
}
// 转换为 值/秒
double slopePerSecond = slopePerDay / 86400.0;
// 积分从 OADate·值 转换为 秒·值
double integralSeconds = integral * 86400.0;
return new CurveStatisticsResult
{
DisplayName = channel.DisplayName,
PointCount = n,
Max = max,
Min = min,
Average = average,
Slope = slopePerSecond,
Integral = integralSeconds
};
}
private void OnChannelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(CurveRecallChannelVM.IsDisplayed)) return;
@@ -334,6 +498,194 @@ namespace CurveModule.ViewModels
Channels.Clear();
_colorIndex = 0;
}
#endregion
#region
/// <summary>
/// 切换截取模式:添加/移除双光标,结束时导出选中范围 CSV。
/// </summary>
private void OnToggleCapture()
{
if (_cursor1 == null)
{
CreateCursors();
CaptureButtonText = "结束截取图形";
}
else
{
double x1 = _cursor1.X;
double x2 = _cursor2!.X;
double minX = Math.Min(x1, x2);
double maxX = Math.Max(x1, x2);
RemoveCursors();
CaptureButtonText = "开始截取图形";
ExportCapturedCsv(minX, maxX);
}
}
/// <summary>
/// 在图表上添加两条可拖拽的竖直光标。
/// </summary>
private void CreateCursors()
{
if (Channels.Count == 0) return;
var dateAxis = Plot.Axes.OfType<DateTimeAxis>().FirstOrDefault();
if (dateAxis == null) return;
double xMin = dateAxis.ActualMinimum;
double xMax = dateAxis.ActualMaximum;
double range = xMax - xMin;
if (range <= 0) return;
_cursor1 = new LineAnnotation
{
Type = LineAnnotationType.Vertical,
X = xMin + range * 0.3,
Color = OxyColors.SteelBlue,
StrokeThickness = 2,
LineStyle = LineStyle.Dash,
Text = "▼",
TextColor = OxyColors.SteelBlue,
TextHorizontalAlignment = HorizontalAlignment.Center,
TextVerticalAlignment = VerticalAlignment.Top
};
_cursor2 = new LineAnnotation
{
Type = LineAnnotationType.Vertical,
X = xMin + range * 0.7,
Color = OxyColors.SteelBlue,
StrokeThickness = 2,
LineStyle = LineStyle.Dash,
Text = "▼",
TextColor = OxyColors.SteelBlue,
TextHorizontalAlignment = HorizontalAlignment.Center,
TextVerticalAlignment = VerticalAlignment.Top
};
_cursor1.MouseDown += OnCursorMouseDown;
_cursor1.MouseUp += OnCursorMouseUp;
_cursor2.MouseDown += OnCursorMouseDown;
_cursor2.MouseUp += OnCursorMouseUp;
Plot.MouseDown += OnPlotMouseMove;
Plot.Annotations.Add(_cursor1);
Plot.Annotations.Add(_cursor2);
Plot.InvalidatePlot(true);
StatusMessage = "已添加双光标,拖拽竖线选择范围,完成后点击「结束截取图形」";
}
/// <summary>
/// 移除双光标并清理事件。
/// </summary>
private void RemoveCursors()
{
if (_cursor1 != null)
{
_cursor1.MouseDown -= OnCursorMouseDown;
_cursor1.MouseUp -= OnCursorMouseUp;
Plot.Annotations.Remove(_cursor1);
_cursor1 = null;
}
if (_cursor2 != null)
{
_cursor2.MouseDown -= OnCursorMouseDown;
_cursor2.MouseUp -= OnCursorMouseUp;
Plot.Annotations.Remove(_cursor2);
_cursor2 = null;
}
Plot.MouseDown -= OnPlotMouseMove;
_draggingCursor = null;
Plot.InvalidatePlot(true);
}
private void OnCursorMouseDown(object sender, OxyMouseDownEventArgs e)
{
if (sender is LineAnnotation line)
_draggingCursor = line;
}
private void OnCursorMouseUp(object sender, OxyMouseEventArgs e)
{
_draggingCursor = null;
}
private void OnPlotMouseMove(object sender, OxyMouseDownEventArgs e)
{
if (_draggingCursor == null) return;
var position = ((OxyMouseEventArgs)e).Position;
var axis = Plot.Axes.OfType<DateTimeAxis>().FirstOrDefault();
if (axis == null) return;
double dataX = axis.InverseTransform(position.X, position.Y, null).X;
// 限制光标不超出数据范围
double xMin = axis.ActualMinimum;
double xMax = axis.ActualMaximum;
dataX = Math.Max(xMin, Math.Min(xMax, dataX));
_draggingCursor.X = dataX;
_draggingCursor.Text = $"▼ {DateTime.FromOADate(dataX):HH:mm:ss}";
Plot.InvalidatePlot(true);
}
/// <summary>
/// 导出双光标选定时间范围内的原始记录到 CSV。
/// </summary>
private void ExportCapturedCsv(double minXOaDate, double maxXOaDate)
{
if (_allRecords.Count == 0)
{
ShowInfoMessageBox("没有加载数据,无法导出", () => { });
return;
}
var startTime = DateTime.FromOADate(minXOaDate);
var endTime = DateTime.FromOADate(maxXOaDate);
var filtered = _allRecords
.Where(r => r.CreateTime >= startTime && r.CreateTime <= endTime)
.OrderBy(r => r.CreateTime)
.ToList();
if (filtered.Count == 0)
{
ShowInfoMessageBox("选定范围内没有数据", () => { });
return;
}
var dlg = new Microsoft.Win32.SaveFileDialog
{
Filter = "CSV 文件 (*.csv)|*.csv",
Title = "导出截取范围数据",
FileName = $"captured_{DateTime.Now:yyyyMMdd_HHmmss}.csv"
};
if (dlg.ShowDialog() != true) return;
using var sw = new StreamWriter(dlg.FileName, false, Encoding.UTF8);
sw.WriteLine("MonitorName,MonitorValue,Scope,CreateTime");
foreach (var r in filtered)
{
sw.WriteLine($"{EscapeCsvField(r.MonitorName)},{r.MonitorValue},{EscapeCsvField(r.Scope)},{r.CreateTime:yyyy-MM-dd HH:mm:ss.fff}");
}
StatusMessage = $"已导出 {filtered.Count} 条记录到 {Path.GetFileName(dlg.FileName)}";
LoggerHelper.Info($"导出截取范围 CSV{dlg.FileName}{filtered.Count} 条记录");
}
private static string EscapeCsvField(string field)
{
if (string.IsNullOrEmpty(field)) return "";
if (field.Contains(',') || field.Contains('"') || field.Contains('\n'))
return $"\"{field.Replace("\"", "\"\"")}\"";
return field;
}
#endregion
#region
@@ -343,6 +695,7 @@ namespace CurveModule.ViewModels
public void Dispose()
{
RemoveCursors();
ClearPlotAndChannels();
}
#endregion

View File

@@ -0,0 +1,95 @@
using Prism.Commands;
using System.Collections.ObjectModel;
using System.Windows.Input;
using UIShare.PubEvent;
using UIShare.ViewModelBase;
namespace CurveModule.ViewModels.Dialogs
{
/// <summary>
/// 曲线统计结果弹窗 ViewModel。
/// </summary>
public class CurveStatisticsViewModel : DialogViewModelBase
{
#region
private string _title = "曲线数学统计";
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public ObservableCollection<CurveStatisticsResult> Results { get; } = new();
#endregion
#region
public ICommand CloseCommand { get; }
#endregion
public CurveStatisticsViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
CloseCommand = new DelegateCommand(Close);
}
private void Close()
{
RequestClose.Invoke(ButtonResult.OK);
}
#region Prism Dialog
public override void OnDialogClosed()
{
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
}
public override void OnDialogOpened(IDialogParameters parameters)
{
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
if (parameters.ContainsKey("Results"))
{
var list = parameters.GetValue<ObservableCollection<CurveStatisticsResult>>("Results");
Results.Clear();
foreach (var item in list)
Results.Add(item);
}
}
#endregion
}
/// <summary>
/// 单条曲线的数学统计结果。
/// </summary>
public class CurveStatisticsResult
{
/// <summary>曲线名称</summary>
public string DisplayName { get; set; } = string.Empty;
/// <summary>数据点数</summary>
public int PointCount { get; set; }
/// <summary>最大值</summary>
public double Max { get; set; }
/// <summary>最小值</summary>
public double Min { get; set; }
/// <summary>平均值</summary>
public double Average { get; set; }
/// <summary>线性回归斜率(单位/秒)</summary>
public double Slope { get; set; }
/// <summary>梯形积分值(单位·秒)</summary>
public double Integral { get; set; }
public string MaxText => Max.ToString("G6");
public string MinText => Min.ToString("G6");
public string AverageText => Average.ToString("G6");
public string SlopeText => Slope.ToString("G6");
public string IntegralText => Integral.ToString("G6");
}
}

View File

@@ -4,12 +4,18 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:oxy="http://oxyplot.org/wpf"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
prism:ViewModelLocator.AutoWireViewModel="True"
mc:Ignorable="d"
d:DesignHeight="700"
d:DesignWidth="1200">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<UserControl.Resources>
<converters:LessThanConverter x:Key="LessThanConverter"/>
</UserControl.Resources>
@@ -46,6 +52,19 @@
Command="{Binding ClearCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="清空已加载的曲线数据"/>
<Button Content="∑ 数学统计"
Command="{Binding StatisticsCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="对每条曲线计算最大值、最小值、平均值、斜率、积分"/>
<Button Content=" 保存"
Command="{Binding SaveCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="保存配置"/>
<Button Content="{Binding CaptureButtonText}"
Command="{Binding CaptureCommand}"
Padding="12,4" Margin="6,0,0,0"
ToolTip="添加双光标选择时间范围,导出选定范围的 CSV"/>
<Button Click="Button_Click" Content="截图" Margin="6,0,0,0"/>
</StackPanel>
</Border>
@@ -140,6 +159,7 @@
BorderBrush="#DDD" BorderThickness="1"
CornerRadius="4">
<oxy:PlotView Model="{Binding Plot}"
x:Name="Chart"
Background="Transparent"/>
</Border>
</Grid>

View File

@@ -24,5 +24,32 @@ namespace CurveModule.Views
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
VisualBrush brush = new VisualBrush(Chart) { Stretch = Stretch.None };
context.DrawRectangle(brush, null, new Rect(0, 0, Chart.ActualWidth, Chart.ActualHeight));
context.Close();
}
RenderTargetBitmap targetBitmap = new RenderTargetBitmap((int)Chart.ActualWidth, (int)Chart.ActualHeight, 96d, 96d, PixelFormats.Default);
targetBitmap.Render(drawingVisual);
PngBitmapEncoder saveEncoder = new PngBitmapEncoder();
saveEncoder.Frames.Add(BitmapFrame.Create(targetBitmap));
string tempFile = @$"D:\ACP\图片\{DateTime.Now:yyyyMMdd_HHmmss}.png";
System.IO.FileStream fs = System.IO.File.Open(tempFile, System.IO.FileMode.OpenOrCreate);
saveEncoder.Save(fs);
fs.Close();
MessageBox.Show($"导出图片成功");
}
catch (Exception ex)
{
MessageBox.Show($"导出图片失败:{ex.Message}");
}
}
}
}

View File

@@ -0,0 +1,89 @@
<UserControl x:Class="CurveModule.Views.Dialogs.CurveStatisticsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prism="http://prismlibrary.com/"
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
Background="White"
prism:ViewModelLocator.AutoWireViewModel="True"
Width="900"
Height="520"
mc:Ignorable="d">
<prism:Dialog.WindowStyle>
<Style BasedOn="{StaticResource DialogUserManageStyle}"
TargetType="Window" />
</prism:Dialog.WindowStyle>
<Grid>
<GroupBox Padding="10,8,10,0"
Header="{Binding Title}"
helpers:WindowDragHelper.EnableWindowDrag="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
ItemsSource="{Binding Results}"
AutoGenerateColumns="False"
IsReadOnly="True"
CanUserAddRows="False"
CanUserSortColumns="True"
Background="Transparent"
SelectionMode="Single"
HeadersVisibility="Column"
GridLinesVisibility="Horizontal"
BorderThickness="1"
BorderBrush="#DDD"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<DataGrid.Columns>
<DataGridTextColumn Header="曲线名称"
Binding="{Binding DisplayName}"
Width="180"/>
<DataGridTextColumn Header="数据点数"
Binding="{Binding PointCount}"
Width="70"/>
<DataGridTextColumn Header="最大值"
Binding="{Binding MaxText}"
Width="100"/>
<DataGridTextColumn Header="最小值"
Binding="{Binding MinText}"
Width="100"/>
<DataGridTextColumn Header="平均值"
Binding="{Binding AverageText}"
Width="100"/>
<DataGridTextColumn Header="斜率 (/s)"
Binding="{Binding SlopeText}"
Width="120">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="ToolTip" Value="线性回归斜率(值/秒)"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Header="积分 (·s)"
Binding="{Binding IntegralText}"
Width="130">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="ToolTip" Value="梯形法积分值(单位×秒)"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="5,8,5,12">
<Button Content="关闭"
Width="80"
Command="{Binding CloseCommand}"/>
</StackPanel>
</Grid>
</GroupBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace CurveModule.Views.Dialogs
{
public partial class CurveStatisticsView : UserControl
{
public CurveStatisticsView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,300 @@
using Common.Attributes;
using DeviceCommand.Base;
using Model.Models;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
[ACPCommand]
public class IT6015C : Tcp
{
// SCPI 指令结束符
private const string ScpiDelimiter = "\n";
public IT6015C(TcpConfig config) : base(config)
{
}
#region 1. IEEE 488.2
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"*CLS{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"*RST{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task<string> SCPI版本号(CancellationToken ct = default)
{
return await WriteReadAsync($":SYSTem:VERSion?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
#endregion
#region 2.
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"SYSTem:REMote{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"SYSTem:LOCal{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"SYSTem:RWLock{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"SYSTem:CLEar{ScpiDelimiter}", ct); //[cite: 1]
}
#endregion
#region 3.
/// <summary>
/// 设置电源优先工作模式 (CV 电压优先 或 CC 电流优先)
/// </summary>
public virtual async Task (IT6000CPriorityMode , CancellationToken ct = default)
{
await SendAsync($"FUNCTION {模式}{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"FUNCTION?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
/// <summary>
/// 设置瞬变/功能模式 (固定, LIST, 电池测试, PV曲线, 汽车波形)
/// </summary>
public virtual async Task (IT6000CFunctionMode , CancellationToken ct = default)
{
await SendAsync($"FUNCTION:MODE {模式}{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"FUNCTION:MODE?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
#endregion
#region 4.
public virtual async Task (bool , CancellationToken ct = default)
{
string = ? "ON" : "OFF";
await SendAsync($"OUTPut {参数}{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"OUTPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1]
}
#endregion
#region 5. /
/// <summary>
/// 设置输出电压 (仅在 CV 模式下生效)
/// </summary>
public virtual async Task (double , CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLTage {0:F2}{1}", , ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
/// <summary>
/// 设置输出电流 (仅在 CC 模式下生效)
/// </summary>
public virtual async Task (double , CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent {0:F3}{1}", , ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"CURRent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
/// <summary>
/// 设置 CV 优先模式下的电流限制上限值 (I+, 限流保护)
/// </summary>
public virtual async Task (double , CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:LIMit:POSitive {0:F3}{1}", , ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"CURRent:LIMit:POSitive?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
/// <summary>
/// 设置电压上升/下降斜率 (单位: V/s, 数值越大变化越快)[cite: 1]
/// </summary>
public virtual async Task (double Vs, CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLTage:SLEW {0:F4}{1}", Vs, ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
#endregion
#region 6. (OVP, OCP, OPP)
/// <summary>
/// 开启或关闭过压保护 (OVP)[cite: 1]
/// </summary>
public virtual async Task (bool , CancellationToken ct = default)
{
string = ? "ON" : "OFF";
await SendAsync($"VOLTage:PROTection:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task (double , CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLTage:PROTection {0:F2}{1}", , ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
/// <summary>
/// 开启或关闭过流保护 (OCP)[cite: 1]
/// </summary>
public virtual async Task (bool , CancellationToken ct = default)
{
string = ? "ON" : "OFF";
await SendAsync($"CURRent:PROTection:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task (double , CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "CURRent:PROTection {0:F3}{1}", , ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
/// <summary>
/// 开启或关闭过功率保护 (OPP)[cite: 1]
/// </summary>
public virtual async Task (bool , CancellationToken ct = default)
{
string = ? "ON" : "OFF";
await SendAsync($"POWER:PROTection:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task (double , CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "POWER:PROTection {0:F2}{1}", , ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
#endregion
#region 7. (MEASure FETCh)
[Monitorable("直流源载实际电压")]
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
[Monitorable("直流源载实际电流")]
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
[Monitorable("直流源载实际功率")]
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"MEASure:POWER?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"FETCh:VOLTage:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"FETCh:CURRent:MINimum?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
#endregion
#region 8. /CR模式设置 ( CC )
/// <summary>
/// 开启或关闭恒阻 (CR) 负载模式[cite: 1]
/// </summary>
/// <remarks>执行该指令前,必须确保设备处于 CC 优先模式,否则会报错</remarks>
public virtual async Task CR模式开关(bool , CancellationToken ct = default)
{
string = ? "ON" : "OFF";
await SendAsync($"SINK:RESistance:STATe {参数}{ScpiDelimiter}", ct); //[cite: 1]
}
public virtual async Task<string> CR模式开关(CancellationToken ct = default)
{
return await WriteReadAsync($"SINK:RESistance:STATe?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
/// <summary>
/// 设置恒阻 (CR) 模式下的电阻值 (单位: Ω)[cite: 1]
/// </summary>
/// <remarks>必须在先开启 CR 模式才能使用,设置为 0 相当于关闭 CR 模式</remarks>
public virtual async Task CR电阻值(double , CancellationToken ct = default)
{
string cmd = string.Format(CultureInfo.InvariantCulture, "SINK:RESistance {0:F2}{1}", , ScpiDelimiter); //[cite: 1]
await SendAsync(cmd, ct);
}
public virtual async Task<string> CR电阻值(CancellationToken ct = default)
{
return await WriteReadAsync($"SINK:RESistance?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
#endregion
}
}

View File

@@ -41,12 +41,12 @@ namespace DeviceCommand.Devices
#endregion
[ACPCommand]
public class IT6000C : Tcp
public class IT6036C : Tcp
{
// SCPI 指令结束符
private const string ScpiDelimiter = "\n";
public IT6000C(TcpConfig config) : base(config)
public IT6036C (TcpConfig config) : base(config)
{
}

View File

@@ -0,0 +1,244 @@
using Common;
using Common.Attributes;
using DeviceCommand.Base;
using System.Threading;
using System.Threading.Tasks;
namespace DeviceCommand.Device
{
/// <summary>
/// 功率分析仪型号HIOKI PW8001
/// </summary>
public class PW8001 : Tcp
{
/// <summary>
/// 构造函数SCPI 通信使用端口 23
/// </summary>
public PW8001()
{
Port = 23;
}
/// <summary>
/// 执行引擎注入的设备实例会话键
/// </summary>
public string? SessionKey { get; set; }
#region 1. IEEE 488.2
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync("*IDN?\r\n", "\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync("*OPT?\r\n", "\n", ct);
}
public virtual async Task (CancellationToken ct = default)
{
await SendAsync("*RST\r\n", ct);
}
public virtual async Task (CancellationToken ct = default)
{
await SendAsync("*CLS\r\n", ct);
}
#endregion
#region 2. (System & Mode)
public virtual async Task _WIDE(CancellationToken ct = default)
{
await SendAsync(":MODE WIDE\r\n", ct);
}
public virtual async Task _IEC(CancellationToken ct = default)
{
await SendAsync(":MODE IEC\r\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync(":MODE?\r\n", "\n", ct);
}
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($":SYNC:SOURce {源}\r\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync(":SYNC:SOURce?\r\n", "\n", ct);
}
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($":ZERO {状态}\r\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync(":ZERO?\r\n", "\n", ct);
}
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($":ZSP {状态}\r\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync(":ZSP?\r\n", "\n", ct);
}
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($":HEADer {状态}\r\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync(":HEADer?\r\n", "\n", ct);
}
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($":KLOCk {状态}\r\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync(":KLOCk?\r\n", "\n", ct);
}
#endregion
#region 3. (Integration)
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($":INTEG:CONTROL {模式}\r\n", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync(":INTEG:CONTROL?\r\n", "\n", ct);
}
public virtual async Task (string , string , CancellationToken ct = default)
{
await SendAsync($":INTEG:MODE{通道号} {模式}\r\n", ct);
}
public virtual async Task<string> (string , CancellationToken ct = default)
{
return await WriteReadAsync($":INTEG:MODE{通道号}?\r\n", "\n", ct);
}
#endregion
#region 4. (Measurement Query)
// ---------------- 基础测量(不含变比) ----------------
public virtual async Task<string> _不含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:U{通道号}?\r\n", "\n", ct);
}
public virtual async Task<string> _不含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:I{通道号}?\r\n", "\n", ct);
}
public virtual async Task<string> _不含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:P{通道号}?\r\n", "\n", ct);
}
// ---------------- 基础测量(含变比 CT ----------------
public virtual async Task<string> _含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:U{通道号}:CT?\r\n", "\n", ct);
}
public virtual async Task<string> _含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:I{通道号}:CT?\r\n", "\n", ct);
}
public virtual async Task<string> _含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:P{通道号}:CT?\r\n", "\n", ct);
}
// ---------------- 其他高阶参数测量 ----------------
public virtual async Task<string> (int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:WP{通道号}?\r\n", "\n", ct);
}
public virtual async Task<string> THD(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:UTHD{通道号}?\r\n", "\n", ct);
}
public virtual async Task<string> THD(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:ITHD{通道号}?\r\n", "\n", ct);
}
public virtual async Task<string> 线U12(CancellationToken ct = default)
{
return await WriteReadAsync(":MEAS:U12?\r\n", "\n", ct);
}
public virtual async Task<string> P123(CancellationToken ct = default)
{
return await WriteReadAsync(":MEAS:P123?\r\n", "\n", ct);
}
// ---------------- 整流平均值 ----------------
public virtual async Task<string> (int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:UMN{通道号}?\r\n", "\n", ct);
}
public virtual async Task<string> (int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:IMN{通道号}?\r\n", "\n", ct);
}
public virtual async Task<string> _含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:UMN{通道号}:CT?\r\n", "\n", ct);
}
public virtual async Task<string> _含变比(int , CancellationToken ct = default)
{
return await WriteReadAsync($":MEAS:IMN{通道号}:CT?\r\n", "\n", ct);
}
#endregion
#region 5.
/// <summary>
/// 发送自定义命令
/// </summary>
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($"{命令}\r\n", ct);
}
#endregion
}
}

View File

@@ -0,0 +1,198 @@
using Common.Attributes;
using DeviceCommand.Base;
using Model.Models;
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
#region
/// <summary>
/// Tektronix MSO 采集模式
/// </summary>
public enum TekAcquisitionMode
{
/// <summary> 采样模式 </summary>
SAMple,
/// <summary> 峰值检测模式 </summary>
PEAKdetect,
/// <summary> 高分辨率模式 </summary>
HIRes,
/// <summary> 平均模式 </summary>
AVErage,
/// <summary> 包络模式 </summary>
ENVelope
}
/// <summary>
/// Tektronix MSO 测量类型 (部分常用)
/// </summary>
public enum TekMeasurementType
{
AMPLitude,
FREQuency,
MEAN,
PK2PK,
MAXimum,
MINimum
}
#endregion
[ACPCommand]
public class TektronixMSO : Tcp
{
// SCPI 指令结束符 (文档中提到消息终止符需使用 LF)[cite: 1]
private const string ScpiDelimiter = "\n";
public TektronixMSO(TcpConfig config) : base(config)
{
}
#region 1.
public virtual async Task<string> (CancellationToken ct = default)
{
// 返回仪器的标识代码[cite: 1]
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
// 查询仪器是否处于忙碌状态,常用于同步操作[cite: 1]
return await WriteReadAsync($"BUSY?{ScpiDelimiter}", ScpiDelimiter, ct);
}
public virtual async Task (CancellationToken ct = default)
{
// 指示仪器执行信号路径校准 (SPC),需要几分钟时间[cite: 1]
await SendAsync($"*CAL?{ScpiDelimiter}", ct);
}
#endregion
#region 2. (Acquisition)
/// <summary>
/// 启动或停止采集
/// </summary>
public virtual async Task (bool , CancellationToken ct = default)
{
// 启动、停止或返回采集状态[cite: 1]
string = ? "RUN" : "STOP";
await SendAsync($"ACQuire:STATE {参数}{ScpiDelimiter}", ct);
}
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"ACQuire:STATE?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
}
public virtual async Task (TekAcquisitionMode , CancellationToken ct = default)
{
// 设置或查询采集模式[cite: 1]
await SendAsync($"ACQuire:MODe {模式}{ScpiDelimiter}", ct);
}
#endregion
#region 3. (Horizontal)
public virtual async Task (double , CancellationToken ct = default)
{
// 设置或查询水平刻度 (Scale)[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SCAle {0:E}{1}", , ScpiDelimiter);
await SendAsync(cmd, ct);
}
public virtual async Task (long , CancellationToken ct = default)
{
// 设置或查询记录长度[cite: 1]
await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct);
}
public virtual async Task (double , CancellationToken ct = default)
{
// 设置或查询水平采样率[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SAMPLERate {0:E}{1}", , ScpiDelimiter);
await SendAsync(cmd, ct);
}
#endregion
#region 4. (Display & Vertical)
/// <summary>
/// 全局启用或关闭某通道的显示
/// </summary>
public virtual async Task (int , bool , CancellationToken ct = default)
{
// 设置或查询指定通道的显示模式(开或关)[cite: 1]
string = ? "ON" : "OFF";
await SendAsync($"DISplay:GLObal:CH{通道号}:STATE {参数}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设置指定通道的垂直缩放比例 (Volts/Div)
/// </summary>
public virtual async Task (int , double , CancellationToken ct = default)
{
// 设置或查询指定Waveform View内指定通道的垂直缩放比例WaveView<x> 通常为1[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "DISplay:WAVEView1:CH{0}:VERTical:SCAle {1:E}{2}", , , ScpiDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 设置指定通道的垂直位置 (Divisions)
/// </summary>
public virtual async Task (int , double , CancellationToken ct = default)
{
// 设置或查询指定Waveform View内指定通道的垂直位置以格为单位[cite: 1]
string cmd = string.Format(CultureInfo.InvariantCulture, "DISplay:WAVEView1:CH{0}:VERTical:POSition {1:F2}{2}", , , ScpiDelimiter);
await SendAsync(cmd, ct);
}
#endregion
#region 5. (Measurement)
/// <summary>
/// 新增一个测量项槽位
/// </summary>
public virtual async Task (CancellationToken ct = default)
{
// 添加一个测量项[cite: 1]
await SendAsync($"MEASUrement:ADDMEAS{ScpiDelimiter}", ct);
}
public virtual async Task (int , TekMeasurementType , CancellationToken ct = default)
{
// 设置或查询测量类型[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct);
}
public virtual async Task (int , string , CancellationToken ct = default)
{
// 设置或查询局部输入源 (例如 CH1, CH2)[cite: 1]
await SendAsync($"MEASUrement:MEAS{测量项编号}:SOURCE {源名称}{ScpiDelimiter}", ct);
}
[Monitorable("测量项当前采集的平均值")]
public virtual async Task<string> (int , CancellationToken ct = default)
{
// 返回当前采集的指定测量的平均值[cite: 1]
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
}
public virtual async Task<string> (int , CancellationToken ct = default)
{
// 返回自上次统计重置以来指定测量的最大值(当前采集)[cite: 1]
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
}
#endregion
}
}

View File

@@ -0,0 +1,147 @@
using DeviceCommand.Devices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
namespace DeviceEditModule.ViewModels
{
public class DG1000ZViewModel : NavigateViewModelBase, IDisposable
{
#region
private readonly DeviceManager _deviceManager;
private DG1000Z? _device;
private CancellationTokenSource? _cts;
#endregion
#region
private string _deviceName = "DG1000Z";
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
private bool _isBusy;
/// <summary>正在执行设备命令时为 true用于 UI 忙碌状态指示。</summary>
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
private string _responseLog = string.Empty;
/// <summary>命令响应日志(最新消息在顶部)。</summary>
public string ResponseLog
{
get => _responseLog;
set => SetProperty(ref _responseLog, value);
}
#endregion
#region
#endregion
public DG1000ZViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_deviceManager = containerProvider.Resolve<DeviceManager>();
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
#region / Navigation
/// <summary>
/// 从 DeviceManager 中查找DG1000Z设备实例。
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
/// </summary>
public void Initialize(string? deviceName = null)
{
DG1000Z? found = null;
string? foundName = null;
if (deviceName != null &&
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
d is DG1000Z e)
{
found = e;
foundName = deviceName;
}
else
{
foreach (var kv in _deviceManager.DeviceMap)
{
if (kv.Value is DG1000Z it)
{
found = it;
foundName = kv.Key;
break;
}
}
}
_device = found;
DeviceName = foundName ?? "IT7800E (未找到)";
IsConnected = _device?.IsConnected ?? false;
AppendLog(found != null
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "" : "")}"
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
}
#region
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
private async Task Exec(Func<Task> action)
{
if (_device == null)
{
AppendLog("错误:未关联到设备实例,请检查设备配置。");
return;
}
if (IsBusy) return;
IsBusy = true;
try
{
await action();
IsConnected = _device.IsConnected;
}
catch (OperationCanceledException)
{
AppendLog("命令超时或已取消。");
}
catch (Exception ex)
{
AppendLog($"错误:{ex.Message}");
}
finally
{
IsBusy = false;
}
}
private void AppendLog(string message)
{
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
ResponseLog = ResponseLog.Length > 4000
? line + "\n" + ResponseLog[..3000]
: line + "\n" + ResponseLog;
}
#endregion
public override void OnNavigatedTo(NavigationContext context)
{
var name = context.Parameters.GetValue<string?>("DeviceName");
Initialize(name);
}
#endregion
}
}

View File

@@ -0,0 +1,147 @@
using DeviceCommand.Devices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
namespace DeviceEditModule.ViewModels
{
public class IT6015CViewModel : NavigateViewModelBase, IDisposable
{
#region
private readonly DeviceManager _deviceManager;
private IT6015C? _device;
private CancellationTokenSource? _cts;
#endregion
#region
private string _deviceName = "IT6015C";
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
private bool _isBusy;
/// <summary>正在执行设备命令时为 true用于 UI 忙碌状态指示。</summary>
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
private string _responseLog = string.Empty;
/// <summary>命令响应日志(最新消息在顶部)。</summary>
public string ResponseLog
{
get => _responseLog;
set => SetProperty(ref _responseLog, value);
}
#endregion
#region
#endregion
public IT6015CViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_deviceManager = containerProvider.Resolve<DeviceManager>();
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
#region / Navigation
/// <summary>
/// 从 DeviceManager 中查找IT6015C设备实例。
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
/// </summary>
public void Initialize(string? deviceName = null)
{
IT6015C? found = null;
string? foundName = null;
if (deviceName != null &&
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
d is IT6015C e)
{
found = e;
foundName = deviceName;
}
else
{
foreach (var kv in _deviceManager.DeviceMap)
{
if (kv.Value is IT6015C it)
{
found = it;
foundName = kv.Key;
break;
}
}
}
_device = found;
DeviceName = foundName ?? "IT7800E (未找到)";
IsConnected = _device?.IsConnected ?? false;
AppendLog(found != null
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "" : "")}"
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
}
#region
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
private async Task Exec(Func<Task> action)
{
if (_device == null)
{
AppendLog("错误:未关联到设备实例,请检查设备配置。");
return;
}
if (IsBusy) return;
IsBusy = true;
try
{
await action();
IsConnected = _device.IsConnected;
}
catch (OperationCanceledException)
{
AppendLog("命令超时或已取消。");
}
catch (Exception ex)
{
AppendLog($"错误:{ex.Message}");
}
finally
{
IsBusy = false;
}
}
private void AppendLog(string message)
{
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
ResponseLog = ResponseLog.Length > 4000
? line + "\n" + ResponseLog[..3000]
: line + "\n" + ResponseLog;
}
#endregion
public override void OnNavigatedTo(NavigationContext context)
{
var name = context.Parameters.GetValue<string?>("DeviceName");
Initialize(name);
}
#endregion
}
}

View File

@@ -0,0 +1,147 @@
using DeviceCommand.Devices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
namespace DeviceEditModule.ViewModels
{
public class IT6036CViewModel : NavigateViewModelBase, IDisposable
{
#region
private readonly DeviceManager _deviceManager;
private IT6036C? _device;
private CancellationTokenSource? _cts;
#endregion
#region
private string _deviceName = "IT6036C";
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
private bool _isBusy;
/// <summary>正在执行设备命令时为 true用于 UI 忙碌状态指示。</summary>
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
private string _responseLog = string.Empty;
/// <summary>命令响应日志(最新消息在顶部)。</summary>
public string ResponseLog
{
get => _responseLog;
set => SetProperty(ref _responseLog, value);
}
#endregion
#region
#endregion
public IT6036CViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_deviceManager = containerProvider.Resolve<DeviceManager>();
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
#region / Navigation
/// <summary>
/// 从 DeviceManager 中查找IT6036C设备实例。
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
/// </summary>
public void Initialize(string? deviceName = null)
{
IT6036C? found = null;
string? foundName = null;
if (deviceName != null &&
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
d is IT6036C e)
{
found = e;
foundName = deviceName;
}
else
{
foreach (var kv in _deviceManager.DeviceMap)
{
if (kv.Value is IT6036C it)
{
found = it;
foundName = kv.Key;
break;
}
}
}
_device = found;
DeviceName = foundName ?? "IT7800E (未找到)";
IsConnected = _device?.IsConnected ?? false;
AppendLog(found != null
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "" : "")}"
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
}
#region
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
private async Task Exec(Func<Task> action)
{
if (_device == null)
{
AppendLog("错误:未关联到设备实例,请检查设备配置。");
return;
}
if (IsBusy) return;
IsBusy = true;
try
{
await action();
IsConnected = _device.IsConnected;
}
catch (OperationCanceledException)
{
AppendLog("命令超时或已取消。");
}
catch (Exception ex)
{
AppendLog($"错误:{ex.Message}");
}
finally
{
IsBusy = false;
}
}
private void AppendLog(string message)
{
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
ResponseLog = ResponseLog.Length > 4000
? line + "\n" + ResponseLog[..3000]
: line + "\n" + ResponseLog;
}
#endregion
public override void OnNavigatedTo(NavigationContext context)
{
var name = context.Parameters.GetValue<string?>("DeviceName");
Initialize(name);
}
#endregion
}
}

View File

@@ -0,0 +1,147 @@
using DeviceCommand.Devices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
namespace DeviceEditModule.ViewModels
{
public class IT6720ViewModel : NavigateViewModelBase, IDisposable
{
#region
private readonly DeviceManager _deviceManager;
private IT6720? _device;
private CancellationTokenSource? _cts;
#endregion
#region
private string _deviceName = "IT6720";
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
private bool _isBusy;
/// <summary>正在执行设备命令时为 true用于 UI 忙碌状态指示。</summary>
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
private string _responseLog = string.Empty;
/// <summary>命令响应日志(最新消息在顶部)。</summary>
public string ResponseLog
{
get => _responseLog;
set => SetProperty(ref _responseLog, value);
}
#endregion
#region
#endregion
public IT6720ViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_deviceManager = containerProvider.Resolve<DeviceManager>();
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
#region / Navigation
/// <summary>
/// 从 DeviceManager 中查找IT6720设备实例。
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
/// </summary>
public void Initialize(string? deviceName = null)
{
IT6720? found = null;
string? foundName = null;
if (deviceName != null &&
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
d is IT6720 e)
{
found = e;
foundName = deviceName;
}
else
{
foreach (var kv in _deviceManager.DeviceMap)
{
if (kv.Value is IT6720 it)
{
found = it;
foundName = kv.Key;
break;
}
}
}
_device = found;
DeviceName = foundName ?? "IT7800E (未找到)";
IsConnected = _device?.IsConnected ?? false;
AppendLog(found != null
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "" : "")}"
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
}
#region
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
private async Task Exec(Func<Task> action)
{
if (_device == null)
{
AppendLog("错误:未关联到设备实例,请检查设备配置。");
return;
}
if (IsBusy) return;
IsBusy = true;
try
{
await action();
IsConnected = _device.IsConnected;
}
catch (OperationCanceledException)
{
AppendLog("命令超时或已取消。");
}
catch (Exception ex)
{
AppendLog($"错误:{ex.Message}");
}
finally
{
IsBusy = false;
}
}
private void AppendLog(string message)
{
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
ResponseLog = ResponseLog.Length > 4000
? line + "\n" + ResponseLog[..3000]
: line + "\n" + ResponseLog;
}
#endregion
public override void OnNavigatedTo(NavigationContext context)
{
var name = context.Parameters.GetValue<string?>("DeviceName");
Initialize(name);
}
#endregion
}
}

View File

@@ -0,0 +1,310 @@
using DeviceCommand.Devices;
using Prism.Commands;
using Prism.Ioc;
using System;
using System.Threading;
using System.Windows.Input;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
namespace DeviceEditModule.ViewModels
{
/// <summary>
/// IT7800E 交直流电源控制面板 ViewModel。
/// <para>
/// 注册为 Navigation View既可由 Region 导航进入,
/// 也可由外部直接实例化后作为 Tab 内容塞入 DialogMangerView
/// <code>
/// var view = container.Resolve&lt;IT7800EView&gt;();
/// (view.DataContext as IT7800EViewModel)?.Initialize("IT7800E");
/// _eventAggregator.GetEvent&lt;AddDialogTabEvent&gt;().Publish(
/// new DialogTabInfo { Title = "IT7800E", Content = view });
/// </code>
/// </para>
/// </summary>
public class IT7800EViewModel : NavigateViewModelBase, IDisposable
{
#region
private readonly DeviceManager _deviceManager;
private IT7800E? _device;
private CancellationTokenSource? _cts;
#endregion
#region
private string _deviceName = "IT7800E";
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
private bool _isBusy;
/// <summary>正在执行设备命令时为 true用于 UI 忙碌状态指示。</summary>
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
#endregion
#region
private double _acVoltage = 220.0;
/// <summary>待设置的交流电压值V。</summary>
public double AcVoltage
{
get => _acVoltage;
set => SetProperty(ref _acVoltage, value);
}
private double _dcVoltage = 0.0;
/// <summary>待设置的直流偏置电压值V。</summary>
public double DcVoltage
{
get => _dcVoltage;
set => SetProperty(ref _dcVoltage, value);
}
private double _frequency = 50.0;
/// <summary>待设置的交流频率Hz。</summary>
public double Frequency
{
get => _frequency;
set => SetProperty(ref _frequency, value);
}
private double _currentLimit = 10.0;
/// <summary>待设置的限流值A。</summary>
public double CurrentLimit
{
get => _currentLimit;
set => SetProperty(ref _currentLimit, value);
}
private PowerCouplingMode _selectedMode = PowerCouplingMode.AC;
/// <summary>待设置的电源工作模式AC / DC / ACDC。</summary>
public PowerCouplingMode SelectedMode
{
get => _selectedMode;
set => SetProperty(ref _selectedMode, value);
}
private double _ovpValue = 260.0;
/// <summary>过压保护值V。</summary>
public double OvpValue
{
get => _ovpValue;
set => SetProperty(ref _ovpValue, value);
}
private double _ocpValue = 15.0;
/// <summary>过流保护值A。</summary>
public double OcpValue
{
get => _ocpValue;
set => SetProperty(ref _ocpValue, value);
}
#endregion
#region
private string _measuredVoltage = "—";
public string MeasuredVoltage
{
get => _measuredVoltage;
set => SetProperty(ref _measuredVoltage, value);
}
private string _measuredCurrent = "—";
public string MeasuredCurrent
{
get => _measuredCurrent;
set => SetProperty(ref _measuredCurrent, value);
}
private string _measuredPower = "—";
public string MeasuredPower
{
get => _measuredPower;
set => SetProperty(ref _measuredPower, value);
}
private string _measuredFrequency = "—";
public string MeasuredFrequency
{
get => _measuredFrequency;
set => SetProperty(ref _measuredFrequency, value);
}
private string _responseLog = string.Empty;
/// <summary>命令响应日志(最新消息在顶部)。</summary>
public string ResponseLog
{
get => _responseLog;
set => SetProperty(ref _responseLog, value);
}
#endregion
#region
public ICommand QueryIdentityCommand { get; }
public ICommand ResetDeviceCommand { get; }
public ICommand OutputOnCommand { get; }
public ICommand OutputOffCommand { get; }
public ICommand SetModeCommand { get; }
public ICommand SetAcVoltageCommand { get; }
public ICommand SetDcVoltageCommand { get; }
public ICommand SetFrequencyCommand { get; }
public ICommand SetCurrentCommand { get; }
public ICommand QueryAllMeasureCommand { get; }
public ICommand SetRemoteModeCommand { get; }
public ICommand SetLocalModeCommand { get; }
public ICommand SetOvpCommand { get; }
public ICommand SetOcpCommand { get; }
public ICommand ClearAlarmCommand { get; }
public ICommand ClearErrorCommand { get; }
#endregion
public IT7800EViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_deviceManager = containerProvider.Resolve<DeviceManager>();
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.(Ct()))));
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(Ct()); AppendLog("设备已重置"); }));
OutputOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.DC输出(true, Ct()); AppendLog("输出已开启"); }));
OutputOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.DC输出(false, Ct()); AppendLog("输出已关闭"); }));
SetModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(SelectedMode, Ct()); AppendLog($"模式已设为 {SelectedMode}"); }));
SetAcVoltageCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(AcVoltage, Ct()); AppendLog($"AC电压已设为 {AcVoltage} V"); }));
SetDcVoltageCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(DcVoltage, Ct()); AppendLog($"DC偏置已设为 {DcVoltage} V"); }));
SetFrequencyCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(Frequency, Ct()); AppendLog($"频率已设为 {Frequency} Hz"); }));
SetCurrentCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(CurrentLimit, Ct()); AppendLog($"限流已设为 {CurrentLimit} A"); }));
SetOvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!._OVP(OvpValue, Ct()); AppendLog($"OVP已设为 {OvpValue} V"); }));
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!._OCP(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
SetRemoteModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(Ct()); AppendLog("已切换到远程控制模式"); }));
SetLocalModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(Ct()); AppendLog("已切换到本地控制模式"); }));
ClearAlarmCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(Ct()); AppendLog("保护告警已清除"); }));
ClearErrorCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.(Ct()); AppendLog("错误队列已清除"); }));
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
{
MeasuredVoltage = await _device!.(Ct());
MeasuredCurrent = await _device!.(Ct());
MeasuredPower = await _device!.(Ct());
MeasuredFrequency = await _device!.(Ct());
AppendLog($"测量 → 电压:{MeasuredVoltage}V 电流:{MeasuredCurrent}A 功率:{MeasuredPower}W 频率:{MeasuredFrequency}Hz");
}));
Initialize();
}
#region / Navigation
/// <summary>
/// 从 DeviceManager 中查找 IT7800E 设备实例。
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
/// </summary>
public void Initialize(string? deviceName = null)
{
IT7800E? found = null;
string? foundName = null;
if (deviceName != null &&
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
d is IT7800E e)
{
found = e;
foundName = deviceName;
}
else
{
foreach (var kv in _deviceManager.DeviceMap)
{
if (kv.Value is IT7800E it)
{
found = it;
foundName = kv.Key;
break;
}
}
}
_device = found;
DeviceName = foundName ?? "IT7800E (未找到)";
IsConnected = _device?.IsConnected ?? false;
AppendLog(found != null
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "" : "")}"
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
}
public override void OnNavigatedTo(NavigationContext context)
{
var name = context.Parameters.GetValue<string?>("DeviceName");
Initialize(name);
}
#endregion
#region
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
private async Task Exec(Func<Task> action)
{
if (_device == null)
{
AppendLog("错误:未关联到设备实例,请检查设备配置。");
return;
}
if (IsBusy) return;
IsBusy = true;
try
{
await action();
IsConnected = _device.IsConnected;
}
catch (OperationCanceledException)
{
AppendLog("命令超时或已取消。");
}
catch (Exception ex)
{
AppendLog($"错误:{ex.Message}");
}
finally
{
IsBusy = false;
}
}
private void AppendLog(string message)
{
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
ResponseLog = ResponseLog.Length > 4000
? line + "\n" + ResponseLog[..3000]
: line + "\n" + ResponseLog;
}
#endregion
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
}
}

View File

@@ -0,0 +1,148 @@
using DeviceCommand.Device;
using DeviceCommand.Devices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
namespace DeviceEditModule.ViewModels
{
public class PW8001ViewModel : NavigateViewModelBase, IDisposable
{
#region
private readonly DeviceManager _deviceManager;
private PW8001? _device;
private CancellationTokenSource? _cts;
#endregion
#region
private string _deviceName = "PW8001";
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
private bool _isBusy;
/// <summary>正在执行设备命令时为 true用于 UI 忙碌状态指示。</summary>
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
private string _responseLog = string.Empty;
/// <summary>命令响应日志(最新消息在顶部)。</summary>
public string ResponseLog
{
get => _responseLog;
set => SetProperty(ref _responseLog, value);
}
#endregion
#region
#endregion
public PW8001ViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_deviceManager = containerProvider.Resolve<DeviceManager>();
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
#region / Navigation
/// <summary>
/// 从 DeviceManager 中查找PW8001设备实例。
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
/// </summary>
public void Initialize(string? deviceName = null)
{
PW8001? found = null;
string? foundName = null;
if (deviceName != null &&
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
d is PW8001 e)
{
found = e;
foundName = deviceName;
}
else
{
foreach (var kv in _deviceManager.DeviceMap)
{
if (kv.Value is PW8001 it)
{
found = it;
foundName = kv.Key;
break;
}
}
}
_device = found;
DeviceName = foundName ?? "IT7800E (未找到)";
IsConnected = _device?.IsConnected ?? false;
AppendLog(found != null
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "" : "")}"
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
}
#region
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
private async Task Exec(Func<Task> action)
{
if (_device == null)
{
AppendLog("错误:未关联到设备实例,请检查设备配置。");
return;
}
if (IsBusy) return;
IsBusy = true;
try
{
await action();
IsConnected = _device.IsConnected;
}
catch (OperationCanceledException)
{
AppendLog("命令超时或已取消。");
}
catch (Exception ex)
{
AppendLog($"错误:{ex.Message}");
}
finally
{
IsBusy = false;
}
}
private void AppendLog(string message)
{
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
ResponseLog = ResponseLog.Length > 4000
? line + "\n" + ResponseLog[..3000]
: line + "\n" + ResponseLog;
}
#endregion
public override void OnNavigatedTo(NavigationContext context)
{
var name = context.Parameters.GetValue<string?>("DeviceName");
Initialize(name);
}
#endregion
}
}

View File

@@ -0,0 +1,148 @@
using DeviceCommand.Device;
using DeviceCommand.Devices;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UIShare.GlobalVariable;
using UIShare.ViewModelBase;
namespace DeviceEditModule.ViewModels
{
public class TektronixMSOViewModel : NavigateViewModelBase, IDisposable
{
#region
private readonly DeviceManager _deviceManager;
private TektronixMSO? _device;
private CancellationTokenSource? _cts;
#endregion
#region
private string _deviceName = "TektronixMSO";
public string DeviceName
{
get => _deviceName;
set => SetProperty(ref _deviceName, value);
}
private bool _isConnected;
public bool IsConnected
{
get => _isConnected;
set => SetProperty(ref _isConnected, value);
}
private bool _isBusy;
/// <summary>正在执行设备命令时为 true用于 UI 忙碌状态指示。</summary>
public bool IsBusy
{
get => _isBusy;
set => SetProperty(ref _isBusy, value);
}
private string _responseLog = string.Empty;
/// <summary>命令响应日志(最新消息在顶部)。</summary>
public string ResponseLog
{
get => _responseLog;
set => SetProperty(ref _responseLog, value);
}
#endregion
#region
#endregion
public TektronixMSOViewModel(IContainerProvider containerProvider) : base(containerProvider)
{
_deviceManager = containerProvider.Resolve<DeviceManager>();
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
#region / Navigation
/// <summary>
/// 从 DeviceManager 中查找TektronixMSO设备实例。
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
/// </summary>
public void Initialize(string? deviceName = null)
{
TektronixMSO? found = null;
string? foundName = null;
if (deviceName != null &&
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
d is TektronixMSO e)
{
found = e;
foundName = deviceName;
}
else
{
foreach (var kv in _deviceManager.DeviceMap)
{
if (kv.Value is TektronixMSO it)
{
found = it;
foundName = kv.Key;
break;
}
}
}
_device = found;
DeviceName = foundName ?? "IT7800E (未找到)";
IsConnected = _device?.IsConnected ?? false;
AppendLog(found != null
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "" : "")}"
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
}
#region
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
private async Task Exec(Func<Task> action)
{
if (_device == null)
{
AppendLog("错误:未关联到设备实例,请检查设备配置。");
return;
}
if (IsBusy) return;
IsBusy = true;
try
{
await action();
IsConnected = _device.IsConnected;
}
catch (OperationCanceledException)
{
AppendLog("命令超时或已取消。");
}
catch (Exception ex)
{
AppendLog($"错误:{ex.Message}");
}
finally
{
IsBusy = false;
}
}
private void AppendLog(string message)
{
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
ResponseLog = ResponseLog.Length > 4000
? line + "\n" + ResponseLog[..3000]
: line + "\n" + ResponseLog;
}
#endregion
public override void OnNavigatedTo(NavigationContext context)
{
var name = context.Parameters.GetValue<string?>("DeviceName");
Initialize(name);
}
#endregion
}
}

View File

@@ -0,0 +1,15 @@
<UserControl x:Class="DeviceEditModule.Views.DG1000ZView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="False"
d:DesignHeight="760" d:DesignWidth="860">
<Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DeviceEditModule.Views
{
/// <summary>
/// DG1000ZView.xaml 的交互逻辑
/// </summary>
public partial class DG1000ZView : UserControl
{
public DG1000ZView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,15 @@
<UserControl x:Class="DeviceEditModule.Views.IT6015CView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="False"
d:DesignHeight="760" d:DesignWidth="860">
<Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DeviceEditModule.Views
{
/// <summary>
/// IT6015CView.xaml 的交互逻辑
/// </summary>
public partial class IT6015CView : UserControl
{
public IT6015CView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,16 @@
<Page x:Class="DeviceEditModule.Views.IT6036CView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="False"
d:DesignHeight="760" d:DesignWidth="860">
<Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DeviceEditModule.Views
{
/// <summary>
/// IT6036CView.xaml 的交互逻辑
/// </summary>
public partial class IT6036CView : Page
{
public IT6036CView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,15 @@
<UserControl x:Class="DeviceEditModule.Views.IT6720View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="False"
d:DesignHeight="760" d:DesignWidth="860">
<Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DeviceEditModule.Views
{
/// <summary>
/// IT6720View.xaml 的交互逻辑
/// </summary>
public partial class IT6720View : UserControl
{
public IT6720View()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,272 @@
<UserControl x:Class="DeviceEditModule.Views.IT7800EView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="False"
d:DesignHeight="760" d:DesignWidth="860">
<UserControl.Resources>
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="12">
<!-- ═══ 设备信息头 ═══ -->
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<materialDesign:PackIcon Kind="Flash" Width="22" Height="22"
Foreground="#1565C0" Margin="0,0,8,0"
VerticalAlignment="Center"/>
<TextBlock Text="IT7800E 交直流可编程电源"
FontSize="15" FontWeight="Bold"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
FontSize="13" Foreground="#757575"
VerticalAlignment="Center" Margin="4,0,0,0"/>
</StackPanel>
<!-- 连接状态指示 -->
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#F44336"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Background" Value="#4CAF50"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<TextBlock VerticalAlignment="Center" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="未连接"/>
<Setter Property="Foreground" Value="#F44336"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Text" Value="已连接"/>
<Setter Property="Foreground" Value="#4CAF50"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- 忙碌指示 -->
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
Margin="12,0,0,0"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
</Grid>
</materialDesign:Card>
<!-- ═══ 主体 2 列 ═══ -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ─── 左列 ─── -->
<StackPanel Grid.Column="0" Margin="0,0,4,0">
<!-- 输出控制 -->
<GroupBox Header="输出控制" Margin="0,0,0,8"
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
<StackPanel Margin="4,4,4,4">
<!-- 开关输出 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="主输出" Style="{StaticResource ParamLabel}"/>
<Button Content="开启输出" Command="{Binding OutputOnCommand}"
Style="{StaticResource MaterialDesignRaisedButton}"
Background="#388E3C" Foreground="White"
Height="32" Padding="12,0" FontSize="12" Margin="4,0"/>
<Button Content="关闭输出" Command="{Binding OutputOffCommand}"
Style="{StaticResource WarnBtn}"/>
</StackPanel>
<!-- 工作模式 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="工作模式" Style="{StaticResource ParamLabel}"/>
<ComboBox Width="90" Height="32" Margin="4,0"
materialDesign:HintAssist.Hint=""
SelectedItem="{Binding SelectedMode}"
VerticalContentAlignment="Center" FontSize="12">
<ComboBoxItem Content="AC"/>
<ComboBoxItem Content="DC"/>
<ComboBoxItem Content="ACDC"/>
</ComboBox>
<Button Content="设置模式" Command="{Binding SetModeCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
<!-- 远程/本地 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
Style="{StaticResource CmdBtn}"/>
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
<!-- 清除 / 重置 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="系统操作" Style="{StaticResource ParamLabel}"/>
<Button Content="清除告警" Command="{Binding ClearAlarmCommand}"
Style="{StaticResource WarnBtn}"/>
<Button Content="清除错误" Command="{Binding ClearErrorCommand}"
Style="{StaticResource WarnBtn}"/>
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
Style="{StaticResource WarnBtn}"/>
</StackPanel>
</StackPanel>
</GroupBox>
<!-- 参数设置 -->
<GroupBox Header="参数设置" Margin="0,0,0,8"
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
<StackPanel Margin="4,4,4,4">
<!-- AC 电压 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="AC电压 (V)" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource NumInput}"
materialDesign:HintAssist.Hint=""
Text="{Binding AcVoltage, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="设置" Command="{Binding SetAcVoltageCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
<!-- DC 偏置 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="DC偏置 (V)" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource NumInput}"
materialDesign:HintAssist.Hint=""
Text="{Binding DcVoltage, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="设置" Command="{Binding SetDcVoltageCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
<!-- 频率 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="频率 (Hz)" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource NumInput}"
materialDesign:HintAssist.Hint=""
Text="{Binding Frequency, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="设置" Command="{Binding SetFrequencyCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
<!-- 限流 -->
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="限流 (A)" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource NumInput}"
materialDesign:HintAssist.Hint=""
Text="{Binding CurrentLimit, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="设置" Command="{Binding SetCurrentCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
</StackPanel>
</GroupBox>
<!-- 保护设置 -->
<GroupBox Header="保护设置" Margin="0,0,0,8"
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
<StackPanel Margin="4,4,4,4">
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="OVP (V)" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource NumInput}"
materialDesign:HintAssist.Hint=""
Text="{Binding OvpValue, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="设置 OVP" Command="{Binding SetOvpCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="OCP (A)" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource NumInput}"
materialDesign:HintAssist.Hint=""
Text="{Binding OcpValue, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="设置 OCP" Command="{Binding SetOcpCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
</StackPanel>
</GroupBox>
</StackPanel>
<!-- ─── 右列 ─── -->
<StackPanel Grid.Column="1" Margin="4,0,0,0">
<!-- 实时测量 -->
<GroupBox Header="实时测量" Margin="0,0,0,8"
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
<StackPanel Margin="4,4,4,4">
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="实际电压" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource MeasureBox}"
materialDesign:HintAssist.Hint=""
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="实际电流" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource MeasureBox}"
materialDesign:HintAssist.Hint=""
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="实际功率" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource MeasureBox}"
materialDesign:HintAssist.Hint=""
Text="{Binding MeasuredPower, Mode=OneWay}"/>
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,4">
<TextBlock Text="输出频率" Style="{StaticResource ParamLabel}"/>
<TextBox Style="{StaticResource MeasureBox}"
materialDesign:HintAssist.Hint=""
Text="{Binding MeasuredFrequency, Mode=OneWay}"/>
<TextBlock Text="Hz" VerticalAlignment="Center" Margin="2,0,8,0"/>
</StackPanel>
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
Style="{StaticResource CmdBtn}"
HorizontalAlignment="Left" Margin="0,4,0,0"/>
</StackPanel>
</GroupBox>
<!-- 设备信息 -->
<GroupBox Header="设备信息" Margin="0,0,0,8"
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
<StackPanel Orientation="Horizontal" Margin="4,8">
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
Style="{StaticResource CmdBtn}"/>
</StackPanel>
</GroupBox>
<!-- 响应日志 -->
<GroupBox Header="响应日志" Margin="0,0,0,8"
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
<ScrollViewer Height="260" VerticalScrollBarVisibility="Auto">
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
materialDesign:HintAssist.Hint=""
IsReadOnly="True"
TextWrapping="Wrap"
FontSize="11"
FontFamily="Consolas"
Background="#FAFAFA"
BorderThickness="0"
VerticalAlignment="Top"/>
</ScrollViewer>
</GroupBox>
</StackPanel>
</Grid>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DeviceEditModule.Views
{
/// <summary>
/// IT7800EView.xaml 的交互逻辑
/// </summary>
public partial class IT7800EView : UserControl
{
public IT7800EView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,15 @@
<UserControl x:Class="DeviceEditModule.Views.PW8001View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="False"
d:DesignHeight="760" d:DesignWidth="860">
<Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DeviceEditModule.Views
{
/// <summary>
/// PW8001View.xaml 的交互逻辑
/// </summary>
public partial class PW8001View : UserControl
{
public PW8001View()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,15 @@
<UserControl x:Class="DeviceEditModule.Views.TektronixMSOView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:prism="http://prismlibrary.com/"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
mc:Ignorable="d"
prism:ViewModelLocator.AutoWireViewModel="False"
d:DesignHeight="760" d:DesignWidth="860">
<Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DeviceEditModule.Views
{
/// <summary>
/// TektronixMSOView.xaml 的交互逻辑
/// </summary>
public partial class TektronixMSOView : UserControl
{
public TektronixMSOView()
{
InitializeComponent();
}
}
}

View File

@@ -17,14 +17,14 @@ namespace MainModule
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(MainView));
// 2. 给 3 个台架分别注入测试视图
// 当你点击 "台架 1" 的 Tab 时,就会显示这个 AutomatedTestingView
regionManager.RegisterViewWithRegion("TestCell1", typeof(AutomatedTestingView));
// 当你点击 "台架 1" 的 Tab 时,就会显示这个 ProtocolStartView
regionManager.RegisterViewWithRegion("TestCell1", typeof(ProtocolStartView));
// 当你点击 "台架 2" 的 Tab 时,就会显示这个 AutomatedTestingView
regionManager.RegisterViewWithRegion("TestCell2", typeof(AutomatedTestingView));
// 当你点击 "台架 2" 的 Tab 时,就会显示这个 ProtocolStartView
regionManager.RegisterViewWithRegion("TestCell2", typeof(ProtocolStartView));
// 当你点击 "台架 3" 的 Tab 时,就会显示这个 AutomatedTestingView
regionManager.RegisterViewWithRegion("TestCell3", typeof(AutomatedTestingView));
// 当你点击 "台架 3" 的 Tab 时,就会显示这个 ProtocolStartView
regionManager.RegisterViewWithRegion("TestCell3", typeof(ProtocolStartView));
}
public void RegisterTypes(IContainerRegistry containerRegistry)

View File

@@ -69,6 +69,10 @@ namespace MainModule.ViewModels
string json = System.IO.File.ReadAllText(filePath);
Newtonsoft.Json.JsonConvert.PopulateObject(json, _systemConfig);
}
else
{
_systemConfig = new SystemConfig();
}
}
//容器解析顺序不要改变!!!
_scopedContext = _scope.Resolve<ScopedContext>();

View File

@@ -29,52 +29,6 @@ namespace UIShare.GlobalVariable
}
return true;
}
/// <summary>
/// 根据标题(格子标识)加载独立的配置文件
/// </summary>
public static SystemConfig Load(string title)
{
if (string.IsNullOrEmpty(title))
{
throw new ArgumentException("配置标题不能为空", nameof(title));
}
// 临时实例化一个对象以获取默认的 SystemPath
var dummy = new SystemConfig();
string configPath = Path.Combine(dummy.SystemPath, $"{title}.json");
if (!File.Exists(configPath))
{
// 如果不存在,创建一个带 Title 的默认配置并保存
var defaultConfig = new SystemConfig { Title = title };
EnsureDefaultCanDevice(defaultConfig);
Save(defaultConfig);
return defaultConfig;
}
lock (_fileLock)
{
try
{
string json = File.ReadAllText(configPath);
var config = JsonConvert.DeserializeObject<SystemConfig>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
config ??= new SystemConfig { Title = title };
EnsureDefaultCanDevice(config);
return config;
}
catch (Exception ex)
{
LoggerHelper.ErrorWithNotify(title, $"格子 [{title}] 配置加载失败: {ex.Message}");
var fallback = new SystemConfig { Title = title };
EnsureDefaultCanDevice(fallback);
return fallback;
}
}
}
/// <summary>
/// 保存指定的配置实例
@@ -107,6 +61,37 @@ namespace UIShare.GlobalVariable
}
}
/// <summary>
/// 保存全局配置
/// </summary>
public static void SaveGlobalConfig(GlobalConfig config)
{
if (config == null) return;
lock (_fileLock)
{
try
{
if (!Directory.Exists(config.SystemPath))
Directory.CreateDirectory(config.SystemPath);
string configPath = Path.Combine(config.SystemPath, "GlobalConfig.json");
string json = JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
File.WriteAllText(configPath, json);
}
catch (Exception ex)
{
}
}
}
/// <summary>
/// 确保配置中至少包含一个 CAN 设备(对应 SystemConfig.CANFD
/// 旧配置或空配置会自动升级,使用户在设置界面能看到 CAN 设备。

View File

@@ -0,0 +1,17 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UIShare.GlobalVariable
{
public class GlobalConfig
{
[JsonIgnore]
public string SystemPath { get; set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ACP");
public string DefaultCSVFilePath { get; set; } = "";
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Management;
using System.Security.Cryptography;
using System.Text;
namespace UIShare.Helpers
{
public static class MachineCodeHelper
{
private static string UniqueCode = "2021E5B9BB0A54685B9445873F01D8F7";
/// <summary>
/// 校验机器码
/// </summary>
public static bool VerifyMachineCode(string MachineCode)
{
return UniqueCode == MachineCode;
}
/// <summary>
/// 获取最终的设备唯一机器码
/// </summary>
public static string GetDeviceMachineCode()
{
string cpuId = GetCpuId();
string boardId = GetMotherboardId();
// 将主板和CPU序列号拼接并转换为MD5生成一个干净的、固定长度的机器码
string rawCode = $"CPU:{cpuId}_BOARD:{boardId}";
return GetMD5Hash(rawCode);
}
/// <summary>
/// 获取 CPU 序列号
/// </summary>
private static string GetCpuId()
{
try
{
using (ManagementClass mc = new ManagementClass("win32_processor"))
{
using (ManagementObjectCollection moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
return mo.Properties["ProcessorId"].Value.ToString();
}
}
}
}
catch
{
// 如果获取失败,返回一个默认标识
}
return "UNKNOWN_CPU";
}
/// <summary>
/// 获取主板序列号 (最稳定的硬件标识)
/// </summary>
private static string GetMotherboardId()
{
try
{
using (ManagementClass mc = new ManagementClass("Win32_BaseBoard"))
{
using (ManagementObjectCollection moc = mc.GetInstances())
{
foreach (ManagementObject mo in moc)
{
return mo.Properties["SerialNumber"].Value.ToString();
}
}
}
}
catch
{
}
return "UNKNOWN_BOARD";
}
/// <summary>
/// 字符串 MD5 加密
/// </summary>
private static string GetMD5Hash(string input)
{
using (MD5 md5 = MD5.Create())
{
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
// 将字节数组转换为16进制字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("X2"));
}
return sb.ToString();
}
}
}
}

View File

@@ -16,6 +16,7 @@
<PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" />
<PackageReference Include="Notifications.Wpf.Core" Version="2.0.1" />
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
<PackageReference Include="System.Management" Version="11.0.0-preview.6.26359.118" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />