Compare commits
57 Commits
f83c93fcf9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8daa1df8e9 | |||
| 7776e6c7ab | |||
| 2e71f68428 | |||
| 388edd1ada | |||
| 6057a71315 | |||
| 12de1b61e0 | |||
| 95700bfb8c | |||
| 013729003a | |||
| add1cd95be | |||
| 2a13c0ad34 | |||
| 92d477972f | |||
| 14d818083d | |||
| 07218fc737 | |||
| c592bcd272 | |||
| 092087f670 | |||
| 9281b48a2b | |||
| a9b6bc245d | |||
| 2cd8ef61db | |||
| 8b8704e59c | |||
| 54db9ac0b4 | |||
| ab7936c4ed | |||
| be7b459385 | |||
| 4386449925 | |||
| 9661e5f063 | |||
| b79eea918e | |||
| 7f2721ab11 | |||
| 48492973c3 | |||
| 2a363d37cd | |||
| 32d62de6f8 | |||
| c0a24af4cb | |||
| c4ec348217 | |||
| 74d544c288 | |||
| 2dfc819bcc | |||
| 0ed080f63c | |||
| 8e44be4894 | |||
| aebbd88e23 | |||
| 2fe307c142 | |||
| 3318c720b7 | |||
| b6b767cf0a | |||
| 9171e0b0c1 | |||
| db8baed9c7 | |||
| 52d486e2b1 | |||
| 412b56935c | |||
| b21c1901e1 | |||
| f5145d10e4 | |||
| 3b41459dcd | |||
| aec0044595 | |||
| 6c2b975999 | |||
| 2820b2f300 | |||
| cb13d3c8b5 | |||
| 60dc155ccf | |||
| 63f43853fa | |||
| f27045153c | |||
| 8f124fb08d | |||
| 571bdbf258 | |||
| 4567f058b5 | |||
| 52edf16176 |
46
ADP.sln
46
ADP.sln
@@ -35,10 +35,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateInfoMoudle", "UpdateI
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorModule\MonitorModule.csproj", "{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorModule\MonitorModule.csproj", "{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CAN驱动", "CAN驱动\CAN驱动.csproj", "{D1868672-0132-4B1A-A393-C2CF49A25E74}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceEditModule\DeviceEditModule.csproj", "{170AD4C1-189D-4FBE-B10D-2A4304527834}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceEditModule\DeviceEditModule.csproj", "{170AD4C1-189D-4FBE-B10D-2A4304527834}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZLGUSBCANFD", "ZLGUSBCANFD\ZLGUSBCANFD.csproj", "{542FE380-2344-4343-9AC6-F0C587C13B88}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchMovementModule", "BenchMovementModule\BenchMovementModule.csproj", "{31F84D0E-3634-4C70-A830-21AF412E3BBF}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CANModule", "CANModule\CANModule.csproj", "{AF2533DD-599D-49D7-942B-B0C26534D15F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "加密狗", "加密狗\加密狗.csproj", "{A35CA316-7B5B-4183-8636-4ECA532B1489}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurveModule", "CurveModule\CurveModule.csproj", "{84006890-9288-44C4-9D17-FA88F3FDECFA}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportModule", "ExportModule\ExportModule.csproj", "{8923122F-F7D3-4AE7-9E86-9E78F1296A11}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -105,14 +115,34 @@ Global
|
|||||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.Build.0 = Release|Any CPU
|
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{D1868672-0132-4B1A-A393-C2CF49A25E74}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.Build.0 = Release|Any CPU
|
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
|
||||||
|
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -125,5 +155,9 @@ Global
|
|||||||
{3456C3AC-E7FF-45F8-A68F-BEED9969812C} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
{3456C3AC-E7FF-45F8-A68F-BEED9969812C} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
{850A5BE8-0BC1-4D6A-8351-DD812C9C4427} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
{170AD4C1-189D-4FBE-B10D-2A4304527834} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
{170AD4C1-189D-4FBE-B10D-2A4304527834} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
|
{31F84D0E-3634-4C70-A830-21AF412E3BBF} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
|
{AF2533DD-599D-49D7-942B-B0C26534D15F} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
|
{84006890-9288-44C4-9D17-FA88F3FDECFA} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
|
{8923122F-F7D3-4AE7-9E86-9E78F1296A11} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -19,10 +19,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\CAN驱动\CAN驱动.csproj" />
|
<ProjectReference Include="..\BenchMovementModule\BenchMovementModule.csproj" />
|
||||||
|
<ProjectReference Include="..\CANModule\CANModule.csproj" />
|
||||||
<ProjectReference Include="..\Command\Command.csproj" />
|
<ProjectReference Include="..\Command\Command.csproj" />
|
||||||
|
<ProjectReference Include="..\CurveModule\CurveModule.csproj" />
|
||||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||||
<ProjectReference Include="..\DeviceEditModule\DeviceEditModule.csproj" />
|
<ProjectReference Include="..\DeviceEditModule\DeviceEditModule.csproj" />
|
||||||
|
<ProjectReference Include="..\ExportModule\ExportModule.csproj" />
|
||||||
<ProjectReference Include="..\LoginModule\LoginModule.csproj" />
|
<ProjectReference Include="..\LoginModule\LoginModule.csproj" />
|
||||||
<ProjectReference Include="..\MainModule\MainModule.csproj" />
|
<ProjectReference Include="..\MainModule\MainModule.csproj" />
|
||||||
<ProjectReference Include="..\MonitorModule\MonitorModule.csproj" />
|
<ProjectReference Include="..\MonitorModule\MonitorModule.csproj" />
|
||||||
@@ -31,6 +34,8 @@
|
|||||||
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
|
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
|
||||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||||
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
||||||
|
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
|
||||||
|
<ProjectReference Include="..\加密狗\加密狗.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
|
|||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare;
|
using UIShare;
|
||||||
using System;
|
using System;
|
||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using ADP.Profiles;
|
using ADP.Profiles;
|
||||||
|
using 加密狗;
|
||||||
|
|
||||||
namespace ADP
|
namespace ADP
|
||||||
{
|
{
|
||||||
@@ -64,10 +65,10 @@ namespace ADP
|
|||||||
LoggerHelper.Progress = new ScopeLogDispatcher(globalInfo);
|
LoggerHelper.Progress = new ScopeLogDispatcher(globalInfo);
|
||||||
|
|
||||||
//初始化数据库
|
//初始化数据库
|
||||||
//DatabaseConfig.SetTenant(10001);
|
DatabaseConfig.SetTenant(10001);
|
||||||
//DatabaseConfig.InitMySql("127.0.0.1",3306,"ADP","root","123456");
|
DatabaseConfig.InitSqlite();
|
||||||
//DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true);
|
DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true);
|
||||||
//SqlSugarContext.InitDatabase();
|
SqlSugarContext.InitDatabase();
|
||||||
//显示登录窗口
|
//显示登录窗口
|
||||||
var login = Container.Resolve<LoginModuleView>();
|
var login = Container.Resolve<LoginModuleView>();
|
||||||
var re = Container.Resolve<IRegionManager>();
|
var re = Container.Resolve<IRegionManager>();
|
||||||
@@ -75,6 +76,52 @@ namespace ADP
|
|||||||
RegionManager.SetRegionManager(Application.Current.MainWindow, re);
|
RegionManager.SetRegionManager(Application.Current.MainWindow, re);
|
||||||
login.Show();
|
login.Show();
|
||||||
}
|
}
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
if (System.Diagnostics.Debugger.IsAttached)
|
||||||
|
{
|
||||||
|
base.OnStartup(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string strUserSn = "ACA89B2C1194650D4E93D97BE189FFCC";
|
||||||
|
string strApiKey = "C6D01BCD442347822876F8232245DE9422643C167B8C61CE4DF6049990E7395588675A8FC6AA4BF7AD353D669C993236BEA83F77551753885A47AF34185CC3A7";
|
||||||
|
|
||||||
|
Byte[] buffer = new Byte[256];
|
||||||
|
buffer = System.Text.Encoding.Default.GetBytes(strUserSn);
|
||||||
|
uint retcode;
|
||||||
|
retcode = 加密狗驱动类.验证加密狗SN(buffer);
|
||||||
|
if (retcode != 0)
|
||||||
|
{
|
||||||
|
MessageBox.Show(string.Format("设置UserSN失败 error code: 0x{0:x}", retcode));
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
base.RegisterRequiredTypes(containerRegistry);
|
base.RegisterRequiredTypes(containerRegistry);
|
||||||
@@ -84,6 +131,8 @@ namespace ADP
|
|||||||
containerRegistry.RegisterScoped<ScopedContext>();
|
containerRegistry.RegisterScoped<ScopedContext>();
|
||||||
containerRegistry.RegisterScoped<DeviceManager>();
|
containerRegistry.RegisterScoped<DeviceManager>();
|
||||||
containerRegistry.RegisterSingleton<GlobalInfo>();
|
containerRegistry.RegisterSingleton<GlobalInfo>();
|
||||||
|
containerRegistry.RegisterSingleton<HardwareDataBroadcaster>();
|
||||||
|
containerRegistry.RegisterSingleton<CANSignalBroadcaster>();
|
||||||
//注册AutoMapper
|
//注册AutoMapper
|
||||||
var config = new MapperConfiguration(
|
var config = new MapperConfiguration(
|
||||||
cfg => cfg.AddProfile<AutoMapperProfile>(),
|
cfg => cfg.AddProfile<AutoMapperProfile>(),
|
||||||
@@ -100,6 +149,9 @@ namespace ADP
|
|||||||
containerRegistry.RegisterInstance<INotificationManager>(NotificationManager);
|
containerRegistry.RegisterInstance<INotificationManager>(NotificationManager);
|
||||||
// 注册仓储
|
// 注册仓储
|
||||||
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
||||||
|
//注册服务
|
||||||
|
containerRegistry.Register<IMonitorValueService, MonitorValueService>();
|
||||||
|
containerRegistry.Register<ITestReportService, TestReportService>();
|
||||||
}
|
}
|
||||||
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
||||||
protected override IModuleCatalog CreateModuleCatalog()
|
protected override IModuleCatalog CreateModuleCatalog()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using UIShare.PubEvent;
|
using UIShare.PubEvent;
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
namespace ADP.ViewModels.Dialogs
|
namespace ADP.ViewModels.Dialogs
|
||||||
{
|
{
|
||||||
@@ -22,8 +23,8 @@ namespace ADP.ViewModels.Dialogs
|
|||||||
set => SetProperty(ref _Message, value);
|
set => SetProperty(ref _Message, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _Icon= $"pack://siteoforigin:,,,/Resources/Images/info.png";
|
private ImageSource _Icon = new System.Windows.Media.Imaging.BitmapImage(new System.Uri("pack://application:,,,/Images/info.png"));
|
||||||
public string Icon
|
public ImageSource Icon
|
||||||
{
|
{
|
||||||
get => _Icon;
|
get => _Icon;
|
||||||
set => SetProperty(ref _Icon, value);
|
set => SetProperty(ref _Icon, value);
|
||||||
@@ -101,13 +102,15 @@ namespace ADP.ViewModels.Dialogs
|
|||||||
Title = parameters.GetValue<string>("Title");
|
Title = parameters.GetValue<string>("Title");
|
||||||
Message = parameters.GetValue<string>("Message");
|
Message = parameters.GetValue<string>("Message");
|
||||||
var iconKey = parameters.GetValue<string>("Icon"); // info / error / warn
|
var iconKey = parameters.GetValue<string>("Icon"); // info / error / warn
|
||||||
Icon = iconKey switch
|
var bmp = (ImageSource)new System.Windows.Media.Imaging.BitmapImage(new System.Uri(iconKey switch
|
||||||
{
|
{
|
||||||
"info" => $"pack://siteoforigin:,,,/Resources/Images/info.png",
|
"info" => "pack://application:,,,/Images/info.png",
|
||||||
"error" => $"pack://siteoforigin:,,,/Resources/Images/error.png",
|
"error" => "pack://application:,,,/Images/error.png",
|
||||||
"warn" => $"pack://siteoforigin:,,,/Resources/Images/warning.png",
|
"warn" => "pack://application:,,,/Images/warning.png",
|
||||||
_ => $"pack://siteoforigin:,,,/Resources/Images/info.png" // 默认
|
_ => "pack://application:,,,/Images/info.png"
|
||||||
};
|
}));
|
||||||
|
bmp.Freeze();
|
||||||
|
Icon = bmp;
|
||||||
|
|
||||||
|
|
||||||
ShowYes = parameters.GetValue<bool>("ShowYes");
|
ShowYes = parameters.GetValue<bool>("ShowYes");
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
|
using DeviceCommand.Devices;
|
||||||
using Logger;
|
using Logger;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
|
using Microsoft.Win32;
|
||||||
using Model.Models;
|
using Model.Models;
|
||||||
using Notifications.Wpf.Core;
|
using Notifications.Wpf.Core;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -10,11 +12,12 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Threading; // 💡 新增:引入定时器命名空间
|
using System.Windows.Threading;
|
||||||
using UIShare;
|
using UIShare;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare.PubEvent;
|
using UIShare.PubEvent;
|
||||||
using UIShare.UIViewModel;
|
using UIShare.UIViewModel;
|
||||||
|
using SqlSugar;
|
||||||
|
|
||||||
namespace ADP.ViewModels
|
namespace ADP.ViewModels
|
||||||
{
|
{
|
||||||
@@ -29,6 +32,7 @@ namespace ADP.ViewModels
|
|||||||
|
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IRegionManager _regionManager;
|
private readonly IRegionManager _regionManager;
|
||||||
|
private readonly IDialogService _dialogService;
|
||||||
private readonly IContainerProvider _containerProvider;
|
private readonly IContainerProvider _containerProvider;
|
||||||
private readonly INotificationManager _notificationManager;
|
private readonly INotificationManager _notificationManager;
|
||||||
private readonly IModuleManager _moduleManager;
|
private readonly IModuleManager _moduleManager;
|
||||||
@@ -50,13 +54,11 @@ namespace ADP.ViewModels
|
|||||||
set => SetProperty(ref _IsLeftDrawerOpen, value);
|
set => SetProperty(ref _IsLeftDrawerOpen, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 代理属性:动态反映当前被激活的 Scope 状态
|
|
||||||
public TimeSpan RunningTime
|
public TimeSpan RunningTime
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (CurrentContext == null) return TimeSpan.Zero;
|
if (CurrentContext == null) return TimeSpan.Zero;
|
||||||
// 💡 核心:如果当前工位的计时器正在跑,动态累加 SW 当前的时间,否则返回其保存的固定时间值
|
|
||||||
return CurrentContext.SW.IsRunning ? CurrentContext.SW.Elapsed : CurrentContext.RunningTime;
|
return CurrentContext.SW.IsRunning ? CurrentContext.SW.Elapsed : CurrentContext.RunningTime;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -147,11 +149,17 @@ namespace ADP.ViewModels
|
|||||||
public ICommand NewCommand { get; set; }
|
public ICommand NewCommand { get; set; }
|
||||||
public ICommand SetDefaultCommand { get; set; }
|
public ICommand SetDefaultCommand { get; set; }
|
||||||
public ICommand ShowDialogManagerViewCommand { get; set; }
|
public ICommand ShowDialogManagerViewCommand { get; set; }
|
||||||
|
public ICommand SelectCANSignalMonitorCommand { get; set; }
|
||||||
|
public ICommand SelectCANMessageCommand { get; set; }
|
||||||
|
public ICommand MonitorValueSettingCommand { get; set; }
|
||||||
|
public ICommand GetFileStringCommand { get; set; }
|
||||||
|
public ICommand SilenceBuzzerCommand { get; set; }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public ShellViewModel(IContainerProvider containerProvider)
|
public ShellViewModel(IContainerProvider containerProvider)
|
||||||
{
|
{
|
||||||
_containerProvider = containerProvider;
|
_containerProvider = containerProvider;
|
||||||
|
_dialogService= containerProvider.Resolve<IDialogService>();
|
||||||
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||||
_regionManager = containerProvider.Resolve<IRegionManager>();
|
_regionManager = containerProvider.Resolve<IRegionManager>();
|
||||||
@@ -175,6 +183,11 @@ namespace ADP.ViewModels
|
|||||||
SaveAsCommand = new DelegateCommand(SaveAs);
|
SaveAsCommand = new DelegateCommand(SaveAs);
|
||||||
SaveCommand = new DelegateCommand(Save);
|
SaveCommand = new DelegateCommand(Save);
|
||||||
SetDefaultCommand = new DelegateCommand(SetDefault);
|
SetDefaultCommand = new DelegateCommand(SetDefault);
|
||||||
|
SelectCANSignalMonitorCommand = new DelegateCommand(SelectCANSignalMonitor);
|
||||||
|
SelectCANMessageCommand = new DelegateCommand(SelectCANMessage);
|
||||||
|
MonitorValueSettingCommand = new DelegateCommand(MonitorValueSetting);
|
||||||
|
GetFileStringCommand = new DelegateCommand(GetFileString);
|
||||||
|
SilenceBuzzerCommand = new AsyncDelegateCommand(SilenceBuzzer);
|
||||||
|
|
||||||
_globalInfo.ContextDic.Add("default", new ScopedContext());
|
_globalInfo.ContextDic.Add("default", new ScopedContext());
|
||||||
|
|
||||||
@@ -197,7 +210,6 @@ namespace ADP.ViewModels
|
|||||||
_uiRefreshTimer.Start();
|
_uiRefreshTimer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void RefreshAllContextProperties()
|
private void RefreshAllContextProperties()
|
||||||
{
|
{
|
||||||
@@ -209,6 +221,70 @@ namespace ADP.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region 命令处理与事件
|
#region 命令处理与事件
|
||||||
|
|
||||||
|
private async Task SilenceBuzzer()
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Publish(_globalInfo.CurrentScope);
|
||||||
|
|
||||||
|
}
|
||||||
|
private void GetFileString()
|
||||||
|
{
|
||||||
|
var openFileDialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "选择文件",
|
||||||
|
Filter = "所有文件 (*.*)|*.*",
|
||||||
|
};
|
||||||
|
if (openFileDialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
string filePath = openFileDialog.FileName;
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Clipboard.SetDataObject(filePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void MonitorValueSetting()
|
||||||
|
{
|
||||||
|
if ( _globalInfo.CurrentScope == "default")
|
||||||
|
{
|
||||||
|
LoggerHelper.Warn("当前作用域未配置 CAN 设备,无法打开报文选择窗口。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_dialogService.ShowDialog("ValueLimitView", new DialogParameters
|
||||||
|
{
|
||||||
|
{ "Scope", _globalInfo.ScopeDic[_globalInfo.CurrentScope] }
|
||||||
|
}, _ => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectCANSignalMonitor()
|
||||||
|
{
|
||||||
|
var canfd = CurrentConfig?.CANFD;
|
||||||
|
if (canfd == null || _globalInfo.CurrentScope == "default")
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dialogService.ShowDialog("CollectCANSignalSettingView", new DialogParameters
|
||||||
|
{
|
||||||
|
{ "SystemConfig", CurrentConfig }
|
||||||
|
}, _ => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectCANMessage()
|
||||||
|
{
|
||||||
|
var canfd = CurrentConfig?.CANFD;
|
||||||
|
if (canfd == null||_globalInfo.CurrentScope=="default")
|
||||||
|
{
|
||||||
|
LoggerHelper.Warn("当前作用域未配置 CAN 设备,无法打开报文选择窗口。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dialogService.ShowDialog("SelectCanMessageView", new DialogParameters
|
||||||
|
{
|
||||||
|
{ "CANFD", canfd }
|
||||||
|
}, _ => { });
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowDialogManagerView()
|
private void ShowDialogManagerView()
|
||||||
{
|
{
|
||||||
_eventAggregator.GetEvent<CancelMinimizeEvent>().Publish();
|
_eventAggregator.GetEvent<CancelMinimizeEvent>().Publish();
|
||||||
@@ -491,7 +567,7 @@ namespace ADP.ViewModels
|
|||||||
ScopedContext? targetContext = CurrentContext;
|
ScopedContext? targetContext = CurrentContext;
|
||||||
|
|
||||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||||
|
CurrentConfig.CurrentADPFile = "新建ADP文件";
|
||||||
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
|
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
|
||||||
targetContext.CurrentFilePath = null;
|
targetContext.CurrentFilePath = null;
|
||||||
targetContext.Program.Parameters.Clear();
|
targetContext.Program.Parameters.Clear();
|
||||||
@@ -555,7 +631,7 @@ namespace ADP.ViewModels
|
|||||||
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
|
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
CurrentConfig.CurrentADPFile = filePath;
|
||||||
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
||||||
targetContext.Program.Parameters = program.Parameters;
|
targetContext.Program.Parameters = program.Parameters;
|
||||||
targetContext.Program.StepCollection = program.StepCollection;
|
targetContext.Program.StepCollection = program.StepCollection;
|
||||||
@@ -615,6 +691,7 @@ namespace ADP.ViewModels
|
|||||||
if (saveFileDialog.ShowDialog() == true)
|
if (saveFileDialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
// 💡 2. 将新路径安全写入快照工位
|
// 💡 2. 将新路径安全写入快照工位
|
||||||
|
CurrentConfig.CurrentADPFile = saveFileDialog.FileName;
|
||||||
targetContext.CurrentFilePath = saveFileDialog.FileName;
|
targetContext.CurrentFilePath = saveFileDialog.FileName;
|
||||||
|
|
||||||
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
||||||
@@ -764,8 +841,17 @@ namespace ADP.ViewModels
|
|||||||
_regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
|
_regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
|
||||||
IsLeftDrawerOpen = false;
|
IsLeftDrawerOpen = false;
|
||||||
break;
|
break;
|
||||||
case "更新界面":
|
case "台架移动界面":
|
||||||
_regionManager.RequestNavigate("ShellViewManager", "UpdateInfoView");
|
if (_globalInfo.CurrentScope != "default") return;
|
||||||
|
_regionManager.RequestNavigate("ShellViewManager", "BenchMovementView");
|
||||||
|
break;
|
||||||
|
case "曲线回读界面":
|
||||||
|
if (_globalInfo.CurrentScope != "default") return;
|
||||||
|
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
|
||||||
|
break;
|
||||||
|
case "测试报告导出界面":
|
||||||
|
if (_globalInfo.CurrentScope != "default") return;
|
||||||
|
_regionManager.RequestNavigate("ShellViewManager", "ExportView");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,21 @@
|
|||||||
Style="{StaticResource MaterialDesignFlatButton}"
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
Margin="8" />
|
Margin="8" />
|
||||||
|
|
||||||
<!--<Button Content="更新界面"
|
<Button Content="台架移动界面"
|
||||||
Command="{Binding NavigateCommand}"
|
Command="{Binding NavigateCommand}"
|
||||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||||
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="测试报告导出界面"
|
||||||
|
Command="{Binding NavigateCommand}"
|
||||||
|
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||||
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
|
Margin="8" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</materialDesign:DrawerHost.LeftDrawerContent>
|
</materialDesign:DrawerHost.LeftDrawerContent>
|
||||||
<Grid>
|
<Grid>
|
||||||
@@ -158,8 +168,44 @@
|
|||||||
Foreground="Black" />
|
Foreground="Black" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<!-- CAN信号监控 -->
|
||||||
|
<MenuItem Header="CAN信号监控"
|
||||||
|
Foreground="Black"
|
||||||
|
Command="{Binding SelectCANSignalMonitorCommand}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="Message"
|
||||||
|
Foreground="Black" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<!-- 监控设置 -->
|
||||||
|
<MenuItem Header="监控设置"
|
||||||
|
Foreground="Black"
|
||||||
|
Command="{Binding MonitorValueSettingCommand}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="Monitor"
|
||||||
|
Foreground="Black" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="CAN报文选择"
|
||||||
|
Foreground="Black"
|
||||||
|
Command="{Binding SelectCANMessageCommand}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="Message"
|
||||||
|
Foreground="Black" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<!-- 获得文件路径字符串 -->
|
||||||
|
<MenuItem Header="获得文件路径字符串"
|
||||||
|
Foreground="Black"
|
||||||
|
Command="{Binding GetFileStringCommand}">
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<materialDesign:PackIcon Kind="File"
|
||||||
|
Foreground="Black" />
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
<!-- 蜂鸣器消音 -->
|
<!-- 弹窗管理器 -->
|
||||||
<MenuItem Header="弹窗管理器"
|
<MenuItem Header="弹窗管理器"
|
||||||
FontSize="14"
|
FontSize="14"
|
||||||
Height="50"
|
Height="50"
|
||||||
|
|||||||
19
BenchMovementModule/BenchMovementModule.cs
Normal file
19
BenchMovementModule/BenchMovementModule.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using BenchMovementModule.Views;
|
||||||
|
|
||||||
|
namespace BenchMovementModule
|
||||||
|
{
|
||||||
|
public class BenchMovementModule : IModule
|
||||||
|
{
|
||||||
|
public void OnInitialized(IContainerProvider containerProvider)
|
||||||
|
{
|
||||||
|
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||||
|
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(BenchMovementView));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||||
|
{
|
||||||
|
containerRegistry.RegisterForNavigation<BenchMovementView>("BenchMovementView");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
BenchMovementModule/BenchMovementModule.csproj
Normal file
14
BenchMovementModule/BenchMovementModule.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
164
BenchMovementModule/HardwareDrive/GantryControlBase.cs
Normal file
164
BenchMovementModule/HardwareDrive/GantryControlBase.cs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
using DeviceCommand.Base;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BenchMovementModule.HardwareDrive
|
||||||
|
{
|
||||||
|
public abstract class GantryControlBase
|
||||||
|
{
|
||||||
|
protected readonly IModbusDevice _device;
|
||||||
|
protected readonly byte _slaveAddress;
|
||||||
|
|
||||||
|
// 默认 PLC Modbus 映射地址
|
||||||
|
protected const ushort ADDR_HM_X_ENABLE = 0xC100; // HM0: X轴使能
|
||||||
|
protected const ushort ADDR_HM_Y_ENABLE = 0xC101; // HM1: Y轴使能
|
||||||
|
protected const ushort ADDR_HM_Z_ENABLE = 0xC102; // HM2: Z轴使能
|
||||||
|
|
||||||
|
protected const ushort ADDR_M_X_START = 30; // M30: X轴指定位置启动
|
||||||
|
protected const ushort ADDR_M_Y_START = 31; // M31: Y轴指定位置启动
|
||||||
|
protected const ushort ADDR_M_Z_START = 35; // M35: Z轴指定位置启动
|
||||||
|
|
||||||
|
protected const ushort ADDR_M_ALL_STOP = 40; // M40: 全停
|
||||||
|
protected const ushort ADDR_M_X_STOP = 41; // M41: X轴停止
|
||||||
|
protected const ushort ADDR_M_Y_STOP = 42; // M42: Y轴停止
|
||||||
|
protected const ushort ADDR_M_Z_STOP = 43; // M43: Z轴停止
|
||||||
|
|
||||||
|
protected const ushort ADDR_M_HOME = 100; // M100: 回原点触发
|
||||||
|
|
||||||
|
protected const ushort ADDR_HD_X_SPEED = 0xA166; // HD230: X轴速度 (32位)
|
||||||
|
protected const ushort ADDR_HD_Y_SPEED = 0xA17A; // HD250: Y轴速度 (32位)
|
||||||
|
protected const ushort ADDR_HD_Z_SPEED = 0xA170; // HD240: Z轴速度 (32位)
|
||||||
|
|
||||||
|
protected const ushort ADDR_HD_X_TARGET = 0xA10C; // HD140: X轴指定位置 (32位)
|
||||||
|
protected const ushort ADDR_HD_Y_TARGET = 0xA120; // HD160: Y轴指定位置 (32位)
|
||||||
|
protected const ushort ADDR_HD_Z_TARGET = 0xA116; // HD150: Z轴指定位置 (32位)
|
||||||
|
|
||||||
|
public bool IsConnected => _device?.IsConnected ?? false;
|
||||||
|
|
||||||
|
protected GantryControlBase(IModbusDevice device, byte slaveAddress = 1)
|
||||||
|
{
|
||||||
|
_device = device ?? throw new ArgumentNullException(nameof(device));
|
||||||
|
_slaveAddress = slaveAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await _device.ConnectAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
_device.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轴使能控制
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetAxisEnableAsync(int axis, bool enable, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_HM_X_ENABLE, // X
|
||||||
|
2 => ADDR_HM_Y_ENABLE, // Y
|
||||||
|
3 => ADDR_HM_Z_ENABLE, // Z
|
||||||
|
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)")
|
||||||
|
};
|
||||||
|
|
||||||
|
await _device.WriteSingleCoilAsync(_slaveAddress, address, enable, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 左右移动(X轴)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">目标绝对位置(脉冲数/单位值)</param>
|
||||||
|
/// <param name="speed">运行速度</param>
|
||||||
|
public async Task MoveLeftRightAsync(int position, int speed, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 1. 写入速度与目标位置(32位数据,写入连续的2个保持寄存器)
|
||||||
|
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_SPEED, Int32ToUshorts(speed), ct);
|
||||||
|
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_TARGET, Int32ToUshorts(position), ct);
|
||||||
|
|
||||||
|
// 2. 边缘触发启动信号 M30
|
||||||
|
await TriggerCoilAsync(ADDR_M_X_START, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上下移动(Z轴)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">目标绝对位置</param>
|
||||||
|
/// <param name="speed">运行速度</param>
|
||||||
|
public async Task MoveUpDownAsync(int position, int speed, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 1. 写入速度与目标位置(32位数据)
|
||||||
|
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_SPEED, Int32ToUshorts(speed), ct);
|
||||||
|
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_TARGET, Int32ToUshorts(position), ct);
|
||||||
|
|
||||||
|
// 2. 边缘触发启动信号 M35
|
||||||
|
await TriggerCoilAsync(ADDR_M_Z_START, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 前后移动(Y轴 - 预留拓展)
|
||||||
|
/// </summary>
|
||||||
|
public async Task MoveFrontBackAsync(int position, int speed, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_SPEED, Int32ToUshorts(speed), ct);
|
||||||
|
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_TARGET, Int32ToUshorts(position), ct);
|
||||||
|
await TriggerCoilAsync(ADDR_M_Y_START, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全局回零
|
||||||
|
/// </summary>
|
||||||
|
public async Task HomeAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await TriggerCoilAsync(ADDR_M_HOME, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轴停止
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="axis">1:X轴, 2:Y轴, 3:Z轴, 0:全停</param>
|
||||||
|
public async Task StopAsync(int axis = 0, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
0 => ADDR_M_ALL_STOP,
|
||||||
|
1 => ADDR_M_X_STOP,
|
||||||
|
2 => ADDR_M_Y_STOP,
|
||||||
|
3 => ADDR_M_Z_STOP,
|
||||||
|
_ => ADDR_M_ALL_STOP
|
||||||
|
};
|
||||||
|
|
||||||
|
await TriggerCoilAsync(address, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 内部工具方法
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发一个点动信号(置 1 后,延时 100ms 自动置 0)
|
||||||
|
/// </summary>
|
||||||
|
protected async Task TriggerCoilAsync(ushort address, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _device.WriteSingleCoilAsync(_slaveAddress, address, true, ct);
|
||||||
|
await Task.Delay(100, ct); // 给予 PLC 扫描周期足够的响应时间
|
||||||
|
await _device.WriteSingleCoilAsync(_slaveAddress, address, false, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 32 位整型数据转换为 Modbus 的 2 个 16 位无符号整数(高低字转换)
|
||||||
|
/// </summary>
|
||||||
|
protected ushort[] Int32ToUshorts(int value)
|
||||||
|
{
|
||||||
|
// 本处采用低字在前(CDAB),如果PLC高字在前(ABCD),可将返回数组顺序颠倒
|
||||||
|
ushort lowWord = (ushort)(value & 0xFFFF);
|
||||||
|
ushort highWord = (ushort)((value >> 16) & 0xFFFF);
|
||||||
|
return new ushort[] { lowWord, highWord };
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
28
BenchMovementModule/HardwareDrive/GantryControlRtu.cs
Normal file
28
BenchMovementModule/HardwareDrive/GantryControlRtu.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using DeviceCommand.Base;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BenchMovementModule.HardwareDrive
|
||||||
|
{
|
||||||
|
public class GantryControlRtu : GantryControlBase
|
||||||
|
{
|
||||||
|
// 暴露出底层的 ModbusRtu 实例以便修改串口配置
|
||||||
|
public ModbusRtu RtuDevice => (ModbusRtu)_device;
|
||||||
|
|
||||||
|
public GantryControlRtu(string portName, int baudRate = 9600, byte slaveAddress = 1)
|
||||||
|
: base(new ModbusRtu(), slaveAddress)
|
||||||
|
{
|
||||||
|
RtuDevice.ConfigureDevice(portName, baudRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GantryControlRtu(string portName, int baudRate, int dataBits, StopBits stopBits, Parity parity, byte slaveAddress = 1)
|
||||||
|
: base(new ModbusRtu(), slaveAddress)
|
||||||
|
{
|
||||||
|
RtuDevice.ConfigureDevice(portName, baudRate, dataBits, stopBits, parity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
BenchMovementModule/HardwareDrive/GantryControlTcp.cs
Normal file
27
BenchMovementModule/HardwareDrive/GantryControlTcp.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BenchMovementModule.HardwareDrive
|
||||||
|
{
|
||||||
|
public class GantryControlTcp : GantryControlBase
|
||||||
|
{
|
||||||
|
// 暴露出底层的 ModbusTcp 实例以便修改网络配置
|
||||||
|
public ModbusTcp TcpDevice => (ModbusTcp)_device;
|
||||||
|
|
||||||
|
public GantryControlTcp(TcpConfig config, byte slaveAddress = 1)
|
||||||
|
: base(new ModbusTcp(config), slaveAddress)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public GantryControlTcp(string ipAddress, int port = 502, byte slaveAddress = 1)
|
||||||
|
: base(new ModbusTcp(), slaveAddress)
|
||||||
|
{
|
||||||
|
TcpDevice.ConfigureDevice(ipAddress, port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
522
BenchMovementModule/ViewModels/BenchMovementViewModel.cs
Normal file
522
BenchMovementModule/ViewModels/BenchMovementViewModel.cs
Normal file
@@ -0,0 +1,522 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using DeviceCommand.Devices;
|
||||||
|
using Logger;
|
||||||
|
using Prism.Ioc;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace BenchMovementModule.ViewModels
|
||||||
|
{
|
||||||
|
public class BenchMovementViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||||
|
{
|
||||||
|
#region 属性
|
||||||
|
|
||||||
|
public bool KeepAlive => true;
|
||||||
|
|
||||||
|
// ===== GantryControlTcp 实例(从 DeviceManager 获取)=====
|
||||||
|
private GantryControlTcp? _gantry;
|
||||||
|
private DeviceManager? _deviceManager;
|
||||||
|
private readonly GlobalInfo _globalInfo;
|
||||||
|
|
||||||
|
// ===== 连接状态 =====
|
||||||
|
private bool _isConnected;
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get => _isConnected;
|
||||||
|
set => SetProperty(ref _isConnected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _connectionStatus = "未连接";
|
||||||
|
public string ConnectionStatus
|
||||||
|
{
|
||||||
|
get => _connectionStatus;
|
||||||
|
set => SetProperty(ref _connectionStatus, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 6轴绝对位置显示(相对原点的实时位置,只读)=====
|
||||||
|
private int _absPos1;
|
||||||
|
public int AbsPos1 { get => _absPos1; set => SetProperty(ref _absPos1, value); }
|
||||||
|
|
||||||
|
private int _absPos2;
|
||||||
|
public int AbsPos2 { get => _absPos2; set => SetProperty(ref _absPos2, value); }
|
||||||
|
|
||||||
|
private int _absPos3;
|
||||||
|
public int AbsPos3 { get => _absPos3; set => SetProperty(ref _absPos3, value); }
|
||||||
|
|
||||||
|
private int _absPos4;
|
||||||
|
public int AbsPos4 { get => _absPos4; set => SetProperty(ref _absPos4, value); }
|
||||||
|
|
||||||
|
private int _absPos5;
|
||||||
|
public int AbsPos5 { get => _absPos5; set => SetProperty(ref _absPos5, value); }
|
||||||
|
|
||||||
|
private int _absPos6;
|
||||||
|
public int AbsPos6 { get => _absPos6; set => SetProperty(ref _absPos6, value); }
|
||||||
|
|
||||||
|
// ===== 寸动目标位置 =====
|
||||||
|
private int _targetX;
|
||||||
|
public int TargetX { get => _targetX; set => SetProperty(ref _targetX, value); }
|
||||||
|
|
||||||
|
private int _targetY;
|
||||||
|
public int TargetY { get => _targetY; set => SetProperty(ref _targetY, value); }
|
||||||
|
|
||||||
|
private int _targetZ;
|
||||||
|
public int TargetZ { get => _targetZ; set => SetProperty(ref _targetZ, value); }
|
||||||
|
|
||||||
|
// ===== 扫描参数 =====
|
||||||
|
private int _scanStepX = 1000;
|
||||||
|
public int ScanStepX { get => _scanStepX; set => SetProperty(ref _scanStepX, value); }
|
||||||
|
|
||||||
|
private int _scanStartX;
|
||||||
|
public int ScanStartX { get => _scanStartX; set => SetProperty(ref _scanStartX, value); }
|
||||||
|
|
||||||
|
private int _scanEndX = 50000;
|
||||||
|
public int ScanEndX { get => _scanEndX; set => SetProperty(ref _scanEndX, value); }
|
||||||
|
|
||||||
|
private int _scanStepY = 1000;
|
||||||
|
public int ScanStepY { get => _scanStepY; set => SetProperty(ref _scanStepY, value); }
|
||||||
|
|
||||||
|
private int _scanStartY;
|
||||||
|
public int ScanStartY { get => _scanStartY; set => SetProperty(ref _scanStartY, value); }
|
||||||
|
|
||||||
|
private int _scanEndY = 50000;
|
||||||
|
public int ScanEndY { get => _scanEndY; set => SetProperty(ref _scanEndY, value); }
|
||||||
|
|
||||||
|
private int _scanStepZ = 100;
|
||||||
|
public int ScanStepZ { get => _scanStepZ; set => SetProperty(ref _scanStepZ, value); }
|
||||||
|
|
||||||
|
private int _scanStartZ;
|
||||||
|
public int ScanStartZ { get => _scanStartZ; set => SetProperty(ref _scanStartZ, value); }
|
||||||
|
|
||||||
|
private int _scanEndZ = 20000;
|
||||||
|
public int ScanEndZ { get => _scanEndZ; set => SetProperty(ref _scanEndZ, value); }
|
||||||
|
|
||||||
|
// ===== 速度设定(脉冲)=====
|
||||||
|
private int _speedX = 5000;
|
||||||
|
public int SpeedX { get => _speedX; set => SetProperty(ref _speedX, value); }
|
||||||
|
|
||||||
|
private int _speedY = 5000;
|
||||||
|
public int SpeedY { get => _speedY; set => SetProperty(ref _speedY, value); }
|
||||||
|
|
||||||
|
private int _speedZ = 2000;
|
||||||
|
public int SpeedZ { get => _speedZ; set => SetProperty(ref _speedZ, value); }
|
||||||
|
|
||||||
|
private int _homeSpeed = 3000;
|
||||||
|
public int HomeSpeed { get => _homeSpeed; set => SetProperty(ref _homeSpeed, value); }
|
||||||
|
|
||||||
|
// ===== 零点设定 =====
|
||||||
|
private int _homeX;
|
||||||
|
public int HomeX { get => _homeX; set => SetProperty(ref _homeX, value); }
|
||||||
|
|
||||||
|
private int _homeY;
|
||||||
|
public int HomeY { get => _homeY; set => SetProperty(ref _homeY, value); }
|
||||||
|
|
||||||
|
private int _homeZ;
|
||||||
|
public int HomeZ { get => _homeZ; set => SetProperty(ref _homeZ, value); }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
public ICommand ConnectCommand { get; }
|
||||||
|
public ICommand DisconnectCommand { get; }
|
||||||
|
public ICommand MoveXCommand { get; }
|
||||||
|
public ICommand MoveYCommand { get; }
|
||||||
|
public ICommand MoveZCommand { get; }
|
||||||
|
public ICommand MoveAllCommand { get; }
|
||||||
|
public ICommand GoHomeCommand { get; }
|
||||||
|
public ICommand EmergencyStopCommand { get; }
|
||||||
|
public ICommand StopXCommand { get; }
|
||||||
|
public ICommand StopYCommand { get; }
|
||||||
|
public ICommand StopZCommand { get; }
|
||||||
|
public ICommand SetSpeedCommand { get; }
|
||||||
|
public ICommand SetHomeCommand { get; }
|
||||||
|
public ICommand ReadStatusCommand { get; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 私有字段
|
||||||
|
private readonly DispatcherTimer _pollTimer;
|
||||||
|
private bool _isInitiated = false;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public BenchMovementViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||||
|
ConnectCommand = new DelegateCommand(OnConnect);
|
||||||
|
DisconnectCommand = new DelegateCommand(OnDisconnect);
|
||||||
|
MoveXCommand = new DelegateCommand(OnMoveX);
|
||||||
|
MoveYCommand = new DelegateCommand(OnMoveY);
|
||||||
|
MoveZCommand = new DelegateCommand(OnMoveZ);
|
||||||
|
MoveAllCommand = new DelegateCommand(OnMoveAll);
|
||||||
|
GoHomeCommand = new DelegateCommand(OnGoHome);
|
||||||
|
EmergencyStopCommand = new DelegateCommand(OnEmergencyStop);
|
||||||
|
StopXCommand = new DelegateCommand(OnStopX);
|
||||||
|
StopYCommand = new DelegateCommand(OnStopY);
|
||||||
|
StopZCommand = new DelegateCommand(OnStopZ);
|
||||||
|
SetSpeedCommand = new DelegateCommand(OnSetSpeed);
|
||||||
|
SetHomeCommand = new DelegateCommand(OnSetHome);
|
||||||
|
ReadStatusCommand = new DelegateCommand(OnReadStatus);
|
||||||
|
|
||||||
|
// 位置轮询定时器(500ms)
|
||||||
|
_pollTimer = new DispatcherTimer(DispatcherPriority.Background)
|
||||||
|
{
|
||||||
|
Interval = TimeSpan.FromMilliseconds(500)
|
||||||
|
};
|
||||||
|
_pollTimer.Tick += OnPollTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 命令处理
|
||||||
|
|
||||||
|
private async void OnConnect()
|
||||||
|
{
|
||||||
|
if (_gantry == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ConnectionStatus = "连接中...";
|
||||||
|
bool ok = await _gantry.ConnectAsync();
|
||||||
|
if (ok)
|
||||||
|
{
|
||||||
|
IsConnected = true;
|
||||||
|
ConnectionStatus = "已连接";
|
||||||
|
_pollTimer.Start();
|
||||||
|
await ReadDeviceParametersAsync();
|
||||||
|
LoggerHelper.Info("[BenchMovement] 台架连接成功");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ConnectionStatus = "连接失败";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 连接失败: {ex.Message}");
|
||||||
|
ConnectionStatus = $"连接失败: {ex.Message}";
|
||||||
|
}
|
||||||
|
UpdateConnectionStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisconnect()
|
||||||
|
{
|
||||||
|
if (_gantry == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pollTimer.Stop();
|
||||||
|
_gantry.Close();
|
||||||
|
IsConnected = false;
|
||||||
|
ConnectionStatus = "未连接";
|
||||||
|
LoggerHelper.Info("[BenchMovement] 台架已断开");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 断开失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
UpdateConnectionStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnMoveX()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetAxisTargetPositionAsync(1, TargetX);
|
||||||
|
await _gantry.StartSingleAxisAsync(1);
|
||||||
|
LoggerHelper.Info($"[BenchMovement] X轴移动到 {TargetX}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] X轴移动失败: {ex.Message}");
|
||||||
|
ShowErrorMessageBox($"X轴移动失败: {ex.Message}", () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnMoveY()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetAxisTargetPositionAsync(2, TargetY);
|
||||||
|
await _gantry.StartSingleAxisAsync(2);
|
||||||
|
LoggerHelper.Info($"[BenchMovement] Y轴移动到 {TargetY}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] Y轴移动失败: {ex.Message}");
|
||||||
|
ShowErrorMessageBox($"Y轴移动失败: {ex.Message}", () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnMoveZ()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetAxisTargetPositionAsync(3, TargetZ);
|
||||||
|
await _gantry.StartSingleAxisAsync(3);
|
||||||
|
LoggerHelper.Info($"[BenchMovement] Z轴移动到 {TargetZ}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] Z轴移动失败: {ex.Message}");
|
||||||
|
ShowErrorMessageBox($"Z轴移动失败: {ex.Message}", () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnMoveAll()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetAxisTargetPositionAsync(1, TargetX);
|
||||||
|
await _gantry.SetAxisTargetPositionAsync(2, TargetY);
|
||||||
|
await _gantry.SetAxisTargetPositionAsync(3, TargetZ);
|
||||||
|
await _gantry.StartAllAxesAsync();
|
||||||
|
LoggerHelper.Info($"[BenchMovement] 三轴同时移动: X={TargetX}, Y={TargetY}, Z={TargetZ}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 三轴移动失败: {ex.Message}");
|
||||||
|
ShowErrorMessageBox($"三轴移动失败: {ex.Message}", () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnGoHome()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.HomeAsync();
|
||||||
|
LoggerHelper.Info("[BenchMovement] 回原点已触发");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 回原点失败: {ex.Message}");
|
||||||
|
ShowErrorMessageBox($"回原点失败: {ex.Message}", () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnEmergencyStop()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetEStopStateAsync(true);
|
||||||
|
LoggerHelper.Info("[BenchMovement] 急停已触发");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 急停失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnStopX()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetStopStateAsync(1, true);
|
||||||
|
await Task.Delay(100);
|
||||||
|
await _gantry.SetStopStateAsync(1, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] X轴停止失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnStopY()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetStopStateAsync(2, true);
|
||||||
|
await Task.Delay(100);
|
||||||
|
await _gantry.SetStopStateAsync(2, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] Y轴停止失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnStopZ()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetStopStateAsync(3, true);
|
||||||
|
await Task.Delay(100);
|
||||||
|
await _gantry.SetStopStateAsync(3, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] Z轴停止失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSetSpeed()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetAxisSpeedAsync(1, SpeedX);
|
||||||
|
await _gantry.SetAxisSpeedAsync(2, SpeedY);
|
||||||
|
await _gantry.SetAxisSpeedAsync(3, SpeedZ);
|
||||||
|
await _gantry.SetHomePositionSpeedAsync(HomeSpeed);
|
||||||
|
LoggerHelper.Info($"[BenchMovement] 速度设定完成: X={SpeedX}, Y={SpeedY}, Z={SpeedZ}, 回零={HomeSpeed}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 速度设定失败: {ex.Message}");
|
||||||
|
ShowErrorMessageBox($"速度设定失败: {ex.Message}", () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSetHome()
|
||||||
|
{
|
||||||
|
if (!EnsureConnected()) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _gantry!.SetHomePositionAsync(HomeX, HomeY, HomeZ);
|
||||||
|
LoggerHelper.Info($"[BenchMovement] 零点设定完成: X={HomeX}, Y={HomeY}, Z={HomeZ}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 零点设定失败: {ex.Message}");
|
||||||
|
ShowErrorMessageBox($"零点设定失败: {ex.Message}", () => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnReadStatus()
|
||||||
|
{
|
||||||
|
await ReadDeviceParametersAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助方法
|
||||||
|
|
||||||
|
private bool EnsureConnected()
|
||||||
|
{
|
||||||
|
if (_gantry == null || !_gantry.IsConnected)
|
||||||
|
{
|
||||||
|
ShowErrorMessageBox("设备未连接,请先连接台架", () => { });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateConnectionStatus()
|
||||||
|
{
|
||||||
|
IsConnected = _gantry?.IsConnected ?? false;
|
||||||
|
ConnectionStatus = IsConnected ? "已连接" : "未连接";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReadDeviceParametersAsync()
|
||||||
|
{
|
||||||
|
if (_gantry == null || !_gantry.IsConnected) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (x, y, z) = await _gantry.GetHomePositionAsync();
|
||||||
|
HomeX = x; HomeY = y; HomeZ = z;
|
||||||
|
|
||||||
|
SpeedX = await _gantry.GetAxisSpeedAsync(1);
|
||||||
|
SpeedY = await _gantry.GetAxisSpeedAsync(2);
|
||||||
|
SpeedZ = await _gantry.GetAxisSpeedAsync(3);
|
||||||
|
HomeSpeed = await _gantry.GetHomePositionSpeedAsync();
|
||||||
|
|
||||||
|
// 读取 6 轴绝对位置
|
||||||
|
var positions = await _gantry.GetAllAbsolutePositionsAsync();
|
||||||
|
AbsPos1 = positions.Axis1; AbsPos2 = positions.Axis2; AbsPos3 = positions.Axis3;
|
||||||
|
AbsPos4 = positions.Axis4; AbsPos5 = positions.Axis5; AbsPos6 = positions.Axis6;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 读取设备参数失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnPollTick(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_gantry == null || !_gantry.IsConnected) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 批量读取 6 轴绝对位置(一次性通信,高效)
|
||||||
|
var pos = await _gantry.GetAllAbsolutePositionsAsync();
|
||||||
|
AbsPos1 = pos.Axis1; AbsPos2 = pos.Axis2; AbsPos3 = pos.Axis3;
|
||||||
|
AbsPos4 = pos.Axis4; AbsPos5 = pos.Axis5; AbsPos6 = pos.Axis6;
|
||||||
|
}
|
||||||
|
catch { /* 忽略轮询错误 */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 生命周期
|
||||||
|
|
||||||
|
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||||
|
{
|
||||||
|
base.OnNavigatedTo(navigationContext);
|
||||||
|
if (!_isInitiated)
|
||||||
|
{
|
||||||
|
_isInitiated = true;
|
||||||
|
InitGantryFromDeviceManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 DeviceManager.DeviceMap 中查找 GantryControlTcp 实例,
|
||||||
|
/// 优先使用当前打开的作用域,找不到则取任意已注册作用域。
|
||||||
|
/// </summary>
|
||||||
|
private void InitGantryFromDeviceManager()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 优先使用当前打开的作用域
|
||||||
|
IScopedProvider? scope = null;
|
||||||
|
if (!string.IsNullOrEmpty(_globalInfo.CurrentOpeningScope)
|
||||||
|
&& _globalInfo.ScopeDic.TryGetValue(_globalInfo.CurrentOpeningScope, out scope))
|
||||||
|
{
|
||||||
|
// 找到了
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 退而求其次,取任意可用作用域
|
||||||
|
scope = _globalInfo.ScopeDic.Values.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope == null)
|
||||||
|
{
|
||||||
|
LoggerHelper.Warn("[BenchMovement] 无可用的作用域,无法解析 DeviceManager。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_deviceManager = scope.Resolve<DeviceManager>();
|
||||||
|
_gantry = _deviceManager.DeviceMap.Values.OfType<GantryControlTcp>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (_gantry != null)
|
||||||
|
{
|
||||||
|
LoggerHelper.Info($"[BenchMovement] 已从 DeviceManager 获取 GantryControlTcp 实例。");
|
||||||
|
UpdateConnectionStatus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LoggerHelper.Warn("[BenchMovement] DeviceManager 中未找到 GantryControlTcp 设备,请检查设备配置。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"[BenchMovement] 初始化 GantryControlTcp 失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_pollTimer.Stop();
|
||||||
|
// GantryControlTcp 由 DeviceManager 统一管理生命周期,此处仅停止轮询
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
394
BenchMovementModule/Views/BenchMovementView.xaml
Normal file
394
BenchMovementModule/Views/BenchMovementView.xaml
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
<UserControl x:Class="BenchMovementModule.Views.BenchMovementView"
|
||||||
|
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"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
d:DesignHeight="1080" d:DesignWidth="1920">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<Style TargetType="TextBlock" x:Key="HeaderStyle">
|
||||||
|
<Setter Property="Foreground" Value="#333333"/>
|
||||||
|
<Setter Property="FontSize" Value="18"/>
|
||||||
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
|
<Setter Property="Margin" Value="2,2,2,5"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBlock" x:Key="LabelStyle">
|
||||||
|
<Setter Property="Foreground" Value="#555555"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Margin" Value="5"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBlock" x:Key="ValueStyle">
|
||||||
|
<Setter Property="Foreground" Value="#0056B3"/>
|
||||||
|
<Setter Property="FontSize" Value="26"/>
|
||||||
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBox">
|
||||||
|
<Setter Property="Background" Value="White"/>
|
||||||
|
<Setter Property="Foreground" Value="#333333"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#CCCCCC"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="Padding" Value="4,2"/>
|
||||||
|
<Setter Property="Height" Value="28"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="#F5F5F5"/>
|
||||||
|
<Setter Property="Foreground" Value="#333333"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#B8B8B8"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="12,5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="Height" Value="34"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,0,8"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="btnBorder"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="3">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#EAEAEA"/>
|
||||||
|
<Setter TargetName="btnBorder" Property="BorderBrush" Value="#4A90E2"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#DCDCDC"/>
|
||||||
|
<Setter TargetName="btnBorder" Property="BorderBrush" Value="#2A6099"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Button" x:Key="StopButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
|
||||||
|
<Setter Property="Background" Value="#FFF0F0"/>
|
||||||
|
<Setter Property="Foreground" Value="#C00000"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#E0A0A0"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="3">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#FFE0E0"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#F0C0C0"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Button" x:Key="EmergencyButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
|
||||||
|
<Setter Property="Background" Value="#D9534F"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#D43F3A"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="4">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#C9302C"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#AC2925"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Button" x:Key="ConnectButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
|
||||||
|
<Setter Property="Background" Value="#5CB85C"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#4CAE4C"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="3">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#449D44"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="btnBorder" Property="Background" Value="#3D8B3D"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="GroupBox">
|
||||||
|
<Setter Property="BorderBrush" Value="#D0D0D0"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Margin" Value="6"/>
|
||||||
|
<Setter Property="Padding" Value="10"/>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid Background="#F0F2F5">
|
||||||
|
<TabControl Background="Transparent" BorderThickness="0">
|
||||||
|
<TabControl.Resources>
|
||||||
|
<Style TargetType="TabItem">
|
||||||
|
<Setter Property="Background" Value="#E0E4E8"/>
|
||||||
|
<Setter Property="Foreground" Value="#333333"/>
|
||||||
|
<Setter Property="FontSize" Value="15"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Padding" Value="25,6"/>
|
||||||
|
<Setter Property="Margin" Value="2,0,2,0"/>
|
||||||
|
</Style>
|
||||||
|
</TabControl.Resources>
|
||||||
|
|
||||||
|
<!-- ======================= 1. 控制面板 ======================= -->
|
||||||
|
<TabItem Header="控制">
|
||||||
|
<Grid Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="4*"/>
|
||||||
|
<ColumnDefinition Width="1*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 顶部:连接状态条 -->
|
||||||
|
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="White" BorderBrush="#D0D0D0" BorderThickness="1" CornerRadius="3" Padding="15,8" Margin="0,0,0,10">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Ellipse Width="14" Height="14" Margin="0,0,8,0">
|
||||||
|
<Ellipse.Style>
|
||||||
|
<Style TargetType="Ellipse">
|
||||||
|
<Setter Property="Fill" Value="#999999"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Fill" Value="#5CB85C"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Ellipse.Style>
|
||||||
|
</Ellipse>
|
||||||
|
<TextBlock Text="{Binding ConnectionStatus}" FontSize="16" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,20,0"/>
|
||||||
|
<Button Content="连接" Width="80" Margin="0" Style="{StaticResource ConnectButtonStyle}" Command="{Binding ConnectCommand}"/>
|
||||||
|
<Button Content="断开" Width="80" Margin="8,0,0,0" Style="{StaticResource StopButtonStyle}" Command="{Binding DisconnectCommand}"/>
|
||||||
|
<Button Content="刷新状态" Width="90" Margin="8,0,0,0" Command="{Binding ReadStatusCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 左中:位置显示 & 寸动目标 -->
|
||||||
|
<Grid Grid.Row="1" Grid.Column="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="1*"/>
|
||||||
|
<ColumnDefinition Width="1.5*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 6轴绝对位置 -->
|
||||||
|
<GroupBox Grid.Column="0">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock Text="绝对位置(只读)" Style="{StaticResource HeaderStyle}"/>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="轴1" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AbsPos1}" Style="{StaticResource ValueStyle}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="轴2" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding AbsPos2}" Style="{StaticResource ValueStyle}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="轴3" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding AbsPos3}" Style="{StaticResource ValueStyle}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="0" Text="轴4" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding AbsPos4}" Style="{StaticResource ValueStyle}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="0" Text="轴5" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding AbsPos5}" Style="{StaticResource ValueStyle}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="5" Grid.Column="0" Text="轴6" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBlock Grid.Row="5" Grid.Column="1" Text="{Binding AbsPos6}" Style="{StaticResource ValueStyle}"/>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 寸动目标位置 -->
|
||||||
|
<GroupBox Grid.Column="1">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock Text="寸动目标" Style="{StaticResource HeaderStyle}"/>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
<RowDefinition/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" Text="X目标" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TargetX}" Margin="5"/>
|
||||||
|
<Button Grid.Row="0" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveXCommand}"/>
|
||||||
|
<Button Grid.Row="0" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopXCommand}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="Y目标" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TargetY}" Margin="5"/>
|
||||||
|
<Button Grid.Row="1" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveYCommand}"/>
|
||||||
|
<Button Grid.Row="1" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopYCommand}"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="Z目标" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TargetZ}" Margin="5"/>
|
||||||
|
<Button Grid.Row="2" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveZCommand}"/>
|
||||||
|
<Button Grid.Row="2" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopZCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 右侧:控制面板 -->
|
||||||
|
<Grid Grid.Row="1" Grid.Column="1" Margin="6,6,0,6">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
|
<Button Content="↩ 回原点" Command="{Binding GoHomeCommand}"/>
|
||||||
|
<Button Content="⬆ 三轴同动" Command="{Binding MoveAllCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="单轴控制" Style="{StaticResource LabelStyle}" HorizontalAlignment="Center" Margin="0,10,0,10"/>
|
||||||
|
<Button Content="↕ X 移动" Command="{Binding MoveXCommand}"/>
|
||||||
|
<Button Content="↔ Y 移动" Command="{Binding MoveYCommand}"/>
|
||||||
|
<Button Content="↕ Z 移动" Command="{Binding MoveZCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Grid.Row="2" Content="❌ 急 停" Style="{StaticResource EmergencyButtonStyle}" FontWeight="Bold" FontSize="18" Height="55" Margin="0" Command="{Binding EmergencyStopCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
<!-- ======================= 2. 设置 ======================= -->
|
||||||
|
<TabItem Header="设置">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel Margin="10">
|
||||||
|
|
||||||
|
<!-- 移动速度设定(脉冲) -->
|
||||||
|
<GroupBox>
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock Text="移动速度设定" Style="{StaticResource HeaderStyle}"/>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<StackPanel Margin="5">
|
||||||
|
<Grid Margin="0,0,0,6">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="160"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="X速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding SpeedX}"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Margin="0,0,0,6">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="160"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="Y速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding SpeedY}"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Margin="0,0,0,6">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="160"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="Z速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding SpeedZ}"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Margin="0,0,0,10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="160"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="回零速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding HomeSpeed}"/>
|
||||||
|
</Grid>
|
||||||
|
<Button Content="速度设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetSpeedCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 零点设定 -->
|
||||||
|
<GroupBox>
|
||||||
|
<GroupBox.Header>
|
||||||
|
<TextBlock Text="零点设定" Style="{StaticResource HeaderStyle}"/>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<StackPanel Margin="5">
|
||||||
|
<Grid Margin="0,0,0,6">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="120"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="X零点" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding HomeX}"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Margin="0,0,0,6">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="120"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="Y零点" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding HomeY}"/>
|
||||||
|
</Grid>
|
||||||
|
<Grid Margin="0,0,0,10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="120"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="Z零点" Style="{StaticResource LabelStyle}"/>
|
||||||
|
<TextBox Grid.Column="1" Text="{Binding HomeZ}"/>
|
||||||
|
</Grid>
|
||||||
|
<Button Content="零点设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetHomeCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
28
BenchMovementModule/Views/BenchMovementView.xaml.cs
Normal file
28
BenchMovementModule/Views/BenchMovementView.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 BenchMovementModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// BenchMovementView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class BenchMovementView : UserControl
|
||||||
|
{
|
||||||
|
public BenchMovementView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
CANModule/CANModule.cs
Normal file
18
CANModule/CANModule.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using CANModule.Views;
|
||||||
|
|
||||||
|
namespace CANModule
|
||||||
|
{
|
||||||
|
public class CANModule : IModule
|
||||||
|
{
|
||||||
|
public void OnInitialized(IContainerProvider containerProvider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||||
|
{
|
||||||
|
containerRegistry.RegisterDialog<SelectCanMessageView>("SelectCanMessageView");
|
||||||
|
containerRegistry.RegisterDialog<CollectCANSignalSettingView>("CollectCANSignalSettingView");
|
||||||
|
containerRegistry.RegisterForNavigation<ZLGCANFDView>("ZLGCANFDView");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
CANModule/CANModule.csproj
Normal file
14
CANModule/CANModule.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
251
CANModule/ViewModels/CollectCANSingalSettingViewModel.cs
Normal file
251
CANModule/ViewModels/CollectCANSingalSettingViewModel.cs
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.PubEvent;
|
||||||
|
using UIShare.UIViewModel;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
using ZLGUSBCANFD;
|
||||||
|
|
||||||
|
namespace CANModule.ViewModels
|
||||||
|
{
|
||||||
|
public class CollectCANSignalSettingViewModel : DialogViewModelBase
|
||||||
|
{
|
||||||
|
#region 字段
|
||||||
|
private ZLGCANFD? _canfd;
|
||||||
|
private SystemConfig? _systemConfig;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性
|
||||||
|
public ObservableCollection<CANSignalConfig> ConfigurationList
|
||||||
|
{
|
||||||
|
get => _systemConfig?.ConfigurationList ?? new ObservableCollection<CANSignalConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private ObservableCollection<string> _messageList;
|
||||||
|
public ObservableCollection<string> MessageList
|
||||||
|
{
|
||||||
|
get => _messageList;
|
||||||
|
set => SetProperty(ref _messageList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<string> _signalList;
|
||||||
|
public ObservableCollection<string> SignalList
|
||||||
|
{
|
||||||
|
get => _signalList;
|
||||||
|
set => SetProperty(ref _signalList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _title = "信号采集设置";
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get => _title;
|
||||||
|
set => SetProperty(ref _title, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _selectedMessage;
|
||||||
|
public string SelectedMessage
|
||||||
|
{
|
||||||
|
get => _selectedMessage;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedMessage, value))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value) && _canfd != null)
|
||||||
|
{
|
||||||
|
var db = _canfd.DBCParser.MsgDatabase;
|
||||||
|
if (MessageChannel >= 0 && MessageChannel < db.Count)
|
||||||
|
{
|
||||||
|
var targetMsg = db[MessageChannel]
|
||||||
|
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
|
||||||
|
|
||||||
|
if (targetMsg != null && targetMsg.signal_Name != null)
|
||||||
|
{
|
||||||
|
SignalList = new ObservableCollection<string>(targetMsg.signal_Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SignalList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SignalList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CANSignalConfig _SelectedItem;
|
||||||
|
public CANSignalConfig SelectedItem
|
||||||
|
{
|
||||||
|
get => _SelectedItem;
|
||||||
|
set => SetProperty(ref _SelectedItem, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _selectedSignal;
|
||||||
|
public string SelectedSignal
|
||||||
|
{
|
||||||
|
get => _selectedSignal;
|
||||||
|
set => SetProperty(ref _selectedSignal, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _MessageChannel=-1;
|
||||||
|
public int MessageChannel
|
||||||
|
{
|
||||||
|
get => _MessageChannel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_MessageChannel != value)
|
||||||
|
{
|
||||||
|
_MessageChannel = value;
|
||||||
|
|
||||||
|
SelectedMessage = null;
|
||||||
|
SelectedSignal = null;
|
||||||
|
SignalList = new ObservableCollection<string>();
|
||||||
|
|
||||||
|
if (_canfd != null)
|
||||||
|
{
|
||||||
|
var db = _canfd.DBCParser.MsgDatabase;
|
||||||
|
if (db != null && value >= 0 && value < db.Count)
|
||||||
|
{
|
||||||
|
MessageList = new ObservableCollection<string>(
|
||||||
|
db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _collectionInterval = 1000;
|
||||||
|
public int CollectionInterval
|
||||||
|
{
|
||||||
|
get => _collectionInterval;
|
||||||
|
set => SetProperty(ref _collectionInterval, value);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
public ICommand AddCommand { get; set; }
|
||||||
|
public ICommand CloseCommand { get; set; }
|
||||||
|
public ICommand SaveCommand { get; set; }
|
||||||
|
public ICommand DeleteCommand { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public CollectCANSignalSettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
CloseCommand = new DelegateCommand(Close);
|
||||||
|
AddCommand = new DelegateCommand(Add);
|
||||||
|
SaveCommand = new DelegateCommand(Save);
|
||||||
|
DeleteCommand = new DelegateCommand(Delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Dialog 生命周期
|
||||||
|
public override void OnDialogOpened(IDialogParameters parameters)
|
||||||
|
{
|
||||||
|
_systemConfig = parameters.GetValue<SystemConfig>("SystemConfig");
|
||||||
|
_canfd = _systemConfig?.CANFD;
|
||||||
|
string title = _systemConfig.Title;
|
||||||
|
char lastChar = title[title.Length - 1];
|
||||||
|
// 3. "TestCellX"转int
|
||||||
|
MessageChannel = int.Parse(lastChar.ToString())-1;
|
||||||
|
if (MessageChannel >= 4) MessageChannel -= 4;
|
||||||
|
RaisePropertyChanged(nameof(ConfigurationList));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令处理
|
||||||
|
private void Delete()
|
||||||
|
{
|
||||||
|
if (SelectedItem != null)
|
||||||
|
{
|
||||||
|
_systemConfig?.ConfigurationList.Remove(SelectedItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Save()
|
||||||
|
{
|
||||||
|
ConfigService.Save(_systemConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void Add()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SelectedSignal) || string.IsNullOrEmpty(SelectedMessage) || CollectionInterval < 100)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_systemConfig == null) return;
|
||||||
|
|
||||||
|
string hexString = ExtractHex(SelectedMessage);
|
||||||
|
if (string.IsNullOrEmpty(hexString)) return;
|
||||||
|
|
||||||
|
int decimalValue = ConvertHexToDecimal(hexString);
|
||||||
|
if (_systemConfig.ConfigurationList.Any(x => x.SignalName == SelectedSignal && x.Channel == MessageChannel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_systemConfig.ConfigurationList.Add(new CANSignalConfig
|
||||||
|
{
|
||||||
|
CollectionID = Guid.NewGuid(),
|
||||||
|
Channel = MessageChannel,
|
||||||
|
MethodName = CANSignalBroadcaster.BuildMethodName((uint)decimalValue, SelectedSignal),
|
||||||
|
MessageName = ExtractMessageName(SelectedMessage),
|
||||||
|
SignalName = SelectedSignal,
|
||||||
|
MessageID = decimalValue,
|
||||||
|
CollectionInterval = CollectionInterval
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExtractHex(string input)
|
||||||
|
{
|
||||||
|
int startIndex = input.IndexOf("0x") + 2;
|
||||||
|
int endIndex = input.IndexOf("]", startIndex);
|
||||||
|
if (startIndex >= 2 && endIndex > startIndex)
|
||||||
|
{
|
||||||
|
return input.Substring(startIndex, endIndex - startIndex);
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ConvertHexToDecimal(string hex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Convert.ToInt32(hex, 16);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExtractMessageName(string input)
|
||||||
|
{
|
||||||
|
var parts = input.Split(']');
|
||||||
|
return parts.Length > 1 ? parts[1] : string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
RequestClose.Invoke();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
187
CANModule/ViewModels/SelectCanMessageViewModel.cs
Normal file
187
CANModule/ViewModels/SelectCanMessageViewModel.cs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
using ZLGUSBCANFD;
|
||||||
|
|
||||||
|
namespace CANModule.ViewModels
|
||||||
|
{
|
||||||
|
public class SelectCanMessageViewModel : DialogViewModelBase
|
||||||
|
{
|
||||||
|
#region 字段
|
||||||
|
private ZLGCANFD? _canfd;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性
|
||||||
|
public DialogCloseListener RequestClose { get; set; }
|
||||||
|
|
||||||
|
private ObservableCollection<int> _ChannelList;
|
||||||
|
public ObservableCollection<int> ChannelList
|
||||||
|
{
|
||||||
|
get => _ChannelList;
|
||||||
|
set => SetProperty(ref _ChannelList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<string> _messageList;
|
||||||
|
public ObservableCollection<string> MessageList
|
||||||
|
{
|
||||||
|
get => _messageList;
|
||||||
|
set => SetProperty(ref _messageList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<string> _signalList;
|
||||||
|
public ObservableCollection<string> SignalList
|
||||||
|
{
|
||||||
|
get => _signalList;
|
||||||
|
set => SetProperty(ref _signalList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _title = "CollectCANSignalSetting";
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get => _title;
|
||||||
|
set => SetProperty(ref _title, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _selectedMessage;
|
||||||
|
public string SelectedMessage
|
||||||
|
{
|
||||||
|
get => _selectedMessage;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _selectedMessage, value))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value) && _canfd != null)
|
||||||
|
{
|
||||||
|
var db = _canfd.DBCParser.MsgDatabase;
|
||||||
|
if (MessageChannel >= 0 && MessageChannel < db.Count)
|
||||||
|
{
|
||||||
|
var targetMsg = db[MessageChannel]
|
||||||
|
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
|
||||||
|
|
||||||
|
if (targetMsg != null && targetMsg.signal_Name != null)
|
||||||
|
{
|
||||||
|
SignalList = new ObservableCollection<string>(targetMsg.signal_Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SignalList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SignalList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _selectedSignal;
|
||||||
|
public string SelectedSignal
|
||||||
|
{
|
||||||
|
get => _selectedSignal;
|
||||||
|
set => SetProperty(ref _selectedSignal, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _MessageChannel = -1;
|
||||||
|
public int MessageChannel
|
||||||
|
{
|
||||||
|
get => _MessageChannel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_MessageChannel != value)
|
||||||
|
{
|
||||||
|
_MessageChannel = value;
|
||||||
|
|
||||||
|
// 1. 先安全清空旧的选择项和信号列表
|
||||||
|
SelectedMessage = null;
|
||||||
|
SelectedSignal = null;
|
||||||
|
SignalList = new ObservableCollection<string>();
|
||||||
|
|
||||||
|
// 2. 刷新当前通道的报文列表
|
||||||
|
if (_canfd != null)
|
||||||
|
{
|
||||||
|
var db = _canfd.DBCParser.MsgDatabase;
|
||||||
|
if (db != null && value >= 0 && value < db.Count)
|
||||||
|
{
|
||||||
|
MessageList = new ObservableCollection<string>(
|
||||||
|
db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MessageList = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
public ICommand CloseCommand { get; set; }
|
||||||
|
public ICommand GetSelectedMessageCommand { get; set; }
|
||||||
|
public ICommand GetSelectedSignalCommand { get; set; }
|
||||||
|
public ICommand LoadCommand { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public SelectCanMessageViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
CloseCommand = new DelegateCommand(Close);
|
||||||
|
GetSelectedMessageCommand = new DelegateCommand(GetSelectedMessage);
|
||||||
|
GetSelectedSignalCommand = new DelegateCommand(GetSelectedSignal);
|
||||||
|
LoadCommand = new DelegateCommand(Load);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Dialog 生命周期
|
||||||
|
public override void OnDialogOpened(IDialogParameters parameters)
|
||||||
|
{
|
||||||
|
_canfd = parameters.GetValue<ZLGCANFD>("CANFD");
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令处理
|
||||||
|
private void Close()
|
||||||
|
{
|
||||||
|
RequestClose.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Load()
|
||||||
|
{
|
||||||
|
int maxCh = _canfd?.DBCParser.MaxChannels ?? 4;
|
||||||
|
ChannelList = new ObservableCollection<int>(Enumerable.Range(0, maxCh));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetSelectedSignal()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(SelectedSignal))
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Clipboard.SetDataObject(SelectedSignal);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetSelectedMessage()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(SelectedMessage) && SelectedMessage.Contains(']'))
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Clipboard.SetDataObject(SelectedMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
649
CANModule/ViewModels/ZLGCANFDViewModel.cs
Normal file
649
CANModule/ViewModels/ZLGCANFDViewModel.cs
Normal file
@@ -0,0 +1,649 @@
|
|||||||
|
using Microsoft.Win32;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.UIViewModel;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
using ZLGUSBCANFD;
|
||||||
|
|
||||||
|
namespace CANModule.ViewModels
|
||||||
|
{
|
||||||
|
public class ZLGCANFDViewModel : NavigateViewModelBase
|
||||||
|
{
|
||||||
|
#region 字段
|
||||||
|
private ZLGCANFD? _canfd;
|
||||||
|
private SystemConfig? _systemConfig;
|
||||||
|
private CancellationTokenSource? _refreshCts;
|
||||||
|
private readonly DeviceManager _deviceManager;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性
|
||||||
|
private string _Title = "周立功CAN";
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get => _Title;
|
||||||
|
set => SetProperty(ref _Title, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<CanMessageShowVM> _CanMessageList = new();
|
||||||
|
public ObservableCollection<CanMessageShowVM> CanMessageList
|
||||||
|
{
|
||||||
|
get => _CanMessageList;
|
||||||
|
set => SetProperty(ref _CanMessageList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _DBCChannel = 0;
|
||||||
|
public int DBCChannel
|
||||||
|
{
|
||||||
|
get => _DBCChannel;
|
||||||
|
set => SetProperty(ref _DBCChannel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _IsDirectlySend = false;
|
||||||
|
public bool IsDirectlySend
|
||||||
|
{
|
||||||
|
get => _IsDirectlySend;
|
||||||
|
set => SetProperty(ref _IsDirectlySend, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<string> _Message;
|
||||||
|
public ObservableCollection<string> Message
|
||||||
|
{
|
||||||
|
get => _Message;
|
||||||
|
set => SetProperty(ref _Message, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<string> _Signal;
|
||||||
|
public ObservableCollection<string> Signal
|
||||||
|
{
|
||||||
|
get => _Signal;
|
||||||
|
set => SetProperty(ref _Signal, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte _channel;
|
||||||
|
private string _selectedMessage;
|
||||||
|
public string SelectedMessage
|
||||||
|
{
|
||||||
|
get => _selectedMessage;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_selectedMessage != value)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value) && _canfd != null)
|
||||||
|
{
|
||||||
|
var db = _canfd.DBCParser.MsgDatabase;
|
||||||
|
if (_channel >= 0 && _channel < db.Count)
|
||||||
|
{
|
||||||
|
var targetMsg = db[(int)_channel]
|
||||||
|
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
|
||||||
|
|
||||||
|
if (targetMsg != null && targetMsg.signal_Name != null)
|
||||||
|
{
|
||||||
|
Signal = new ObservableCollection<string>(targetMsg.signal_Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Signal = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Signal = new ObservableCollection<string>();
|
||||||
|
}
|
||||||
|
_selectedMessage = value;
|
||||||
|
RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _selectedSignal;
|
||||||
|
public string SelectedSignal
|
||||||
|
{
|
||||||
|
get => _selectedSignal;
|
||||||
|
set => SetProperty(ref _selectedSignal, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _signalValue;
|
||||||
|
public string SignalValue
|
||||||
|
{
|
||||||
|
get => _signalValue;
|
||||||
|
set => SetProperty(ref _signalValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _cycleSendInterval;
|
||||||
|
public string CycleSendInterval
|
||||||
|
{
|
||||||
|
get => _cycleSendInterval;
|
||||||
|
set => SetProperty(ref _cycleSendInterval, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _messageSendChannel = 0;
|
||||||
|
public int MessageSendChannel
|
||||||
|
{
|
||||||
|
get => _messageSendChannel;
|
||||||
|
set => SetProperty(ref _messageSendChannel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BLFPath
|
||||||
|
{
|
||||||
|
get => _systemConfig?.DefaultBLFFilePath ?? "";
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_systemConfig != null && _systemConfig.DefaultBLFFilePath != value)
|
||||||
|
{
|
||||||
|
_systemConfig.DefaultBLFFilePath = value;
|
||||||
|
RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string DBCPath
|
||||||
|
{
|
||||||
|
get => _systemConfig?.DefaultDBCFilePath ?? "";
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_systemConfig != null && _systemConfig.DefaultDBCFilePath != value)
|
||||||
|
{
|
||||||
|
_systemConfig.DefaultDBCFilePath = value;
|
||||||
|
RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _IsSaved;
|
||||||
|
public bool IsSaved
|
||||||
|
{
|
||||||
|
get => _IsSaved;
|
||||||
|
set => SetProperty(ref _IsSaved, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<AutoDBCLoadItem> DBCAutoLoadList
|
||||||
|
{
|
||||||
|
get => _systemConfig?.DBCAutoLoadList ?? new ObservableCollection<AutoDBCLoadItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private AutoDBCLoadItem _SelectedDcAutoLoad;
|
||||||
|
public AutoDBCLoadItem SelectedDcAutoLoad
|
||||||
|
{
|
||||||
|
get => _SelectedDcAutoLoad;
|
||||||
|
set => SetProperty(ref _SelectedDcAutoLoad, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 自定义报文发送属性 =====
|
||||||
|
private APP_CHANNEL[] _appChannelNames = Enum.GetValues<APP_CHANNEL>();
|
||||||
|
public APP_CHANNEL[] APP_CHANNEL
|
||||||
|
{
|
||||||
|
get => _appChannelNames;
|
||||||
|
set => SetProperty(ref _appChannelNames, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private APP_CHANNEL _aIdxChn;
|
||||||
|
public APP_CHANNEL AIdxChn
|
||||||
|
{
|
||||||
|
get => _aIdxChn;
|
||||||
|
set => SetProperty(ref _aIdxChn, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _aID;
|
||||||
|
public int AID
|
||||||
|
{
|
||||||
|
get => _aID;
|
||||||
|
set => SetProperty(ref _aID, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _aIsTx;
|
||||||
|
public bool AIsTx
|
||||||
|
{
|
||||||
|
get => _aIsTx;
|
||||||
|
set => SetProperty(ref _aIsTx, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _aIsExt;
|
||||||
|
public bool AIsExt
|
||||||
|
{
|
||||||
|
get => _aIsExt;
|
||||||
|
set => SetProperty(ref _aIsExt, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _aIsRemote;
|
||||||
|
public bool AIsRemote
|
||||||
|
{
|
||||||
|
get => _aIsRemote;
|
||||||
|
set => SetProperty(ref _aIsRemote, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte _aDlc;
|
||||||
|
public byte ADLC
|
||||||
|
{
|
||||||
|
get => _aDlc;
|
||||||
|
set => SetProperty(ref _aDlc, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] _aDataArray;
|
||||||
|
public byte[] ADataArray
|
||||||
|
{
|
||||||
|
get => _aDataArray;
|
||||||
|
set => SetProperty(ref _aDataArray, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _aIsFD = true;
|
||||||
|
public bool AIsFD
|
||||||
|
{
|
||||||
|
get => _aIsFD;
|
||||||
|
set => SetProperty(ref _aIsFD, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _aIsBRS = false;
|
||||||
|
public bool AIsBRS
|
||||||
|
{
|
||||||
|
get => _aIsBRS;
|
||||||
|
set => SetProperty(ref _aIsBRS, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _AutoSetting = true;
|
||||||
|
public bool AutoSetting
|
||||||
|
{
|
||||||
|
get => _AutoSetting;
|
||||||
|
set => SetProperty(ref _AutoSetting, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _CustomMessage = "[00,11,22,33,AA,BB,CC,DD]";
|
||||||
|
public string CustomMessage
|
||||||
|
{
|
||||||
|
get => _CustomMessage;
|
||||||
|
set => SetProperty(ref _CustomMessage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _CustomSendCycleInterval = 0.0f;
|
||||||
|
public float CustomSendCycleInterval
|
||||||
|
{
|
||||||
|
get => _CustomSendCycleInterval;
|
||||||
|
set => SetProperty(ref _CustomSendCycleInterval, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private bool _UpdateToDB;
|
||||||
|
//public bool UpdateToDB
|
||||||
|
//{
|
||||||
|
// get => _UpdateToDB;
|
||||||
|
// set => SetProperty(ref _UpdateToDB, value);
|
||||||
|
//}
|
||||||
|
|
||||||
|
// ===== 连接状态 =====
|
||||||
|
private bool _IsDeviceOpened;
|
||||||
|
public bool IsDeviceOpened
|
||||||
|
{
|
||||||
|
get => _IsDeviceOpened;
|
||||||
|
set => SetProperty(ref _IsDeviceOpened, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ObservableCollection<int> _ChannelList;
|
||||||
|
public ObservableCollection<int> ChannelList
|
||||||
|
{
|
||||||
|
get => _ChannelList;
|
||||||
|
set => SetProperty(ref _ChannelList, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _SelectedChannel = -1;
|
||||||
|
public int SelectedChannel
|
||||||
|
{
|
||||||
|
get => _SelectedChannel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_SelectedChannel != value)
|
||||||
|
{
|
||||||
|
_SelectedChannel = value;
|
||||||
|
if (value >= 0 && _canfd != null)
|
||||||
|
{
|
||||||
|
_canfd.初始化并启动通道((uint)value);
|
||||||
|
}
|
||||||
|
RaisePropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
public ICommand SelectBLFPathCommand { get; set; }
|
||||||
|
public ICommand SaveBLFPathCommand { get; set; }
|
||||||
|
public ICommand StartTranscribeCommand { get; set; }
|
||||||
|
public ICommand EndTranscribeCommand { get; set; }
|
||||||
|
public ICommand ConnectCanCommand { get; set; }
|
||||||
|
public ICommand CloseCanCommand { get; set; }
|
||||||
|
public ICommand ClearCycleCommand { get; set; }
|
||||||
|
public ICommand UpLoadDBCCommand { get; set; }
|
||||||
|
public ICommand ChangeDBCCommand { get; set; }
|
||||||
|
public ICommand LoadDBCCommand { get; set; }
|
||||||
|
public ICommand LoadMessageCommand { get; set; }
|
||||||
|
public ICommand SetAndSendMessageCommand { get; set; }
|
||||||
|
public ICommand DeleteDcAutoLoadCommand { get; set; }
|
||||||
|
public ICommand SendCustomMessageCommand { get; set; }
|
||||||
|
public ICommand LoadCommand { get; set; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public ZLGCANFDViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_systemConfig = containerProvider.Resolve<SystemConfig>();
|
||||||
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
_canfd = _deviceManager.CANFD;
|
||||||
|
SelectBLFPathCommand = new DelegateCommand(SelectBLFPath);
|
||||||
|
SaveBLFPathCommand = new DelegateCommand(SaveBLFPath);
|
||||||
|
StartTranscribeCommand = new DelegateCommand(StartTranscribe);
|
||||||
|
EndTranscribeCommand = new DelegateCommand(EndTranscribe);
|
||||||
|
ConnectCanCommand = new DelegateCommand(ConnectCan);
|
||||||
|
CloseCanCommand = new DelegateCommand(CloseCan);
|
||||||
|
ClearCycleCommand = new DelegateCommand(ClearCycle);
|
||||||
|
UpLoadDBCCommand = new DelegateCommand(UpLoadDBC);
|
||||||
|
ChangeDBCCommand = new DelegateCommand(ChangeDBC);
|
||||||
|
LoadDBCCommand = new DelegateCommand(LoadDBC);
|
||||||
|
SetAndSendMessageCommand = new DelegateCommand(SetAndSendMessage);
|
||||||
|
LoadMessageCommand = new DelegateCommand(LoadMessage);
|
||||||
|
DeleteDcAutoLoadCommand = new DelegateCommand(DeleteDcAutoLoad);
|
||||||
|
SendCustomMessageCommand = new DelegateCommand(SendCustomMessage);
|
||||||
|
_canfd.OnDbcMessageDecoded += OnDbcMessageDecoded;
|
||||||
|
LoadCommand = new DelegateCommand(Load);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Dialog 生命周期
|
||||||
|
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||||
|
{
|
||||||
|
if (_canfd != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RaisePropertyChanged(nameof(BLFPath));
|
||||||
|
RaisePropertyChanged(nameof(DBCPath));
|
||||||
|
RaisePropertyChanged(nameof(DBCAutoLoadList));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNavigatedFrom(NavigationContext navigationContext)
|
||||||
|
{
|
||||||
|
if (_canfd != null)
|
||||||
|
{
|
||||||
|
_canfd.OnDbcMessageDecoded -= OnDbcMessageDecoded;
|
||||||
|
}
|
||||||
|
_refreshCts?.Cancel();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令处理
|
||||||
|
private void Load()
|
||||||
|
{
|
||||||
|
int maxCh = _canfd?.DBCParser.MaxChannels ?? 4;
|
||||||
|
ChannelList = new ObservableCollection<int>(Enumerable.Range(0, maxCh));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendCustomMessage()
|
||||||
|
{
|
||||||
|
if (_canfd == null) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ADataArray = 报文转换(CustomMessage);
|
||||||
|
if (ADataArray.Length > byte.MaxValue) throw new Exception("报文数量超过上限!");
|
||||||
|
if (AutoSetting)
|
||||||
|
{
|
||||||
|
ADLC = (byte)ADataArray.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ShowErrorMessageBox($"报文转换失败:{ex.Message}", () => { });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_canfd.发送自定义报文(
|
||||||
|
(uint)AIdxChn,
|
||||||
|
AID.ToString(),
|
||||||
|
ADataArray,
|
||||||
|
ADLC,
|
||||||
|
AIsExt,
|
||||||
|
AIsFD,
|
||||||
|
AIsBRS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteDcAutoLoad()
|
||||||
|
{
|
||||||
|
if (SelectedDcAutoLoad != null)
|
||||||
|
{
|
||||||
|
DBCAutoLoadList.Remove(SelectedDcAutoLoad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadMessage()
|
||||||
|
{
|
||||||
|
if (_canfd == null) return;
|
||||||
|
|
||||||
|
if (MessageSendChannel >= 0)
|
||||||
|
{
|
||||||
|
var db = _canfd.DBCParser.MsgDatabase;
|
||||||
|
if (MessageSendChannel < db.Count)
|
||||||
|
{
|
||||||
|
Message = new ObservableCollection<string>(
|
||||||
|
db[MessageSendChannel].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetAndSendMessage()
|
||||||
|
{
|
||||||
|
if (_canfd == null) return;
|
||||||
|
|
||||||
|
if (int.TryParse(CycleSendInterval, out int sendPeriod) &&
|
||||||
|
double.TryParse(SignalValue, out double SValue) &&
|
||||||
|
MessageSendChannel >= 0)
|
||||||
|
{
|
||||||
|
_channel = (byte)MessageSendChannel;
|
||||||
|
var message = SelectedMessage.Split(']')[1];
|
||||||
|
|
||||||
|
// 通过 DBC 查找帧 ID
|
||||||
|
var db = _canfd.DBCParser.MsgDatabase;
|
||||||
|
if (MessageSendChannel < db.Count)
|
||||||
|
{
|
||||||
|
var targetMsg = db[MessageSendChannel]
|
||||||
|
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == SelectedMessage);
|
||||||
|
|
||||||
|
if (targetMsg != null)
|
||||||
|
{
|
||||||
|
_canfd.设置报文((uint)MessageSendChannel, targetMsg.msg_id.ToString(), SelectedSignal, SValue, sendPeriod, IsDirectlySend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpLoadDBC()
|
||||||
|
{
|
||||||
|
if (_canfd == null) return;
|
||||||
|
|
||||||
|
// 释放当前通道的 DBC
|
||||||
|
if (DBCChannel >= 0)
|
||||||
|
{
|
||||||
|
_canfd.释放通道DBC((uint)DBCChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangeDBC()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Title = "更换DBC文件",
|
||||||
|
Filter = "DBC 文件 (*.dbc)|*.dbc|所有文件 (*.*)|*.*",
|
||||||
|
DefaultExt = ".dbc",
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
DBCPath = System.IO.Path.GetFullPath(dialog.FileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadDBC()
|
||||||
|
{
|
||||||
|
if (_canfd == null || DBCChannel < 0) return;
|
||||||
|
|
||||||
|
bool re = _canfd.加载通道DBC文件((uint)DBCChannel, DBCPath);
|
||||||
|
|
||||||
|
if (re && IsSaved)
|
||||||
|
{
|
||||||
|
var item = DBCAutoLoadList.FirstOrDefault(s => s.DBCChannel == DBCChannel);
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
DBCAutoLoadList.Add(new AutoDBCLoadItem
|
||||||
|
{
|
||||||
|
DBCFilePath = DBCPath,
|
||||||
|
DBCChannel = DBCChannel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DBCAutoLoadList.Remove(item);
|
||||||
|
DBCAutoLoadList.Add(new AutoDBCLoadItem
|
||||||
|
{
|
||||||
|
DBCFilePath = DBCPath,
|
||||||
|
DBCChannel = DBCChannel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ConfigService.Save(_systemConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void ClearCycle()
|
||||||
|
{
|
||||||
|
_canfd?.停止所有循环发送();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseCan()
|
||||||
|
{
|
||||||
|
if (_canfd == null) return;
|
||||||
|
|
||||||
|
_canfd.关闭CAN卡设备();
|
||||||
|
IsDeviceOpened = false;
|
||||||
|
_refreshCts?.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConnectCan()
|
||||||
|
{
|
||||||
|
if (_canfd == null) return;
|
||||||
|
|
||||||
|
bool re = _canfd.打开设备();
|
||||||
|
IsDeviceOpened = re;
|
||||||
|
|
||||||
|
if (re)
|
||||||
|
{
|
||||||
|
// 启动实时报文刷新
|
||||||
|
_refreshCts?.Cancel();
|
||||||
|
_refreshCts = new CancellationTokenSource();
|
||||||
|
_ = Refresh(_refreshCts.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndTranscribe()
|
||||||
|
{
|
||||||
|
// TODO: BLF 录制功能在 ZLGCANFD 中未实现,暂不支持
|
||||||
|
ShowInfoMessageBox("BLF 录制功能尚未在此硬件驱动中实现。", () => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartTranscribe()
|
||||||
|
{
|
||||||
|
// TODO: BLF 录制功能在 ZLGCANFD 中未实现,暂不支持
|
||||||
|
ShowInfoMessageBox("BLF 录制功能尚未在此硬件驱动中实现。", () => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveBLFPath()
|
||||||
|
{
|
||||||
|
// 路径已直接绑定到 SystemConfig,无需额外操作
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectBLFPath()
|
||||||
|
{
|
||||||
|
var dialog = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Title = "保存 CAN报文回放 BLF 文件",
|
||||||
|
Filter = "BLF 文件 (*.blf)|*.blf|所有文件 (*.*)|*.*",
|
||||||
|
DefaultExt = ".blf",
|
||||||
|
FileName = "log.blf",
|
||||||
|
OverwritePrompt = true
|
||||||
|
};
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
BLFPath = System.IO.Path.GetFullPath(dialog.FileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助方法
|
||||||
|
public byte[] 报文转换(string s)
|
||||||
|
{
|
||||||
|
s = s.Replace("[", string.Empty).Replace("]", string.Empty);
|
||||||
|
var strs = s.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
byte[] bytes = new byte[strs.Length];
|
||||||
|
for (int i = 0; i < strs.Length; i++)
|
||||||
|
{
|
||||||
|
bytes[i] = Convert.ToByte(strs[i], 16);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Refresh(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// 通过 OnDbcMessageDecoded 事件已实时更新 CanMessageList
|
||||||
|
// 此处仅做周期性 UI 刷新触发
|
||||||
|
await Task.Delay(50, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDbcMessageDecoded(uint channel, ZDBC.DBCMessage msg)
|
||||||
|
{
|
||||||
|
// DBC 解码后的报文回调
|
||||||
|
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||||
|
{
|
||||||
|
var msgName = System.Text.Encoding.Default.GetString(msg.strName).TrimEnd('\0');
|
||||||
|
var find = CanMessageList.FirstOrDefault(s => s.报文ID == (int)msg.nID&& s.通道==channel);
|
||||||
|
if (find != null)
|
||||||
|
{
|
||||||
|
find.通道 = (byte)channel;
|
||||||
|
find.报文ID = (int)msg.nID;
|
||||||
|
find.时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds(); ;
|
||||||
|
find.长度 = (byte)msg.nSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CanMessageList.Add(new CanMessageShowVM
|
||||||
|
{
|
||||||
|
通道 = (byte)channel,
|
||||||
|
报文ID = (int)msg.nID,
|
||||||
|
时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds(),
|
||||||
|
长度 = (byte)msg.nSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private string 十六进制字符串转换(System.Span<byte> s)
|
||||||
|
{
|
||||||
|
if (s == null || s.Length == 0)
|
||||||
|
return "[]";
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append('[');
|
||||||
|
for (int i = 0; i < s.Length; i++)
|
||||||
|
{
|
||||||
|
sb.Append("0x");
|
||||||
|
sb.Append(s[i].ToString("X2"));
|
||||||
|
if (i < s.Length - 1)
|
||||||
|
sb.Append(", ");
|
||||||
|
}
|
||||||
|
sb.Append(']');
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
120
CANModule/Views/CollectCANSignalSettingView.xaml
Normal file
120
CANModule/Views/CollectCANSignalSettingView.xaml
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
<UserControl x:Class="CANModule.Views.CollectCANSignalSettingView"
|
||||||
|
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:local="clr-namespace:CANModule.Views"
|
||||||
|
xmlns:oxy="http://oxyplot.org/wpf"
|
||||||
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
Background="White"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
Height="400"
|
||||||
|
Width="1000">
|
||||||
|
<prism:Dialog.WindowStyle>
|
||||||
|
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||||
|
TargetType="Window" />
|
||||||
|
</prism:Dialog.WindowStyle>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<cons:HexConverter x:Key="HexConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<GroupBox Padding="0,0,0,0"
|
||||||
|
MouseLeftButtonDown="MouseLeftButtonDown">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<Grid Margin="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="CAN信号采集设置"
|
||||||
|
Foreground="White"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="5,0,10,0" />
|
||||||
|
<Button Content="关闭"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="150 0 0 0"
|
||||||
|
Width="60"
|
||||||
|
Command="{Binding CloseCommand}"
|
||||||
|
Height="25" />
|
||||||
|
</Grid>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<DataGrid x:Name="配置表格"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
ItemsSource="{Binding ConfigurationList}"
|
||||||
|
SelectedItem="{Binding SelectedItem}"
|
||||||
|
IsReadOnly="True">
|
||||||
|
<DataGrid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="删除"
|
||||||
|
Command="{Binding DeleteCommand}"
|
||||||
|
/>
|
||||||
|
</ContextMenu>
|
||||||
|
</DataGrid.ContextMenu>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="通道"
|
||||||
|
Binding="{Binding Channel}" />
|
||||||
|
<DataGridTextColumn Header="报文ID"
|
||||||
|
Binding="{Binding MessageID,Converter={StaticResource HexConverter}}" />
|
||||||
|
<DataGridTextColumn Header="报文名称"
|
||||||
|
Binding="{Binding MessageName}" />
|
||||||
|
<DataGridTextColumn Header="信号名称"
|
||||||
|
Binding="{Binding SignalName}" />
|
||||||
|
<DataGridTextColumn Header="采集间隔"
|
||||||
|
Binding="{Binding CollectionInterval}" />
|
||||||
|
<DataGridTextColumn Header="采集id"
|
||||||
|
Binding="{Binding CollectionID}" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="10">
|
||||||
|
<Label Content="通道(从0开始)"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<TextBox Name="SignalComboBox"
|
||||||
|
Text="{Binding MessageChannel}"
|
||||||
|
MaxWidth="200"
|
||||||
|
MinWidth="120"
|
||||||
|
Margin="10"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
materialDesign:HintAssist.Hint="信号通道" />
|
||||||
|
<Label Content="报文"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ComboBox MinWidth="100"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
SelectedItem="{Binding SelectedMessage,Mode=TwoWay}"
|
||||||
|
ItemsSource="{Binding MessageList}"
|
||||||
|
/>
|
||||||
|
<Label Content="信号"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ComboBox SelectedItem="{Binding SelectedSignal}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MaxWidth="200"
|
||||||
|
ItemsSource="{Binding SignalList,Mode=TwoWay}"
|
||||||
|
MinWidth="100" />
|
||||||
|
<Label Content="采集间隔(ms)"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<TextBox Text="{Binding CollectionInterval}"
|
||||||
|
MinWidth="100"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<Button Content="添加"
|
||||||
|
Margin="10"
|
||||||
|
Command="{Binding AddCommand}"
|
||||||
|
/>
|
||||||
|
<Button Content="保存"
|
||||||
|
Margin="10"
|
||||||
|
Command="{Binding SaveCommand}"
|
||||||
|
/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
</UserControl>
|
||||||
25
CANModule/Views/CollectCANSignalSettingView.xaml.cs
Normal file
25
CANModule/Views/CollectCANSignalSettingView.xaml.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace CANModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CollectCANSignalSettingView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class CollectCANSignalSettingView : UserControl
|
||||||
|
{
|
||||||
|
public CollectCANSignalSettingView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
{
|
||||||
|
Window.GetWindow(this)?.DragMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
CANModule/Views/SelectCanMessageView.xaml
Normal file
69
CANModule/Views/SelectCanMessageView.xaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<UserControl x:Class="CANModule.Views.SelectCanMessageView"
|
||||||
|
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:local="clr-namespace:CANModule.Views"
|
||||||
|
xmlns:oxy="http://oxyplot.org/wpf"
|
||||||
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
Background="White"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
Height="350"
|
||||||
|
Width="350">
|
||||||
|
<prism:Dialog.WindowStyle>
|
||||||
|
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||||
|
TargetType="Window" />
|
||||||
|
</prism:Dialog.WindowStyle>
|
||||||
|
<i:Interaction.Triggers>
|
||||||
|
<i:EventTrigger EventName="Loaded">
|
||||||
|
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
|
<GroupBox Padding="0,0,0,0"
|
||||||
|
MouseLeftButtonDown="MouseLeftButtonDown">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<Grid Margin="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="CAN报文选择"
|
||||||
|
Foreground="White"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="5,0,10,0" />
|
||||||
|
<Button Content="关闭"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="0 0 0 0"
|
||||||
|
Width="60"
|
||||||
|
Command="{Binding CloseCommand}"
|
||||||
|
Height="25" />
|
||||||
|
</Grid>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Grid.Row="1" Margin="10">
|
||||||
|
<Label Content="通道" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox ItemsSource="{Binding ChannelList}"
|
||||||
|
SelectedItem="{Binding MessageChannel,Mode=TwoWay}"
|
||||||
|
MinWidth="100"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
materialDesign:HintAssist.Hint="" />
|
||||||
|
<Label Content="报文" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="100"
|
||||||
|
SelectedItem="{Binding SelectedMessage,Mode=TwoWay}"
|
||||||
|
ItemsSource="{Binding MessageList}" />
|
||||||
|
<Label Content="信号" VerticalAlignment="Center"/>
|
||||||
|
<ComboBox materialDesign:HintAssist.Hint=""
|
||||||
|
SelectedItem="{Binding SelectedSignal,Mode=TwoWay}"
|
||||||
|
ItemsSource="{Binding SignalList}" MinWidth="100" />
|
||||||
|
<Button Content="选择报文名" Margin="10" Command="{Binding GetSelectedMessageCommand}"/>
|
||||||
|
<Button Content="选择信号名" Margin="10" Command="{Binding GetSelectedSignalCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
</UserControl>
|
||||||
36
CANModule/Views/SelectCanMessageView.xaml.cs
Normal file
36
CANModule/Views/SelectCanMessageView.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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 CANModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SelectCanMessageView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class SelectCanMessageView : UserControl
|
||||||
|
{
|
||||||
|
public SelectCanMessageView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
{
|
||||||
|
Window.GetWindow(this)?.DragMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
286
CANModule/Views/ZLGCANFDView.xaml
Normal file
286
CANModule/Views/ZLGCANFDView.xaml
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
<UserControl x:Class="CANModule.Views.ZLGCANFDView"
|
||||||
|
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:local="clr-namespace:CANModule.Views"
|
||||||
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
Background="White"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
Height="850"
|
||||||
|
Width="900">
|
||||||
|
<prism:Dialog.WindowStyle>
|
||||||
|
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||||
|
TargetType="Window" />
|
||||||
|
</prism:Dialog.WindowStyle>
|
||||||
|
<i:Interaction.Triggers>
|
||||||
|
<i:EventTrigger EventName="Loaded">
|
||||||
|
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
|
||||||
|
</i:EventTrigger>
|
||||||
|
</i:Interaction.Triggers>
|
||||||
|
<UserControl.Resources>
|
||||||
|
<cons:HexConverter x:Key="HexConverter" />
|
||||||
|
</UserControl.Resources>
|
||||||
|
<GroupBox Padding="0,0,0,0"
|
||||||
|
>
|
||||||
|
<GroupBox.Header>
|
||||||
|
<Grid Margin="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="CAN手动设置"
|
||||||
|
Foreground="White"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="5,0,10,0" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- CAN卡连接 -->
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Margin="5"
|
||||||
|
Grid.Row="0">
|
||||||
|
<Button Content="打开设备"
|
||||||
|
Command="{Binding ConnectCanCommand}"
|
||||||
|
Margin="5" />
|
||||||
|
<Button Content="关闭设备"
|
||||||
|
Command="{Binding CloseCanCommand}"
|
||||||
|
Margin="5" />
|
||||||
|
<Button Content="清空循环发送报文"
|
||||||
|
Command="{Binding ClearCycleCommand}"
|
||||||
|
Margin="5" />
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Margin="5"
|
||||||
|
Grid.Row="1">
|
||||||
|
<Label Content="选择通道初始化(选择前先打开设备)"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ComboBox SelectedItem="{Binding SelectedChannel}"
|
||||||
|
ItemsSource="{Binding ChannelList}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="80"
|
||||||
|
Margin="5"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
<TreeView Grid.Row="2">
|
||||||
|
<!-- 监视选项 -->
|
||||||
|
<TreeViewItem Header="监视">
|
||||||
|
<DataGrid x:Name="表格"
|
||||||
|
Width="550"
|
||||||
|
Height="350"
|
||||||
|
VirtualizingPanel.IsVirtualizing="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
ItemsSource="{Binding CanMessageList, Mode=OneWay}">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="通道" Binding="{Binding 通道}" Width="90" />
|
||||||
|
<DataGridTextColumn Header="报文ID" Binding="{Binding 报文ID, Converter={StaticResource HexConverter}}" Width="150" />
|
||||||
|
<DataGridTextColumn Header="长度" Binding="{Binding 长度}" Width="90" />
|
||||||
|
<DataGridTextColumn Header="时间戳" Binding="{Binding 时间戳}" Width="Auto" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</TreeViewItem>
|
||||||
|
|
||||||
|
<!-- DBC 配置 -->
|
||||||
|
<TreeViewItem Header="DBC">
|
||||||
|
<Label Content="请注意,要在断开CAN卡的情况下才能操作DBC!"
|
||||||
|
Foreground="Red" />
|
||||||
|
<Button Content="卸载当前通道DBC"
|
||||||
|
Command="{Binding UpLoadDBCCommand}" />
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Content="更换DBC"
|
||||||
|
Command="{Binding ChangeDBCCommand}" />
|
||||||
|
<Label Content="DBC文件路径"
|
||||||
|
VerticalContentAlignment="Center" />
|
||||||
|
<TextBox Text="{Binding DBCPath,Mode=TwoWay}"
|
||||||
|
MinWidth="100"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Content="加载DBC"
|
||||||
|
Command="{Binding LoadDBCCommand}" />
|
||||||
|
<Label Content="通道(从0开始)"
|
||||||
|
VerticalContentAlignment="Center" />
|
||||||
|
<TextBox Text="{Binding DBCChannel,Mode=TwoWay}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="100"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<CheckBox Margin="5 0 0 0"
|
||||||
|
IsChecked="{Binding IsSaved}" />
|
||||||
|
<Label Content="加载记录保存"
|
||||||
|
VerticalContentAlignment="Center" />
|
||||||
|
</StackPanel>
|
||||||
|
<DataGrid ItemsSource="{Binding DBCAutoLoadList}"
|
||||||
|
Width="650"
|
||||||
|
IsReadOnly="True"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
SelectedItem="{Binding SelectedDcAutoLoad}">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="通道"
|
||||||
|
Binding="{Binding DBCChannel}"
|
||||||
|
Width="60" />
|
||||||
|
<DataGridTextColumn Header="文件路径"
|
||||||
|
Binding="{Binding DBCFilePath}"
|
||||||
|
Width="*" />
|
||||||
|
</DataGrid.Columns>
|
||||||
|
<DataGrid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="删除"
|
||||||
|
Foreground="Red"
|
||||||
|
Command="{Binding DeleteDcAutoLoadCommand}" />
|
||||||
|
</ContextMenu>
|
||||||
|
</DataGrid.ContextMenu>
|
||||||
|
</DataGrid>
|
||||||
|
</TreeViewItem>
|
||||||
|
|
||||||
|
<!-- DBC报文发送 -->
|
||||||
|
<TreeViewItem Header="DBC报文发送">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Label Content="通道(从0开始)"
|
||||||
|
VerticalContentAlignment="Center" />
|
||||||
|
<ComboBox SelectedValue="{Binding MessageSendChannel}"
|
||||||
|
SelectedValuePath="Content"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="60"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,5,5,5">
|
||||||
|
<ComboBoxItem Content="0"/>
|
||||||
|
<ComboBoxItem Content="1"/>
|
||||||
|
<ComboBoxItem Content="2"/>
|
||||||
|
<ComboBoxItem Content="3"/>
|
||||||
|
</ComboBox>
|
||||||
|
<Button Content="加载报文"
|
||||||
|
Command="{Binding LoadMessageCommand}"
|
||||||
|
Margin="5" />
|
||||||
|
</StackPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="报文" />
|
||||||
|
<ComboBox MinWidth="100"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
SelectedItem="{Binding SelectedMessage}"
|
||||||
|
ItemsSource="{Binding Message}" />
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="信号" />
|
||||||
|
<ComboBox SelectedItem="{Binding SelectedSignal}"
|
||||||
|
ItemsSource="{Binding Signal}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="100" />
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="信号值" />
|
||||||
|
<TextBox Text="{Binding SignalValue}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="100" />
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="循环发送时间间隔(ms)(0为单次发送)" />
|
||||||
|
<TextBox Text="{Binding CycleSendInterval}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="100" />
|
||||||
|
</DockPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Content="设置信号并发送"
|
||||||
|
Command="{Binding SetAndSendMessageCommand}" />
|
||||||
|
<CheckBox Content="直接发送"
|
||||||
|
IsChecked="{Binding IsDirectlySend}" />
|
||||||
|
</StackPanel>
|
||||||
|
</TreeViewItem>
|
||||||
|
|
||||||
|
<!-- 自定义报文发送 -->
|
||||||
|
<TreeViewItem Header="自定义报文发送">
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="通道" />
|
||||||
|
<ComboBox MinWidth="100"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
SelectedItem="{Binding AIdxChn}"
|
||||||
|
ItemsSource="{Binding APP_CHANNEL}" />
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="ID" />
|
||||||
|
<TextBox MinWidth="100"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding AID, Converter={StaticResource HexConverter}, Mode=TwoWay}" />
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="DLC" />
|
||||||
|
<TextBox MinWidth="200"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding ADLC}" />
|
||||||
|
<CheckBox Content="自动设定"
|
||||||
|
IsChecked="{Binding AutoSetting}" />
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="报文" />
|
||||||
|
<TextBox MinWidth="200"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding CustomMessage}" />
|
||||||
|
</DockPanel>
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="循环发送时间间隔(ms)(0为单次发送)" />
|
||||||
|
<TextBox Text="{Binding CustomSendCycleInterval}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="100" />
|
||||||
|
</DockPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<CheckBox Content="发送帧"
|
||||||
|
IsChecked="{Binding AIsTx}" />
|
||||||
|
<CheckBox Content="扩展帧"
|
||||||
|
IsChecked="{Binding AIsExt}" />
|
||||||
|
<CheckBox Content="远程帧"
|
||||||
|
IsChecked="{Binding AIsRemote}" />
|
||||||
|
<CheckBox Content="FD"
|
||||||
|
IsChecked="{Binding AIsFD}" />
|
||||||
|
<CheckBox Content="BRS"
|
||||||
|
IsChecked="{Binding AIsBRS}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Content="发送"
|
||||||
|
Command="{Binding SendCustomMessageCommand}" />
|
||||||
|
<!--<CheckBox Margin="10"
|
||||||
|
Content="更新到数据库"
|
||||||
|
IsChecked="{Binding UpdateToDB}" />-->
|
||||||
|
</StackPanel>
|
||||||
|
</TreeViewItem>
|
||||||
|
|
||||||
|
<!-- 报文录制 -->
|
||||||
|
<TreeViewItem Header="报文录制">
|
||||||
|
<DockPanel>
|
||||||
|
<Label Content="路径(开始录制时使用)"
|
||||||
|
VerticalContentAlignment="Center" />
|
||||||
|
<TextBox MinWidth="100"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding BLFPath}"
|
||||||
|
VerticalContentAlignment="Center" />
|
||||||
|
<Button Content="选择路径"
|
||||||
|
Margin="10 0"
|
||||||
|
Command="{Binding SelectBLFPathCommand}" />
|
||||||
|
<Button Content="保存路径"
|
||||||
|
Command="{Binding SaveBLFPathCommand}" />
|
||||||
|
</DockPanel>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Content="开始录制"
|
||||||
|
Command="{Binding StartTranscribeCommand}" />
|
||||||
|
<Button Content="结束录制"
|
||||||
|
Margin="10 0"
|
||||||
|
Command="{Binding EndTranscribeCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</TreeViewItem>
|
||||||
|
</TreeView>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
</UserControl>
|
||||||
35
CANModule/Views/ZLGCANFDView.xaml.cs
Normal file
35
CANModule/Views/ZLGCANFDView.xaml.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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 CANModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ZLGCANFDView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class ZLGCANFDView : UserControl
|
||||||
|
{
|
||||||
|
public ZLGCANFDView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
{
|
||||||
|
Window.GetWindow(this)?.DragMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
417
CAN驱动/CAN.cs
417
CAN驱动/CAN.cs
@@ -1,417 +0,0 @@
|
|||||||
using Common.Attributes;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.Json;
|
|
||||||
using TSMaster;
|
|
||||||
namespace CAN驱动
|
|
||||||
{
|
|
||||||
[ADPCommand]
|
|
||||||
public class CAN
|
|
||||||
{
|
|
||||||
|
|
||||||
public static event Action ConnectEvent;
|
|
||||||
public static event Action DisConnectEvent;
|
|
||||||
public static bool ConnectFlag { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化CAN驱动
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ProjectName"></param>
|
|
||||||
/// <param name="filePath"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int Init(string ProjectName, string? filePath = null)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(filePath))
|
|
||||||
{
|
|
||||||
return TsMasterApi.initialize_lib_tsmaster(ProjectName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return TsMasterApi.initialize_lib_tsmaster_with_project(ProjectName, Path.GetFullPath(filePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 释放CAN驱动
|
|
||||||
/// </summary>
|
|
||||||
[Browsable(false)]
|
|
||||||
public static void Release()
|
|
||||||
{
|
|
||||||
TsMasterApi.finalize_lib_tsmaster();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int obj = 0;
|
|
||||||
public static TCANFDQueueEvent_Win32 listener;
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int RegisterListener(TCANFDQueueEvent_Win32 listenEvent)
|
|
||||||
{
|
|
||||||
listener = listenEvent;
|
|
||||||
var re = TsMasterApi.tsapp_register_event_canfd(ref obj, listener);
|
|
||||||
Debug.Assert(re == 0);
|
|
||||||
return re;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int UnRegisterListener(TCANFDQueueEvent_Win32 listenEvent = null)
|
|
||||||
{
|
|
||||||
if (listenEvent is not null)
|
|
||||||
{
|
|
||||||
listener = listenEvent;
|
|
||||||
}
|
|
||||||
var re = TsMasterApi.tsapp_unregister_event_canfd(ref obj, listener);
|
|
||||||
Debug.Assert(re == 0);
|
|
||||||
return re;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 连接
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int Connect()
|
|
||||||
{
|
|
||||||
|
|
||||||
var re = TsMasterApi.tsapp_connect();
|
|
||||||
if (re == 0)
|
|
||||||
{
|
|
||||||
re = TsMasterApi.tscom_can_rbs_start();
|
|
||||||
if (re == 0)
|
|
||||||
{
|
|
||||||
ConnectFlag = true;
|
|
||||||
Task.Run(() => ConnectEvent?.Invoke());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"CAN_RBS启动失败,错误代码:{re}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"CAN连接失败,错误代码:{re}");
|
|
||||||
return re;
|
|
||||||
}
|
|
||||||
return re;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 断开
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int DisConnect()
|
|
||||||
{
|
|
||||||
var re = TsMasterApi.tsapp_disconnect();
|
|
||||||
//Debug.Assert(re == 0);
|
|
||||||
if (re == 0)
|
|
||||||
{
|
|
||||||
ConnectFlag = false;
|
|
||||||
Task.Run(() => DisConnectEvent?.Invoke());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"断开CAN连接失败,错误代码:{re}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return re;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载DBC
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath"></param>
|
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="databaseID"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int LoadDBC(string filePath, int[] channel, out uint databaseID)
|
|
||||||
{
|
|
||||||
databaseID = 0;
|
|
||||||
|
|
||||||
var re = TsMasterApi.tsdb_load_can_db(Path.GetFullPath(filePath), string.Join(",", channel), ref databaseID);
|
|
||||||
foreach (var item in channel)
|
|
||||||
{
|
|
||||||
DBCParse.parse(databaseID, item);
|
|
||||||
DBCParse.rbs_parse(databaseID, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return re;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 卸载DBC
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="databaseID"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int UnLoadDBC(uint? databaseID = null)
|
|
||||||
{
|
|
||||||
if (databaseID == null)
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsdb_unload_can_dbs();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsdb_unload_can_db(databaseID.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 开始记录日志
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int StartLogging(string filePath)
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 结束记录日志
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int StopLogging()
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_stop_logging();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 弹出通道映射窗口
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isWait"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int ShowChannelMappingWindow(bool isWait = false)
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_show_tsmaster_window("Hardware", isWait);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取信号值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="AMsgName"></param>
|
|
||||||
/// <param name="ASgnName"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static double GetSignalValue(byte channel, string AMsgName, string ASgnName)
|
|
||||||
{
|
|
||||||
double value = double.NaN;
|
|
||||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
|
||||||
var re = TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取信号值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="AMsgName"></param>
|
|
||||||
/// <param name="ASgnName"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int GetSignalValue(byte channel, string AMsgName, string ASgnName, ref double value)
|
|
||||||
{
|
|
||||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
|
||||||
return TsMasterApi.tsdb_get_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, ref value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取信号值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="ACANFD"></param>
|
|
||||||
/// <param name="AMsgName"></param>
|
|
||||||
/// <param name="ASgnName"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int GetSignalValue(ref TLIBCANFD ACANFD, string AMsgName, string ASgnName, ref double value)
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsdb_get_signal_value_canfd(ref ACANFD, AMsgName, ASgnName, ref value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置信号值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="AMsgName"></param>
|
|
||||||
/// <param name="ASgnName"></param>
|
|
||||||
/// <param name="AValue"></param>
|
|
||||||
/// <param name="isSend"></param>
|
|
||||||
/// <param name="sendPeriod"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <exception cref="Exception"></exception>
|
|
||||||
public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0)
|
|
||||||
{
|
|
||||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
|
||||||
find.ACANFD.FIdxChn = channel;
|
|
||||||
var re = TsMasterApi.tsdb_set_signal_value_canfd(ref find.ACANFD, AMsgName, ASgnName, AValue);
|
|
||||||
Debug.Assert(re == 0);
|
|
||||||
if (re != 0) throw new Exception($"设置报文失败!返回代码{re}");
|
|
||||||
if (re != 0) return re;
|
|
||||||
if (isSend)
|
|
||||||
{
|
|
||||||
if (sendPeriod == 0)
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置信号值
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="AMsgName"></param>
|
|
||||||
/// <param name="sendPeriod"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0)
|
|
||||||
{
|
|
||||||
var find = DBCParse.MsgDatabase[(byte)channel].First(s => s.msg_name == AMsgName);
|
|
||||||
find.ACANFD.FIdxChn = (byte)channel;
|
|
||||||
if (sendPeriod == 0)
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_transmit_canfd_async(ref find.ACANFD);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送自定义报文
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
[Browsable(false)]
|
|
||||||
public static int SetMsg(TLIBCANFD msg)
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_transmit_canfd_async(ref msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置报文
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel">通道</param>
|
|
||||||
/// <param name="ID">DBC数据库ID</param>
|
|
||||||
/// <param name="bytes">报文数组</param>
|
|
||||||
public static void SetMsg(APP_CHANNEL channel, int ID, byte[] bytes)
|
|
||||||
{
|
|
||||||
var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.ACANFD.FIdentifier == ID);
|
|
||||||
if (find != null)
|
|
||||||
{
|
|
||||||
Array.Copy(bytes, 0, find.ACANFD.FData, 0, Math.Min(bytes.Length, find.ACANFD.FData.Length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 发送自定义报文
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="AIdxChn"></param>
|
|
||||||
/// <param name="AID"></param>
|
|
||||||
/// <param name="AIsTx"></param>
|
|
||||||
/// <param name="AIsExt"></param>
|
|
||||||
/// <param name="AIsRemote"></param>
|
|
||||||
/// <param name="ADLC"></param>
|
|
||||||
/// <param name="ADataArray"></param>
|
|
||||||
/// <param name="AIsFD"></param>
|
|
||||||
/// <param name="AIsBRS"></param>
|
|
||||||
/// <param name="isUpdateDBCDatabase"></param>
|
|
||||||
/// <param name="sendPeriod"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int SendMsg(APP_CHANNEL AIdxChn, int AID, bool AIsTx, bool AIsExt,
|
|
||||||
bool AIsRemote, byte ADLC, byte[] ADataArray, bool AIsFD = true, bool AIsBRS = false,
|
|
||||||
bool isUpdateDBCDatabase = false, float sendPeriod = 0)
|
|
||||||
{
|
|
||||||
var send = new TLIBCANFD
|
|
||||||
{
|
|
||||||
FIdxChn = (byte)AIdxChn,
|
|
||||||
FProperties = 0,
|
|
||||||
FIdentifier = AID,
|
|
||||||
FDLC = ADLC,
|
|
||||||
FTimeUS = 0uL,
|
|
||||||
FData = new byte[64],
|
|
||||||
FFDProperties = 0,
|
|
||||||
FIsTx = AIsTx,
|
|
||||||
FIsError = false,
|
|
||||||
FIsExt = AIsExt,
|
|
||||||
FIsRemote = AIsRemote,
|
|
||||||
FIsFD = AIsFD,
|
|
||||||
FIsBRS = AIsBRS
|
|
||||||
};
|
|
||||||
int length = Math.Min(ADataArray.Length, 64);
|
|
||||||
Array.Copy(ADataArray, 0, send.FData, 0, length);
|
|
||||||
int re;
|
|
||||||
if (sendPeriod == 0)
|
|
||||||
{
|
|
||||||
re = TsMasterApi.tsapp_transmit_canfd_async(ref send);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
re = TsMasterApi.tsapp_add_cyclic_msg_canfd(ref send, sendPeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUpdateDBCDatabase)
|
|
||||||
{
|
|
||||||
var find = DBCParse.MsgDatabase[(byte)AIdxChn].FirstOrDefault(s => s.ACANFD.FIdentifier == send.FIdentifier);
|
|
||||||
if (find != null)
|
|
||||||
{
|
|
||||||
Array.Copy(send.FData, 0, find.ACANFD.FData, 0, Math.Min(ADataArray.Length, find.ACANFD.FData.Length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return re;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加循环发送报文
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="channel"></param>
|
|
||||||
/// <param name="AMsgName"></param>
|
|
||||||
/// <param name="sendPeriod"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod)
|
|
||||||
{
|
|
||||||
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
|
|
||||||
find.ACANFD.FIdxChn = channel;
|
|
||||||
return TsMasterApi.tsapp_add_cyclic_msg_canfd(ref find.ACANFD, sendPeriod);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除循环发送的报文
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static int DeleteCyclicMsgs()
|
|
||||||
{
|
|
||||||
return TsMasterApi.tsapp_delete_cyclic_msgs();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取错误提示
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="errorCode">错误代码</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static string GetErrorDescription(int errorCode)
|
|
||||||
{
|
|
||||||
IntPtr ADesc = IntPtr.Zero;
|
|
||||||
TsMasterApi.tsapp_get_error_description(errorCode, ref ADesc);
|
|
||||||
|
|
||||||
if (ADesc == IntPtr.Zero) return $"未知错误代码: {errorCode}";
|
|
||||||
|
|
||||||
// 假设返回的是 ANSI 字符串(如 C 的 char*)
|
|
||||||
string? description = Marshal.PtrToStringAnsi(ADesc);
|
|
||||||
|
|
||||||
return description ?? $"未知错误代码: {errorCode}";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using TSMaster;
|
|
||||||
|
|
||||||
namespace CAN驱动
|
|
||||||
{
|
|
||||||
public struct can_network
|
|
||||||
{
|
|
||||||
[JsonInclude]
|
|
||||||
public string network_name;
|
|
||||||
[JsonInclude]
|
|
||||||
public can_node[] can_nodes;
|
|
||||||
|
|
||||||
}
|
|
||||||
public struct can_node
|
|
||||||
{
|
|
||||||
[JsonInclude]
|
|
||||||
public string node_name;
|
|
||||||
[JsonInclude]
|
|
||||||
public can_message[] tx_can_Messages;
|
|
||||||
[JsonInclude]
|
|
||||||
public can_message[] rx_can_Messages;
|
|
||||||
}
|
|
||||||
public struct can_message
|
|
||||||
{
|
|
||||||
[JsonInclude]
|
|
||||||
public string message_name;
|
|
||||||
[JsonInclude]
|
|
||||||
public string[] signals;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class _Msg_
|
|
||||||
{
|
|
||||||
public int msg_type { get; set; }
|
|
||||||
public int msg_id { get; set; }
|
|
||||||
public int msg_cyclic { get; set; }
|
|
||||||
public byte msg_dlc { get; set; }
|
|
||||||
public string msg_name { get; set; }
|
|
||||||
public string[] signal_Name { get; set; }
|
|
||||||
public int msg_brs { get; set; }
|
|
||||||
public TLIBCANFD ACANFD;
|
|
||||||
}
|
|
||||||
public enum Msg_Type
|
|
||||||
{
|
|
||||||
CAN = 0,
|
|
||||||
CANFD = 1,
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// DBC 文件信息类型枚举,用于指定 tsdb_get_can_db_info 查询的内容类型。
|
|
||||||
/// </summary>
|
|
||||||
public enum DBCINFO
|
|
||||||
{
|
|
||||||
/// <summary>网络名称</summary>
|
|
||||||
NETWORK_Name = 0,
|
|
||||||
|
|
||||||
/// <summary>CAN 报文总数</summary>
|
|
||||||
CAN_MSG_COUNT = 11,
|
|
||||||
/// <summary>CAN FD 报文总数</summary>
|
|
||||||
CANFD_MSG_COUNT = 12,
|
|
||||||
/// <summary>节点总数</summary>
|
|
||||||
NODE_COUNT = 14,
|
|
||||||
|
|
||||||
/// <summary>信号名称</summary>
|
|
||||||
SIGNAL_NAME = 33,
|
|
||||||
|
|
||||||
/// <summary>CAN 报文类型</summary>
|
|
||||||
CAN_MSG_TYPE = 40,
|
|
||||||
/// <summary>CAN 报文 DLC(数据长度码)</summary>
|
|
||||||
CAN_MSG_DLC = 41,
|
|
||||||
/// <summary>CAN 报文 ID</summary>
|
|
||||||
CAN_MSG_ID = 42,
|
|
||||||
/// <summary>CAN 报文周期</summary>
|
|
||||||
CAN_MSG_CYCLIC = 43,
|
|
||||||
/// <summary>CAN 报文名称</summary>
|
|
||||||
CAN_MSG_NAME = 44,
|
|
||||||
/// <summary>CAN 报文信号数量</summary>
|
|
||||||
CAN_MSG_SIGNAL_COUNT = 47,
|
|
||||||
/// <summary>CAN 报文信号名称索引</summary>
|
|
||||||
CAN_MSG_SIGNAL_NAME_INDEX = 48,
|
|
||||||
|
|
||||||
/// <summary>CAN FD 报文类型</summary>
|
|
||||||
CANFD_MSG_TYPE = 60,
|
|
||||||
/// <summary>CAN FD 报文 DLC(数据长度码)</summary>
|
|
||||||
CANFD_MSG_DLC = 61,
|
|
||||||
/// <summary>CAN FD 报文 ID</summary>
|
|
||||||
CANFD_MSG_ID = 62,
|
|
||||||
/// <summary>CAN FD 报文周期</summary>
|
|
||||||
CANFD_MSG_CYCLIC = 63,
|
|
||||||
/// <summary>CAN FD 报文名称</summary>
|
|
||||||
CANFD_MSG_NAME = 64,
|
|
||||||
/// <summary>CAN FD 报文信号数量</summary>
|
|
||||||
CANFD_MSG_SIGNAL_COUNT = 67,
|
|
||||||
/// <summary>CAN FD 报文信号名称索引</summary>
|
|
||||||
CANFD_MSG_SIGNAL_NAME_INDEX = 68,
|
|
||||||
|
|
||||||
/// <summary>节点名称</summary>
|
|
||||||
NODE_Name = 101,
|
|
||||||
/// <summary>节点发送 CAN 报文数量</summary>
|
|
||||||
NODE_TX_CAN_COUNT = 103,
|
|
||||||
/// <summary>节点发送 CAN 报文索引</summary>
|
|
||||||
NODE_TX_CAN_MESSAGE_INDEX = 104,
|
|
||||||
/// <summary>节点接收 CAN 报文数量</summary>
|
|
||||||
NODE_RX_CAN_COUNT = 105,
|
|
||||||
/// <summary>节点接收 CAN 报文索引</summary>
|
|
||||||
NODE_RX_CAN_MESSAGE_INDEX = 106,
|
|
||||||
/// <summary>节点发送 CAN FD 报文数量</summary>
|
|
||||||
NODE_TX_CANFD_COUNT = 107,
|
|
||||||
/// <summary>节点发送 CAN FD 报文索引</summary>
|
|
||||||
NODE_TX_CANFD_MESSAGE_INDEX = 108,
|
|
||||||
/// <summary>节点接收 CAN FD 报文数量</summary>
|
|
||||||
NODE_RX_CANFD_COUNT = 109,
|
|
||||||
/// <summary>节点接收 CAN FD 报文索引</summary>
|
|
||||||
NODE_RX_CANFD_MESSAGE_INDEX = 110,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DBCParse
|
|
||||||
{
|
|
||||||
public static List<List<_Msg_>> MsgDatabase { get; set; } =
|
|
||||||
[.. Enumerable.Range(0, 12).Select(_ => new List<_Msg_>())];
|
|
||||||
public static can_network can_Network;
|
|
||||||
public static string tsdb_get_can_db_info(uint ADatabaseId, int AType, int AIndex, int ASubIndex)
|
|
||||||
{
|
|
||||||
nint tmpInt = new nint();
|
|
||||||
int ret = TsMasterApi.tsdb_get_can_db_info(ADatabaseId, AType, AIndex, ASubIndex, ref tmpInt);
|
|
||||||
if (ret == 0)
|
|
||||||
{
|
|
||||||
return Marshal.PtrToStringAnsi(tmpInt) ?? "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void parse(uint Aid, int channel)
|
|
||||||
{
|
|
||||||
int CANcount = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_COUNT, 0, 0));
|
|
||||||
for (int i = 0; i < CANcount; i++)
|
|
||||||
{
|
|
||||||
_Msg_ temp = new _Msg_();
|
|
||||||
temp.msg_type = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_TYPE, i, 0));
|
|
||||||
temp.msg_dlc = byte.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_DLC, i, 0));
|
|
||||||
temp.msg_id = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_ID, i, 0));
|
|
||||||
temp.msg_name = tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_NAME, i, 0);
|
|
||||||
temp.msg_cyclic = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_CYCLIC, i, 0));
|
|
||||||
temp.signal_Name = new string[int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, i, 0))];
|
|
||||||
for (int signalcount = 0; signalcount < temp.signal_Name.Length; signalcount++)
|
|
||||||
{
|
|
||||||
temp.signal_Name[signalcount] = tsdb_get_can_db_info(Aid, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, i, signalcount)), 0);
|
|
||||||
}
|
|
||||||
temp.msg_brs = 0;
|
|
||||||
temp.ACANFD = new TLIBCANFD(APP_CHANNEL.CHN1,
|
|
||||||
temp.msg_id,
|
|
||||||
true,
|
|
||||||
temp.msg_type == 1 || temp.msg_type == 4 ? true : false,
|
|
||||||
false, temp.msg_dlc,
|
|
||||||
temp.msg_type < 2 ? false : true,
|
|
||||||
false);
|
|
||||||
MsgDatabase[channel].Add(temp);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
CANcount = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_COUNT, 0, 0));
|
|
||||||
for (int i = 0; i < CANcount; i++)
|
|
||||||
{
|
|
||||||
_Msg_ temp = new _Msg_();
|
|
||||||
temp.msg_type = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_TYPE, i, 0));
|
|
||||||
temp.msg_dlc = byte.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_DLC, i, 0));
|
|
||||||
temp.msg_id = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_ID, i, 0));
|
|
||||||
temp.msg_name = tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_NAME, i, 0);
|
|
||||||
temp.msg_cyclic = int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_CYCLIC, i, 0));
|
|
||||||
temp.signal_Name = new string[int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, i, 0))];
|
|
||||||
for (int signalcount = 0; signalcount < temp.signal_Name.Length; signalcount++)
|
|
||||||
{
|
|
||||||
temp.signal_Name[signalcount] = tsdb_get_can_db_info(Aid, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(Aid, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, i, signalcount)), 0);
|
|
||||||
}
|
|
||||||
temp.msg_brs = int.Parse(tsdb_get_can_db_info(Aid, 69, i, 0));
|
|
||||||
temp.ACANFD = new TLIBCANFD(APP_CHANNEL.CHN1, temp.msg_id, true, temp.msg_type == 1 || temp.msg_type == 4 ? true : false, false, temp.msg_dlc, temp.msg_type < 2 ? false : true, temp.msg_brs == 1 ? true : false);
|
|
||||||
MsgDatabase[channel].Add(temp);
|
|
||||||
}
|
|
||||||
MsgDatabase[channel].Sort((a, b) => a.msg_id.CompareTo(b.msg_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void rbs_parse(uint AID, int channel)
|
|
||||||
{
|
|
||||||
can_Network = new can_network();
|
|
||||||
can_Network.network_name = tsdb_get_can_db_info(AID, (int)DBCINFO.NETWORK_Name, 0, 0);
|
|
||||||
int Node_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_COUNT, 0, 0));
|
|
||||||
can_Network.can_nodes = new can_node[Node_count];
|
|
||||||
for (int i = 0; i < Node_count; i++)
|
|
||||||
{
|
|
||||||
can_Network.can_nodes[i].node_name = tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_Name, i, 0);
|
|
||||||
int tx_can_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CAN_COUNT, i, 0));
|
|
||||||
int tx_canfd_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CANFD_COUNT, i, 0));
|
|
||||||
|
|
||||||
int rx_can_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CAN_COUNT, i, 0));
|
|
||||||
int rx_canfd_count = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CANFD_COUNT, i, 0));
|
|
||||||
|
|
||||||
//获取tx_can结构体
|
|
||||||
can_Network.can_nodes[i].tx_can_Messages = new can_message[tx_can_count + tx_canfd_count];
|
|
||||||
|
|
||||||
can_Network.can_nodes[i].rx_can_Messages = new can_message[rx_can_count + rx_canfd_count];
|
|
||||||
//获取报文名以及信号名
|
|
||||||
//1 get can_message idnex
|
|
||||||
//2 get can message name
|
|
||||||
//3 get signal count
|
|
||||||
//4 get signal name
|
|
||||||
int tx_index = -1;
|
|
||||||
for (int tx_can = 0; tx_can < tx_can_count; tx_can++)
|
|
||||||
{
|
|
||||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CAN_MESSAGE_INDEX, i, tx_can));
|
|
||||||
can_Network.can_nodes[i].tx_can_Messages[tx_can].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_NAME, can_message_idnex, 0);
|
|
||||||
|
|
||||||
can_Network.can_nodes[i].tx_can_Messages[tx_can].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
|
||||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].tx_can_Messages[tx_can].signals.Length; signalcount++)
|
|
||||||
{
|
|
||||||
can_Network.can_nodes[i].tx_can_Messages[tx_can].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
tx_index = tx_can;
|
|
||||||
}
|
|
||||||
for (int tx_can = 0; tx_can < tx_canfd_count; tx_can++, tx_index++)
|
|
||||||
{
|
|
||||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_TX_CANFD_MESSAGE_INDEX, i, tx_can));
|
|
||||||
can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_NAME, can_message_idnex, 0);
|
|
||||||
|
|
||||||
can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
|
||||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals.Length; signalcount++)
|
|
||||||
{
|
|
||||||
can_Network.can_nodes[i].tx_can_Messages[tx_index + 1].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int rx_index = -1;
|
|
||||||
for (int tx_can = 0; tx_can < rx_can_count; tx_can++)
|
|
||||||
{
|
|
||||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CAN_MESSAGE_INDEX, i, tx_can));
|
|
||||||
can_Network.can_nodes[i].rx_can_Messages[tx_can].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_NAME, can_message_idnex, 0);
|
|
||||||
|
|
||||||
can_Network.can_nodes[i].rx_can_Messages[tx_can].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
|
||||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].rx_can_Messages[tx_can].signals.Length; signalcount++)
|
|
||||||
{
|
|
||||||
can_Network.can_nodes[i].rx_can_Messages[tx_can].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CAN_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
rx_index = tx_can;
|
|
||||||
}
|
|
||||||
for (int tx_can = 0; tx_can < rx_canfd_count; tx_can++, rx_index++)
|
|
||||||
{
|
|
||||||
int can_message_idnex = int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.NODE_RX_CANFD_MESSAGE_INDEX, i, tx_can));
|
|
||||||
can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].message_name = tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_NAME, can_message_idnex, 0);
|
|
||||||
|
|
||||||
can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals = new string[int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_COUNT, can_message_idnex, 0))];
|
|
||||||
for (int signalcount = 0; signalcount < can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals.Length; signalcount++)
|
|
||||||
{
|
|
||||||
can_Network.can_nodes[i].rx_can_Messages[rx_index + 1].signals[signalcount] = tsdb_get_can_db_info(AID, (int)DBCINFO.SIGNAL_NAME, int.Parse(tsdb_get_can_db_info(AID, (int)DBCINFO.CANFD_MSG_SIGNAL_NAME_INDEX, can_message_idnex, signalcount)), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
Binary file not shown.
Binary file not shown.
19
Common/Attributes/MonitorableAttribute.cs
Normal file
19
Common/Attributes/MonitorableAttribute.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Common.Attributes
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public class MonitorableAttribute : Attribute
|
||||||
|
{
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
public MonitorableAttribute(string? description = null)
|
||||||
|
{
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
20
CurveModule/CurveModule.cs
Normal file
20
CurveModule/CurveModule.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
using CurveModule.Views;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace CurveModule
|
||||||
|
{
|
||||||
|
public class CurveModule: IModule
|
||||||
|
{
|
||||||
|
public void OnInitialized(IContainerProvider containerProvider)
|
||||||
|
{
|
||||||
|
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||||
|
{
|
||||||
|
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
CurveModule/CurveModule.csproj
Normal file
20
CurveModule/CurveModule.csproj
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Service\Service.csproj" />
|
||||||
|
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
381
CurveModule/ViewModels/CurveRecallViewModel.cs
Normal file
381
CurveModule/ViewModels/CurveRecallViewModel.cs
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
using Logger;
|
||||||
|
using OxyPlot;
|
||||||
|
using OxyPlot.Axes;
|
||||||
|
using OxyPlot.Legends;
|
||||||
|
using OxyPlot.Series;
|
||||||
|
using Prism.Commands;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace CurveModule.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 曲线回读:加载 RecordView 导出的 CSV 文件,按 (MonitorName, Scope) 分组绘制曲线。
|
||||||
|
/// </summary>
|
||||||
|
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||||
|
{
|
||||||
|
#region 属性
|
||||||
|
public bool KeepAlive => true;
|
||||||
|
|
||||||
|
public PlotModel Plot { get; }
|
||||||
|
|
||||||
|
/// <summary>已加载的曲线通道列表</summary>
|
||||||
|
public ObservableCollection<CurveRecallChannelVM> Channels { get; } = new();
|
||||||
|
|
||||||
|
private CurveRecallChannelVM? _selectedChannel;
|
||||||
|
public CurveRecallChannelVM? SelectedChannel
|
||||||
|
{
|
||||||
|
get => _selectedChannel;
|
||||||
|
set => SetProperty(ref _selectedChannel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _statusMessage = "请加载 CSV 文件以回读曲线";
|
||||||
|
public string StatusMessage
|
||||||
|
{
|
||||||
|
get => _statusMessage;
|
||||||
|
set => SetProperty(ref _statusMessage, value);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
public ICommand LoadCsvCommand { get; }
|
||||||
|
public ICommand ResetViewCommand { get; }
|
||||||
|
public ICommand ClearCommand { get; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 私有字段
|
||||||
|
// 颜色调色盘(与 MonitorView 一致)
|
||||||
|
private static readonly OxyColor[] _palette =
|
||||||
|
{
|
||||||
|
OxyColors.SteelBlue, OxyColors.IndianRed, OxyColors.SeaGreen,
|
||||||
|
OxyColors.DarkOrange, OxyColors.MediumPurple, OxyColors.Goldenrod,
|
||||||
|
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
|
||||||
|
};
|
||||||
|
private int _colorIndex;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
Plot = BuildEmptyPlot();
|
||||||
|
LoadCsvCommand = new DelegateCommand(OnLoadCsv);
|
||||||
|
ResetViewCommand = new DelegateCommand(OnResetView);
|
||||||
|
ClearCommand = new DelegateCommand(OnClear);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 命令处理
|
||||||
|
/// <summary>
|
||||||
|
/// 打开 CSV 文件对话框,解析数据并按 (MonitorName, Scope) 分组绘制曲线。
|
||||||
|
/// </summary>
|
||||||
|
private void OnLoadCsv()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||||
|
Title = "选择要加载的监测数据 CSV 文件"
|
||||||
|
};
|
||||||
|
if (dlg.ShowDialog() != true) return;
|
||||||
|
|
||||||
|
var records = ParseCsv(dlg.FileName);
|
||||||
|
if (records.Count == 0)
|
||||||
|
{
|
||||||
|
StatusMessage = "CSV 文件为空或格式不正确";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按 (MonitorName, Scope) 分组
|
||||||
|
var groups = records.GroupBy(r => (r.MonitorName, r.Scope))
|
||||||
|
.OrderBy(g => g.Key.MonitorName)
|
||||||
|
.ThenBy(g => g.Key.Scope);
|
||||||
|
|
||||||
|
// 清空旧数据
|
||||||
|
ClearPlotAndChannels();
|
||||||
|
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
var color = _palette[_colorIndex % _palette.Length];
|
||||||
|
_colorIndex++;
|
||||||
|
|
||||||
|
string displayName = string.IsNullOrEmpty(group.Key.Scope)
|
||||||
|
? group.Key.MonitorName
|
||||||
|
: $"{group.Key.MonitorName} [{group.Key.Scope}]";
|
||||||
|
|
||||||
|
var channel = new CurveRecallChannelVM
|
||||||
|
{
|
||||||
|
MonitorName = group.Key.MonitorName,
|
||||||
|
Scope = group.Key.Scope,
|
||||||
|
DisplayName = displayName,
|
||||||
|
Color = color,
|
||||||
|
IsDisplayed = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建 LineSeries 并填充数据点
|
||||||
|
channel.Series = new LineSeries
|
||||||
|
{
|
||||||
|
Title = displayName,
|
||||||
|
Color = color,
|
||||||
|
StrokeThickness = 1.5
|
||||||
|
};
|
||||||
|
|
||||||
|
// 按时间排序
|
||||||
|
var sortedPoints = group.OrderBy(r => r.CreateTime);
|
||||||
|
foreach (var point in sortedPoints)
|
||||||
|
{
|
||||||
|
channel.Series.Points.Add(new DataPoint(point.CreateTime.ToOADate(), point.MonitorValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.PropertyChanged += OnChannelPropertyChanged;
|
||||||
|
|
||||||
|
Plot.Series.Add(channel.Series);
|
||||||
|
Channels.Add(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
Plot.InvalidatePlot(true);
|
||||||
|
StatusMessage = $"已加载 {Path.GetFileName(dlg.FileName)},共 {Channels.Count} 条曲线,{records.Count} 个数据点";
|
||||||
|
LoggerHelper.Info($"曲线回读:加载 {dlg.FileName},{Channels.Count} 条曲线,{records.Count} 个数据点");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.Error($"加载 CSV 失败:{ex.Message}");
|
||||||
|
StatusMessage = $"加载失败:{ex.Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnResetView()
|
||||||
|
{
|
||||||
|
Plot.ResetAllAxes();
|
||||||
|
Plot.InvalidatePlot(false);
|
||||||
|
StatusMessage = "视图已复原";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClear()
|
||||||
|
{
|
||||||
|
ClearPlotAndChannels();
|
||||||
|
Plot.InvalidatePlot(true);
|
||||||
|
StatusMessage = "已清空,请重新加载 CSV 文件";
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region CSV 解析
|
||||||
|
/// <summary>
|
||||||
|
/// 解析 RecordView 导出的 CSV 文件,返回监测记录列表。
|
||||||
|
/// </summary>
|
||||||
|
private static List<MonitorRecord> ParseCsv(string path)
|
||||||
|
{
|
||||||
|
var results = new List<MonitorRecord>();
|
||||||
|
|
||||||
|
using var sr = new StreamReader(path, Encoding.UTF8);
|
||||||
|
|
||||||
|
// 读取表头
|
||||||
|
string? headerLine = sr.ReadLine();
|
||||||
|
if (headerLine == null) return results;
|
||||||
|
|
||||||
|
var headers = ParseCsvLine(headerLine);
|
||||||
|
int monitorNameIdx = headers.FindIndex(h => h.Trim() == "MonitorName");
|
||||||
|
int monitorValueIdx = headers.FindIndex(h => h.Trim() == "MonitorValue");
|
||||||
|
int scopeIdx = headers.FindIndex(h => h.Trim() == "Scope");
|
||||||
|
int createTimeIdx = headers.FindIndex(h => h.Trim() == "CreateTime");
|
||||||
|
|
||||||
|
if (monitorNameIdx < 0 || monitorValueIdx < 0 || scopeIdx < 0 || createTimeIdx < 0)
|
||||||
|
return results;
|
||||||
|
|
||||||
|
int maxIdx = Math.Max(monitorNameIdx, Math.Max(monitorValueIdx, Math.Max(scopeIdx, createTimeIdx)));
|
||||||
|
|
||||||
|
string? line;
|
||||||
|
while ((line = sr.ReadLine()) != null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||||
|
var fields = ParseCsvLine(line);
|
||||||
|
if (fields.Count <= maxIdx) continue;
|
||||||
|
|
||||||
|
if (!double.TryParse(fields[monitorValueIdx].Trim(), out double value)) continue;
|
||||||
|
if (!DateTime.TryParse(fields[createTimeIdx].Trim(), out DateTime createTime)) continue;
|
||||||
|
|
||||||
|
results.Add(new MonitorRecord
|
||||||
|
{
|
||||||
|
MonitorName = fields[monitorNameIdx].Trim(),
|
||||||
|
MonitorValue = value,
|
||||||
|
Scope = fields[scopeIdx].Trim(),
|
||||||
|
CreateTime = createTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 解析单行 CSV,正确处理引号转义。
|
||||||
|
/// </summary>
|
||||||
|
private static List<string> ParseCsvLine(string line)
|
||||||
|
{
|
||||||
|
var fields = new List<string>();
|
||||||
|
bool inQuotes = false;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < line.Length; i++)
|
||||||
|
{
|
||||||
|
char c = line[i];
|
||||||
|
if (inQuotes)
|
||||||
|
{
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
if (i + 1 < line.Length && line[i + 1] == '"')
|
||||||
|
{
|
||||||
|
sb.Append('"');
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inQuotes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (c == '"')
|
||||||
|
{
|
||||||
|
inQuotes = true;
|
||||||
|
}
|
||||||
|
else if (c == ',')
|
||||||
|
{
|
||||||
|
fields.Add(sb.ToString());
|
||||||
|
sb.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields.Add(sb.ToString());
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region OxyPlot 构建
|
||||||
|
/// <summary>
|
||||||
|
/// 构建空的 OxyPlot 模型(与 MonitorView 完全一致的配置)。
|
||||||
|
/// </summary>
|
||||||
|
private static PlotModel BuildEmptyPlot()
|
||||||
|
{
|
||||||
|
var pm = new PlotModel
|
||||||
|
{
|
||||||
|
Title = "曲线回读",
|
||||||
|
PlotAreaBorderColor = OxyColors.LightGray,
|
||||||
|
Background = OxyColors.White
|
||||||
|
};
|
||||||
|
pm.Axes.Add(new DateTimeAxis
|
||||||
|
{
|
||||||
|
Position = AxisPosition.Bottom,
|
||||||
|
Title = "时间",
|
||||||
|
StringFormat = "HH:mm:ss",
|
||||||
|
MajorGridlineStyle = LineStyle.Dot,
|
||||||
|
MinorGridlineStyle = LineStyle.None
|
||||||
|
});
|
||||||
|
pm.Axes.Add(new LinearAxis
|
||||||
|
{
|
||||||
|
Position = AxisPosition.Left,
|
||||||
|
Title = "值",
|
||||||
|
MajorGridlineStyle = LineStyle.Dot,
|
||||||
|
MinorGridlineStyle = LineStyle.None
|
||||||
|
});
|
||||||
|
pm.Legends.Add(new Legend
|
||||||
|
{
|
||||||
|
LegendPosition = LegendPosition.RightTop,
|
||||||
|
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
|
||||||
|
LegendBorder = OxyColors.LightGray
|
||||||
|
});
|
||||||
|
return pm;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助方法
|
||||||
|
private void OnChannelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName != nameof(CurveRecallChannelVM.IsDisplayed)) return;
|
||||||
|
if (sender is not CurveRecallChannelVM channel) return;
|
||||||
|
|
||||||
|
if (channel.IsDisplayed)
|
||||||
|
{
|
||||||
|
if (channel.Series != null && !Plot.Series.Contains(channel.Series))
|
||||||
|
{
|
||||||
|
Plot.Series.Add(channel.Series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (channel.Series != null)
|
||||||
|
{
|
||||||
|
Plot.Series.Remove(channel.Series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Plot.InvalidatePlot(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearPlotAndChannels()
|
||||||
|
{
|
||||||
|
foreach (var ch in Channels)
|
||||||
|
{
|
||||||
|
ch.PropertyChanged -= OnChannelPropertyChanged;
|
||||||
|
}
|
||||||
|
Plot.Series.Clear();
|
||||||
|
Channels.Clear();
|
||||||
|
_colorIndex = 0;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 生命周期
|
||||||
|
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
ClearPlotAndChannels();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSV 监测记录(单行数据)。
|
||||||
|
/// </summary>
|
||||||
|
internal class MonitorRecord
|
||||||
|
{
|
||||||
|
public string MonitorName { get; set; } = string.Empty;
|
||||||
|
public double MonitorValue { get; set; }
|
||||||
|
public string Scope { get; set; } = string.Empty;
|
||||||
|
public DateTime CreateTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 曲线回读通道:MonitorName + Scope 唯一标识一条曲线。
|
||||||
|
/// </summary>
|
||||||
|
public class CurveRecallChannelVM : Prism.Mvvm.BindableBase
|
||||||
|
{
|
||||||
|
public string MonitorName { get; set; } = string.Empty;
|
||||||
|
public string Scope { get; set; } = string.Empty;
|
||||||
|
public string DisplayName { get; set; } = string.Empty;
|
||||||
|
public OxyColor Color { get; set; } = OxyColors.SteelBlue;
|
||||||
|
|
||||||
|
private bool _isDisplayed = true;
|
||||||
|
public bool IsDisplayed
|
||||||
|
{
|
||||||
|
get => _isDisplayed;
|
||||||
|
set => SetProperty(ref _isDisplayed, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineSeries? Series { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
158
CurveModule/Views/CurveRecallView.xaml
Normal file
158
CurveModule/Views/CurveRecallView.xaml
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<UserControl x:Class="CurveModule.Views.CurveRecallView"
|
||||||
|
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:oxy="http://oxyplot.org/wpf"
|
||||||
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="700"
|
||||||
|
d:DesignWidth="1200">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Border Background="#F5F7FA">
|
||||||
|
<Grid x:Name="RootGrid" Margin="8">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Row0:标题 -->
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Text="曲线回读"
|
||||||
|
FontSize="20" FontWeight="Bold"
|
||||||
|
Margin="4,0,0,8"/>
|
||||||
|
|
||||||
|
<!-- Row1:工具栏 -->
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
|
CornerRadius="4" Padding="8" Margin="0,0,0,6">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Content="📂 加载 CSV"
|
||||||
|
Command="{Binding LoadCsvCommand}"
|
||||||
|
Padding="12,4"/>
|
||||||
|
<Button Content="↺ 复原视图"
|
||||||
|
Command="{Binding ResetViewCommand}"
|
||||||
|
Padding="12,4" Margin="6,0,0,0"
|
||||||
|
ToolTip="按数据范围重置坐标轴缩放/平移"/>
|
||||||
|
<Button Content="✕ 清空"
|
||||||
|
Command="{Binding ClearCommand}"
|
||||||
|
Padding="12,4" Margin="6,0,0,0"
|
||||||
|
ToolTip="清空已加载的曲线数据"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Row2:主体 -->
|
||||||
|
<Grid Grid.Row="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition>
|
||||||
|
<ColumnDefinition.Style>
|
||||||
|
<Style TargetType="ColumnDefinition">
|
||||||
|
<Setter Property="Width" Value="260"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||||
|
<Setter Property="Width" Value="0"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ColumnDefinition.Style>
|
||||||
|
</ColumnDefinition>
|
||||||
|
<ColumnDefinition>
|
||||||
|
<ColumnDefinition.Style>
|
||||||
|
<Style TargetType="ColumnDefinition">
|
||||||
|
<Setter Property="Width" Value="6"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||||
|
<Setter Property="Width" Value="0"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ColumnDefinition.Style>
|
||||||
|
</ColumnDefinition>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 左侧:通道列表 -->
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
|
CornerRadius="4">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Border Grid.Row="0" Background="#ECEFF4" Padding="8,4">
|
||||||
|
<TextBlock Text="曲线通道(勾选显示/隐藏)" FontWeight="Bold"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<ListBox Grid.Row="1"
|
||||||
|
ItemsSource="{Binding Channels}"
|
||||||
|
SelectedItem="{Binding SelectedChannel}"
|
||||||
|
BorderThickness="0">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Margin="2">
|
||||||
|
<DockPanel>
|
||||||
|
<CheckBox IsChecked="{Binding IsDisplayed}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ToolTip="勾选=在图表上显示,取消=隐藏"/>
|
||||||
|
<Border Width="10" Height="10"
|
||||||
|
CornerRadius="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="4,0,6,0">
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush Color="SteelBlue"/>
|
||||||
|
</Border.Background>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="{Binding DisplayName}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
</DockPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2"
|
||||||
|
Text="{Binding StatusMessage}"
|
||||||
|
FontSize="11" Foreground="#666"
|
||||||
|
Margin="4,2" TextWrapping="Wrap"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<GridSplitter Grid.Column="1"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Background="Transparent"/>
|
||||||
|
|
||||||
|
<!-- 右侧:OxyPlot 图表 -->
|
||||||
|
<Border Grid.Column="2"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
|
CornerRadius="4">
|
||||||
|
<oxy:PlotView Model="{Binding Plot}"
|
||||||
|
Background="Transparent"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Row3:状态栏 -->
|
||||||
|
<Border Grid.Row="3"
|
||||||
|
Background="#ECEFF4"
|
||||||
|
Padding="8,4" Margin="0,6,0,0"
|
||||||
|
CornerRadius="2">
|
||||||
|
<TextBlock Text="{Binding StatusMessage}"
|
||||||
|
Foreground="#444"
|
||||||
|
FontSize="12"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
28
CurveModule/Views/CurveRecallView.xaml.cs
Normal file
28
CurveModule/Views/CurveRecallView.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 CurveModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CurveRecallView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class CurveRecallView : UserControl
|
||||||
|
{
|
||||||
|
public CurveRecallView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using NModbus;
|
using Model.Models;
|
||||||
|
using NModbus;
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
@@ -19,7 +20,11 @@ namespace DeviceCommand.Base
|
|||||||
public bool IsConnected => _tcpClient?.Connected ?? false;
|
public bool IsConnected => _tcpClient?.Connected ?? false;
|
||||||
|
|
||||||
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
protected readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
public ModbusTcp(TcpConfig config) : this()
|
||||||
|
{
|
||||||
|
if (config == null) return;
|
||||||
|
ConfigureDevice(config.IPAddress, config.Port, config.SendTimeout, config.ReceiveTimeout);
|
||||||
|
}
|
||||||
public ModbusTcp()
|
public ModbusTcp()
|
||||||
{
|
{
|
||||||
_tcpClient = new TcpClient();
|
_tcpClient = new TcpClient();
|
||||||
|
|||||||
@@ -230,7 +230,89 @@ namespace DeviceCommand.Base
|
|||||||
_commLock.Release();
|
_commLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#region 扩展:读取所有可用的二进制网络字节 (无 SCPI 块头解析)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 无锁核心方法:发送命令并一次性读取所有回传的二进制原始数据包(不进行 # 协议头解析,专用于读取纯文件流如 PNG)
|
||||||
|
/// </summary>
|
||||||
|
private async Task<byte[]> LoglessReadAllBytesAsync(string queryCommand, CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||||
|
|
||||||
|
// 1. 发送查询命令(例如 :PRINt? PNG\n)
|
||||||
|
await LoglessSendAsync(Encoding.UTF8.GetBytes(queryCommand), ct);
|
||||||
|
|
||||||
|
NetworkStream stream = _tcpClient.GetStream();
|
||||||
|
|
||||||
|
// 2. 鼎阳示波器的截图数据大概在 100KB - 800KB 左右,使用动态内存流接收整个包
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[8192]; // 8KB 缓冲区
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 先给设备短暂的反应时间,等待数据到达网络缓冲区
|
||||||
|
int delayCount = 0;
|
||||||
|
while (!_tcpClient.GetStream().DataAvailable && delayCount < 50)
|
||||||
|
{
|
||||||
|
await Task.Delay(10, ct);
|
||||||
|
delayCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 循环读取,直到网络流中没有更多数据
|
||||||
|
do
|
||||||
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
int read = ReceiveTimeout > 0
|
||||||
|
? await stream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
: await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (read == 0) break; // 远程流关闭
|
||||||
|
|
||||||
|
ms.Write(buffer, 0, read);
|
||||||
|
|
||||||
|
// 如果流里没有剩余数据了,退出读取(防止 ReadAsync 在没有数据时无限阻塞等待)
|
||||||
|
if (!stream.DataAvailable)
|
||||||
|
{
|
||||||
|
// 极短延时再确认一次,防止分包网络延迟引起的“假结束”
|
||||||
|
await Task.Delay(30, ct);
|
||||||
|
if (!stream.DataAvailable)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
return ms.ToArray();
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
await ResetConnectionAsync(ct);
|
||||||
|
throw new TimeoutException($"读取二进制大包超时(等待:{ReceiveTimeout} ms),链路已重置。", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 【公开方法】发送命令并读取设备回传的全部原始二进制字节数组(不带协议头解析,直接返回整个字节缓冲区)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<byte[]> ReadAllBytesAsync(string queryCommand, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await LoglessReadAllBytesAsync(queryCommand, ct);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_tcpClient?.Dispose();
|
_tcpClient?.Dispose();
|
||||||
|
|||||||
540
DeviceCommand/Devices/GantryControlTcp.cs
Normal file
540
DeviceCommand/Devices/GantryControlTcp.cs
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
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;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
[ADPCommand]
|
||||||
|
public class GantryControlTcp : ModbusTcp
|
||||||
|
{
|
||||||
|
private readonly byte _slaveAddress=1;
|
||||||
|
|
||||||
|
// 向后兼容:TcpDevice 即自身(继承自 ModbusTcp)
|
||||||
|
public ModbusTcp TcpDevice => this;
|
||||||
|
|
||||||
|
public GantryControlTcp(TcpConfig config) : base(config)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// ------------------ Coil / Bit 状态与控制 (位元件) ------------------
|
||||||
|
// =================================================================
|
||||||
|
private const ushort ADDR_M_ESTOP = 20496; // 0x5010: 紧停
|
||||||
|
private const ushort ADDR_M_X_STOP = 41; // 0x29: X轴停止
|
||||||
|
private const ushort ADDR_M_Y_STOP = 42; // 0x2A: Y轴停止
|
||||||
|
private const ushort ADDR_M_Z_STOP = 43; // 0x2B: Z轴停止
|
||||||
|
|
||||||
|
private const ushort ADDR_M_X_START = 30; // 0x1E: X轴指定位置启动
|
||||||
|
private const ushort ADDR_M_Y_START = 31; // 0x1F: Y轴指定位置启动
|
||||||
|
private const ushort ADDR_M_Z_START = 35; // 0x23: Z轴指定位置启动
|
||||||
|
|
||||||
|
private const ushort ADDR_M_HOME_TRIGGER = 100; // 0x64: 回原点触发
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// ------------------ 轴使能控制 (HM 寄存器) ------------------
|
||||||
|
// =================================================================
|
||||||
|
private const ushort ADDR_HM_AXIS1_ENABLE = 49408; // 0xC100: 轴1使能 (X轴)
|
||||||
|
private const ushort ADDR_HM_AXIS2_ENABLE = 49409; // 0xC101: 轴2使能 (Y轴)
|
||||||
|
private const ushort ADDR_HM_AXIS3_ENABLE = 49410; // 0xC102: 轴3使能 (Z轴)
|
||||||
|
private const ushort ADDR_HM_AXIS4_ENABLE = 49411; // 0xC103: 轴4使能
|
||||||
|
private const ushort ADDR_HM_AXIS5_ENABLE = 49412; // 0xC104: 轴5使能
|
||||||
|
private const ushort ADDR_HM_AXIS6_ENABLE = 49413; // 0xC105: 轴6使能
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// ------------------ 速度与位置参数 (HD 32位数据寄存器) --------------
|
||||||
|
// =================================================================
|
||||||
|
private const ushort ADDR_HD_HOME_POS_SPEED = 41288; // 0xA148: 原点定位速度 (32位)
|
||||||
|
|
||||||
|
// 轴原点位置
|
||||||
|
private const ushort ADDR_HD_X_HOME_POS = 41198; // 0xA0EE: X轴原点位 (32位)
|
||||||
|
private const ushort ADDR_HD_Z_HOME_POS = 41208; // 0xA0F8: Z轴原点位 (32位)
|
||||||
|
private const ushort ADDR_HD_Y_HOME_POS = 41218; // 0xA102: Y轴原点位 (32位)
|
||||||
|
|
||||||
|
// 轴运行目标位置
|
||||||
|
private const ushort ADDR_HD_Y_TARGET = 41228; // 0xA10C: Y轴指定位置 (32位)
|
||||||
|
private const ushort ADDR_HD_Z_TARGET = 41238; // 0xA116: Z轴指定位置 (32位)
|
||||||
|
private const ushort ADDR_HD_X_TARGET = 41248; // 0xA120: X轴指定位置 (32位)
|
||||||
|
|
||||||
|
// 轴运行速度
|
||||||
|
private const ushort ADDR_HD_X_SPEED = 41318; // 0xA166: X轴速度 (32位)
|
||||||
|
private const ushort ADDR_HD_Z_SPEED = 41328; // 0xA170: Z轴速度 (32位)
|
||||||
|
private const ushort ADDR_HD_Y_SPEED = 41338; // 0xA17A: Y轴速度 (32位)
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// ------------------ 轴绝对位置(只读,相对原点)------------------
|
||||||
|
// =================================================================
|
||||||
|
private const ushort ADDR_ABS_AXIS1_POS = 47232; // 轴1绝对位置 (32位)
|
||||||
|
private const ushort ADDR_ABS_AXIS2_POS = 47236; // 轴2绝对位置 (32位)
|
||||||
|
private const ushort ADDR_ABS_AXIS3_POS = 47240; // 轴3绝对位置 (32位)
|
||||||
|
private const ushort ADDR_ABS_AXIS4_POS = 47244; // 轴4绝对位置 (32位)
|
||||||
|
private const ushort ADDR_ABS_AXIS5_POS = 47248; // 轴5绝对位置 (32位)
|
||||||
|
private const ushort ADDR_ABS_AXIS6_POS = 47252; // 轴6绝对位置 (32位)
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// ------------------ 核心控制逻辑与参数读写 ------------------------
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 轴使能控制
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="axis">轴编号 (1 - 6)</param>
|
||||||
|
public async Task SetAxisEnableAsync(int axis, bool enable, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_HM_AXIS1_ENABLE,
|
||||||
|
2 => ADDR_HM_AXIS2_ENABLE,
|
||||||
|
3 => ADDR_HM_AXIS3_ENABLE,
|
||||||
|
4 => ADDR_HM_AXIS4_ENABLE,
|
||||||
|
5 => ADDR_HM_AXIS5_ENABLE,
|
||||||
|
6 => ADDR_HM_AXIS6_ENABLE,
|
||||||
|
_ => throw new ArgumentException("轴通道错误,仅支持 1 - 6", nameof(axis))
|
||||||
|
};
|
||||||
|
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, address, enable, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取指定轴的使能状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||||
|
public async Task<bool> GetAxisEnableAsync(int axis, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_HM_AXIS1_ENABLE,
|
||||||
|
2 => ADDR_HM_AXIS2_ENABLE,
|
||||||
|
3 => ADDR_HM_AXIS3_ENABLE,
|
||||||
|
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||||
|
};
|
||||||
|
|
||||||
|
bool[] coils = await ReadCoilsAsync(_slaveAddress, address, 1, ct);
|
||||||
|
return coils != null && coils.Length > 0 && coils[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 同时设置 X/Y/Z 轴的原点位置(带各自独立的范围检查)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="xPos">X轴原点位置 (范围: 0 ~ 50000)</param>
|
||||||
|
/// <param name="yPos">Y轴原点位置 (范围: 0 ~ 50000)</param>
|
||||||
|
/// <param name="zPos">Z轴原点位置 (范围: 0 ~ 20000)</param>
|
||||||
|
public async Task SetHomePositionAsync(int xPos, int yPos, int zPos, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (xPos < 0 || xPos > 50000)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(xPos), $"X轴原点设置值 [{xPos}] 超出合法范围 (0 - 50000)");
|
||||||
|
|
||||||
|
if (yPos < 0 || yPos > 50000)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(yPos), $"Y轴原点设置值 [{yPos}] 超出合法范围 (0 - 50000)");
|
||||||
|
|
||||||
|
if (zPos < 0 || zPos > 20000)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(zPos), $"Z轴原点设置值 [{zPos}] 超出合法范围 (0 - 20000)");
|
||||||
|
|
||||||
|
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, Int32ToUshorts(xPos), ct);
|
||||||
|
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_HOME_POS, Int32ToUshorts(yPos), ct);
|
||||||
|
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_HOME_POS, Int32ToUshorts(zPos), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量查询当前 X、Y、Z 三轴设定的原点位置值
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>返回包含三轴原点位置的元组 (X, Y, Z)</returns>
|
||||||
|
public async Task<(int X, int Y, int Z)> GetHomePositionAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 41218(Y) - 41198(X) = 20,加Y轴自身的 2 个寄存器,共批量读取 22 个连续寄存器
|
||||||
|
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, 22, ct);
|
||||||
|
|
||||||
|
if (registers == null || registers.Length < 22)
|
||||||
|
throw new InvalidOperationException("从 PLC 读取三轴原点位置失败,返回数据长度不足。");
|
||||||
|
|
||||||
|
int xHomePos = UshortsToInt32(new ushort[] { registers[0], registers[1] });
|
||||||
|
int zHomePos = UshortsToInt32(new ushort[] { registers[10], registers[11] });
|
||||||
|
int yHomePos = UshortsToInt32(new ushort[] { registers[20], registers[21] });
|
||||||
|
|
||||||
|
return (xHomePos, yHomePos, zHomePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置回原点定位速度(带 0 - 100000 范围检查)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetHomePositionSpeedAsync(int speed, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (speed < 0 || speed > 100000)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(speed), $"回原点定位速度值 [{speed}] 超出合法范围 (0 - 100000)");
|
||||||
|
|
||||||
|
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, Int32ToUshorts(speed), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询当前设定的回原点定位速度
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int> GetHomePositionSpeedAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, 2, ct);
|
||||||
|
|
||||||
|
if (registers == null || registers.Length < 2)
|
||||||
|
throw new InvalidOperationException("从 PLC 读取回原点定位速度失败,返回数据长度不足。");
|
||||||
|
|
||||||
|
return UshortsToInt32(registers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置指定轴的运行速度(带 0 - 100000 范围检查)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||||
|
public async Task SetAxisSpeedAsync(int axis, int speed, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (speed < 0 || speed > 100000)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(speed), $"轴速度设置值 [{speed}] 超出合法范围 (0 - 100000)");
|
||||||
|
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_HD_X_SPEED,
|
||||||
|
2 => ADDR_HD_Y_SPEED,
|
||||||
|
3 => ADDR_HD_Z_SPEED,
|
||||||
|
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||||
|
};
|
||||||
|
|
||||||
|
await WriteMultipleRegistersAsync(_slaveAddress, address, Int32ToUshorts(speed), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取指定轴当前设定的运行速度
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int> GetAxisSpeedAsync(int axis, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_HD_X_SPEED,
|
||||||
|
2 => ADDR_HD_Y_SPEED,
|
||||||
|
3 => ADDR_HD_Z_SPEED,
|
||||||
|
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||||
|
};
|
||||||
|
|
||||||
|
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||||
|
if (registers == null || registers.Length < 2)
|
||||||
|
throw new InvalidOperationException($"从 PLC 读取轴 {axis} 速度失败。");
|
||||||
|
|
||||||
|
return UshortsToInt32(registers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 写入指定轴的目标位置(动态安全校验:目标位置不能超过 [轴最大物理极限 - 当前轴原点设置值])
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetAxisTargetPositionAsync(int axis, int targetPos, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||||
|
|
||||||
|
ushort targetAddress;
|
||||||
|
int maxLimit;
|
||||||
|
int currentHomePos;
|
||||||
|
|
||||||
|
switch (axis)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
targetAddress = ADDR_HD_X_TARGET;
|
||||||
|
maxLimit = 50000;
|
||||||
|
currentHomePos = xHome;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
targetAddress = ADDR_HD_Y_TARGET;
|
||||||
|
maxLimit = 50000;
|
||||||
|
currentHomePos = yHome;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
targetAddress = ADDR_HD_Z_TARGET;
|
||||||
|
maxLimit = 20000;
|
||||||
|
currentHomePos = zHome;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis));
|
||||||
|
}
|
||||||
|
|
||||||
|
int allowedMaxLimit = maxLimit - currentHomePos;
|
||||||
|
|
||||||
|
if (targetPos < 0 || targetPos > allowedMaxLimit)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(targetPos),
|
||||||
|
$"轴 {axis} 目标位置 [{targetPos}] 不合法!当前原点为 [{currentHomePos}],允许的有效写入范围为: 0 - {allowedMaxLimit}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await WriteMultipleRegistersAsync(_slaveAddress, targetAddress, Int32ToUshorts(targetPos), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取指定轴当前设定的目标指定位置
|
||||||
|
/// </summary>
|
||||||
|
public async Task<int> GetAxisTargetPositionAsync(int axis, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_HD_X_TARGET,
|
||||||
|
2 => ADDR_HD_Y_TARGET,
|
||||||
|
3 => ADDR_HD_Z_TARGET,
|
||||||
|
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||||
|
};
|
||||||
|
|
||||||
|
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||||
|
|
||||||
|
if (registers == null || registers.Length < 2)
|
||||||
|
throw new InvalidOperationException($"从 PLC 读取轴 {axis} 目标位置失败。");
|
||||||
|
|
||||||
|
return UshortsToInt32(registers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置系统紧停锁定状态 (true: 锁定激活, false: 解除锁定)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetEStopStateAsync(bool state, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_ESTOP, state, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置指定轴的停止控制状态 (true: 持续输出停止信号, false: 释放停止信号)
|
||||||
|
/// </summary>
|
||||||
|
public async Task SetStopStateAsync(int axis, bool state, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_M_X_STOP,
|
||||||
|
2 => ADDR_M_Y_STOP,
|
||||||
|
3 => ADDR_M_Z_STOP,
|
||||||
|
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||||
|
};
|
||||||
|
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, address, state, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 全局回零(安全校验版:先检查回原点速度是否为 0,为0则拒绝触发)
|
||||||
|
/// </summary>
|
||||||
|
public async Task HomeAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
int currentSpeed = await GetHomePositionSpeedAsync(ct);
|
||||||
|
|
||||||
|
if (currentSpeed == 0)
|
||||||
|
throw new InvalidOperationException("无法触发回原点!当前回原点定位速度为 0,请先设置合法的回零速度。");
|
||||||
|
|
||||||
|
await TriggerCoilAsync(ADDR_M_HOME_TRIGGER, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单轴指定位置启动(含速度判定、使能自动补齐、动作目标位置独立范围校验)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||||
|
public async Task StartSingleAxisAsync(int axis, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort startAddress;
|
||||||
|
int maxLimit;
|
||||||
|
int currentHomePos;
|
||||||
|
|
||||||
|
// 1. 获取基础参数与校验边界
|
||||||
|
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||||
|
switch (axis)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
startAddress = ADDR_M_X_START;
|
||||||
|
maxLimit = 50000;
|
||||||
|
currentHomePos = xHome;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
startAddress = ADDR_M_Y_START;
|
||||||
|
maxLimit = 50000;
|
||||||
|
currentHomePos = yHome;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
startAddress = ADDR_M_Z_START;
|
||||||
|
maxLimit = 20000;
|
||||||
|
currentHomePos = zHome;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查动作轴的速度
|
||||||
|
int currentSpeed = await GetAxisSpeedAsync(axis, ct);
|
||||||
|
if (currentSpeed == 0)
|
||||||
|
throw new InvalidOperationException($"轴 {axis} 无法启动!当前设定速度为 0。");
|
||||||
|
|
||||||
|
// 3. 检查动作轴使能,若为 false 则自动补写 true 激活驱动器
|
||||||
|
bool isEnabled = await GetAxisEnableAsync(axis, ct);
|
||||||
|
if (!isEnabled)
|
||||||
|
{
|
||||||
|
await SetAxisEnableAsync(axis, true, ct);
|
||||||
|
await Task.Delay(100, ct); // 等待硬件继电器响应与伺服驱动就绪
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 精准独立校验当前需要动作轴的目标定位是否越界
|
||||||
|
int currentTarget = await GetAxisTargetPositionAsync(axis, ct);
|
||||||
|
int allowedMaxLimit = maxLimit - currentHomePos;
|
||||||
|
if (currentTarget < 0 || currentTarget > allowedMaxLimit)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException($"轴 {axis} 无法启动!当前指定目标位置 [{currentTarget}] 超过了允许的最大安全极限值 [{allowedMaxLimit}]。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 校验完成,边缘触发点动启动
|
||||||
|
await TriggerCoilAsync(startAddress, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 三轴同时启动(全开:严格校验全部运行轴速度、批量检测与补正1~6号轴使能、联动校验三轴目标范围极限)
|
||||||
|
/// </summary>
|
||||||
|
public async Task StartAllAxesAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 1. 一次性读取三轴速度,严禁任何一轴速度为 0
|
||||||
|
int xSpeed = await GetAxisSpeedAsync(1, ct);
|
||||||
|
int ySpeed = await GetAxisSpeedAsync(2, ct);
|
||||||
|
int zSpeed = await GetAxisSpeedAsync(3, ct);
|
||||||
|
|
||||||
|
if (xSpeed == 0 || ySpeed == 0 || zSpeed == 0)
|
||||||
|
throw new InvalidOperationException($"无法全开启动!存在运行速度为 0 的轴。当前速度 -> X:{xSpeed}, Y:{ySpeed}, Z:{zSpeed}");
|
||||||
|
|
||||||
|
// 2. 批量读取 1 ~ 6 号轴连续的使能状态线圈,极大地优化网络性能
|
||||||
|
bool[] enableStates = await ReadCoilsAsync(_slaveAddress, ADDR_HM_AXIS1_ENABLE, 6, ct);
|
||||||
|
if (enableStates == null || enableStates.Length < 6)
|
||||||
|
throw new InvalidOperationException("从 PLC 读取 1~6 号轴使能状态失败。");
|
||||||
|
|
||||||
|
bool hasModifiedEnable = false;
|
||||||
|
ushort[] enableAddresses = { ADDR_HM_AXIS1_ENABLE, ADDR_HM_AXIS2_ENABLE, ADDR_HM_AXIS3_ENABLE, ADDR_HM_AXIS4_ENABLE, ADDR_HM_AXIS5_ENABLE, ADDR_HM_AXIS6_ENABLE };
|
||||||
|
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
if (!enableStates[i])
|
||||||
|
{
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, enableAddresses[i], true, ct);
|
||||||
|
hasModifiedEnable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果执行过使能补齐补齐写入,在循环外进行统一延时,保证PLC时序安全
|
||||||
|
if (hasModifiedEnable)
|
||||||
|
await Task.Delay(100, ct);
|
||||||
|
|
||||||
|
// 3. 获取三轴原点及当前设定的目标指定位置
|
||||||
|
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||||
|
|
||||||
|
int xTarget = await GetAxisTargetPositionAsync(1, ct);
|
||||||
|
int yTarget = await GetAxisTargetPositionAsync(2, ct);
|
||||||
|
int zTarget = await GetAxisTargetPositionAsync(3, ct);
|
||||||
|
|
||||||
|
// X轴安全范围联动校验
|
||||||
|
int xMaxAllowed = 50000 - xHome;
|
||||||
|
if (xTarget < 0 || xTarget > xMaxAllowed)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(xTarget), $"X轴目标位置 [{xTarget}] 越界,当前原点下最大范围 0-{xMaxAllowed}");
|
||||||
|
|
||||||
|
// Y轴安全范围联动校验
|
||||||
|
int yMaxAllowed = 50000 - yHome;
|
||||||
|
if (yTarget < 0 || yTarget > yMaxAllowed)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(yTarget), $"Y轴目标位置 [{yTarget}] 越界,当前原点下最大范围 0-{yMaxAllowed}");
|
||||||
|
|
||||||
|
// Z轴安全范围联动校验
|
||||||
|
int zMaxAllowed = 20000 - zHome;
|
||||||
|
if (zTarget < 0 || zTarget > zMaxAllowed)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(zTarget), $"Z轴目标位置 [{zTarget}] 越界,当前原点下最大范围 0-{zMaxAllowed}");
|
||||||
|
|
||||||
|
// 4. 全部联动校验通过,三轴点动同步启动
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, true, ct);
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, true, ct);
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, true, ct);
|
||||||
|
|
||||||
|
await Task.Delay(100, ct); // 保持高电平状态以适配 PLC 扫描周期
|
||||||
|
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, false, ct);
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, false, ct);
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, false, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 内部高低字工具转化方法
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 触发点动脉冲信号(置 1 后,等待 100ms 自动置 0 释放)
|
||||||
|
/// </summary>
|
||||||
|
private async Task TriggerCoilAsync(ushort address, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, address, true, ct);
|
||||||
|
await Task.Delay(100, ct);
|
||||||
|
await WriteSingleCoilAsync(_slaveAddress, address, false, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 32 位整型数据转换为 Modbus 的 2 个 16 位无符号整数(低字在前 CDAB 格式)
|
||||||
|
/// </summary>
|
||||||
|
private ushort[] Int32ToUshorts(int value)
|
||||||
|
{
|
||||||
|
ushort lowWord = (ushort)(value & 0xFFFF);
|
||||||
|
ushort highWord = (ushort)((value >> 16) & 0xFFFF);
|
||||||
|
return new ushort[] { lowWord, highWord };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 Modbus 的 2 个 16 位无符号整数转换为 32 位整型(低字在前 CDAB 格式)
|
||||||
|
/// </summary>
|
||||||
|
private int UshortsToInt32(ushort[] registers)
|
||||||
|
{
|
||||||
|
uint lowWord = registers[0];
|
||||||
|
uint highWord = registers[1];
|
||||||
|
return (int)((highWord << 16) | lowWord);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 轴绝对位置读取(只读)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取指定轴的绝对位置(相对原点的实时位置,只读)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="axis">轴编号 (1-6)</param>
|
||||||
|
public async Task<int> GetAbsolutePositionAsync(int axis, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
ushort address = axis switch
|
||||||
|
{
|
||||||
|
1 => ADDR_ABS_AXIS1_POS,
|
||||||
|
2 => ADDR_ABS_AXIS2_POS,
|
||||||
|
3 => ADDR_ABS_AXIS3_POS,
|
||||||
|
4 => ADDR_ABS_AXIS4_POS,
|
||||||
|
5 => ADDR_ABS_AXIS5_POS,
|
||||||
|
6 => ADDR_ABS_AXIS6_POS,
|
||||||
|
_ => throw new ArgumentException("轴编号错误,仅支持 1-6", nameof(axis))
|
||||||
|
};
|
||||||
|
|
||||||
|
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||||
|
if (registers == null || registers.Length < 2)
|
||||||
|
throw new InvalidOperationException($"读取轴 {axis} 绝对位置失败。");
|
||||||
|
|
||||||
|
return UshortsToInt32(registers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量读取 1-6 轴的绝对位置(一次性读取 22 个寄存器,高效)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>返回 6 轴绝对位置元组</returns>
|
||||||
|
public async Task<(int Axis1, int Axis2, int Axis3, int Axis4, int Axis5, int Axis6)> GetAllAbsolutePositionsAsync(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 从 47232 开始连续读取 22 个寄存器(覆盖 47232-47253)
|
||||||
|
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_ABS_AXIS1_POS, 22, ct);
|
||||||
|
if (registers == null || registers.Length < 22)
|
||||||
|
throw new InvalidOperationException("批量读取 6 轴绝对位置失败,返回数据长度不足。");
|
||||||
|
|
||||||
|
// 每轴间隔 4 个寄存器(2个数据 + 2个间隙),提取各轴数据
|
||||||
|
int axis1 = UshortsToInt32(new ushort[] { registers[0], registers[1] });
|
||||||
|
int axis2 = UshortsToInt32(new ushort[] { registers[4], registers[5] });
|
||||||
|
int axis3 = UshortsToInt32(new ushort[] { registers[8], registers[9] });
|
||||||
|
int axis4 = UshortsToInt32(new ushort[] { registers[12], registers[13] });
|
||||||
|
int axis5 = UshortsToInt32(new ushort[] { registers[16], registers[17] });
|
||||||
|
int axis6 = UshortsToInt32(new ushort[] { registers[20], registers[21] });
|
||||||
|
|
||||||
|
return (axis1, axis2, axis3, axis4, axis5, axis6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Common.Attributes;
|
using Common.Attributes;
|
||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
|
using Model.Models;
|
||||||
using NModbus;
|
using NModbus;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -8,15 +9,15 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
[ADPCommand]
|
//[ADPCommand]
|
||||||
public class IOBoard : ModbusTcp
|
public class IOBoard : ModbusTcp
|
||||||
{
|
{
|
||||||
//只有两个,八台产品公用两两分组各用一个
|
|
||||||
public IOBoard(string Ip地址, int 端口, int 发送超时, int 接收超时)
|
public IOBoard(TcpConfig config) : base(config)
|
||||||
{
|
{
|
||||||
ConfigureDevice(Ip地址, 端口, 发送超时, 接收超时);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
160
DeviceCommand/Devices/IOBoardGroup.cs
Normal file
160
DeviceCommand/Devices/IOBoardGroup.cs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using DeviceCommand.Devices;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Devices
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 系统控制功能类型枚举 (中文)
|
||||||
|
/// </summary>
|
||||||
|
public enum 功能动作 : byte
|
||||||
|
{
|
||||||
|
单相充电,
|
||||||
|
抛负载,
|
||||||
|
短路测试,
|
||||||
|
蜂鸣器报警
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 精简后的 IO 端口信息结构体
|
||||||
|
/// </summary>
|
||||||
|
public struct IoPortInfo
|
||||||
|
{
|
||||||
|
public int ModuleIndex { get; set; } // IO板卡/模块序号 (1 或 2)
|
||||||
|
public ushort Address { get; set; } // 转换为 Modbus 的线圈相对偏移地址 (Y1->0, Y2->1)
|
||||||
|
|
||||||
|
public IoPortInfo(int moduleIndex, ushort address)
|
||||||
|
{
|
||||||
|
ModuleIndex = moduleIndex;
|
||||||
|
Address = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ADPCommand]
|
||||||
|
public class IOBoardGroup
|
||||||
|
{
|
||||||
|
// 声明底层的 2 个 IO 板卡实例
|
||||||
|
public IOBoard Board1 { get; private set; }
|
||||||
|
public IOBoard Board2 { get; private set; }
|
||||||
|
|
||||||
|
private readonly byte _slaveAddress = 1; // 默认 Modbus 从站号为 1
|
||||||
|
|
||||||
|
// 核心:严格对齐图片数据的双层嵌套配置字典 (2个模块,1-8台架)
|
||||||
|
private static readonly Dictionary<int, Dictionary<功能动作, IoPortInfo>> IoMapping
|
||||||
|
= new Dictionary<int, Dictionary<功能动作, IoPortInfo>>()
|
||||||
|
{
|
||||||
|
{ 1, new Dictionary<功能动作, IoPortInfo> {
|
||||||
|
{ 功能动作.单相充电, new IoPortInfo(1, 5) }, // Y6 -> 5
|
||||||
|
{ 功能动作.抛负载, new IoPortInfo(1, 0) }, // Y1 -> 0
|
||||||
|
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||||
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 15) } // Y9 -> 8
|
||||||
|
}},
|
||||||
|
{ 2, new Dictionary<功能动作, IoPortInfo> {
|
||||||
|
{ 功能动作.单相充电, new IoPortInfo(1, 6) }, // Y7 -> 6
|
||||||
|
{ 功能动作.抛负载, new IoPortInfo(1, 2) }, // Y3 -> 2
|
||||||
|
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||||
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 14) } // Y10 -> 9
|
||||||
|
}},
|
||||||
|
{ 3, new Dictionary<功能动作, IoPortInfo> {
|
||||||
|
{ 功能动作.单相充电, new IoPortInfo(1, 7) }, // Y8 -> 7
|
||||||
|
{ 功能动作.抛负载, new IoPortInfo(1, 3) }, // Y4 -> 3
|
||||||
|
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||||
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 13) } // Y11 -> 10
|
||||||
|
}},
|
||||||
|
{ 4, new Dictionary<功能动作, IoPortInfo> {
|
||||||
|
{ 功能动作.单相充电, new IoPortInfo(1, 8) }, // Y9 -> 8
|
||||||
|
{ 功能动作.抛负载, new IoPortInfo(1, 4) }, // Y5 -> 4
|
||||||
|
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||||
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 12) } // Y12 -> 11
|
||||||
|
}},
|
||||||
|
{ 5, new Dictionary<功能动作, IoPortInfo> {
|
||||||
|
{ 功能动作.单相充电, new IoPortInfo(2, 4) }, // Y5 -> 4
|
||||||
|
{ 功能动作.抛负载, new IoPortInfo(2, 0) }, // Y1 -> 0
|
||||||
|
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||||
|
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 11) } // Y13 -> 12
|
||||||
|
}},
|
||||||
|
{ 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)
|
||||||
|
{
|
||||||
|
Board1 = board1 ?? throw new ArgumentNullException(nameof(board1), "IOBoard Board1 实例不能为空。");
|
||||||
|
Board2 = board2 ?? throw new ArgumentNullException(nameof(board2), "IOBoard Board2 实例不能为空。");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行指定台架的功能动作控制(带高低电平开关及底层安全互锁)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="台架序号">台架/工位序号 (1 - 8)</param>
|
||||||
|
/// <param name="动作">控制功能类型 (单相充电, 抛负载, 短路测试, 蜂鸣器报警)</param>
|
||||||
|
/// <param name="开关状态">开关状态 (true: 开启吸合, false: 关闭断开)</param>
|
||||||
|
/// <param name="取消令牌">异步取消令牌</param>
|
||||||
|
public async Task 执行台架动作Async(int 台架序号, 功能动作 动作, bool 开关状态, CancellationToken 取消令牌 = default)
|
||||||
|
{
|
||||||
|
// 1. 根据传入的台架序号和动作检索字典映射关系
|
||||||
|
if (!IoMapping.TryGetValue(台架序号, out var 动作字典) || !动作字典.TryGetValue(动作, out var io信息))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"未找到台架 [{台架序号}] 对应功能 [{动作}] 的 IO 点位映射配置。");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 软件防错高危互锁逻辑
|
||||||
|
if (开关状态)
|
||||||
|
{
|
||||||
|
if (动作 == 功能动作.单相充电)
|
||||||
|
{
|
||||||
|
var 短路点位 = IoMapping[台架序号][功能动作.短路测试];
|
||||||
|
await GetBoardInstance(短路点位.ModuleIndex).写输出开关(_slaveAddress, 短路点位.Address, false);
|
||||||
|
}
|
||||||
|
else if (动作 == 功能动作.短路测试)
|
||||||
|
{
|
||||||
|
var 充电点位 = IoMapping[台架序号][功能动作.单相充电];
|
||||||
|
await GetBoardInstance(充电点位.ModuleIndex).写输出开关(_slaveAddress, 充电点位.Address, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 动态获取目标板卡实例并直接驱动开关端口
|
||||||
|
IOBoard 目标板卡 = GetBoardInstance(io信息.ModuleIndex);
|
||||||
|
await 目标板卡.写输出开关(_slaveAddress, io信息.Address, 开关状态);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 内部路由辅助方法:根据板卡索引安全获取当前连接的实例对象
|
||||||
|
/// </summary>
|
||||||
|
private IOBoard GetBoardInstance(int moduleIndex)
|
||||||
|
{
|
||||||
|
IOBoard board = moduleIndex switch
|
||||||
|
{
|
||||||
|
1 => Board1,
|
||||||
|
2 => Board2,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(moduleIndex), "非法的板卡序号,系统仅支持 1# 和 2# 模块。")
|
||||||
|
};
|
||||||
|
|
||||||
|
return board ?? throw new InvalidOperationException($"IO板卡模块 [{moduleIndex}#] 尚未完成初始化,无法建立总线通信。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,54 +6,70 @@ using System.Globalization;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
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
|
||||||
|
|
||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class IT7800E : Tcp
|
public class IT7800E : Tcp
|
||||||
{
|
{
|
||||||
//只有一个,八台产品一起用一个
|
// 八台产品共用一个,使用换行符 \n (ASCII 0x0A) 作为 SCPI 结束符[cite: 1]
|
||||||
// 根据通用 SCPI 指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
|
||||||
private const string ScpiDelimiter = "\n";
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化 IT7800E 交直流电源通信参数。
|
|
||||||
/// </summary>
|
|
||||||
public IT7800E(TcpConfig config) : base(config)
|
public IT7800E(TcpConfig config) : base(config)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 1. IEEE 488.2 公共命令
|
#region 1. IEEE 488.2 公共命令
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
await SendAsync($"*CLS{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取电源识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 复位命令。使电源恢复到出厂默认预定义安全值
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //
|
await SendAsync($"*RST{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取状态字节寄存器
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -61,81 +77,57 @@ namespace DeviceCommand.Device
|
|||||||
#region 2. 耦合模式与拉载开关控制
|
#region 2. 耦合模式与拉载开关控制
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置电源输出源的电气模式/工作耦合模式
|
/// 设置电源输出源的电气模式/工作耦合模式 (AC, DC, ACDC, DCAC)[cite: 1]
|
||||||
/// (AC: 纯交流, DC: 纯直流, ACDC: 交直流混合模式)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电源模式(string 模式, CancellationToken ct = default)
|
public virtual async Task 设置电源模式(PowerCouplingMode 模式, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string modeUpper = 模式.ToUpper();
|
await SendAsync($":SOURce:FUNCtion {模式}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
if (modeUpper != "AC" && modeUpper != "DC" && modeUpper != "ACDC")
|
|
||||||
throw new ArgumentException("工作模式只能为 AC, DC, 或 ACDC");
|
|
||||||
|
|
||||||
await SendAsync($":SOURce:MODE {modeUpper}{ScpiDelimiter}", ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询电源当前的工作模式
|
/// 查询电源当前的工作电气模式[cite: 1]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询电源模式(CancellationToken ct = default)
|
public virtual async Task<string> 查询电源模式(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":SOURce:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":SOURce:FUNCtion?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 控制电源的主输出开关 (True: 开启输出, False: 关闭输出)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 注意:虽然方法名叫设置DC输出,但对交直流电源它代表通用的主输出控制(Main Output)
|
string 参数 = 开启 ? "ON" : "OFF"; //[cite: 1]
|
||||||
string 参数 = 开启 ? "ON" : "OFF";
|
await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询电源当前主输出开关状态 (返回 ON 或 OFF)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
|
#region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设定交流(AC)模式下的电压有效值 (单位: V)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:AC {0:F2}{1}", 电压, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设定直流(DC)模式或交直流(AC+DC)模式下的直流偏置电压值 (单位: V)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设定电源交流输出的频率值 (单位: Hz)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
|
public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter); //[cite: 1]
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置硬件交流输出的有效值限流门限 (单位: A)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,106 +135,127 @@ namespace DeviceCommand.Device
|
|||||||
|
|
||||||
#region 4. 测量与数据回测(高频数据轮询核心)
|
#region 4. 测量与数据回测(高频数据轮询核心)
|
||||||
|
|
||||||
/// <summary>
|
[Monitorable("交流电源电压")]
|
||||||
/// 查询通道实时测得的电压有效值(RMS,支持 AC 或 DC 模式下的回测)(单位: V)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[Monitorable("交流电源电流")]
|
||||||
/// 查询通道实时测得的电流有效值(RMS)(单位: A)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
[Monitorable("交流电源功率")]
|
||||||
/// 查询通道实时测得的有功功率值 (单位: W)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询通道实时测得的视在功率 (单位: VA)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询视在功率(CancellationToken ct = default)
|
public virtual async Task<string> 查询视在功率(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询通道实时测得的输出频率 (单位: Hz)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询实际频率(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际频率(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查询当前的功率因数 (PF)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询功率因数(CancellationToken ct = default)
|
public virtual async Task<string> 查询功率因数(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:PF?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":MEASure:POWer:PFACtor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 5. 保护参数设置与系统监控
|
#region 5. 保护参数设置与系统监控
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置电源的过流保护(OCP)值 (单位: A)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection {0:F3}{1}", 电流, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection:RMS {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置电源的过压保护(OVP)值 (单位: V)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection {0:F2}{1}", 电压, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection:PEAK {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 读取系统的出错记录信息(获取上一条未处理的错误码)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 切换仪器至计算机控制的远程控制模式 (锁定前面板按键)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
|
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct); //[cite: 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 切换仪器至前面板操作的本地模式 (前面板解锁)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
|
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>
|
/// <summary>
|
||||||
/// 清除仪器当前的保护状态(例如触发 OCP/OVP 被锁死后进行软件解锁)
|
/// 读取当前充放电累计的安时值 (单位: Ah)[cite: 1]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
public virtual async Task<string> 查询累计安时_Ah(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
|
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
|
#endregion
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ using System.Globalization;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
[ADPCommand]
|
//[ADPCommand]
|
||||||
public class N36200 : Tcp
|
public class N36200 : Tcp
|
||||||
{
|
{
|
||||||
//只有一个,八台产品一起用一个
|
//只有一个,八台产品一起用一个
|
||||||
@@ -226,6 +226,7 @@ namespace DeviceCommand.Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.4.1. 回读通道输出端子上的实时测得电压值 (单位: V)
|
/// 3.4.1. 回读通道输出端子上的实时测得电压值 (单位: V)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("高压直流电源电压")]
|
||||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
@@ -234,6 +235,7 @@ namespace DeviceCommand.Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.4.2. 回读通道输出端子上的实时测得电流值 (单位: A)
|
/// 3.4.2. 回读通道输出端子上的实时测得电流值 (单位: A)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("高压直流电源电流")]
|
||||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
@@ -242,6 +244,7 @@ namespace DeviceCommand.Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.4.3. 回读通道输出端子上的实时测得功率值 (单位: W)
|
/// 3.4.3. 回读通道输出端子上的实时测得功率值 (单位: W)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("高压直流电源功率")]
|
||||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using System.Globalization;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class N36600 : Tcp
|
public class N36600 : Tcp
|
||||||
@@ -172,7 +172,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
|
public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":APPLY {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
|
// 修正:去除前缀冒号,规范应用 APPL 短格式
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "APPL {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +182,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询快捷电压电流设定(CancellationToken ct = default)
|
public virtual async Task<string> 查询快捷电压电流设定(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":APPLY?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号
|
||||||
|
return await WriteReadAsync($"APPL?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -191,25 +193,31 @@ namespace DeviceCommand.Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
|
/// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("低压直流电源电流")]
|
||||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:CURRent?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号,改用标准 MEAS:CURR?
|
||||||
|
return await WriteReadAsync($"MEAS:CURR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
|
/// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("低压直流电源功率")]
|
||||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:POWer?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号,改用标准 MEAS:POW?
|
||||||
|
return await WriteReadAsync($"MEAS:POW?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
|
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("低压直流电源电压")]
|
||||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:VOLTage?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号,改用标准 MEAS:VOLT?
|
||||||
|
return await WriteReadAsync($"MEAS:VOLT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -217,31 +225,18 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号
|
||||||
|
return await WriteReadAsync($"MEAS:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.5.5 查询通道输出端子上测得的电池容量
|
/// 3.5.5 查询通道输出端子上测得的输出时间长度
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询测得电池容量(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($":MEASure:CAPAcity?{SCPIDelimiter}", SCPIDelimiter, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 3.5.6 查询通道输出端子上测得的恒流输出时间长度 (单位: ms)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询恒流输出时间长度(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($":MEASure:TIMEr:CC?{SCPIDelimiter}", SCPIDelimiter, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 3.5.7 查询通道输出端子上测得的输出时间长度
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
|
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:TIME:OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号
|
||||||
|
return await WriteReadAsync($"MEAS:TIME:OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -253,8 +248,9 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// 修正:去除前缀冒号,改用 OUTP 短格式
|
||||||
string 状态 = 开启 ? "ON" : "OFF";
|
string 状态 = 开启 ? "ON" : "OFF";
|
||||||
await SendAsync($":OUTPut {状态}{SCPIDelimiter}", ct);
|
await SendAsync($"OUTP {状态}{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -262,7 +258,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号
|
||||||
|
return await WriteReadAsync($"OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -270,8 +267,9 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
|
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// 修正:去除前缀冒号
|
||||||
string 状态 = 开启 ? "ON" : "OFF";
|
string 状态 = 开启 ? "ON" : "OFF";
|
||||||
await SendAsync($":OUTPut:TIMEr {状态}{SCPIDelimiter}", ct);
|
await SendAsync($"OUTP:TIME {状态}{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -279,7 +277,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
|
public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":OUTPut:TIMEr:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
|
// 修正:去除前缀冒号
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "OUTP:TIME:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,8 +287,9 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置输出模式优先(bool 是CV优先, CancellationToken ct = default)
|
public virtual async Task 设置输出模式优先(bool 是CV优先, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// 修正:去除前缀冒号
|
||||||
string 模式 = 是CV优先 ? "CV" : "CC";
|
string 模式 = 是CV优先 ? "CV" : "CC";
|
||||||
await SendAsync($":OUTPut:MODE {模式}{SCPIDelimiter}", ct);
|
await SendAsync($"OUTP:MODE {模式}{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -301,7 +301,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
|
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":CURRent:PROTection:CLEar{SCPIDelimiter}", ct);
|
// 修正:去除前缀冒号
|
||||||
|
await SendAsync($"CURR:PROT:CLE{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -309,8 +310,9 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
|
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// 修正:去除前缀冒号,将 STATE 改为更常用的 STAT 简写
|
||||||
string 状态 = 启用 ? "ON" : "OFF";
|
string 状态 = 启用 ? "ON" : "OFF";
|
||||||
await SendAsync($":CURRent:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
await SendAsync($"CURR:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -318,7 +320,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
|
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent:PROTection {0:F3}{1}", 电流, SCPIDelimiter);
|
// 修正:去除前缀冒号。按 NGI 通用指令树,加上层级限制或者直接作用于 CURR:PROT
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR:PROT {0:F3}{1}", 电流, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,7 +330,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F3}{1}", 电流, SCPIDelimiter);
|
// 修正:去除前缀冒号,改用标准 CURR 短格式
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR {0:F3}{1}", 电流, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +340,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
|
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":VOLTage:PROTection:CLEar{SCPIDelimiter}", ct);
|
// 修正:去除前缀冒号
|
||||||
|
await SendAsync($"VOLT:PROT:CLE{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -344,8 +349,9 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
|
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// 修正:去除前缀冒号
|
||||||
string 状态 = 启用 ? "ON" : "OFF";
|
string 状态 = 启用 ? "ON" : "OFF";
|
||||||
await SendAsync($":VOLTage:PROTection:STATE {状态}{SCPIDelimiter}", ct);
|
await SendAsync($"VOLT:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -353,7 +359,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:PROTection {0:F3}{1}", 电压, SCPIDelimiter);
|
// 修正:去除前缀冒号
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:PROT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +369,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, SCPIDelimiter);
|
// 修正:去除前缀冒号,改用标准 VOLT 短格式
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,16 +379,25 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:LIMIT {0:F3}{1}", 电压, SCPIDelimiter);
|
// 修正:去除前缀冒号
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:LIM {0:F3}{1}", 电压, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 3.7.2.9 清除电压上限限制(恢复为设备物理允许的最大电压值)
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 清除电压上限限制(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 发送 MAXimum 设为最大值以达到“清除限制”的效果
|
||||||
|
await SendAsync($"VOLT:LIMIT MAX{SCPIDelimiter}", ct); // 对应手册 3.7.2.8
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 3.7.3.1 设置输出功率值 (W)
|
/// 3.7.3.1 设置输出功率值 (W)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
|
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, SCPIDelimiter);
|
// 修正:去除前缀冒号,改用标准 POW 短格式
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "POW {0:F3}{1}", 功率, SCPIDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,8 +406,9 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
|
public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// 修正:去除前缀冒号
|
||||||
string 状态 = 启用 ? "ON" : "OFF";
|
string 状态 = 启用 ? "ON" : "OFF";
|
||||||
await SendAsync($":POWer:STATE {状态}{SCPIDelimiter}", ct);
|
await SendAsync($"POW:STAT {状态}{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -402,8 +420,9 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
|
public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
// 修正:去除前缀冒号
|
||||||
string 状态 = 开启 ? "ON" : "OFF";
|
string 状态 = 开启 ? "ON" : "OFF";
|
||||||
await SendAsync($":SYSTem:BEEPer:STATE {状态}{SCPIDelimiter}", ct);
|
await SendAsync($"SYST:BEEP:STAT {状态}{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -411,7 +430,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
|
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":SYSTem:BEEPer{SCPIDelimiter}", ct);
|
// 修正:去除前缀冒号
|
||||||
|
await SendAsync($"SYST:BEEP{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -419,7 +439,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
|
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":SYSTem:ERRor:COUNT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号
|
||||||
|
return await WriteReadAsync($"SYST:ERR:COUN?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -427,7 +448,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":SYSTem:ERRor?{SCPIDelimiter}", SCPIDelimiter, ct);
|
// 修正:去除前缀冒号
|
||||||
|
return await WriteReadAsync($"SYST:ERR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -435,7 +457,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":SYSTem:LOCal{SCPIDelimiter}", ct);
|
// 修正:去除前缀冒号
|
||||||
|
await SendAsync($"SYST:LOC{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -443,7 +466,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":SYSTem:REMote{SCPIDelimiter}", ct);
|
// 修正:去除前缀冒号
|
||||||
|
await SendAsync($"SYST:REM{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -451,7 +475,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
|
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":SYSTem:RWLock{SCPIDelimiter}", ct);
|
// 修正:去除前缀冒号
|
||||||
|
await SendAsync($"SYST:RWL{SCPIDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using System.Globalization;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class N69200 : Tcp
|
public class N69200 : Tcp
|
||||||
@@ -24,15 +24,22 @@ namespace DeviceCommand.Device
|
|||||||
#region 3.1. IEEE 488.2 公共命令
|
#region 3.1. IEEE 488.2 公共命令
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
/// 清除状态命令。清除标准事件状态寄存器和错误队列 (*CLS)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 查询错误信息
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取电子负载设备识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
/// 读取电子负载设备识别字符串 (*IDN?)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -40,7 +47,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 复位命令。使电子负载恢复到出厂默认配置状态
|
/// 复位命令。使电子负载恢复到出厂默认配置状态 (*RST)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -48,7 +55,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取标准状态字节寄存器
|
/// 读取标准状态字节寄存器 (*STB?)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -56,18 +63,24 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存当前系统参数到指定的存储单元 (单元范围依型号而定)
|
/// 保存当前系统参数到指定的存储单元 (1~100)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 保存当前状态(int 单元, CancellationToken ct = default)
|
public virtual async Task 保存当前状态(int 单元, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
if (单元 < 1 || 单元 > 100)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(单元), "存储单元位置应在 1~100 之间");
|
||||||
|
|
||||||
await SendAsync($"*SAV {单元}{ScpiDelimiter}", ct);
|
await SendAsync($"*SAV {单元}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 调用指定存储单元中保存的系统参数
|
/// 调用指定存储单元中保存的系统参数 (1~100)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 调用存储状态(int 单元, CancellationToken ct = default)
|
public virtual async Task 调用存储状态(int 单元, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
|
if (单元 < 1 || 单元 > 100)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(单元), "存储单元位置应在 1~100 之间");
|
||||||
|
|
||||||
await SendAsync($"*RCL {单元}{ScpiDelimiter}", ct);
|
await SendAsync($"*RCL {单元}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,34 +89,54 @@ namespace DeviceCommand.Device
|
|||||||
#region 3.2. 电子负载模式切换及拉载开关
|
#region 3.2. 电子负载模式切换及拉载开关
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制电子负载的输入控制开关(True: 开启拉载输入, False: 关闭拉载输入)
|
/// 控制电子负载的输入控制开关(INPut:STATe <bool>)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置DC输入(bool 开启, CancellationToken ct = default)
|
public virtual async Task 设置DC输入(bool 开启, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string 参数 = 开启 ? "ON" : "OFF";
|
string 参数 = 开启 ? "1" : "0";
|
||||||
await SendAsync($":INPut {参数}{ScpiDelimiter}", ct);
|
await SendAsync($"INPut:STATe {参数}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询电子负载当前的拉载开关状态 (返回 ON 或 OFF)
|
/// 查询电子负载当前的拉载开关状态 (返回 0 或 1)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询DC输入状态(CancellationToken ct = default)
|
public virtual async Task<string> 查询DC输入状态(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":INPut?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"INPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置电子负载的基本工作模式
|
/// 设置电子负载的基本工作模式 (INPut:FUNCtion <mode>)
|
||||||
/// (CC: 恒电流, CV: 恒电压, CP: 恒功率, CR: 恒电阻)
|
/// (支持 CC, CV, CR, CP, CCD, CVD, CRD, CPD, OCP, OPP, ESR, MPP, SWEEP 等)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置负载模式(string 模式, CancellationToken ct = default)
|
public enum DeviceWorkMode
|
||||||
{
|
{
|
||||||
// 转换为大写以确保设备命令解析兼容
|
CC,
|
||||||
string modeUpper = 模式.ToUpper();
|
CV,
|
||||||
if (modeUpper != "CC" && modeUpper != "CV" && modeUpper != "CP" && modeUpper != "CR")
|
CR,
|
||||||
throw new ArgumentException("工作模式只能为 CC, CV, CP, 或 CR");
|
CP,
|
||||||
|
CCD,
|
||||||
await SendAsync($":MODE {modeUpper}{ScpiDelimiter}", ct);
|
ESR,
|
||||||
|
AUTO,
|
||||||
|
DISCHARGE,
|
||||||
|
CHARGE,
|
||||||
|
OCP,
|
||||||
|
CVD,
|
||||||
|
CRD,
|
||||||
|
MPP,
|
||||||
|
CVCC,
|
||||||
|
CRCC,
|
||||||
|
CPCC,
|
||||||
|
CVCR,
|
||||||
|
WAVE,
|
||||||
|
SWEEP,
|
||||||
|
OPP,
|
||||||
|
CPD,
|
||||||
|
SZ
|
||||||
|
}
|
||||||
|
public virtual async Task 设置负载模式(DeviceWorkMode 模式, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await SendAsync($"INPut:FUNCtion {Enum.GetName(模式)}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -111,79 +144,79 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询负载模式(CancellationToken ct = default)
|
public virtual async Task<string> 查询负载模式(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"INPut:FUNCtion?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 3.3. 负载参数设置 (CC / CV / CP / CR)
|
#region 3.3. 负载参数设置 (CC / CV / CP / CR 定态大量程)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设定恒电流模式(CC)下的拉载电流目标值 (单位: A)
|
/// 设定恒电流模式(CC)大量程下的拉载电流目标值 (单位: A)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置恒电流CC(double 电流, CancellationToken ct = default)
|
public virtual async Task 设置恒电流CC(double 电流, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F4}{1}", 电流, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CC:HIGH:LEVel {0:F4}{1}", 电流, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询恒电流模式下设定的拉载电流值
|
/// 查询恒电流模式大量程下设定的拉载电流值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询恒电流CC设定(CancellationToken ct = default)
|
public virtual async Task<string> 查询恒电流CC设定(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"STATic:CC:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设定恒电压模式(CV)下的拉载电压目标值 (单位: V)
|
/// 设定恒电压模式(CV)大量程下的拉载电压目标值 (单位: V)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置恒电压CV(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置恒电压CV(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CV:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询恒电压模式下设定的拉载电压值
|
/// 查询恒电压模式大量程下设定的拉载电压值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询恒电压CV设定(CancellationToken ct = default)
|
public virtual async Task<string> 查询恒电压CV设定(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"STATic:CV:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设定恒功率模式(CP)下的拉载功率目标值 (单位: W)
|
/// 设定恒功率模式(CP)大量程下的拉载功率目标值 (单位: W)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置恒功率CP(double 功率, CancellationToken ct = default)
|
public virtual async Task 设置恒功率CP(double 功率, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CP:HIGH:LEVel {0:F3}{1}", 功率, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询恒功率模式下设定的拉载功率值
|
/// 查询恒功率模式大量程下设定的拉载功率值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询恒功率CP设定(CancellationToken ct = default)
|
public virtual async Task<string> 查询恒功率CP设定(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"STATic:CP:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设定恒电阻模式(CR)下的等效拉载电阻阻值 (单位: Ω)
|
/// 设定恒电阻模式(CR)大量程下的等效拉载电阻阻值 (单位: Ω)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置恒电阻CR(double 电阻, CancellationToken ct = default)
|
public virtual async Task 设置恒电阻CR(double 电阻, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":RESistance {0:F4}{1}", 电阻, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CR:HIGH:LEVel {0:F4}{1}", 电阻, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询恒电阻模式下设定的拉载电阻值
|
/// 查询恒电阻模式大量程下设定的拉载电阻值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询恒电阻CR设定(CancellationToken ct = default)
|
public virtual async Task<string> 查询恒电阻CR设定(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":RESistance?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"STATic:CR:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -193,33 +226,28 @@ namespace DeviceCommand.Device
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 回读电子负载输入端子上的实时测得电压值 (单位: V)
|
/// 回读电子负载输入端子上的实时测得电压值 (单位: V)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("高压直流负载电压")]
|
||||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 回读电子负载输入端子上的实时测得电流值 (单位: A)
|
/// 回读电子负载输入端子上的实时测得电流值 (单位: A)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("高压直流负载电流")]
|
||||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 回读电子负载输入端子上的实时测得功率值 (单位: W)
|
/// 回读电子负载输入端子上的实时测得功率值 (单位: W)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Monitorable("高压直流负载功率")]
|
||||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 一键回读当前负载测得的电压、电流和功率的组合数组数据
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
return await WriteReadAsync($":MEASure:VAP?{ScpiDelimiter}", ScpiDelimiter, ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -227,29 +255,29 @@ namespace DeviceCommand.Device
|
|||||||
#region 3.5. 保护限制参数设置
|
#region 3.5. 保护限制参数设置
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置电子负载的过流保护(OCP)报警值 (单位: A)
|
/// 设置电子负载的电流软保护阈值 (单位: A, 设置为 0 表示关闭)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置过流保护值_OCP(double 电流, CancellationToken ct = default)
|
public virtual async Task 设置过流保护值_OCP(double 电流, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:CURRent:PROTection {0:F3}{1}", 电流, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置电子负载的过功率保护(OPP)报警值 (单位: W)
|
/// 设置电子负载的功率软保护阈值 (单位: W, 设置为 0 表示关闭)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置过功率保护值_OPP(double 功率, CancellationToken ct = default)
|
public virtual async Task 设置过功率保护值_OPP(double 功率, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:POWer {0:F3}{1}", 功率, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:POWer:PROTection {0:F3}{1}", 功率, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置电子负载的过压保护(OVP)报警值 (单位: V)
|
/// 设置电子负载的电压软保护阈值 (单位: V, 设置为 0 表示关闭)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置过压保护值_OVP(double 电压, CancellationToken ct = default)
|
public virtual async Task 设置过压保护值_OVP(double 电压, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:VOLTage:PROTection {0:F3}{1}", 电压, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,37 +286,290 @@ namespace DeviceCommand.Device
|
|||||||
#region 3.6. 系统控制及报警清除
|
#region 3.6. 系统控制及报警清除
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取仪器的出错记录记录 (当系统产生告警或指令解析异常时读取)
|
/// 读取负载报警/事件寄存器信息 (MEASure:EVENT?)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
public virtual async Task<string> 查询报警事件信息(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
|
return await WriteReadAsync($"MEASure:EVENT?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 切换电子负载到本地面板操作状态 (前面板按键解锁)
|
/// 清除负载当前的保护触发锁定状态 (INPut:CLearPROTect)
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 切换电子负载到远程计算机操作状态 (前面板按键锁定)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除负载当前的软件过流/过功率等保护(Protection)锁定触发状态
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
|
await SendAsync($"INPut:CLearPROTect{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 3.7. 手册第 112~124 页中的原子控制命令
|
||||||
|
|
||||||
|
#region 组合测试 (COMBination:*)
|
||||||
|
|
||||||
|
public virtual async Task 设置组合量程_CVCC(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"COMBination:CVCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置恒压速率_CVCC(int 速率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"COMBination:CVCC:HIGH:RATE {速率}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置恒压大量程电压_CVCC(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCC:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置限定电流_CVCC(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置组合量程_CRCC(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"COMBination:CRCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置恒阻小量程电阻_CRCC(double 电阻, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:LEVel {0:F4}{1}", 电阻, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置限定电流_CRCC(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置上升斜率_CRCC(double 斜率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:SLEWRate:RAIse {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置下降斜率_CRCC(double 斜率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:SLEWRate:FALL {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置组合量程_CPCC(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"COMBination:CPCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置恒功率小量程功率_CPCC(double 功率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CPCC:LOW:LEVel {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置限定电流_CPCC(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CPCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置组合量程_CVCR(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"COMBination:CVCR:RANGE {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置恒压大量程电压_CVCR(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCR:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置限定电阻_CVCR(double 电阻, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCR:CLIMit {0:F4}{1}", 电阻, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 瞬态测试 (DYNAmic:*)
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态运行方式_CC(int 模式, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"DYNAmic:CC:MODe {模式}{ScpiDelimiter}", ct); // 0-连续, 1-单次, 2-A/B
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态量程_CC(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"DYNAmic:CC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态主值电流_CC(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:LEVel:MAIN {0:F4}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态瞬态值电流_CC(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:LEVel:TRANSient {0:F4}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态上升斜率_CC(double 斜率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:SLEWRate:RAIse {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态下降斜率_CC(double 斜率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:SLEWRate:FALL {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态主值脉宽_CC(double 时间_ms, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:WIDth:MAIN {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态瞬态脉宽_CC(double 时间_ms, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:WIDth:TRANSient {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态运行方式_CV(int 模式, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"DYNAmic:CV:MODe {模式}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态量程_CV(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"DYNAmic:CV:RANGE {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态主值电压_CV(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:HIGH:LEVel:MAIN {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态瞬态值电压_CV(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:HIGH:LEVel:TRANSient {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态主值脉宽_CV(double 时间_ms, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:WIDth:MAIN {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置瞬态瞬态脉宽_CV(double 时间_ms, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:WIDth:TRANSient {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 充放电测试 (DISCHarge:* / CHARge:*)
|
||||||
|
|
||||||
|
public virtual async Task 设置放电电流(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:CURRent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置放电终止电压(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:VOLTage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置放电终止时间(double 时间_s, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:TIMe {0:F0}{1}", 时间_s, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置放电终止容量(double 容量_Ah, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:CAPacity {0:F3}{1}", 容量_Ah, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询放电实时时间(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"DISCHarge:ECHO:TIMe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询放电实时容量(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"DISCHarge:ECHo:CAPacity?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置充电电流(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "CHARge:CURRent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置充电电压(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "CHARge:VOLTage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置充电恒压时间(int 时间_s, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"CHARge:TIMe {时间_s}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region OCP / OPP 保护扫描测试 (OCP:* / OPP:*)
|
||||||
|
|
||||||
|
public virtual async Task 设置OCP初始电流(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:BCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置OCP步进电流(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:SCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置OCP单步时间(double 时间_s, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:Time {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置OCP终止电压(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:EVOLtage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询OCP状态(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"OCP:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取OCP结果电流(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"OCP:RCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置OPP初始功率(double 功率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:BPOWer {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置OPP步进功率(double 功率, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:SPOWer {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置OPP单步时间(double 时间_s, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:TIME {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置OPP终止电压(double 电压, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:EVOLtage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询OPP状态(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"OPP:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取OPP结果功率(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"OPP:RPOWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 扫频 SWEEP 与 波形拉载 WAVE (SWEEP:* / WAVE:*)
|
||||||
|
|
||||||
|
public virtual async Task 设置扫频最小电流(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:TCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置扫频最大电流(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:MCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置扫频起始频率(double 频率_Hz, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:SFRequency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置扫频结束频率(double 频率_Hz, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:EFRequency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置扫频单步时间(double 时间_ms, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:STPTime {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置扫频占空比(double 占空比, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:DUTYcycle {0:F1}{1}", 占空比, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取扫频当前运行频率(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"SWEEP:CFRequency?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取扫频测试结果(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"SWEEP:RESult?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置波形量程(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"WAVE:RANGe {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置波形类型(int 类型, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"WAVE:TYPe {类型}{ScpiDelimiter}", ct); // 0-正弦波, 1-方波, 2-三角波, 3-锯齿波, 4-自定义
|
||||||
|
|
||||||
|
public virtual async Task 设置波形频率(double 频率_Hz, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:FREQuency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置波形幅值(double 幅值, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:SETValue {0:F3}{1}", 幅值, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置波形时间(double 时间_ms, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:TIMe {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置波形叠加值(double 叠加值, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:BVALue {0:F3}{1}", 叠加值, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 内阻测试 ESR (ESR:*)
|
||||||
|
|
||||||
|
public virtual async Task 设置ESR带载量程(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"ESR:LOADRange {量程}{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置ESR大量程带载电流(double 电流, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "ESR:CURRenthigh {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置ESR测量方法(int 方法, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"ESR:MODE {方法}{ScpiDelimiter}", ct); // 0-方波平均法, 1-单脉冲法
|
||||||
|
|
||||||
|
public virtual async Task 设置ESR采样量程(int 量程, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"ESR:MEASurerange {量程}{ScpiDelimiter}", ct); // 0-1000mV, 1-100mV, 2-10mV
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询ESR状态(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"ESR:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取ESR结果(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"ESR:RESult?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region MPPT 测试 (MPP:*)
|
||||||
|
|
||||||
|
public virtual async Task 设置MPPT步进电压(double 电压_V, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "MPP:BVOLtage {0:F3}{1}", 电压_V, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置MPPT步进时间(double 时间_s, CancellationToken ct = default)
|
||||||
|
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "MPP:TIME {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||||
|
|
||||||
|
public virtual async Task 设置MPPT模式(int 模式, CancellationToken ct = default)
|
||||||
|
=> await SendAsync($"MPP:MODE {模式}{ScpiDelimiter}", ct); // 0-扫描模式, 1-跟踪模式
|
||||||
|
|
||||||
|
public virtual async Task<string> 查询MPPT状态(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"MPP:STATus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取MPPT最大功率(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"MPP:MPOWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取MPPT最大功率电压(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"MPP:MVOLtage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取MPPT最大功率电流(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"MPP:MCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取MPPT开路电压(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"MPP:OVOLtage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
public virtual async Task<string> 读取MPPT短路电流(CancellationToken ct = default)
|
||||||
|
=> await WriteReadAsync($"MPP:SCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,20 +3,20 @@ using DeviceCommand.Base;
|
|||||||
using Model.Models;
|
using Model.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class SDS2000X_HD : Tcp
|
public class SDS2000X_HD : Tcp
|
||||||
{
|
{
|
||||||
// 示波器底层 Socket 字符串命令通常以换行符 \n 结束
|
// 示波器底层 Socket 字符串命令以换行符 \n 结束
|
||||||
private const string ScpiDelimiter = "\n";
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化示波器通信参数。
|
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化示波器通信参数。
|
||||||
/// 鼎阳示波器网口 Socket 默认端口通常为 5025,请在配置中设置。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SDS2000X_HD(TcpConfig config) : base(config)
|
public SDS2000X_HD(TcpConfig config) : base(config)
|
||||||
{
|
{
|
||||||
@@ -31,7 +31,13 @@ namespace DeviceCommand.Device
|
|||||||
{
|
{
|
||||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 查询错误信息
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||||
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取示波器识别字符串(制造商、型号、序列号、固件版本)
|
/// 读取示波器识别字符串(制造商、型号、序列号、固件版本)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -49,8 +55,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【补充】查询先前操作是否完成。
|
/// 查询先前操作是否完成。
|
||||||
/// 在重置设备(*RST)或切换大物理量程后调用,返回 "1" 代表示波器继电器切换就绪,防止后续指令引发阻塞。
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<bool> 检查操作完成_OPC(CancellationToken ct = default)
|
public virtual async Task<bool> 检查操作完成_OPC(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -63,65 +68,82 @@ namespace DeviceCommand.Device
|
|||||||
#region 2. 运行与捕获控制 (Run / Stop / Single)
|
#region 2. 运行与捕获控制 (Run / Stop / Single)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
|
/// 控制示波器开始捕获波形
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"RUN{ScpiDelimiter}", ct);
|
await SendAsync($":TRIGger:RUN{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 停止捕获波形 (等同于按下前端面板的 Stop 键)
|
/// 停止捕获波形
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"STOP{ScpiDelimiter}", ct);
|
await SendAsync($"TRIGger:STOP{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 强制示波器进入单次触发捕获模式 (常用于捕捉充电瞬间的过冲浪涌波形)
|
/// 强制示波器进入单次触发捕获模式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"SINGle{ScpiDelimiter}", ct);
|
await SendAsync($":TRIGger:MODE SINGle{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 触发一次波形采样 (当触发源设为 Manual 时使用)
|
/// 触发一次波形采样
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 强制触发(CancellationToken ct = default)
|
public virtual async Task 强制触发(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*TRG{ScpiDelimiter}", ct);
|
await SendAsync($"::TRIGger:MODE FTRIG{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【补充】设置触发模式 (AUTO, NORM, SINGLE)
|
/// 示波器触发控制模式(符合 SDS2000X-HD 规范)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发模式(string mode, CancellationToken ct = default)
|
public enum TriggerMode
|
||||||
{
|
{
|
||||||
string modeUpper = mode.ToUpper();
|
/// <summary>
|
||||||
if (modeUpper != "AUTO" && modeUpper != "NORM" && modeUpper != "SINGLE")
|
/// 自动触发模式 (即使无触发信号也周期性刷屏)
|
||||||
throw new ArgumentException("触发模式只能为 AUTO, NORM, 或 SINGLE");
|
/// </summary>
|
||||||
|
AUTO,
|
||||||
|
|
||||||
await SendAsync($"TRMD {modeUpper}{ScpiDelimiter}", ct);
|
/// <summary>
|
||||||
|
/// 普通触发模式 (仅当满足触发条件时才刷新)
|
||||||
|
/// </summary>
|
||||||
|
NORM,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单次触发模式 (捕捉到一次满足条件的信号后立刻 STOP)
|
||||||
|
/// </summary>
|
||||||
|
SINGLE
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 设置触发模式 (AUTO 自动, NORM 普通, SINGLE 单次)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">触发模式枚举</param>
|
||||||
|
/// <param name="ct">取消令牌</param>
|
||||||
|
public virtual async Task 设置触发模式(TriggerMode mode, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 例如发送:TRMD AUTO\n、TRMD NORM\n 或 TRMD SINGLE\n
|
||||||
|
string cmd = $"TRMD {mode}{ScpiDelimiter}";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启或关闭指定的模拟通道
|
/// 开启或关闭指定的模拟通道 (符合手册 57 页规范)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:根据手册规范,使用 1/0 比 ON/OFF 在高低版本固件中兼容性更稳定
|
string state = enable ? "ON" : "OFF";
|
||||||
string state = enable ? "1" : "0";
|
|
||||||
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct);
|
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置指定通道的垂直电压档位 (Volts/Div,单位: V,例如 0.05 代表 50mV/div)
|
/// 设置指定通道的垂直电压档位 (Volts/Div)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -130,7 +152,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【补充验证】查询指定通道当前的电压档位 (用于 Setup-Verify 闭环验证逻辑)
|
/// 查询指定通道当前的电压档位
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询通道电压档位(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询通道电压档位(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -138,22 +160,29 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置指定通道的垂直偏移量 (Offset,单位: V)
|
/// 设置指定通道的垂直偏移量 (Offset)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default)
|
public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置通道的输入阻抗与耦合模式
|
/// 示波器通道输入阻抗类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="coupling">合法参数:A1M (交流1M), D1M (直流1M), D50 (直流50欧)</param>
|
public enum ImpedanceType
|
||||||
public virtual async Task 设置通道耦合与阻抗(int channel, string coupling, CancellationToken ct = default)
|
|
||||||
{
|
{
|
||||||
string coupUpper = coupling.ToUpper();
|
/// <summary>50Ω 阻抗</summary>
|
||||||
await SendAsync($"C{channel}:COUPling {coupUpper}{ScpiDelimiter}", ct);
|
FIFty,
|
||||||
|
|
||||||
|
/// <summary>1MΩ 阻抗</summary>
|
||||||
|
ONEM
|
||||||
|
}
|
||||||
|
public virtual async Task 设置通道阻抗(int channel, ImpedanceType impedance, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 例如发送:C1:IMPedance FIFty\n 或 C1:IMPedance ONEM\n
|
||||||
|
string cmd = $"CHANnel{channel}:IMPedance {impedance}{ScpiDelimiter}";
|
||||||
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -161,21 +190,19 @@ namespace DeviceCommand.Device
|
|||||||
#region 4. Timebase 水平时基子系统
|
#region 4. Timebase 水平时基子系统
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置示波器的水平时基档位 (Time/Div,单位: s,例如 0.001 代表 1ms/div)
|
/// 设置示波器的水平时基档位 (Time/Div)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:统一使用更精简且符合手册定义的简写形式 TDIV
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置示波器的触发水平延迟位置 (Horizontal Delay,单位: s)
|
/// 设置示波器的触发水平延迟位置 (Horizontal Delay)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:采用手册标准简写 TRDL 效率更高
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
@@ -185,12 +212,12 @@ namespace DeviceCommand.Device
|
|||||||
#region 5. Trigger 触发子系统
|
#region 5. Trigger 触发子系统
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置边沿触发的电平值 (Trigger Level,单位: V)
|
/// 设置边沿触发的电平值 (Trigger Level)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:采用标准简写 TRLV
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRLV {0:F3}{1}", level, ScpiDelimiter);
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:EDGE:LEVel {0:F3}{1}", level, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,30 +226,24 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 修正:采用标准简写 TRSE 体系配置指令
|
await SendAsync($"TRSE EDGE,SR,{source.ToUpper()}{ScpiDelimiter}", ct);
|
||||||
await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 6. Measure 自动测量参数回读 (高频轮询核心)
|
#region 6. Measure 自动测量参数回读
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询指定通道自动测量项的当前实时测量数值
|
/// 查询指定通道自动测量项的当前实时测量数值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">通道号 (1-4)</param>
|
|
||||||
/// <param name="paramName">参数名称助记符:
|
|
||||||
/// PKPK(峰峰值), MAX(最大值), MIN(最小值), AMPL(振幅值),
|
|
||||||
/// FREQ(频率), PER(周期), MEAN(平均值), RMS(均方根) 等</param>
|
|
||||||
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 优化:采用更兼容的测量读取指令语法格式
|
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter);
|
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter);
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轮询便捷接口:查询指定通道的电压峰峰值 (Vpp)
|
/// 查询指定通道的电压峰峰值 (Vpp)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -230,7 +251,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轮询便捷接口:查询指定通道的频率值 (Frequency)
|
/// 查询指定通道的频率值 (Frequency)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -238,7 +259,7 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 轮询便捷接口:查询指定通道的真均方根电压值 (Vrms)
|
/// 查询指定通道的真均方根电压值 (Vrms)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -246,5 +267,60 @@ namespace DeviceCommand.Device
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 7. 屏幕截图导出子系统
|
||||||
|
/// 【一键获取并保存截图】
|
||||||
|
/// 自动下发 :PRINt? PNG 指令,接收示波器返回的纯 PNG 二进制流并直接保存到指定路径。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="saveFilePath">本地绝对保存路径 (例如: @"D:\Oscilloscope\Screen_01.png")</param>
|
||||||
|
/// <param name="ct">取消令牌</param>
|
||||||
|
/// <returns>返回是否获取并保存成功</returns>
|
||||||
|
public virtual async Task<bool> 获取屏幕图像并保存(string saveFilePath, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 调用我们在基类 Tcp 中全新实现的 ReadRawAsync
|
||||||
|
// 它会在内部下发 ":PRINt? PNG\n",自动接收完整的网络字节流
|
||||||
|
byte[] pureImageBytes = await ReadAllBytesAsync($":PRINt? PNG{ScpiDelimiter}", ct);
|
||||||
|
|
||||||
|
// 2. 校验返回数据
|
||||||
|
if (pureImageBytes == null || pureImageBytes.Length < 8)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("错误:接收到的图片数据为空或长度不足。");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 核心校验:验证是否为标准的 PNG 文件格式 (PNG 头通常为 89 50 4E 47)
|
||||||
|
if (pureImageBytes[0] != 0x89 || pureImageBytes[1] != 'P' || pureImageBytes[2] != 'N' || pureImageBytes[3] != 'G')
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("错误:接收到的二进制数据不符合标准 PNG 格式头!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 如果传入的目标文件夹路径不存在,自动帮其创建
|
||||||
|
string directory = Path.GetDirectoryName(saveFilePath);
|
||||||
|
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 使用文件流直接将 PNG 字节数据落地写入本地硬盘
|
||||||
|
using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write))
|
||||||
|
{
|
||||||
|
await fs.WriteAsync(pureImageBytes, 0, pureImageBytes.Length, ct);
|
||||||
|
await fs.FlushAsync(ct); // 强制刷新,确保数据完整落地
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Diagnostics.Debug.WriteLine($"成功:截图已保存至路径: {saveFilePath}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"崩溃:保存截图时发生未知异常: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ using System.Globalization;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DeviceCommand.Device
|
namespace DeviceCommand.Devices
|
||||||
{
|
{
|
||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class SPAW7000 : Tcp
|
public class SPAW7000 : Tcp
|
||||||
@@ -31,7 +31,14 @@ namespace DeviceCommand.Device
|
|||||||
{
|
{
|
||||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 查询错误信息 发hsl居然可以但是手册上没有而且软件上也用不了这个命令
|
||||||
|
/// </summary>
|
||||||
|
//public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||||
|
//{
|
||||||
|
// string query = string.Format(":SYSTem:ERRor?", ScpiDelimiter);
|
||||||
|
// return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
|
//}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取功率分析仪识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
/// 读取功率分析仪识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -58,108 +65,142 @@ namespace DeviceCommand.Device
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 2. MEASure / NUMeric 核心数据测量与轮询 (高频轮询核心)
|
#region 2. NUMeric 核心数据测量与轮询 (动态单项读取)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询指定通道的实时 RMS 电压值 (单位: V)
|
/// 查询指定通道的实时 RMS 电压值 (单位: V)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
|
||||||
public virtual async Task<string> 查询实际电压(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? U,{0}{1}", channel, ScpiDelimiter);
|
// 1. 先配置 ITEM1 为指定通道的电压有效值 (URMS)
|
||||||
|
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 URMS,{0}{1}", channel, ScpiDelimiter);
|
||||||
|
await SendAsync(setItem, ct);
|
||||||
|
|
||||||
|
// 2. 再读取 ITEM1 的值
|
||||||
|
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询指定通道的实时 RMS 电流值 (单位: A)
|
/// 查询指定通道的实时 RMS 电流值 (单位: A)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
|
||||||
public virtual async Task<string> 查询实际电流(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电流(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? I,{0}{1}", channel, ScpiDelimiter);
|
// 1. 配置 ITEM1 为指定通道的电流有效值 (IRMS)
|
||||||
|
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 IRMS,{0}{1}", channel, ScpiDelimiter);
|
||||||
|
await SendAsync(setItem, ct);
|
||||||
|
|
||||||
|
// 2. 读取 ITEM1 的值
|
||||||
|
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询指定通道的实时有功功率值 (单位: W)
|
/// 查询指定通道的实时有功功率值 (单位: W)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
|
||||||
public virtual async Task<string> 查询实际功率(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际功率(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? P,{0}{1}", channel, ScpiDelimiter);
|
// 1. 配置 ITEM1 为指定通道的有功功率 (P)
|
||||||
|
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 P,{0}{1}", channel, ScpiDelimiter);
|
||||||
|
await SendAsync(setItem, ct);
|
||||||
|
|
||||||
|
// 2. 读取 ITEM1 的值
|
||||||
|
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询指定通道的实时频率值 (单位: Hz)
|
/// 查询指定通道的实时频率值 (单位: Hz)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
|
||||||
public virtual async Task<string> 查询频率(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询频率(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? FREQuency,{0}{1}", channel, ScpiDelimiter);
|
// 1. 配置 ITEM1 为指定通道的电压频率 (FU)
|
||||||
|
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 FU,{0}{1}", channel, ScpiDelimiter);
|
||||||
|
await SendAsync(setItem, ct);
|
||||||
|
|
||||||
|
// 2. 读取 ITEM1 的值
|
||||||
|
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 查询指定通道的功率因数 (Power Factor)
|
/// 查询指定通道的功率因数 (Power Factor)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="channel">通道号 (例如: 1, 2, 3...)</param>
|
|
||||||
public virtual async Task<string> 查询功率因数(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询功率因数(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? PF,{0}{1}", channel, ScpiDelimiter);
|
// 1. 配置 ITEM1 为指定通道的功率因数 (LAMBda)
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 LAMBda,{0}{1}", channel, ScpiDelimiter);
|
||||||
}
|
await SendAsync(setItem, ct);
|
||||||
|
|
||||||
/// <summary>
|
// 2. 读取 ITEM1 的值
|
||||||
/// 自定义组合参数批量读取接口
|
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||||
/// </summary>
|
|
||||||
/// <param name="parameter">参数助记符 (如 "U,I,P" 或 "S,Q,LAMBda")</param>
|
|
||||||
/// <param name="channel">通道号</param>
|
|
||||||
public virtual async Task<string> 查询自定义测量组合(string parameter, int channel, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? {0},{1}{2}", parameter, channel, ScpiDelimiter);
|
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 3. INPut 通道电气参数配置
|
#region 3. INPut 通道电气参数配置
|
||||||
|
/// <summary>
|
||||||
|
/// SPAW7000 电压量程枚举 (单位: V)
|
||||||
|
/// </summary>
|
||||||
|
public enum VoltageRange
|
||||||
|
{
|
||||||
|
V_15 = 15,
|
||||||
|
V_30 = 30,
|
||||||
|
V_60 = 60,
|
||||||
|
V_100 = 100,
|
||||||
|
V_150 = 150,
|
||||||
|
V_300 = 300,
|
||||||
|
V_600 = 600,
|
||||||
|
V_1000 = 1000
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置指定通道的电压量程 (例如: 15, 30, 60, 150, 300, 600, 1000)
|
/// SPAW7000 电流量程枚举 (单位: A)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电压量程(int channel, double range, CancellationToken ct = default)
|
public enum CurrentRange
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:VOLTage:RANGe {0},{1:F1}{2}", channel, range, ScpiDelimiter);
|
A_1 = 1,
|
||||||
|
A_2 = 2,
|
||||||
|
A_5 = 5,
|
||||||
|
A_10 = 10,
|
||||||
|
A_20 = 20,
|
||||||
|
A_50 = 50
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 设置指定通道的电压量程
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">通道号/单元号 (1 - 7)</param>
|
||||||
|
/// <param name="range">电压量程枚举</param>
|
||||||
|
/// <param name="ct">取消令牌</param>
|
||||||
|
public virtual async Task 设置电压量程(int channel, VoltageRange range, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
// 正确格式例如: :INPut:VOLTage:RANGe:ELEMent1 300\n
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture,
|
||||||
|
":INPut:VOLTage:RANGe:ELEMent{0} {1}{2}",
|
||||||
|
channel, (int)range, ScpiDelimiter);
|
||||||
|
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置指定通道的电流量程 (取决于接线单元或传感器输入类型)
|
/// 设置指定通道的电流量程
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置电流量程(int channel, double range, CancellationToken ct = default)
|
/// <param name="channel">通道号/单元号 (1 - 7)</param>
|
||||||
|
/// <param name="range">电流量程枚举</param>
|
||||||
|
/// <param name="ct">取消令牌</param>
|
||||||
|
public virtual async Task 设置电流量程(int channel, CurrentRange range, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:CURRent:RANGe {0},{1:F3}{2}", channel, range, ScpiDelimiter);
|
// 正确格式例如: :INPut:CURRent:RANGe:ELEMent1 5\n
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture,
|
||||||
|
":INPut:CURRent:RANGe:ELEMent{0} {1}{2}",
|
||||||
|
channel, (int)range, ScpiDelimiter);
|
||||||
|
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置通道的耦合模式 (AC, DC, ACDC)
|
|
||||||
/// </summary>
|
|
||||||
public virtual async Task 设置通道耦合模式(int channel, string mode, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
string modeUpper = mode.ToUpper();
|
|
||||||
if (modeUpper != "AC" && modeUpper != "DC" && modeUpper != "ACDC")
|
|
||||||
throw new ArgumentException("耦合模式只能为 AC, DC, 或 ACDC");
|
|
||||||
|
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:COUPling {0},{1}{2}", channel, modeUpper, ScpiDelimiter);
|
|
||||||
await SendAsync(cmd, ct);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 4. SYSTem 系统设置与状态查询
|
#region 4. SYSTem 系统设置与状态查询
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 14. 查询仪器型号名称
|
/// 14. 查询仪器型号名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
193
DeviceCommand/Flexible/FModbusRTU.cs
Normal file
193
DeviceCommand/Flexible/FModbusRTU.cs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using NModbus;
|
||||||
|
using NModbus.Serial;
|
||||||
|
using System;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Flexible
|
||||||
|
{
|
||||||
|
[ADPCommand]
|
||||||
|
public static class FModbusRTU
|
||||||
|
{
|
||||||
|
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
|
private static SerialPort CreatePort(
|
||||||
|
string portName,
|
||||||
|
int baudRate,
|
||||||
|
int dataBits,
|
||||||
|
StopBits stopBits,
|
||||||
|
Parity parity,
|
||||||
|
int readTimeout,
|
||||||
|
int writeTimeout)
|
||||||
|
{
|
||||||
|
return new SerialPort(portName, baudRate, parity, dataBits, stopBits)
|
||||||
|
{
|
||||||
|
ReadTimeout = readTimeout > 0 ? readTimeout : SerialPort.InfiniteTimeout,
|
||||||
|
WriteTimeout = writeTimeout > 0 ? writeTimeout : SerialPort.InfiniteTimeout
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IModbusMaster CreateMaster(SerialPort port)
|
||||||
|
{
|
||||||
|
return new ModbusFactory().CreateRtuMaster(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Holding Register
|
||||||
|
|
||||||
|
public static async Task<ushort[]> ReadHoldingRegistersAsync(
|
||||||
|
string portName,
|
||||||
|
int baudRate,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort startAddress,
|
||||||
|
ushort numberOfPoints,
|
||||||
|
int dataBits = 8,
|
||||||
|
StopBits stopBits = StopBits.One,
|
||||||
|
Parity parity = Parity.None,
|
||||||
|
int readTimeout = 3000,
|
||||||
|
int writeTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var port = CreatePort(
|
||||||
|
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||||
|
|
||||||
|
port.Open();
|
||||||
|
|
||||||
|
var master = CreateMaster(port);
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (readTimeout > 0)
|
||||||
|
cts.CancelAfter(readTimeout);
|
||||||
|
|
||||||
|
return await master
|
||||||
|
.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task WriteSingleRegisterAsync(
|
||||||
|
string portName,
|
||||||
|
int baudRate,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort registerAddress,
|
||||||
|
ushort value,
|
||||||
|
int dataBits = 8,
|
||||||
|
StopBits stopBits = StopBits.One,
|
||||||
|
Parity parity = Parity.None,
|
||||||
|
int readTimeout = 3000,
|
||||||
|
int writeTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var port = CreatePort(
|
||||||
|
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||||
|
|
||||||
|
port.Open();
|
||||||
|
|
||||||
|
var master = CreateMaster(port);
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (writeTimeout > 0)
|
||||||
|
cts.CancelAfter(writeTimeout);
|
||||||
|
|
||||||
|
await master
|
||||||
|
.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(writeTimeout),cts.Token);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Coil
|
||||||
|
|
||||||
|
public static async Task<bool[]> ReadCoilsAsync(
|
||||||
|
string portName,
|
||||||
|
int baudRate,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort startAddress,
|
||||||
|
ushort numberOfPoints,
|
||||||
|
int dataBits = 8,
|
||||||
|
StopBits stopBits = StopBits.One,
|
||||||
|
Parity parity = Parity.None,
|
||||||
|
int readTimeout = 3000,
|
||||||
|
int writeTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var port = CreatePort(
|
||||||
|
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||||
|
|
||||||
|
port.Open();
|
||||||
|
|
||||||
|
var master = CreateMaster(port);
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (readTimeout > 0)
|
||||||
|
cts.CancelAfter(readTimeout);
|
||||||
|
|
||||||
|
return await master
|
||||||
|
.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task WriteSingleCoilAsync(
|
||||||
|
string portName,
|
||||||
|
int baudRate,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort coilAddress,
|
||||||
|
bool value,
|
||||||
|
int dataBits = 8,
|
||||||
|
StopBits stopBits = StopBits.One,
|
||||||
|
Parity parity = Parity.None,
|
||||||
|
int readTimeout = 3000,
|
||||||
|
int writeTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var port = CreatePort(
|
||||||
|
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||||
|
|
||||||
|
port.Open();
|
||||||
|
|
||||||
|
var master = CreateMaster(port);
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (writeTimeout > 0)
|
||||||
|
cts.CancelAfter(writeTimeout);
|
||||||
|
|
||||||
|
await master
|
||||||
|
.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(writeTimeout), cts.Token);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
133
DeviceCommand/Flexible/FModbusTCP.cs
Normal file
133
DeviceCommand/Flexible/FModbusTCP.cs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using NModbus;
|
||||||
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Flexible
|
||||||
|
{
|
||||||
|
[ADPCommand]
|
||||||
|
public static class FModbusTCP
|
||||||
|
{
|
||||||
|
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
|
private static async Task<IModbusMaster> ConnectAsync(string ipAddress, int port, int sendTimeout, int receiveTimeout, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var tcpClient = new TcpClient();
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (sendTimeout > 0)
|
||||||
|
cts.CancelAfter(sendTimeout);
|
||||||
|
|
||||||
|
await tcpClient.ConnectAsync(ipAddress, port, cts.Token);
|
||||||
|
return new ModbusFactory().CreateMaster(tcpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Holding Registers
|
||||||
|
|
||||||
|
public static async Task<ushort[]> ReadHoldingRegistersAsync(
|
||||||
|
string ipAddress,
|
||||||
|
int port,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort startAddress,
|
||||||
|
ushort numberOfPoints,
|
||||||
|
int sendTimeout = 3000,
|
||||||
|
int receiveTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||||
|
var result = await ((IModbusMaster)master)
|
||||||
|
.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task WriteSingleRegisterAsync(
|
||||||
|
string ipAddress,
|
||||||
|
int port,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort registerAddress,
|
||||||
|
ushort value,
|
||||||
|
int sendTimeout = 3000,
|
||||||
|
int receiveTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||||
|
await ((IModbusMaster)master)
|
||||||
|
.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Coils
|
||||||
|
|
||||||
|
public static async Task<bool[]> ReadCoilsAsync(
|
||||||
|
string ipAddress,
|
||||||
|
int port,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort startAddress,
|
||||||
|
ushort numberOfPoints,
|
||||||
|
int sendTimeout = 3000,
|
||||||
|
int receiveTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||||
|
var result = await ((IModbusMaster)master)
|
||||||
|
.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task WriteSingleCoilAsync(
|
||||||
|
string ipAddress,
|
||||||
|
int port,
|
||||||
|
byte slaveAddress,
|
||||||
|
ushort coilAddress,
|
||||||
|
bool value,
|
||||||
|
int sendTimeout = 3000,
|
||||||
|
int receiveTimeout = 3000,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||||
|
await ((IModbusMaster)master)
|
||||||
|
.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
104
DeviceCommand/Flexible/FSerialPort.cs
Normal file
104
DeviceCommand/Flexible/FSerialPort.cs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using System;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Flexible
|
||||||
|
{
|
||||||
|
[ADPCommand]
|
||||||
|
public static class FSerialPort
|
||||||
|
{
|
||||||
|
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
|
private static SerialPort CreatePort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int sendTimeout,int receiveTimeout)
|
||||||
|
{
|
||||||
|
return new SerialPort(portName, baudRate, parity, dataBits, stopBits)
|
||||||
|
{
|
||||||
|
Encoding = Encoding.UTF8,
|
||||||
|
WriteTimeout = sendTimeout > 0 ? sendTimeout : SerialPort.InfiniteTimeout,
|
||||||
|
ReadTimeout = receiveTimeout > 0 ? receiveTimeout : SerialPort.InfiniteTimeout
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 最常用:只发送字符串
|
||||||
|
|
||||||
|
public static async Task SendAsync(string portName,int baudRate,Parity parity,int dataBits,StopBits stopBits,int sendTimeout,int receiveTimeout,string command,CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var port = CreatePort(
|
||||||
|
portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout);
|
||||||
|
|
||||||
|
port.Open();
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (sendTimeout > 0)
|
||||||
|
cts.CancelAfter(sendTimeout);
|
||||||
|
|
||||||
|
var bytes = port.Encoding.GetBytes(command);
|
||||||
|
await port.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
|
||||||
|
await port.BaseStream.FlushAsync(cts.Token);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 最常用:发送字符串并读取字符串
|
||||||
|
|
||||||
|
public static async Task<string> SendReadAsync(string portName,int baudRate, Parity parity, int dataBits, StopBits stopBits,int sendTimeout, int receiveTimeout,string command,string delimiter = "\n", CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var port = CreatePort(
|
||||||
|
portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout);
|
||||||
|
|
||||||
|
port.Open();
|
||||||
|
|
||||||
|
// Send
|
||||||
|
var sendBytes = port.Encoding.GetBytes(command);
|
||||||
|
await port.BaseStream.WriteAsync(sendBytes, 0, sendBytes.Length, ct);
|
||||||
|
await port.BaseStream.FlushAsync(ct);
|
||||||
|
|
||||||
|
// Read
|
||||||
|
delimiter ??= "\n";
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
byte[] buffer = new byte[256];
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (receiveTimeout > 0)
|
||||||
|
cts.CancelAfter(receiveTimeout);
|
||||||
|
|
||||||
|
while (!cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
int read = await port.BaseStream.ReadAsync(
|
||||||
|
buffer, 0, buffer.Length, cts.Token);
|
||||||
|
|
||||||
|
if (read == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
sb.Append(port.Encoding.GetString(buffer, 0, read));
|
||||||
|
|
||||||
|
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||||
|
if (index >= 0)
|
||||||
|
return sb.ToString(0, index).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException("串口读取超时");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
115
DeviceCommand/Flexible/FTCP.cs
Normal file
115
DeviceCommand/Flexible/FTCP.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DeviceCommand.Flexible
|
||||||
|
{
|
||||||
|
[ADPCommand]
|
||||||
|
public static class FTCP
|
||||||
|
{
|
||||||
|
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||||
|
|
||||||
|
#region Send
|
||||||
|
|
||||||
|
public static async Task SendAsync(string ipAddress,int port,int sendTimeout, byte[] buffer, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new TcpClient();
|
||||||
|
client.SendTimeout = sendTimeout;
|
||||||
|
|
||||||
|
await client.ConnectAsync(ipAddress, port, ct);
|
||||||
|
|
||||||
|
using NetworkStream stream = client.GetStream();
|
||||||
|
await stream.WriteAsync(buffer, 0, buffer.Length, ct).WaitAsync(TimeSpan.FromMilliseconds(sendTimeout));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task SendAsync(string ipAddress, int port,int sendTimeout, string text,CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return SendAsync( ipAddress, port, sendTimeout, Encoding.UTF8.GetBytes(text),ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Read
|
||||||
|
|
||||||
|
public static async Task<byte[]> ReadAsync(string ipAddress,int port,int receiveTimeout,int length,CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new TcpClient();
|
||||||
|
client.ReceiveTimeout = receiveTimeout;
|
||||||
|
|
||||||
|
await client.ConnectAsync(ipAddress, port, ct);
|
||||||
|
|
||||||
|
using NetworkStream stream = client.GetStream();
|
||||||
|
byte[] buffer = new byte[length];
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (receiveTimeout > 0)
|
||||||
|
cts.CancelAfter(receiveTimeout);
|
||||||
|
|
||||||
|
while (offset < length)
|
||||||
|
{
|
||||||
|
int read = await stream.ReadAsync(buffer, offset, length - offset, cts.Token);
|
||||||
|
if (read == 0) break;
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[..offset];
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> ReadLineAsync( string ipAddress, int port, int receiveTimeout, string delimiter = "\n",CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
await _commLock.WaitAsync(ct);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new TcpClient();
|
||||||
|
client.ReceiveTimeout = receiveTimeout;
|
||||||
|
|
||||||
|
await client.ConnectAsync(ipAddress, port, ct);
|
||||||
|
|
||||||
|
using NetworkStream stream = client.GetStream();
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
|
||||||
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||||
|
if (receiveTimeout > 0)
|
||||||
|
cts.CancelAfter(receiveTimeout);
|
||||||
|
|
||||||
|
while (!cts.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||||
|
if (bytesRead == 0) break;
|
||||||
|
|
||||||
|
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||||
|
|
||||||
|
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||||
|
if (index >= 0)
|
||||||
|
return sb.ToString(0, index).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TimeoutException("读取超时或对端关闭");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_commLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,12 @@ namespace DeviceEditModule
|
|||||||
containerRegistry.RegisterForNavigation<N69200View>("N69200View");
|
containerRegistry.RegisterForNavigation<N69200View>("N69200View");
|
||||||
containerRegistry.RegisterForNavigation<SDS2000X_HDView>("SDS2000X_HDView");
|
containerRegistry.RegisterForNavigation<SDS2000X_HDView>("SDS2000X_HDView");
|
||||||
containerRegistry.RegisterForNavigation<SPAW7000View>("SPAW7000View");
|
containerRegistry.RegisterForNavigation<SPAW7000View>("SPAW7000View");
|
||||||
|
containerRegistry.Register<IT7800EViewModel>();
|
||||||
|
containerRegistry.Register<N36200ViewModel>();
|
||||||
|
containerRegistry.Register<N36600ViewModel>();
|
||||||
|
containerRegistry.Register<N69200ViewModel>();
|
||||||
|
containerRegistry.Register<SDS2000X_HDViewModel>();
|
||||||
|
containerRegistry.Register<SPAW7000ViewModel>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
@@ -92,9 +92,9 @@ namespace DeviceEditModule.ViewModels
|
|||||||
set => SetProperty(ref _currentLimit, value);
|
set => SetProperty(ref _currentLimit, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _selectedMode = "AC";
|
private PowerCouplingMode _selectedMode = PowerCouplingMode.AC;
|
||||||
/// <summary>待设置的电源工作模式:AC / DC / ACDC。</summary>
|
/// <summary>待设置的电源工作模式:AC / DC / ACDC。</summary>
|
||||||
public string SelectedMode
|
public PowerCouplingMode SelectedMode
|
||||||
{
|
{
|
||||||
get => _selectedMode;
|
get => _selectedMode;
|
||||||
set => SetProperty(ref _selectedMode, value);
|
set => SetProperty(ref _selectedMode, value);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
@@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
|
using static DeviceCommand.Devices.N69200;
|
||||||
|
|
||||||
namespace DeviceEditModule.ViewModels
|
namespace DeviceEditModule.ViewModels
|
||||||
{
|
{
|
||||||
@@ -49,9 +50,9 @@ namespace DeviceEditModule.ViewModels
|
|||||||
|
|
||||||
#region 输入参数属性
|
#region 输入参数属性
|
||||||
|
|
||||||
private string _selectedMode = "CC";
|
private DeviceWorkMode _selectedMode = DeviceWorkMode.CC;
|
||||||
/// <summary>工作模式:CC / CV / CP / CR。</summary>
|
/// <summary>工作模式:CC / CV / CP / CR。</summary>
|
||||||
public string SelectedMode
|
public DeviceWorkMode SelectedMode
|
||||||
{
|
{
|
||||||
get => _selectedMode;
|
get => _selectedMode;
|
||||||
set => SetProperty(ref _selectedMode, value);
|
set => SetProperty(ref _selectedMode, value);
|
||||||
@@ -155,8 +156,7 @@ namespace DeviceEditModule.ViewModels
|
|||||||
public ICommand SetOcpCommand { get; }
|
public ICommand SetOcpCommand { get; }
|
||||||
public ICommand SetOppCommand { get; }
|
public ICommand SetOppCommand { get; }
|
||||||
public ICommand ClearAlarmCommand { get; }
|
public ICommand ClearAlarmCommand { get; }
|
||||||
public ICommand SetRemoteModeCommand { get; }
|
|
||||||
public ICommand SetLocalModeCommand { get; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -174,32 +174,30 @@ namespace DeviceEditModule.ViewModels
|
|||||||
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护值_OCP(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护值_OCP(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
||||||
SetOppCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过功率保护值_OPP(OppValue, Ct()); AppendLog($"OPP已设为 {OppValue} W"); }));
|
SetOppCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过功率保护值_OPP(OppValue, Ct()); AppendLog($"OPP已设为 {OppValue} W"); }));
|
||||||
ClearAlarmCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除保护告警(Ct()); AppendLog("保护告警已清除"); }));
|
ClearAlarmCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除保护告警(Ct()); AppendLog("保护告警已清除"); }));
|
||||||
SetRemoteModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换远程控制模式(Ct()); AppendLog("已切换到远程控制模式"); }));
|
|
||||||
SetLocalModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换本地控制模式(Ct()); AppendLog("已切换到本地控制模式"); }));
|
|
||||||
|
|
||||||
SetLoadValueCommand = new DelegateCommand(async () => await Exec(async () =>
|
SetLoadValueCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
{
|
{
|
||||||
var mode = SelectedMode.ToUpperInvariant();
|
switch (SelectedMode)
|
||||||
switch (mode)
|
|
||||||
{
|
{
|
||||||
case "CC":
|
case DeviceWorkMode.CC:
|
||||||
await _device!.设置恒电流CC(LoadValue, Ct());
|
await _device!.设置恒电流CC(LoadValue, Ct());
|
||||||
AppendLog($"恒流 CC 已设为 {LoadValue} A");
|
AppendLog($"恒流 CC 已设为 {LoadValue} A");
|
||||||
break;
|
break;
|
||||||
case "CV":
|
case DeviceWorkMode.CV:
|
||||||
await _device!.设置恒电压CV(LoadValue, Ct());
|
await _device!.设置恒电压CV(LoadValue, Ct());
|
||||||
AppendLog($"恒压 CV 已设为 {LoadValue} V");
|
AppendLog($"恒压 CV 已设为 {LoadValue} V");
|
||||||
break;
|
break;
|
||||||
case "CP":
|
case DeviceWorkMode.CP:
|
||||||
await _device!.设置恒功率CP(LoadValue, Ct());
|
await _device!.设置恒功率CP(LoadValue, Ct());
|
||||||
AppendLog($"恒功率 CP 已设为 {LoadValue} W");
|
AppendLog($"恒功率 CP 已设为 {LoadValue} W");
|
||||||
break;
|
break;
|
||||||
case "CR":
|
case DeviceWorkMode.CR:
|
||||||
await _device!.设置恒电阻CR(LoadValue, Ct());
|
await _device!.设置恒电阻CR(LoadValue, Ct());
|
||||||
AppendLog($"恒电阻 CR 已设为 {LoadValue} Ω");
|
AppendLog($"恒电阻 CR 已设为 {LoadValue} Ω");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
AppendLog($"未知模式 {SelectedMode},无法设置设定值");
|
AppendLog($"模式 {SelectedMode} 不支持设置设定值");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
|
using static DeviceCommand.Devices.SDS2000X_HD;
|
||||||
|
|
||||||
namespace DeviceEditModule.ViewModels
|
namespace DeviceEditModule.ViewModels
|
||||||
{
|
{
|
||||||
@@ -142,24 +146,28 @@ namespace DeviceEditModule.ViewModels
|
|||||||
|
|
||||||
#region 命令
|
#region 命令
|
||||||
|
|
||||||
public ICommand QueryIdentityCommand { get; }
|
public ICommand QueryIdentityCommand { get; }
|
||||||
public ICommand ResetDeviceCommand { get; }
|
public ICommand ResetDeviceCommand { get; }
|
||||||
public ICommand RunCommand { get; }
|
public ICommand RunCommand { get; }
|
||||||
public ICommand StopCommand { get; }
|
public ICommand StopCommand { get; }
|
||||||
public ICommand SingleCommand { get; }
|
public ICommand SingleCommand { get; }
|
||||||
public ICommand ForceTriggerCommand { get; }
|
public ICommand ForceTriggerCommand { get; }
|
||||||
public ICommand SetChannelOnCommand { get; }
|
public ICommand SetChannelOnCommand { get; }
|
||||||
public ICommand SetChannelOffCommand { get; }
|
public ICommand SetChannelOffCommand { get; }
|
||||||
public ICommand SetVoltsDivCommand { get; }
|
public ICommand SetVoltsDivCommand { get; }
|
||||||
public ICommand SetOffsetCommand { get; }
|
public ICommand SetOffsetCommand { get; }
|
||||||
|
|
||||||
public ICommand SetTimeBaseCommand { get; }
|
// 🛠️ 补全:设置阻抗的 Command
|
||||||
public ICommand SetTriggerLevelCommand { get; }
|
public ICommand SetImpedance50Command { get; }
|
||||||
|
public ICommand SetImpedance1MCommand { get; }
|
||||||
|
|
||||||
|
public ICommand SetTimeBaseCommand { get; }
|
||||||
|
public ICommand SetTriggerLevelCommand { get; }
|
||||||
public ICommand SetTriggerSourceCommand { get; }
|
public ICommand SetTriggerSourceCommand { get; }
|
||||||
public ICommand QueryMeasurementsCommand{ get; }
|
public ICommand QueryMeasurementsCommand { get; }
|
||||||
public ICommand QueryVppCommand { get; }
|
public ICommand QueryVppCommand { get; }
|
||||||
public ICommand QueryFrequencyCommand { get; }
|
public ICommand QueryFrequencyCommand { get; }
|
||||||
public ICommand QueryRmsCommand { get; }
|
public ICommand QueryRmsCommand { get; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -167,32 +175,73 @@ namespace DeviceEditModule.ViewModels
|
|||||||
{
|
{
|
||||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
|
||||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||||
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始捕获"); }));
|
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始捕获"); }));
|
||||||
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获"); }));
|
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获"); }));
|
||||||
SingleCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.单次触发_SINGLE(Ct()); AppendLog("已触发单次捕获"); }));
|
SingleCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.单次触发_SINGLE(Ct()); AppendLog("已触发单次捕获"); }));
|
||||||
ForceTriggerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.强制触发(Ct()); AppendLog("已强制触发"); }));
|
ForceTriggerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.强制触发(Ct()); AppendLog("已强制触发"); }));
|
||||||
SetChannelOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, true, Ct()); AppendLog($"通道 {Channel} 已开启"); }));
|
SetChannelOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, true, Ct()); AppendLog($"通道 {Channel} 已开启"); }));
|
||||||
SetChannelOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, false, Ct()); AppendLog($"通道 {Channel} 已关闭"); }));
|
SetChannelOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, false, Ct()); AppendLog($"通道 {Channel} 已关闭"); }));
|
||||||
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道电压档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 电压档位已设为 {VoltsPerDiv} V/div"); }));
|
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道电压档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 电压档位已设为 {VoltsPerDiv} V/div"); }));
|
||||||
SetOffsetCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道垂直偏移(Channel, Offset, Ct()); AppendLog($"C{Channel} 垂直偏移已设为 {Offset} V"); }));
|
SetOffsetCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道垂直偏移(Channel, Offset, Ct()); AppendLog($"C{Channel} 垂直偏移已设为 {Offset} V"); }));
|
||||||
|
|
||||||
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
// 🛠️ 绑定:设置 50Ω 和 1MΩ 阻抗控制
|
||||||
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发电平(TriggerLevel, Ct()); AppendLog($"触发电平已设为 {TriggerLevel} V"); }));
|
SetImpedance50Command = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
await _device!.设置通道阻抗(Channel, ImpedanceType.FIFty, Ct());
|
||||||
|
Is50Ohm = true;
|
||||||
|
AppendLog($"C{Channel} 输入阻抗已设为 50Ω");
|
||||||
|
}));
|
||||||
|
|
||||||
|
SetImpedance1MCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
await _device!.设置通道阻抗(Channel, ImpedanceType.ONEM, Ct());
|
||||||
|
Is50Ohm = false;
|
||||||
|
AppendLog($"C{Channel} 输入阻抗已设为 1MΩ");
|
||||||
|
}));
|
||||||
|
|
||||||
|
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
||||||
|
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发电平(TriggerLevel, Ct()); AppendLog($"触发电平已设为 {TriggerLevel} V"); }));
|
||||||
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发源(TriggerSource, Ct()); AppendLog($"触发源已设为 {TriggerSource}"); }));
|
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发源(TriggerSource, Ct()); AppendLog($"触发源已设为 {TriggerSource}"); }));
|
||||||
|
|
||||||
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
{
|
{
|
||||||
MeasuredVpp = await _device!.查询实际电压峰峰值(Channel, Ct());
|
// 1. 保留设备原生吐出的完整原始字符串
|
||||||
MeasuredFrequency = await _device!.查询实际频率(Channel, Ct());
|
string rawVpp = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||||||
MeasuredRms = await _device!.查询实际电压均方根(Channel, Ct());
|
string rawFreq = await _device!.查询实际频率(Channel, Ct());
|
||||||
AppendLog($"C{Channel} 测量 → Vpp:{MeasuredVpp} Freq:{MeasuredFrequency}Hz RMS:{MeasuredRms}V");
|
string rawRms = await _device!.查询实际电压均方根(Channel, Ct());
|
||||||
|
|
||||||
|
// 2. 清洗数据并将科学计数法转为常规小数后赋值给 UI 属性
|
||||||
|
MeasuredVpp = ParseMeasurement(rawVpp);
|
||||||
|
MeasuredFrequency = ParseMeasurement(rawFreq);
|
||||||
|
MeasuredRms = ParseMeasurement(rawRms);
|
||||||
|
|
||||||
|
// 3. 在日志中同时体现设备原始报文与解析呈现值,极方便联调
|
||||||
|
AppendLog($"C{Channel} 测量原始数据 → Vpp:{rawVpp.Trim()} Freq:{rawFreq.Trim()} RMS:{rawRms.Trim()}");
|
||||||
|
AppendLog($"C{Channel} 界面呈现数值 → Vpp:{MeasuredVpp}V Freq:{MeasuredFrequency}Hz RMS:{MeasuredRms}V");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
QueryVppCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredVpp = await _device!.查询实际电压峰峰值(Channel, Ct()); AppendLog($"C{Channel} Vpp: {MeasuredVpp}"); }));
|
QueryVppCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredFrequency = await _device!.查询实际频率(Channel, Ct()); AppendLog($"C{Channel} Freq: {MeasuredFrequency}"); }));
|
{
|
||||||
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredRms = await _device!.查询实际电压均方根(Channel, Ct()); AppendLog($"C{Channel} RMS: {MeasuredRms}"); }));
|
string raw = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||||||
|
MeasuredVpp = ParseMeasurement(raw);
|
||||||
|
AppendLog($"C{Channel} Vpp: {MeasuredVpp} (Raw: {raw.Trim()})");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
string raw = await _device!.查询实际频率(Channel, Ct());
|
||||||
|
MeasuredFrequency = ParseMeasurement(raw);
|
||||||
|
AppendLog($"C{Channel} Freq: {MeasuredFrequency} (Raw: {raw.Trim()})");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
string raw = await _device!.查询实际电压均方根(Channel, Ct());
|
||||||
|
MeasuredRms = ParseMeasurement(raw);
|
||||||
|
AppendLog($"C{Channel} RMS: {MeasuredRms} (Raw: {raw.Trim()})");
|
||||||
|
}));
|
||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
}
|
}
|
||||||
@@ -224,8 +273,8 @@ namespace DeviceEditModule.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_device = found;
|
_device = found;
|
||||||
DeviceName = foundName ?? "SDS2000X_HD (未找到)";
|
DeviceName = foundName ?? "SDS2000X_HD (未找到)";
|
||||||
IsConnected = _device?.IsConnected ?? false;
|
IsConnected = _device?.IsConnected ?? false;
|
||||||
|
|
||||||
AppendLog(found != null
|
AppendLog(found != null
|
||||||
@@ -281,6 +330,52 @@ namespace DeviceEditModule.ViewModels
|
|||||||
: line + "\n" + ResponseLog;
|
: line + "\n" + ResponseLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 清洗示波器原始返回的测量字符串(例如 "C1:PAVA RMS,5.57E-03V")并安全转化为无科学计数法的小数形式。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rawResponse">设备原始应答数据</param>
|
||||||
|
/// <returns>可直接绑定到 UI 呈现的字符串数字</returns>
|
||||||
|
private string ParseMeasurement(string rawResponse)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(rawResponse)) return "—";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string cleanData = rawResponse.Trim();
|
||||||
|
|
||||||
|
// 1. 斩断报头,提取逗号后面的具体内容(如 "5.57E-03V" 或 "****")
|
||||||
|
int commaIndex = cleanData.IndexOf(',');
|
||||||
|
if (commaIndex == -1) return "—";
|
||||||
|
|
||||||
|
string valStr = cleanData.Substring(commaIndex + 1);
|
||||||
|
|
||||||
|
var match = Regex.Match(valStr, @"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?");
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
valStr = match.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 校验并拦截设备未测出时的无效星号 "****"
|
||||||
|
if (valStr.Contains("*") || string.IsNullOrWhiteSpace(valStr))
|
||||||
|
{
|
||||||
|
return "0"; // 回归为零或 "—",防止触发数据异常
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 解析科学计数法,并重新以不带科学计数法的小数样式展开
|
||||||
|
if (double.TryParse(valStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double result))
|
||||||
|
{
|
||||||
|
// "0.######" 样式会自动消除末尾无用的冗余零,并显示为普通小数(如 0.00557)
|
||||||
|
return result.ToString("0.######", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 捕获可能产生的边缘转换故障,保证轮询线程绝不崩溃
|
||||||
|
}
|
||||||
|
|
||||||
|
return "—";
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -289,4 +384,4 @@ namespace DeviceEditModule.ViewModels
|
|||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using UIShare.ViewModelBase;
|
using UIShare.ViewModelBase;
|
||||||
@@ -57,29 +59,27 @@ namespace DeviceEditModule.ViewModels
|
|||||||
set => SetProperty(ref _channel, value);
|
set => SetProperty(ref _channel, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double _voltageRange = 300.0;
|
private SPAW7000.VoltageRange _voltageRange = SPAW7000.VoltageRange.V_300;
|
||||||
/// <summary>电压量程(V)。</summary>
|
/// <summary>电压量程枚举。</summary>
|
||||||
public double VoltageRange
|
public SPAW7000.VoltageRange VoltageRange
|
||||||
{
|
{
|
||||||
get => _voltageRange;
|
get => _voltageRange;
|
||||||
set => SetProperty(ref _voltageRange, value);
|
set => SetProperty(ref _voltageRange, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double _currentRange = 5.0;
|
private SPAW7000.CurrentRange _currentRange = SPAW7000.CurrentRange.A_5;
|
||||||
/// <summary>电流量程(A)。</summary>
|
/// <summary>电流量程枚举。</summary>
|
||||||
public double CurrentRange
|
public SPAW7000.CurrentRange CurrentRange
|
||||||
{
|
{
|
||||||
get => _currentRange;
|
get => _currentRange;
|
||||||
set => SetProperty(ref _currentRange, value);
|
set => SetProperty(ref _currentRange, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _couplingMode = "DC";
|
public SPAW7000.VoltageRange[] VoltageRangeOptions { get; } =
|
||||||
/// <summary>耦合模式:AC / DC / ACDC。</summary>
|
(SPAW7000.VoltageRange[])Enum.GetValues(typeof(SPAW7000.VoltageRange));
|
||||||
public string CouplingMode
|
|
||||||
{
|
public SPAW7000.CurrentRange[] CurrentRangeOptions { get; } =
|
||||||
get => _couplingMode;
|
(SPAW7000.CurrentRange[])Enum.GetValues(typeof(SPAW7000.CurrentRange));
|
||||||
set => SetProperty(ref _couplingMode, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int _resolution = 6;
|
private int _resolution = 6;
|
||||||
/// <summary>显示分辨率(5 或 6)。</summary>
|
/// <summary>显示分辨率(5 或 6)。</summary>
|
||||||
@@ -162,19 +162,18 @@ namespace DeviceEditModule.ViewModels
|
|||||||
|
|
||||||
#region 命令
|
#region 命令
|
||||||
|
|
||||||
public ICommand QueryIdentityCommand { get; }
|
public ICommand QueryIdentityCommand { get; }
|
||||||
public ICommand ResetDeviceCommand { get; }
|
public ICommand ResetDeviceCommand { get; }
|
||||||
public ICommand QueryAllMeasureCommand { get; }
|
public ICommand QueryAllMeasureCommand { get; }
|
||||||
public ICommand SetVoltageRangeCommand { get; }
|
public ICommand SetVoltageRangeCommand { get; }
|
||||||
public ICommand SetCurrentRangeCommand { get; }
|
public ICommand SetCurrentRangeCommand { get; }
|
||||||
public ICommand SetCouplingModeCommand { get; }
|
public ICommand SetResolutionCommand { get; }
|
||||||
public ICommand SetResolutionCommand { get; }
|
public ICommand SetBrightnessCommand { get; }
|
||||||
public ICommand SetBrightnessCommand { get; }
|
public ICommand SetTouchLockOnCommand { get; }
|
||||||
public ICommand SetTouchLockOnCommand { get; }
|
public ICommand SetTouchLockOffCommand { get; }
|
||||||
public ICommand SetTouchLockOffCommand { get; }
|
public ICommand QueryModelCommand { get; }
|
||||||
public ICommand QueryModelCommand { get; }
|
public ICommand QuerySerialCommand { get; }
|
||||||
public ICommand QuerySerialCommand { get; }
|
public ICommand QueryStatusByteCommand { get; }
|
||||||
public ICommand QueryStatusByteCommand { get; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -182,26 +181,34 @@ namespace DeviceEditModule.ViewModels
|
|||||||
{
|
{
|
||||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
|
||||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||||
SetVoltageRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电压量程(Channel, VoltageRange, Ct()); AppendLog($"通道 {Channel} 电压量程已设为 {VoltageRange} V"); }));
|
SetVoltageRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电压量程(Channel, VoltageRange, Ct()); AppendLog($"通道 {Channel} 电压量程已设为 {(int)VoltageRange} V"); }));
|
||||||
SetCurrentRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流量程(Channel, CurrentRange, Ct()); AppendLog($"通道 {Channel} 电流量程已设为 {CurrentRange} A"); }));
|
SetCurrentRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流量程(Channel, CurrentRange, Ct()); AppendLog($"通道 {Channel} 电流量程已设为 {(int)CurrentRange} A"); }));
|
||||||
SetCouplingModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道耦合模式(Channel, CouplingMode, Ct()); AppendLog($"通道 {Channel} 耦合模式已设为 {CouplingMode}"); }));
|
SetResolutionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示分辨率(Resolution, Ct()); AppendLog($"显示分辨率已设为 {Resolution} 位"); }));
|
||||||
SetResolutionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示分辨率(Resolution, Ct()); AppendLog($"显示分辨率已设为 {Resolution} 位"); }));
|
SetBrightnessCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示亮度(Brightness, Ct()); AppendLog($"屏幕亮度已设为 {Brightness}"); }));
|
||||||
SetBrightnessCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示亮度(Brightness, Ct()); AppendLog($"屏幕亮度已设为 {Brightness}"); }));
|
SetTouchLockOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(true, Ct()); AppendLog("屏幕触摸已锁定"); }));
|
||||||
SetTouchLockOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(true, Ct()); AppendLog("屏幕触摸已锁定"); }));
|
|
||||||
SetTouchLockOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(false, Ct()); AppendLog("屏幕触摸已解锁"); }));
|
SetTouchLockOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(false, Ct()); AppendLog("屏幕触摸已解锁"); }));
|
||||||
QueryModelCommand = new DelegateCommand(async () => await Exec(async () => { DeviceModel = await _device!.查询设备型号(Ct()); AppendLog($"型号: {DeviceModel}"); }));
|
QueryModelCommand = new DelegateCommand(async () => await Exec(async () => { DeviceModel = await _device!.查询设备型号(Ct()); AppendLog($"型号: {DeviceModel}"); }));
|
||||||
QuerySerialCommand = new DelegateCommand(async () => await Exec(async () => { DeviceSerial = await _device!.查询设备序列号(Ct()); AppendLog($"序列号: {DeviceSerial}"); }));
|
QuerySerialCommand = new DelegateCommand(async () => await Exec(async () => { DeviceSerial = await _device!.查询设备序列号(Ct()); AppendLog($"序列号: {DeviceSerial}"); }));
|
||||||
QueryStatusByteCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("STB: " + await _device!.读取状态字节(Ct()))));
|
QueryStatusByteCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("STB: " + await _device!.读取状态字节(Ct()))));
|
||||||
|
|
||||||
|
// 只修改这里:保持你原本的 5 次驱动查询调用不变,仅对读回来的科学计数法进行两位小数格式化
|
||||||
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
{
|
{
|
||||||
MeasuredVoltage = await _device!.查询实际电压(Channel, Ct());
|
string rawU = await _device!.查询实际电压(Channel, Ct());
|
||||||
MeasuredCurrent = await _device!.查询实际电流(Channel, Ct());
|
string rawI = await _device!.查询实际电流(Channel, Ct());
|
||||||
MeasuredPower = await _device!.查询实际功率(Channel, Ct());
|
string rawP = await _device!.查询实际功率(Channel, Ct());
|
||||||
MeasuredFrequency = await _device!.查询频率(Channel, Ct());
|
string rawF = await _device!.查询频率(Channel, Ct());
|
||||||
MeasuredPowerFactor = await _device!.查询功率因数(Channel, Ct());
|
string rawPF = await _device!.查询功率因数(Channel, Ct());
|
||||||
|
|
||||||
|
// 转换科学计数法格式,如果失败会自动保持原样
|
||||||
|
MeasuredVoltage = FormatToDecimal(rawU, "F2"); // 电压两位小数
|
||||||
|
MeasuredCurrent = FormatToDecimal(rawI, "F3"); // 电流通常较小,建议保留3位小数
|
||||||
|
MeasuredPower = FormatToDecimal(rawP, "F2"); // 功率两位小数
|
||||||
|
MeasuredFrequency = FormatToDecimal(rawF, "F2"); // 频率两位小数
|
||||||
|
MeasuredPowerFactor = FormatToDecimal(rawPF, "F3"); // 功率因数保留3位小数
|
||||||
|
|
||||||
AppendLog($"CH{Channel} 测量 → U:{MeasuredVoltage}V I:{MeasuredCurrent}A P:{MeasuredPower}W F:{MeasuredFrequency}Hz PF:{MeasuredPowerFactor}");
|
AppendLog($"CH{Channel} 测量 → U:{MeasuredVoltage}V I:{MeasuredCurrent}A P:{MeasuredPower}W F:{MeasuredFrequency}Hz PF:{MeasuredPowerFactor}");
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -235,8 +242,8 @@ namespace DeviceEditModule.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_device = found;
|
_device = found;
|
||||||
DeviceName = foundName ?? "SPAW7000 (未找到)";
|
DeviceName = foundName ?? "SPAW7000 (未找到)";
|
||||||
IsConnected = _device?.IsConnected ?? false;
|
IsConnected = _device?.IsConnected ?? false;
|
||||||
|
|
||||||
AppendLog(found != null
|
AppendLog(found != null
|
||||||
@@ -256,6 +263,41 @@ namespace DeviceEditModule.ViewModels
|
|||||||
|
|
||||||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将科学计数法字符串转换为标准小数格式
|
||||||
|
/// </summary>
|
||||||
|
private string FormatToDecimal(string rawInput, string decimalFormat = "F2")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(rawInput))
|
||||||
|
return "—";
|
||||||
|
|
||||||
|
// 去除可能夹杂的命令头,保留数据部分
|
||||||
|
string cleanInput = CleanResponseHeader(rawInput);
|
||||||
|
|
||||||
|
// 解析科学计数法(NumberStyles.Any 和 InvariantCulture 是关键)
|
||||||
|
if (double.TryParse(cleanInput, NumberStyles.Any, CultureInfo.InvariantCulture, out double value))
|
||||||
|
{
|
||||||
|
return value.ToString(decimalFormat, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 辅助清洗:剥离可能伴随吐回的命令头或双引号
|
||||||
|
/// </summary>
|
||||||
|
private string CleanResponseHeader(string response)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(response)) return string.Empty;
|
||||||
|
string result = response.Trim();
|
||||||
|
if (result.Contains(" "))
|
||||||
|
{
|
||||||
|
int lastSpaceIndex = result.LastIndexOf(' ');
|
||||||
|
result = result.Substring(lastSpaceIndex + 1).Trim();
|
||||||
|
}
|
||||||
|
return result.Replace("\"", "").Replace("'", "").Trim();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task Exec(Func<Task> action)
|
private async Task Exec(Func<Task> action)
|
||||||
{
|
{
|
||||||
if (_device == null)
|
if (_device == null)
|
||||||
@@ -300,4 +342,4 @@ namespace DeviceEditModule.ViewModels
|
|||||||
_cts?.Dispose();
|
_cts?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
Height="850" Width="900">
|
Height="900" Width="900">
|
||||||
|
|
||||||
<!-- 此窗口独立开启 ShowInTaskbar,以支持最小化后在任务栏恢复 -->
|
<!-- 此窗口独立开启 ShowInTaskbar,以支持最小化后在任务栏恢复 -->
|
||||||
<prism:Dialog.WindowStyle>
|
<prism:Dialog.WindowStyle>
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
<!-- 标题栏 -->
|
<!-- 标题栏 -->
|
||||||
<RowDefinition Height="38"/>
|
<RowDefinition Height="38"/>
|
||||||
<!-- Tab 标签条 -->
|
<!-- Tab 标签条 -->
|
||||||
<RowDefinition Height="40"/>
|
<RowDefinition Height="80"/>
|
||||||
<!-- 内容区 -->
|
<!-- 内容区 -->
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|||||||
@@ -7,57 +7,13 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||||
|
|
||||||
<!-- 普通操作按钮 -->
|
|
||||||
<Style x:Key="CmdBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="Padding" Value="12,0"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- 危险/警告操作按钮(橙色/红色) -->
|
|
||||||
<Style x:Key="WarnBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="Padding" Value="12,0"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
<Setter Property="Background" Value="#EF6C00"/>
|
|
||||||
<Setter Property="Foreground" Value="White"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- 数字输入框 -->
|
|
||||||
<Style x:Key="NumInput" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
|
|
||||||
<Setter Property="Width" Value="100"/>
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- 测量值只读显示 -->
|
|
||||||
<Style x:Key="MeasureBox" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
|
|
||||||
<Setter Property="Width" Value="110"/>
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="IsReadOnly" Value="True"/>
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
<Setter Property="Background" Value="#F5F5F5"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<!-- 标签 -->
|
|
||||||
<Style x:Key="ParamLabel" TargetType="TextBlock">
|
|
||||||
<Setter Property="Width" Value="80"/>
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="0,0,4,0"/>
|
|
||||||
</Style>
|
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
|
|||||||
@@ -7,52 +7,11 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||||
|
|
||||||
<Style x:Key="CmdBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="Padding" Value="12,0"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="WarnBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="Padding" Value="12,0"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
<Setter Property="Background" Value="#EF6C00"/>
|
|
||||||
<Setter Property="Foreground" Value="White"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="NumInput" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
|
|
||||||
<Setter Property="Width" Value="100"/>
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="MeasureBox" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
|
|
||||||
<Setter Property="Width" Value="110"/>
|
|
||||||
<Setter Property="Height" Value="32"/>
|
|
||||||
<Setter Property="IsReadOnly" Value="True"/>
|
|
||||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="4,0"/>
|
|
||||||
<Setter Property="Background" Value="#F5F5F5"/>
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="ParamLabel" TargetType="TextBlock">
|
|
||||||
<Setter Property="Width" Value="80"/>
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Margin" Value="0,0,4,0"/>
|
|
||||||
</Style>
|
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@@ -100,14 +100,15 @@
|
|||||||
<!-- 工作模式 -->
|
<!-- 工作模式 -->
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="工作模式" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="工作模式" Style="{StaticResource ParamLabel}"/>
|
||||||
<ComboBox Width="90" Height="32" Margin="4,0"
|
<ComboBox xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
|
Width="90" Height="32" Margin="4,0"
|
||||||
materialDesign:HintAssist.Hint=""
|
materialDesign:HintAssist.Hint=""
|
||||||
SelectedItem="{Binding SelectedMode}"
|
SelectedItem="{Binding SelectedMode}"
|
||||||
VerticalContentAlignment="Center" FontSize="12">
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
<ComboBoxItem Content="CC"/>
|
<sys:String>CC</sys:String>
|
||||||
<ComboBoxItem Content="CV"/>
|
<sys:String>CV</sys:String>
|
||||||
<ComboBoxItem Content="CP"/>
|
<sys:String>CP</sys:String>
|
||||||
<ComboBoxItem Content="CR"/>
|
<sys:String>CR</sys:String>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<Button Content="设置" Command="{Binding SetModeCommand}"
|
<Button Content="设置" Command="{Binding SetModeCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
@@ -115,13 +116,13 @@
|
|||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<!-- 远程/本地 -->
|
<!-- 远程/本地 -->
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<!--<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
|
||||||
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
|
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
|
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>-->
|
||||||
<!-- 系统操作 -->
|
<!-- 系统操作 -->
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="系统操作" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="系统操作" Style="{StaticResource ParamLabel}"/>
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
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:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@@ -92,10 +93,10 @@
|
|||||||
<ComboBox Width="80" Height="32" Margin="4,0"
|
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||||
SelectedItem="{Binding Channel}"
|
SelectedItem="{Binding Channel}"
|
||||||
VerticalContentAlignment="Center" FontSize="12">
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
<ComboBoxItem Content="1"/>
|
<sys:Int32>1</sys:Int32>
|
||||||
<ComboBoxItem Content="2"/>
|
<sys:Int32>2</sys:Int32>
|
||||||
<ComboBoxItem Content="3"/>
|
<sys:Int32>3</sys:Int32>
|
||||||
<ComboBoxItem Content="4"/>
|
<sys:Int32>4</sys:Int32>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<Button Content="开启" Command="{Binding SetChannelOnCommand}"
|
<Button Content="开启" Command="{Binding SetChannelOnCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
@@ -137,7 +138,7 @@
|
|||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="V/div (V)" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="V/div (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource NumInput}"
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
Text="{Binding VoltsPerDiv, UpdateSourceTrigger=PropertyChanged}"/>
|
Text="{Binding VoltsPerDiv}"/>
|
||||||
<Button Content="设置" Command="{Binding SetVoltsDivCommand}"
|
<Button Content="设置" Command="{Binding SetVoltsDivCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -165,7 +166,7 @@
|
|||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="时基 (s/div)" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="时基 (s/div)" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource NumInput}"
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
Text="{Binding TimeBase, UpdateSourceTrigger=PropertyChanged}"/>
|
Text="{Binding TimeBase}"/>
|
||||||
<Button Content="设置" Command="{Binding SetTimeBaseCommand}"
|
<Button Content="设置" Command="{Binding SetTimeBaseCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||||
d:DesignHeight="760" d:DesignWidth="860">
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@@ -92,38 +93,44 @@
|
|||||||
<ComboBox Width="80" Height="32" Margin="4,0"
|
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||||
SelectedItem="{Binding Channel}"
|
SelectedItem="{Binding Channel}"
|
||||||
VerticalContentAlignment="Center" FontSize="12">
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
<ComboBoxItem Content="1"/>
|
<sys:Int32>1</sys:Int32>
|
||||||
<ComboBoxItem Content="2"/>
|
<sys:Int32>2</sys:Int32>
|
||||||
<ComboBoxItem Content="3"/>
|
<sys:Int32>3</sys:Int32>
|
||||||
<ComboBoxItem Content="4"/>
|
<sys:Int32>4</sys:Int32>
|
||||||
|
<sys:Int32>5</sys:Int32>
|
||||||
|
<sys:Int32>6</sys:Int32>
|
||||||
|
<sys:Int32>7</sys:Int32>
|
||||||
|
<sys:Int32>8</sys:Int32>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="电压量程 (V)" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="电压量程 (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource NumInput}"
|
<ComboBox Width="100" Height="32" Margin="4,0"
|
||||||
Text="{Binding VoltageRange, UpdateSourceTrigger=PropertyChanged}"/>
|
ItemsSource="{Binding VoltageRangeOptions}"
|
||||||
|
SelectedItem="{Binding VoltageRange}"
|
||||||
|
VerticalContentAlignment="Center" FontSize="12"/>
|
||||||
<Button Content="设置" Command="{Binding SetVoltageRangeCommand}"
|
<Button Content="设置" Command="{Binding SetVoltageRangeCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="电流量程 (A)" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="电流量程 (A)" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource NumInput}"
|
<ComboBox Width="100" Height="32" Margin="4,0"
|
||||||
Text="{Binding CurrentRange, UpdateSourceTrigger=PropertyChanged}"/>
|
ItemsSource="{Binding CurrentRangeOptions}"
|
||||||
|
SelectedItem="{Binding CurrentRange}"
|
||||||
|
VerticalContentAlignment="Center" FontSize="12"/>
|
||||||
<Button Content="设置" Command="{Binding SetCurrentRangeCommand}"
|
<Button Content="设置" Command="{Binding SetCurrentRangeCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<!--<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="耦合模式" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="耦合模式" Style="{StaticResource ParamLabel}"/>
|
||||||
<ComboBox Width="90" Height="32" Margin="4,0"
|
<ComboBox Width="90" Height="32" Margin="4,0"
|
||||||
SelectedItem="{Binding CouplingMode}"
|
SelectedItem="{Binding CouplingMode}"
|
||||||
|
ItemsSource="{Binding InputMeasureModeOptions}"
|
||||||
VerticalContentAlignment="Center" FontSize="12">
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
<ComboBoxItem Content="AC"/>
|
|
||||||
<ComboBoxItem Content="DC"/>
|
|
||||||
<ComboBoxItem Content="ACDC"/>
|
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<Button Content="设置" Command="{Binding SetCouplingModeCommand}"
|
<Button Content="设置" Command="{Binding SetCouplingModeCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
</StackPanel>
|
</StackPanel>-->
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
@@ -136,8 +143,8 @@
|
|||||||
<ComboBox Width="80" Height="32" Margin="4,0"
|
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||||
SelectedItem="{Binding Resolution}"
|
SelectedItem="{Binding Resolution}"
|
||||||
VerticalContentAlignment="Center" FontSize="12">
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
<ComboBoxItem Content="5"/>
|
<sys:Int32>5</sys:Int32>
|
||||||
<ComboBoxItem Content="6"/>
|
<sys:Int32>6</sys:Int32>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<Button Content="设置" Command="{Binding SetResolutionCommand}"
|
<Button Content="设置" Command="{Binding SetResolutionCommand}"
|
||||||
Style="{StaticResource CmdBtn}"/>
|
Style="{StaticResource CmdBtn}"/>
|
||||||
@@ -179,12 +186,12 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="型号" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="型号" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource MeasureBox}" Width="120"
|
<TextBox Style="{StaticResource MeasureBox}" Width="281"
|
||||||
Text="{Binding DeviceModel, Mode=OneWay}"/>
|
Text="{Binding DeviceModel, Mode=OneWay}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
<TextBlock Text="序列号" Style="{StaticResource ParamLabel}"/>
|
<TextBlock Text="序列号" Style="{StaticResource ParamLabel}"/>
|
||||||
<TextBox Style="{StaticResource MeasureBox}" Width="160"
|
<TextBox Style="{StaticResource MeasureBox}" Width="281"
|
||||||
Text="{Binding DeviceSerial, Mode=OneWay}"/>
|
Text="{Binding DeviceSerial, Mode=OneWay}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
20
ExportModule/ExportModule.cs
Normal file
20
ExportModule/ExportModule.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
using ExportModule.Views;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ExportModule
|
||||||
|
{
|
||||||
|
public class ExportModule: IModule
|
||||||
|
{
|
||||||
|
public void OnInitialized(IContainerProvider containerProvider)
|
||||||
|
{
|
||||||
|
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||||
|
{
|
||||||
|
containerRegistry.RegisterForNavigation<ExportView>("ExportView");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
ExportModule/ExportModule.csproj
Normal file
20
ExportModule/ExportModule.csproj
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ClosedXML" Version="0.104.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\ORM\ORM.csproj" />
|
||||||
|
<ProjectReference Include="..\Service\Service.csproj" />
|
||||||
|
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
262
ExportModule/ViewModels/ExportViewModel.cs
Normal file
262
ExportModule/ViewModels/ExportViewModel.cs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
using ClosedXML.Excel;
|
||||||
|
using Logger;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using Model.Models;
|
||||||
|
using Service.Interface;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace ExportModule.ViewModels
|
||||||
|
{
|
||||||
|
public class ExportViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||||
|
{
|
||||||
|
#region 属性
|
||||||
|
public bool KeepAlive => true;
|
||||||
|
|
||||||
|
private ObservableCollection<TestReportModel> _testReportModelList = new();
|
||||||
|
|
||||||
|
public ObservableCollection<TestReportModel> TestReportModelList
|
||||||
|
{
|
||||||
|
get { return _testReportModelList; }
|
||||||
|
set { SetProperty(ref _testReportModelList, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? _startTime;
|
||||||
|
public DateTime? StartTime
|
||||||
|
{
|
||||||
|
get => _startTime;
|
||||||
|
set => SetProperty(ref _startTime, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? _endTime;
|
||||||
|
public DateTime? EndTime
|
||||||
|
{
|
||||||
|
get => _endTime;
|
||||||
|
set => SetProperty(ref _endTime, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _selectedScope = "全部";
|
||||||
|
public string SelectedScope
|
||||||
|
{
|
||||||
|
get => _selectedScope;
|
||||||
|
set => SetProperty(ref _selectedScope, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObservableCollection<string> ScopeOptions { get; } = new()
|
||||||
|
{
|
||||||
|
"全部",
|
||||||
|
"TestCell1", "TestCell2", "TestCell3", "TestCell4",
|
||||||
|
"TestCell5", "TestCell6", "TestCell7", "TestCell8"
|
||||||
|
};
|
||||||
|
|
||||||
|
private string _statusMessage = "就绪";
|
||||||
|
public string StatusMessage
|
||||||
|
{
|
||||||
|
get => _statusMessage;
|
||||||
|
set => SetProperty(ref _statusMessage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _totalCount;
|
||||||
|
public int TotalCount
|
||||||
|
{
|
||||||
|
get => _totalCount;
|
||||||
|
set => SetProperty(ref _totalCount, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestReportModel? _selectedTestReport;
|
||||||
|
public TestReportModel? SelectedTestReport
|
||||||
|
{
|
||||||
|
get => _selectedTestReport;
|
||||||
|
set => SetProperty(ref _selectedTestReport, value);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
public ICommand LoadedCommand { get; }
|
||||||
|
public ICommand QueryCommand { get; }
|
||||||
|
public ICommand ExportCommand { get; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 私有字段
|
||||||
|
private readonly ITestReportService _testReportService;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public ExportViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_testReportService = containerProvider.Resolve<ITestReportService>();
|
||||||
|
|
||||||
|
LoadedCommand = new AsyncDelegateCommand(OnLoad);
|
||||||
|
QueryCommand = new AsyncDelegateCommand(OnQuery);
|
||||||
|
ExportCommand = new AsyncDelegateCommand(OnExport);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 命令处理
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载全部(无筛选条件)
|
||||||
|
/// </summary>
|
||||||
|
private async Task OnLoad()
|
||||||
|
{
|
||||||
|
var result = await _testReportService.GetFilterList();
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
TestReportModelList = new ObservableCollection<TestReportModel>(result.Data ?? new List<TestReportModel>());
|
||||||
|
TotalCount = TestReportModelList.Count;
|
||||||
|
ShowInfoMessageBox($"加载完成,共 {TotalCount} 条记录", () => { });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TestReportModelList = new ObservableCollection<TestReportModel>();
|
||||||
|
TotalCount = 0;
|
||||||
|
ShowErrorMessageBox($"加载失败:{result.Msg}", () => { });
|
||||||
|
LoggerHelper.Error(result.Msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 查询(根据开始日期、结束日期、台架名称筛选)
|
||||||
|
/// </summary>
|
||||||
|
private async Task OnQuery()
|
||||||
|
{
|
||||||
|
// 处理结束日期:若用户只选了日期没选时间,默认到当天末尾
|
||||||
|
DateTime? queryEnd = EndTime;
|
||||||
|
if (queryEnd.HasValue)
|
||||||
|
{
|
||||||
|
queryEnd = queryEnd.Value.Date.AddDays(1).AddSeconds(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
string? scope = SelectedScope == "全部" ? null : SelectedScope;
|
||||||
|
|
||||||
|
var result = await _testReportService.GetFilterList(StartTime, queryEnd, scope);
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
TestReportModelList = new ObservableCollection<TestReportModel>(result.Data ?? new List<TestReportModel>());
|
||||||
|
TotalCount = TestReportModelList.Count;
|
||||||
|
ShowInfoMessageBox($"查询完成,共 {TotalCount} 条记录", () => { });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TestReportModelList = new ObservableCollection<TestReportModel>();
|
||||||
|
TotalCount = 0;
|
||||||
|
ShowErrorMessageBox($"查询失败:{result.Msg}", () => { });
|
||||||
|
LoggerHelper.Error(result.Msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出 Excel:根据用户在表格中选中的 TestReportModel.TestRoundId 查询并导出完整步骤数据
|
||||||
|
/// </summary>
|
||||||
|
private async Task OnExport()
|
||||||
|
{
|
||||||
|
if (SelectedTestReport == null)
|
||||||
|
{
|
||||||
|
ShowErrorMessageBox("请先在列表中选择一条测试记录。", () => { });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusMessage = "正在查询导出数据...";
|
||||||
|
var entityResult = await _testReportService.GetByTestRoundIdAsync(SelectedTestReport.TestRoundId);
|
||||||
|
if (!entityResult.IsSuccess || entityResult.Data == null || entityResult.Data.Count == 0)
|
||||||
|
{
|
||||||
|
ShowErrorMessageBox("未找到该测试记录的详细步骤数据。", () => { });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialog = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Filter = "Excel 工作簿 (*.xlsx)|*.xlsx|所有文件 (*.*)|*.*",
|
||||||
|
DefaultExt = ".xlsx",
|
||||||
|
FileName = $"测试报告_{SelectedTestReport.Scope}_{SelectedTestReport.StartTime:yyyyMMdd_HHmmss}.xlsx"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true) return;
|
||||||
|
|
||||||
|
var entities = entityResult.Data;
|
||||||
|
StatusMessage = $"正在导出 {entities.Count} 条步骤记录...";
|
||||||
|
await Task.Run(() => ExportToExcel(dialog.FileName, entities));
|
||||||
|
ShowInfoMessageBox($"导出完成,共 {entities.Count} 条步骤记录,已保存至:{dialog.FileName}", () => { });
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助方法
|
||||||
|
|
||||||
|
private static void ExportToExcel(string filePath, List<TestReportEntity> entities)
|
||||||
|
{
|
||||||
|
using var workbook = new XLWorkbook();
|
||||||
|
var ws = workbook.Worksheets.Add("测试报告");
|
||||||
|
|
||||||
|
// 表头
|
||||||
|
string[] headers = {
|
||||||
|
"Id", "CreateTime", "TestRoundId", "Scope", "FileName",
|
||||||
|
"StepIndex", "StepName", "StepType", "MethodName", "MethodFullName",
|
||||||
|
"Result", "RunTimeMs", "OutputValue", "Depth", "IsErrorStep", "LoopRemaining"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int c = 0; c < headers.Length; c++)
|
||||||
|
{
|
||||||
|
ws.Cell(1, c + 1).Value = headers[c];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题行样式
|
||||||
|
var headerRange = ws.Range(1, 1, 1, headers.Length);
|
||||||
|
headerRange.Style.Font.Bold = true;
|
||||||
|
headerRange.Style.Fill.BackgroundColor = XLColor.FromArgb(0xECEFF4);
|
||||||
|
headerRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
||||||
|
headerRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
||||||
|
|
||||||
|
// 填充数据
|
||||||
|
for (int i = 0; i < entities.Count; i++)
|
||||||
|
{
|
||||||
|
var e = entities[i];
|
||||||
|
int row = i + 2;
|
||||||
|
|
||||||
|
ws.Cell(row, 1).Value = e.Id;
|
||||||
|
ws.Cell(row, 2).Value = e.CreateTime.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
ws.Cell(row, 3).Value = e.TestRoundId.ToString();
|
||||||
|
ws.Cell(row, 4).Value = e.Scope ?? "";
|
||||||
|
ws.Cell(row, 5).Value = e.FileName ?? "";
|
||||||
|
ws.Cell(row, 6).Value = e.StepIndex;
|
||||||
|
ws.Cell(row, 7).Value = e.StepName ?? "";
|
||||||
|
ws.Cell(row, 8).Value = e.StepType ?? "";
|
||||||
|
ws.Cell(row, 9).Value = e.MethodName ?? "";
|
||||||
|
ws.Cell(row, 10).Value = e.MethodFullName ?? "";
|
||||||
|
ws.Cell(row, 11).Value = e.Result;
|
||||||
|
ws.Cell(row, 12).Value = e.RunTimeMs;
|
||||||
|
ws.Cell(row, 13).Value = e.OutputValue ?? "";
|
||||||
|
ws.Cell(row, 14).Value = e.Depth;
|
||||||
|
ws.Cell(row, 15).Value = e.IsErrorStep ? "是" : "否";
|
||||||
|
ws.Cell(row, 16).Value = e.LoopRemaining;
|
||||||
|
|
||||||
|
// 数据行边框
|
||||||
|
var dataRange = ws.Range(row, 1, row, headers.Length);
|
||||||
|
dataRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
||||||
|
dataRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.Columns().AdjustToContents();
|
||||||
|
workbook.SaveAs(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 生命周期
|
||||||
|
|
||||||
|
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
204
ExportModule/Views/ExportView.xaml
Normal file
204
ExportModule/Views/ExportView.xaml
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
<UserControl x:Class="ExportModule.Views.ExportView"
|
||||||
|
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:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
|
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"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="700"
|
||||||
|
d:DesignWidth="1200">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Border Background="#F5F7FA">
|
||||||
|
<Grid x:Name="RootGrid" Margin="8">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- ========== 标题 ========== -->
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Text="测试报告导出"
|
||||||
|
FontSize="20" FontWeight="Bold"
|
||||||
|
Margin="4,4,0,12"/>
|
||||||
|
|
||||||
|
<!-- ========== 主内容卡片 ========== -->
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Background="White"
|
||||||
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
|
CornerRadius="4">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<!-- 工具栏 -->
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- ====== 工具栏(筛选条件 + 操作按钮) ====== -->
|
||||||
|
<Border Grid.Row="0"
|
||||||
|
Background="#ECEFF4"
|
||||||
|
Padding="12,8"
|
||||||
|
BorderBrush="#DDD" BorderThickness="0,0,0,1">
|
||||||
|
|
||||||
|
<!-- 第一行:筛选条件 -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="160"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="160"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="120"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 开始日期 -->
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="开始日期:"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Margin="0,0,4,0"/>
|
||||||
|
<DatePicker Grid.Column="1"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
SelectedDate="{Binding StartTime}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,16,0"/>
|
||||||
|
|
||||||
|
<!-- 结束日期 -->
|
||||||
|
<TextBlock Grid.Column="2"
|
||||||
|
Text="结束日期:"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Margin="0,0,4,0"/>
|
||||||
|
<DatePicker Grid.Column="3"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
SelectedDate="{Binding EndTime}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,16,0"/>
|
||||||
|
|
||||||
|
<!-- 台架选择 -->
|
||||||
|
<TextBlock Grid.Column="4"
|
||||||
|
Text="台架:"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Margin="0,0,4,0"/>
|
||||||
|
<ComboBox Grid.Column="5"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
ItemsSource="{Binding ScopeOptions}"
|
||||||
|
SelectedItem="{Binding SelectedScope}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="0,0,16,0"/>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<Button Grid.Column="6"
|
||||||
|
Content="查询"
|
||||||
|
Command="{Binding QueryCommand}"
|
||||||
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
|
Padding="16,6"
|
||||||
|
Margin="4,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Button Grid.Column="7"
|
||||||
|
Content="导出 Excel"
|
||||||
|
Command="{Binding ExportCommand}"
|
||||||
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
|
Padding="16,6"
|
||||||
|
Margin="4,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<Button Grid.Column="8"
|
||||||
|
Content="加载全部"
|
||||||
|
Command="{Binding LoadedCommand}"
|
||||||
|
Style="{StaticResource MaterialDesignFlatButton}"
|
||||||
|
Padding="16,6"
|
||||||
|
Margin="4,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ====== 数据展示区 ====== -->
|
||||||
|
<DataGrid Grid.Row="1"
|
||||||
|
ItemsSource="{Binding TestReportModelList}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
IsReadOnly="True"
|
||||||
|
RowHeaderWidth="0"
|
||||||
|
BorderThickness="0"
|
||||||
|
Background="White"
|
||||||
|
AlternatingRowBackground="#F5F7FA"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectedItem="{Binding SelectedTestReport}"
|
||||||
|
Margin="8">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="TestRoundId"
|
||||||
|
Width="280"
|
||||||
|
Binding="{Binding TestRoundId}"/>
|
||||||
|
<DataGridTextColumn Header="台架名称"
|
||||||
|
Width="120"
|
||||||
|
Binding="{Binding Scope}"/>
|
||||||
|
<DataGridTextColumn Header="开始时间"
|
||||||
|
Width="180"
|
||||||
|
Binding="{Binding StartTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}"/>
|
||||||
|
<DataGridTextColumn Header="结束时间"
|
||||||
|
Width="180"
|
||||||
|
Binding="{Binding EndTime, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}}"/>
|
||||||
|
<DataGridTextColumn Header="文件名"
|
||||||
|
Width="*"
|
||||||
|
Binding="{Binding FileName}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<!-- 标题行样式:与项目风格一致 -->
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="Background" Value="#ECEFF4"/>
|
||||||
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#DDD"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0,0,1,1"/>
|
||||||
|
<Setter Property="Padding" Value="8,6"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="DataGridCell">
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Padding" Value="8,4"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
</DataGrid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ========== 状态栏 ========== -->
|
||||||
|
<Border Grid.Row="2"
|
||||||
|
Background="#ECEFF4"
|
||||||
|
Padding="10,6"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
CornerRadius="2">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
Text="{Binding StatusMessage}"
|
||||||
|
Foreground="#444"
|
||||||
|
FontSize="12"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Grid.Column="1"
|
||||||
|
Foreground="#666"
|
||||||
|
FontSize="12"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="8,0,0,0">
|
||||||
|
<Run Text="记录数:"/>
|
||||||
|
<Run Text="{Binding TotalCount}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
28
ExportModule/Views/ExportView.xaml.cs
Normal file
28
ExportModule/Views/ExportView.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 ExportModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ExportView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class ExportView : UserControl
|
||||||
|
{
|
||||||
|
public ExportView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using DeviceCommand.Device;
|
using DeviceCommand.Devices;
|
||||||
using MainModule.Views;
|
using MainModule.Views;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using DeviceCommand.Devices;
|
||||||
using Logger;
|
using Logger;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using TestingModule.ViewModels;
|
using TestingModule.ViewModels;
|
||||||
@@ -19,6 +22,8 @@ namespace MainModule.ViewModels
|
|||||||
private string _testStatus;
|
private string _testStatus;
|
||||||
private readonly IScopedProvider _scope;
|
private readonly IScopedProvider _scope;
|
||||||
private bool IsInitialized =false;
|
private bool IsInitialized =false;
|
||||||
|
private bool _isRunningErrorSteps = false;
|
||||||
|
private SubscriptionToken? _alarmToken;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 属性
|
#region 属性
|
||||||
@@ -64,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>();
|
||||||
@@ -78,9 +87,111 @@ namespace MainModule.ViewModels
|
|||||||
RefreshCommand = new DelegateCommand(OnRefresh);
|
RefreshCommand = new DelegateCommand(OnRefresh);
|
||||||
BackToProtocolCommand = new DelegateCommand(OnBackToProtocol);
|
BackToProtocolCommand = new DelegateCommand(OnBackToProtocol);
|
||||||
LoadedCommand = new AsyncDelegateCommand(OnLoad);
|
LoadedCommand = new AsyncDelegateCommand(OnLoad);
|
||||||
|
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Subscribe(async (scope) => await SilenceBuzzer(scope));
|
||||||
|
_alarmToken = _eventAggregator.GetEvent<AlarmEvent>().Subscribe(OnAlarmTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SilenceBuzzer(string scope)
|
||||||
|
{
|
||||||
|
if (_deviceManager.IOGroup == null) return;
|
||||||
|
|
||||||
|
if (scope == "default")
|
||||||
|
{
|
||||||
|
for(int i = 1; i <= 8; i++)
|
||||||
|
{
|
||||||
|
await _deviceManager.IOGroup.执行台架动作Async(i, 功能动作.蜂鸣器报警, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (scope == TestStatus)
|
||||||
|
{
|
||||||
|
int 台架号 = _systemConfig.SharedParameterList
|
||||||
|
.FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
|
||||||
|
if (台架号 >= 1 && 台架号 <= 8)
|
||||||
|
{
|
||||||
|
await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AlarmEvent 回调:
|
||||||
|
/// 上下限 → 蜂鸣器报警 true
|
||||||
|
/// 上下极限 → 蜂鸣器报警 true + 执行对应 scope 的异常流程
|
||||||
|
/// </summary>
|
||||||
|
private async void OnAlarmTriggered((string Scope, string Fingerprint, string Status) args)
|
||||||
|
{
|
||||||
|
if (args.Scope != TestStatus) return;
|
||||||
|
if (_deviceManager?.IOGroup == null) return;
|
||||||
|
|
||||||
|
int 台架号 = _systemConfig.SharedParameterList
|
||||||
|
.FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0;
|
||||||
|
if (台架号 < 1 || 台架号 > 8) return;
|
||||||
|
|
||||||
|
// 上下限 / 上下极限 均触发蜂鸣器报警
|
||||||
|
await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, true);
|
||||||
|
LoggerHelper.WarnWithNotify(args.Scope, $"[{args.Scope}] 报警触发: {args.Status} ({args.Fingerprint}),蜂鸣器已开启。");
|
||||||
|
|
||||||
|
// 上下极限额外触发异常流程
|
||||||
|
if (args.Status == "超上极限" || args.Status == "超下极限")
|
||||||
|
{
|
||||||
|
_ = TriggerErrorStepsAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行异常流程(带并发保护,同一时间仅允许一次)。
|
||||||
|
/// 如果正常流程正在运行,先取消并等待其停止,再执行异常流程。
|
||||||
|
/// </summary>
|
||||||
|
private async Task TriggerErrorStepsAsync()
|
||||||
|
{
|
||||||
|
if (_isRunningErrorSteps) return;
|
||||||
|
_isRunningErrorSteps = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 1. 如果正常流程正在运行,先停止它
|
||||||
|
// IsStop == false 表示正在运行;IsStop == null 表示未启动或已结束
|
||||||
|
if (_scopedContext.IsStop == false)
|
||||||
|
{
|
||||||
|
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 报警触发,正在停止正常流程...");
|
||||||
|
_stepRunning.stepCTS.Cancel();
|
||||||
|
|
||||||
|
// 等待 ShellViewModel 清理完毕(IsStop 变回 null、stepCTS 被重置)
|
||||||
|
// ExecuteMethodStep 内部硬编码使用 stepCTS.Token,必须等它重置后才能跑异常流程
|
||||||
|
var deadline = DateTime.Now.AddSeconds(5);
|
||||||
|
while ((_scopedContext.IsStop != null || _stepRunning.stepCTS.IsCancellationRequested)
|
||||||
|
&& DateTime.Now < deadline)
|
||||||
|
{
|
||||||
|
await Task.Delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超时后仍未重置则强制重置,确保异常流程的设备命令能拿到有效 Token
|
||||||
|
if (_stepRunning.stepCTS.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_stepRunning.stepCTS = new CancellationTokenSource();
|
||||||
|
_scopedContext.IsStop = null;
|
||||||
|
_scopedContext.RunState = "运行";
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerHelper.WarnWithNotify(TestStatus, $"[{TestStatus}] 正常流程已停止,开始执行异常流程");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 执行异常流程
|
||||||
|
await _stepRunning.ExecuteErrorSteps(
|
||||||
|
_scopedContext.Program,
|
||||||
|
cancellationToken: _stepRunning.errorStepCTS.Token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.ErrorWithNotify(TestStatus, $"[{TestStatus}] 报警触发异常流程失败: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isRunningErrorSteps = false;
|
||||||
|
if (_stepRunning.errorStepCTS.IsCancellationRequested)
|
||||||
|
_stepRunning.errorStepCTS = new CancellationTokenSource();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// 1. 如果 TestStatus 为空,说明还没走到 OnNavigatedTo 赋值,直接释放 scope 即可
|
// 1. 如果 TestStatus 为空,说明还没走到 OnNavigatedTo 赋值,直接释放 scope 即可
|
||||||
@@ -92,6 +203,9 @@ namespace MainModule.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (_alarmToken != null)
|
||||||
|
_eventAggregator.GetEvent<AlarmEvent>().Unsubscribe(_alarmToken);
|
||||||
|
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Unsubscribe(async (scope) => await SilenceBuzzer(scope));
|
||||||
// 2. 显式释放硬件资源(防止端口占用/死锁)
|
// 2. 显式释放硬件资源(防止端口占用/死锁)
|
||||||
if (_deviceManager is IDisposable disposableDevice)
|
if (_deviceManager is IDisposable disposableDevice)
|
||||||
{
|
{
|
||||||
@@ -156,6 +270,7 @@ namespace MainModule.ViewModels
|
|||||||
if(_systemConfig.DefaultProgramFilePath != null&&File.Exists(_systemConfig.DefaultProgramFilePath))
|
if(_systemConfig.DefaultProgramFilePath != null&&File.Exists(_systemConfig.DefaultProgramFilePath))
|
||||||
{
|
{
|
||||||
var filePath = _systemConfig.DefaultProgramFilePath;
|
var filePath = _systemConfig.DefaultProgramFilePath;
|
||||||
|
_systemConfig.CurrentADPFile= _systemConfig.DefaultProgramFilePath;
|
||||||
// 读取 JSON 文件
|
// 读取 JSON 文件
|
||||||
string json = File.ReadAllText(filePath);
|
string json = File.ReadAllText(filePath);
|
||||||
|
|
||||||
|
|||||||
@@ -231,7 +231,7 @@
|
|||||||
</ContentControl>
|
</ContentControl>
|
||||||
|
|
||||||
<!-- 单元 9:TestCell9,默认 (2,2) -->
|
<!-- 单元 9:TestCell9,默认 (2,2) -->
|
||||||
<ContentControl prism:RegionManager.RegionName="TestCell9" Margin="2">
|
<!--<ContentControl prism:RegionManager.RegionName="TestCell9" Margin="2">
|
||||||
<ContentControl.Style>
|
<ContentControl.Style>
|
||||||
<Style TargetType="ContentControl">
|
<Style TargetType="ContentControl">
|
||||||
<Setter Property="Grid.Row" Value="2"/>
|
<Setter Property="Grid.Row" Value="2"/>
|
||||||
@@ -253,6 +253,6 @@
|
|||||||
</Style.Triggers>
|
</Style.Triggers>
|
||||||
</Style>
|
</Style>
|
||||||
</ContentControl.Style>
|
</ContentControl.Style>
|
||||||
</ContentControl>
|
</ContentControl>-->
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
24
Model/Entity/MonitorValueEntity.cs
Normal file
24
Model/Entity/MonitorValueEntity.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Model.Entity
|
||||||
|
{
|
||||||
|
public class MonitorValueEntity:BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>监测项名称</summary>
|
||||||
|
[SugarColumn(ColumnName = "MonitorName")]
|
||||||
|
public string MonitorName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>监测值</summary>
|
||||||
|
[SugarColumn(ColumnName = "MonitorValue")]
|
||||||
|
public double MonitorValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>作用域标识(台架名称)</summary>
|
||||||
|
[SugarColumn(ColumnName = "Scope")]
|
||||||
|
public string Scope { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Model/Entity/TestReportEntity.cs
Normal file
67
Model/Entity/TestReportEntity.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Model.Entity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 测试报告记录:每次 ExecuteSteps / ExecuteErrorSteps 中每个步骤的执行结果。
|
||||||
|
/// 同一次运行的所有步骤共享同一个 TestRoundId。
|
||||||
|
/// </summary>
|
||||||
|
public class TestReportEntity : BaseEntity
|
||||||
|
{
|
||||||
|
/// <summary>同一次运行的统一标识(StepRunning.TestRoundID)</summary>
|
||||||
|
[SugarColumn(ColumnName = "TestRoundId", ColumnDescription = "运行轮次标识")]
|
||||||
|
public Guid TestRoundId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>作用域/台架名称</summary>
|
||||||
|
[SugarColumn(ColumnName = "Scope", ColumnDescription = "台架名称")]
|
||||||
|
public string Scope { get; set; } = string.Empty;
|
||||||
|
/// <summary>测试项名称(当前ADP文件路径)</summary>
|
||||||
|
[SugarColumn(ColumnName = "FileName", ColumnDescription = "测试项名称")]
|
||||||
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>步骤序号</summary>
|
||||||
|
[SugarColumn(ColumnName = "StepIndex")]
|
||||||
|
public int StepIndex { get; set; }
|
||||||
|
|
||||||
|
/// <summary>步骤名称</summary>
|
||||||
|
[SugarColumn(ColumnName = "StepName", Length = 200)]
|
||||||
|
public string StepName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>步骤类型(普通步骤/循环开始/循环结束)</summary>
|
||||||
|
[SugarColumn(ColumnName = "StepType", Length = 50)]
|
||||||
|
public string StepType { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>执行的方法名称</summary>
|
||||||
|
[SugarColumn(ColumnName = "MethodName", Length = 200, IsNullable = true)]
|
||||||
|
public string? MethodName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>方法的完整类型名(实际方法所属类)</summary>
|
||||||
|
[SugarColumn(ColumnName = "MethodFullName", Length = 500, IsNullable = true)]
|
||||||
|
public string? MethodFullName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>执行结果(成功/失败/未执行/跳过)</summary>
|
||||||
|
[SugarColumn(ColumnName = "Result", Length = 20)]
|
||||||
|
public string Result { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>执行耗时(毫秒)</summary>
|
||||||
|
[SugarColumn(ColumnName = "RunTimeMs", IsNullable = true)]
|
||||||
|
public int? RunTimeMs { get; set; }
|
||||||
|
|
||||||
|
/// <summary>输出值(如果有)</summary>
|
||||||
|
[SugarColumn(ColumnName = "OutputValue", Length = 500, IsNullable = true)]
|
||||||
|
public string? OutputValue { get; set; }
|
||||||
|
|
||||||
|
/// <summary>子程序嵌套深度(0=主程序)</summary>
|
||||||
|
[SugarColumn(ColumnName = "Depth")]
|
||||||
|
public int Depth { get; set; }
|
||||||
|
|
||||||
|
/// <summary>是否异常流程步骤</summary>
|
||||||
|
[SugarColumn(ColumnName = "IsErrorStep")]
|
||||||
|
public bool IsErrorStep { get; set; }
|
||||||
|
|
||||||
|
/// <summary>循环剩余次数(仅循环步骤有值)</summary>
|
||||||
|
[SugarColumn(ColumnName = "LoopRemaining", IsNullable = true)]
|
||||||
|
public int? LoopRemaining { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
23
Model/Models/AvailableMethodItem.cs
Normal file
23
Model/Models/AvailableMethodItem.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Model.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 可用设备方法项(供用户选择添加为监测通道)
|
||||||
|
/// </summary>
|
||||||
|
public class AvailableMethodItem
|
||||||
|
{
|
||||||
|
public string DeviceName { get; set; } = string.Empty;
|
||||||
|
/// <summary>硬件指纹(物理设备唯一标识)</summary>
|
||||||
|
public string Fingerprint { get; set; } = string.Empty;
|
||||||
|
public string MethodName { get; set; } = string.Empty;
|
||||||
|
public string DisplayName { get; set; } = string.Empty;
|
||||||
|
public MethodInfo MethodInfo { get; set; } = null!;
|
||||||
|
public object Device { get; set; } = null!;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Model/Models/HardwareReportArgs.cs
Normal file
26
Model/Models/HardwareReportArgs.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Model.Models
|
||||||
|
{
|
||||||
|
public class HardwareReportArgs
|
||||||
|
{
|
||||||
|
/// <summary>作用域标识(台架名称)</summary>
|
||||||
|
public string Scope { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>硬件指纹(物理设备唯一标识,如 "Tcp:192.168.1.1:5000")</summary>
|
||||||
|
public string HardwareFingerprint { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>方法名</summary>
|
||||||
|
public string MethodName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>采样值</summary>
|
||||||
|
public double Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>采样时间</summary>
|
||||||
|
public DateTime Time { get; set; } = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Model/Models/TestReportModel.cs
Normal file
17
Model/Models/TestReportModel.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Model.Models
|
||||||
|
{
|
||||||
|
public class TestReportModel
|
||||||
|
{
|
||||||
|
public Guid TestRoundId { get; set; }
|
||||||
|
public string Scope { get; set; }
|
||||||
|
public DateTime StartTime { get; set; }
|
||||||
|
public DateTime EndTime { get; set; }
|
||||||
|
public string FileName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
using MonitorModule.Views;
|
using MonitorModule.Views;
|
||||||
|
using MonitorModule.Views.Dialogs;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace MonitorModule
|
namespace MonitorModule
|
||||||
@@ -16,6 +17,7 @@ namespace MonitorModule
|
|||||||
{
|
{
|
||||||
containerRegistry.RegisterForNavigation<MonitorView>("MonitorView");
|
containerRegistry.RegisterForNavigation<MonitorView>("MonitorView");
|
||||||
containerRegistry.RegisterForNavigation<RecordView>("RecordView");
|
containerRegistry.RegisterForNavigation<RecordView>("RecordView");
|
||||||
|
containerRegistry.RegisterDialog<ValueLimitView>("ValueLimitView");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
398
MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
Normal file
398
MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using Prism.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.PubEvent;
|
||||||
|
using UIShare.UIViewModel;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
using ZLGUSBCANFD;
|
||||||
|
|
||||||
|
namespace MonitorModule.ViewModels.Dialogs
|
||||||
|
{
|
||||||
|
public class ValueLimitViewModel : DialogViewModelBase
|
||||||
|
{
|
||||||
|
#region 属性
|
||||||
|
private string _Title = "报警设置";
|
||||||
|
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get { return _Title; }
|
||||||
|
set { SetProperty(ref _Title, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueLimitVM _SelectValueLimitVM;
|
||||||
|
|
||||||
|
public ValueLimitVM SelectValueLimitVM
|
||||||
|
{
|
||||||
|
get => _SelectValueLimitVM;
|
||||||
|
set => SetProperty(ref _SelectValueLimitVM, value);
|
||||||
|
}
|
||||||
|
private ObservableCollection<ValueLimitVM> _ValueLimitList;
|
||||||
|
public ObservableCollection<ValueLimitVM> ValueLimitList
|
||||||
|
{
|
||||||
|
get => _ValueLimitList;
|
||||||
|
set => SetProperty(ref _ValueLimitList, value);
|
||||||
|
}
|
||||||
|
//private ObservableCollection<string> _DeviceSingleList = new();
|
||||||
|
|
||||||
|
//public ObservableCollection<string> DeviceSingleList
|
||||||
|
//{
|
||||||
|
// get => _DeviceSingleList;
|
||||||
|
// set => SetProperty(ref _DeviceSingleList, value);
|
||||||
|
//}
|
||||||
|
//private string _SelectCurve;
|
||||||
|
|
||||||
|
//public string SelectCurve
|
||||||
|
//{
|
||||||
|
// get => _SelectCurve;
|
||||||
|
// set => SetProperty(ref _SelectCurve, value);
|
||||||
|
//}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region 命令
|
||||||
|
public ICommand SaveCommand { get; set; }
|
||||||
|
public ICommand CloseCommand { get; set; }
|
||||||
|
//public ICommand SelectSignalCommand { get; set; }
|
||||||
|
public ICommand DeleteCommand { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
private GlobalInfo _globalInfo { get; set; }
|
||||||
|
private SystemConfig _systemConfig { get; set; }
|
||||||
|
private IScopedProvider _scope { get; set; }
|
||||||
|
private DeviceManager? _deviceManager;
|
||||||
|
|
||||||
|
/// <summary>SignalName → CAN信号标识,用于DBC加载/卸载时清理</summary>
|
||||||
|
private readonly Dictionary<string, (string Fingerprint, string MethodName)> _canSignalMap = new();
|
||||||
|
|
||||||
|
private SubscriptionToken? _dbcLoadedToken;
|
||||||
|
private SubscriptionToken? _dbcUnloadedToken;
|
||||||
|
|
||||||
|
public ValueLimitViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||||
|
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||||
|
SaveCommand = new DelegateCommand(OnSave);
|
||||||
|
CloseCommand = new DelegateCommand(OnClose);
|
||||||
|
//SelectSignalCommand = new DelegateCommand(SelectSignal);
|
||||||
|
DeleteCommand = new DelegateCommand(Deletel);
|
||||||
|
}
|
||||||
|
#region 命令处理
|
||||||
|
private void Deletel()
|
||||||
|
{
|
||||||
|
if (SelectValueLimitVM == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ValueLimitList.Remove(SelectValueLimitVM);
|
||||||
|
SelectValueLimitVM = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void InitData()
|
||||||
|
{
|
||||||
|
ValueLimitList = new ObservableCollection<ValueLimitVM>();
|
||||||
|
//DeviceSingleList = new ObservableCollection<string>();
|
||||||
|
_canSignalMap.Clear();
|
||||||
|
|
||||||
|
// 1. 加载已保存的 ValueLimitList(保留用户设置)
|
||||||
|
if (_systemConfig?.ValueLimitList != null)
|
||||||
|
{
|
||||||
|
foreach (var item in _systemConfig.ValueLimitList)
|
||||||
|
ValueLimitList.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 发现设备方法信号并加入 ValueLimitList / DeviceSingleList
|
||||||
|
foreach (var (displayName, fingerprint, methodName) in DiscoverDeviceSignals())
|
||||||
|
{
|
||||||
|
//DeviceSingleList.Add(displayName);
|
||||||
|
EnsureValueLimit(fingerprint, displayName, displayName, methodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 发现 CAN 信号并加入 ValueLimitList,同时收集有效信号名
|
||||||
|
var validCanSignalNames = new HashSet<string>();
|
||||||
|
foreach (var (displayName, fingerprint, methodName) in DiscoverCanSignals())
|
||||||
|
{
|
||||||
|
_canSignalMap[displayName] = (fingerprint, methodName);
|
||||||
|
EnsureValueLimit(fingerprint, displayName, displayName, methodName);
|
||||||
|
validCanSignalNames.Add(displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 清理已不存在于 DBC 中的 CAN 信号(Fingerprint 以 "CAN" 开头且不在有效集合中)
|
||||||
|
var staleCanSignals = ValueLimitList
|
||||||
|
.Where(x => !string.IsNullOrEmpty(x.Fingerprint) &&
|
||||||
|
x.Fingerprint.StartsWith("CAN") &&
|
||||||
|
!validCanSignalNames.Contains(x.SignalName))
|
||||||
|
.ToList();
|
||||||
|
foreach (var item in staleCanSignals)
|
||||||
|
ValueLimitList.Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发现当前作用域所有可监测的设备方法信号(与 MonitorViewModel 逻辑一致)。
|
||||||
|
/// 返回 (DisplayName, Fingerprint, MethodName) 列表。
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<(string DisplayName, string Fingerprint, string MethodName)> DiscoverDeviceSignals()
|
||||||
|
{
|
||||||
|
if (_deviceManager?.DeviceMap == null) yield break;
|
||||||
|
|
||||||
|
var instanceToFp = BuildInstanceToFingerprint();
|
||||||
|
foreach (var kvp in _deviceManager.DeviceMap)
|
||||||
|
{
|
||||||
|
string deviceName = kvp.Key;
|
||||||
|
var device = kvp.Value;
|
||||||
|
|
||||||
|
if (!instanceToFp.TryGetValue(device, out string? fingerprint))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var methods = device.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(m =>
|
||||||
|
{
|
||||||
|
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
|
||||||
|
if (m.ReturnType != typeof(Task<string>)) return false;
|
||||||
|
var parms = m.GetParameters();
|
||||||
|
return parms.Length == 0 ||
|
||||||
|
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var method in methods)
|
||||||
|
{
|
||||||
|
var attr = method.GetCustomAttribute<MonitorableAttribute>();
|
||||||
|
string displayName = !string.IsNullOrEmpty(attr?.Description)
|
||||||
|
? $"{deviceName}.{attr.Description}"
|
||||||
|
: $"{deviceName}.{method.Name}";
|
||||||
|
yield return (displayName, fingerprint, method.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建设备实例 → 硬件指纹的反向查找表。
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<object, string> BuildInstanceToFingerprint()
|
||||||
|
{
|
||||||
|
var map = new Dictionary<object, string>(ReferenceEqualityComparer.Instance);
|
||||||
|
if (_globalInfo?.HardwarePool == null) return map;
|
||||||
|
|
||||||
|
foreach (var poolEntry in _globalInfo.HardwarePool)
|
||||||
|
{
|
||||||
|
var lazy = poolEntry.Value;
|
||||||
|
if (lazy.IsValueCreated && lazy.Value != null)
|
||||||
|
map[lazy.Value] = poolEntry.Key;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发现当前已加载 DBC 中的 CAN 信号(以 ConfigurationList 为白名单)。
|
||||||
|
/// 返回 (DisplayName, Fingerprint, MethodName) 列表。
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<(string DisplayName, string Fingerprint, string MethodName)> DiscoverCanSignals()
|
||||||
|
{
|
||||||
|
if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) yield break;
|
||||||
|
if (_systemConfig?.ConfigurationList == null) yield break;
|
||||||
|
|
||||||
|
var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
|
||||||
|
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
|
||||||
|
foreach (var cfg in _systemConfig.ConfigurationList)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(cfg.SignalName)) continue;
|
||||||
|
if (cfg.Channel < 0 || cfg.Channel >= msgDb.Count) continue;
|
||||||
|
|
||||||
|
string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, (uint)cfg.Channel);
|
||||||
|
string methodName = CANSignalBroadcaster.BuildMethodName((uint)cfg.MessageID, cfg.SignalName);
|
||||||
|
string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
|
||||||
|
|
||||||
|
if (ExistsInDbc(methodName, msgDb[cfg.Channel]))
|
||||||
|
yield return (displayName, fingerprint, methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断指定的 MethodName 是否存在于该通道的 DBC 消息数据库中。
|
||||||
|
/// </summary>
|
||||||
|
private static bool ExistsInDbc(string methodName, List<_Msg_> messages)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(methodName)) return false;
|
||||||
|
int dotIndex = methodName.IndexOf('.');
|
||||||
|
if (dotIndex <= 0 || dotIndex >= methodName.Length - 1) return false;
|
||||||
|
|
||||||
|
if (!uint.TryParse(methodName.Substring(0, dotIndex), System.Globalization.NumberStyles.HexNumber, null, out uint msgId))
|
||||||
|
return false;
|
||||||
|
string signalName = methodName.Substring(dotIndex + 1);
|
||||||
|
|
||||||
|
return messages.Any(m => m.msg_id == msgId && m.signal_Name != null && m.signal_Name.Contains(signalName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 确保 ValueLimitList 中存在指定信号;不存在则添加默认值。
|
||||||
|
/// </summary>
|
||||||
|
private void EnsureValueLimit(string fingerprint, string displayName, string signalName, string methodName = "")
|
||||||
|
{
|
||||||
|
if (ValueLimitList.Any(x => x.SignalName == signalName)) return;
|
||||||
|
|
||||||
|
ValueLimitList.Add(new ValueLimitVM
|
||||||
|
{
|
||||||
|
DisplayName = displayName,
|
||||||
|
Fingerprint = fingerprint,
|
||||||
|
SignalName = signalName,
|
||||||
|
MethodName = methodName,
|
||||||
|
Upper = 9999,
|
||||||
|
Lower = -9999,
|
||||||
|
UpperExtreme = 9999,
|
||||||
|
LowerExtreme = -9999,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 ValueLimitList 和 DeviceSingleList 中移除指定信号。
|
||||||
|
/// </summary>
|
||||||
|
private void RemoveValueLimit(string signalName)
|
||||||
|
{
|
||||||
|
var item = ValueLimitList.FirstOrDefault(x => x.SignalName == signalName);
|
||||||
|
if (item != null) ValueLimitList.Remove(item);
|
||||||
|
|
||||||
|
//if (DeviceSingleList.Contains(signalName))
|
||||||
|
// DeviceSingleList.Remove(signalName);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private void SelectSignal()
|
||||||
|
//{
|
||||||
|
// if (ValueLimitList.Where(x => x.SignalName == SelectCurve).Count() > 0)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// ValueLimitList.Add(new ValueLimitVM
|
||||||
|
// {
|
||||||
|
// SignalName = SelectCurve,
|
||||||
|
// Upper = 9999,
|
||||||
|
// Lower = -9999,
|
||||||
|
// UpperExtreme = 9999,
|
||||||
|
// LowerExtreme = -9999,
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
private void OnClose()
|
||||||
|
{
|
||||||
|
RequestClose.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSave()
|
||||||
|
{
|
||||||
|
_systemConfig.ValueLimitList = ValueLimitList;
|
||||||
|
ConfigService.Save(_systemConfig);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
public override void OnDialogOpened(IDialogParameters parameters)
|
||||||
|
{
|
||||||
|
// 1. 优先执行基类的打开逻辑(如果基类有需要初始化的通用事务)
|
||||||
|
base.OnDialogOpened(parameters);
|
||||||
|
|
||||||
|
// 2. 解析参数
|
||||||
|
if (parameters != null && parameters.ContainsKey("Scope")) // 修复:Prism 中应使用 ContainsKey
|
||||||
|
{
|
||||||
|
_scope = parameters.GetValue<IScopedProvider>("Scope");
|
||||||
|
_systemConfig = _scope.Resolve<SystemConfig>();
|
||||||
|
_deviceManager = _scope.Resolve<DeviceManager>();
|
||||||
|
|
||||||
|
// 3. 订阅 DBC 加载/卸载事件
|
||||||
|
_dbcLoadedToken = _eventAggregator.GetEvent<DBCLoadedEvent>()
|
||||||
|
.Subscribe(OnDbcLoaded, ThreadOption.UIThread);
|
||||||
|
_dbcUnloadedToken = _eventAggregator.GetEvent<DBCUnloadedEvent>()
|
||||||
|
.Subscribe(OnDbcUnloaded, ThreadOption.UIThread);
|
||||||
|
|
||||||
|
// 4. 初始化数据
|
||||||
|
InitData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDialogClosed()
|
||||||
|
{
|
||||||
|
base.OnDialogClosed();
|
||||||
|
|
||||||
|
// 取消 DBC 事件订阅,避免内存泄漏
|
||||||
|
if (_dbcLoadedToken != null)
|
||||||
|
_eventAggregator.GetEvent<DBCLoadedEvent>().Unsubscribe(_dbcLoadedToken);
|
||||||
|
if (_dbcUnloadedToken != null)
|
||||||
|
_eventAggregator.GetEvent<DBCUnloadedEvent>().Unsubscribe(_dbcUnloadedToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DBCLoadedEvent 回调:清理该通道中不存在于 DBC 的 CAN 信号限制项。
|
||||||
|
/// </summary>
|
||||||
|
private void OnDbcLoaded(DBCLoadedArgs args)
|
||||||
|
{
|
||||||
|
if (args.Scope != _systemConfig?.Title) return;
|
||||||
|
if (_deviceManager?.CANFD?.DBCParser?.MsgDatabase == null) return;
|
||||||
|
|
||||||
|
int channel = (int)args.Channel;
|
||||||
|
var msgDb = _deviceManager.CANFD.DBCParser.MsgDatabase;
|
||||||
|
if (channel < 0 || channel >= msgDb.Count) return;
|
||||||
|
|
||||||
|
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
|
||||||
|
string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, args.Channel);
|
||||||
|
|
||||||
|
// 1. 移除该通道中已失效的 CAN 信号限制项
|
||||||
|
var staleSignals = _canSignalMap
|
||||||
|
.Where(kvp => kvp.Value.Fingerprint == fingerprint &&
|
||||||
|
!ExistsInDbc(kvp.Value.MethodName, msgDb[channel]))
|
||||||
|
.Select(kvp => kvp.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var signalName in staleSignals)
|
||||||
|
{
|
||||||
|
RemoveValueLimit(signalName);
|
||||||
|
_canSignalMap.Remove(signalName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 根据 ConfigurationList 补充新加载的有效信号
|
||||||
|
if (_systemConfig?.ConfigurationList != null)
|
||||||
|
{
|
||||||
|
foreach (var cfg in _systemConfig.ConfigurationList.Where(c => c.Channel == channel))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(cfg.SignalName)) continue;
|
||||||
|
|
||||||
|
string methodName = CANSignalBroadcaster.BuildMethodName((uint)cfg.MessageID, cfg.SignalName);
|
||||||
|
string displayName = CANSignalBroadcaster.BuildDisplayName(cfg.MessageName, cfg.SignalName);
|
||||||
|
|
||||||
|
if (ExistsInDbc(methodName, msgDb[channel]))
|
||||||
|
{
|
||||||
|
_canSignalMap[displayName] = (fingerprint, methodName);
|
||||||
|
//if (!DeviceSingleList.Contains(displayName))
|
||||||
|
// DeviceSingleList.Add(displayName);
|
||||||
|
EnsureValueLimit(fingerprint, displayName, displayName, methodName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DBCUnloadedEvent 回调:删除该通道所有 CAN 信号的限制项。
|
||||||
|
/// </summary>
|
||||||
|
private void OnDbcUnloaded(DBCUnloadedArgs args)
|
||||||
|
{
|
||||||
|
if (args.Scope != _systemConfig?.Title) return;
|
||||||
|
|
||||||
|
string canDeviceFingerprint = _deviceManager.GetCanDeviceFingerprint();
|
||||||
|
string fingerprint = CANSignalBroadcaster.BuildFingerprint(canDeviceFingerprint, args.Channel);
|
||||||
|
var toRemove = _canSignalMap
|
||||||
|
.Where(kvp => kvp.Value.Fingerprint == fingerprint)
|
||||||
|
.Select(kvp => kvp.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var signalName in toRemove)
|
||||||
|
{
|
||||||
|
RemoveValueLimit(signalName);
|
||||||
|
_canSignalMap.Remove(signalName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,13 @@
|
|||||||
using Logger;
|
using Common.Attributes;
|
||||||
|
using DeviceCommand.Base;
|
||||||
|
using Logger;
|
||||||
|
using Model.Entity;
|
||||||
|
using ORM;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
@@ -12,9 +17,9 @@ using UIShare.ViewModelBase;
|
|||||||
namespace MonitorModule.ViewModels
|
namespace MonitorModule.ViewModels
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 记录界面:用于查看运行目录下 SQL/ADP.db 中的数据。
|
/// 记录界面:查询 MonitorValueEntity 表中的历史监测数据。
|
||||||
/// 功能:表选择 / 关键字 WHERE 查询 / 分页 / 导出 CSV。
|
/// 功能:监测项筛选 / 台架筛选 / 日期筛选 / 分页 / 导出 CSV。
|
||||||
/// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份查询状态。
|
/// 使用 SqlSugarContext.DbContext 全局单例,与写入端共用同一数据库连接。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||||
{
|
{
|
||||||
@@ -30,30 +35,44 @@ namespace MonitorModule.ViewModels
|
|||||||
set => SetProperty(ref _testStatus, value);
|
set => SetProperty(ref _testStatus, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<string> TableNames
|
/// <summary>监测项名称下拉列表</summary>
|
||||||
|
public ObservableCollection<string> MonitorNames
|
||||||
{
|
{
|
||||||
get => _tableNames;
|
get => _monitorNames;
|
||||||
set => SetProperty(ref _tableNames, value);
|
set => SetProperty(ref _monitorNames, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? SelectedTable
|
/// <summary>选中的监测项(null = 全部)</summary>
|
||||||
|
public string? SelectedMonitorName
|
||||||
{
|
{
|
||||||
get => _selectedTable;
|
get => _selectedMonitorName;
|
||||||
set
|
set => SetProperty(ref _selectedMonitorName, value);
|
||||||
{
|
|
||||||
if (SetProperty(ref _selectedTable, value))
|
|
||||||
{
|
|
||||||
PageIndex = 1;
|
|
||||||
Query();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户填写的 WHERE 子句(不带 WHERE 关键字),例如:Status='OK' AND Id>10
|
/// <summary>台架(作用域)下拉列表</summary>
|
||||||
public string WhereClause
|
public ObservableCollection<string> ScopeNames
|
||||||
{
|
{
|
||||||
get => _whereClause;
|
get => _scopeNames;
|
||||||
set => SetProperty(ref _whereClause, value);
|
set => SetProperty(ref _scopeNames, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>选中的台架(null = 全部)</summary>
|
||||||
|
public string? SelectedScope
|
||||||
|
{
|
||||||
|
get => _selectedScope;
|
||||||
|
set => SetProperty(ref _selectedScope, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>查询日期(null = 全部日期)</summary>
|
||||||
|
public DateTime? SelectedStartDate
|
||||||
|
{
|
||||||
|
get => _selectedStartDate;
|
||||||
|
set => SetProperty(ref _selectedStartDate, value);
|
||||||
|
}
|
||||||
|
public DateTime? SelectedEndDate
|
||||||
|
{
|
||||||
|
get => _selectedEndDate;
|
||||||
|
set => SetProperty(ref _selectedEndDate, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataTable ResultTable
|
public DataTable ResultTable
|
||||||
@@ -111,40 +130,39 @@ namespace MonitorModule.ViewModels
|
|||||||
|
|
||||||
#region 命令
|
#region 命令
|
||||||
public ICommand LoadedCommand { get; }
|
public ICommand LoadedCommand { get; }
|
||||||
public ICommand RefreshTablesCommand { get; }
|
|
||||||
public ICommand QueryCommand { get; }
|
public ICommand QueryCommand { get; }
|
||||||
|
public ICommand ClearFilterCommand { get; }
|
||||||
public ICommand FirstPageCommand { get; }
|
public ICommand FirstPageCommand { get; }
|
||||||
public ICommand PrevPageCommand { get; }
|
public ICommand PrevPageCommand { get; }
|
||||||
public ICommand NextPageCommand { get; }
|
public ICommand NextPageCommand { get; }
|
||||||
public ICommand LastPageCommand { get; }
|
public ICommand LastPageCommand { get; }
|
||||||
public ICommand ExportCsvCommand { get; }
|
public ICommand ExportCsvCommand { get; }
|
||||||
// 双击展开/折叠:与 MonitorView/AutomatedTestingView 共用
|
|
||||||
public ICommand RefreshCommand { get; }
|
public ICommand RefreshCommand { get; }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 私有字段
|
#region 私有字段
|
||||||
// 数据库相对路径:运行目录\SQL\ADP.db(数据库未就绪时容错处理,不抛异常)
|
|
||||||
private static readonly string DbFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SQL");
|
|
||||||
private static readonly string DbPath = Path.Combine(DbFolder, "ADP.db");
|
|
||||||
private static readonly string ConnStr = $"Data Source={DbPath};Version=3;";
|
|
||||||
private bool IsInitiated = false;
|
|
||||||
private IScopedProvider _scope;
|
private IScopedProvider _scope;
|
||||||
private string _testStatus = string.Empty;
|
private string _testStatus = string.Empty;
|
||||||
private ObservableCollection<string> _tableNames = new();
|
private ObservableCollection<string> _monitorNames = new();
|
||||||
private string? _selectedTable;
|
private string? _selectedMonitorName;
|
||||||
private string _whereClause = string.Empty;
|
private ObservableCollection<string> _scopeNames = new(new[] { "全部", "TestCell1", "TestCell2", "TestCell3", "TestCell4", "TestCell5", "TestCell6", "TestCell7", "TestCell8" });
|
||||||
|
private string? _selectedScope = "全部";
|
||||||
|
private DateTime? _selectedStartDate;
|
||||||
|
private DateTime? _selectedEndDate;
|
||||||
private DataTable _resultTable = new();
|
private DataTable _resultTable = new();
|
||||||
private string _statusMessage = "未连接";
|
private string _statusMessage = "未连接";
|
||||||
private int _pageIndex = 1;
|
private int _pageIndex = 1;
|
||||||
private int _pageSize = 50;
|
private int _pageSize = 50;
|
||||||
private long _totalCount;
|
private long _totalCount;
|
||||||
|
private bool IsInitiated = false;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public RecordViewModel(IContainerExtension container) : base(container)
|
public RecordViewModel(IContainerExtension container) : base(container)
|
||||||
{
|
{
|
||||||
_globalInfo = container.Resolve<GlobalInfo>();
|
_globalInfo = container.Resolve<GlobalInfo>();
|
||||||
LoadedCommand = new DelegateCommand(LoadTables);
|
LoadedCommand = new DelegateCommand(LoadFilterOptions);
|
||||||
RefreshTablesCommand = new DelegateCommand(LoadTables);
|
|
||||||
QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); });
|
QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); });
|
||||||
|
ClearFilterCommand = new DelegateCommand(ClearFilter);
|
||||||
FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } });
|
FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } });
|
||||||
PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } });
|
PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } });
|
||||||
NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } });
|
NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } });
|
||||||
@@ -158,105 +176,137 @@ namespace MonitorModule.ViewModels
|
|||||||
_scope?.Dispose();
|
_scope?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 数据库操作
|
#region 筛选条件加载
|
||||||
private SqlSugarClient CreateClient()
|
/// <summary>
|
||||||
|
/// 加载筛选条件:监测项通过反射发现(与 MonitorViewModel 一致),台架从数据库查询。
|
||||||
|
/// </summary>
|
||||||
|
private void LoadFilterOptions()
|
||||||
{
|
{
|
||||||
return new SqlSugarClient(new ConnectionConfig
|
// 监测项:反射发现当前作用域所有带 [Monitorable] 的设备方法
|
||||||
{
|
DiscoverMonitorNames();
|
||||||
DbType = SqlSugar.DbType.Sqlite,
|
|
||||||
ConnectionString = ConnStr,
|
Query();
|
||||||
IsAutoCloseConnection = true,
|
|
||||||
InitKeyType = InitKeyType.Attribute
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 读取数据库中的所有用户表。数据库不存在时不报错,仅在状态栏提示。
|
/// 反射扫描当前作用域 DeviceManager 中所有带 [Monitorable] 特性的设备方法,
|
||||||
|
/// 生成与 MonitorViewModel 完全一致的 DisplayName 列表,填充到下拉框。
|
||||||
|
/// 这样即使数据库还没数据,用户也能看到所有可筛选的监测项。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void LoadTables()
|
private void DiscoverMonitorNames()
|
||||||
{
|
{
|
||||||
|
MonitorNames = new ObservableCollection<string>();
|
||||||
|
|
||||||
|
if (_scope == null) return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(DbFolder)) Directory.CreateDirectory(DbFolder);
|
var deviceManager = _scope.Resolve<DeviceManager>();
|
||||||
if (!File.Exists(DbPath))
|
if (deviceManager?.DeviceMap == null || deviceManager.DeviceMap.Count == 0)
|
||||||
{
|
{
|
||||||
TableNames = new ObservableCollection<string>();
|
StatusMessage = "当前工位无可用设备";
|
||||||
SelectedTable = null;
|
|
||||||
ResultTable = new DataTable();
|
|
||||||
TotalCount = 0;
|
|
||||||
StatusMessage = $"数据库尚未创建:{DbPath}";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var db = CreateClient();
|
// 构建 设备实例 → 硬件指纹 的反向查找表
|
||||||
var tables = db.DbMaintenance.GetTableInfoList(false)
|
var instanceToFp = new Dictionary<object, string>(ReferenceEqualityComparer.Instance);
|
||||||
.Select(t => t.Name)
|
foreach (var poolEntry in _globalInfo.HardwarePool)
|
||||||
.OrderBy(n => n)
|
{
|
||||||
.ToList();
|
var lazy = poolEntry.Value;
|
||||||
|
if (lazy.IsValueCreated && lazy.Value != null)
|
||||||
|
instanceToFp[lazy.Value] = poolEntry.Key;
|
||||||
|
}
|
||||||
|
|
||||||
TableNames = new ObservableCollection<string>(tables);
|
var names = new List<string>();
|
||||||
if (tables.Count == 0)
|
|
||||||
|
foreach (var kvp in deviceManager.DeviceMap)
|
||||||
{
|
{
|
||||||
SelectedTable = null;
|
string deviceName = kvp.Key;
|
||||||
ResultTable = new DataTable();
|
var device = kvp.Value;
|
||||||
TotalCount = 0;
|
var deviceType = device.GetType();
|
||||||
StatusMessage = "数据库已连接,但暂无数据表";
|
|
||||||
}
|
// 只列出在硬件指纹池中注册的设备
|
||||||
else
|
if (!instanceToFp.ContainsKey(device))
|
||||||
{
|
continue;
|
||||||
StatusMessage = $"已连接:{DbPath}({tables.Count} 张表)";
|
|
||||||
if (string.IsNullOrEmpty(SelectedTable) || !tables.Contains(SelectedTable))
|
var methods = deviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||||
|
.Where(m =>
|
||||||
|
{
|
||||||
|
if (m.GetCustomAttribute<MonitorableAttribute>() == null) return false;
|
||||||
|
if (m.ReturnType != typeof(Task<string>)) return false;
|
||||||
|
var parms = m.GetParameters();
|
||||||
|
return parms.Length == 0 ||
|
||||||
|
(parms.Length == 1 && parms[0].ParameterType == typeof(CancellationToken));
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var method in methods)
|
||||||
{
|
{
|
||||||
SelectedTable = tables[0]; // setter 会触发 Query
|
var attr = method.GetCustomAttribute<MonitorableAttribute>();
|
||||||
}
|
string displayName = !string.IsNullOrEmpty(attr?.Description)
|
||||||
else
|
? $"{deviceName}.{attr.Description}"
|
||||||
{
|
: $"{deviceName}.{method.Name}";
|
||||||
Query();
|
names.Add(displayName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
names.Sort();
|
||||||
|
MonitorNames = new ObservableCollection<string>(names);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,$"加载数据表失败:{ex.Message}");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"反射发现监测项失败:{ex.Message}");
|
||||||
StatusMessage = $"加载失败:{ex.Message}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>清除所有筛选条件</summary>
|
||||||
|
private void ClearFilter()
|
||||||
|
{
|
||||||
|
SelectedMonitorName = null;
|
||||||
|
SelectedScope = "全部";
|
||||||
|
SelectedStartDate = null;
|
||||||
|
SelectedEndDate = null;
|
||||||
|
PageIndex = 1;
|
||||||
|
Query();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 查询
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 执行分页查询。WHERE 为空则查询全部。
|
/// 按当前筛选条件分页查询 MonitorValueEntity。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Query()
|
private void Query()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
|
||||||
{
|
|
||||||
ResultTable = new DataTable();
|
|
||||||
TotalCount = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var db = CreateClient();
|
var db = SqlSugarContext.DbContext;
|
||||||
string table = $"[{SelectedTable}]";
|
|
||||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
|
||||||
|
|
||||||
// COUNT 总数
|
// 预计算日期范围,避免在表达式树中访问可空属性
|
||||||
var countSql = $"SELECT COUNT(*) FROM {table} WHERE {where}";
|
DateTime? dateStart = SelectedStartDate?.Date;
|
||||||
TotalCount = db.Ado.GetLong(countSql);
|
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
|
||||||
|
|
||||||
|
var query = db.Queryable<MonitorValueEntity>()
|
||||||
|
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
||||||
|
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
|
||||||
|
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
|
||||||
|
.OrderBy(x => x.CreateTime, OrderByType.Desc);
|
||||||
|
|
||||||
|
TotalCount = query.Count();
|
||||||
|
|
||||||
// 修正越界
|
// 修正越界
|
||||||
if (PageIndex > TotalPages) PageIndex = TotalPages;
|
if (PageIndex > TotalPages) PageIndex = TotalPages;
|
||||||
if (PageIndex < 1) PageIndex = 1;
|
if (PageIndex < 1) PageIndex = 1;
|
||||||
|
|
||||||
int offset = (PageIndex - 1) * PageSize;
|
int skip = (PageIndex - 1) * PageSize;
|
||||||
var dataSql = $"SELECT * FROM {table} WHERE {where} LIMIT {PageSize} OFFSET {offset}";
|
ResultTable = query
|
||||||
ResultTable = db.Ado.GetDataTable(dataSql);
|
.Skip(skip)
|
||||||
|
.Take(PageSize)
|
||||||
|
.ToDataTable();
|
||||||
|
|
||||||
StatusMessage = $"表 [{SelectedTable}] 共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
|
StatusMessage = $"共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify(TestStatus,$"查询失败:{ex.Message}");
|
LoggerHelper.ErrorWithNotify(TestStatus, $"查询失败:{ex.Message}");
|
||||||
StatusMessage = $"查询失败:{ex.Message}";
|
StatusMessage = $"查询失败:{ex.Message}";
|
||||||
ResultTable = new DataTable();
|
ResultTable = new DataTable();
|
||||||
TotalCount = 0;
|
TotalCount = 0;
|
||||||
@@ -266,36 +316,42 @@ namespace MonitorModule.ViewModels
|
|||||||
|
|
||||||
#region 导出 CSV
|
#region 导出 CSV
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 按当前 WHERE 条件导出全部结果到 CSV(不分页)。
|
/// 按当前筛选条件导出全部结果到 CSV(不分页)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ExportCsv()
|
private void ExportCsv()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
|
|
||||||
{
|
|
||||||
StatusMessage = "无可导出数据";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
|
||||||
{
|
|
||||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
|
||||||
FileName = $"{SelectedTable}_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
|
|
||||||
Title = $"导出 [{SelectedTable}] 为 CSV"
|
|
||||||
};
|
|
||||||
if (dlg.ShowDialog() != true) return;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var db = CreateClient();
|
var db = SqlSugarContext.DbContext;
|
||||||
string table = $"[{SelectedTable}]";
|
|
||||||
string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
|
DateTime? dateStart = SelectedStartDate?.Date;
|
||||||
var sql = $"SELECT * FROM {table} WHERE {where}";
|
DateTime? dateEnd = SelectedEndDate?.Date.AddDays(1);
|
||||||
var dt = db.Ado.GetDataTable(sql);
|
|
||||||
|
var query = db.Queryable<MonitorValueEntity>()
|
||||||
|
.WhereIF(!string.IsNullOrEmpty(SelectedMonitorName), x => x.MonitorName == SelectedMonitorName)
|
||||||
|
.WhereIF(!string.IsNullOrEmpty(SelectedScope) && SelectedScope != "全部", x => x.Scope == SelectedScope)
|
||||||
|
.WhereIF(dateStart.HasValue, x => x.CreateTime >= dateStart && x.CreateTime < dateEnd!.Value)
|
||||||
|
.OrderBy(x => x.CreateTime, OrderByType.Desc);
|
||||||
|
|
||||||
|
var dt = query.ToDataTable();
|
||||||
|
if (dt.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
StatusMessage = "无可导出数据";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||||
|
{
|
||||||
|
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||||
|
FileName = $"MonitorData_{DateTime.Now:yyyyMMdd_HHmmss}.csv",
|
||||||
|
Title = "导出监测数据为 CSV"
|
||||||
|
};
|
||||||
|
if (dlg.ShowDialog() != true) return;
|
||||||
|
|
||||||
WriteCsv(dlg.FileName, dt);
|
WriteCsv(dlg.FileName, dt);
|
||||||
|
|
||||||
StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
|
StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
|
||||||
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出监测数据至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -333,7 +389,6 @@ namespace MonitorModule.ViewModels
|
|||||||
if (string.IsNullOrEmpty(TestStatus)) return;
|
if (string.IsNullOrEmpty(TestStatus)) return;
|
||||||
_globalInfo.CurrentScope = TestStatus;
|
_globalInfo.CurrentScope = TestStatus;
|
||||||
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
_eventAggregator.GetEvent<ExpandViewEvent>().Publish(TestStatus);
|
||||||
|
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -341,14 +396,14 @@ namespace MonitorModule.ViewModels
|
|||||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||||
{
|
{
|
||||||
base.OnNavigatedTo(navigationContext);
|
base.OnNavigatedTo(navigationContext);
|
||||||
if (!IsInitiated&&navigationContext.Parameters.ContainsKey("Name"))
|
if (!IsInitiated && navigationContext.Parameters.ContainsKey("Name"))
|
||||||
{
|
{
|
||||||
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
TestStatus = navigationContext.Parameters.GetValue<string>("Name");
|
||||||
_scope = _globalInfo.ScopeDic[TestStatus];
|
_scope = _globalInfo.ScopeDic[TestStatus];
|
||||||
_scopedContext = _scope.Resolve<ScopedContext>();
|
_scopedContext = _scope.Resolve<ScopedContext>();
|
||||||
IsInitiated = true;
|
IsInitiated = true;
|
||||||
}
|
}
|
||||||
LoadTables();
|
LoadFilterOptions();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
110
MonitorModule/Views/Dialogs/ValueLimitView.xaml
Normal file
110
MonitorModule/Views/Dialogs/ValueLimitView.xaml
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<UserControl x:Class="MonitorModule.Views.Dialogs.ValueLimitView"
|
||||||
|
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:local="clr-namespace:MonitorModule.Views.Dialogs"
|
||||||
|
xmlns:oxy="http://oxyplot.org/wpf"
|
||||||
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
Background="White"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
Height="500"
|
||||||
|
Width="900">
|
||||||
|
<prism:Dialog.WindowStyle>
|
||||||
|
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||||
|
TargetType="Window" />
|
||||||
|
</prism:Dialog.WindowStyle>
|
||||||
|
<GroupBox Padding="0,0,0,0"
|
||||||
|
MouseLeftButtonDown="MouseLeftButtonDown">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<Grid Margin="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="上下限设置"
|
||||||
|
Foreground="White"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="5,0,10,0" />
|
||||||
|
<Button Content="关闭"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="150 0 0 0"
|
||||||
|
Width="60"
|
||||||
|
Command="{Binding CloseCommand}"
|
||||||
|
Height="25" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<DataGrid SelectedItem="{Binding SelectValueLimitVM}"
|
||||||
|
SelectionUnit="FullRow"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
IsReadOnly="False"
|
||||||
|
ItemsSource="{Binding ValueLimitList}"
|
||||||
|
Margin="0,0,0,10">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="信号名"
|
||||||
|
Binding="{Binding SignalName}"
|
||||||
|
Width="auto" />
|
||||||
|
<DataGridTextColumn Header="上限"
|
||||||
|
Binding="{Binding Upper, Mode=TwoWay}"
|
||||||
|
Width="auto" />
|
||||||
|
<DataGridTextColumn Header="下限"
|
||||||
|
Binding="{Binding Lower, Mode=TwoWay}"
|
||||||
|
Width="auto" />
|
||||||
|
<DataGridTextColumn Header="上极限"
|
||||||
|
Binding="{Binding UpperExtreme, Mode=TwoWay}"
|
||||||
|
Width="auto" />
|
||||||
|
<DataGridTextColumn Header="下极限"
|
||||||
|
Binding="{Binding LowerExtreme, Mode=TwoWay}"
|
||||||
|
Width="auto" />
|
||||||
|
<DataGridTextColumn Header="报警状态"
|
||||||
|
Binding="{Binding AlarmSatus}"
|
||||||
|
Width="auto" />
|
||||||
|
|
||||||
|
</DataGrid.Columns>
|
||||||
|
<!--<DataGrid.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem Header="删除"
|
||||||
|
Foreground="Red"
|
||||||
|
Command="{Binding DeleteCommand}" />
|
||||||
|
</ContextMenu>
|
||||||
|
</DataGrid.ContextMenu>-->
|
||||||
|
</DataGrid>
|
||||||
|
<!--<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Label Content="待添加监控值"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ComboBox ItemsSource="{Binding DeviceSingleList}"
|
||||||
|
SelectedItem="{Binding SelectCurve}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
MinWidth="130"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<Button Content="添加"
|
||||||
|
Margin="10,0,10,0"
|
||||||
|
Command="{Binding SelectSignalCommand}" />
|
||||||
|
</StackPanel>-->
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Button Content="保存"
|
||||||
|
Width="80"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
Command="{Binding SaveCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
</UserControl>
|
||||||
36
MonitorModule/Views/Dialogs/ValueLimitView.xaml.cs
Normal file
36
MonitorModule/Views/Dialogs/ValueLimitView.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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 MonitorModule.Views.Dialogs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ValueLimitView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class ValueLimitView : UserControl
|
||||||
|
{
|
||||||
|
public ValueLimitView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
{
|
||||||
|
Window.GetWindow(this)?.DragMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,17 +16,14 @@
|
|||||||
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
<!-- 外层 Border:装 MouseDoubleClickBehavior 实现九宫格双击展开 -->
|
|
||||||
<Border Background="#F5F7FA">
|
<Border Background="#F5F7FA">
|
||||||
<i:Interaction.Behaviors>
|
<i:Interaction.Behaviors>
|
||||||
<b:MouseDoubleClickBehavior
|
<b:MouseDoubleClickBehavior
|
||||||
Command="{Binding DataContext.RefreshCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
Command="{Binding DataContext.RefreshCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
|
||||||
</i:Interaction.Behaviors>
|
</i:Interaction.Behaviors>
|
||||||
|
|
||||||
<!-- 给最外层 Grid 命名,方便子控件 DataTrigger 监听 ActualWidth -->
|
|
||||||
<Grid x:Name="RootGrid" Margin="8">
|
<Grid x:Name="RootGrid" Margin="8">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<!-- Row0 标题:窄宽时折叠为 0 高度 -->
|
|
||||||
<RowDefinition>
|
<RowDefinition>
|
||||||
<RowDefinition.Style>
|
<RowDefinition.Style>
|
||||||
<Style TargetType="RowDefinition">
|
<Style TargetType="RowDefinition">
|
||||||
@@ -39,7 +36,6 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</RowDefinition.Style>
|
</RowDefinition.Style>
|
||||||
</RowDefinition>
|
</RowDefinition>
|
||||||
<!-- Row1 工具栏:窄宽时折叠为 0 -->
|
|
||||||
<RowDefinition>
|
<RowDefinition>
|
||||||
<RowDefinition.Style>
|
<RowDefinition.Style>
|
||||||
<Style TargetType="RowDefinition">
|
<Style TargetType="RowDefinition">
|
||||||
@@ -52,9 +48,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</RowDefinition.Style>
|
</RowDefinition.Style>
|
||||||
</RowDefinition>
|
</RowDefinition>
|
||||||
<!-- Row2 主体(图表区) -->
|
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
<!-- Row3 状态栏:窄宽时折叠为 0 -->
|
|
||||||
<RowDefinition>
|
<RowDefinition>
|
||||||
<RowDefinition.Style>
|
<RowDefinition.Style>
|
||||||
<Style TargetType="RowDefinition">
|
<Style TargetType="RowDefinition">
|
||||||
@@ -73,38 +67,19 @@
|
|||||||
<TextBlock Grid.Row="0"
|
<TextBlock Grid.Row="0"
|
||||||
Text="{Binding TestStatus, StringFormat=监控界面 - {0}}"
|
Text="{Binding TestStatus, StringFormat=监控界面 - {0}}"
|
||||||
FontSize="20" FontWeight="Bold"
|
FontSize="20" FontWeight="Bold"
|
||||||
Margin="4,0,0,8">
|
Margin="4,0,0,8"/>
|
||||||
<TextBlock.Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</TextBlock.Style>
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<!-- ====== Row1:工具栏 ====== -->
|
<!-- ====== Row1:工具栏 ====== -->
|
||||||
<Border Grid.Row="1"
|
<Border Grid.Row="1"
|
||||||
Background="White"
|
Background="White"
|
||||||
BorderBrush="#DDD" BorderThickness="1"
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
CornerRadius="4" Padding="8" Margin="0,0,0,6">
|
CornerRadius="4" Padding="8" Margin="0,0,0,6">
|
||||||
<Border.Style>
|
|
||||||
<Style TargetType="Border">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Border.Style>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Button Content="+ 添加信号"
|
<Button Content="+ 添加监测项"
|
||||||
Command="{Binding AddSignalCommand}"
|
Command="{Binding AddChannelCommand}"
|
||||||
Padding="12,4"/>
|
Padding="12,4"/>
|
||||||
<Button Content="− 删除信号"
|
<Button Content="− 删除监测项"
|
||||||
Command="{Binding DeleteSignalCommand}"
|
Command="{Binding DeleteChannelCommand}"
|
||||||
Padding="12,4" Margin="6,0,0,0"/>
|
Padding="12,4" Margin="6,0,0,0"/>
|
||||||
<Button Content="↺ 复原视图"
|
<Button Content="↺ 复原视图"
|
||||||
Command="{Binding ResetViewCommand}"
|
Command="{Binding ResetViewCommand}"
|
||||||
@@ -114,17 +89,20 @@
|
|||||||
Command="{Binding RefreshDataCommand}"
|
Command="{Binding RefreshDataCommand}"
|
||||||
Padding="12,4" Margin="6,0,0,0"
|
Padding="12,4" Margin="6,0,0,0"
|
||||||
ToolTip="重新绘制图表"/>
|
ToolTip="重新绘制图表"/>
|
||||||
|
<Button Content=" 保存"
|
||||||
|
Command="{Binding SaveCommand}"
|
||||||
|
Padding="12,4" Margin="6,0,0,0"
|
||||||
|
ToolTip="保存到配置文件"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- ====== Row2:主体(左信号列表 + 右图表)====== -->
|
<!-- ====== Row2:主体 ====== -->
|
||||||
<Grid Grid.Row="2">
|
<Grid Grid.Row="2">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<!-- Col0 信号列表:窄宽时折叠为 0 宽 -->
|
|
||||||
<ColumnDefinition>
|
<ColumnDefinition>
|
||||||
<ColumnDefinition.Style>
|
<ColumnDefinition.Style>
|
||||||
<Style TargetType="ColumnDefinition">
|
<Style TargetType="ColumnDefinition">
|
||||||
<Setter Property="Width" Value="220"/>
|
<Setter Property="Width" Value="280"/>
|
||||||
<Style.Triggers>
|
<Style.Triggers>
|
||||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||||
<Setter Property="Width" Value="0"/>
|
<Setter Property="Width" Value="0"/>
|
||||||
@@ -133,7 +111,7 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</ColumnDefinition.Style>
|
</ColumnDefinition.Style>
|
||||||
</ColumnDefinition>
|
</ColumnDefinition>
|
||||||
<!-- Col1 拖拽条:窄宽时折叠为 0 宽 -->
|
|
||||||
<ColumnDefinition>
|
<ColumnDefinition>
|
||||||
<ColumnDefinition.Style>
|
<ColumnDefinition.Style>
|
||||||
<Style TargetType="ColumnDefinition">
|
<Style TargetType="ColumnDefinition">
|
||||||
@@ -149,61 +127,84 @@
|
|||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 左:信号列表(窄宽时整体隐藏) -->
|
<!-- 左侧:可用方法列表 + 已添加监测通道列表 -->
|
||||||
<Border Grid.Column="0"
|
<Border Grid.Column="0"
|
||||||
Background="White"
|
Background="White"
|
||||||
BorderBrush="#DDD" BorderThickness="1"
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
CornerRadius="4">
|
CornerRadius="4">
|
||||||
<Border.Style>
|
<Grid>
|
||||||
<Style TargetType="Border">
|
<Grid.RowDefinitions>
|
||||||
<Style.Triggers>
|
<RowDefinition Height="Auto"/>
|
||||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
<RowDefinition Height="*"/>
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
<RowDefinition Height="Auto"/>
|
||||||
</DataTrigger>
|
<RowDefinition Height="*"/>
|
||||||
</Style.Triggers>
|
<RowDefinition Height="Auto"/>
|
||||||
</Style>
|
</Grid.RowDefinitions>
|
||||||
</Border.Style>
|
|
||||||
<DockPanel>
|
<!-- 可用方法列表 -->
|
||||||
<Border DockPanel.Dock="Top"
|
<Border Grid.Row="0" Background="#ECEFF4" Padding="8,4">
|
||||||
Background="#ECEFF4"
|
<TextBlock Text="可用监测项(选择后点击添加)" FontWeight="Bold"/>
|
||||||
Padding="8,4">
|
|
||||||
<TextBlock Text="信号列表" FontWeight="Bold"/>
|
|
||||||
</Border>
|
</Border>
|
||||||
<ListBox ItemsSource="{Binding Signals}"
|
<ListBox Grid.Row="1"
|
||||||
SelectedItem="{Binding SelectedSignal}"
|
ItemsSource="{Binding AvailableMethods}"
|
||||||
|
SelectedItem="{Binding SelectedAvailableMethod}"
|
||||||
BorderThickness="0">
|
BorderThickness="0">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal" Margin="2">
|
<TextBlock Text="{Binding DisplayName}" Margin="2"/>
|
||||||
<Border Width="12" Height="12"
|
</DataTemplate>
|
||||||
CornerRadius="2"
|
</ListBox.ItemTemplate>
|
||||||
Background="#888"
|
</ListBox>
|
||||||
VerticalAlignment="Center"/>
|
|
||||||
<TextBlock Text="{Binding Name}"
|
<!-- 分隔 -->
|
||||||
Margin="6,0,0,0"
|
<Border Grid.Row="2" Background="#ECEFF4" Padding="8,4">
|
||||||
VerticalAlignment="Center"/>
|
<TextBlock Text="已添加监测通道" FontWeight="Bold"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 监测通道列表(带显示/隐藏CheckBox) -->
|
||||||
|
<ListBox Grid.Row="3"
|
||||||
|
ItemsSource="{Binding Channels}"
|
||||||
|
SelectedItem="{Binding SelectedChannel}"
|
||||||
|
BorderThickness="0">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Margin="2">
|
||||||
|
<DockPanel>
|
||||||
|
<!-- 显示/隐藏复选框 -->
|
||||||
|
<CheckBox IsChecked="{Binding IsDisplayed}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ToolTip="勾选=在图表上显示,取消=仅记录不显示"/>
|
||||||
|
<!-- 颜色色块 -->
|
||||||
|
<Border Width="10" Height="10"
|
||||||
|
CornerRadius="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="4,0,6,0">
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush Color="SteelBlue"/>
|
||||||
|
</Border.Background>
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="{Binding DisplayName}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
</DockPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
</DockPanel>
|
|
||||||
|
<!-- 状态 -->
|
||||||
|
<TextBlock Grid.Row="4"
|
||||||
|
Text="{Binding StatusMessage}"
|
||||||
|
FontSize="11" Foreground="#666"
|
||||||
|
Margin="4,2" TextWrapping="Wrap"/>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<GridSplitter Grid.Column="1"
|
<GridSplitter Grid.Column="1"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="Transparent">
|
Background="Transparent"/>
|
||||||
<GridSplitter.Style>
|
|
||||||
<Style TargetType="GridSplitter">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</GridSplitter.Style>
|
|
||||||
</GridSplitter>
|
|
||||||
|
|
||||||
<!-- 右:OxyPlot 图表(核心,始终显示) -->
|
<!-- 右侧:OxyPlot 图表 -->
|
||||||
<Border Grid.Column="2"
|
<Border Grid.Column="2"
|
||||||
Background="White"
|
Background="White"
|
||||||
BorderBrush="#DDD" BorderThickness="1"
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
@@ -218,15 +219,6 @@
|
|||||||
Background="#ECEFF4"
|
Background="#ECEFF4"
|
||||||
Padding="8,4" Margin="0,6,0,0"
|
Padding="8,4" Margin="0,6,0,0"
|
||||||
CornerRadius="2">
|
CornerRadius="2">
|
||||||
<Border.Style>
|
|
||||||
<Style TargetType="Border">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
|
||||||
<Setter Property="Visibility" Value="Collapsed"/>
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Border.Style>
|
|
||||||
<TextBlock Text="{Binding StatusMessage}"
|
<TextBlock Text="{Binding StatusMessage}"
|
||||||
Foreground="#444"
|
Foreground="#444"
|
||||||
FontSize="12"/>
|
FontSize="12"/>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
FontSize="20" FontWeight="Bold"
|
FontSize="20" FontWeight="Bold"
|
||||||
Margin="4,0,0,8"/>
|
Margin="4,0,0,8"/>
|
||||||
|
|
||||||
<!-- 工具栏:表选择 / WHERE 条件 / 查询 / 刷新表 / 导出 -->
|
<!-- 工具栏:监测项筛选 / 台架筛选 / 日期筛选 / 查询 / 清除 / 导出 -->
|
||||||
<Border Grid.Row="1"
|
<Border Grid.Row="1"
|
||||||
Background="White"
|
Background="White"
|
||||||
BorderBrush="#DDD" BorderThickness="1"
|
BorderBrush="#DDD" BorderThickness="1"
|
||||||
@@ -47,57 +47,77 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="180"/>
|
<ColumnDefinition Width="200"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="150"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 监测项 -->
|
||||||
<TextBlock Grid.Column="0"
|
<TextBlock Grid.Column="0"
|
||||||
Text="表:"
|
Text="监测项:"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="0,0,4,0"/>
|
Margin="0,0,4,0"/>
|
||||||
<ComboBox Grid.Column="1"
|
<ComboBox Grid.Column="1"
|
||||||
materialDesign:HintAssist.Hint="数据表"
|
materialDesign:HintAssist.Hint="全部"
|
||||||
ItemsSource="{Binding TableNames}"
|
ItemsSource="{Binding MonitorNames}"
|
||||||
SelectedItem="{Binding SelectedTable}"
|
SelectedItem="{Binding SelectedMonitorName}"
|
||||||
|
IsEditable="True"
|
||||||
VerticalAlignment="Center"/>
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- 台架 -->
|
||||||
<TextBlock Grid.Column="2"
|
<TextBlock Grid.Column="2"
|
||||||
Text="WHERE:"
|
Text="台架:"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Margin="12,0,4,0"/>
|
Margin="12,0,4,0"/>
|
||||||
<TextBox Grid.Column="3"
|
<ComboBox Grid.Column="3"
|
||||||
Text="{Binding WhereClause, UpdateSourceTrigger=PropertyChanged}"
|
materialDesign:HintAssist.Hint="全部"
|
||||||
VerticalAlignment="Center"
|
ItemsSource="{Binding ScopeNames}"
|
||||||
materialDesign:HintAssist.Hint="条件筛选"
|
SelectedItem="{Binding SelectedScope}"
|
||||||
ToolTip="例如:Status='OK' AND Id>10。留空表示查询全部">
|
IsEditable="True"
|
||||||
<TextBox.InputBindings>
|
VerticalAlignment="Center"/>
|
||||||
<KeyBinding Key="Enter" Command="{Binding QueryCommand}"/>
|
|
||||||
</TextBox.InputBindings>
|
|
||||||
</TextBox>
|
|
||||||
|
|
||||||
<Button Grid.Column="4"
|
<!-- 日期 -->
|
||||||
|
<TextBlock Grid.Column="4"
|
||||||
|
Text="日期:"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="12,0,4,0"/>
|
||||||
|
<DatePicker Grid.Column="5"
|
||||||
|
SelectedDate="{Binding SelectedStartDate}"
|
||||||
|
materialDesign:HintAssist.Hint="开始时间"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<DatePicker Grid.Column="6"
|
||||||
|
Margin=" 5 0 0 0"
|
||||||
|
SelectedDate="{Binding SelectedEndDate}"
|
||||||
|
materialDesign:HintAssist.Hint="结束时间"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<Button Grid.Column="7"
|
||||||
Content="查询"
|
Content="查询"
|
||||||
Command="{Binding QueryCommand}"
|
Command="{Binding QueryCommand}"
|
||||||
Padding="12,4" Margin="8,0,0,0"/>
|
Padding="12,4" Margin="12,0,0,0"/>
|
||||||
<Button Grid.Column="5"
|
<Button Grid.Column="8"
|
||||||
Content="刷新表"
|
Content="清除"
|
||||||
Command="{Binding RefreshTablesCommand}"
|
Command="{Binding ClearFilterCommand}"
|
||||||
Padding="12,4" Margin="6,0,0,0"/>
|
Padding="12,4" Margin="6,0,0,0"/>
|
||||||
<Button Grid.Column="6"
|
<Button Grid.Column="9"
|
||||||
Content="导出 CSV"
|
Content="导出 CSV"
|
||||||
Command="{Binding ExportCsvCommand}"
|
Command="{Binding ExportCsvCommand}"
|
||||||
Padding="12,4" Margin="6,0,0,0"/>
|
Padding="12,4" Margin="6,0,0,0"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- DataGrid:自动生成列,绑 DataTable.DefaultView 直显示 -->
|
<!-- DataGrid:显式列定义,隐藏 Id / IsDel -->
|
||||||
<DataGrid Grid.Row="2"
|
<DataGrid Grid.Row="2"
|
||||||
ItemsSource="{Binding ResultTable}"
|
ItemsSource="{Binding ResultTable}"
|
||||||
AutoGenerateColumns="True"
|
AutoGenerateColumns="False"
|
||||||
IsReadOnly="True"
|
IsReadOnly="True"
|
||||||
CanUserAddRows="False"
|
CanUserAddRows="False"
|
||||||
CanUserDeleteRows="False"
|
CanUserDeleteRows="False"
|
||||||
@@ -106,7 +126,14 @@
|
|||||||
AlternatingRowBackground="#F9FAFB"
|
AlternatingRowBackground="#F9FAFB"
|
||||||
RowBackground="White"
|
RowBackground="White"
|
||||||
HorizontalScrollBarVisibility="Auto"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Auto"/>
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="监测项" Binding="{Binding MonitorName}" Width="*"/>
|
||||||
|
<DataGridTextColumn Header="监测值" Binding="{Binding MonitorValue}" Width="120"/>
|
||||||
|
<DataGridTextColumn Header="台架" Binding="{Binding Scope}" Width="120"/>
|
||||||
|
<DataGridTextColumn Header="记录时间" Binding="{Binding CreateTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="180"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
|
||||||
<!-- 分页控件 -->
|
<!-- 分页控件 -->
|
||||||
<Border Grid.Row="3"
|
<Border Grid.Row="3"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace ORM
|
|||||||
|
|
||||||
// 拼接数据库文件路径
|
// 拼接数据库文件路径
|
||||||
string DBPath = Path.Combine(folder, "SQL.db");
|
string DBPath = Path.Combine(folder, "SQL.db");
|
||||||
DbConnectionString = $"Data Source={DBPath};Version=3;";
|
DbConnectionString = $"Data Source={DBPath};";
|
||||||
}
|
}
|
||||||
public static void InitMySql(string Server, int Port, string Database, string Uid,string Pwd)
|
public static void InitMySql(string Server, int Port, string Database, string Uid,string Pwd)
|
||||||
{
|
{
|
||||||
|
|||||||
71
Service/Implement/MonitorValueService.cs
Normal file
71
Service/Implement/MonitorValueService.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using ORM;
|
||||||
|
using Service.Interface;
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Implement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 监测数值记录服务实现
|
||||||
|
/// </summary>
|
||||||
|
public class MonitorValueService : BaseService<MonitorValueEntity>, IMonitorValueService
|
||||||
|
{
|
||||||
|
// 继承父类构造函数,注入 SqlSugar 仓储
|
||||||
|
public MonitorValueService(SqlSugarRepository<MonitorValueEntity> repository) : base(repository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量插入监测数据
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<bool>> InsertRangeAsync(List<MonitorValueEntity> entities)
|
||||||
|
{
|
||||||
|
if (entities == null || entities.Count == 0)
|
||||||
|
return Result<bool>.Success(true);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 使用 SqlSugar 的批量插入,底层会转换为 BulkCopy 或是批量 SQL,速度极快
|
||||||
|
var result = await _repository.Context.Insertable(entities).ExecuteCommandAsync();
|
||||||
|
return Result<bool>.Success(result > 0);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<bool>.Error("批量插入监测数据失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据监测通道名称,分页查询历史记录
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<List<MonitorValueEntity>>> GetPagedByNameAsync(string monitorName, int pageIndex, int pageSize, RefAsync<int> total)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = _repository.Entities;
|
||||||
|
|
||||||
|
// 如果传了名字则按名字过滤
|
||||||
|
if (!string.IsNullOrEmpty(monitorName))
|
||||||
|
{
|
||||||
|
query = query.Where(x => x.MonitorName == monitorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = await query
|
||||||
|
.OrderByDescending(d => d.CreateTime) // 监测项通常优先看最新的数据
|
||||||
|
.ToPageListAsync(pageIndex, pageSize, total);
|
||||||
|
|
||||||
|
// 计算总页数
|
||||||
|
total.Value = (int)Math.Ceiling((double)total.Value / pageSize);
|
||||||
|
return Result<List<MonitorValueEntity>>.Success(list);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<MonitorValueEntity>>.Error($"根据名称 [{monitorName}] 分页查询数据失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
Service/Implement/TestReportService.cs
Normal file
109
Service/Implement/TestReportService.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using Model.Models;
|
||||||
|
using ORM;
|
||||||
|
using Service.Interface;
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static System.Formats.Asn1.AsnWriter;
|
||||||
|
|
||||||
|
namespace Service.Implement
|
||||||
|
{
|
||||||
|
public class TestReportService : BaseService<TestReportEntity>, ITestReportService
|
||||||
|
{
|
||||||
|
public TestReportService(SqlSugarRepository<TestReportEntity> repository) : base(repository)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据 TestRoundId 获取符合条件的测试步骤
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<List<TestReportEntity>>> GetByTestRoundIdAsync(Guid testRoundId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var list = await _repository.Entities
|
||||||
|
.Where(x => x.TestRoundId == testRoundId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Result<List<TestReportEntity>>.Success(list);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<TestReportEntity>>.Error("根据 TestRoundId 查询数据失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据 TestRoundId 分组,获取唯一记录并按分组统计最大与最小时间,
|
||||||
|
/// 支持按日期范围与台架名称筛选。
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<IList<TestReportModel>>> GetFilterList(DateTime? startTime = null, DateTime? endTime = null, string? scope = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 使用 SqlSugar 的 GroupBy 查询,聚合最大/最小时间
|
||||||
|
// 推荐采用这种按组投影的方式,将分组计算交由数据库处理,性能最佳
|
||||||
|
var query = _repository.Context.Queryable<TestReportEntity>();
|
||||||
|
|
||||||
|
// 日期范围筛选
|
||||||
|
if (startTime.HasValue)
|
||||||
|
query = query.Where(x => x.CreateTime >= startTime.Value);
|
||||||
|
if (endTime.HasValue)
|
||||||
|
query = query.Where(x => x.CreateTime <= endTime.Value);
|
||||||
|
|
||||||
|
// 台架名称筛选
|
||||||
|
if (!string.IsNullOrEmpty(scope))
|
||||||
|
query = query.Where(x => x.Scope == scope);
|
||||||
|
|
||||||
|
var list = await query
|
||||||
|
.GroupBy(x => x.TestRoundId)
|
||||||
|
.Select(x => new TestReportModel
|
||||||
|
{
|
||||||
|
TestRoundId = x.TestRoundId,
|
||||||
|
StartTime = SqlFunc.AggregateMin(x.CreateTime),
|
||||||
|
EndTime = SqlFunc.AggregateMax(x.CreateTime),
|
||||||
|
Scope = x.Scope,
|
||||||
|
FileName = x.FileName,
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Result<IList<TestReportModel>>.Success(list);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<IList<TestReportModel>>.Error("获取分组过滤列表失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 根据日期和台架名称筛选获取完整的测试步骤实体列表(无分组,用于导出)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<List<TestReportEntity>>> GetEntitiesByFilter(DateTime? startTime = null, DateTime? endTime = null, string? scope = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = _repository.Context.Queryable<TestReportEntity>();
|
||||||
|
|
||||||
|
if (startTime.HasValue)
|
||||||
|
query = query.Where(x => x.CreateTime >= startTime.Value);
|
||||||
|
if (endTime.HasValue)
|
||||||
|
query = query.Where(x => x.CreateTime <= endTime.Value);
|
||||||
|
if (!string.IsNullOrEmpty(scope))
|
||||||
|
query = query.Where(x => x.Scope == scope);
|
||||||
|
|
||||||
|
var list = await query
|
||||||
|
.OrderBy(x => x.CreateTime)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Result<List<TestReportEntity>>.Success(list);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<TestReportEntity>>.Error("获取导出数据失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Service/Interface/IMonitorValueService.cs
Normal file
32
Service/Interface/IMonitorValueService.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Interface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 监测数值记录服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IMonitorValueService : IBaseService<MonitorValueEntity>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 批量插入监测数据(工控高频采样推荐使用,性能远高于单条循环插入)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entities">实体集合</param>
|
||||||
|
/// <returns>返回操作是否成功的 Result</returns>
|
||||||
|
Task<Result<bool>> InsertRangeAsync(List<MonitorValueEntity> entities);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据监测通道名称,分页查询历史记录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="monitorName">通道名称(例如:台架1.读取主轴温度)</param>
|
||||||
|
/// <param name="pageIndex">页码</param>
|
||||||
|
/// <param name="pageSize">每页大小</param>
|
||||||
|
/// <param name="total">总条数输出</param>
|
||||||
|
/// <returns>分页数据结果</returns>
|
||||||
|
Task<Result<List<MonitorValueEntity>>> GetPagedByNameAsync(string monitorName, int pageIndex, int pageSize, RefAsync<int> total);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
Service/Interface/ITestReportService.cs
Normal file
37
Service/Interface/ITestReportService.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using Model.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Service.Interface
|
||||||
|
{
|
||||||
|
public interface ITestReportService:IBaseService<TestReportEntity>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 根据TestRoundId获符合的测试步骤
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="TestRoundId">TestRoundId</param>
|
||||||
|
/// <returns>返回符合的TestReportEntity列表</returns>
|
||||||
|
Task<Result<List<TestReportEntity>>> GetByTestRoundIdAsync(Guid TestRoundId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据 TestRoundId 分组,获取唯一记录并按分组统计最大与最小时间
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startTime">开始日期筛选(可选)</param>
|
||||||
|
/// <param name="endTime">结束日期筛选(可选)</param>
|
||||||
|
/// <param name="scope">台架名称筛选(可选)</param>
|
||||||
|
Task<Result<IList<TestReportModel>>> GetFilterList(DateTime? startTime = null, DateTime? endTime = null, string? scope = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据日期和台架名称筛选获取完整的测试步骤实体列表(无分组,用于导出)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startTime">开始日期筛选(可选)</param>
|
||||||
|
/// <param name="endTime">结束日期筛选(可选)</param>
|
||||||
|
/// <param name="scope">台架名称筛选(可选)</param>
|
||||||
|
Task<Result<List<TestReportEntity>>> GetEntitiesByFilter(DateTime? startTime = null, DateTime? endTime = null, string? scope = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ namespace SettingModule
|
|||||||
// 设备连接配置弹窗
|
// 设备连接配置弹窗
|
||||||
containerRegistry.RegisterDialog<TCPConfigView>("TCPConfig");
|
containerRegistry.RegisterDialog<TCPConfigView>("TCPConfig");
|
||||||
containerRegistry.RegisterDialog<SerialPortConfigView>("SerialPortConfig");
|
containerRegistry.RegisterDialog<SerialPortConfigView>("SerialPortConfig");
|
||||||
|
containerRegistry.RegisterDialog<CANConfigView>("CANConfig");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
134
SettingModule/ViewModels/Dialogs/CANConfigViewModel.cs
Normal file
134
SettingModule/ViewModels/Dialogs/CANConfigViewModel.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.PubEvent;
|
||||||
|
using UIShare.UIViewModel;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace SettingModule.ViewModels.Dialogs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CAN 连接配置对话框 VM。
|
||||||
|
/// 通过 DialogParameters 接收宿主 DeviceInfoVM;保存时把副本写回宿主。
|
||||||
|
/// 参数对应 ZLGCANFD 构造函数 + 初始化并启动通道 方法。
|
||||||
|
/// </summary>
|
||||||
|
public class CANConfigViewModel : DialogViewModelBase
|
||||||
|
{
|
||||||
|
#region 属性
|
||||||
|
|
||||||
|
private string _title = "CAN 连接配置";
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get => _title;
|
||||||
|
set => SetProperty(ref _title, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>编辑用的副本,取消时不会污染宿主对象。</summary>
|
||||||
|
private CANConfigVM _config = new();
|
||||||
|
public CANConfigVM Config
|
||||||
|
{
|
||||||
|
get => _config;
|
||||||
|
set => SetProperty(ref _config, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>常用仲裁域波特率。</summary>
|
||||||
|
public ObservableCollection<string> CommonABitBauds { get; } = new()
|
||||||
|
{
|
||||||
|
"250000", "500000", "1000000"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>常用数据域波特率。</summary>
|
||||||
|
public ObservableCollection<string> CommonDBitBauds { get; } = new()
|
||||||
|
{
|
||||||
|
"1000000", "2000000", "4000000", "5000000", "8000000"
|
||||||
|
};
|
||||||
|
|
||||||
|
private string _errorMessage = string.Empty;
|
||||||
|
public string ErrorMessage
|
||||||
|
{
|
||||||
|
get => _errorMessage;
|
||||||
|
set => SetProperty(ref _errorMessage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
public ICommand SaveCommand { get; }
|
||||||
|
public ICommand CancelCommand { get; }
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// 用于保存时把副本回写到原对象
|
||||||
|
private DeviceInfoVM? _hostDevice;
|
||||||
|
|
||||||
|
public CANConfigViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
SaveCommand = new DelegateCommand(OnSave);
|
||||||
|
CancelCommand = new DelegateCommand(OnCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Validate(out string error)
|
||||||
|
{
|
||||||
|
error = string.Empty;
|
||||||
|
if (Config.DeviceType == 0)
|
||||||
|
{
|
||||||
|
error = "设备类型号不能为 0";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(Config.ABitBaud))
|
||||||
|
{
|
||||||
|
error = "仲裁域波特率不能为空";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(Config.DBitBaud))
|
||||||
|
{
|
||||||
|
error = "数据域波特率不能为空";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSave()
|
||||||
|
{
|
||||||
|
if (!Validate(out var error))
|
||||||
|
{
|
||||||
|
ErrorMessage = error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ErrorMessage = string.Empty;
|
||||||
|
|
||||||
|
// 把副本写回宿主
|
||||||
|
if (_hostDevice != null)
|
||||||
|
{
|
||||||
|
_hostDevice.CANConfig ??= new CANConfigVM();
|
||||||
|
Config.CopyTo(_hostDevice.CANConfig);
|
||||||
|
_hostDevice.ConnectionType = "CAN";
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestClose.Invoke(ButtonResult.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancel() => RequestClose.Invoke(ButtonResult.Cancel);
|
||||||
|
|
||||||
|
#region Prism Dialog 规范
|
||||||
|
public override void OnDialogOpened(IDialogParameters parameters)
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
|
||||||
|
|
||||||
|
if (parameters.ContainsKey("Device"))
|
||||||
|
{
|
||||||
|
_hostDevice = parameters.GetValue<DeviceInfoVM>("Device");
|
||||||
|
Title = $"CAN 连接配置 - {_hostDevice?.DeviceName}";
|
||||||
|
Config = new CANConfigVM(_hostDevice?.CANConfig);
|
||||||
|
}
|
||||||
|
else if (parameters.ContainsKey("Config"))
|
||||||
|
{
|
||||||
|
Config = new CANConfigVM(parameters.GetValue<CANConfigVM>("Config"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDialogClosed()
|
||||||
|
{
|
||||||
|
_eventAggregator.GetEvent<OverlayEvent>().Publish(false);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,10 +59,9 @@ namespace SettingModule.ViewModels
|
|||||||
|
|
||||||
public ObservableCollection<string> ConnectionTypes { get; } = new()
|
public ObservableCollection<string> ConnectionTypes { get; } = new()
|
||||||
{
|
{
|
||||||
"None", "Tcp", "Serial"
|
"None", "Tcp", "Serial", "CAN"
|
||||||
};
|
};
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 命令
|
#region 命令
|
||||||
public ICommand RefreshCommand { get; }
|
public ICommand RefreshCommand { get; }
|
||||||
public ICommand SaveCommand { get; }
|
public ICommand SaveCommand { get; }
|
||||||
@@ -165,6 +164,7 @@ namespace SettingModule.ViewModels
|
|||||||
{
|
{
|
||||||
"Tcp" => "TCPConfig",
|
"Tcp" => "TCPConfig",
|
||||||
"Serial" => "SerialPortConfig",
|
"Serial" => "SerialPortConfig",
|
||||||
|
"CAN" => "CANConfig",
|
||||||
_ => string.Empty
|
_ => string.Empty
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -200,6 +200,7 @@ namespace SettingModule.ViewModels
|
|||||||
_scope = _globalInfo.ScopeDic[TestStatus];
|
_scope = _globalInfo.ScopeDic[TestStatus];
|
||||||
_scopedContext = _globalInfo.ContextDic[TestStatus];
|
_scopedContext = _globalInfo.ContextDic[TestStatus];
|
||||||
SystemConfig = _scope.Resolve<SystemConfig>();
|
SystemConfig = _scope.Resolve<SystemConfig>();
|
||||||
|
ConfigService.EnsureDefaultCanDevice(SystemConfig);
|
||||||
if (DeviceList != null && DeviceList.Count > 0)
|
if (DeviceList != null && DeviceList.Count > 0)
|
||||||
{
|
{
|
||||||
SelectedDevice = DeviceList[0];
|
SelectedDevice = DeviceList[0];
|
||||||
|
|||||||
131
SettingModule/Views/Dialogs/CANConfigView.xaml
Normal file
131
SettingModule/Views/Dialogs/CANConfigView.xaml
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<UserControl x:Class="SettingModule.Views.Dialogs.CANConfigView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:SettingModule.Views.Dialogs"
|
||||||
|
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||||
|
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||||
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
Background="White"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
Width="440"
|
||||||
|
Height="360">
|
||||||
|
<prism:Dialog.WindowStyle>
|
||||||
|
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||||
|
TargetType="Window" />
|
||||||
|
</prism:Dialog.WindowStyle>
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:StringToVisibilityConverter x:Key="StringToVisibility"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<GroupBox Padding="12,8,12,8"
|
||||||
|
helpers:WindowDragHelper.EnableWindowDrag="True">
|
||||||
|
<GroupBox.Header>
|
||||||
|
<Grid Margin="0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Text="{Binding Title}"
|
||||||
|
Foreground="White"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="5,0,10,0" />
|
||||||
|
</Grid>
|
||||||
|
</GroupBox.Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 表单 -->
|
||||||
|
<Grid Grid.Row="0" Margin="0,4,0,0">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="120"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 设备类型号 -->
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||||
|
Text="设备类型号:"
|
||||||
|
VerticalAlignment="Center" Margin="0,6"/>
|
||||||
|
<TextBox Grid.Row="0" Grid.Column="1" Margin="0,6"
|
||||||
|
materialDesign:HintAssist.Hint="43 = USBCANFD-400U"
|
||||||
|
Text="{Binding Config.DeviceType, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
|
||||||
|
<!-- 设备索引 -->
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||||
|
Text="设备索引:"
|
||||||
|
VerticalAlignment="Center" Margin="0,6"/>
|
||||||
|
<TextBox Grid.Row="1" Grid.Column="1" Margin="0,6"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding Config.DeviceIndex, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
|
||||||
|
<!-- 仲裁域波特率 -->
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||||
|
Text="仲裁域波特率:"
|
||||||
|
VerticalAlignment="Center" Margin="0,6"/>
|
||||||
|
<ComboBox Grid.Row="2" Grid.Column="1" Margin="0,6"
|
||||||
|
IsEditable="True"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
ItemsSource="{Binding CommonABitBauds}"
|
||||||
|
Text="{Binding Config.ABitBaud, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
|
||||||
|
<!-- 数据域波特率 -->
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="0"
|
||||||
|
Text="数据域波特率:"
|
||||||
|
VerticalAlignment="Center" Margin="0,6"/>
|
||||||
|
<ComboBox Grid.Row="3" Grid.Column="1" Margin="0,6"
|
||||||
|
IsEditable="True"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
ItemsSource="{Binding CommonDBitBauds}"
|
||||||
|
Text="{Binding Config.DBitBaud, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
|
||||||
|
<!-- 终端电阻 -->
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="0"
|
||||||
|
Text="终端电阻:"
|
||||||
|
VerticalAlignment="Center" Margin="0,6"/>
|
||||||
|
<ToggleButton Grid.Row="4" Grid.Column="1" Margin="0,6"
|
||||||
|
IsChecked="{Binding Config.EnableTerminalResistance}"
|
||||||
|
Style="{StaticResource MaterialDesignSwitchToggleButton}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 错误提示 -->
|
||||||
|
<TextBlock Grid.Row="1"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
Foreground="#D32F2F"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="{Binding ErrorMessage}"
|
||||||
|
Visibility="{Binding ErrorMessage, Converter={StaticResource StringToVisibility}}"/>
|
||||||
|
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Margin="0,12,0,0">
|
||||||
|
<Button Content="取消"
|
||||||
|
Width="80" Padding="0,4"
|
||||||
|
Command="{Binding CancelCommand}"/>
|
||||||
|
<Button Content="保存"
|
||||||
|
Width="80" Padding="0,4"
|
||||||
|
Margin="10,0,0,0"
|
||||||
|
IsDefault="True"
|
||||||
|
Command="{Binding SaveCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</GroupBox>
|
||||||
|
</UserControl>
|
||||||
15
SettingModule/Views/Dialogs/CANConfigView.xaml.cs
Normal file
15
SettingModule/Views/Dialogs/CANConfigView.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace SettingModule.Views.Dialogs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// CANConfigView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class CANConfigView : UserControl
|
||||||
|
{
|
||||||
|
public CANConfigView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -368,6 +368,33 @@
|
|||||||
<Run Text="{Binding SelectedDevice.SerialPortConfig.Parity}"/>
|
<Run Text="{Binding SelectedDevice.SerialPortConfig.Parity}"/>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- CAN 参数预览 -->
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
|
||||||
|
Margin="0,4,0,0" Orientation="Horizontal">
|
||||||
|
<StackPanel.Style>
|
||||||
|
<Style TargetType="StackPanel">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding SelectedDevice.ConnectionType}" Value="CAN">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Style>
|
||||||
|
<TextBlock Foreground="#666" FontSize="12">
|
||||||
|
<Run Text="设备类型:"/>
|
||||||
|
<Run Text="{Binding SelectedDevice.CANConfig.DeviceType}"/>
|
||||||
|
<Run Text=" 设备索引:"/>
|
||||||
|
<Run Text="{Binding SelectedDevice.CANConfig.DeviceIndex}"/>
|
||||||
|
<Run Text=" 仲裁/数据波特率:"/>
|
||||||
|
<Run Text="{Binding SelectedDevice.CANConfig.ABitBaud}"/>
|
||||||
|
<Run Text="/"/>
|
||||||
|
<Run Text="{Binding SelectedDevice.CANConfig.DBitBaud}"/>
|
||||||
|
<Run Text=" 终端电阻:"/>
|
||||||
|
<Run Text="{Binding SelectedDevice.CANConfig.EnableTerminalResistance}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -375,11 +402,11 @@
|
|||||||
<Border Background="White"
|
<Border Background="White"
|
||||||
BorderBrush="#E0E0E0" BorderThickness="1"
|
BorderBrush="#E0E0E0" BorderThickness="1"
|
||||||
CornerRadius="4" Padding="14">
|
CornerRadius="4" Padding="14">
|
||||||
<StackPanel>
|
<!--<StackPanel>
|
||||||
<TextBlock Text="高级设置" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
<TextBlock Text="高级设置" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||||
<TextBlock Foreground="#888" FontSize="12" TextWrapping="Wrap"
|
<TextBlock Foreground="#888" FontSize="12" TextWrapping="Wrap"
|
||||||
Text="超时、重试、缓存策略等高级选项后续在此扩展。"/>
|
Text="超时、重试、缓存策略等高级选项后续在此扩展。"/>
|
||||||
</StackPanel>
|
</StackPanel>-->
|
||||||
</Border>
|
</Border>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
@@ -448,7 +475,7 @@
|
|||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Slider Grid.Column="0"
|
<Slider Grid.Column="0"
|
||||||
Minimum="0" Maximum="100"
|
Minimum="100" Maximum="200"
|
||||||
TickFrequency="10" IsSnapToTickEnabled="True"
|
TickFrequency="10" IsSnapToTickEnabled="True"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Value="{Binding SystemConfig.PerformanceLevel}"/>
|
Value="{Binding SystemConfig.PerformanceLevel}"/>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user