Compare commits
25 Commits
2d7f302bb9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66bb6d36ba | ||
|
|
7250080a24 | ||
|
|
537c4554ae | ||
|
|
857bd3fb56 | ||
| ef52530a14 | |||
| 7e6c342908 | |||
|
|
df520f91ac | ||
|
|
9f8cf5f267 | ||
| a15334f02d | |||
|
|
21cc593d3f | ||
|
|
63af886f26 | ||
|
|
2e74627b31 | ||
|
|
5cfb8af9e4 | ||
|
|
ceae6e3421 | ||
|
|
e90dc805fc | ||
|
|
850156bfa2 | ||
|
|
f350058f53 | ||
|
|
156a04817c | ||
|
|
0e9f65ea21 | ||
|
|
252c70ef05 | ||
|
|
e4107c9997 | ||
| f1b6fb72f0 | |||
| c42264064e | |||
|
|
4050053870 | ||
|
|
1ccbfd852d |
6
ACP.sln
6
ACP.sln
@@ -37,8 +37,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceE
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CANModule", "CANModule\CANModule.csproj", "{AF2533DD-599D-49D7-942B-B0C26534D15F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CANModule", "CANModule\CANModule.csproj", "{AF2533DD-599D-49D7-942B-B0C26534D15F}"
|
||||||
EndProject
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurveModule", "CurveModule\CurveModule.csproj", "{84006890-9288-44C4-9D17-FA88F3FDECFA}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportModule", "ExportModule\ExportModule.csproj", "{8923122F-F7D3-4AE7-9E86-9E78F1296A11}"
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.Build.0 = 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
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
<ProjectReference Include="..\TSMasterCAN\TSMasterCAN.csproj" />
|
<ProjectReference Include="..\TSMasterCAN\TSMasterCAN.csproj" />
|
||||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||||
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
||||||
<ProjectReference Include="..\加密狗\加密狗.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ using DeviceCommand.Base;
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using ACP.Profiles;
|
using ACP.Profiles;
|
||||||
using 加密狗;
|
using UIShare.Helpers;
|
||||||
|
|
||||||
namespace ACP
|
namespace ACP
|
||||||
{
|
{
|
||||||
@@ -83,44 +83,17 @@ namespace ACP
|
|||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
string myMachineCode = MachineCodeHelper.GetDeviceMachineCode();
|
||||||
string strUserSn = "ACA89B2C1194650D4E93D97BE189FFCC";
|
if (MachineCodeHelper.VerifyMachineCode(myMachineCode))
|
||||||
string strApiKey = "C6D01BCD442347822876F8232245DE9422643C167B8C61CE4DF6049990E7395588675A8FC6AA4BF7AD353D669C993236BEA83F77551753885A47AF34185CC3A7";
|
|
||||||
|
|
||||||
Byte[] buffer = new Byte[256];
|
|
||||||
buffer = System.Text.Encoding.Default.GetBytes(strUserSn);
|
|
||||||
uint retcode;
|
|
||||||
retcode = 加密狗驱动类.验证加密狗SN(buffer);
|
|
||||||
if (retcode != 0)
|
|
||||||
{
|
{
|
||||||
MessageBox.Show(string.Format("设置UserSN失败 error code: 0x{0:x}", retcode));
|
base.OnStartup(e);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageBox.Show("机器码不匹配!");
|
||||||
return;
|
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)
|
protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
|
||||||
{
|
{
|
||||||
@@ -133,6 +106,7 @@ namespace ACP
|
|||||||
containerRegistry.RegisterSingleton<GlobalInfo>();
|
containerRegistry.RegisterSingleton<GlobalInfo>();
|
||||||
containerRegistry.RegisterSingleton<HardwareDataBroadcaster>();
|
containerRegistry.RegisterSingleton<HardwareDataBroadcaster>();
|
||||||
containerRegistry.RegisterSingleton<CANSignalBroadcaster>();
|
containerRegistry.RegisterSingleton<CANSignalBroadcaster>();
|
||||||
|
containerRegistry.RegisterSingleton<GlobalConfig>();
|
||||||
//注册AutoMapper
|
//注册AutoMapper
|
||||||
var config = new MapperConfiguration(
|
var config = new MapperConfiguration(
|
||||||
cfg => cfg.AddProfile<AutoMapperProfile>(),
|
cfg => cfg.AddProfile<AutoMapperProfile>(),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ namespace ACP.ViewModels
|
|||||||
private readonly INotificationManager _notificationManager;
|
private readonly INotificationManager _notificationManager;
|
||||||
private readonly IModuleManager _moduleManager;
|
private readonly IModuleManager _moduleManager;
|
||||||
private readonly GlobalInfo _globalInfo;
|
private readonly GlobalInfo _globalInfo;
|
||||||
|
private readonly GlobalConfig _globalConfig;
|
||||||
|
|
||||||
// 💡 新增:UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间
|
// 💡 新增:UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间
|
||||||
private readonly DispatcherTimer _uiRefreshTimer;
|
private readonly DispatcherTimer _uiRefreshTimer;
|
||||||
@@ -165,6 +166,21 @@ namespace ACP.ViewModels
|
|||||||
_regionManager = containerProvider.Resolve<IRegionManager>();
|
_regionManager = containerProvider.Resolve<IRegionManager>();
|
||||||
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
||||||
_moduleManager = containerProvider.Resolve<IModuleManager>();
|
_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);
|
LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
|
||||||
ShowDialogManagerViewCommand = new DelegateCommand(ShowDialogManagerView);
|
ShowDialogManagerViewCommand = new DelegateCommand(ShowDialogManagerView);
|
||||||
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
|
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
|
||||||
@@ -841,10 +857,7 @@ namespace ACP.ViewModels
|
|||||||
_regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
|
_regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
|
||||||
IsLeftDrawerOpen = false;
|
IsLeftDrawerOpen = false;
|
||||||
break;
|
break;
|
||||||
case "台架移动界面":
|
|
||||||
if (_globalInfo.CurrentScope != "default") return;
|
|
||||||
_regionManager.RequestNavigate("ShellViewManager", "BenchMovementView");
|
|
||||||
break;
|
|
||||||
case "曲线回读界面":
|
case "曲线回读界面":
|
||||||
if (_globalInfo.CurrentScope != "default") return;
|
if (_globalInfo.CurrentScope != "default") return;
|
||||||
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
|
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
|
||||||
|
|||||||
@@ -28,6 +28,32 @@
|
|||||||
<converter:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
<converter:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||||
<converter:TimeSpanToStringConverter x:Key="TimeSpanConverter" />
|
<converter:TimeSpanToStringConverter x:Key="TimeSpanConverter" />
|
||||||
</Window.Resources>
|
</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"
|
<materialDesign:DrawerHost x:Name="MainDrawerHost"
|
||||||
IsLeftDrawerOpen="{Binding IsLeftDrawerOpen, Mode=TwoWay}">
|
IsLeftDrawerOpen="{Binding IsLeftDrawerOpen, Mode=TwoWay}">
|
||||||
|
|
||||||
@@ -61,11 +87,7 @@
|
|||||||
Style="{StaticResource MaterialDesignFlatButton}"
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
Margin="8" />
|
Margin="8" />
|
||||||
|
|
||||||
<Button Content="台架移动界面"
|
|
||||||
Command="{Binding NavigateCommand}"
|
|
||||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
|
||||||
Style="{StaticResource MaterialDesignFlatButton}"
|
|
||||||
Margin="8" />
|
|
||||||
<Button Content="曲线回读界面"
|
<Button Content="曲线回读界面"
|
||||||
Command="{Binding NavigateCommand}"
|
Command="{Binding NavigateCommand}"
|
||||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||||
@@ -227,7 +249,7 @@ Command="{Binding GetFileStringCommand}">
|
|||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="切换九宫格"
|
<MenuItem Header="切换初始主界面"
|
||||||
FontSize="13"
|
FontSize="13"
|
||||||
Height="50"
|
Height="50"
|
||||||
Foreground="White"
|
Foreground="White"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ncalc" Version="1.3.8" />
|
<PackageReference Include="ncalc" Version="1.3.8" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
|
||||||
<PackageReference Include="System.IO.Ports" Version="11.0.0-preview.4.26230.115" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
using CurveModule.Views;
|
using CurveModule.Views;
|
||||||
|
using CurveModule.Views.Dialogs;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace CurveModule
|
namespace CurveModule
|
||||||
@@ -14,6 +15,7 @@ namespace CurveModule
|
|||||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||||
{
|
{
|
||||||
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
|
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
|
||||||
|
containerRegistry.RegisterForNavigation<CurveStatisticsView>("CurveStatistics");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Logger;
|
using Logger;
|
||||||
using OxyPlot;
|
using OxyPlot;
|
||||||
|
using OxyPlot.Annotations;
|
||||||
using OxyPlot.Axes;
|
using OxyPlot.Axes;
|
||||||
using OxyPlot.Legends;
|
using OxyPlot.Legends;
|
||||||
using OxyPlot.Series;
|
using OxyPlot.Series;
|
||||||
@@ -11,7 +12,9 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using CurveModule.ViewModels.Dialogs;
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
|
||||||
namespace CurveModule.ViewModels
|
namespace CurveModule.ViewModels
|
||||||
{
|
{
|
||||||
@@ -20,6 +23,9 @@ namespace CurveModule.ViewModels
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||||
{
|
{
|
||||||
|
#region 私有字段
|
||||||
|
private GlobalConfig _globalConfig;
|
||||||
|
#endregion
|
||||||
#region 属性
|
#region 属性
|
||||||
public bool KeepAlive => true;
|
public bool KeepAlive => true;
|
||||||
|
|
||||||
@@ -47,6 +53,11 @@ namespace CurveModule.ViewModels
|
|||||||
public ICommand LoadCsvCommand { get; }
|
public ICommand LoadCsvCommand { get; }
|
||||||
public ICommand ResetViewCommand { get; }
|
public ICommand ResetViewCommand { get; }
|
||||||
public ICommand ClearCommand { get; }
|
public ICommand ClearCommand { get; }
|
||||||
|
public ICommand StatisticsCommand { get; }
|
||||||
|
public ICommand SaveCommand { get; }
|
||||||
|
public ICommand LoadCommand { get; }
|
||||||
|
public ICommand CaptureCommand { get; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 私有字段
|
#region 私有字段
|
||||||
@@ -58,39 +69,84 @@ namespace CurveModule.ViewModels
|
|||||||
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
|
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
|
||||||
};
|
};
|
||||||
private int _colorIndex;
|
private int _colorIndex;
|
||||||
|
|
||||||
|
// 双光标截取
|
||||||
|
private LineAnnotation? _cursor1;
|
||||||
|
private LineAnnotation? _cursor2;
|
||||||
|
private LineAnnotation? _draggingCursor;
|
||||||
|
private List<MonitorRecord> _allRecords = new();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>截取按钮文本(随状态切换)</summary>
|
||||||
|
private string _captureButtonText = "开始截取图形";
|
||||||
|
public string CaptureButtonText
|
||||||
|
{
|
||||||
|
get => _captureButtonText;
|
||||||
|
set => SetProperty(ref _captureButtonText, value);
|
||||||
|
}
|
||||||
|
|
||||||
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
{
|
{
|
||||||
|
_globalConfig = containerProvider.Resolve<GlobalConfig>();
|
||||||
Plot = BuildEmptyPlot();
|
Plot = BuildEmptyPlot();
|
||||||
LoadCsvCommand = new DelegateCommand(OnLoadCsv);
|
LoadCsvCommand = new DelegateCommand<string>(OnLoadCsv);
|
||||||
ResetViewCommand = new DelegateCommand(OnResetView);
|
ResetViewCommand = new DelegateCommand(OnResetView);
|
||||||
ClearCommand = new DelegateCommand(OnClear);
|
ClearCommand = new DelegateCommand(OnClear);
|
||||||
|
StatisticsCommand = new DelegateCommand(OnStatistics);
|
||||||
|
SaveCommand = new DelegateCommand(OnSave);
|
||||||
|
LoadCommand = new DelegateCommand(Onload);
|
||||||
|
CaptureCommand = new DelegateCommand(OnToggleCapture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#region 命令处理
|
#region 命令处理
|
||||||
|
private void OnSave()
|
||||||
|
{
|
||||||
|
ConfigService.SaveGlobalConfig(_globalConfig);
|
||||||
|
}
|
||||||
|
private void Onload()
|
||||||
|
{
|
||||||
|
if (File.Exists(_globalConfig.DefaultCSVFilePath))
|
||||||
|
{
|
||||||
|
OnLoadCsv(_globalConfig.DefaultCSVFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打开 CSV 文件对话框,解析数据并按 (MonitorName, Scope) 分组绘制曲线。
|
/// 打开 CSV 文件对话框,解析数据并按 (MonitorName, Scope) 分组绘制曲线。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnLoadCsv()
|
|
||||||
|
private void OnLoadCsv(string filename)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var dlg = new Microsoft.Win32.OpenFileDialog
|
// 1. 统一处理 filename
|
||||||
|
if (string.IsNullOrEmpty(filename))
|
||||||
{
|
{
|
||||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||||
Title = "选择要加载的监测数据 CSV 文件"
|
{
|
||||||
};
|
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||||
if (dlg.ShowDialog() != true) return;
|
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)
|
if (records.Count == 0)
|
||||||
{
|
{
|
||||||
StatusMessage = "CSV 文件为空或格式不正确";
|
StatusMessage = "CSV 文件为空或格式不正确";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按 (MonitorName, Scope) 分组
|
// 3. 按 (MonitorName, Scope) 分组
|
||||||
var groups = records.GroupBy(r => (r.MonitorName, r.Scope))
|
var groups = records.GroupBy(r => (r.MonitorName, r.Scope))
|
||||||
.OrderBy(g => g.Key.MonitorName)
|
.OrderBy(g => g.Key.MonitorName)
|
||||||
.ThenBy(g => g.Key.Scope);
|
.ThenBy(g => g.Key.Scope);
|
||||||
@@ -138,8 +194,10 @@ namespace CurveModule.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
Plot.InvalidatePlot(true);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -157,10 +215,43 @@ namespace CurveModule.ViewModels
|
|||||||
|
|
||||||
private void OnClear()
|
private void OnClear()
|
||||||
{
|
{
|
||||||
|
RemoveCursors();
|
||||||
|
CaptureButtonText = "开始截取图形";
|
||||||
ClearPlotAndChannels();
|
ClearPlotAndChannels();
|
||||||
|
_allRecords.Clear();
|
||||||
Plot.InvalidatePlot(true);
|
Plot.InvalidatePlot(true);
|
||||||
StatusMessage = "已清空,请重新加载 CSV 文件";
|
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
|
#endregion
|
||||||
|
|
||||||
#region CSV 解析
|
#region CSV 解析
|
||||||
@@ -302,6 +393,79 @@ namespace CurveModule.ViewModels
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 辅助方法
|
#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)
|
private void OnChannelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName != nameof(CurveRecallChannelVM.IsDisplayed)) return;
|
if (e.PropertyName != nameof(CurveRecallChannelVM.IsDisplayed)) return;
|
||||||
@@ -334,6 +498,194 @@ namespace CurveModule.ViewModels
|
|||||||
Channels.Clear();
|
Channels.Clear();
|
||||||
_colorIndex = 0;
|
_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
|
#endregion
|
||||||
|
|
||||||
#region 生命周期
|
#region 生命周期
|
||||||
@@ -343,6 +695,7 @@ namespace CurveModule.ViewModels
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
RemoveCursors();
|
||||||
ClearPlotAndChannels();
|
ClearPlotAndChannels();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
95
CurveModule/ViewModels/Dialogs/CurveStatisticsViewModel.cs
Normal file
95
CurveModule/ViewModels/Dialogs/CurveStatisticsViewModel.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,18 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:prism="http://prismlibrary.com/"
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
xmlns:oxy="http://oxyplot.org/wpf"
|
xmlns:oxy="http://oxyplot.org/wpf"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="700"
|
d:DesignHeight="700"
|
||||||
d:DesignWidth="1200">
|
d:DesignWidth="1200">
|
||||||
|
<i:Interaction.Triggers>
|
||||||
|
<i:EventTrigger EventName="Loaded">
|
||||||
|
<i:InvokeCommandAction Command="{Binding LoadCommand}"/>
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
@@ -46,6 +52,19 @@
|
|||||||
Command="{Binding ClearCommand}"
|
Command="{Binding ClearCommand}"
|
||||||
Padding="12,4" Margin="6,0,0,0"
|
Padding="12,4" Margin="6,0,0,0"
|
||||||
ToolTip="清空已加载的曲线数据"/>
|
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>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -140,6 +159,7 @@
|
|||||||
BorderBrush="#DDD" BorderThickness="1"
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
CornerRadius="4">
|
CornerRadius="4">
|
||||||
<oxy:PlotView Model="{Binding Plot}"
|
<oxy:PlotView Model="{Binding Plot}"
|
||||||
|
x:Name="Chart"
|
||||||
Background="Transparent"/>
|
Background="Transparent"/>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -24,5 +24,32 @@ namespace CurveModule.Views
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
89
CurveModule/Views/Dialogs/CurveStatisticsView.xaml
Normal file
89
CurveModule/Views/Dialogs/CurveStatisticsView.xaml
Normal 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>
|
||||||
12
CurveModule/Views/Dialogs/CurveStatisticsView.xaml.cs
Normal file
12
CurveModule/Views/Dialogs/CurveStatisticsView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace CurveModule.Views.Dialogs
|
||||||
|
{
|
||||||
|
public partial class CurveStatisticsView : UserControl
|
||||||
|
{
|
||||||
|
public CurveStatisticsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -223,5 +223,59 @@ namespace DeviceCommand.Base
|
|||||||
}
|
}
|
||||||
commLock?.Dispose();
|
commLock?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在 Serial_Port 类中添加以下方法:
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发送原始二进制数据帧(无分隔符,用于IT6720等自定义二进制协议)
|
||||||
|
/// </summary>
|
||||||
|
public async Task WriteBytesAsync(byte[] data, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||||
|
await _serialPort.BaseStream.WriteAsync(data, 0, data.Length, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取指定长度的原始二进制数据帧(无分隔符)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<byte[]> ReadBytesAsync(int count, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||||
|
if (count <= 0) return Array.Empty<byte>();
|
||||||
|
|
||||||
|
byte[] buffer = new byte[count];
|
||||||
|
int offset = 0;
|
||||||
|
while (offset < count)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
int read = await _serialPort.BaseStream.ReadAsync(buffer, offset, count - offset, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (read == 0) throw new EndOfStreamException("串口读取数据流意外终止");
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
ClearHardwareBuffer();
|
||||||
|
throw new TimeoutException($"串口读取 {count} 字节二进制数据超时 ({ReadTimeout} ms)", ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
22
DeviceCommand/Devices/ANEVH80.cs
Normal file
22
DeviceCommand/Devices/ANEVH80.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 水冷机 一对一
|
||||||
|
/// </summary>
|
||||||
|
[ACPCommand]
|
||||||
|
public class ANEVH80 : Tcp
|
||||||
|
{
|
||||||
|
public ANEVH80(TcpConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
207
DeviceCommand/Devices/Chroma61800.cs
Normal file
207
DeviceCommand/Devices/Chroma61800.cs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 交流源 一拖三
|
||||||
|
/// </summary>
|
||||||
|
[ACPCommand]
|
||||||
|
public class Chroma61800 : Tcp
|
||||||
|
{
|
||||||
|
// Chroma SCPI 默认使用 \n 作为结束符
|
||||||
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
|
public Chroma61800(TcpConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 1. 基础系统控制
|
||||||
|
|
||||||
|
public virtual async Task 清除错误队列(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 2. LIST 模式初始化
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化 LIST 模式的三相独立编辑环境
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="loopCount">循环次数,0代表无限循环</param>
|
||||||
|
public virtual async Task 初始化三相List模式(int loopCount = 0, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"INST:PHAS THRE{ScpiDelimiter}", ct); // 选择三相模式
|
||||||
|
await SendAsync($"INST:EDIT EACH{ScpiDelimiter}", ct); // 选择编辑电压方式为分别编辑
|
||||||
|
await SendAsync($"LIST:COUN {loopCount}{ScpiDelimiter}", ct); // 循环次数设定
|
||||||
|
await SendAsync($"LIST:TRIG AUTO{ScpiDelimiter}", ct); // 设定触发方式
|
||||||
|
await SendAsync($"LIST:BASE TIME{ScpiDelimiter}", ct); // 选择执行时间类别
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 3. LIST 步阶参数配置 (独立发送版 - 三参数固定版)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换目标相位并设置波形为正弦波
|
||||||
|
/// 建议在发送具体 LIST 参数前调用一次即可
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="phase">相位 (1: L1, 2: L2, 3: L3)</param>
|
||||||
|
public virtual async Task 切换相位Async(int phase, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (phase < 1 || phase > 3)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(phase), "相位必须为 1, 2, 或 3");
|
||||||
|
|
||||||
|
await SendAsync($"INST:NSEL {phase}{ScpiDelimiter}", ct);
|
||||||
|
await SendAsync($"FUNC:SHAP:A SINE{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步起始角度
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List起始角度Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:DEGR {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步交流起始电压
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List交流起始电压Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:VOLT:AC:STAR {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步交流结束电压
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List交流结束电压Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:VOLT:AC:END {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步直流起始电压
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List直流起始电压Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:VOLT:DC:STAR {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步直流结束电压
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List直流结束电压Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:VOLT:DC:END {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步起始频率
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List起始频率Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:FREQ:STAR {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步结束频率
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List结束频率Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:FREQ:END {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 配置 LIST 各步执行时间 (单位: 毫秒)
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 配置List执行时间Async(double step1, double step2, double step3, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string strValues = JoinParameters(step1, step2, step3);
|
||||||
|
await SendAsync($"LIST:DWEL {strValues}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 辅助方法:将三个 double 值转换为逗号分隔的字符串,确保小数点格式正确
|
||||||
|
/// </summary>
|
||||||
|
private string JoinParameters(double val1, double val2, double val3)
|
||||||
|
{
|
||||||
|
return $"{val1.ToString("0.###", CultureInfo.InvariantCulture)}," +
|
||||||
|
$"{val2.ToString("0.###", CultureInfo.InvariantCulture)}," +
|
||||||
|
$"{val3.ToString("0.###", CultureInfo.InvariantCulture)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 4. LIST 触发与状态监控
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 切换到 LIST 模式并触发输出
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 启动List输出(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"OUTP:MODE LIST{ScpiDelimiter}", ct);
|
||||||
|
await SendAsync($"TRIG ON{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询 LIST 序列是否正在运行
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<bool> 查询List是否运行中(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string state = await WriteReadAsync($"TRIG:STATE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
state = state?.Trim().ToUpper();
|
||||||
|
return state == "ON" || state == "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 阻塞异步等待直到 LIST 序列执行完毕 (带安全超时保护)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeoutMs">最大等待超时时间(建议比实际LIST总时长多5-10秒)</param>
|
||||||
|
public virtual async Task 等待List执行结束(int timeoutMs = 60000, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 给仪器留出响应触发的时间
|
||||||
|
await Task.Delay(500, ct);
|
||||||
|
|
||||||
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
bool isRunning = true;
|
||||||
|
|
||||||
|
while (isRunning)
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
if (sw.ElapsedMilliseconds > timeoutMs)
|
||||||
|
{
|
||||||
|
throw new TimeoutException($"等待 LIST 执行结束超时 ({timeoutMs} ms)。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取当前状态
|
||||||
|
isRunning = await 查询List是否运行中(ct);
|
||||||
|
|
||||||
|
if (isRunning)
|
||||||
|
{
|
||||||
|
// 还在运行,适当休眠后继续轮询,避免总线阻塞
|
||||||
|
await Task.Delay(500, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace DeviceCommand.Devices
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 信号源 一拖三
|
||||||
|
/// </summary>
|
||||||
#region 枚举定义
|
#region 枚举定义
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace DeviceCommand.Devices
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IO板卡 一对一由于板卡映射不完全一样采用IOGroup
|
||||||
|
/// </summary>
|
||||||
//[ACPCommand]
|
//[ACPCommand]
|
||||||
public class IOBoard : ModbusTcp
|
public class IOBoard : ModbusTcp
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,14 +11,26 @@ using System.ComponentModel;
|
|||||||
namespace DeviceCommand.Devices
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 系统控制功能类型枚举 (中文)
|
/// 系统控制功能类型枚举 (根据三工位 ACP接线图 更新)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum 功能动作 : byte
|
public enum 功能动作 : byte
|
||||||
{
|
{
|
||||||
单相充电,
|
产品充电三相,
|
||||||
抛负载,
|
产品放电三相,
|
||||||
短路测试,
|
KL30通断,
|
||||||
蜂鸣器报警
|
KL15通断,
|
||||||
|
电子锁60欧电阻,
|
||||||
|
温度检测1K欧电阻,
|
||||||
|
CC信号串1K欧电阻,
|
||||||
|
CP信号串1K欧电阻,
|
||||||
|
CC信号串680欧电阻,
|
||||||
|
CC信号串220欧电阻,
|
||||||
|
CC信号串100欧电阻,
|
||||||
|
CC信号空载,
|
||||||
|
蜂鸣器报警,
|
||||||
|
抛负载通断, // (接线图中标注于 Y14, KM8)
|
||||||
|
短路测试HV, // (接线图中标注于 Y15, KM9)
|
||||||
|
短路测试LV // (接线图中标注于 Y16, KM10)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -26,7 +38,7 @@ namespace DeviceCommand.Devices
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public struct IoPortInfo
|
public struct IoPortInfo
|
||||||
{
|
{
|
||||||
public int ModuleIndex { get; set; } // IO板卡/模块序号 (1 或 2)
|
public int ModuleIndex { get; set; } // IO板卡/模块序号 (1、2 或 3)
|
||||||
public ushort Address { get; set; } // 转换为 Modbus 的线圈相对偏移地址 (Y1->0, Y2->1)
|
public ushort Address { get; set; } // 转换为 Modbus 的线圈相对偏移地址 (Y1->0, Y2->1)
|
||||||
|
|
||||||
public IoPortInfo(int moduleIndex, ushort address)
|
public IoPortInfo(int moduleIndex, ushort address)
|
||||||
@@ -39,79 +51,89 @@ namespace DeviceCommand.Devices
|
|||||||
[ACPCommand]
|
[ACPCommand]
|
||||||
public class IOBoardGroup
|
public class IOBoardGroup
|
||||||
{
|
{
|
||||||
// 声明底层的 2 个 IO 板卡实例
|
// 声明底层的 3 个 IO 板卡实例(对应三工位系统)
|
||||||
public IOBoard Board1 { get; private set; }
|
public IOBoard Board1 { get; private set; }
|
||||||
public IOBoard Board2 { get; private set; }
|
public IOBoard Board2 { get; private set; }
|
||||||
|
public IOBoard Board3 { get; private set; }
|
||||||
|
|
||||||
private readonly byte _slaveAddress = 1; // 默认 Modbus 从站号为 1
|
private readonly byte _slaveAddress = 1; // 默认 Modbus 从站号为 1
|
||||||
|
|
||||||
// 核心:严格对齐图片数据的双层嵌套配置字典 (2个模块,1-8台架)
|
// 核心:根据 ACP硬件接线图 严格对齐的数据字典 (3个模块独立对应1-3工位)
|
||||||
private static readonly Dictionary<int, Dictionary<功能动作, IoPortInfo>> IoMapping
|
private static readonly Dictionary<int, Dictionary<功能动作, IoPortInfo>> IoMapping
|
||||||
= new Dictionary<int, Dictionary<功能动作, IoPortInfo>>()
|
= new Dictionary<int, Dictionary<功能动作, IoPortInfo>>()
|
||||||
{
|
{
|
||||||
|
// 1# 工位 -> IO继电器模块 1#
|
||||||
{ 1, new Dictionary<功能动作, IoPortInfo> {
|
{ 1, new Dictionary<功能动作, IoPortInfo> {
|
||||||
{ 功能动作.单相充电, new IoPortInfo(1, 5) }, // Y6 -> 5
|
{ 功能动作.产品充电三相, new IoPortInfo(1, 0) }, // Y1 -> 0 (KM2)
|
||||||
{ 功能动作.抛负载, new IoPortInfo(1, 0) }, // Y1 -> 0
|
{ 功能动作.产品放电三相, new IoPortInfo(1, 1) }, // Y2 -> 1 (KM3)
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
{ 功能动作.KL30通断, new IoPortInfo(1, 2) }, // Y3 -> 2
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 15) } // Y9 -> 8
|
{ 功能动作.KL15通断, new IoPortInfo(1, 3) }, // Y4 -> 3
|
||||||
|
{ 功能动作.电子锁60欧电阻, new IoPortInfo(1, 4) }, // Y5 -> 4
|
||||||
|
{ 功能动作.温度检测1K欧电阻, new IoPortInfo(1, 5) }, // Y6 -> 5
|
||||||
|
{ 功能动作.CC信号串1K欧电阻, new IoPortInfo(1, 6) }, // Y7 -> 6
|
||||||
|
{ 功能动作.CP信号串1K欧电阻, new IoPortInfo(1, 7) }, // Y8 -> 7
|
||||||
|
{ 功能动作.CC信号串680欧电阻, new IoPortInfo(1, 8) }, // Y9 -> 8
|
||||||
|
{ 功能动作.CC信号串220欧电阻, new IoPortInfo(1, 9) }, // Y10 -> 9
|
||||||
|
{ 功能动作.CC信号串100欧电阻, new IoPortInfo(1, 10) }, // Y11 -> 10
|
||||||
|
{ 功能动作.CC信号空载, new IoPortInfo(1, 11) }, // Y12 -> 11
|
||||||
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(1, 12) }, // Y13 -> 12
|
||||||
|
{ 功能动作.抛负载通断, new IoPortInfo(3, 13) }, // Y14 -> 13 (KM8)
|
||||||
|
{ 功能动作.短路测试HV, new IoPortInfo(3, 14) }, // Y15 -> 14 (KM9)
|
||||||
|
{ 功能动作.短路测试LV, new IoPortInfo(3, 15) } // Y16 -> 15 (KM10)
|
||||||
}},
|
}},
|
||||||
|
// 2# 工位 -> IO继电器模块 2#
|
||||||
{ 2, new Dictionary<功能动作, IoPortInfo> {
|
{ 2, new Dictionary<功能动作, IoPortInfo> {
|
||||||
{ 功能动作.单相充电, new IoPortInfo(1, 6) }, // Y7 -> 6
|
{ 功能动作.产品充电三相, new IoPortInfo(2, 0) }, // Y1 -> 0 (KM4)
|
||||||
{ 功能动作.抛负载, new IoPortInfo(1, 2) }, // Y3 -> 2
|
{ 功能动作.产品放电三相, new IoPortInfo(2, 1) }, // Y2 -> 1 (KM5)
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
{ 功能动作.KL30通断, new IoPortInfo(2, 2) }, // Y3 -> 2
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 14) } // Y10 -> 9
|
{ 功能动作.KL15通断, new IoPortInfo(2, 3) }, // Y4 -> 3
|
||||||
|
{ 功能动作.电子锁60欧电阻, new IoPortInfo(2, 4) }, // Y5 -> 4
|
||||||
|
{ 功能动作.温度检测1K欧电阻, new IoPortInfo(2, 5) }, // Y6 -> 5
|
||||||
|
{ 功能动作.CC信号串1K欧电阻, new IoPortInfo(2, 6) }, // Y7 -> 6
|
||||||
|
{ 功能动作.CP信号串1K欧电阻, new IoPortInfo(2, 7) }, // Y8 -> 7
|
||||||
|
{ 功能动作.CC信号串680欧电阻, new IoPortInfo(2, 8) }, // Y9 -> 8
|
||||||
|
{ 功能动作.CC信号串220欧电阻, new IoPortInfo(2, 9) }, // Y10 -> 9
|
||||||
|
{ 功能动作.CC信号串100欧电阻, new IoPortInfo(2, 10) }, // Y11 -> 10
|
||||||
|
{ 功能动作.CC信号空载, new IoPortInfo(2, 11) }, // Y12 -> 11
|
||||||
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 12) }, // Y13 -> 12
|
||||||
|
{ 功能动作.抛负载通断, new IoPortInfo(3, 13) }, // Y14 -> 13 (KM8)
|
||||||
|
{ 功能动作.短路测试HV, new IoPortInfo(3, 14) }, // Y15 -> 14 (KM9)
|
||||||
|
{ 功能动作.短路测试LV, new IoPortInfo(3, 15) } // Y16 -> 15 (KM10)
|
||||||
}},
|
}},
|
||||||
|
// 3# 工位 -> IO继电器模块 3# (包含额外的抛负载及短路测试控制)
|
||||||
{ 3, new Dictionary<功能动作, IoPortInfo> {
|
{ 3, new Dictionary<功能动作, IoPortInfo> {
|
||||||
{ 功能动作.单相充电, new IoPortInfo(1, 7) }, // Y8 -> 7
|
{ 功能动作.产品充电三相, new IoPortInfo(3, 0) }, // Y1 -> 0 (KM6)
|
||||||
{ 功能动作.抛负载, new IoPortInfo(1, 3) }, // Y4 -> 3
|
{ 功能动作.产品放电三相, new IoPortInfo(3, 1) }, // Y2 -> 1 (KM7)
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
{ 功能动作.KL30通断, new IoPortInfo(3, 2) }, // Y3 -> 2
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 13) } // Y11 -> 10
|
{ 功能动作.KL15通断, new IoPortInfo(3, 3) }, // Y4 -> 3
|
||||||
}},
|
{ 功能动作.电子锁60欧电阻, new IoPortInfo(3, 4) }, // Y5 -> 4
|
||||||
{ 4, new Dictionary<功能动作, IoPortInfo> {
|
{ 功能动作.温度检测1K欧电阻, new IoPortInfo(3, 5) }, // Y6 -> 5
|
||||||
{ 功能动作.单相充电, new IoPortInfo(1, 8) }, // Y9 -> 8
|
{ 功能动作.CC信号串1K欧电阻, new IoPortInfo(3, 6) }, // Y7 -> 6
|
||||||
{ 功能动作.抛负载, new IoPortInfo(1, 4) }, // Y5 -> 4
|
{ 功能动作.CP信号串1K欧电阻, new IoPortInfo(3, 7) }, // Y8 -> 7
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
{ 功能动作.CC信号串680欧电阻, new IoPortInfo(3, 8) }, // Y9 -> 8
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 12) } // Y12 -> 11
|
{ 功能动作.CC信号串220欧电阻, new IoPortInfo(3, 9) }, // Y10 -> 9
|
||||||
}},
|
{ 功能动作.CC信号串100欧电阻, new IoPortInfo(3, 10) }, // Y11 -> 10
|
||||||
{ 5, new Dictionary<功能动作, IoPortInfo> {
|
{ 功能动作.CC信号空载, new IoPortInfo(3, 11) }, // Y12 -> 11
|
||||||
{ 功能动作.单相充电, new IoPortInfo(2, 4) }, // Y5 -> 4
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(3, 12) }, // Y13 -> 12
|
||||||
{ 功能动作.抛负载, new IoPortInfo(2, 0) }, // Y1 -> 0
|
{ 功能动作.抛负载通断, new IoPortInfo(3, 13) }, // Y14 -> 13 (KM8)
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
{ 功能动作.短路测试HV, new IoPortInfo(3, 14) }, // Y15 -> 14 (KM9)
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 11) } // Y13 -> 12
|
{ 功能动作.短路测试LV, new IoPortInfo(3, 15) } // Y16 -> 15 (KM10)
|
||||||
}},
|
|
||||||
{ 6, new Dictionary<功能动作, IoPortInfo> {
|
|
||||||
{ 功能动作.单相充电, new IoPortInfo(2, 5) }, // Y6 -> 5
|
|
||||||
{ 功能动作.抛负载, new IoPortInfo(2, 1) }, // Y2 -> 1
|
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 10) } // Y14 -> 13
|
|
||||||
}},
|
|
||||||
{ 7, new Dictionary<功能动作, IoPortInfo> {
|
|
||||||
{ 功能动作.单相充电, new IoPortInfo(2, 6) }, // Y7 -> 6
|
|
||||||
{ 功能动作.抛负载, new IoPortInfo(2, 2) }, // Y3 -> 2
|
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 9) } // Y15 -> 14
|
|
||||||
}},
|
|
||||||
{ 8, new Dictionary<功能动作, IoPortInfo> {
|
|
||||||
{ 功能动作.单相充电, new IoPortInfo(2, 7) }, // Y8 -> 7
|
|
||||||
{ 功能动作.抛负载, new IoPortInfo(2, 3) }, // Y4 -> 3
|
|
||||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
|
||||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 8) } // Y16 -> 15
|
|
||||||
}}
|
}}
|
||||||
};
|
};
|
||||||
|
|
||||||
public IOBoardGroup(IOBoard board1, IOBoard board2)
|
// 修改构造函数以支持 3 个板块
|
||||||
|
public IOBoardGroup(IOBoard board1, IOBoard board2, IOBoard board3)
|
||||||
{
|
{
|
||||||
Board1 = board1 ?? throw new ArgumentNullException(nameof(board1), "IOBoard Board1 实例不能为空。");
|
Board1 = board1 ?? throw new ArgumentNullException(nameof(board1), "IOBoard Board1 实例不能为空。");
|
||||||
Board2 = board2 ?? throw new ArgumentNullException(nameof(board2), "IOBoard Board2 实例不能为空。");
|
Board2 = board2 ?? throw new ArgumentNullException(nameof(board2), "IOBoard Board2 实例不能为空。");
|
||||||
|
Board3 = board3 ?? throw new ArgumentNullException(nameof(board3), "IOBoard Board3 实例不能为空。");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 执行指定台架的功能动作控制(带高低电平开关及底层安全互锁)
|
/// 执行指定台架的功能动作控制(带高低电平开关及底层安全互锁)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="台架序号">台架/工位序号 (1 - 8)</param>
|
/// <param name="台架序号">台架/工位序号 (1 - 3)</param>
|
||||||
/// <param name="动作">控制功能类型 (单相充电, 抛负载, 短路测试, 蜂鸣器报警)</param>
|
/// <param name="动作">控制功能类型</param>
|
||||||
/// <param name="开关状态">开关状态 (true: 开启吸合, false: 关闭断开)</param>
|
/// <param name="开关状态">开关状态 (true: 开启吸合, false: 关闭断开)</param>
|
||||||
/// <param name="取消令牌">异步取消令牌</param>
|
/// <param name="取消令牌">异步取消令牌</param>
|
||||||
public async Task 执行台架动作Async(int 台架序号, 功能动作 动作, bool 开关状态, CancellationToken 取消令牌 = default)
|
public async Task 执行台架动作Async(int 台架序号, 功能动作 动作, bool 开关状态, CancellationToken 取消令牌 = default)
|
||||||
@@ -119,21 +141,25 @@ namespace DeviceCommand.Devices
|
|||||||
// 1. 根据传入的台架序号和动作检索字典映射关系
|
// 1. 根据传入的台架序号和动作检索字典映射关系
|
||||||
if (!IoMapping.TryGetValue(台架序号, out var 动作字典) || !动作字典.TryGetValue(动作, out var io信息))
|
if (!IoMapping.TryGetValue(台架序号, out var 动作字典) || !动作字典.TryGetValue(动作, out var io信息))
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"未找到台架 [{台架序号}] 对应功能 [{动作}] 的 IO 点位映射配置。");
|
throw new ArgumentException($"未找到台架 [{台架序号}] 对应功能 [{动作}] 的 IO 点位映射配置。请注意抛负载及短路测试仅存在于 3# 工位。");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 软件防错高危互锁逻辑
|
// 2. 软件防错高危互锁逻辑:防止同时执行充电和放电
|
||||||
if (开关状态)
|
if (开关状态)
|
||||||
{
|
{
|
||||||
if (动作 == 功能动作.单相充电)
|
if (动作 == 功能动作.产品充电三相)
|
||||||
{
|
{
|
||||||
var 短路点位 = IoMapping[台架序号][功能动作.短路测试];
|
if (IoMapping[台架序号].TryGetValue(功能动作.产品放电三相, out var 放电点位))
|
||||||
await GetBoardInstance(短路点位.ModuleIndex).写输出开关(_slaveAddress, 短路点位.Address, false);
|
{
|
||||||
|
await GetBoardInstance(放电点位.ModuleIndex).写输出开关(_slaveAddress, 放电点位.Address, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (动作 == 功能动作.短路测试)
|
else if (动作 == 功能动作.产品放电三相)
|
||||||
{
|
{
|
||||||
var 充电点位 = IoMapping[台架序号][功能动作.单相充电];
|
if (IoMapping[台架序号].TryGetValue(功能动作.产品充电三相, out var 充电点位))
|
||||||
await GetBoardInstance(充电点位.ModuleIndex).写输出开关(_slaveAddress, 充电点位.Address, false);
|
{
|
||||||
|
await GetBoardInstance(充电点位.ModuleIndex).写输出开关(_slaveAddress, 充电点位.Address, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +177,8 @@ namespace DeviceCommand.Devices
|
|||||||
{
|
{
|
||||||
1 => Board1,
|
1 => Board1,
|
||||||
2 => Board2,
|
2 => Board2,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(moduleIndex), "非法的板卡序号,系统仅支持 1# 和 2# 模块。")
|
3 => Board3,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(moduleIndex), "非法的板卡序号,系统仅支持 1#、2# 和 3# 模块。")
|
||||||
};
|
};
|
||||||
|
|
||||||
return board ?? throw new InvalidOperationException($"IO板卡模块 [{moduleIndex}#] 尚未完成初始化,无法建立总线通信。");
|
return board ?? throw new InvalidOperationException($"IO板卡模块 [{moduleIndex}#] 尚未完成初始化,无法建立总线通信。");
|
||||||
|
|||||||
@@ -1,331 +0,0 @@
|
|||||||
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>
|
|
||||||
/// IT6000C 优先工作模式 (CV优先 / CC优先)
|
|
||||||
/// </summary>
|
|
||||||
public enum IT6000CPriorityMode
|
|
||||||
{
|
|
||||||
/// <summary> 恒压优先模式 (CV优先) </summary>
|
|
||||||
VOLTage,
|
|
||||||
/// <summary> 恒流优先模式 (CC优先) </summary>
|
|
||||||
CURRent
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// IT6000C 瞬变功能模式
|
|
||||||
/// </summary>
|
|
||||||
public enum IT6000CFunctionMode
|
|
||||||
{
|
|
||||||
/// <summary> 固定模式 (普通输出) </summary>
|
|
||||||
FIXed,
|
|
||||||
/// <summary> List 列表模式 </summary>
|
|
||||||
LIST,
|
|
||||||
/// <summary> 电池测试模式 </summary>
|
|
||||||
BATTer,
|
|
||||||
/// <summary> 光伏/太阳能板曲线模拟模式 </summary>
|
|
||||||
SOLar,
|
|
||||||
/// <summary> 汽车波形模拟模式 </summary>
|
|
||||||
CARProfile
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
[ACPCommand]
|
|
||||||
public class IT6000C : Tcp
|
|
||||||
{
|
|
||||||
// SCPI 指令结束符
|
|
||||||
private const string ScpiDelimiter = "\n";
|
|
||||||
|
|
||||||
public IT6000C(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,17 +9,19 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace DeviceCommand.Devices
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 低压电源 一拖三
|
||||||
|
/// </summary>
|
||||||
[ACPCommand]
|
[ACPCommand]
|
||||||
public class IT6720 : Serial_Port // 直接继承您提供的 Serial_Port 基类
|
public class IT6720: Serial_Port // 直接继承您提供的 Serial_Port 基类
|
||||||
{
|
{
|
||||||
// 帧格式:同步头 AA + 地址 + 命令字 + 数据[4-25] + 校验和,共 26 字节
|
// 帧格式:同步头 AA + 地址 + 命令字 + 数据[4-25] + 校验和,共 26 字节
|
||||||
private const byte FrameSyncByte = 0xAA;
|
private const byte FrameSyncByte = 0xAA;
|
||||||
private const int FrameLength = 26;
|
private const int FrameLength = 26;
|
||||||
private readonly byte _deviceAddress; // 电源地址
|
private readonly byte _deviceAddress = 0x00; // 电源地址
|
||||||
|
|
||||||
public IT6720(SerialPortConfig config, byte address = 0x00) : base(config)
|
public IT6720(SerialPortConfig config) : base(config)
|
||||||
{
|
{
|
||||||
_deviceAddress = address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 核心协议封装(不变)
|
#region 核心协议封装(不变)
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
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>
|
|
||||||
/// 电源电气/耦合模式
|
|
||||||
/// </summary>
|
|
||||||
public enum PowerCouplingMode
|
|
||||||
{
|
|
||||||
/// <summary> 纯交流模式 </summary>
|
|
||||||
AC,
|
|
||||||
/// <summary> 纯直流模式 </summary>
|
|
||||||
DC,
|
|
||||||
/// <summary> 交直流混合模式 </summary>
|
|
||||||
ACDC,
|
|
||||||
/// <summary> 直交流混合模式 </summary>
|
|
||||||
DCAC
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设备操作运行角色/模式
|
|
||||||
/// </summary>
|
|
||||||
public enum DeviceOperationMode
|
|
||||||
{
|
|
||||||
/// <summary> 电压源模式(常规源) </summary>
|
|
||||||
VOLTage,
|
|
||||||
/// <summary> 电子负载模式(放电拉载) </summary>
|
|
||||||
LOAD,
|
|
||||||
/// <summary> 电流源模式 </summary>
|
|
||||||
CURRent
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
[ACPCommand]
|
|
||||||
public class IT7800E : Tcp
|
|
||||||
{
|
|
||||||
// 八台产品共用一个,使用换行符 \n (ASCII 0x0A) 作为 SCPI 结束符[cite: 1]
|
|
||||||
private const string ScpiDelimiter = "\n";
|
|
||||||
|
|
||||||
public IT7800E(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]
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 2. 耦合模式与拉载开关控制
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置电源输出源的电气模式/工作耦合模式 (AC, DC, ACDC, DCAC)[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置电源模式(PowerCouplingMode 模式, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
await SendAsync($":SOURce:FUNCtion {模式}{ScpiDelimiter}", ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询电源当前的工作电气模式[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询电源模式(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($":SOURce:FUNCtion?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string 参数 = 开启 ? "ON" : "OFF"; //[cite: 1]
|
|
||||||
await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
|
|
||||||
|
|
||||||
public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter); //[cite: 1]
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 4. 测量与数据回测(高频数据轮询核心)
|
|
||||||
|
|
||||||
[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($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<string> 查询实际频率(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task<string> 查询功率因数(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($":MEASure:POWer:PFACtor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 5. 保护参数设置与系统监控
|
|
||||||
|
|
||||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection:RMS {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection:PEAK {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
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: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($":OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 6. 充放电与能量统计扩展命令
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除最近的安时(Ah)与瓦时(Wh)统计数据[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 清除充放电电量统计(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
await SendAsync($"SENSe:AHOur:RESet{ScpiDelimiter}", ct); //[cite: 1]
|
|
||||||
await SendAsync($"SENSe:WHOur:RESet{ScpiDelimiter}", ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取当前充放电累计的安时值 (单位: Ah)[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询累计安时_Ah(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($"FETCh:AHOur?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取当前充放电累计的瓦时值 (单位: Wh)[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询累计瓦时_Wh(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($"FETCh:WHOur?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取充放电积分持续时间 (单位: 秒)[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询积分时间(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($"FETCh:ENERgy:TIME?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 切换设备的操作模式 (VOLTage: 电源模式, LOAD: 负载模式/放电, CURRent: 电流源)[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置操作模式(DeviceOperationMode 模式, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
await SendAsync($"SYSTem:OPERation:MODE {模式}{ScpiDelimiter}", ct); //[cite: 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置放电截止电压/电压下限 (单位: V,用于防止电池过放)[cite: 1]
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置电压下限(double 电压, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:LIMit:LOW {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
DeviceCommand/Devices/MCc30W.cs
Normal file
20
DeviceCommand/Devices/MCc30W.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 水冷机 一拖三
|
||||||
|
/// </summary>
|
||||||
|
public class MCc30W:ModbusTcp
|
||||||
|
{
|
||||||
|
public MCc30W(TcpConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
244
DeviceCommand/Devices/PW8001.cs
Normal file
244
DeviceCommand/Devices/PW8001.cs
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
using Common;
|
||||||
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Device
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 功率分析仪(型号:HIOKI PW8001) 一拖三
|
||||||
|
/// </summary>
|
||||||
|
|
||||||
|
public class PW8001 : Tcp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 构造函数:SCPI 通信使用端口 23
|
||||||
|
/// </summary>
|
||||||
|
public PW8001(TcpConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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
|
||||||
|
}
|
||||||
|
}
|
||||||
206
DeviceCommand/Devices/RLT1000.cs
Normal file
206
DeviceCommand/Devices/RLT1000.cs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 环境箱(型号:RLT1000) 一拖三
|
||||||
|
/// </summary>
|
||||||
|
[ACPCommand]
|
||||||
|
public class RLT1000 : ModbusTcp
|
||||||
|
{
|
||||||
|
public RLT1000(TcpConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
private const byte SlaveId = 1;
|
||||||
|
|
||||||
|
#region 读取
|
||||||
|
|
||||||
|
public async Task<ushort> 读取状态监控(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 5, 1, ct);
|
||||||
|
return re[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> 读取当前温度(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 10, 2, ct);
|
||||||
|
return ConvertToFloat(re);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> 读取当前湿度(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 16, 2, ct);
|
||||||
|
return ConvertToFloat(re);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> 读取当前水温(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 22, 2, ct);
|
||||||
|
return ConvertToFloat(re);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> 读取当前1号水流量值(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 28, 2, ct);
|
||||||
|
return ConvertToFloat(re);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> 读取当前2号水流量值(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 34, 2, ct);
|
||||||
|
return ConvertToFloat(re);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> 读取当前3号水流量值(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 40, 2, ct);
|
||||||
|
return ConvertToFloat(re);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 读写(控制命令)
|
||||||
|
|
||||||
|
public async Task 切换为本地模式(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 147, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 切换为远程模式(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 147, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 远程模式关机(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 148, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 远程模式开机(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 148, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 远程模式复位结束(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 149, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 远程模式复位(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 149, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 切换为程序模式(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 153, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 切换为定值模式(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 153, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 定值温度设定(float 温度, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var tmp = ConvertFromFloat(温度);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveId, 154, tmp, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 定值湿度设定(float 湿度, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var tmp = ConvertFromFloat(湿度);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveId, 158, tmp, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 定值水温设定(float 水温, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var tmp = ConvertFromFloat(水温);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveId, 162, tmp, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 定值1号水流量设定(float 水流量, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var tmp = ConvertFromFloat(水流量);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveId, 164, tmp, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 定值2号水流量设定(float 水流量, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var tmp = ConvertFromFloat(水流量);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveId, 166, tmp, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 定值3号水流量设定(float 水流量, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var tmp = ConvertFromFloat(水流量);
|
||||||
|
await WriteMultipleRegistersAsync(SlaveId, 168, tmp, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 水温模式不使用(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 1181, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 水温模式使用(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 1181, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 环境箱温度模式不使用(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 1182, 0, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 环境箱温度模式使用(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleRegisterAsync(SlaveId, 1182, 1, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 数据转换助手方法
|
||||||
|
|
||||||
|
private float ConvertToFloat(ushort[] values)
|
||||||
|
{
|
||||||
|
if (values == null || values.Length < 2) return 0f;
|
||||||
|
|
||||||
|
byte[] bytes = new byte[4];
|
||||||
|
bytes[3] = (byte)(values[0] >> 8);
|
||||||
|
bytes[2] = (byte)(values[0] & 0xFF);
|
||||||
|
bytes[1] = (byte)(values[1] >> 8);
|
||||||
|
bytes[0] = (byte)(values[1] & 0xFF);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ushort[] ConvertFromFloat(float value)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
ushort[] result = new ushort[2];
|
||||||
|
result[0] = (ushort)((bytes[3] << 8) | bytes[2]);
|
||||||
|
result[1] = (ushort)((bytes[1] << 8) | bytes[0]);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
213
DeviceCommand/Devices/S7200.cs
Normal file
213
DeviceCommand/Devices/S7200.cs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
// 根据您的项目实际路径保留枚举的引用
|
||||||
|
// using 通讯驱动Base.枚举;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
public enum S7200负载模式_枚举
|
||||||
|
{
|
||||||
|
CC,
|
||||||
|
CV,
|
||||||
|
CP,
|
||||||
|
CR,
|
||||||
|
CVCC,
|
||||||
|
CRCC,
|
||||||
|
CVCR,
|
||||||
|
AUTO
|
||||||
|
}
|
||||||
|
public enum S7200直流源模式_枚举
|
||||||
|
{
|
||||||
|
VOLTage,
|
||||||
|
CURRent,
|
||||||
|
BATTest,
|
||||||
|
BATSim,
|
||||||
|
SOLar,
|
||||||
|
EN50530,
|
||||||
|
LIST,
|
||||||
|
UDW,
|
||||||
|
SQUare,
|
||||||
|
TRIangle,
|
||||||
|
SINusoid,
|
||||||
|
DIN40839_12V,
|
||||||
|
DIN40839_24V,
|
||||||
|
IS016750_SHORTDROP_12V,
|
||||||
|
IS016750_SHORTDROP_24V,
|
||||||
|
IS016750_RESETTEST,
|
||||||
|
IS016750_STARTINGPROFILE_12V_1,
|
||||||
|
IS016750_STARTINGPROFILE_12V_2,
|
||||||
|
IS016750_STARTINGPROFILE_12V_3,
|
||||||
|
IS016750_STARTINGPROFILE_12V_4,
|
||||||
|
IS016750_STARTINGPROFILE_24V_1,
|
||||||
|
IS016750_STARTINGPROFILE_24V_2,
|
||||||
|
IS016750_STARTINGPROFILE_24V_3,
|
||||||
|
IS016750_LOADDUMP_TESTA_12V,
|
||||||
|
IS016750_LOADDUMP_TESTA_24V,
|
||||||
|
IS016750_LOADDUMP_TESTB_12V,
|
||||||
|
IS016750_LOADDUMP_TESTB_24V,
|
||||||
|
LV123_HV_1,
|
||||||
|
LV123_HV_2A,
|
||||||
|
LV123_HV_2B,
|
||||||
|
LV123_HV_3
|
||||||
|
}
|
||||||
|
public enum S7200工作模式_枚举
|
||||||
|
{
|
||||||
|
电源模式,
|
||||||
|
负载模式,
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 高压直流源载一体机 (适配 IT6000C SCPI指令集)
|
||||||
|
/// </summary>
|
||||||
|
[ACPCommand]
|
||||||
|
public class S7200 : Tcp
|
||||||
|
{
|
||||||
|
// SCPI 指令结束符
|
||||||
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
|
#region 设置
|
||||||
|
|
||||||
|
public async Task 设置为远程模式(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"SYSTem:REMote{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置电源工作模式(S7200工作模式_枚举 开关, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// IT6000C 为双向无缝切换,底层硬件无需显式发送源载切换指令
|
||||||
|
// 保留此方法仅为兼容您原有上位机的工步调用流程,直接返回即可
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置直流源工作模式(S7200直流源模式_枚举 模式, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 原指令: MODE {模式} -> 现 IT6000C 指令: FUNCtion {模式} (如 VOLTage / CURRent)[cite: 1]
|
||||||
|
// 若枚举 ToString() 后不匹配,建议在此处加 Switch 转换
|
||||||
|
await SendAsync($"FUNCtion {模式}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置负载工作模式(S7200负载模式_枚举 模式, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// IT6000C 负载控制通过负向边界实现。若是想开启纯电阻模式,可调用 SINK:RESistance:STATe ON[cite: 1]
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置通道开关(bool 开关, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"OUTPut {(开关 ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置CC模式电流(double 电流, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"CURRent {电流}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置CC电压H(double 电流, CancellationToken ct = default) // 沿用原参数名“电流”,实际是设定电压上限
|
||||||
|
{
|
||||||
|
await SendAsync($"VOLTage:LIMit:POSitive {电流}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置CC电压L(double 电流, CancellationToken ct = default) // 沿用原参数名“电流”,实际是设定电压下限
|
||||||
|
{
|
||||||
|
await SendAsync($"VOLTage:LIMit:NEGative {电流}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置CV正向电流(double 电流, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"CURRent:LIMit:POSitive {电流}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置CV反向电流(double 电流, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 提醒: 此处传入的参数如果是正数,建议在外部或此处转换为负值,因为载模式限制需要负数[cite: 1]
|
||||||
|
await SendAsync($"CURRent:LIMit:NEGative {电流}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置正向功率阈值(double 功率, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"POWer:LIMit:POSitive {功率}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置反向功率阈值(double 功率, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 提醒: 此处传入的参数必须为负数[cite: 1]
|
||||||
|
await SendAsync($"POWer:LIMit:NEGative {功率}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"VOLTage {电压}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 清除保护状态(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置OCP开关(bool v, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"CURRent:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置OVP开关(bool v, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"VOLTage:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置电压保护OVP电压(double oVP电压, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"VOLTage:PROTection {oVP电压}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置电流保护OCP电流(double oCP电流, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"CURRent:PROTection {oCP电流}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置功率保护开关(bool v, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"POWer:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 设置功率保护功率(double oPP功率, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"POWer:PROTection {oPP功率}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task 发送自定义命令(string 指令, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"{指令}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 查询
|
||||||
|
|
||||||
|
public async Task<string> 查询设备信息(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> 查询实时电压(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var str = await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
|
return double.TryParse(str, out double result) ? result : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> 查询实时电流(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var str = await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
|
return double.TryParse(str, out double result) ? result : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<double> 查询功率(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var str = await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
|
return double.TryParse(str, out double result) ? result : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
349
DeviceCommand/Devices/TektronixMSO.cs
Normal file
349
DeviceCommand/Devices/TektronixMSO.cs
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 示波器 MSO 4/5/6 系列
|
||||||
|
/// </summary>
|
||||||
|
#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)
|
||||||
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
|
public TektronixMSO(TcpConfig config) : base(config)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 1. 公共命令与状态查询
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 返回仪器的标识代码[cite: 2]
|
||||||
|
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询忙碌状态(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 查询仪器是否处于忙碌状态,常用于同步操作[cite: 2]
|
||||||
|
return await WriteReadAsync($"BUSY?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task 执行自校准(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 指示仪器执行信号路径校准 (SPC)[cite: 2]
|
||||||
|
await SendAsync($"*CAL?{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 2. 采集系统控制 (Acquisition)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动或停止采集
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 设置采集状态(bool 运行, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 启动、停止或返回采集状态[cite: 2]
|
||||||
|
string 参数 = 运行 ? "RUN" : "STOP";
|
||||||
|
await SendAsync($"ACQuire:STATE {参数}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询采集状态(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 启动、停止或返回采集状态[cite: 2]
|
||||||
|
return await WriteReadAsync($"ACQuire:STATE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task 设置采集模式(TekAcquisitionMode 模式, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 设置或查询采集模式[cite: 2]
|
||||||
|
await SendAsync($"ACQuire:MODe {模式}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 3. 水平系统控制 (Horizontal)
|
||||||
|
|
||||||
|
public virtual async Task 设置水平刻度(double 秒每格, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 设置或查询水平刻度 (Scale)[cite: 2]
|
||||||
|
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: 2]
|
||||||
|
await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询采样率(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 查询水平采样率[cite: 2]
|
||||||
|
return await WriteReadAsync($"HORizontal:SAMPLERate?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 4. 通道与显示控制 (Vertical)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全局启用或关闭某通道的显示
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 设置通道显示开关(int 通道号, bool 开启, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 4/5/6 Series MSO 标准通道显示指令[cite: 2]
|
||||||
|
string 参数 = 开启 ? "ON" : "OFF";
|
||||||
|
await SendAsync($"DISplay:GLObal:CH{通道号}:STATE {参数}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置指定通道的垂直缩放比例 (Volts/Div)
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 设置通道垂直刻度(int 通道号, double 伏特每格, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:SCAle {1:E}{2}", 通道号, 伏特每格, ScpiDelimiter);
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置指定通道的垂直位置 (Divisions)
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 设置通道垂直位置(int 通道号, double 格数, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:POSition {1:F2}{2}", 通道号, 格数, ScpiDelimiter);
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 5. 自动测量 (Measurement)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 动态添加并开启一个测量项
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 开启测量项(int 测量项编号, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 4/5/6 Series MSO 必须先动态创建测量项[cite: 2]
|
||||||
|
await SendAsync($"MEASUrement:ADDNew \"MEAS{测量项编号}\"{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task 设置测量类型(int 测量项编号, TekMeasurementType 类型, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 设置或查询测量类型[cite: 2]
|
||||||
|
await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task 设置测量源(int 测量项编号, string 源名称, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 4/5/6 Series 中设置测量源[cite: 2]
|
||||||
|
await SendAsync($"MEASUrement:MEAS{测量项编号}:SOUrce1 {源名称}{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Monitorable("测量项的当前采集平均值")]
|
||||||
|
public virtual async Task<string> 查询测量项当前值(int 测量项编号, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 4/5/6 Series 获取当前采集值的标准方式,查询平均值[cite: 2]
|
||||||
|
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询测量项全局平均值(int 测量项编号, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 获取所有采集(AllAcqs)历史累积的平均值[cite: 2]
|
||||||
|
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询测量项全局最大值(int 测量项编号, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 获取所有采集(AllAcqs)历史累积的最大值[cite: 2]
|
||||||
|
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 6. 屏幕截图与文件系统 (Hard Copy & File System)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 捕获示波器屏幕并保存到上位机本地路径
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="上位机文件路径">保存到电脑本地的文件路径 (例如: @"C:\temp\screen.png")</param>
|
||||||
|
/// <param name="ct">异步取消令牌</param>
|
||||||
|
public virtual async Task 保存屏幕截图(string 上位机文件路径, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string 临时文件 = "E:/temp1.png";
|
||||||
|
|
||||||
|
// 1. 直接保存截图到示波器U盘 (自动识别 .png 扩展名)[cite: 2]
|
||||||
|
await SendAsync($"SAVe:IMAGe \"{临时文件}\"{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
// 2. 发送 *OPC? 并等待返回 1,确保图像文件已经完全写入磁盘[cite: 2]
|
||||||
|
await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
// 3. 将文件内容读取到当前通信接口[cite: 2]
|
||||||
|
await SendAsync($"FILESystem:READFile \"{临时文件}\"{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
// 4. 提取底层的二进制块 (Block Data)
|
||||||
|
byte[] imageBytes = await ReadBinaryBlockAsync(ct);
|
||||||
|
|
||||||
|
// 5. 将提取的二进制字节流写入上位机本地磁盘
|
||||||
|
await File.WriteAllBytesAsync(上位机文件路径, imageBytes, ct);
|
||||||
|
|
||||||
|
// 6. 删除示波器上的临时文件,保持设备存储干净[cite: 2]
|
||||||
|
await SendAsync($"FILESystem:DELEte \"{临时文件}\"{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ⚠️ 请在这里将您的网络流对接起来!
|
||||||
|
/// 依据您 Tcp 基类的设计,返回用于读取底层字节流的 Stream。
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Stream GetNetworkStream()
|
||||||
|
{
|
||||||
|
// 【修改提示】:
|
||||||
|
// 如果您的基类暴露了 TcpClient,请取消注释并使用这一行:
|
||||||
|
// return this.TcpClient.GetStream();
|
||||||
|
|
||||||
|
// 如果您的基类直接叫 _stream 或 Stream,请返回它:
|
||||||
|
// return this.Stream;
|
||||||
|
|
||||||
|
// 假如不知道怎么填,请查看 Tcp 类的源码。
|
||||||
|
throw new NotImplementedException("请在此方法中返回基类的 NetworkStream 或 Socket 流对象。");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 辅助方法:读取 IEEE 488.2 标准的二进制块数据 (Block Data)
|
||||||
|
/// </summary>
|
||||||
|
protected virtual async Task<byte[]> ReadBinaryBlockAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var stream = GetNetworkStream();
|
||||||
|
byte[] singleByte = new byte[1];
|
||||||
|
|
||||||
|
// 1. 逐字节读取,直到读到起始符 '#'
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int read = await stream.ReadAsync(singleByte, 0, 1, ct);
|
||||||
|
if (read == 0) throw new EndOfStreamException("通信中断:未找到二进制块起始符 '#'");
|
||||||
|
if (singleByte[0] == (byte)'#') break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 读取 1 个字符 <NZDig>,它代表接下来有多少个字符表示数据长度
|
||||||
|
await stream.ReadAsync(singleByte, 0, 1, ct);
|
||||||
|
char nzDigChar = (char)singleByte[0];
|
||||||
|
if (!char.IsDigit(nzDigChar)) throw new InvalidDataException($"无效的块头标识: {nzDigChar}");
|
||||||
|
int lengthBytesCount = int.Parse(nzDigChar.ToString());
|
||||||
|
|
||||||
|
// 3. 读取 <NZDig> 所指示的长度字符,将其解析为整数 N
|
||||||
|
byte[] lengthBytes = new byte[lengthBytesCount];
|
||||||
|
int currentRead = 0;
|
||||||
|
while (currentRead < lengthBytesCount)
|
||||||
|
{
|
||||||
|
int read = await stream.ReadAsync(lengthBytes, currentRead, lengthBytesCount - currentRead, ct);
|
||||||
|
if (read == 0) throw new EndOfStreamException("通信中断:读取数据长度失败");
|
||||||
|
currentRead += read;
|
||||||
|
}
|
||||||
|
int dataLength = int.Parse(Encoding.ASCII.GetString(lengthBytes));
|
||||||
|
|
||||||
|
// 4. 从流中精确读取 N 个字节 <DChar>...,这就是完整的图像数据
|
||||||
|
byte[] imageData = new byte[dataLength];
|
||||||
|
int totalBytesRead = 0;
|
||||||
|
while (totalBytesRead < dataLength)
|
||||||
|
{
|
||||||
|
int read = await stream.ReadAsync(imageData, totalBytesRead, dataLength - totalBytesRead, ct);
|
||||||
|
if (read == 0) throw new EndOfStreamException("通信中断:读取图像数据不完整");
|
||||||
|
totalBytesRead += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 结尾可能包含 <EOM> (例如 LF \n),将其读取并丢弃以清空缓冲区
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await stream.ReadAsync(singleByte, 0, 1, ct);
|
||||||
|
}
|
||||||
|
catch { /* 忽略末尾符读取的可能超时 */ }
|
||||||
|
|
||||||
|
return imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 7. 状态与错误队列 (Status and Error)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清除所有的状态寄存器和错误/事件队列
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 清除错误队列Async(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 发送 *CLS 指令清空状态[cite: 2]
|
||||||
|
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询错误队列中当前的事件/错误数量
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<string> 查询错误数量Async(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 返回事件队列中的事件数量
|
||||||
|
return await WriteReadAsync($"EVQty?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<string> 读取单条错误消息Async(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 返回事件队列中的事件代码和消息
|
||||||
|
return await WriteReadAsync($"EVMsg?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取并清空队列中的所有错误和事件消息
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<string> 读取所有错误消息Async(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 返回所有事件及其消息
|
||||||
|
return await WriteReadAsync($"ALLEv?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
147
DeviceEditModule/ViewModels/DG1000ZViewModel.cs
Normal file
147
DeviceEditModule/ViewModels/DG1000ZViewModel.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
147
DeviceEditModule/ViewModels/IT6720ViewModel.cs
Normal file
147
DeviceEditModule/ViewModels/IT6720ViewModel.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
148
DeviceEditModule/ViewModels/PW8001ViewModel.cs
Normal file
148
DeviceEditModule/ViewModels/PW8001ViewModel.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
148
DeviceEditModule/ViewModels/TektronixMSOViewModel.cs
Normal file
148
DeviceEditModule/ViewModels/TektronixMSOViewModel.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
15
DeviceEditModule/Views/DG1000ZView.xaml
Normal file
15
DeviceEditModule/Views/DG1000ZView.xaml
Normal 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>
|
||||||
28
DeviceEditModule/Views/DG1000ZView.xaml.cs
Normal file
28
DeviceEditModule/Views/DG1000ZView.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
DeviceEditModule/Views/IT6720View.xaml
Normal file
15
DeviceEditModule/Views/IT6720View.xaml
Normal 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>
|
||||||
28
DeviceEditModule/Views/IT6720View.xaml.cs
Normal file
28
DeviceEditModule/Views/IT6720View.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
DeviceEditModule/Views/PW8001View.xaml
Normal file
15
DeviceEditModule/Views/PW8001View.xaml
Normal 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>
|
||||||
28
DeviceEditModule/Views/PW8001View.xaml.cs
Normal file
28
DeviceEditModule/Views/PW8001View.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
DeviceEditModule/Views/TektronixMSOView.xaml
Normal file
15
DeviceEditModule/Views/TektronixMSOView.xaml
Normal 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>
|
||||||
28
DeviceEditModule/Views/TektronixMSOView.xaml.cs
Normal file
28
DeviceEditModule/Views/TektronixMSOView.xaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,30 @@
|
|||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using DeviceCommand.Devices;
|
using DeviceCommand.Devices;
|
||||||
using MainModule.Views;
|
using MainModule.Views;
|
||||||
using System.Reflection;
|
using Prism.Ioc;
|
||||||
using UIShare.GlobalVariable;
|
using Prism.Modularity;
|
||||||
|
|
||||||
namespace MainModule
|
namespace MainModule
|
||||||
{
|
{
|
||||||
[Module(OnDemand=true)]
|
[Module(OnDemand = true)]
|
||||||
public class MainModule : IModule
|
public class MainModule : IModule
|
||||||
{
|
{
|
||||||
public void OnInitialized(IContainerProvider containerProvider)
|
public void OnInitialized(IContainerProvider containerProvider)
|
||||||
{
|
{
|
||||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||||
|
|
||||||
|
// 1. 加载主界面
|
||||||
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(MainView));
|
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(MainView));
|
||||||
|
|
||||||
|
// 2. 给 3 个台架分别注入测试视图
|
||||||
|
// 当你点击 "台架 1" 的 Tab 时,就会显示这个 ProtocolStartView
|
||||||
|
regionManager.RegisterViewWithRegion("TestCell1", typeof(ProtocolStartView));
|
||||||
|
|
||||||
|
// 当你点击 "台架 2" 的 Tab 时,就会显示这个 ProtocolStartView
|
||||||
|
regionManager.RegisterViewWithRegion("TestCell2", typeof(ProtocolStartView));
|
||||||
|
|
||||||
|
// 当你点击 "台架 3" 的 Tab 时,就会显示这个 ProtocolStartView
|
||||||
|
regionManager.RegisterViewWithRegion("TestCell3", typeof(ProtocolStartView));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||||
@@ -22,4 +34,4 @@ namespace MainModule
|
|||||||
containerRegistry.RegisterForNavigation<ProtocolStartView>("ProtocolStartView");
|
containerRegistry.RegisterForNavigation<ProtocolStartView>("ProtocolStartView");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,10 @@ namespace MainModule.ViewModels
|
|||||||
string json = System.IO.File.ReadAllText(filePath);
|
string json = System.IO.File.ReadAllText(filePath);
|
||||||
Newtonsoft.Json.JsonConvert.PopulateObject(json, _systemConfig);
|
Newtonsoft.Json.JsonConvert.PopulateObject(json, _systemConfig);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_systemConfig = new SystemConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//容器解析顺序不要改变!!!
|
//容器解析顺序不要改变!!!
|
||||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
mc:Ignorable="d" d:DesignHeight="1080" d:DesignWidth="1920">
|
mc:Ignorable="d" d:DesignHeight="1080" d:DesignWidth="1920">
|
||||||
|
|
||||||
<i:Interaction.Triggers>
|
<i:Interaction.Triggers>
|
||||||
<i:EventTrigger EventName="Loaded">
|
<i:EventTrigger EventName="Loaded">
|
||||||
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
|
<i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
|
||||||
@@ -18,241 +19,100 @@
|
|||||||
<converters:StringToVisibilityConverter x:Key="StringToVisibilityConverter"/>
|
<converters:StringToVisibilityConverter x:Key="StringToVisibilityConverter"/>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid Background="#F0F2F5">
|
||||||
<Grid.RowDefinitions>
|
<!-- 加一个浅灰底色更能衬托出白色的选项卡,可根据你的UI调整 -->
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="*"/>
|
<!-- 顶部的选项卡,切换 3 个台架 -->
|
||||||
<RowDefinition Height="*"/>
|
<TabControl Margin="5">
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
<TabControl.Resources>
|
||||||
<ColumnDefinition Width="*"/>
|
<!-- 1. 去除 TabControl 原生的丑陋边框和灰色背景 -->
|
||||||
<ColumnDefinition Width="*"/>
|
<Style TargetType="TabControl">
|
||||||
<ColumnDefinition Width="*"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
</Grid.ColumnDefinitions>
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Padding" Value="0,5,0,0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- 2. 美化 TabItem 标签头 -->
|
||||||
|
<Style TargetType="TabItem">
|
||||||
|
<Setter Property="FontSize" Value="16"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Foreground" Value="#8C8C8C"/>
|
||||||
|
<!-- 默认灰字 -->
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Setter Property="Padding" Value="25,12"/>
|
||||||
|
<!-- 撑大点击区域 -->
|
||||||
|
<Setter Property="Margin" Value="0,0,4,0"/>
|
||||||
|
<!-- 标签之间的间距 -->
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
|
||||||
|
<!-- 核心魔法:重写外观结构 -->
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="TabItem">
|
||||||
|
<!-- 用一个带底部边框的 Border 包装文字 -->
|
||||||
|
<Border Name="Border"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
BorderThickness="0,0,0,3"
|
||||||
|
CornerRadius="4,4,0,0">
|
||||||
|
<ContentPresenter x:Name="ContentSite"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
ContentSource="Header"
|
||||||
|
Margin="{TemplateBinding Padding}"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<!-- 交互状态1:鼠标悬浮 -->
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="#E6E6E6"/>
|
||||||
|
<Setter Property="Foreground" Value="#595959"/>
|
||||||
|
</Trigger>
|
||||||
|
|
||||||
|
<!-- 交互状态2:被选中 -->
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background" Value="White"/>
|
||||||
|
<!-- 选中时的底部指示条颜色(蓝色) -->
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush" Value="#007ACC"/>
|
||||||
|
<!-- 选中时的字体颜色(蓝色) -->
|
||||||
|
<Setter Property="Foreground" Value="#007ACC"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
|
||||||
<!-- 单元 1:TestCell1,默认 (0,0) -->
|
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell1" Margin="2">
|
|
||||||
<ContentControl.Style>
|
|
||||||
<Style TargetType="ContentControl">
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
|
<!-- !!!这是你原有的全屏隐藏逻辑!完美保留!!! -->
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell1">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</ContentControl.Style>
|
</TabControl.Resources>
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 2:TestCell2,默认 (0,1) -->
|
<!-- 标签页 1 -->
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell2" Margin="2">
|
<TabItem Header="台架 1 (Bench 1)">
|
||||||
<ContentControl.Style>
|
<!-- 加一个白底包裹,显得更像现代卡片 -->
|
||||||
<Style TargetType="ContentControl">
|
<Border Background="White" CornerRadius="0,4,4,4" Margin="0" BorderThickness="0" Padding="5">
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
<ContentControl prism:RegionManager.RegionName="TestCell1" />
|
||||||
<Setter Property="Grid.Column" Value="1"/>
|
</Border>
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
</TabItem>
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell2">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 3:TestCell3,默认 (0,2) -->
|
<!-- 标签页 2 -->
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell3" Margin="2">
|
<TabItem Header="台架 2 (Bench 2)">
|
||||||
<ContentControl.Style>
|
<Border Background="White" CornerRadius="0,4,4,4" Margin="0" BorderThickness="0" Padding="5">
|
||||||
<Style TargetType="ContentControl">
|
<ContentControl prism:RegionManager.RegionName="TestCell2" />
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
</Border>
|
||||||
<Setter Property="Grid.Column" Value="2"/>
|
</TabItem>
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell3">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 4:TestCell4,默认 (1,0) -->
|
<!-- 标签页 3 -->
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell4" Margin="2">
|
<TabItem Header="台架 3 (Bench 3)">
|
||||||
<ContentControl.Style>
|
<Border Background="White" CornerRadius="0,4,4,4" Margin="0" BorderThickness="0" Padding="5">
|
||||||
<Style TargetType="ContentControl">
|
<ContentControl prism:RegionManager.RegionName="TestCell3" />
|
||||||
<Setter Property="Grid.Row" Value="1"/>
|
</Border>
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
</TabItem>
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell4">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 5:TestCell5,默认 (1,1) -->
|
</TabControl>
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell5" Margin="2">
|
|
||||||
<ContentControl.Style>
|
|
||||||
<Style TargetType="ContentControl">
|
|
||||||
<Setter Property="Grid.Row" Value="1"/>
|
|
||||||
<Setter Property="Grid.Column" Value="1"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell5">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 6:TestCell6,默认 (1,2) -->
|
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell6" Margin="2">
|
|
||||||
<ContentControl.Style>
|
|
||||||
<Style TargetType="ContentControl">
|
|
||||||
<Setter Property="Grid.Row" Value="1"/>
|
|
||||||
<Setter Property="Grid.Column" Value="2"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell6">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 7:TestCell7,默认 (2,0) -->
|
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell7" Margin="2">
|
|
||||||
<ContentControl.Style>
|
|
||||||
<Style TargetType="ContentControl">
|
|
||||||
<Setter Property="Grid.Row" Value="2"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell7">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 8:TestCell8,默认 (2,1) -->
|
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell8" Margin="2">
|
|
||||||
<ContentControl.Style>
|
|
||||||
<Style TargetType="ContentControl">
|
|
||||||
<Setter Property="Grid.Row" Value="2"/>
|
|
||||||
<Setter Property="Grid.Column" Value="1"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell8">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>
|
|
||||||
|
|
||||||
<!-- 单元 9:TestCell9,默认 (2,2) -->
|
|
||||||
<!--<ContentControl prism:RegionManager.RegionName="TestCell9" Margin="2">
|
|
||||||
<ContentControl.Style>
|
|
||||||
<Style TargetType="ContentControl">
|
|
||||||
<Setter Property="Grid.Row" Value="2"/>
|
|
||||||
<Setter Property="Grid.Column" Value="2"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="1"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="1"/>
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName, Converter={StaticResource StringToVisibilityConverter}}" Value="Visible">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding ExpandedCellName}" Value="TestCell9">
|
|
||||||
<Setter Property="Visibility" Value="Visible"/>
|
|
||||||
<Setter Property="Grid.Row" Value="0"/>
|
|
||||||
<Setter Property="Grid.Column" Value="0"/>
|
|
||||||
<Setter Property="Grid.RowSpan" Value="3"/>
|
|
||||||
<Setter Property="Grid.ColumnSpan" Value="3"/>
|
|
||||||
<Setter Property="Panel.ZIndex" Value="99"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</ContentControl.Style>
|
|
||||||
</ContentControl>-->
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -4,82 +4,86 @@
|
|||||||
xmlns:prism="http://prismlibrary.com/"
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True">
|
prism:ViewModelLocator.AutoWireViewModel="True">
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<!-- 背景:多层柔光圆 -->
|
||||||
<Grid.Background>
|
<Grid.Background>
|
||||||
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
|
||||||
<GradientStop Color="#F8FAFC" Offset="0"/>
|
<GradientStop Color="#E8F0FE" Offset="0"/>
|
||||||
<GradientStop Color="#E2E8F0" Offset="1"/>
|
<GradientStop Color="#F3E8FF" Offset="0.5"/>
|
||||||
|
<GradientStop Color="#FCE7F3" Offset="1"/>
|
||||||
</LinearGradientBrush>
|
</LinearGradientBrush>
|
||||||
</Grid.Background>
|
</Grid.Background>
|
||||||
|
|
||||||
<materialDesign:Card Width="320" Height="380"
|
<!-- 背景装饰光斑 -->
|
||||||
UniformCornerRadius="16"
|
<Ellipse Width="200" Height="200" Fill="#93C5FD" Opacity="0.3"
|
||||||
Background="#FFFFFF"
|
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="-50,-50,0,0"/>
|
||||||
materialDesign:ElevationAssist.Elevation="Dp4"
|
<Ellipse Width="150" Height="150" Fill="#C4B5FD" Opacity="0.3"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-30,-30"/>
|
||||||
VerticalAlignment="Center">
|
|
||||||
|
|
||||||
<Grid Margin="30">
|
<!-- 中心磨砂卡片 -->
|
||||||
|
<Border Width="380" Height="440" CornerRadius="20"
|
||||||
|
Background="#CCFFFFFF" BorderBrush="#E2E8F0" BorderThickness="1"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="40" ShadowDepth="0" Opacity="0.12"/>
|
||||||
|
</Border.Effect>
|
||||||
|
<Grid Margin="32">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<StackPanel Grid.Row="0" HorizontalAlignment="Center" Margin="0,8,0,0">
|
<!-- 顶部 -->
|
||||||
<materialDesign:PackIcon Kind="Chip"
|
<StackPanel Grid.Row="0" HorizontalAlignment="Center">
|
||||||
Width="24" Height="24"
|
<Border Width="64" Height="64" Background="#E0E7FF" CornerRadius="32">
|
||||||
HorizontalAlignment="Center"
|
<materialDesign:PackIcon Kind="Chip" Width="32" Height="32"
|
||||||
Foreground="#94A3B8"/>
|
Foreground="#4F46E5"
|
||||||
<TextBlock Text="{Binding TestStatus}"
|
HorizontalAlignment="Center"
|
||||||
FontSize="15"
|
VerticalAlignment="Center"/>
|
||||||
FontWeight="SemiBold"
|
</Border>
|
||||||
Foreground="#64748B"
|
|
||||||
Margin="0,6,0,0"
|
<StackPanel Orientation="Horizontal" Margin="0,10,0,0"
|
||||||
HorizontalAlignment="Center"/>
|
HorizontalAlignment="Center">
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding TestStatus}" FontSize="13" Foreground="#64748B"/>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Grid Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center">
|
<!-- 中央按钮 -->
|
||||||
<Ellipse Width="150" Height="150" Fill="#F1F5F9"/>
|
<Grid Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
<Ellipse Width="130" Height="130" Fill="#E2E8F0"/>
|
<Button Width="130" Height="130"
|
||||||
|
Command="{Binding StartProtocolCommand}"
|
||||||
<Button Command="{Binding StartProtocolCommand}"
|
Background="#7C3AED" BorderBrush="#6D28D9" BorderThickness="0"
|
||||||
Width="110" Height="110"
|
materialDesign:ElevationAssist.Elevation="Dp6">
|
||||||
Style="{StaticResource MaterialDesignFloatingActionDarkButton}"
|
|
||||||
Background="#1E293B"
|
|
||||||
BorderBrush="#334155"
|
|
||||||
BorderThickness="2"
|
|
||||||
materialDesign:ElevationAssist.Elevation="Dp6"
|
|
||||||
materialDesign:RippleAssist.Feedback="#38BDF8">
|
|
||||||
<Button.Resources>
|
<Button.Resources>
|
||||||
<Style TargetType="Border">
|
<Style TargetType="Border">
|
||||||
<Setter Property="CornerRadius" Value="55"/>
|
<Setter Property="CornerRadius" Value="65"/>
|
||||||
</Style>
|
</Style>
|
||||||
</Button.Resources>
|
</Button.Resources>
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
<materialDesign:PackIcon Kind="Play"
|
<materialDesign:PackIcon Kind="Power" Width="44" Height="44"
|
||||||
Width="48" Height="48"
|
Foreground="#FFFFFF" HorizontalAlignment="Center"/>
|
||||||
Foreground="#38BDF8"
|
<TextBlock Text="START" Margin="0,6,0,0"
|
||||||
HorizontalAlignment="Center"
|
FontSize="14" FontWeight="Bold"
|
||||||
VerticalAlignment="Center"
|
Foreground="#FFFFFF" HorizontalAlignment="Center"/>
|
||||||
Margin="4,0,0,0"/>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<StackPanel Grid.Row="2" HorizontalAlignment="Center" Margin="0,0,0,8">
|
<!-- 底部 -->
|
||||||
<TextBlock Text="READY"
|
<StackPanel Grid.Row="2" HorizontalAlignment="Center">
|
||||||
FontSize="13"
|
<Border Background="#E0E7FF" CornerRadius="8" Padding="16,6">
|
||||||
FontWeight="Black"
|
<StackPanel Orientation="Horizontal">
|
||||||
Foreground="#10B981"
|
<Ellipse Width="8" Height="8" Fill="#4F46E5" Margin="0,0,6,0"/>
|
||||||
HorizontalAlignment="Center"/>
|
<TextBlock Text="系统准备中" FontSize="13"
|
||||||
<TextBlock Text="点击按钮加载测试序列"
|
FontWeight="Bold" Foreground="#4338CA"/>
|
||||||
FontSize="12"
|
</StackPanel>
|
||||||
Foreground="#94A3B8"
|
</Border>
|
||||||
Margin="0,4,0,0"
|
<TextBlock Text="点击按钮加载测试序列" Margin="0,10,0,0"
|
||||||
HorizontalAlignment="Center"/>
|
FontSize="12" Foreground="#94A3B8" HorizontalAlignment="Center"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</materialDesign:Card>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.215-preview13" />
|
<PackageReference Include="SqlSugarCore" Version="5.1.4.215-preview13" />
|
||||||
<PackageReference Include="System.IO.Ports" Version="11.0.0-preview.4.26230.115" />
|
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -29,53 +29,7 @@ namespace UIShare.GlobalVariable
|
|||||||
}
|
}
|
||||||
return true;
|
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>
|
/// <summary>
|
||||||
/// 保存指定的配置实例
|
/// 保存指定的配置实例
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// 确保配置中至少包含一个 CAN 设备(对应 SystemConfig.CANFD)。
|
/// 确保配置中至少包含一个 CAN 设备(对应 SystemConfig.CANFD)。
|
||||||
/// 旧配置或空配置会自动升级,使用户在设置界面能看到 CAN 设备。
|
/// 旧配置或空配置会自动升级,使用户在设置界面能看到 CAN 设备。
|
||||||
|
|||||||
@@ -214,15 +214,15 @@ namespace UIShare.GlobalVariable
|
|||||||
// IOGroup 初始化:从已实例化的设备中取出前两个 IOBoard 实例组建 IOBoardGroup
|
// IOGroup 初始化:从已实例化的设备中取出前两个 IOBoard 实例组建 IOBoardGroup
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var ioBoards = DeviceMap.Values.OfType<IOBoard>().Take(2).ToList();
|
var ioBoards = DeviceMap.Values.OfType<IOBoard>().Take(3).ToList();
|
||||||
if (ioBoards.Count == 2)
|
if (ioBoards.Count == 3)
|
||||||
{
|
{
|
||||||
IOGroup = new IOBoardGroup(ioBoards[0], ioBoards[1]);
|
IOGroup = new IOBoardGroup(ioBoards[0], ioBoards[1], ioBoards[2]);
|
||||||
LoggerHelper.Info($"IOBoardGroup 已初始化,Board1={ioBoards[0].GetType().Name}, Board2={ioBoards[1].GetType().Name}。");
|
LoggerHelper.Info($"IOBoardGroup 已初始化,Board1={ioBoards[0].GetType().Name}, Board2={ioBoards[1].GetType().Name}, Board3={ioBoards[2].GetType().Name}。");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LoggerHelper.Warn($"IOBoardGroup 初始化跳过:需要 2 个 IOBoard 实例,当前仅找到 {ioBoards.Count} 个。");
|
LoggerHelper.Warn($"IOBoardGroup 初始化跳过:需要 3 个 IOBoard 实例,当前仅找到 {ioBoards.Count} 个。");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
17
UIShare/GlobalVariable/GlobalConfig.cs
Normal file
17
UIShare/GlobalVariable/GlobalConfig.cs
Normal 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; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,22 +51,6 @@ namespace UIShare.GlobalVariable
|
|||||||
Name = "台架序号",
|
Name = "台架序号",
|
||||||
Value = 1,
|
Value = 1,
|
||||||
IsEditable=false
|
IsEditable=false
|
||||||
},
|
|
||||||
new ParameterVM
|
|
||||||
{
|
|
||||||
Category = ParameterCategory.Input,
|
|
||||||
Type = typeof(int),
|
|
||||||
Name = "直流负载通道",
|
|
||||||
Value = 1,
|
|
||||||
IsEditable=false
|
|
||||||
},
|
|
||||||
new ParameterVM
|
|
||||||
{
|
|
||||||
Category = ParameterCategory.Input,
|
|
||||||
Type = typeof(int),
|
|
||||||
Name = "交流电源通道",
|
|
||||||
Value = 1,
|
|
||||||
IsEditable=false
|
|
||||||
},
|
},
|
||||||
new ParameterVM
|
new ParameterVM
|
||||||
{
|
{
|
||||||
@@ -113,63 +97,112 @@ namespace UIShare.GlobalVariable
|
|||||||
//{
|
//{
|
||||||
// new DeviceInfoVM
|
// new DeviceInfoVM
|
||||||
// {
|
// {
|
||||||
// DeviceName = "IT7800E",
|
// DeviceName = "Chroma61800",
|
||||||
// DeviceType = "IT7800E",
|
// DeviceType = "Chroma61800",
|
||||||
// Remark = "交流可编程电源供应器",
|
// Remark = "可编程交流电源",
|
||||||
// ConnectionType = "Tcp",
|
// ConnectionType = "Tcp",
|
||||||
// IsEnabled = true,
|
// IsEnabled = true,
|
||||||
// IsConnected = false
|
// IsConnected = false
|
||||||
// },
|
// },
|
||||||
|
// new DeviceInfoVM
|
||||||
// new DeviceInfoVM
|
// {
|
||||||
// {
|
// DeviceName = "DG1000Z",
|
||||||
// DeviceName = "N36200",
|
// DeviceType = "DG1000Z",
|
||||||
// DeviceType = "N36200",
|
// Remark = "信号发生器",
|
||||||
// Remark = "宽范围可编程直流电源",
|
// ConnectionType = "Tcp",
|
||||||
// ConnectionType = "Tcp",
|
// IsEnabled = true,
|
||||||
// IsEnabled = true,
|
// IsConnected = false
|
||||||
// IsConnected = false
|
// },
|
||||||
// },
|
// new DeviceInfoVM
|
||||||
|
// {
|
||||||
// new DeviceInfoVM
|
// DeviceName = "IT6015C",
|
||||||
// {
|
// DeviceType = "IT6015C",
|
||||||
// DeviceName = "N36600",
|
// Remark = "低压直流源载一体机",
|
||||||
// DeviceType = "N36600",
|
// ConnectionType = "Tcp",
|
||||||
// Remark = "便携式宽范围可编程直流电源",
|
// IsEnabled = true,
|
||||||
// ConnectionType = "Tcp",
|
// IsConnected = false
|
||||||
// IsEnabled = false,
|
// },
|
||||||
// IsConnected = false
|
// new DeviceInfoVM
|
||||||
// },
|
// {
|
||||||
|
// DeviceName = "IT6036C",
|
||||||
// new DeviceInfoVM
|
// DeviceType = "IT6036C",
|
||||||
// {
|
// Remark = "高压直流源载一体机",
|
||||||
// DeviceName = "N69200",
|
// ConnectionType = "Tcp",
|
||||||
// DeviceType = "N69200",
|
// IsEnabled = true,
|
||||||
// Remark = "可编程直流电子负载",
|
// IsConnected = false
|
||||||
// ConnectionType = "Tcp",
|
// },
|
||||||
// IsEnabled = true,
|
// new DeviceInfoVM
|
||||||
// IsConnected = false
|
// {
|
||||||
// },
|
// DeviceName = "IT6720",
|
||||||
|
// DeviceType = "IT6720",
|
||||||
// new DeviceInfoVM
|
// Remark = "低压电源",
|
||||||
// {
|
// ConnectionType = "Tcp",
|
||||||
// DeviceName = "SDS2000X_HD",
|
// IsEnabled = true,
|
||||||
// DeviceType = "SDS2000X_HD",
|
// IsConnected = false
|
||||||
// Remark = "数字存储示波器",
|
// },
|
||||||
// ConnectionType = "Tcp",
|
// new DeviceInfoVM
|
||||||
// IsEnabled = true,
|
// {
|
||||||
// IsConnected = false
|
// DeviceName = "MCc06U",
|
||||||
// },
|
// DeviceType = "MCc06U",
|
||||||
|
// Remark = "水冷机",
|
||||||
// new DeviceInfoVM
|
// ConnectionType = "Tcp",
|
||||||
// {
|
// IsEnabled = true,
|
||||||
// DeviceName = "SPAW7000",
|
// IsConnected = false
|
||||||
// DeviceType = "SPAW7000",
|
// },
|
||||||
// Remark = "功率分析记录仪",
|
// new DeviceInfoVM
|
||||||
// ConnectionType = "Tcp",
|
// {
|
||||||
// IsEnabled = true,
|
// DeviceName = "PW8001",
|
||||||
// IsConnected = false
|
// DeviceType = "PW8001",
|
||||||
// }
|
// Remark = "功率分析记录仪",
|
||||||
//};
|
// ConnectionType = "Tcp",
|
||||||
|
// IsEnabled = true,
|
||||||
|
// IsConnected = false
|
||||||
|
// },
|
||||||
|
// new DeviceInfoVM
|
||||||
|
// {
|
||||||
|
// DeviceName = "RLT1000",
|
||||||
|
// DeviceType = "RLT1000",
|
||||||
|
// Remark = "环境箱",
|
||||||
|
// ConnectionType = "Tcp",
|
||||||
|
// IsEnabled = true,
|
||||||
|
// IsConnected = false
|
||||||
|
// },
|
||||||
|
// new DeviceInfoVM
|
||||||
|
// {
|
||||||
|
// DeviceName = "TektronixMSO",
|
||||||
|
// DeviceType = "TektronixMSO",
|
||||||
|
// Remark = "示波器",
|
||||||
|
// ConnectionType = "Tcp",
|
||||||
|
// IsEnabled = true,
|
||||||
|
// IsConnected = false
|
||||||
|
// },
|
||||||
|
// new DeviceInfoVM
|
||||||
|
// {
|
||||||
|
// DeviceName = "IOBoard1",
|
||||||
|
// DeviceType = "IOBoard",
|
||||||
|
// Remark = "IO板卡1",
|
||||||
|
// ConnectionType = "Tcp",
|
||||||
|
// IsEnabled = true,
|
||||||
|
// IsConnected = false
|
||||||
|
// },
|
||||||
|
// new DeviceInfoVM
|
||||||
|
// {
|
||||||
|
// DeviceName = "IOBoard2",
|
||||||
|
// DeviceType = "IOBoard",
|
||||||
|
// Remark = "IO板卡2",
|
||||||
|
// ConnectionType = "Tcp",
|
||||||
|
// IsEnabled = true,
|
||||||
|
// IsConnected = false
|
||||||
|
// },
|
||||||
|
// new DeviceInfoVM
|
||||||
|
// {
|
||||||
|
// DeviceName = "IOBoard3",
|
||||||
|
// DeviceType = "IOBoard",
|
||||||
|
// Remark = "IO板卡3",
|
||||||
|
// ConnectionType = "Tcp",
|
||||||
|
// IsEnabled = true,
|
||||||
|
// IsConnected = false
|
||||||
|
// }
|
||||||
|
//};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
UIShare/Tools/MachineCodeHelper.cs
Normal file
102
UIShare/Tools/MachineCodeHelper.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
<PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" />
|
<PackageReference Include="MaterialDesignThemes.MahApps" Version="5.3.0" />
|
||||||
<PackageReference Include="Notifications.Wpf.Core" Version="2.0.1" />
|
<PackageReference Include="Notifications.Wpf.Core" Version="2.0.1" />
|
||||||
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
|
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
|
||||||
|
<PackageReference Include="System.Management" Version="11.0.0-preview.6.26359.118" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Common\Common.csproj" />
|
<ProjectReference Include="..\Common\Common.csproj" />
|
||||||
|
|||||||
BIN
加密狗/ViKey.dll
BIN
加密狗/ViKey.dll
Binary file not shown.
Binary file not shown.
@@ -1,16 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<!-- 配置 ViKey.dll 复制到输出目录 -->
|
|
||||||
<None Include="ViKey.dll">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
252
加密狗/加密狗API类.cs
252
加密狗/加密狗API类.cs
@@ -1,252 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace 加密狗
|
|
||||||
{
|
|
||||||
public enum VikeyType
|
|
||||||
{
|
|
||||||
ViKeyAPP = 0, //实用型加密狗
|
|
||||||
ViKeySTD = 1, //标准型加密狗
|
|
||||||
ViKeyNET = 2, //网络型加密狗
|
|
||||||
ViKeyPRO = 3, //专业型加密狗
|
|
||||||
ViKeyWEB = 4, //身份认证型加密狗
|
|
||||||
ViKeyTIME = 5, //时钟型加密狗,内部独立时钟
|
|
||||||
ViKeyMultiFunctional = 0x0A, //多功能加密狗 支持软件加密 支持文档加密
|
|
||||||
ViKeyMultiFunctionalTime = 0x0B, //多功能时钟加密狗
|
|
||||||
ViKeyInvalid //无效类型
|
|
||||||
};
|
|
||||||
public enum 返回值 : uint
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 成功
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_SUCCESS = 0x00000000,
|
|
||||||
/// <summary>
|
|
||||||
/// 没有找到ViKey加密锁
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_NO_VIKEY = 0x80000001,
|
|
||||||
/// <summary>
|
|
||||||
/// 密码错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_INVALID_PASSWORD = 0x80000002,
|
|
||||||
/// <summary>
|
|
||||||
/// 请先查找加密锁
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_NEED_FIND = 0x80000003,
|
|
||||||
/// <summary>
|
|
||||||
/// 无效的句柄
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_INVALID_INDEX = 0x80000004,
|
|
||||||
/// <summary>
|
|
||||||
/// 数值错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_INVALID_VALUE = 0x80000005,
|
|
||||||
/// <summary>
|
|
||||||
/// 秘钥无效
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_INVALID_KEY = 0x80000006,
|
|
||||||
/// <summary>
|
|
||||||
/// 读取信息错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_GET_VALUE = 0x80000007,
|
|
||||||
/// <summary>
|
|
||||||
/// 设置信息错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_SET_VALUE = 0x80000008,
|
|
||||||
/// <summary>
|
|
||||||
/// 没有机会
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_NO_CHANCE = 0x80000009,
|
|
||||||
/// <summary>
|
|
||||||
/// 权限不足
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_NO_TAUTHORITY = 0x8000000A,
|
|
||||||
/// <summary>
|
|
||||||
/// 地址或长度错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_INVALID_ADDR_OR_SIZE = 0x8000000B,
|
|
||||||
/// <summary>
|
|
||||||
/// 获取随机数错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_RANDOM = 0x8000000C,
|
|
||||||
/// <summary>
|
|
||||||
/// 获取种子错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_SEED = 0x8000000D,
|
|
||||||
/// <summary>
|
|
||||||
/// 通信错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_CONNECTION = 0x8000000E,
|
|
||||||
/// <summary>
|
|
||||||
/// 算法或计算错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_CALCULATE = 0x8000000F,
|
|
||||||
/// <summary>
|
|
||||||
/// 计数器错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_MODULE = 0x80000010,
|
|
||||||
/// <summary>
|
|
||||||
/// 产生密码错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_GENERATE_NEW_PASSWORD = 0x80000011,
|
|
||||||
/// <summary>
|
|
||||||
/// 加密数据错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_ENCRYPT_FAILED = 0x80000012,
|
|
||||||
/// <summary>
|
|
||||||
/// 解密数据错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_DECRYPT_FAILED = 0x80000013,
|
|
||||||
/// <summary>
|
|
||||||
/// ViKey加密锁已经被锁定
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_ALREADY_LOCKED = 0x80000014,
|
|
||||||
/// <summary>
|
|
||||||
/// 无效的命令
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_UNKNOWN_COMMAND = 0x80000015,
|
|
||||||
/// <summary>
|
|
||||||
/// 当前ViKey加密锁不支持此功能
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_NO_SUPPORT = 0x80000016,
|
|
||||||
/// <summary>
|
|
||||||
/// 发生异常
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_CATCH = 0x80000017,
|
|
||||||
/// <summary>
|
|
||||||
/// 未知错误
|
|
||||||
/// </summary>
|
|
||||||
VIKEY_ERROR_UNKNOWN_ERROR = 0xFFFFFFFF,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class 加密狗API类
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 查找加密锁。使用其他API前必须先调用次函数。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pdwCount">[out] 如果查找到系统中存在加密狗,返回查找到加密狗的个数</param>
|
|
||||||
/// <returns>0表示系统中存在ViKey加密狗。</returns>
|
|
||||||
[DllImport("ViKey.dll")]
|
|
||||||
public static extern uint VikeyFind(ref uint pdwCount);
|
|
||||||
/// <summary>
|
|
||||||
/// 获取加密狗的硬件ID,加密狗的硬件ID是加密狗的唯一标识,每个加密狗的硬件ID都不一样。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index">[in]指定要操作加密狗的序号</param>
|
|
||||||
/// <param name="pdwHID">[out]返回加密狗的硬件ID</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll")]
|
|
||||||
public static extern uint VikeyGetHID(ushort Index, out uint pdwHID);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取加密狗的当前权限
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index">指定要操作加密狗的序号</param>
|
|
||||||
/// <param name="pLevel">返回加密狗的当前权限 0表示加密狗尚未登录 1表示加密狗是用户权限 2表示加密狗是管理员权限</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall)]
|
|
||||||
public static extern uint VikeyGetLevel(ushort Index, out byte pLevel);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 以用户权限登录加密狗
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index"></param>
|
|
||||||
/// <param name="pUserPassushort"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
|
|
||||||
public static extern uint VikeyUserLogin(ushort Index, Byte[] pUserPassword);
|
|
||||||
/// <summary>
|
|
||||||
/// 以管理员权限登录加密狗
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index"></param>
|
|
||||||
/// <param name="pAdminPassword"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
|
|
||||||
public static extern uint VikeyAdminLogin(ushort Index, Byte[] pAdminPassword);
|
|
||||||
/// <summary>
|
|
||||||
/// 注销登录加密狗 注销后的加密狗权限为0
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index">[in]指定要操作加密狗的序号</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
|
|
||||||
public static extern uint VikeyLogoff(ushort Index);
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
public struct SVikeyTime
|
|
||||||
{
|
|
||||||
public byte cYear;
|
|
||||||
public byte cMonth;
|
|
||||||
public byte cDay;
|
|
||||||
public byte cHour;
|
|
||||||
public byte cMinute;
|
|
||||||
public byte cSecond;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// 获取时钟型加密狗中的内部独立时间
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index"></param>
|
|
||||||
/// <param name="pTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall)]
|
|
||||||
public static extern uint VikeyGetTime(ushort Index, out SVikeyTime pTime);
|
|
||||||
/// <summary>
|
|
||||||
/// 获取时钟型加密狗中的到期时间
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index"></param>
|
|
||||||
/// <param name="pTime"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall)]
|
|
||||||
public static extern uint VikeyGetValidTime(ushort Index, out SVikeyTime pTime);
|
|
||||||
/// <summary>
|
|
||||||
/// 检测时钟型加密狗的时钟功能是否到期
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index">[in]指定要操作加密狗的序号</param>
|
|
||||||
/// <param name="pIsValid">[out]返回是否到期结果 1表示没有到期 0表示已经到期</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall)]
|
|
||||||
public static extern uint VikeyCheckValidTime(ushort Index, out byte pIsValid);
|
|
||||||
/// <summary>
|
|
||||||
/// 从加密狗中的获取4个双字的随机数数据
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index">指定要操作加密狗的序号</param>
|
|
||||||
/// <param name="pwRandom1"></param>
|
|
||||||
/// <param name="pwRandom2"></param>
|
|
||||||
/// <param name="pwRandom3"></param>
|
|
||||||
/// <param name="pwRandom4"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall)]
|
|
||||||
public static extern uint ViKeyRandom(ushort Index,
|
|
||||||
out ushort pwRandom1, out ushort pwRandom2, out ushort pwRandom3, out ushort pwRandom4);
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall)]
|
|
||||||
public static extern uint VikeyReadData(ushort Index, ushort Addr, ushort Length, byte[] buffer);
|
|
||||||
[DllImport("ViKey.dll", CallingConvention = CallingConvention.StdCall)]
|
|
||||||
public static extern uint VikeyWriteData(ushort Index, ushort Addr, ushort Length, byte[] buffer);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// // 7.4.6版本(含)以后的加密狗必须在查找加密狗前调用VikeySetUserSN并输入正确的UserSN后面才能查找到所属用户的加密狗
|
|
||||||
// 7.4.6版本以前的加密狗可以忽略此函数 不用调用此函数
|
|
||||||
// UserSN在ViKey加密管理工具的->帮助中心页面->复制出来
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pUserSN"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey")]
|
|
||||||
public static extern uint VikeySetUserSN(Byte[] pUserSN);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// //设置加密狗ApiKey 硬件版本为7.4.5(含)以后的加密狗必须要调用此接口才能登录
|
|
||||||
//ApiKey 是由ViKey加密管理工具的->加密狗管理->密钥设置页面 通过Api密钥种子生成的
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Index"></param>
|
|
||||||
/// <param name="pApiKey"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[DllImport("ViKey")]
|
|
||||||
public static extern uint VikeySetApiKey(ushort Index, Byte[] pApiKey);
|
|
||||||
|
|
||||||
[DllImport("ViKey")]
|
|
||||||
public static extern uint VikeyUninitialization();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
145
加密狗/加密狗驱动类.cs
145
加密狗/加密狗驱动类.cs
@@ -1,145 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace 加密狗
|
|
||||||
{
|
|
||||||
public class 加密狗驱动类
|
|
||||||
{
|
|
||||||
public static void Uninitialization()
|
|
||||||
{
|
|
||||||
加密狗API类.VikeyUninitialization();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保留原有方法,但添加新方法
|
|
||||||
public static int 写入加密狗(ushort Index, string 管理密码, string 校验密码)
|
|
||||||
{
|
|
||||||
// 保持不变
|
|
||||||
uint 加密狗数量 = 0;
|
|
||||||
var re = 加密狗API类.VikeyFind(ref 加密狗数量);
|
|
||||||
if (re != 0) return -1;
|
|
||||||
加密狗API类.VikeyAdminLogin(Index, Encoding.Default.GetBytes(管理密码));
|
|
||||||
SHA256 sHA256 = SHA256.Create();
|
|
||||||
加密狗API类.VikeyGetHID(Index, out uint pid);
|
|
||||||
byte[] HID = BitConverter.GetBytes(pid);
|
|
||||||
var pswd = Encoding.UTF8.GetBytes(校验密码);
|
|
||||||
byte[] 校验数组 = HID.Concat(pswd).ToArray();
|
|
||||||
var 校验值 = sHA256.ComputeHash(校验数组);
|
|
||||||
加密狗API类.VikeyWriteData(Index, 0, 32, 校验值);
|
|
||||||
byte[] randomBytes = new byte[96];
|
|
||||||
Random random = new Random();
|
|
||||||
random.NextBytes(randomBytes);
|
|
||||||
加密狗API类.VikeyWriteData(Index, 32, 96, randomBytes);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 新方法:仅检查加密狗时间,不验证密码和数据
|
|
||||||
/// </summary>
|
|
||||||
public static int 寻找加密狗仅检查时间()
|
|
||||||
{
|
|
||||||
uint 加密狗数量 = 0;
|
|
||||||
var re = 加密狗API类.VikeyFind(ref 加密狗数量);
|
|
||||||
if (re != 0 || 加密狗数量 == 0) return -1;
|
|
||||||
|
|
||||||
// 直接返回第一个找到的加密狗
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 原有方法:寻找加密狗并进行完整验证
|
|
||||||
/// </summary>
|
|
||||||
public static int 寻找加密狗(string 用户密码,string 校验密码)
|
|
||||||
{
|
|
||||||
uint 加密狗数量 = 0;
|
|
||||||
var re = 加密狗API类.VikeyFind(ref 加密狗数量);
|
|
||||||
if (re != 0) return -1;
|
|
||||||
int 找到的加密狗 = -1;
|
|
||||||
for (ushort i = 0; i < 加密狗数量; i++)
|
|
||||||
{
|
|
||||||
re = 加密狗API类.VikeyUserLogin(i, Encoding.Default.GetBytes(用户密码));
|
|
||||||
if (检查加密狗数据(i, 校验密码)) return i;
|
|
||||||
}
|
|
||||||
return (int)加密狗数量;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool 检查加密狗数据(ushort Index, string 校验密码)
|
|
||||||
{
|
|
||||||
// 保持不变
|
|
||||||
SHA256 sHA256 = SHA256.Create();
|
|
||||||
加密狗API类.VikeyGetHID(Index, out uint pid);
|
|
||||||
byte[] HID = BitConverter.GetBytes(pid);
|
|
||||||
var pswd = Encoding.UTF8.GetBytes(校验密码);
|
|
||||||
|
|
||||||
byte[] 校验数组 = HID.Concat(pswd).ToArray();
|
|
||||||
var 校验值 = sHA256.ComputeHash(校验数组);
|
|
||||||
var buffer = new byte[32];
|
|
||||||
加密狗API类.VikeyReadData(Index, 0, 32, buffer);
|
|
||||||
|
|
||||||
return buffer.SequenceEqual(校验值);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TimeSpan 检查剩余时间(int 加密狗)
|
|
||||||
{
|
|
||||||
ushort 序号 = (ushort)加密狗;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 读时间前必须先登录(用户权限即可)
|
|
||||||
uint re = 加密狗API类.VikeyUserLogin(序号, Encoding.Default.GetBytes("11111111"));
|
|
||||||
if (re != 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"VikeyUserLogin 失败: 0x{re:X}");
|
|
||||||
return TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
re = 加密狗API类.VikeyGetTime(序号, out 加密狗API类.SVikeyTime 现在时间);
|
|
||||||
if (re != 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"VikeyGetTime 失败: 0x{re:X}");
|
|
||||||
return TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
re = 加密狗API类.VikeyGetValidTime(序号, out 加密狗API类.SVikeyTime 到期时间);
|
|
||||||
if (re != 0)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"VikeyGetValidTime 失败: 0x{re:X}");
|
|
||||||
return TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
re = 加密狗API类.VikeyCheckValidTime(序号, out byte isValid);
|
|
||||||
Console.WriteLine($"VikeyCheckValidTime: ret=0x{re:X}, isValid={isValid}");
|
|
||||||
|
|
||||||
// cYear 是 byte,ViKey 用距 2000 年的偏移量
|
|
||||||
DateTime _现在时间 = new DateTime(现在时间.cYear + 2000, 现在时间.cMonth, 现在时间.cDay,
|
|
||||||
现在时间.cHour, 现在时间.cMinute, 现在时间.cSecond);
|
|
||||||
DateTime _到期时间 = new DateTime(到期时间.cYear + 2000, 到期时间.cMonth, 到期时间.cDay,
|
|
||||||
到期时间.cHour, 到期时间.cMinute, 到期时间.cSecond);
|
|
||||||
|
|
||||||
Console.WriteLine($"加密狗当前时间: {_现在时间:yyyy-MM-dd HH:mm:ss}");
|
|
||||||
Console.WriteLine($"加密狗到期时间: {_到期时间:yyyy-MM-dd HH:mm:ss}");
|
|
||||||
|
|
||||||
return _到期时间 - _现在时间;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"检查剩余时间异常: {ex.Message}");
|
|
||||||
return TimeSpan.Zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint 验证加密狗SN(Byte[] buffer)
|
|
||||||
{
|
|
||||||
return 加密狗API类.VikeySetUserSN(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint 验证加密狗APIkey(ushort index, Byte[] buffer)
|
|
||||||
{
|
|
||||||
return 加密狗API类.VikeySetApiKey(index, buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using 加密狗;
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════
|
|
||||||
// 刷入脚本(独立运行,不属于 ACP 主程序流程)
|
|
||||||
// 使用前请先用 ViKey 管理工具设置好密码和到期时间
|
|
||||||
// ═══════════════════════════════════════════════════════
|
|
||||||
public class Program
|
|
||||||
{
|
|
||||||
public static void Main(string[] args)
|
|
||||||
{
|
|
||||||
//写入加密狗(需管理员密码)
|
|
||||||
加密狗驱动类.写入加密狗(0, "00000000", "ACP项目");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user