diff --git a/ADP.sln b/ADP.sln
new file mode 100644
index 0000000..9850756
--- /dev/null
+++ b/ADP.sln
@@ -0,0 +1,116 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35527.113 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ADP", "ADP\ADP.csproj", "{8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{520D7017-1A2F-427D-B8A4-54E3D0575EFD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "Model\Model.csproj", "{42623437-0ADD-4149-B33F-DB30E7B13D69}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "Service\Service.csproj", "{F9A522A1-869E-462E-9782-57ED3DE5566E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ORM", "ORM\ORM.csproj", "{19ED286B-4E83-4EA0-9BD4-37D2AB796F22}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logger", "Logger\Logger.csproj", "{84F26384-D1DC-4175-98A3-A64A4FB961D2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceCommand", "DeviceCommand\DeviceCommand.csproj", "{1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Command", "Command\Command.csproj", "{FC353140-A722-4E8E-97BA-FB866E8188A9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIShare", "UIShare\UIShare.csproj", "{53198B54-BDF9-446D-B1FD-EC0A52D794E5}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{1E92D601-68FC-45F6-8251-BD0F39226E5B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoginModule", "LoginModule\LoginModule.csproj", "{DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SettingModule", "SettingModule\SettingModule.csproj", "{51384EB4-1940-44FB-8E18-56F7201642CD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestingModule", "TestingModule\TestingModule.csproj", "{BD74E7F3-BBB5-4375-A37E-F24E8507A436}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainModule", "MainModule\MainModule.csproj", "{DFC084AE-FE45-4492-A2DA-EAE2941F26E9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateInfoMoudle", "UpdateInfoMoudle\UpdateInfoMoudle.csproj", "{3456C3AC-E7FF-45F8-A68F-BEED9969812C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonitorModule", "MonitorModule\MonitorModule.csproj", "{850A5BE8-0BC1-4D6A-8351-DD812C9C4427}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B48B0CD-55F9-4623-9A10-BFE25B21EBD6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {520D7017-1A2F-427D-B8A4-54E3D0575EFD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42623437-0ADD-4149-B33F-DB30E7B13D69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42623437-0ADD-4149-B33F-DB30E7B13D69}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42623437-0ADD-4149-B33F-DB30E7B13D69}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42623437-0ADD-4149-B33F-DB30E7B13D69}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F9A522A1-869E-462E-9782-57ED3DE5566E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F9A522A1-869E-462E-9782-57ED3DE5566E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F9A522A1-869E-462E-9782-57ED3DE5566E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F9A522A1-869E-462E-9782-57ED3DE5566E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {19ED286B-4E83-4EA0-9BD4-37D2AB796F22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {84F26384-D1DC-4175-98A3-A64A4FB961D2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1F4E6A3D-B273-4F76-8D59-37F7C8E858BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FC353140-A722-4E8E-97BA-FB866E8188A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FC353140-A722-4E8E-97BA-FB866E8188A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FC353140-A722-4E8E-97BA-FB866E8188A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FC353140-A722-4E8E-97BA-FB866E8188A9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {53198B54-BDF9-446D-B1FD-EC0A52D794E5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72}.Release|Any CPU.Build.0 = Release|Any CPU
+ {51384EB4-1940-44FB-8E18-56F7201642CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {51384EB4-1940-44FB-8E18-56F7201642CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {51384EB4-1940-44FB-8E18-56F7201642CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {51384EB4-1940-44FB-8E18-56F7201642CD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD74E7F3-BBB5-4375-A37E-F24E8507A436}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DFC084AE-FE45-4492-A2DA-EAE2941F26E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3456C3AC-E7FF-45F8-A68F-BEED9969812C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {850A5BE8-0BC1-4D6A-8351-DD812C9C4427}.Debug|Any CPU.ActiveCfg = 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.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {DF1DE1F7-B570-41D7-A2B5-AB2CDCD4BD72} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
+ {51384EB4-1940-44FB-8E18-56F7201642CD} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
+ {BD74E7F3-BBB5-4375-A37E-F24E8507A436} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
+ {DFC084AE-FE45-4492-A2DA-EAE2941F26E9} = {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}
+ EndGlobalSection
+EndGlobal
diff --git a/ADP/ADP.csproj b/ADP/ADP.csproj
new file mode 100644
index 0000000..e915270
--- /dev/null
+++ b/ADP/ADP.csproj
@@ -0,0 +1,24 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ADP/App.xaml b/ADP/App.xaml
new file mode 100644
index 0000000..ec1b892
--- /dev/null
+++ b/ADP/App.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/ADP/App.xaml.cs b/ADP/App.xaml.cs
new file mode 100644
index 0000000..f5a0595
--- /dev/null
+++ b/ADP/App.xaml.cs
@@ -0,0 +1,88 @@
+using Common;
+using ADP.ViewModels;
+using ADP.ViewModels.Dialogs;
+using ADP.Views;
+using ADP.Views;
+using ADP.Views.Dialogs;
+using Logger;
+using Notifications.Wpf.Core;
+using ORM;
+using Service.Implement;
+using Service.Interface;
+using System.Configuration;
+using System.Data;
+using System.Reflection;
+using System.Windows;
+using UIShare.PubEvent;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+using UIShare.GlobalVariable;
+using UIShare;
+
+namespace ADP
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : PrismApplication
+ {
+ protected override Window CreateShell()
+ {
+ //UI线程未捕获异常处理事件
+ this.DispatcherUnhandledException += OnDispatcherUnhandledException;
+ //Task线程内未捕获异常处理事件
+ TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
+ ////多线程异常
+ AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
+ return Container.Resolve();
+ }
+ private void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
+ {
+ LoggerHelper.Error(e.Exception.Message, e.Exception.StackTrace);
+ }
+
+ private void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
+ {
+ LoggerHelper.Error(e.Exception.Message, e.Exception.StackTrace);
+ }
+
+ private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ //记录dump文件
+ Exception ex = e.ExceptionObject as Exception;
+ MiniDump.TryDump($"dumps\\Error_{DateTime.Now:yyyy-MM-dd HH-mm-ss-ms}.dmp", MiniDump.Option.WithFullMemory, ex);
+ }
+ protected override void OnInitialized()
+ {
+ //初始化数据库
+ //DatabaseConfig.SetTenant(10001);
+ //DatabaseConfig.InitMySql("127.0.0.1",3306,"ADP","root","123456");
+ //DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true);
+ //SqlSugarContext.InitDatabase();
+ //显示登录窗口
+ var login = Container.Resolve();
+ var re = Container.Resolve();
+ RegionManager.SetRegionManager(login, re);
+ RegionManager.SetRegionManager(Application.Current.MainWindow, re);
+ login.Show();
+ }
+ protected override void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ //注册弹窗
+ containerRegistry.RegisterDialog("MessageBox");
+ // 注册通知管理器
+ INotificationManager NotificationManager = new NotificationManager();
+ containerRegistry.RegisterInstance(NotificationManager);
+ //注册全局变量
+ containerRegistry.RegisterScoped();
+ containerRegistry.RegisterScoped();
+ containerRegistry.RegisterScoped();
+ containerRegistry.RegisterSingleton();
+ }
+ //指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
+ protected override IModuleCatalog CreateModuleCatalog()
+ {
+ //指定模块加载方式为从文件夹中以反射发现并加载module(推荐用法)
+ return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
+ }
+ }
+}
diff --git a/ADP/AssemblyInfo.cs b/ADP/AssemblyInfo.cs
new file mode 100644
index 0000000..b0ec827
--- /dev/null
+++ b/ADP/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/ADP/Resources/Images/error.png b/ADP/Resources/Images/error.png
new file mode 100644
index 0000000..de079d0
Binary files /dev/null and b/ADP/Resources/Images/error.png differ
diff --git a/ADP/Resources/Images/info.png b/ADP/Resources/Images/info.png
new file mode 100644
index 0000000..8fed1fb
Binary files /dev/null and b/ADP/Resources/Images/info.png differ
diff --git a/ADP/Resources/Images/warning.png b/ADP/Resources/Images/warning.png
new file mode 100644
index 0000000..1acd082
Binary files /dev/null and b/ADP/Resources/Images/warning.png differ
diff --git a/ADP/ViewModels/Dialogs/MessageBoxViewModel.cs b/ADP/ViewModels/Dialogs/MessageBoxViewModel.cs
new file mode 100644
index 0000000..d16c403
--- /dev/null
+++ b/ADP/ViewModels/Dialogs/MessageBoxViewModel.cs
@@ -0,0 +1,120 @@
+using UIShare.PubEvent;
+using UIShare.ViewModelBase;
+using System.Windows.Input;
+
+namespace ADP.ViewModels.Dialogs
+{
+ public class MessageBoxViewModel : DialogViewModelBase
+ {
+ #region 属性
+
+ private string _Title;
+ public string Title
+ {
+ get => _Title;
+ set => SetProperty(ref _Title, value);
+ }
+
+ private string _Message = "";
+ public string Message
+ {
+ get => _Message;
+ set => SetProperty(ref _Message, value);
+ }
+
+ private string _Icon= $"pack://siteoforigin:,,,/Resources/Images/info.png";
+ public string Icon
+ {
+ get => _Icon;
+ set => SetProperty(ref _Icon, value);
+ }
+
+ private bool _ShowYes;
+ public bool ShowYes
+ {
+ get => _ShowYes;
+ set => SetProperty(ref _ShowYes, value);
+ }
+
+ private bool _ShowNo;
+ public bool ShowNo
+ {
+ get => _ShowNo;
+ set => SetProperty(ref _ShowNo, value);
+ }
+
+ private bool _ShowOk;
+ public bool ShowOk
+ {
+ get => _ShowOk;
+ set => SetProperty(ref _ShowOk, value);
+ }
+
+ private bool _ShowCancel;
+ public bool ShowCancel
+ {
+ get => _ShowCancel;
+ set => SetProperty(ref _ShowCancel, value);
+ }
+
+ #endregion
+
+ #region 命令
+ public ICommand YesCommand { get; set; }
+ public ICommand NoCommand { get; set; }
+ public ICommand OkCommand { get; set; }
+ public ICommand CancelCommand { get; set; }
+ #endregion
+
+ public DialogCloseListener RequestClose { get; set; }
+
+ public MessageBoxViewModel(IContainerProvider containerProvider):base(containerProvider)
+ {
+ YesCommand = new DelegateCommand(OnYes);
+ NoCommand = new DelegateCommand(OnNo);
+ OkCommand = new DelegateCommand(OnOk);
+ CancelCommand = new DelegateCommand(OnCancel);
+ }
+
+ private void CloseDialog(ButtonResult result)
+ {
+ var parameters = new DialogParameters();
+ RequestClose.Invoke(new DialogResult(result));
+ }
+
+ private void OnYes() => CloseDialog(ButtonResult.Yes);
+ private void OnNo() => CloseDialog(ButtonResult.No);
+ private void OnOk() => CloseDialog(ButtonResult.OK);
+ private void OnCancel() => CloseDialog(ButtonResult.Cancel);
+
+ #region Prism Dialog 规范
+ public bool CanCloseDialog() => true;
+
+ public override void OnDialogClosed()
+ {
+ _eventAggregator.GetEvent().Publish(false);
+ }
+
+ public override void OnDialogOpened(IDialogParameters parameters)
+ {
+ _eventAggregator.GetEvent().Publish(true);
+ Title = parameters.GetValue("Title");
+ Message = parameters.GetValue("Message");
+ var iconKey = parameters.GetValue("Icon"); // info / error / warn
+ Icon = iconKey switch
+ {
+ "info" => $"pack://siteoforigin:,,,/Resources/Images/info.png",
+ "error" => $"pack://siteoforigin:,,,/Resources/Images/error.png",
+ "warn" => $"pack://siteoforigin:,,,/Resources/Images/warning.png",
+ _ => $"pack://siteoforigin:,,,/Resources/Images/info.png" // 默认
+ };
+
+
+ ShowYes = parameters.GetValue("ShowYes");
+ ShowNo = parameters.GetValue("ShowNo");
+ ShowOk = parameters.GetValue("ShowOk");
+ ShowCancel = parameters.GetValue("ShowCancel");
+ }
+ #endregion
+ }
+}
diff --git a/ADP/ViewModels/ShellViewModel.cs b/ADP/ViewModels/ShellViewModel.cs
new file mode 100644
index 0000000..05ffb65
--- /dev/null
+++ b/ADP/ViewModels/ShellViewModel.cs
@@ -0,0 +1,772 @@
+
+using Logger;
+using MaterialDesignThemes.Wpf;
+using Notifications.Wpf.Core;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.IO;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Threading; // 💡 新增:引入定时器命名空间
+using UIShare;
+using UIShare.GlobalVariable;
+using UIShare.PubEvent;
+using UIShare.UIViewModel;
+
+namespace ADP.ViewModels
+{
+ public class ShellViewModel : BindableBase
+ {
+ #region 私有字段
+ private string _Title = "";
+ private bool _IsLeftDrawerOpen;
+
+ private readonly ConcurrentDictionary _executionTasks = new();
+ private readonly ConcurrentDictionary _errorExecutionTasks = new();
+
+ private readonly IEventAggregator _eventAggregator;
+ private readonly IRegionManager _regionManager;
+ private readonly IContainerProvider _containerProvider;
+ private readonly INotificationManager _notificationManager;
+ private readonly IModuleManager _moduleManager;
+ private readonly GlobalInfo _globalInfo;
+
+ // 💡 新增:UI 刷新定时器,用于高频让前端重新拉取当前工位的 SW 累计时间
+ private readonly DispatcherTimer _uiRefreshTimer;
+ #endregion
+
+ #region 属性
+ public string Title
+ {
+ get => _Title;
+ set => SetProperty(ref _Title, value);
+ }
+ public bool IsLeftDrawerOpen
+ {
+ get => _IsLeftDrawerOpen;
+ set => SetProperty(ref _IsLeftDrawerOpen, value);
+ }
+
+ // 代理属性:动态反映当前被激活的 Scope 状态
+ public TimeSpan RunningTime
+ {
+ get
+ {
+ if (CurrentContext == null) return TimeSpan.Zero;
+ // 💡 核心:如果当前工位的计时器正在跑,动态累加 SW 当前的时间,否则返回其保存的固定时间值
+ return CurrentContext.SW.IsRunning ? CurrentContext.SW.Elapsed : CurrentContext.RunningTime;
+ }
+ set
+ {
+ if (CurrentContext != null && CurrentContext.RunningTime != value)
+ {
+ CurrentContext.RunningTime = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public bool IsTerminate
+ {
+ get => CurrentContext?.IsTerminate ?? false;
+ set
+ {
+ if (CurrentContext != null && CurrentContext.IsTerminate != value)
+ {
+ CurrentContext.IsTerminate = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ private ScopedContext? CurrentContext =>
+ _globalInfo.ContextDic.TryGetValue(_globalInfo.CurrentScope, out var ctx) ? ctx : null;
+ private StepRunning? CurrentRunner =>
+ _globalInfo.StepRunningDic.TryGetValue(_globalInfo.CurrentScope, out var runner) ? runner : null;
+
+ public string RunState
+ {
+ get => CurrentContext?.RunState ?? "运行";
+ set
+ {
+ if (CurrentContext != null && CurrentContext.RunState != value)
+ {
+ CurrentContext.RunState = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public bool SingleStep
+ {
+ get => CurrentContext?.SingleStep ?? false;
+ set
+ {
+ if (CurrentContext != null && CurrentContext.SingleStep != value)
+ {
+ CurrentContext.SingleStep = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+
+ public PackIconKind RunIcon
+ {
+ get => CurrentContext?.RunIcon ?? PackIconKind.Play;
+ set
+ {
+ if (CurrentContext != null && CurrentContext.RunIcon != value)
+ {
+ CurrentContext.RunIcon = value;
+ RaisePropertyChanged();
+ }
+ }
+ }
+ #endregion
+
+ #region 命令
+ public ICommand LeftDrawerOpenCommand { get; set; }
+ public ICommand MinimizeCommand { get; set; }
+ public ICommand MaximizeCommand { get; set; }
+ public ICommand CloseCommand { get; set; }
+ public ICommand NavigateCommand { get; set; }
+ public ICommand LoadCommand { get; set; }
+ public ICommand RefreshCommand { get; set; }
+ public ICommand DestroyCommand { get; set; }
+ public ICommand RunningCommand { get; set; }
+ public ICommand RunSingleCommand { get; set; }
+ public ICommand RestorationCommand { get; set; }
+ public ICommand RunAbnormalStepsCommand { get; set; }
+ public ICommand SaveAsCommand { get; set; }
+ public ICommand SaveCommand { get; set; }
+ public ICommand OpenCommand { get; set; }
+ public ICommand NewCommand { get; set; }
+ public ICommand SetDefaultCommand { get; set; }
+ #endregion
+
+ public ShellViewModel(IContainerProvider containerProvider)
+ {
+ _containerProvider = containerProvider;
+ _globalInfo = containerProvider.Resolve();
+ _eventAggregator = containerProvider.Resolve();
+ _regionManager = containerProvider.Resolve();
+ _notificationManager = containerProvider.Resolve();
+ _moduleManager = containerProvider.Resolve();
+
+ LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
+ MinimizeCommand = new DelegateCommand(MinimizeWindow);
+ MaximizeCommand = new DelegateCommand(MaximizeWindow);
+ CloseCommand = new DelegateCommand(CloseWindow);
+ NavigateCommand = new DelegateCommand(Navigate);
+ LoadCommand = new DelegateCommand(Load);
+ RefreshCommand = new DelegateCommand(OnRefresh);
+ DestroyCommand = new DelegateCommand(OnDestroy);
+ RunningCommand = new DelegateCommand(OnRunning);
+ RunSingleCommand = new DelegateCommand(RunSingle);
+ RestorationCommand = new AsyncDelegateCommand(OnRestoration);
+ RunAbnormalStepsCommand = new AsyncDelegateCommand(OnRunAbnormalSteps);
+ NewCommand = new DelegateCommand(New);
+ OpenCommand = new AsyncDelegateCommand(Open);
+ SaveAsCommand = new DelegateCommand(SaveAs);
+ SaveCommand = new DelegateCommand(Save);
+ SetDefaultCommand = new DelegateCommand(SetDefault);
+
+ _globalInfo.ContextDic.Add("default", new ScopedContext());
+
+ _eventAggregator.GetEvent().Subscribe(() =>
+ {
+ Application.Current.MainWindow.Show();
+ _regionManager.RequestNavigate("ShellViewManager", "MainView");
+ });
+
+ _eventAggregator.GetEvent().Subscribe(UpdateRunIcon);
+
+ _globalInfo.ScopeChanged += (s, e) =>
+ {
+ RefreshAllContextProperties();
+ };
+
+ // 💡 初始化轻量级 UI 定时器:每 100 毫秒刷新一次当前可见工位的界面时间
+ _uiRefreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
+ _uiRefreshTimer.Tick += (s, e) => RaisePropertyChanged(nameof(RunningTime));
+ _uiRefreshTimer.Start();
+ }
+
+ private void RefreshAllContextProperties()
+ {
+ RaisePropertyChanged(nameof(RunState));
+ RaisePropertyChanged(nameof(SingleStep));
+ RaisePropertyChanged(nameof(RunIcon));
+ RaisePropertyChanged(nameof(RunningTime));
+ RaisePropertyChanged(nameof(IsTerminate));
+ }
+
+ #region 命令处理与事件
+ private async void OnRunning()
+ {
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+ StepRunning? targetRunner = CurrentRunner;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
+ {
+ return;
+ }
+
+ if (targetContext.RunState == "运行")
+ {
+ targetContext.SingleStep = false;
+ LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 运行命令");
+
+ targetContext.RunState = "暂停";
+ targetContext.RunIcon = PackIconKind.Pause;
+
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+
+ if (targetContext.IsStop == null)
+ {
+ targetContext.IsStop = false;
+
+ // 💡 启动或恢复:启动属于该快照工位独立的 SW 计时器
+ targetContext.SW.Start();
+
+ if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null)
+ {
+ existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token);
+ _executionTasks[runningScope] = existingTask;
+ }
+
+ try
+ {
+ await existingTask;
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常中止: {ex.Message}");
+ }
+
+ // 💡 测试正常完毕或取消:停止当前工位的 SW,并将最终时间固化到 RunningTime 字段中
+ targetContext.SW.Stop();
+ targetContext.RunningTime = targetContext.SW.Elapsed;
+
+ targetContext.RunState = "运行";
+ targetContext.RunIcon = PackIconKind.Play;
+ targetContext.IsStop = null;
+
+ if (targetRunner.stepCTS.IsCancellationRequested)
+ {
+ targetRunner.stepCTS = new CancellationTokenSource();
+ _executionTasks.TryRemove(runningScope, out _);
+ }
+ else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted)
+ {
+ targetContext.IsTerminate = true;
+ _executionTasks.TryRemove(runningScope, out _);
+ }
+
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+ else if (targetContext.IsStop == true)
+ {
+ targetContext.IsStop = false;
+ // 💡 从暂停中恢复,继续累加当前工位计时
+ targetContext.SW.Start();
+ }
+ }
+ else // 用户点击了暂停
+ {
+ LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 点击工位 [{runningScope}] 暂停命令");
+ targetContext.SingleStep = true;
+ targetContext.IsStop = true;
+ targetContext.RunState = "运行";
+ targetContext.RunIcon = PackIconKind.Play;
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+ }
+
+ private async void RunSingle()
+ {
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+ StepRunning? targetRunner = CurrentRunner;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
+ {
+ return;
+ }
+
+ if (targetContext.RunState == "运行")
+ {
+ targetContext.SingleStep = true;
+ LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 单步执行命令");
+
+ targetContext.RunState = "暂停";
+ targetContext.RunIcon = PackIconKind.Pause;
+
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+
+ if (targetContext.IsStop == null)
+ {
+ targetContext.IsStop = false;
+
+ // 💡 单步启动:让专属工位的 SW 跑起来
+ targetContext.SW.Start();
+
+ if (!_executionTasks.TryGetValue(runningScope, out var existingTask) || existingTask == null)
+ {
+ existingTask = targetRunner.ExecuteSteps(targetContext.Program, cancellationToken: targetRunner.stepCTS.Token);
+ _executionTasks[runningScope] = existingTask;
+ }
+
+ try
+ {
+ await existingTask;
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 单步执行异常: {ex.Message}");
+ }
+
+ // 💡 单步单步完成后会被 StepRunning 挂起,在此暂时停止 SW
+ targetContext.SW.Stop();
+ targetContext.RunningTime = targetContext.SW.Elapsed;
+
+ targetContext.RunState = "运行";
+ targetContext.RunIcon = PackIconKind.Play;
+ targetContext.IsStop = null;
+
+ if (targetRunner.stepCTS.IsCancellationRequested)
+ {
+ targetRunner.stepCTS = new CancellationTokenSource();
+ _executionTasks.TryRemove(runningScope, out _);
+ }
+ else if (_executionTasks.TryGetValue(runningScope, out var currentTask) && currentTask != null && currentTask.IsCompleted)
+ {
+ targetContext.IsTerminate = true;
+ _executionTasks.TryRemove(runningScope, out _);
+ }
+
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+ else if (targetContext.IsStop == true)
+ {
+ targetContext.IsStop = false;
+ // 💡 继续单步,继续计时
+ targetContext.SW.Start();
+ }
+ }
+ }
+
+ private async Task OnRestoration()
+ {
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+ StepRunning? targetRunner = CurrentRunner;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
+ {
+ return;
+ }
+
+ LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 复位命令");
+
+ _executionTasks.TryGetValue(runningScope, out var currentTask);
+ _errorExecutionTasks.TryGetValue(runningScope, out var errorTask);
+
+ if (currentTask != null || errorTask != null)
+ {
+ targetRunner.stepCTS.Cancel();
+ targetRunner.errorStepCTS.Cancel();
+
+ await Task.Delay(200);
+
+ // 💡 彻底复位:停止当前工位的计时器,并且完全归零(Reset)
+ targetContext.SW.Reset();
+ targetContext.RunningTime = TimeSpan.Zero;
+
+ targetContext.IsStop = null;
+ targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection);
+ targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection);
+ targetContext.IsTerminate = false;
+ }
+ else
+ {
+ targetRunner.stepCTS = new CancellationTokenSource();
+ targetRunner.errorStepCTS = new CancellationTokenSource();
+
+ // 💡 无任务状态下的直接归零
+ targetContext.SW.Reset();
+ targetContext.RunningTime = TimeSpan.Zero;
+
+ targetContext.IsStop = null;
+ targetRunner.ResetAllStepStatus(targetContext.Program.StepCollection);
+ targetRunner.ResetAllStepStatus(targetContext.Program.ErrorStepCollection);
+ targetContext.IsTerminate = false;
+ }
+
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+
+ private async Task OnRunAbnormalSteps()
+ {
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+ StepRunning? targetRunner = CurrentRunner;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null || targetRunner == null)
+ {
+ return;
+ }
+
+ LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 异常流程命令");
+
+ // 💡 异常测试启动计时
+ targetContext.SW.Start();
+
+ if (!_errorExecutionTasks.TryGetValue(runningScope, out var existingErrorTask) || existingErrorTask == null)
+ {
+ existingErrorTask = targetRunner.ExecuteErrorSteps(targetContext.Program, cancellationToken: targetRunner.errorStepCTS.Token);
+ _errorExecutionTasks[runningScope] = existingErrorTask;
+ }
+
+ try
+ {
+ await existingErrorTask;
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常流程执行出错: {ex.Message}");
+ }
+
+ // 💡 异常测试结束停止计时
+ targetContext.SW.Stop();
+ targetContext.RunningTime = targetContext.SW.Elapsed;
+
+ if (targetRunner.errorStepCTS.IsCancellationRequested)
+ {
+ targetRunner.errorStepCTS = new CancellationTokenSource();
+ _errorExecutionTasks.TryRemove(runningScope, out _);
+ }
+ else if (_errorExecutionTasks.TryGetValue(runningScope, out var currentErrorTask) && currentErrorTask != null && currentErrorTask.IsCompleted)
+ {
+ targetContext.IsTerminate = true;
+ _errorExecutionTasks.TryRemove(runningScope, out _);
+ }
+
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+
+ private void SetDefault()
+ {
+ // 💡 1. 抓取触发瞬间的 Scope 与 Context 快照
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
+
+ if (targetContext.CurrentFilePath != null)
+ {
+ //SystemConfig.DefaultProgramFilePath = targetContext.CurrentFilePath;
+ //ConfigService.Save();
+ LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 已成功将当前程序设为默认启动程序");
+ }
+ }
+
+ private void New()
+ {
+ // 💡 1. 抓取快照
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
+
+ // 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
+ targetContext.CurrentFilePath = null;
+ targetContext.Program.Parameters.Clear();
+ targetContext.Program.StepCollection.Clear();
+ targetContext.Program.ErrorStepCollection.Clear();
+
+ LoggerHelper.InfoWithNotify($"工位 [{runningScope}] 创建了空程序文件");
+
+ // 如果当前正看着该工位,刷新 UI 显示
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+
+ private async Task Open(string filePath = null)
+ {
+ // 💡 1. 抓取进方法瞬间的快照,锁死上下文
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
+
+ try
+ {
+ // 如果没有传路径,弹出文件选择对话框(WPF 对话框是模态的,会阻塞当前 UI 线程)
+ if (string.IsNullOrEmpty(filePath))
+ {
+ var openFileDialog = new Microsoft.Win32.OpenFileDialog
+ {
+ Filter = "ADP程序文件|*.ADP|所有文件|*.*",
+ Title = $"工位 [{runningScope}] 打开程序",
+ };
+
+ if (openFileDialog.ShowDialog() != true)
+ return; // 用户取消选择
+
+ filePath = openFileDialog.FileName;
+ }
+
+ // 确认文件存在
+ if (!File.Exists(filePath))
+ {
+ LoggerHelper.ErrorWithNotify($"文件不存在: {filePath}");
+ return;
+ }
+
+ // 读取 JSON 文件
+ string json = File.ReadAllText(filePath);
+
+ // 反序列化为 ProgramModel
+ var program = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
+
+ if (program == null)
+ {
+ LoggerHelper.WarnWithNotify($"文件格式不正确或为空: {filePath}");
+ return;
+ }
+
+ // 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
+ targetContext.Program.Parameters = program.Parameters;
+ targetContext.Program.StepCollection = program.StepCollection;
+ targetContext.Program.ErrorStepCollection = program.ErrorStepCollection;
+ targetContext.CurrentFilePath = filePath;
+
+ LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 成功打开文件: {filePath}");
+
+ // 💡 3. 安全调用异步复位,确保重置的是对应工位的数据
+ // 注意:由于在 OnRestoration 内部第一行也做了快照拦截,
+ // 如果用户此时正好在看这个工位,它会自动完美执行
+ await OnRestoration();
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 打开文件失败: {ex.Message}");
+ }
+ finally
+ {
+ // 💡 4. 异步回归:不管中途用户切去了哪里,回到该工位时及时刷新显示
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+ }
+
+ private void SaveAs()
+ {
+ if (!_globalInfo.IsAdmin) return;
+
+ // 💡 1. 抓取快照
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
+
+ string defaultPath = @"D:\ADP\子程序";
+ if (!Directory.Exists(defaultPath))
+ Directory.CreateDirectory(defaultPath);
+
+ var saveFileDialog = new Microsoft.Win32.SaveFileDialog
+ {
+ Filter = "ADP程序文件|*.adp|所有文件|*.*",
+ Title = $"工位 [{runningScope}] 程序另存为",
+ FileName = "NewProgram.ADP",
+ InitialDirectory = defaultPath
+ };
+
+ if (saveFileDialog.ShowDialog() == true)
+ {
+ // 💡 2. 将新路径安全写入快照工位
+ targetContext.CurrentFilePath = saveFileDialog.FileName;
+
+ // 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
+ SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
+
+ LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 另存为文件成功: {saveFileDialog.FileName}");
+
+ if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
+ }
+ }
+
+ private void Save()
+ {
+ if (!_globalInfo.IsAdmin) return;
+
+ // 💡 1. 抓取快照
+ string runningScope = _globalInfo.CurrentScope;
+ ScopedContext? targetContext = CurrentContext;
+
+ if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
+
+ // 💡 2. 判断当前工位是否有历史路径
+ if (targetContext.CurrentFilePath == null)
+ {
+ SaveAs();
+ return;
+ }
+
+ // 💡 3. 持久化当前快照工位的 Program
+ SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
+ LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 程序保存成功!");
+ }
+
+ // 💡 辅助方法:建议将你的通用序列化落盘代码调整为接收 ProgramModel 参数,提高复用性
+ private void SaveProgramToFile(string filePath, ProgramModel programModel)
+ {
+ try
+ {
+ string json = Newtonsoft.Json.JsonConvert.SerializeObject(programModel, Newtonsoft.Json.Formatting.Indented);
+ File.WriteAllText(filePath, json);
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"写入程序文件失败: {ex.Message}");
+ }
+ }
+
+ private void UpdateRunIcon(string obj)
+ {
+ CurrentContext?.SW.Stop();
+ CurrentContext.RunningTime = CurrentContext.SW.Elapsed;
+ RunIcon = obj switch
+ {
+ "Play" => PackIconKind.Play,
+ "Pause" => PackIconKind.Pause,
+ _ => RunIcon
+ };
+ }
+
+ private void OnRefresh()
+ {
+ _eventAggregator.GetEvent().Publish("");
+ _globalInfo.CurrentScope = "default";
+ }
+ private void OnDestroy()
+ {
+ if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) return;
+
+ string targetRegionName = _globalInfo.CurrentScope;
+
+ // 💡 物理销毁工位时,顺便清理任务字典,防止内存泄漏
+ _executionTasks.TryRemove(targetRegionName, out _);
+ _errorExecutionTasks.TryRemove(targetRegionName, out _);
+
+ if (_regionManager.Regions.ContainsRegionWithName(targetRegionName))
+ {
+ var region = _regionManager.Regions[targetRegionName];
+ var viewsToDestroy = region.Views.ToList();
+
+ foreach (var view in viewsToDestroy)
+ {
+ if (view is FrameworkElement element)
+ {
+ var viewModel = element.DataContext;
+ region.Remove(view);
+ if (viewModel is IDisposable disposableVM) disposableVM.Dispose();
+ }
+ }
+ }
+
+ _globalInfo.ContextDic.Remove(targetRegionName);
+ _globalInfo.StepRunningDic.Remove(targetRegionName);
+
+ var parameters = new NavigationParameters { { "Name", targetRegionName } };
+ _regionManager.RequestNavigate(targetRegionName, "ProtocolStartView", parameters);
+ _eventAggregator.GetEvent().Publish("");
+ }
+ private void Load()
+ {
+ _notificationManager.ShowAsync(new NotificationContent { Title = "登录成功", Message = "", Type = NotificationType.Success });
+ //默认导航到主界面
+ Type moduleAType = typeof(MainModule.MainModule);
+ _moduleManager.LoadModule(moduleAType.Name);
+ }
+ private void Navigate(string content)
+ {
+ switch (content)
+ {
+ case "主界面":
+ if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope))
+ {
+ _regionManager.RequestNavigate("ShellViewManager", "MainView");
+ break;
+ }
+ var TestingRegion = _globalInfo.CurrentScope;
+ if (!_regionManager.Regions.ContainsRegionWithName(TestingRegion)) break;
+ _regionManager.RequestNavigate(TestingRegion, "AutomatedTestingView");
+ break;
+ case "监控界面":
+ // 仅当某个工位被选中(九宫格已展开/选择)时才跳转,默认 default 跳过。
+ if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
+
+ // 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
+ var monitorRegion = _globalInfo.CurrentScope;
+ if (!_regionManager.Regions.ContainsRegionWithName(monitorRegion)) break;
+
+ var monitorParameters = new NavigationParameters { { "Name", monitorRegion } };
+ _regionManager.RequestNavigate(monitorRegion, "MonitorView", monitorParameters);
+ IsLeftDrawerOpen = false;
+ break;
+ case "记录界面":
+ if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
+
+ // 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
+ var recordRegion = _globalInfo.CurrentScope;
+ if (!_regionManager.Regions.ContainsRegionWithName(recordRegion)) break;
+
+ var recordParameters = new NavigationParameters { { "Name", recordRegion } };
+ _regionManager.RequestNavigate(recordRegion, "RecordView", recordParameters);
+ IsLeftDrawerOpen = false;
+ break;
+ case "设置界面":
+ if (_globalInfo.CurrentScope == "default" || string.IsNullOrEmpty(_globalInfo.CurrentScope)) break;
+
+ // 记录界面不是填到全局的 ShellViewManager,而是填到该工位专属的 region。
+ var settingRegion = _globalInfo.CurrentScope;
+ if (!_regionManager.Regions.ContainsRegionWithName(settingRegion)) break;
+
+ var settingParameters = new NavigationParameters { { "Name", settingRegion } };
+ _regionManager.RequestNavigate(settingRegion, "SettingView", settingParameters);
+ IsLeftDrawerOpen = false;
+ break;
+ case "更新界面":
+ _regionManager.RequestNavigate("ShellViewManager", "UpdateInfoView");
+ break;
+ }
+ }
+
+ private void LeftDrawerOpen()
+ {
+ IsLeftDrawerOpen = true;
+ }
+
+ private void MinimizeWindow(Window window)
+ {
+ if (window != null)
+ window.WindowState = WindowState.Minimized;
+ }
+
+ private void MaximizeWindow(Window window)
+ {
+ if (window != null)
+ {
+ window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
+ }
+ }
+
+ private void CloseWindow(Window window)
+ {
+ window?.Close();
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ADP/Views/Dialogs/MessageBoxView.xaml b/ADP/Views/Dialogs/MessageBoxView.xaml
new file mode 100644
index 0000000..d4de9e6
--- /dev/null
+++ b/ADP/Views/Dialogs/MessageBoxView.xaml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ADP/Views/Dialogs/MessageBoxView.xaml.cs b/ADP/Views/Dialogs/MessageBoxView.xaml.cs
new file mode 100644
index 0000000..44ad07f
--- /dev/null
+++ b/ADP/Views/Dialogs/MessageBoxView.xaml.cs
@@ -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 ADP.Views.Dialogs
+{
+ ///
+ /// MessageBoxView.xaml 的交互逻辑
+ ///
+ public partial class MessageBoxView : UserControl
+ {
+ public MessageBoxView()
+ {
+ InitializeComponent();
+ }
+
+ private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (e.LeftButton == MouseButtonState.Pressed)
+ {
+ Window.GetWindow(this)?.DragMove();
+ }
+ }
+ }
+}
diff --git a/ADP/Views/LoginModuleView.xaml b/ADP/Views/LoginModuleView.xaml
new file mode 100644
index 0000000..f8aa552
--- /dev/null
+++ b/ADP/Views/LoginModuleView.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/ADP/Views/LoginModuleView.xaml.cs b/ADP/Views/LoginModuleView.xaml.cs
new file mode 100644
index 0000000..8f11bf1
--- /dev/null
+++ b/ADP/Views/LoginModuleView.xaml.cs
@@ -0,0 +1,38 @@
+using UIShare.PubEvent;
+using MahApps.Metro.Controls;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+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.Shapes;
+using Path = System.IO.Path;
+
+namespace ADP.Views
+{
+ ///
+ /// Login.xaml 的交互逻辑
+ ///
+ public partial class LoginModuleView : MetroWindow
+ {
+ public LoginModuleView(IEventAggregator eventAggregator)
+ {
+ InitializeComponent();
+ //订阅登录成功事件
+ eventAggregator.GetEvent().Subscribe(() =>
+ {
+ this.Close();
+ });
+ }
+ }
+}
diff --git a/ADP/Views/ShellView.xaml b/ADP/Views/ShellView.xaml
new file mode 100644
index 0000000..c41b7b3
--- /dev/null
+++ b/ADP/Views/ShellView.xaml
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ADP/Views/ShellView.xaml.cs b/ADP/Views/ShellView.xaml.cs
new file mode 100644
index 0000000..622150a
--- /dev/null
+++ b/ADP/Views/ShellView.xaml.cs
@@ -0,0 +1,51 @@
+using UIShare.PubEvent;
+using Prism.Events;
+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.Shapes;
+
+namespace ADP.Views
+{
+ ///
+ /// ShellView.xaml 的交互逻辑
+ ///
+ public partial class ShellView : Window
+ {
+ public ShellView(IEventAggregator eventAggregator)
+ {
+ InitializeComponent();
+ //注册灰度遮罩层
+ eventAggregator.GetEvent().Subscribe(ShowOverlay);
+ eventAggregator.GetEvent().Subscribe(ShowWaitinglay);
+ }
+
+ private void ShowWaitinglay(bool arg)
+ {
+ Waitinglay.Visibility = arg ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ private void ShowOverlay(bool arg)
+ {
+ Overlay.Visibility = arg ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+
+ private void ColorZone_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (e.LeftButton == MouseButtonState.Pressed)
+ {
+ Window.GetWindow(this)?.DragMove();
+ }
+ }
+ }
+}
diff --git a/Command/Command.csproj b/Command/Command.csproj
new file mode 100644
index 0000000..0eabc2b
--- /dev/null
+++ b/Command/Command.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Command/CommandApplication.cs b/Command/CommandApplication.cs
new file mode 100644
index 0000000..968b111
--- /dev/null
+++ b/Command/CommandApplication.cs
@@ -0,0 +1,268 @@
+using Common.Attributes;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Command
+{
+
+ [ADPCommand]
+ public static class CommandApplication
+ {
+ ///
+ /// 打开外部应用程序
+ ///
+ /// 程序路径
+ /// 是否等待关闭
+ /// 是否请求管理员运行
+ public static void OpenApplication(string route, bool waitShutdown, bool AdminRun)
+ {
+
+ // 创建一个新的进程实例
+ Process process = new Process();
+
+ try
+ {
+ // 指定要启动的程序路径
+ process.StartInfo.FileName = route;
+
+ // 指定是否等待程序关闭
+ process.StartInfo.UseShellExecute = !waitShutdown;
+ if (AdminRun) process.StartInfo.Verb = "runas";
+ // 启动程序
+ process.Start();
+
+ // 如果需要等待程序关闭,则等待程序退出
+ if (waitShutdown)
+ {
+ process.WaitForExit();
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("错误!: " + ex.Message);
+ }
+ finally
+ {
+ // 确保进程对象被释放
+ process.Dispose();
+ }
+
+ }
+ ///
+ /// 使用默认方式打开一个文件
+ ///
+ /// 文件路径
+ public static void OpenFile(string route)
+ {
+ // 创建一个新的进程实例
+ Process process = new Process();
+
+ try
+ {
+ // 指定要启动的程序路径
+ process.StartInfo.FileName = route;
+ process.StartInfo.UseShellExecute = true;
+ // 启动程序
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("错误!: " + ex.Message);
+ }
+ finally
+ {
+ // 确保进程对象被释放
+ process.Dispose();
+ }
+
+ }
+
+
+
+ // Python引擎相关字段
+ private static Process _pythonProcess;
+ private static string _pythonPath = string.Empty;
+ private static bool _isPythonEngineInitialized = false;
+
+ ///
+ /// 初始化python引擎
+ ///
+ /// Python解释器路径
+ public static void Initialization_Python(string py路径)
+ {
+ try
+ {
+ // 验证Python路径是否存在
+ if (string.IsNullOrEmpty(py路径) || !File.Exists(py路径))
+ {
+ throw new FileNotFoundException($"Python解释器路径不存在: {py路径}");
+ }
+
+ // 验证是否为Python可执行文件
+ var fileInfo = new FileInfo(py路径);
+ if (!fileInfo.Name.ToLower().Contains("python"))
+ {
+ Console.WriteLine("警告: 指定的文件可能不是Python解释器");
+ }
+
+ // 测试Python是否能正常运行
+ var testProcess = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = py路径,
+ Arguments = "--version",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ }
+ };
+
+ testProcess.Start();
+ testProcess.WaitForExit(5000); // 5秒超时
+
+ if (testProcess.ExitCode != 0)
+ {
+ throw new InvalidOperationException($"Python解释器验证失败,退出码: {testProcess.ExitCode}");
+ }
+
+ _pythonPath = py路径;
+ _isPythonEngineInitialized = true;
+
+ Console.WriteLine($"Python引擎初始化成功,版本: {testProcess.StandardOutput.ReadToEnd().Trim()}");
+ }
+ catch (Exception ex)
+ {
+ _isPythonEngineInitialized = false;
+ _pythonPath = string.Empty;
+ Console.WriteLine($"初始化Python引擎失败: {ex.Message}");
+ throw;
+ }
+ }
+
+ ///
+ /// 运行python脚本
+ ///
+ /// Python脚本路径
+ /// 脚本执行结果
+ public static string Run_Python_Script(string 路径)
+ {
+ if (!_isPythonEngineInitialized)
+ {
+ throw new InvalidOperationException("Python引擎未初始化,请先调用初始化python引擎");
+ }
+
+ if (string.IsNullOrEmpty(路径) || !File.Exists(路径))
+ {
+ throw new FileNotFoundException($"Python脚本路径不存在: {路径}");
+ }
+
+ Process process = null;
+ try
+ {
+ process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = _pythonPath,
+ Arguments = $"\"{路径}\"",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ }
+ };
+
+ var output = new StringBuilder();
+ var error = new StringBuilder();
+
+ // 设置输出和错误处理
+ process.OutputDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ output.AppendLine(e.Data);
+ };
+
+ process.ErrorDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ error.AppendLine(e.Data);
+ };
+
+ process.Start();
+
+ // 异步读取输出
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ process.WaitForExit();
+
+ var outputResult = output.ToString().Trim();
+ var errorResult = error.ToString().Trim();
+
+ if (process.ExitCode != 0)
+ {
+ throw new InvalidOperationException($"Python脚本执行失败,退出码: {process.ExitCode}, 错误: {errorResult}");
+ }
+
+ return string.IsNullOrEmpty(outputResult) ? "执行成功" : outputResult;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"运行Python脚本失败: {ex.Message}");
+ throw;
+ }
+ finally
+ {
+ process?.Dispose();
+ }
+ }
+
+ ///
+ /// 释放python引擎
+ ///
+ /// Python解释器路径(可选,用于验证)
+ public static void Release_Python(string py = "")
+ {
+ try
+ {
+ if (!string.IsNullOrEmpty(py) && py != _pythonPath)
+ {
+ Console.WriteLine($"警告: 释放的Python路径与当前初始化路径不匹配");
+ }
+
+ // 如果有正在运行的Python进程,尝试终止它
+ if (_pythonProcess != null && !_pythonProcess.HasExited)
+ {
+ try
+ {
+ _pythonProcess.Kill();
+ _pythonProcess.WaitForExit(1000);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"终止Python进程时出错: {ex.Message}");
+ }
+ }
+
+ _pythonPath = string.Empty;
+ _isPythonEngineInitialized = false;
+ _pythonProcess = null;
+
+ Console.WriteLine("Python引擎已释放");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"释放Python引擎失败: {ex.Message}");
+ throw;
+ }
+ }
+ }
+}
diff --git a/Command/CommandArray.cs b/Command/CommandArray.cs
new file mode 100644
index 0000000..22d711c
--- /dev/null
+++ b/Command/CommandArray.cs
@@ -0,0 +1,261 @@
+using Common.Attributes;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Command
+{
+
+ [ADPCommand]
+ public static class CommandArray
+ {
+ #region 数组操作
+ ///
+ /// 从数组中获取指定位置的元素。
+ ///
+ /// 要索引的数组。
+ /// 要获取的元素的位置。
+ /// 指定位置的元素。
+ public static object IndexArray(object Array, int Index)
+ {
+ return ((dynamic)Array)[Index];
+ }
+
+ ///
+ /// 设置数组中指定位置的元素为给定值。
+ ///
+ /// 要设置元素的数组。
+ /// 要设置的元素的位置。
+ /// 要设置的值。
+ /// 修改后的数组。
+ public static object SetArrayElement(object Array, int Location, object Value)
+ {
+ ((dynamic)Array)[Location] = (dynamic)Value;
+ return Array;
+ }
+ ///
+ /// 将给定数组的长度设置为指定值,并返回一个新的数组。
+ /// 如果指定的长度小于原始数组的长度,则截取原始数组的一部分作为新数组。
+ ///
+ /// 要设置长度的数组。
+ /// 新数组的长度。
+ /// 新的数组,或者原始数组(如果输入参数不是数组类型)。
+ public static object SetArrayLength(object Arr, int Length)
+ {
+ if (Arr is Array)
+ {
+ // 创建一个新的数组实例来存储结果
+ Array temp = Array.CreateInstance(Arr.GetType()!.GetElementType()!, Length);
+ // 确定复制的长度,取原始数组长度和目标长度的最小值
+ int CopyLength = Math.Min(((Array)Arr).Length, Length);
+ // 复制原始数组的元素到新数组中
+ Array.Copy((Array)Arr, temp, CopyLength);
+ return temp;
+ }
+ else
+ {
+ // 如果输入参数不是数组类型,则返回原始数组
+ return Arr;
+ }
+ }
+
+ ///
+ /// 在指定位置插入元素到数组中。
+ ///
+ /// 要插入元素的数组。
+ /// 要插入元素的位置。
+ /// 要插入的元素。
+ /// 插入元素后的数组。
+ public static object SetArrayInsertElement(object Arr, int Location, object InsertElement)
+ {
+ // 将数组转换为可编辑列表
+ var temp = Enumerable.ToList((dynamic)Arr);
+
+ // 在指定位置插入元素
+ temp.Insert(Location, (dynamic)InsertElement);
+
+ // 如果原数组是数组类型,则将可编辑列表转换回数组并返回
+ if (Arr is Array)
+ {
+ return Enumerable.ToArray(temp);
+ }
+
+ // 返回插入元素后的可编辑列表
+ return temp;
+ }
+ ///
+ /// 从数组中删除指定的元素。
+ ///
+ /// 要删除元素的数组。
+ /// 要从数组中删除的元素。
+ /// 删除元素后的数组。
+ public static object SetArrayDeleteElement(object Arr, object DeleteElement)
+ {
+ // 将数组转换为可编辑列表
+ var temp = Enumerable.ToList((dynamic)Arr);
+
+ // 从列表中移除指定元素
+ temp.Remove((dynamic)DeleteElement);
+
+ // 如果原数组是数组类型,则将可编辑列表转换回数组并返回
+ if (Arr is Array)
+ {
+ return Enumerable.ToArray(temp);
+ }
+
+ // 返回删除元素后的可编辑列表
+ return temp;
+ }
+ ///
+ /// 从数组中删除指定位置的元素。
+ ///
+ /// 要删除元素的数组。
+ /// 要从数组中删除的元素的位置。
+ /// 删除元素后的数组。
+ public static object SetArrayDeleteLocationElement(object Arr, int DeleteElementLocation)
+ {
+ var temp = Enumerable.ToList((dynamic)Arr);
+ temp.RemoveAt(DeleteElementLocation);
+ if (Arr is Array)
+ {
+ return Enumerable.ToArray(temp);
+ }
+ return temp;
+ }
+ ///
+ /// 在数组中寻找第一个匹配的元素,并返回其索引位置。
+ ///
+ /// 要搜索的数组。
+ /// 要寻找的元素。
+ /// 要寻找的元素在数组中的第一个匹配项的索引;如果未找到匹配项,则为 -1。
+ public static int ArrayQueryFirstMatchElement(object Arr, object QueryElement)
+ {
+ if (Arr is Array)
+ {
+ return Array.IndexOf((dynamic)Arr, (dynamic)QueryElement);
+ }
+ return ((dynamic)Arr).IndexOf((dynamic)QueryElement);
+ }
+ ///
+ /// 替换数组中第一个匹配的元素为指定的新元素。
+ ///
+ /// 要进行替换操作的数组。
+ /// 要替换的元素。
+ /// 要替换为的新元素。
+ /// 替换后的数组。
+ public static object ArrayReplaceFirstMatchElement(object Arr, object FirstMatchElement, object ReplaceElement)
+ {
+ var temp = Enumerable.ToList((dynamic)Arr);
+ int Location = ArrayQueryFirstMatchElement(Arr, FirstMatchElement);
+ temp[Location] = (dynamic)ReplaceElement;
+ return temp;
+ }
+ ///
+ /// 将数组中所有匹配的元素替换为指定的新元素。
+ ///
+ /// 要进行替换操作的数组。
+ /// 要替换的元素。
+ /// 要替换为的新元素。
+ /// 替换后的数组。
+ public static object ArrayReplaceAllMatchElement(object Arr, object MatchElement, object ReplaceElement)
+ {
+ var temp = Enumerable.ToList((dynamic)Arr);
+ int 长度 = temp.Count;
+
+ for (int i = 0; i < 长度; i++)
+ {
+ if (((dynamic)Arr)[i] == (dynamic)MatchElement)
+ {
+ ((dynamic)Arr)[i] = (dynamic)ReplaceElement;
+ }
+ }
+ return Arr;
+ }
+ ///
+ /// 反转数组中元素的顺序。
+ ///
+ /// 要反转的数组。
+ /// 反转后的数组。
+ public static object ArrayReverse(object Arr)
+ {
+ var temp = Enumerable.ToList((dynamic)Arr);
+ temp.Reverse();
+ if (Arr is Array)
+ {
+ return Enumerable.ToArray(temp);
+ }
+ return temp;
+ }
+ ///
+ /// 对数组进行排序。
+ ///
+ /// 要排序的数组。
+ /// 排序后的数组。
+ public static object ArraySorting(object Arr)
+ {
+ var temp = Enumerable.ToList((dynamic)Arr);
+ temp = Enumerable.ToList(Enumerable.Order(temp));
+ if (Arr is Array)
+ {
+ return Enumerable.ToArray(temp);
+ }
+ return temp;
+ }
+ ///
+ /// 获取数组的长度。
+ ///
+ /// 要获取长度的数组。
+ /// 数组的长度。
+ public static int GetArrayLength(object Arr)
+ {
+ if (Arr is Array)
+ {
+ return ((Array)Arr).Length;
+ }
+ return ((ICollection)Arr).Count;
+
+ }
+ ///
+ /// 将对象转换为数组。
+ ///
+ /// 要转换的对象。
+ /// 转换后的数组。
+ public static object objectConvertToArray(object Arr)
+ {
+ return Enumerable.ToArray((dynamic)Arr);
+ }
+ ///
+ /// 将对象转换为列表。
+ ///
+ /// 要转换的对象。
+ /// 转换后的列表。
+ public static object objectConvertToList(object Arr)
+ {
+ return Enumerable.ToList((dynamic)Arr);
+ }
+ ///
+ /// 从数组中截取指定长度的子数组。
+ ///
+ /// 要截取的数组。
+ /// 截取开始的位置。
+ /// 要截取的长度。
+ /// 截取得到的子数组。
+ public static object ArrayCapture(object Arr, int StartLocation, int Length)
+ {
+ var temp = Enumerable.ToList((dynamic)Arr);
+ temp = Enumerable.ToList(Enumerable.Take(Enumerable.Skip(temp, StartLocation), Length));
+ if (Arr is Array)
+ {
+ return Enumerable.ToArray(temp);
+ }
+ return temp;
+ }
+
+ #endregion
+
+ }
+}
diff --git a/Command/CommandMath.cs b/Command/CommandMath.cs
new file mode 100644
index 0000000..dfe4264
--- /dev/null
+++ b/Command/CommandMath.cs
@@ -0,0 +1,473 @@
+using Common.Attributes;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Command
+{
+ public enum ComparisonFuncEnum
+ {
+ Big,
+ BigOrEqual,
+ Small,
+ SmallOrEqual,
+ Equal,
+ NotEqual
+ }
+
+ [ADPCommand]
+ public static class CommandMath
+ {
+ #region 返回值是Double类型数据
+ ///
+ /// 两数相加(Param1 + Param2)
+ ///
+ /// 传入值:实数1
+ /// 传入值:实数2
+ /// Param1:9 Param2:2 返回值:11
+ ///
+ public static double ParamAdd(double Param1, double Param2)
+ {
+ return Param1 + Param2;
+ }
+
+ ///
+ /// 两数相减(Param1 - Param2)
+ ///
+ /// 传入值:实数1
+ /// 传入值:实数2
+ /// Param1:9 Param2:2 返回值:7
+ ///
+ public static double ParamReduce(double Param1, double Param2)
+ {
+ return Param1 - Param2;
+ }
+
+ ///
+ /// 两数相乘(Param1 * Param2)
+ ///
+ /// 传入值:实数1
+ /// 传入值:实数2
+ /// Param1:9 Param2:2 返回值:18
+ ///
+ public static double ParamMult(double Param1, double Param2)
+ {
+ return Param1 * Param2;
+ }
+
+ ///
+ /// 两数相除(Param1 / Param2)
+ ///
+ /// 传入值:实数1
+ /// 传入值:实数2
+ /// 传入值:保留几位小数
+ /// Param1:10 Param2:3 Param3:3 返回值:3.333
+ /// Param1:11 Param2:3 Param3:2 返回值:3.67
+ ///
+ public static double ParamDivide(double Param1, double Param2, int Param3)
+ {
+ return Math.Round((Param1 / Param2), Param3);
+ }
+
+ ///
+ /// 两数相除求余(Param1 % Param2)
+ ///
+ /// 传入值:实数1
+ /// 传入值:实数2
+ /// Param1:9 Param2:2 返回值:1
+ ///
+ public static double ParamRemainder(double Param1, double Param2)
+ {
+ return Param1 % Param2;
+ }
+
+ ///
+ /// 实数开根号
+ ///
+ /// 传入值:实数1
+ /// Param1:16 返回值:4
+ ///
+ public static double ParamSquareRoot(double Param1)
+ {
+ return Math.Sqrt(Param1);
+ }
+
+ ///
+ /// 实数的幂运算
+ ///
+ /// 传入值:实数1
+ /// 传入值:实数1需要做几次方
+ /// Param1:3 Param2:2 返回值:9
+ /// Param1:2 Param2:3 返回值:8
+ /// Param1:5 Param2:4 返回值:625
+ ///
+ public static double ParamPow(double Param1, double Param2)
+ {
+ return Math.Pow(Param1, Param2);
+ }
+
+ ///
+ /// 实数的绝对值
+ ///
+ /// 传入值:实数1
+ /// Param1:-3.7 返回值:3.7
+ ///
+ public static double ParamAbs(double Param1)
+ {
+ return Math.Abs(Param1);
+ }
+
+ ///
+ /// 实数的向下取整
+ ///
+ /// 传入值:实数1
+ /// Param1:3.7 返回值:3
+ ///
+ public static double ParamFloor(double Param1)
+ {
+ return Math.Floor(Param1);
+ }
+
+ ///
+ /// 实数的向上取整
+ ///
+ /// 传入值:实数1
+ /// Param1:3.7 返回值:4
+ ///
+ public static double ParamCeiling(double Param1)
+ {
+ return Math.Ceiling(Param1);
+ }
+ #endregion
+
+
+ #region 返回值是String类型的数据
+ ///
+ /// A与B转换为double后比较,返回A比较B的结果
+ ///
+ /// 比较数1
+ /// 比较数2
+ /// 比较方法
+ /// 返回A比较B
+ public static bool Comparison(string a, string b, ComparisonFuncEnum 比较方法)
+ {
+ var aa = Convert.ToDouble(a);
+ var bb = Convert.ToDouble(b);
+
+ switch (比较方法)
+ {
+ case ComparisonFuncEnum.Big:
+ return aa > bb;
+ case ComparisonFuncEnum.BigOrEqual:
+ return aa >= bb;
+ case ComparisonFuncEnum.Small:
+ return aa < bb;
+ case ComparisonFuncEnum.SmallOrEqual:
+ return aa <= bb;
+ case ComparisonFuncEnum.Equal:
+ return aa == bb;
+ case ComparisonFuncEnum.NotEqual:
+ return aa != bb;
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// 加法指令,转换为double后返回o1+o2
+ ///
+ /// 运算数1
+ /// 运算数2
+ /// 返回o1+o2
+ public static string TwoNumberAdd(string o1, string o2)
+ {
+ return (Convert.ToDecimal(o1) + Convert.ToDecimal(o2)).ToString();
+ }
+ ///
+ /// 减法指令,转换为double后返回o1-o2
+ ///
+ /// 运算数1
+ /// 运算数2
+ /// 返回o1-o2
+ public static string TwoNumberReduce(string o1, string o2)
+ {
+ return (Convert.ToDecimal(o1) - Convert.ToDecimal(o2)).ToString();
+ }
+ ///
+ /// 乘法指令,转换为double后返回o1*o2
+ ///
+ /// 运算数1
+ /// 运算数2
+ /// 返回o1*o2
+ public static string TwoNumberMult(string o1, string o2)
+ {
+ return (Convert.ToDecimal(o1) * Convert.ToDecimal(o2)).ToString();
+ }
+ ///
+ /// 除法指令,转换为double后返回o1/o2
+ ///
+ /// 运算数1
+ /// 运算数2
+ /// 返回o1/o2
+ public static string TwoNumberDivide1(string o1, string o2)
+ {
+ return (Convert.ToDecimal(o1) / Convert.ToDecimal(o2)).ToString();
+ }
+ ///
+ /// 转换为double后返回o1/o2
+ ///
+ /// 运算数1
+ /// 运算数2
+ /// 保留几位小数
+ /// 返回o1/o2
+ public static string TwoNumberDivide2(string o1, string o2, int o3)
+ {
+ return (Math.Round((Convert.ToDecimal(o1) / Convert.ToDecimal(o2)), o3)).ToString();
+ }
+ ///
+ /// 返回o1%o2
+ ///
+ /// 运算数1
+ /// 运算数2
+ /// 返回o1%o2
+ public static string TwoNumberRemainder(string o1, string o2)
+ {
+ return (Convert.ToDecimal(o1) % (Convert.ToDecimal(o2))).ToString();
+ }
+ ///
+ /// 计算两个数字的平方。
+ ///
+ /// 第一个数字的字符串表示。
+ /// 第二个数字的字符串表示。
+ /// Param1:3 Param2:2 返回值:9
+ /// Param1:2 Param2:3 返回值:8
+ /// Param1:5 Param2:4 返回值:625
+ /// 返回两个数字的平方的字符串表示。
+ public static string NumberPow(string o1, string o2)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算它们的平方
+ // 最后将结果转换为字符串并返回
+ return Math.Pow(Convert.ToDouble(o1), Convert.ToDouble(o2)).ToString();
+ }
+ ///
+ /// 计算一个数的平方根。
+ ///
+ /// 要计算平方根的数字的字符串表示。
+ /// 返回输入数字的平方根的字符串表示。
+ public static string NumberSqrt(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其平方根。
+ // 最后将结果转换为字符串并返回。
+ return Math.Sqrt(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个数的绝对值。
+ ///
+ /// 要计算绝对值的数字的字符串表示。
+ /// 返回输入数字的绝对值的字符串表示。
+ public static string NumberAbs(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其绝对值。
+ // 最后将结果转换为字符串并返回。
+ return Math.Abs(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个角度的正弦值。
+ ///
+ /// 要计算正弦值的角度的字符串表示(单位为弧度)。
+ /// 返回输入角度的正弦值的字符串表示。
+ public static string NumberSin(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其正弦值。
+ // 最后将结果转换为字符串并返回。
+ return Math.Sin(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个角度的余弦值。
+ ///
+ /// 要计算余弦值的角度的字符串表示(单位为弧度)。
+ /// 返回输入角度的余弦值的字符串表示。
+ public static string NumberCos(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其余弦值。
+ // 最后将结果转换为字符串并返回。
+ return Math.Cos(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个角度的正切值。
+ ///
+ /// 要计算正切值的角度的字符串表示(单位为弧度)。
+ /// 返回输入角度的正切值的字符串表示。
+ public static string NumberTan(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其正切值。
+ // 最后将结果转换为字符串并返回。
+ return Math.Tan(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个数的反正弦值。
+ ///
+ /// 要计算反正弦值的数的字符串表示。
+ /// 返回输入数的反正弦值的字符串表示(单位为弧度)。
+ public static string NumberASin(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其反正弦值。
+ // 最后将结果转换为字符串并返回。
+ return Math.Asin(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个数的反余弦值。
+ ///
+ /// 要计算反余弦值的数的字符串表示。
+ /// 返回输入数的反余弦值的字符串表示(单位为弧度)。
+ public static string NumberACos(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其反余弦值。
+ // 最后将结果转换为字符串并返回。
+ return Math.Acos(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个数的反正切值。
+ ///
+ /// 要计算反正切值的数的字符串表示。
+ /// 返回输入数的反正切值的字符串表示(单位为弧度)。
+ public static string NumberATan(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其反正切值。
+ // 最后将结果转换为字符串并返回。
+ return Math.Atan(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算 e 的给定次幂。
+ ///
+ /// e 的指数的字符串表示。
+ /// 返回 e 的给定次幂的字符串表示。
+ public static string eIndex(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算 e 的给定次幂。
+ // 最后将结果转换为字符串并返回。
+ return Math.Exp(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个数的自然对数。
+ ///
+ /// 要计算自然对数的数的字符串表示。
+ /// 返回输入数的自然对数的字符串表示。
+ public static string NumberNaturalLogarithm(string o1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其自然对数。
+ // 最后将结果转换为字符串并返回。
+ return Math.Log(Convert.ToDouble(o1)).ToString();
+ }
+ ///
+ /// 计算一个数在指定基数下的对数。
+ ///
+ /// 要计算对数的数的字符串表示。
+ /// 对数的基数的字符串表示。
+ /// 返回输入数在指定基数下的对数的字符串表示。
+ public static string NumberLogarithm(string o1, string Base)
+ {
+ // 将输入的字符串转换为双精度浮点数,并计算其在指定基数下的对数。
+ // 最后将结果转换为字符串并返回。
+ return Math.Log(Convert.ToDouble(o1), Convert.ToDouble(Base)).ToString();
+ }
+ ///
+ /// 对输入数字进行四舍五入取整。
+ ///
+ /// 要进行取整操作的数字的字符串表示。
+ /// 返回经过四舍五入取整后的结果的字符串表示。
+ public static string NumberRoundToNearest(string n1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并进行四舍五入取整。
+ // 最后将结果转换为字符串并返回。
+ return Math.Round(Convert.ToDouble(n1)).ToString();
+ }
+
+ ///
+ /// 对输入数字进行向上舍入取整。
+ ///
+ /// 要进行取整操作的数字的字符串表示。
+ /// 返回经过向上舍入取整后的结果的字符串表示。
+ public static string NumberRoundUp(string n1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并进行向上舍入取整。
+ // 最后将结果转换为字符串并返回。
+ return Math.Ceiling(Convert.ToDouble(n1)).ToString();
+ }
+
+ ///
+ /// 对输入数字进行向下舍入取整。
+ ///
+ /// 要进行取整操作的数字的字符串表示。
+ /// 返回经过向下舍入取整后的结果的字符串表示。
+ public static string NumberRoundDown(string n1)
+ {
+ // 将输入的字符串转换为双精度浮点数,并进行向下舍入取整。
+ // 最后将结果转换为字符串并返回。
+ return Math.Floor(Convert.ToDouble(n1)).ToString();
+ }
+
+ ///
+ /// 获取圆周率π的字符串表示。
+ ///
+ /// 返回圆周率π的字符串表示。
+ public static string PI()
+ {
+ // 返回圆周率π的字符串表示。
+ return Math.PI.ToString();
+ }
+
+ //public static Random random1 = new Random();
+ ///
+ /// 生成一个随机整数。
+ ///
+ /// 返回生成的随机整数的字符串表示。
+ public static string RandomNumber_Int()
+ {
+ Random random = new Random();
+ // 使用随机数生成器生成一个随机整数,并将其转换为字符串表示。
+ return random.Next().ToString();
+ }
+
+ ///
+ /// 生成一个指定范围内的随机整数。
+ ///
+ /// 随机数生成范围的最小值。
+ /// 随机数生成范围的最大值(不包含)。
+ /// 返回生成的指定范围内的随机整数的字符串表示。
+ public static string RandomNumber_RangeInt(int 最小, int 最大)
+ {
+ Random random = new Random();
+ // 使用随机数生成器生成一个指定范围内的随机整数,并将其转换为字符串表示。
+ return random.Next(最小, 最大).ToString();
+ }
+
+ ///
+ /// 生成一个随机小数。
+ ///
+ /// 返回生成的随机小数的字符串表示。
+ public static string RandomNumber_Decimal()
+ {
+ Random random = new Random();
+ // 使用随机数生成器生成一个随机小数,并将其转换为字符串表示。
+ return random.NextDouble().ToString();
+ }
+
+ ///
+ /// 计算给定算式的结果。
+ ///
+ /// 要计算的算式的字符串表示。
+ /// 返回计算结果的字符串表示。
+ public static string FormulaCalculation(string s)
+ {
+ // 创建一个 DataTable 实例来进行算式的计算。
+ DataTable dataTable = new DataTable();
+ // 使用 DataTable 的 Compute 方法计算给定的算式,并将结果转换为字符串表示。
+ return dataTable.Compute(s, "").ToString();
+ }
+ #endregion
+ }
+}
diff --git a/Command/CommandRadixChange.cs b/Command/CommandRadixChange.cs
new file mode 100644
index 0000000..22a008d
--- /dev/null
+++ b/Command/CommandRadixChange.cs
@@ -0,0 +1,153 @@
+using Common.Attributes;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Command
+{
+
+ [ADPCommand]
+ public static class CommandRadixChange
+ {
+ #region 进制转换
+ ///
+ /// 二进制 转换成 八进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"101010" 返回值:"52"
+ ///
+ public static string TwoRadix_ConvertTo_EightRadix(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 2),8);
+ }
+
+ ///
+ /// 二进制 转换成 十进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"101010" 返回值:"42"
+ ///
+ public static string TwoRadix_ConvertTo_TenRadix(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 2));
+ }
+
+ ///
+ /// 二进制 转换成 十六进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"101010" 返回值:"2A"
+ ///
+ public static string TwoRadix_ConvertTo_SixteenRadix(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 2), 16);
+ }
+
+ ///
+ /// 八进制 转换成 二进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"88" 返回值:"1001000"
+ ///
+ public static string EightRadix_ConvertTo_TwoRadix(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 8), 2);
+ }
+
+ ///
+ /// 八进制 转换成 十进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"88" 返回值:"72"
+ ///
+ public static double EightRadix_ConvertTo_TenRadix(string Param1)
+ {
+ double Value = Convert.ToInt32(Param1, 8);
+ return Value;
+ }
+
+ ///
+ /// 八进制 转换成 十六进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"88" 返回值:"48"
+ ///
+ public static string EightRadix_ConvertTo_SixteenRadix(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 8), 16);
+ }
+
+ ///
+ /// 十进制 转换成 二进制
+ ///
+ /// 传入值:整型数值
+ /// Param1:60 返回值:"111100"
+ ///
+ public static string TenRadix_ConvertTo_TwoRadix(int Param1)
+ {
+ return Convert.ToString(Param1, 2);
+ }
+
+ ///
+ /// 十进制 转换成 八进制
+ ///
+ /// 传入值:整型数值
+ /// Param1:60 返回值:"74"
+ ///
+ public static string TenRadix_ConvertTo_EightRadix(int Param1)
+ {
+ return Convert.ToString(Param1, 8);
+ }
+
+ ///
+ /// 十进制 转换成 十六进制
+ ///
+ /// 传入值:整型数值
+ /// Param1:60 返回值:"3C"
+ ///
+ public static string TenRadix_ConvertTo_SixteenRadix(int Param1)
+ {
+ return Convert.ToString(Param1, 16);
+ }
+
+ ///
+ /// 十六进制 转换成 二进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"6F" 返回值:"1101111"
+ ///
+ public static string SixteenRadix_ConvertTo_Radix2(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 16), 2);
+ }
+
+ ///
+ /// 十六进制 转换成 八进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"6F" 返回值:"157"
+ ///
+ public static string SixteenRadix_ConvertTo_EightRadix8(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 16), 8);
+ }
+
+ ///
+ /// 十六进制 转换成 十进制
+ ///
+ /// 传入值:字符类型
+ /// Param1:"6F" 返回值:"111"
+ ///
+ public static string SixteenRadix_ConvertTo_TenRadix(string Param1)
+ {
+ return Convert.ToString(Convert.ToInt32(Param1, 16));
+ }
+
+
+ #endregion
+
+ }
+}
diff --git a/Command/CommandStringProcessing.cs b/Command/CommandStringProcessing.cs
new file mode 100644
index 0000000..752390d
--- /dev/null
+++ b/Command/CommandStringProcessing.cs
@@ -0,0 +1,196 @@
+using Common.Attributes;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Command
+{
+
+ [ADPCommand]
+ public static class CommandStringProcessing
+ {
+ ///
+ ///获取值
+ ///
+ ///要获取的值。
+ ///returns>获取值(字符串)。
+ public static string GetValue(object obj)
+ {
+ return Convert.ToString(obj) ?? string.Empty;
+ }
+
+ ///
+ /// 在原字符串中查找指定字符的位置。
+ ///
+ /// 要查找的字符串。
+ /// 要查找的字符。
+ /// 字符在字符串中的位置,如果未找到则返回 -1。
+ public static int FindStr(string str, string findStr)
+ {
+ return str.IndexOf(findStr);
+ }
+
+ ///
+ /// 将原字符串中的指定字符替换为新字符。
+ ///
+ /// 要替换的字符串。
+ /// 要被替换的字符。
+ /// 替换后的新字符。
+ /// 替换后的字符串。
+ public static string ReplaceStr(string str, string ReplaceStr, string ReplacedStr)
+ {
+ return str.Replace(ReplaceStr, ReplacedStr);
+ }
+
+ ///
+ /// 从原字符串中移除指定位置开始的指定个数的字符。
+ ///
+ /// 要操作的字符串。
+ /// 开始移除字符的位置。
+ /// 要移除的字符个数。
+ /// 移除后的字符串。
+ public static string RemoveStr(string str, int startLocation, int removeQty)
+ {
+ return str.Remove(startLocation, removeQty);
+ }
+
+ ///
+ /// 在原字符串的指定位置插入新字符串。
+ ///
+ /// 要操作的字符串。
+ /// 要插入字符串的位置。
+ /// 要插入的字符串。
+ /// 插入后的字符串。
+ public static string InsertStr(string str, int startLocation, string newStr)
+ {
+ return str.Insert(startLocation, newStr);
+ }
+
+ ///
+ /// 从原字符串中截取指定位置开始的指定个数的字符。
+ ///
+ /// 要操作的字符串。
+ /// 要截取的起始位置。
+ /// 要截取的字符个数。
+ /// 截取后的字符串。
+ public static string CaptureStr(string str, int startLocation, int qty)
+ {
+ return str.Substring(startLocation, qty);
+ }
+
+ ///
+ /// 将字符串转换为小写形式。
+ ///
+ /// 要转换的字符串。
+ /// 转换为小写后的字符串。
+ public static string StrConvertToLower(string str)
+ {
+ return str.ToLower();
+ }
+
+ ///
+ /// 将字符串转换为大写形式。
+ ///
+ /// 要转换的字符串。
+ /// 转换为大写后的字符串。
+ public static string StrConvertToUpper(string str)
+ {
+ return str.ToUpper();
+ }
+
+ ///
+ /// 获取字符串的长度。
+ ///
+ /// 要获取长度的字符串。
+ /// 字符串的长度。
+ public static int GetStrLength(string str)
+ {
+ return str.Length;
+ }
+
+ ///
+ /// 去除字符串两端的空白字符。
+ ///
+ /// 要去除空白字符的字符串。
+ /// 去除空白字符后的字符串。
+ public static string StrRemoveLeadingAndTrailingWhitespaces(string str)
+ {
+ return str.Trim();
+ }
+
+ ///
+ /// 将字节数组转换为字符串,使用 UTF-8 编码。
+ ///
+ /// 要转换的字节数组。
+ /// 转换后的字符串。
+ public static string ByteArrayToStr_UTF8(byte[] arr)
+ {
+ return Encoding.UTF8.GetString(arr);
+ }
+
+ ///
+ /// 将字节数组转换为字符串,使用 GB18030 编码。
+ ///
+ /// 要转换的字节数组。
+ /// 转换后的字符串。
+ public static string ByteArrayToStr_GB18030(byte[] arr)
+ {
+ return Encoding.GetEncoding("gb18030").GetString(arr);
+ }
+
+ ///
+ /// 将字节数组转换为字符串,使用自选的编码类型。
+ ///
+ /// 要转换的字节数组。
+ /// 要使用的编码类型。
+ /// 转换后的字符串。
+ public static string ByteArrayToStr_SelfSelectedEncoding(byte[] arr, string encodingType)
+ {
+ return Encoding.GetEncoding(encodingType).GetString(arr);
+ }
+
+ ///
+ /// 将字节数组转换为16进制字符串
+ ///
+ /// 要转换的字节数组。
+ /// 转换后的字符串。
+ public static string ByteArrayTo_HexStr(byte[] str)
+ {
+ return Convert.ToInt32(str.ToString(),2).ToString("X2");
+ }
+
+ ///
+ /// 将字符串转换为字节数组,使用 UTF-8 编码。
+ ///
+ /// 要转换的字符串。
+ /// 转换后的字节数组。
+ public static byte[] StrToByteArray_UTF8(string str)
+ {
+ return Encoding.UTF8.GetBytes(str);
+ }
+ ///
+ /// 将字符串转换为字节数组,使用 GB18030 编码。
+ ///
+ /// 要转换的字符串。
+ /// 转换后的字节数组。
+ public static byte[] StrToByteArray_GB18030(string str)
+ {
+ return Encoding.GetEncoding("gb18030").GetBytes(str);
+ }
+
+ ///
+ /// 将字符串转换为字节数组,使用自选的编码类型。
+ ///
+ /// 要转换的字符串。
+ /// 要使用的编码类型。
+ /// 转换后的字节数组。
+ public static byte[] StrToByteArray_SelfSelectedEncoding(string str, string encodingType)
+ {
+ return Encoding.GetEncoding(encodingType).GetBytes(str);
+ }
+
+ }
+}
diff --git a/Command/CommandSystem.cs b/Command/CommandSystem.cs
new file mode 100644
index 0000000..3625bdc
--- /dev/null
+++ b/Command/CommandSystem.cs
@@ -0,0 +1,463 @@
+using Common.Attributes;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+
+namespace Command
+{
+ [ADPCommand]
+ public static class CommandSystem
+ {
+ #region 类型转换
+ ///
+ /// int转化为double
+ ///
+ /// 传入值:整型数值
+ /// Param1:40 返回值:40
+ ///
+ public static double Int_ConvertTo_Double(int Param1)
+ {
+ return Convert.ToDouble(Param1);
+ }
+
+ ///
+ /// double转化为Int16
+ ///
+ /// 传入值:实数数值
+ /// Param1:4 返回值:4
+ /// Param1:4.67 返回值:4
+ ///
+ public static Int16 Double_ConvertTo_Int16(double Param1)
+ {
+ Int16 Value = Convert.ToInt16(Param1);
+ return Value;
+ }
+
+ ///
+ /// double转化为int,即Int32
+ ///
+ /// 传入值:实数数值
+ /// Param1:4 返回值:4
+ /// Param1:4.67 返回值:4
+ ///
+ public static int Double_ConvertTo_Int(double Param1)
+ {
+ int Value = Convert.ToInt32(Param1);
+ return Value;
+ }
+
+ ///
+ /// double转化为Int64
+ ///
+ /// 传入值:实数数值
+ /// Param1:4 返回值:4
+ /// Param1:4.67 返回值:4
+ ///
+ public static Int64 Double_ConvertTo_Int64(double Param1)
+ {
+ Int64 Value = Convert.ToInt64(Param1);
+ return Value;
+ }
+
+ ///
+ /// float转化为double
+ ///
+ /// 传入值:浮点数值
+ /// Param1:4.0 返回值:4
+ /// Param1:4.2 返回值:4.2
+ ///
+ public static double Float_ConvertTo_Double(float Param1)
+ {
+ double Value = Convert.ToDouble(Param1);
+ return Value;
+ }
+
+ ///
+ /// 判断string转化为int,转换成功返回 true,失败返回 false
+ ///
+ /// 传入值:字符串
+ /// Param1:"2" 返回值:true
+ /// Param1:"abc" 返回值:false
+ ///
+ public static bool BoolString_ConvertTo_Int(string Param1)
+ {
+ bool Value = int.TryParse(Param1, out int intValue);
+ return Value;
+ }
+
+ ///
+ /// string转化为int
+ ///
+ /// 传入值:字符串
+ /// Param1:"2" 返回值:2
+ ///
+ public static int String_ConvertTo_Int(string Param1)
+ {
+ int Value = Convert.ToInt32(Param1);
+ return Value;
+ }
+
+ ///
+ /// int转化为string
+ ///
+ /// 传入值:整型数值
+ /// Param1:2 返回值:"2"
+ ///
+ public static string Int_ConvertTo_String(int Param1)
+ {
+ string Value = Convert.ToString(Param1);
+ return Value;
+ }
+
+ ///
+ /// 判断string转化为double,转换成功返回 true,失败返回 false
+ ///
+ /// 传入值:字符串
+ /// Param1:"2" 返回值:true
+ /// Param1:"2.1" 返回值:true
+ /// Param1:"abc" 返回值:false
+ ///
+ public static bool BoolString_ConvertTo_Double(string Param1)
+ {
+ bool Value = double.TryParse(Param1, out double intValue);
+ return Value;
+ }
+
+ ///
+ /// string转化为double
+ ///
+ /// 传入值:字符串
+ /// Param1:"2" 返回值:2
+ /// Param1:"2.1" 返回值:2.1
+ ///
+ public static double String_ConvertTo_Double(string Param1)
+ {
+ double Value = Convert.ToDouble(Param1);
+ return Value;
+ }
+
+ ///
+ /// double转化为string
+ ///
+ /// 传入值:实数数值
+ /// Param1:2 返回值:"2"
+ /// Param1:2.1 返回值:"2.1"
+ ///
+ public static string Double_ConvertTo_String(double Param1)
+ {
+ string Value = Convert.ToString(Param1);
+ return Value;
+ }
+
+ ///
+ /// float转化为string
+ ///
+ /// 传入值:浮点数值
+ /// Param1:2.0 返回值:"2.0"
+ /// Param1:2.1 返回值:"2.1"
+ ///
+ public static string Float_ConvertTo_String(float Param1)
+ {
+ string Value = Convert.ToString(Param1);
+ return Value;
+ }
+
+ ///
+ /// 判断string转化为datetime,转换成功返回 true,失败返回 false
+ ///
+ /// 传入值:时间字符串
+ /// Param1:"2025-08-14" 返回值:true
+ /// Param1:"2025-08-14 13:14:15" 返回值:true
+ /// Param1:"abc" 返回值:false
+ ///
+ public static bool BoolString_ConvertTo_Datetime(string Param1)
+ {
+ bool Value = DateTime.TryParse(Param1, out DateTime DateTimeValue);
+ return Value;
+ }
+
+ ///
+ /// string转化为datetime
+ ///
+ /// 传入值:时间字符串
+ /// Param1:"2025-08-14" 返回值:2025/8/14 0:00:00
+ /// Param1:"2025-08-14 13:14:15" 返回值:2025/8/14 13:14:15
+ ///
+ public static DateTime String_ConvertTo_Datetime(string Param1)
+ {
+ DateTime Value = Convert.ToDateTime(Param1);
+ return Value;
+ }
+
+ ///
+ /// datetime转化为string
+ ///
+ /// 传入值:时间
+ /// Param1:2025/8/14 13:14:15 返回值:"2025/08/14 13:14:15"
+ ///
+ public static string Datetime_ConvertTo_String1(DateTime Param1)
+ {
+ string Value = Convert.ToString(Param1);
+ return Value;
+ }
+
+ ///
+ /// datetime转化为string
+ ///
+ /// 传入值:时间
+ /// 传入值:字符串时间格式
+ /// Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd" 返回值:"2025-08-14"
+ /// Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd hh:mm:ss" 返回值:"2025-08-14 01:14:15"
+ /// Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd HH:mm:ss" 返回值:"2025-08-14 13:14:15"
+ /// Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyyMMdd" 返回值:"20250814"
+ ///
+ public static string Datetime_ConvertTo_String2(DateTime Param1,string DateTimeFormat)
+ {
+ string Value = Param1.ToString(DateTimeFormat);
+ return Value;
+ }
+
+ ///
+ /// bool转化为string
+ ///
+ /// 传入值:布尔值
+ /// Param1:true 返回值:"true"
+ /// Param1:false 返回值:"false"
+ ///
+ public static string Bool_ConvertTo_String(bool Param1)
+ {
+ string Value = Convert.ToString(Param1);
+ return Value;
+ }
+
+ ///
+ /// string转化为bool
+ ///
+ /// 传入值:字符串
+ /// Param1:"true" 返回值:true
+ /// Param1:"false" 返回值:false
+ /// Param1:"abc" 返回值:false
+ ///
+ public static bool String_ConvertTo_Bool(string Param1)
+ {
+ bool Value = bool.TryParse(Param1, out bool boolValue);
+ return Value;
+ }
+
+ ///
+ /// 将无符号整型转换为整型
+ ///
+ ///
+ ///
+ public static int Uint_ConvertTo_Int(uint u)
+ {
+ return (int)u;
+ }
+
+ ///
+ /// 将对象转换为字符串
+ ///
+ ///
+ ///
+ public static string Object_ConvertTo_String(object obj)
+ {
+ return Convert.ToString(obj);
+ }
+
+ ///
+ /// 将字符串内容转换为byte
+ ///
+ /// 要转换的内容
+ ///
+ public static byte String_ConvertTo_Byte(string s)
+ {
+ return Convert.ToByte(s);
+ }
+
+ ///
+ /// 转换为short(int16)
+ ///
+ /// 要转换的内容
+ ///
+ public static short String_ConvertTo_Short(string s)
+ {
+ return Convert.ToInt16(s);
+ }
+
+ ///
+ /// 将字符串转换为无符号整数。
+ ///
+ /// 要转换的字符串。
+ /// 转换后的无符号整数。
+ public static uint String_ConvertTo_Uint(string s)
+ {
+ return Convert.ToUInt32(s);
+ }
+
+ ///
+ /// 将指定进制的字符串转换为整数。
+ ///
+ /// 要转换的字符串。
+ /// 源字符串的进制。只能为2,8,10,16
+ /// 转换后的整数。
+ public static int XRadixString_ConvertTo_Int(string s, int XRadix)
+ {
+ return Convert.ToInt32(s, XRadix);
+ }
+ #endregion
+
+
+ #region 逻辑操作
+ ///
+ /// 执行整数的异或操作。
+ ///
+ /// 第一个整数。
+ /// 第二个整数。
+ /// 异或操作结果。
+ public static int XOR(int a, int b)
+ {
+ return a ^ b;
+ }
+
+ ///
+ /// 执行整数的位或操作。
+ ///
+ /// 第一个整数。
+ /// 第二个整数。
+ /// 位或操作结果。
+ public static int BitwiseOR(int a, int b)
+ {
+ return a | b;
+ }
+
+ ///
+ /// 执行整数的位与操作。
+ ///
+ /// 第一个整数。
+ /// 第二个整数。
+ /// 位与操作结果。
+ public static int BitwiseAND(int a, int b)
+ {
+ return a & b;
+ }
+
+ ///
+ /// 将整数向左移动指定位数。
+ ///
+ /// 要移动的整数。
+ /// 左移的位数。
+ /// 移动后的结果。
+ public static int LeftShift(int a, int BitwiseLeftShift)
+ {
+ return a << BitwiseLeftShift;
+ }
+ ///
+ /// 将整数向右移动指定位数。
+ ///
+ /// 要移动的整数。
+ /// 右移的位数。
+ /// 移动后的结果。
+ public static int RightShift(int a, int BitwiseRightShift)
+ {
+ return a >> BitwiseRightShift;
+ }
+ ///
+ /// 执行逻辑或操作。
+ ///
+ /// 第一个逻辑值。
+ /// 第二个逻辑值。
+ /// 逻辑或操作结果。
+ public static bool LogicalOR(bool a, bool b)
+ {
+ return a || b;
+ }
+
+ ///
+ /// 执行逻辑与操作。
+ ///
+ /// 第一个逻辑值。
+ /// 第二个逻辑值。
+ /// 逻辑与操作结果。
+ public static bool LogicalAND(bool a, bool b)
+ {
+ return a && b;
+ }
+
+ ///
+ /// 将字节的高低位对调。
+ ///
+ /// 要对调的字节。
+ /// 对调后的字节。
+ public static byte ByteHighAndLowBitSwap(byte b)
+ {
+ return (byte)(((b & 0x0F) << 4) | ((b & 0xF0) >> 4));
+ }
+
+ ///
+ /// 翻转字节数组中的元素顺序。
+ ///
+ /// 要翻转的字节数组。
+ /// 翻转后的字节数组。
+ public static byte[] ByteArrayReverse(byte[] b)
+ {
+ return b.Reverse().ToArray();
+ }
+
+ ///
+ /// 将当前线程挂起指定的时间。
+ ///
+ /// 要延时的时间,以毫秒为单位。
+ public static void Delay_ms(int time)
+ {
+ Thread.Sleep(time);
+ }
+
+ ///
+ /// 将当前线程挂起指定的时间。
+ ///
+ /// 要延时的时间,以秒为单位。
+ public static void Delay_s(int time)
+ {
+ Thread.Sleep(time * 1000);
+ }
+
+ ///
+ /// 将当前线程挂起指定的时间。
+ ///
+ /// 要延时的时间,以分钟为单位。
+ public static void Delay_min(int time)
+ {
+ Thread.Sleep(time * 1000 * 60);
+ }
+
+ ///
+ /// 将当前线程挂起指定的时间。
+ ///
+ /// 要延时的时间,以小时为单位。
+ public static void Delay_hour(int time)
+ {
+ Thread.Sleep(time * 1000 * 60 * 60);
+ }
+
+ ///
+ /// 将当前线程挂起指定的时间。
+ ///
+ /// 要延时的时间,以天为单位。
+ public static void Delay_day(int time)
+ {
+ Thread.Sleep(time * 1000 * 60 * 60 * 24);
+ }
+
+ #endregion
+
+
+
+ #region
+
+ #endregion
+ }
+}
diff --git a/Command/CommandTime.cs b/Command/CommandTime.cs
new file mode 100644
index 0000000..1013fae
--- /dev/null
+++ b/Command/CommandTime.cs
@@ -0,0 +1,95 @@
+using Common.Attributes;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Command
+{
+
+ [ADPCommand]
+ public static class CommandTime
+ {
+ #region 时间处理
+ ///
+ /// 获取给定时间段的总毫秒数。
+ ///
+ /// 要获取总毫秒数的时间段。
+ /// 返回时间段的总毫秒数。
+ public static double GetTimePeriodMilliseconds(TimeSpan TimePeriod)
+ {
+ // 返回时间段的总毫秒数。
+ return TimePeriod.TotalMilliseconds;
+ }
+
+ ///
+ /// 获取给定时间段的总秒数。
+ ///
+ /// 要获取总秒数的时间段。
+ /// 返回时间段的总秒数。
+ public static double GetTimePeriodSeconds(TimeSpan TimePeriod)
+ {
+ // 返回时间段的总秒数。
+ return TimePeriod.TotalSeconds;
+ }
+
+ ///
+ /// 获取给定时间段的总分钟数。
+ ///
+ /// 要获取总分钟数的时间段。
+ /// 返回时间段的总分钟数。
+ public static double GetTimePeriodMinutes(TimeSpan TimePeriod)
+ {
+ // 返回时间段的总分钟数。
+ return TimePeriod.TotalMinutes;
+ }
+
+ ///
+ /// 获取给定时间段的总小时数。
+ ///
+ /// 要获取总小时数的时间段。
+ /// 返回时间段的总小时数。
+ public static double GetTimePeriodHours(TimeSpan TimePeriod)
+ {
+ // 返回时间段的总小时数。
+ return TimePeriod.TotalHours;
+ }
+
+ ///
+ /// 获取给定时间段的总天数。
+ ///
+ /// 要获取总天数的时间段。
+ /// 返回时间段的总天数。
+ public static double GetTimePeriodDays(TimeSpan TimePeriod)
+ {
+ // 返回时间段的总天数。
+ return TimePeriod.TotalDays;
+ }
+
+ ///
+ /// 计算两个日期时间之间的时间差。
+ ///
+ /// 第一个日期时间。
+ /// 第二个日期时间。
+ /// 返回两个日期时间之间的时间差。
+ public static TimeSpan GetDateReduce(DateTime date1, DateTime date2)
+ {
+ // 返回第一个日期时间减去第二个日期时间的时间差。
+ return date1 - date2;
+ }
+
+ ///
+ /// 获取当前系统时间。
+ ///
+ /// 返回当前系统时间。
+ public static DateTime GetNowTime()
+ {
+ // 返回当前系统时间。
+ return DateTime.Now;
+ }
+ #endregion
+
+ }
+}
diff --git a/Command/Delay.cs b/Command/Delay.cs
new file mode 100644
index 0000000..dc710bf
--- /dev/null
+++ b/Command/Delay.cs
@@ -0,0 +1,58 @@
+using Common.Attributes;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Command
+{
+ [ADPCommand]
+ public static class Delay
+ {
+ ///
+ /// 等待_毫秒
+ ///
+ ///
+ ///
+ ///
+ public static async Task Delay_ms(int millisecond, CancellationToken ct)
+ {
+ await Task.Delay(millisecond, ct);
+ }
+
+ ///
+ /// 等待_秒
+ ///
+ ///
+ ///
+ ///
+ public static async Task Delay_s(float second, CancellationToken ct)
+ {
+ await Task.Delay((int)second * 1000, ct);
+ }
+
+ ///
+ /// 等待_分钟
+ ///
+ ///
+ ///
+ ///
+ public static async Task Delay_m(float minnute, CancellationToken ct)
+ {
+ await Task.Delay((int)minnute * 60 * 1000, ct);
+ }
+
+ ///
+ /// 等待_小时
+ ///
+ ///
+ ///
+ ///
+ public static async Task Delay_h(float minnute, CancellationToken ct)
+ {
+ await Task.Delay((int)minnute * 60 * 1000, ct);
+ }
+ }
+}
diff --git a/Common/Attributes/ADPCommandAttribute.cs b/Common/Attributes/ADPCommandAttribute.cs
new file mode 100644
index 0000000..0bce9fe
--- /dev/null
+++ b/Common/Attributes/ADPCommandAttribute.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Common.Attributes
+{
+ ///
+ /// 标记可加载到指令集中的类或方法
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
+ public sealed class ADPCommandAttribute : Attribute
+ {
+ ///
+ /// 指令描述(用于工具提示)
+ ///
+ public string Description { get; }
+
+ ///
+ /// 指令分类(用于树形视图分组)
+ ///
+ public string Category { get; set; } = "默认分类";
+
+ public ADPCommandAttribute() { }
+
+ public ADPCommandAttribute(string description)
+ {
+ Description = description;
+ }
+ }
+}
diff --git a/Common/Common.csproj b/Common/Common.csproj
new file mode 100644
index 0000000..7b5d323
--- /dev/null
+++ b/Common/Common.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/Common/MiniDump.cs b/Common/MiniDump.cs
new file mode 100644
index 0000000..9abaa41
--- /dev/null
+++ b/Common/MiniDump.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Common
+{
+ public static class MiniDump
+ {
+ [Flags]
+ public enum Option : uint
+ {
+ Normal = 0x00000000,
+ WithDataSegs = 0x00000001,
+ WithFullMemory = 0x00000002,
+ WithHandleData = 0x00000004,
+ FilterMemory = 0x00000008,
+ ScanMemory = 0x00000010,
+ WithUnloadedModules = 0x00000020,
+ WithIndirectlyReferencedMemory = 0x00000040,
+ FilterModulePaths = 0x00000080,
+ WithProcessThreadData = 0x00000100,
+ WithPrivateReadWriteMemory = 0x00000200,
+ WithoutOptionalData = 0x00000400,
+ WithFullMemoryInfo = 0x00000800,
+ WithThreadInfo = 0x00001000,
+ WithCodeSegs = 0x00002000,
+ WithoutAuxiliaryState = 0x00004000,
+ WithFullAuxiliaryState = 0x00008000,
+ WithPrivateWriteCopyMemory = 0x00010000,
+ IgnoreInaccessibleMemory = 0x00020000,
+ ValidTypeFlags = 0x0003ffff,
+ }
+
+ enum ExceptionInfo
+ {
+ None,
+ Present
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64!
+ struct MiniDumpExceptionInformation
+ {
+ public uint ThreadId;
+ public IntPtr ExceptionPointers;
+ [MarshalAs(UnmanagedType.Bool)]
+ public bool ClientPointers;
+ }
+
+ // MiniDumpWriteDump function declarations
+ [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
+ static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);
+
+ [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
+ static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
+
+ [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
+ static extern uint GetCurrentThreadId();
+
+ static bool Write(SafeHandle fileHandle, Option options, ExceptionInfo exceptionInfo, Exception exception = null)
+ {
+ Process currentProcess = Process.GetCurrentProcess();
+ IntPtr currentProcessHandle = currentProcess.Handle;
+ uint currentProcessId = (uint)currentProcess.Id;
+
+ MiniDumpExceptionInformation exp;
+ exp.ThreadId = GetCurrentThreadId();
+ exp.ClientPointers = false;
+ exp.ExceptionPointers = IntPtr.Zero;
+
+ if (exceptionInfo == ExceptionInfo.Present && exception != null)
+ {
+ // Get exception pointers
+ exp.ExceptionPointers = Marshal.GetExceptionPointers();
+ }
+
+ // Write dump
+ bool result = exp.ExceptionPointers == IntPtr.Zero
+ ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
+ : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint)options, ref exp, IntPtr.Zero, IntPtr.Zero);
+
+ if (exception != null)
+ {
+
+ }
+
+ return result;
+ }
+
+
+
+ public static Boolean TryDump(String dmpPath, Option dmpType = Option.Normal, Exception exception = null)
+ {
+ var path = Path.Combine(Environment.CurrentDirectory, dmpPath);
+ var dir = Path.GetDirectoryName(path);
+ if (dir != null && !Directory.Exists(dir))
+ {
+ Directory.CreateDirectory(dir);
+ }
+
+ using (var fs = new FileStream(path, FileMode.Create))
+ {
+ return Write(fs.SafeFileHandle, dmpType, exception == null ? ExceptionInfo.None : ExceptionInfo.Present, exception);
+ }
+ }
+ }
+
+}
diff --git a/Common/Tool/ExpressionEvaluator.cs b/Common/Tool/ExpressionEvaluator.cs
new file mode 100644
index 0000000..7b0bf17
--- /dev/null
+++ b/Common/Tool/ExpressionEvaluator.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using NCalc;
+
+namespace Common.Tools
+{
+ public class ExpressionEvaluator
+ {
+ public static bool EvaluateExpression(string expression, Dictionary? variables = null)
+ {
+ try
+ {
+ // 预处理:替换中文变量名为英文别名
+ var (processedExpression, processedVariables) = PreprocessExpression(expression, variables);
+
+ var expr = new Expression(processedExpression, EvaluateOptions.IgnoreCase);
+
+ if (processedVariables != null)
+ {
+ foreach (var kvp in processedVariables)
+ {
+ expr.Parameters[kvp.Key] = kvp.Value;
+ }
+ }
+
+ if (expr.HasErrors())
+ {
+ throw new ArgumentException($"条件表达式格式错误: {expr.Error}");
+ }
+
+ var result = expr.Evaluate();
+ return Convert.ToBoolean(result);
+ }
+ catch (Exception ex)
+ {
+ throw new ArgumentException($"条件表达式异常: {ex.Message}");
+ }
+ }
+
+ private static (string, Dictionary) PreprocessExpression(
+ string expression,
+ Dictionary? variables)
+ {
+ if (variables == null || variables.Count == 0)
+ return (expression, variables ?? new Dictionary());
+
+ // 生成变量名映射 (中文 -> 英文别名)
+ var chineseToAlias = new Dictionary();
+ var aliasToValue = new Dictionary();
+ int counter = 1;
+
+ foreach (var key in variables.Keys)
+ {
+ if (ContainsChinese(key))
+ {
+ string alias = $"var_{counter}";
+ counter++;
+ chineseToAlias[key] = alias;
+ aliasToValue[alias] = variables[key];
+ }
+ }
+
+ // 如果没有中文变量名,直接返回原始数据
+ if (chineseToAlias.Count == 0)
+ return (expression, variables);
+
+ // 替换表达式中的中文变量名
+ string processedExpression = expression;
+ foreach (var pair in chineseToAlias)
+ {
+ // 使用正则确保完整匹配变量名
+ string pattern = $@"\b{Regex.Escape(pair.Key)}\b";
+ processedExpression = Regex.Replace(
+ processedExpression,
+ pattern,
+ pair.Value,
+ RegexOptions.Compiled
+ );
+ }
+
+ // 创建新变量字典(英文别名 + 原始英文变量)
+ var newVariables = new Dictionary(aliasToValue);
+ foreach (var key in variables.Keys)
+ {
+ if (!ContainsChinese(key))
+ {
+ newVariables[key] = variables[key];
+ }
+ }
+
+ return (processedExpression, newVariables);
+ }
+
+ // 检查字符串是否包含中文字符
+ private static bool ContainsChinese(string text)
+ {
+ return text.Any(c => c >= 0x4E00 && c <= 0x9FFF);
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Base/IBaseInterface.cs b/DeviceCommand/Base/IBaseInterface.cs
new file mode 100644
index 0000000..ebffb50
--- /dev/null
+++ b/DeviceCommand/Base/IBaseInterface.cs
@@ -0,0 +1,16 @@
+using NModbus;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Base
+{
+ public interface IBaseInterface
+ {
+ public bool IsConnected { get; }
+ public Task ConnectAsync(CancellationToken ct = default);
+ public void Close();
+ }
+}
diff --git a/DeviceCommand/Base/IModbusDevice.cs b/DeviceCommand/Base/IModbusDevice.cs
new file mode 100644
index 0000000..360e843
--- /dev/null
+++ b/DeviceCommand/Base/IModbusDevice.cs
@@ -0,0 +1,19 @@
+using NModbus;
+
+
+namespace DeviceCommand.Base
+{
+ public interface IModbusDevice : IBaseInterface,IDisposable
+ {
+ IModbusMaster Modbus { get; }
+
+ Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default);
+ Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default);
+ Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
+
+ Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default);
+ Task ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
+
+ Task ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Base/ISerialPort.cs b/DeviceCommand/Base/ISerialPort.cs
new file mode 100644
index 0000000..e54a802
--- /dev/null
+++ b/DeviceCommand/Base/ISerialPort.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Base
+{
+ public interface ISerialPort : IBaseInterface,IDisposable
+ {
+ Task SendAsync(string data, CancellationToken ct = default);
+ Task ReadAsync(string delimiter = "\n", CancellationToken ct = default);
+ Task WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Base/ITcp.cs b/DeviceCommand/Base/ITcp.cs
new file mode 100644
index 0000000..e889668
--- /dev/null
+++ b/DeviceCommand/Base/ITcp.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Base
+{
+ public interface ITcp : IBaseInterface,IDisposable
+ {
+ Task SendAsync(byte[] buffer, CancellationToken ct = default);
+ Task SendAsync(string str, CancellationToken ct = default);
+ Task ReadAsync(int length, CancellationToken ct = default);
+ Task ReadAsync(string delimiter = "\n", CancellationToken ct = default);
+ Task WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Base/ModbusRtu.cs b/DeviceCommand/Base/ModbusRtu.cs
new file mode 100644
index 0000000..c9ceecb
--- /dev/null
+++ b/DeviceCommand/Base/ModbusRtu.cs
@@ -0,0 +1,164 @@
+using NModbus;
+using NModbus.Serial;
+using System;
+using System.IO.Ports;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Base
+{
+ public class ModbusRtu : IModbusDevice
+ {
+ public string PortName { get; private set; } = "COM1";
+ public int BaudRate { get; private set; } = 9600;
+ public int DataBits { get; private set; } = 8;
+ public StopBits StopBits { get; private set; } = StopBits.One;
+ public Parity Parity { get; private set; } = Parity.None;
+ public int ReadTimeout { get; private set; } = 3000;
+ public int WriteTimeout { get; private set; } = 3000;
+
+ private SerialPort _serialPort;
+ public IModbusMaster Modbus { get; private set; }
+ public bool IsConnected => _serialPort?.IsOpen ?? false;
+
+ protected readonly SemaphoreSlim _commLock = new(1, 1);
+
+ public ModbusRtu()
+ {
+ _serialPort = new SerialPort();
+ }
+
+ public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
+ {
+ PortName = portName;
+ BaudRate = baudRate;
+ DataBits = dataBits;
+ StopBits = stopBits;
+ Parity = parity;
+ ReadTimeout = readTimeout;
+ WriteTimeout = writeTimeout;
+ }
+
+ public virtual async Task ConnectAsync(CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ if (_serialPort.IsOpen)
+ _serialPort.Close();
+
+ _serialPort.PortName = PortName;
+ _serialPort.BaudRate = BaudRate;
+ _serialPort.DataBits = DataBits;
+ _serialPort.StopBits = StopBits;
+ _serialPort.Parity = Parity;
+ _serialPort.ReadTimeout = ReadTimeout;
+ _serialPort.WriteTimeout = WriteTimeout;
+ _serialPort.Open();
+
+ Modbus = new ModbusFactory().CreateRtuMaster(_serialPort);
+ return true;
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public virtual void Close()
+ {
+ if (_serialPort.IsOpen)
+ _serialPort.Close();
+ }
+
+ public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ await Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
+ .WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ await Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values)
+ .WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ return await Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
+ .WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ await Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
+ .WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ return await Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
+ .WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ return await Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints)
+ .WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public void Dispose()
+ {
+ _serialPort?.Dispose();
+ _commLock?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Base/ModbusTcp.cs b/DeviceCommand/Base/ModbusTcp.cs
new file mode 100644
index 0000000..8db68f3
--- /dev/null
+++ b/DeviceCommand/Base/ModbusTcp.cs
@@ -0,0 +1,158 @@
+using NModbus;
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Base
+{
+ public class ModbusTcp : IModbusDevice
+ {
+ public string IPAddress { get; private set; } = "127.0.0.1";
+ public int Port { get; private set; } = 502;
+ public int SendTimeout { get; private set; } = 3000;
+ public int ReceiveTimeout { get; private set; } = 3000;
+
+ private TcpClient _tcpClient;
+ public IModbusMaster Modbus { get; private set; }
+ public bool IsConnected => _tcpClient?.Connected ?? false;
+
+ protected readonly SemaphoreSlim _commLock = new(1, 1);
+
+ public ModbusTcp()
+ {
+ _tcpClient = new TcpClient();
+ }
+
+ public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
+ {
+ IPAddress = ipAddress;
+ Port = port;
+ SendTimeout = sendTimeout;
+ ReceiveTimeout = receiveTimeout;
+ }
+
+ public virtual async Task ConnectAsync(CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ if (_tcpClient.Connected)
+ {
+ var remoteEndPoint = (IPEndPoint)_tcpClient.Client.RemoteEndPoint!;
+ if (remoteEndPoint.Address.MapToIPv4().ToString() == IPAddress && remoteEndPoint.Port == Port)
+ return true;
+ }
+
+ _tcpClient.Close();
+ _tcpClient.Dispose();
+ _tcpClient = new TcpClient();
+
+ await _tcpClient.ConnectAsync(IPAddress, Port, ct);
+ Modbus = new ModbusFactory().CreateMaster(_tcpClient);
+ return true;
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public virtual void Close()
+ {
+ if (_tcpClient.Connected) _tcpClient.Close();
+ }
+
+ public async Task WriteSingleRegisterAsync(byte slaveAddress, ushort registerAddress, ushort value, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ // 修复:FromMinutes 改为 FromMilliseconds
+ await Modbus.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
+ .WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task WriteMultipleRegistersAsync(byte slaveAddress, ushort startAddress, ushort[] values, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ await Modbus.WriteMultipleRegistersAsync(slaveAddress, startAddress, values)
+ .WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ return await Modbus.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
+ .WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ await Modbus.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
+ .WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ return await Modbus.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
+ .WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ return await Modbus.ReadInputRegistersAsync(slaveAddress, startAddress, numberOfPoints)
+ .WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public void Dispose()
+ {
+ _tcpClient?.Dispose();
+ _commLock?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Base/Serial_Port.cs b/DeviceCommand/Base/Serial_Port.cs
new file mode 100644
index 0000000..9c0ed0f
--- /dev/null
+++ b/DeviceCommand/Base/Serial_Port.cs
@@ -0,0 +1,156 @@
+using System;
+using System.IO.Ports;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Base
+{
+ public class Serial_Port : ISerialPort
+ {
+ public string PortName { get; set; } = "COM1";
+ public int BaudRate { get; set; } = 9600;
+ public int DataBits { get; set; } = 8;
+ public StopBits StopBits { get; set; } = StopBits.One;
+ public Parity Parity { get; set; } = Parity.None;
+ public int ReadTimeout { get; set; } = 3000;
+ public int WriteTimeout { get; set; } = 3000;
+
+ private SerialPort _serialPort;
+ public bool IsConnected => _serialPort?.IsOpen ?? false;
+ protected readonly SemaphoreSlim commLock = new(1, 1);
+
+ public Serial_Port()
+ {
+ _serialPort = new SerialPort();
+ }
+
+ public void ConfigureDevice(string portName, int baudRate, int dataBits = 8, StopBits stopBits = StopBits.One, Parity parity = Parity.None, int readTimeout = 3000, int writeTimeout = 3000)
+ {
+ PortName = portName;
+ BaudRate = baudRate;
+ DataBits = dataBits;
+ StopBits = stopBits;
+ Parity = parity;
+ ReadTimeout = readTimeout;
+ WriteTimeout = writeTimeout;
+ }
+
+ public virtual async Task ConnectAsync(CancellationToken ct = default)
+ {
+ await commLock.WaitAsync(ct);
+ try
+ {
+ if (_serialPort.IsOpen) _serialPort.Close();
+
+ _serialPort.PortName = PortName;
+ _serialPort.BaudRate = BaudRate;
+ _serialPort.DataBits = DataBits;
+ _serialPort.StopBits = StopBits;
+ _serialPort.Parity = Parity;
+ _serialPort.ReadTimeout = ReadTimeout;
+ _serialPort.WriteTimeout = WriteTimeout;
+
+ _serialPort.Open();
+ return true;
+ }
+ finally
+ {
+ commLock.Release();
+ }
+ }
+
+ public virtual void Close()
+ {
+ if (_serialPort.IsOpen) _serialPort.Close();
+ }
+
+ // 内部无锁发送方法,供原子组合操作调用
+ private async Task LoglessSendAsync(string data, CancellationToken ct)
+ {
+ if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
+
+ byte[] bytes = Encoding.UTF8.GetBytes(data);
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ if (WriteTimeout > 0) cts.CancelAfter(WriteTimeout);
+
+ await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
+ }
+
+ public async Task SendAsync(string data, CancellationToken ct = default)
+ {
+ await commLock.WaitAsync(ct);
+ try
+ {
+ await LoglessSendAsync(data, ct);
+ }
+ finally
+ {
+ commLock.Release();
+ }
+ }
+
+ // 内部无锁读取方法,利用 BaseStream 挂起线程,高性能不吃 CPU
+ private async Task LoglessReadAsync(string delimiter, CancellationToken ct)
+ {
+ if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
+
+ delimiter ??= "\n";
+ var sb = new StringBuilder();
+ byte[] buffer = new byte[1024];
+
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ if (ReadTimeout > 0) cts.CancelAfter(ReadTimeout);
+
+ while (!cts.Token.IsCancellationRequested)
+ {
+ // 核心优化:利用流异步挂起,替代原先的 BytesToRead 循环延时
+ int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
+ if (bytesRead == 0) continue;
+
+ 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("读取数据超时");
+ }
+
+ public async Task ReadAsync(string delimiter = "\n", CancellationToken ct = default)
+ {
+ await commLock.WaitAsync(ct);
+ try
+ {
+ return await LoglessReadAsync(delimiter, ct);
+ }
+ finally
+ {
+ commLock.Release();
+ }
+ }
+
+ // 核心优化:保证多线程环境下发送和等待回包是一个原子过程
+ public async Task WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
+ {
+ await commLock.WaitAsync(ct);
+ try
+ {
+ await LoglessSendAsync(command, ct);
+ return await LoglessReadAsync(delimiter, ct);
+ }
+ finally
+ {
+ commLock.Release();
+ }
+ }
+
+ public void Dispose()
+ {
+ _serialPort?.Dispose();
+ commLock?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Base/TCP.cs b/DeviceCommand/Base/TCP.cs
new file mode 100644
index 0000000..28f5621
--- /dev/null
+++ b/DeviceCommand/Base/TCP.cs
@@ -0,0 +1,179 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Base
+{
+ public class Tcp : ITcp
+ {
+ public string IPAddress { get; set; } = "127.0.0.1";
+ public int Port { get; set; } = 502;
+ public int SendTimeout { get; set; } = 3000;
+ public int ReceiveTimeout { get; set; } = 3000;
+
+ private TcpClient _tcpClient;
+ public bool IsConnected => _tcpClient?.Connected ?? false;
+ protected readonly SemaphoreSlim _commLock = new(1, 1);
+
+ public Tcp()
+ {
+ _tcpClient = new TcpClient();
+ }
+
+ public void ConfigureDevice(string ipAddress, int port, int sendTimeout = 3000, int receiveTimeout = 3000)
+ {
+ IPAddress = ipAddress;
+ Port = port;
+ SendTimeout = sendTimeout;
+ ReceiveTimeout = receiveTimeout;
+ }
+
+ public virtual async Task ConnectAsync(CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ if (_tcpClient.Connected) return true;
+
+ // 修复:释放并彻底清空旧的连接实例,否则复用引发异常
+ _tcpClient.Close();
+ _tcpClient.Dispose();
+
+ _tcpClient = new TcpClient();
+ await _tcpClient.ConnectAsync(IPAddress, Port, ct);
+ return true;
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public virtual void Close()
+ {
+ if (_tcpClient.Connected) _tcpClient.Close();
+ }
+
+ private async Task LoglessSendAsync(byte[] buffer, CancellationToken ct)
+ {
+ if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
+
+ NetworkStream stream = _tcpClient.GetStream();
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ if (SendTimeout > 0) cts.CancelAfter(SendTimeout);
+
+ await stream.WriteAsync(buffer, 0, buffer.Length, cts.Token);
+ }
+
+ public async Task SendAsync(byte[] buffer, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ await LoglessSendAsync(buffer, ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public async Task SendAsync(string str, CancellationToken ct = default)
+ {
+ await SendAsync(Encoding.UTF8.GetBytes(str), ct);
+ }
+
+ public async Task ReadAsync(int length, CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
+
+ NetworkStream stream = _tcpClient.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 offset == 0 ? Array.Empty() : buffer[..offset];
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ private async Task LoglessReadAsync(string delimiter, CancellationToken ct)
+ {
+ if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
+
+ delimiter ??= "\n";
+ var sb = new StringBuilder();
+ byte[] buffer = new byte[1024];
+ NetworkStream stream = _tcpClient.GetStream();
+
+ 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) throw new IOException("远程主机已关闭连接");
+
+ 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("读取数据超时");
+ }
+
+ public async Task ReadAsync(string delimiter = "\n", CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ return await LoglessReadAsync(delimiter, ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ // 核心优化:确保发送与读取在同一组锁生命周期内
+ public async Task WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
+ {
+ await _commLock.WaitAsync(ct);
+ try
+ {
+ await LoglessSendAsync(Encoding.UTF8.GetBytes(command), ct);
+ return await LoglessReadAsync(delimiter, ct);
+ }
+ finally
+ {
+ _commLock.Release();
+ }
+ }
+
+ public void Dispose()
+ {
+ _tcpClient?.Dispose();
+ _commLock?.Dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/DeviceCommand.csproj b/DeviceCommand/DeviceCommand.csproj
new file mode 100644
index 0000000..d9b2b2a
--- /dev/null
+++ b/DeviceCommand/DeviceCommand.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DeviceCommand/Devices/IT7800E.cs b/DeviceCommand/Devices/IT7800E.cs
new file mode 100644
index 0000000..ec57a1d
--- /dev/null
+++ b/DeviceCommand/Devices/IT7800E.cs
@@ -0,0 +1,249 @@
+using Common.Attributes;
+using DeviceCommand.Base;
+using System;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Device
+{
+ [ADPCommand]
+ public class IT7800E : Tcp
+ {
+ // 根据通用 SCPI 指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
+ private const string ScpiDelimiter = "\n";
+
+ ///
+ /// 构造函数:初始化 IT7800E 交直流电源通信参数
+ ///
+ public IT7800E(string ipAddress, int port, int sendTimeout, int receiveTimeout)
+ {
+ ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
+ }
+
+ #region 1. IEEE 488.2 公共命令
+
+ ///
+ /// 清除状态命令。清除标准事件状态寄存器和错误队列
+ ///
+ public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
+ {
+ await SendAsync($"*CLS{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 读取电源识别字符串(制造商、产品型号、系统 SN、软件版本号)
+ ///
+ public virtual async Task 查询设备标识(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
+ }
+
+ ///
+ /// 复位命令。使电源恢复到出厂默认预定义安全值
+ ///
+ public virtual async Task 重置设备(CancellationToken ct = default)
+ {
+ await SendAsync($"*RST{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 读取状态字节寄存器
+ ///
+ public virtual async Task 读取状态字节(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //
+ }
+
+ #endregion
+
+ #region 2. 耦合模式与拉载开关控制
+
+ ///
+ /// 设置电源输出源的电气模式/工作耦合模式
+ /// (AC: 纯交流, DC: 纯直流, ACDC: 交直流混合模式)
+ ///
+ public virtual async Task 设置电源模式(string 模式, CancellationToken ct = default)
+ {
+ string modeUpper = 模式.ToUpper();
+ if (modeUpper != "AC" && modeUpper != "DC" && modeUpper != "ACDC")
+ throw new ArgumentException("工作模式只能为 AC, DC, 或 ACDC");
+
+ await SendAsync($":SOURce:MODE {modeUpper}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 查询电源当前的工作模式
+ ///
+ public virtual async Task 查询电源模式(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":SOURce:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 控制电源的主输出开关 (True: 开启输出, False: 关闭输出)
+ ///
+ public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
+ {
+ // 注意:虽然方法名叫设置DC输出,但对交直流电源它代表通用的主输出控制(Main Output)
+ string 参数 = 开启 ? "ON" : "OFF";
+ await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 查询电源当前主输出开关状态 (返回 ON 或 OFF)
+ ///
+ public virtual async Task 查询DC输出状态(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
+
+ ///
+ /// 设定交流(AC)模式下的电压有效值 (单位: V)
+ ///
+ public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:AC {0:F2}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设定直流(DC)模式或交直流(AC+DC)模式下的直流偏置电压值 (单位: V)
+ ///
+ public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设定电源交流输出的频率值 (单位: Hz)
+ ///
+ public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置硬件交流输出的有效值限流门限 (单位: A)
+ ///
+ public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ #endregion
+
+ #region 4. 测量与数据回测(高频数据轮询核心)
+
+ ///
+ /// 查询通道实时测得的电压有效值(RMS,支持 AC 或 DC 模式下的回测)(单位: V)
+ ///
+ public virtual async Task 查询实际电压(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询通道实时测得的电流有效值(RMS)(单位: A)
+ ///
+ public virtual async Task 查询实际电流(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询通道实时测得的有功功率值 (单位: W)
+ ///
+ public virtual async Task 查询实际功率(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询通道实时测得的视在功率 (单位: VA)
+ ///
+ public virtual async Task 查询视在功率(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询通道实时测得的输出频率 (单位: Hz)
+ ///
+ public virtual async Task 查询实际频率(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询当前的功率因数 (PF)
+ ///
+ public virtual async Task 查询功率因数(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:PF?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 5. 保护参数设置与系统监控
+
+ ///
+ /// 设置电源的过流保护(OCP)值 (单位: A)
+ ///
+ public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置电源的过压保护(OVP)值 (单位: V)
+ ///
+ public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection {0:F2}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 读取系统的出错记录信息(获取上一条未处理的错误码)
+ ///
+ public virtual async Task 查询错误信息(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 切换仪器至计算机控制的远程控制模式 (锁定前面板按键)
+ ///
+ public virtual async Task 切换远程控制模式(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 切换仪器至前面板操作的本地模式 (前面板解锁)
+ ///
+ public virtual async Task 切换本地控制模式(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 清除仪器当前的保护状态(例如触发 OCP/OVP 被锁死后进行软件解锁)
+ ///
+ public virtual async Task 清除保护告警(CancellationToken ct = default)
+ {
+ await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Devices/N36200.cs b/DeviceCommand/Devices/N36200.cs
new file mode 100644
index 0000000..99dac04
--- /dev/null
+++ b/DeviceCommand/Devices/N36200.cs
@@ -0,0 +1,382 @@
+using Common.Attributes;
+using DeviceCommand.Base;
+using System;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Device
+{
+ [ADPCommand]
+ public class N36200 : Tcp
+ {
+ // 手册第 9 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
+ private const string ScpiDelimiter = "\n";
+
+ ///
+ /// 构造函数:初始化 N36200/N36300 设备通信参数
+ ///
+ public N36200(string ipAddress, int port, int sendTimeout, int receiveTimeout)
+ {
+ ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
+ }
+
+ #region 3.1. IEEE 488.2 公共命令
+
+ ///
+ /// 3.1.1. 清除标准事件状态寄存器和错误队列
+ ///
+ public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
+ {
+ await SendAsync($"*CLS{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.1.4. 读取直流电源相关信息(制造商、产品型号、系统 SN、软件版本号)
+ ///
+ public virtual async Task 查询设备标识(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.1.7. 恢复出厂设置 (注意:设备重置保存数据大约需要 10 秒)
+ ///
+ public virtual async Task 重置设备(CancellationToken ct = default)
+ {
+ await SendAsync($"*RST{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.1.8. 读取状态字节寄存器(只读寄存器,读取时不会清除位)
+ ///
+ public virtual async Task 读取状态字节(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.2. 设定输出电压与限流值
+
+ ///
+ /// 3.2.1. 设定输出电压值 (单位: V)
+ ///
+ public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.2.1. 查询输出电压设定值 (单位: V)
+ ///
+ public virtual async Task 查询电压设定(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"SOURce:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.2.2. 设置输出限流值 (单位: A)
+ ///
+ public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.2.2. 查询输出限流值设定值 (单位: A)
+ ///
+ public virtual async Task 查询电流设定(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"SOURce:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.2.3. 设置输出模拟内阻值 (单位: mΩ)
+ ///
+ public virtual async Task 设置内阻(double 内阻, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:INTErnalres {0:F1}{1}", 内阻, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.2.4. 保存当前测试参数到指定存储组 (范围: 1-10)
+ ///
+ public virtual async Task 保存测试参数(int 组别, CancellationToken ct = default)
+ {
+ if (组别 < 1 || 组别 > 10) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~10");
+ await SendAsync($"SOURce:FUNCtion:SAVe {组别}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.2.5. 调用指定存储组的测试参数 (范围: 1-10)
+ ///
+ public virtual async Task 调用测试参数(int 组别, CancellationToken ct = default)
+ {
+ if (组别 < 1 || 组别 > 10) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~10");
+ await SendAsync($"SOURce:FUNCtion:RECAll {组别}{ScpiDelimiter}", ct);
+ }
+
+ #endregion
+
+ #region 3.3. 输出控制及状态
+
+ ///
+ /// 3.3.1. 控制电源输出开关 (True: 开启, False: 关闭)
+ ///
+ public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
+ {
+ string 参数 = 开启 ? "ON" : "OFF";
+ await SendAsync($"OUTPut:ONOFF {参数}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.3.1. 查询电源输出开关状态 (返回 "ON" 或 "OFF")
+ ///
+ public virtual async Task 查询DC输出开关状态(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"OUTPut:ONOFF?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.3.2. 设定电源工作模式
+ /// (NORMal: 普通模式, CHARge: 电池充电, SEQuence: 序列模式, CPOWer: 恒功率模式, CARWave: 汽车测试, APG: 外部编程)
+ ///
+ public virtual async Task 设置运行模式(string 模式, CancellationToken ct = default)
+ {
+ await SendAsync($"OUTPut:MODE {模式}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.3.2. 查询电源当前运行工作模式
+ ///
+ public virtual async Task 查询运行模式(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"OUTPut:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.3.11. 设置电源输出 CV/CC 优先权 (CV 或 CC)
+ ///
+ public virtual async Task 设置CVCC优先权(string 优先模式, CancellationToken ct = default)
+ {
+ if (优先模式 != "CV" && 优先模式 != "CC") throw new ArgumentException("优先模式只能为 'CV' 或 'CC'");
+ await SendAsync($"OUTPut:PRIority {优先模式}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.3.12. 获取电源状态字(通过解析返回整数的二进制 Bit 位获取全状态环路及告警)
+ ///
+ public virtual async Task 查询设备状态字(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"OUTPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.3.13. 获取电源事件告警状态值 (返回整数通过位定义标识 UVP/OVP/OCP/OPP/OTP)
+ ///
+ public virtual async Task 查询事件告警状态(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"OUTPut:EVENT?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.3.13. 清除当前的告警状态
+ ///
+ public virtual async Task 清除告警(CancellationToken ct = default)
+ {
+ await SendAsync($"OUTPut:EVENT 0{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.3.16. 打开/关闭设备定时关机功能
+ ///
+ public virtual async Task 设置定时关机开关(bool 开启, CancellationToken ct = default)
+ {
+ string 参数 = 开启 ? "ON" : "OFF";
+ await SendAsync($"OUTPut:TIMing:SWITch {参数}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 3.3.17. 设定设备定时关机倒计时时间 (单位: s)
+ ///
+ public virtual async Task 设置定时关机时间(double 秒数, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "OUTPut:TIMing:DWELI {0:F1}{1}", 秒数, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.3.18. 控制泄放电路(Bleeder)开关状态
+ ///
+ public virtual async Task 设置泄放电路开关(bool 开启, CancellationToken ct = default)
+ {
+ string 参数 = 开启 ? "ON" : "OFF";
+ await SendAsync($"OUTPut:DISRes {参数}{ScpiDelimiter}", ct);
+ }
+
+ #endregion
+
+ #region 3.4. 读取输出电压电流及功率值 (实时轮询核心)
+
+ ///
+ /// 3.4.1. 回读通道输出端子上的实时测得电压值 (单位: V)
+ ///
+ public virtual async Task 查询实际电压(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.4.2. 回读通道输出端子上的实时测得电流值 (单位: A)
+ ///
+ public virtual async Task 查询实际电流(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.4.3. 回读通道输出端子上的实时测得功率值 (单位: W)
+ ///
+ public virtual async Task 查询实际功率(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.4.4. 回读电池充电模式下当前累计已充入的容量值 (单位: mAh)
+ ///
+ public virtual async Task 查询已充电容量MAH(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"MEASure:MAH?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.4.5. 获取当前电源的硬件额定电压上限值 (单位: V)
+ ///
+ public virtual async Task 获取设备额定电压(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"MEASure:VOLTage:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 3.4.6. 获取当前电源的硬件额定电流上限值 (单位: A)
+ ///
+ public virtual async Task 获取设备额定电流(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"MEASure:CURRent:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.5. 保护功能
+
+ ///
+ /// 3.5.1. 设置欠压保护门限值 (单位: V)
+ ///
+ public virtual async Task 设置过欠压保护_UVP(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:LESS:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.5.4. 设置过压保护门限值 (单位: V)
+ ///
+ public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:OVER:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.5.6. 设置硬件硬件过流保护硬指标参数 (单位: A)
+ ///
+ public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.5.8. 设置硬件过功率保护门限值 (单位: W)
+ ///
+ public virtual async Task 设置过功率保护_OPP(double 功率, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "PROTect:POWer {0:F3}{1}", 功率, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.5.12. 设置可调节的用户软输出电压下限值 (单位: V)
+ ///
+ public virtual async Task 设置电压下限(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage:LEVel:LIMit:LOW {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.5.13. 设置可调节的用户软输出电压上限值 (单位: V)
+ ///
+ public virtual async Task 设置电压上限(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage:LEVel:LIMit {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.5.14. 设置可调节的用户软输出电流下限值 (单位: A)
+ ///
+ public virtual async Task 设置电流下限(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent:LEVel:LIMit:LOW {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.5.15. 设置可调节的用户软输出电流上限值 (单位: A)
+ ///
+ public virtual async Task 设置电流上限(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent:LEVel:LIMit {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ #endregion
+
+ #region 3.6. 恒功率模式配置 (CPOWer)
+
+ ///
+ /// 3.6.1. 设定恒功率工作模式下的限定电压值 (单位: V)
+ ///
+ public virtual async Task 设置恒功率模式电压(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.6.2. 设定恒功率工作模式下的限定电流值 (单位: A)
+ ///
+ public virtual async Task 设置恒功率模式电流(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.6.3. 设定恒功率工作模式下的运行功率目标值 (单位: W)
+ ///
+ public virtual async Task 设置恒功率模式功率(double 功率, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:POWer {0:F3}{1}", 功率, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Devices/N36600.cs b/DeviceCommand/Devices/N36600.cs
new file mode 100644
index 0000000..6579900
--- /dev/null
+++ b/DeviceCommand/Devices/N36600.cs
@@ -0,0 +1,459 @@
+using Common.Attributes;
+using DeviceCommand.Base;
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Device
+{
+ [ADPCommand]
+ public class N36600 : Tcp
+ {
+ private CancellationTokenSource? _heartbeatCts;
+ private Task? _heartbeatTask;
+ private const int HeartbeatInterval = 3000; // 心跳间隔 3 秒
+
+ public bool IsActive { get; private set; } = false;
+ public int ReConnectionAttempts { get; private set; } = 0;
+ public const int MaxReconnectAttempts = 10;
+
+ // 手册第 4 页明确规定:每条命令后面都要加结束符 0x0A (\n)
+ private const string SCPIDelimiter = "\n";
+
+ public N36600(string ipAddress, int port, int sendTimeout, int receiveTimeout)
+ {
+ ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
+ }
+
+ ///
+ /// 建立 TCP 连接,成功后自动激活心跳
+ ///
+ public new async Task ConnectAsync(CancellationToken ct = default)
+ {
+ bool isConnected = await base.ConnectAsync(ct);
+ if (isConnected)
+ {
+ IsActive = true;
+ ReConnectionAttempts = 0;
+ StartHeartbeat();
+ }
+ return isConnected;
+ }
+
+ public override void Close()
+ {
+ StopHeartbeat();
+ base.Close();
+ }
+
+ #region 心跳与断线重连逻辑
+
+ [Browsable(false)]
+ public void StartHeartbeat()
+ {
+ if (_heartbeatTask != null && !_heartbeatTask.IsCompleted)
+ return;
+
+ _heartbeatCts?.Cancel();
+ _heartbeatCts?.Dispose();
+ _heartbeatCts = new CancellationTokenSource();
+
+ _heartbeatTask = Task.Run(() => HeartbeatLoop(_heartbeatCts.Token));
+ }
+
+ [Browsable(false)]
+ public void StopHeartbeat()
+ {
+ IsActive = false;
+ if (_heartbeatCts != null && !_heartbeatCts.IsCancellationRequested)
+ {
+ _heartbeatCts.Cancel();
+ }
+ _heartbeatTask = null;
+ }
+
+ private async Task HeartbeatLoop(CancellationToken ct)
+ {
+ while (!ct.IsCancellationRequested)
+ {
+ try
+ {
+ await Task.Delay(HeartbeatInterval, ct);
+ if (ct.IsCancellationRequested) break;
+
+ // 使用公共查询命令发送心跳,确保通道连接正常,且不破坏远程或本地锁定状态
+ await SendAsync($"*IDN?{SCPIDelimiter}", ct);
+ IsActive = true;
+ ReConnectionAttempts = 0;
+ }
+ catch (Exception)
+ {
+ IsActive = false;
+ ReConnectionAttempts++;
+
+ if (ReConnectionAttempts > MaxReconnectAttempts)
+ {
+ StopHeartbeat();
+ base.Close();
+ return;
+ }
+
+ await ReconnectDeviceAsync(ct);
+ }
+ }
+ }
+
+ private async Task ReconnectDeviceAsync(CancellationToken ct)
+ {
+ try
+ {
+ await ConnectAsync(ct);
+ }
+ catch (Exception)
+ {
+ // 静默处理,等待下一轮心跳重试
+ }
+ }
+
+ #endregion
+
+ #region 3.1 IEEE 488.2 公用命令
+
+ ///
+ /// 3.1.1 读取电源的相关信息(制造商, 产品标号, 产品序列号, 软件版本号)
+ ///
+ public virtual async Task 查询设备标识(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*IDN?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.1.2 从指定的单位中恢复 *SAV 命令保存的设定值 (参数:1~99)
+ ///
+ public virtual async Task 调用存储状态(int 组别, CancellationToken ct = default)
+ {
+ if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
+ await SendAsync($"*RCL {组别}{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.1.3 将仪器当前系统状态保存到非易失性内存中 (参数:1~99)
+ ///
+ public virtual async Task 保存当前状态(int 组别, CancellationToken ct = default)
+ {
+ if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
+ await SendAsync($"*SAV {组别}{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.1.4 远程控制触发一次
+ ///
+ public virtual async Task 远程触发(CancellationToken ct = default)
+ {
+ await SendAsync($"*TRG{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.1.5 返回状态、采集电压、采集电流 (格式: 状态码,电压值,电流值)
+ ///
+ public virtual async Task 查询全部状态及采样(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*ALL?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.2 APPLy 命令子系统
+
+ ///
+ /// 3.2.1 设置输出电压和输出电流值
+ ///
+ public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":APPLY {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.2.1 查询当前设定的输出电压和电流值
+ ///
+ public virtual async Task 查询快捷电压电流设定(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":APPLY?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.5 MEASure 命令子系统
+
+ ///
+ /// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
+ ///
+ public virtual async Task 查询实际电流(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:CURRent?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
+ ///
+ public virtual async Task 查询实际功率(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:POWer?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
+ ///
+ public virtual async Task 查询实际电压(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:VOLTage?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.5.4 查询通道输出端子上测得的电压、电流和功率的组合值
+ ///
+ public virtual async Task 查询电压电流功率数组(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.5.5 查询通道输出端子上测得的电池容量
+ ///
+ public virtual async Task 查询测得电池容量(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:CAPAcity?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.5.6 查询通道输出端子上测得的恒流输出时间长度 (单位: ms)
+ ///
+ public virtual async Task 查询恒流输出时间长度(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:TIMEr:CC?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.5.7 查询通道输出端子上测得的输出时间长度
+ ///
+ public virtual async Task 查询总输出时间长度(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:TIME:OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.6 OUTPut 命令子系统
+
+ ///
+ /// 3.6.1 设置电源输出开关
+ ///
+ public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
+ {
+ string 状态 = 开启 ? "ON" : "OFF";
+ await SendAsync($":OUTPut {状态}{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.6.1 查询通道输出状态 (返回 ON 或 OFF)
+ ///
+ public virtual async Task 查询DC输出状态(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":OUTPut?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.6.2 控制电源输出定时器的开关状态
+ ///
+ public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
+ {
+ string 状态 = 开启 ? "ON" : "OFF";
+ await SendAsync($":OUTPut:TIMEr {状态}{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.6.3 设定电源输出定时器的时间 (单位: s, 范围: 0.1 ~ 999999.9)
+ ///
+ public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":OUTPut:TIMEr:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.6.4 设定电源输出的模式优先权 (CV优先 或 CC优先)
+ ///
+ public virtual async Task 设置输出模式优先(bool 是CV优先, CancellationToken ct = default)
+ {
+ string 模式 = 是CV优先 ? "CV" : "CC";
+ await SendAsync($":OUTPut:MODE {模式}{SCPIDelimiter}", ct);
+ }
+
+ #endregion
+
+ #region 3.7 SOURce 命令子系统
+
+ ///
+ /// 3.7.1.1 清除输出电流保护(OCP)电路状态
+ ///
+ public virtual async Task 清除电流保护状态(CancellationToken ct = default)
+ {
+ await SendAsync($":CURRent:PROTection:CLEar{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.7.1.2 使能或禁止当前输出电流保护(OCP)电路
+ ///
+ public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
+ {
+ string 状态 = 启用 ? "ON" : "OFF";
+ await SendAsync($":CURRent:PROTection:STATE {状态}{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.7.1.4 设置输出电流保护(OCP)阀值 (A)
+ ///
+ public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent:PROTection {0:F3}{1}", 电流, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.7.1.7 设置电源的输出电流值 (A)
+ ///
+ public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F3}{1}", 电流, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.7.2.1 清除输出电压保护(OVP)电路状态
+ ///
+ public virtual async Task 清除电压保护状态(CancellationToken ct = default)
+ {
+ await SendAsync($":VOLTage:PROTection:CLEar{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.7.2.2 使能或禁止当前输出电压保护(OVP)电路
+ ///
+ public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
+ {
+ string 状态 = 启用 ? "ON" : "OFF";
+ await SendAsync($":VOLTage:PROTection:STATE {状态}{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.7.2.4 设置输出电压保护(OVP)阀值 (V)
+ ///
+ public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:PROTection {0:F3}{1}", 电压, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.7.2.7 设置电源的输出电压值 (V)
+ ///
+ public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.7.2.8 设定电压输出范围的上限电压值 (V)
+ ///
+ public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage:LIMIT {0:F3}{1}", 电压, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.7.3.1 设置输出功率值 (W)
+ ///
+ public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, SCPIDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 3.7.3.2 禁止或使能当前恒功率(CP)输出
+ ///
+ public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
+ {
+ string 状态 = 启用 ? "ON" : "OFF";
+ await SendAsync($":POWer:STATE {状态}{SCPIDelimiter}", ct);
+ }
+
+ #endregion
+
+ #region 3.9 SYSTem 命令子系统
+
+ ///
+ /// 3.9.1 控制蜂鸣器开关
+ ///
+ public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
+ {
+ string 状态 = 开启 ? "ON" : "OFF";
+ await SendAsync($":SYSTem:BEEPer:STATE {状态}{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.9.2 使蜂鸣器强制鸣叫一声
+ ///
+ public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:BEEPer{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.9.4 查询仪器当前出错记录数量 (最大 18 组)
+ ///
+ public virtual async Task 查询错误记录数量(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":SYSTem:ERRor:COUNT?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.9.5 读取仪器的出错信息 (成功返回 0,"No error")
+ ///
+ public virtual async Task 查询错误信息(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":SYSTem:ERRor?{SCPIDelimiter}", SCPIDelimiter, ct);
+ }
+
+ ///
+ /// 3.9.7 设置电源为面板控制模式 (本地 Local 状态,前面板按键可用)
+ ///
+ public virtual async Task 切换本地控制模式(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:LOCal{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.9.8 设置电源为远程控制模式 (Remote 状态)
+ ///
+ public virtual async Task 切换远程控制模式(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:REMote{SCPIDelimiter}", ct);
+ }
+
+ ///
+ /// 3.9.9 通过通信接口设置电源为远程控制锁定模式
+ ///
+ public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:RWLock{SCPIDelimiter}", ct);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Devices/N69200.cs b/DeviceCommand/Devices/N69200.cs
new file mode 100644
index 0000000..2b88a79
--- /dev/null
+++ b/DeviceCommand/Devices/N69200.cs
@@ -0,0 +1,294 @@
+using Common.Attributes;
+using DeviceCommand.Base;
+using System;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Device
+{
+ [ADPCommand]
+ public class N69200 : Tcp
+ {
+ // 手册第 15 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
+ private const string ScpiDelimiter = "\n";
+
+ ///
+ /// 构造函数:初始化 N69200 电子负载通信参数
+ ///
+ public N69200(string ipAddress, int port, int sendTimeout, int receiveTimeout)
+ {
+ ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
+ }
+
+ #region 3.1. IEEE 488.2 公共命令
+
+ ///
+ /// 清除状态命令。清除标准事件状态寄存器和错误队列
+ ///
+ public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
+ {
+ await SendAsync($"*CLS{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 读取电子负载设备识别字符串(制造商、产品型号、系统 SN、软件版本号)
+ ///
+ public virtual async Task 查询设备标识(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 复位命令。使电子负载恢复到出厂默认配置状态
+ ///
+ public virtual async Task 重置设备(CancellationToken ct = default)
+ {
+ await SendAsync($"*RST{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 读取标准状态字节寄存器
+ ///
+ public virtual async Task 读取状态字节(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 保存当前系统参数到指定的存储单元 (单元范围依型号而定)
+ ///
+ public virtual async Task 保存当前状态(int 单元, CancellationToken ct = default)
+ {
+ await SendAsync($"*SAV {单元}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 调用指定存储单元中保存的系统参数
+ ///
+ public virtual async Task 调用存储状态(int 单元, CancellationToken ct = default)
+ {
+ await SendAsync($"*RCL {单元}{ScpiDelimiter}", ct);
+ }
+
+ #endregion
+
+ #region 3.2. 电子负载模式切换及拉载开关
+
+ ///
+ /// 控制电子负载的输入控制开关(True: 开启拉载输入, False: 关闭拉载输入)
+ ///
+ public virtual async Task 设置DC输入(bool 开启, CancellationToken ct = default)
+ {
+ string 参数 = 开启 ? "ON" : "OFF";
+ await SendAsync($":INPut {参数}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 查询电子负载当前的拉载开关状态 (返回 ON 或 OFF)
+ ///
+ public virtual async Task 查询DC输入状态(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":INPut?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 设置电子负载的基本工作模式
+ /// (CC: 恒电流, CV: 恒电压, CP: 恒功率, CR: 恒电阻)
+ ///
+ public virtual async Task 设置负载模式(string 模式, CancellationToken ct = default)
+ {
+ // 转换为大写以确保设备命令解析兼容
+ string modeUpper = 模式.ToUpper();
+ if (modeUpper != "CC" && modeUpper != "CV" && modeUpper != "CP" && modeUpper != "CR")
+ throw new ArgumentException("工作模式只能为 CC, CV, CP, 或 CR");
+
+ await SendAsync($":MODE {modeUpper}{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 查询电子负载当前处于何种工作模式
+ ///
+ public virtual async Task 查询负载模式(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.3. 负载参数设置 (CC / CV / CP / CR)
+
+ ///
+ /// 设定恒电流模式(CC)下的拉载电流目标值 (单位: A)
+ ///
+ public virtual async Task 设置恒电流CC(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":CURRent {0:F4}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 查询恒电流模式下设定的拉载电流值
+ ///
+ public virtual async Task 查询恒电流CC设定(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 设定恒电压模式(CV)下的拉载电压目标值 (单位: V)
+ ///
+ public virtual async Task 设置恒电压CV(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 查询恒电压模式下设定的拉载电压值
+ ///
+ public virtual async Task 查询恒电压CV设定(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 设定恒功率模式(CP)下的拉载功率目标值 (单位: W)
+ ///
+ public virtual async Task 设置恒功率CP(double 功率, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":POWer {0:F3}{1}", 功率, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 查询恒功率模式下设定的拉载功率值
+ ///
+ public virtual async Task 查询恒功率CP设定(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 设定恒电阻模式(CR)下的等效拉载电阻阻值 (单位: Ω)
+ ///
+ public virtual async Task 设置恒电阻CR(double 电阻, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":RESistance {0:F4}{1}", 电阻, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 查询恒电阻模式下设定的拉载电阻值
+ ///
+ public virtual async Task 查询恒电阻CR设定(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":RESistance?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.4. 测量与数据回测(高频数据轮询核心)
+
+ ///
+ /// 回读电子负载输入端子上的实时测得电压值 (单位: V)
+ ///
+ public virtual async Task 查询实际电压(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 回读电子负载输入端子上的实时测得电流值 (单位: A)
+ ///
+ public virtual async Task 查询实际电流(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 回读电子负载输入端子上的实时测得功率值 (单位: W)
+ ///
+ public virtual async Task 查询实际功率(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 一键回读当前负载测得的电压、电流和功率的组合数组数据
+ ///
+ public virtual async Task 查询电压电流功率数组(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":MEASure:VAP?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 3.5. 保护限制参数设置
+
+ ///
+ /// 设置电子负载的过流保护(OCP)报警值 (单位: A)
+ ///
+ public virtual async Task 设置过流保护值_OCP(double 电流, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置电子负载的过功率保护(OPP)报警值 (单位: W)
+ ///
+ public virtual async Task 设置过功率保护值_OPP(double 功率, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:POWer {0:F3}{1}", 功率, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置电子负载的过压保护(OVP)报警值 (单位: V)
+ ///
+ public virtual async Task 设置过压保护值_OVP(double 电压, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":PROTection:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ #endregion
+
+ #region 3.6. 系统控制及报警清除
+
+ ///
+ /// 读取仪器的出错记录记录 (当系统产生告警或指令解析异常时读取)
+ ///
+ public virtual async Task 查询错误信息(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 切换电子负载到本地面板操作状态 (前面板按键解锁)
+ ///
+ public virtual async Task 切换本地控制模式(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 切换电子负载到远程计算机操作状态 (前面板按键锁定)
+ ///
+ public virtual async Task 切换远程控制模式(CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 清除负载当前的软件过流/过功率等保护(Protection)锁定触发状态
+ ///
+ public virtual async Task 清除保护告警(CancellationToken ct = default)
+ {
+ await SendAsync($":PROTection:CLEar{ScpiDelimiter}", ct);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Devices/SDS2000X_HD.cs b/DeviceCommand/Devices/SDS2000X_HD.cs
new file mode 100644
index 0000000..e44dbb0
--- /dev/null
+++ b/DeviceCommand/Devices/SDS2000X_HD.cs
@@ -0,0 +1,213 @@
+using Common.Attributes;
+using DeviceCommand.Base;
+using System;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Device
+{
+ [ADPCommand]
+ public class SDS2000X_HD : Tcp
+ {
+ // 示波器底层 Socket 字符串命令通常以换行符 \n 结束
+ private const string ScpiDelimiter = "\n";
+
+ ///
+ /// 构造函数:初始化示波器通信参数 (鼎阳示波器网口 Socket 默认端口通常为 5025)
+ ///
+ public SDS2000X_HD(string ipAddress, int port = 5025, int sendTimeout = 3000, int receiveTimeout = 3000)
+ {
+ ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
+ }
+
+ #region 1. IEEE 488.2 公共命令
+
+ ///
+ /// 清除标准事件状态寄存器和错误队列
+ ///
+ public virtual async Task 清除状态(CancellationToken ct = default)
+ {
+ await SendAsync($"*CLS{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 读取示波器识别字符串(制造商、型号、序列号、固件版本)
+ ///
+ public virtual async Task 查询设备标识(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
+ }
+
+ ///
+ /// 复位命令。使示波器恢复到默认的出厂设置
+ ///
+ public virtual async Task 重置设备(CancellationToken ct = default)
+ {
+ await SendAsync($"*RST{ScpiDelimiter}", ct); //
+ }
+ #endregion
+ #region 2. 运行与捕获控制控制 (Run / Stop / Single)
+
+ ///
+ /// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
+ ///
+ public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
+ {
+ await SendAsync($"RUN{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 停止捕获波形 (等同于按下前端面板的 Stop 键)
+ ///
+ public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
+ {
+ await SendAsync($"STOP{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 强制示波器进入单次触发捕获模式
+ ///
+ public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
+ {
+ await SendAsync($"SINGle{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 触发一次波形采样 (当触发源设为 Manual 时使用)
+ ///
+ public virtual async Task 强制触发(CancellationToken ct = default)
+ {
+ await SendAsync($"*TRG{ScpiDelimiter}", ct); //
+ }
+
+ #endregion
+
+ #region 3. Channel 垂直控制子系统 (C1 ~ C4)
+
+ ///
+ /// 开启或关闭指定的模拟通道 (例如: channel=1 代表 C1)
+ ///
+ public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
+ {
+ string state = enable ? "ON" : "OFF";
+ await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 设置指定通道的垂直电压档位 (Volts/Div,单位: V,例如 0.05 代表 50mV/div)
+ ///
+ public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:VDIV {1:F4}{2}", channel, volts, ScpiDelimiter); //
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置指定通道的垂直偏移量 (Offset,单位: V)
+ ///
+ 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); //
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置通道的输入阻抗 (1MΩ 或 50Ω)
+ ///
+ /// True: 50欧姆, False: 1M欧姆
+ public virtual async Task 设置通道输入阻抗(int channel, bool is50Ohm, CancellationToken ct = default)
+ {
+ string value = is50Ohm ? "50" : "1M";
+ await SendAsync($"C{channel}:COUPling {value}{ScpiDelimiter}", ct); //
+ }
+
+ #endregion
+
+ #region 4. Timebase 水平时基子系统
+
+ ///
+ /// 设置示波器的水平时基档位 (Time/Div,单位: s,例如 0.001 代表 1ms/div)
+ ///
+ public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "TIME_DIV {0:E6}{1}", scale, ScpiDelimiter); //
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置示波器的触发水平延迟位置 (Horizontal Delay,单位: s)
+ ///
+ public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:DELay {0:E6}{1}", delay, ScpiDelimiter); //
+ await SendAsync(cmd, ct);
+ }
+
+ #endregion
+
+ #region 5. Trigger 触发子系统
+
+ ///
+ /// 设置边沿触发的电平值 (Trigger Level,单位: V)
+ ///
+ public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:LEVel {0:F3}{1}", level, ScpiDelimiter); //
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置边沿触发的触发源 (例如: C1, C2, C3, C4, EX, LINE)
+ ///
+ public virtual async Task 设置触发源(string source, CancellationToken ct = default)
+ {
+ await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct); //
+ }
+
+ #endregion
+
+ #region 6. Measure 自动测量参数回读 (高频轮询核心)
+
+ ///
+ /// 查询指定通道自动测量项的当前实时测量数值
+ ///
+ /// 通道号 (1-4)
+ /// 参数名称助记符:
+ /// PKPK(峰峰值), MAX(最大值), MIN(最小值), AMPL(振幅值),
+ /// FREQ(频率), PER(周期), MEAN(平均值), RMS(均方根) 等
+ /// 设备返回的科学计数法或自定义字符串数值
+ public virtual async Task 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
+ {
+ // 语法格式示例:C1:PAVA? FREQ
+ string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter); //
+ return await WriteReadAsync(query, ScpiDelimiter, ct); //
+ }
+
+ ///
+ /// 轮询便捷接口:查询指定通道的电压峰峰值 (Vpp)
+ ///
+ public virtual async Task 查询实际电压峰峰值(int channel, CancellationToken ct = default)
+ {
+ return await 查询通道测量项参数(channel, "PKPK", ct); //
+ }
+
+ ///
+ /// 轮询便捷接口:查询指定通道的频率值 (Frequency)
+ ///
+ public virtual async Task 查询实际频率(int channel, CancellationToken ct = default)
+ {
+ return await 查询通道测量项参数(channel, "FREQ", ct); //
+ }
+
+ ///
+ /// 轮询便捷接口:查询指定通道的真均方根电压值 (Vrms)
+ ///
+ public virtual async Task 查询实际电压均方根(int channel, CancellationToken ct = default)
+ {
+ return await 查询通道测量项参数(channel, "RMS", ct); //
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/DeviceCommand/Devices/SPAW7000.cs b/DeviceCommand/Devices/SPAW7000.cs
new file mode 100644
index 0000000..3bed253
--- /dev/null
+++ b/DeviceCommand/Devices/SPAW7000.cs
@@ -0,0 +1,217 @@
+using Common.Attributes;
+using DeviceCommand.Base;
+using System;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace DeviceCommand.Device
+{
+ [ADPCommand]
+ public class SPAW7000 : Tcp
+ {
+ // 根据通用 SCPI 与远宽指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
+ private const string ScpiDelimiter = "\n";
+
+ ///
+ /// 构造函数:初始化 SPAW7000 功率分析记录仪通信参数
+ ///
+ public SPAW7000(string ipAddress, int port, int sendTimeout, int receiveTimeout)
+ {
+ ConfigureDevice(ipAddress, port, sendTimeout, receiveTimeout);
+ }
+
+ #region 1. IEEE 488.2 公共命令
+
+ ///
+ /// 清除状态命令。清除标准事件状态寄存器和错误队列
+ ///
+ public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
+ {
+ await SendAsync($"*CLS{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 读取功率分析仪识别字符串(制造商、产品型号、系统 SN、软件版本号)
+ ///
+ public virtual async Task 查询设备标识(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 复位命令。使功率分析仪恢复到出厂默认配置状态
+ ///
+ public virtual async Task 重置设备(CancellationToken ct = default)
+ {
+ await SendAsync($"*RST{ScpiDelimiter}", ct);
+ }
+
+ ///
+ /// 读取标准状态字节寄存器
+ ///
+ public virtual async Task 读取状态字节(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
+ }
+
+ #endregion
+
+ #region 2. MEASure / NUMeric 核心数据测量与轮询 (高频轮询核心)
+
+ ///
+ /// 查询指定通道的实时 RMS 电压值 (单位: V)
+ ///
+ /// 通道号 (例如: 1, 2, 3...)
+ public virtual async Task 查询实际电压(int channel, CancellationToken ct = default)
+ {
+ string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? U,{0}{1}", channel, ScpiDelimiter);
+ return await WriteReadAsync(query, ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询指定通道的实时 RMS 电流值 (单位: A)
+ ///
+ /// 通道号 (例如: 1, 2, 3...)
+ public virtual async Task 查询实际电流(int channel, CancellationToken ct = default)
+ {
+ string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? I,{0}{1}", channel, ScpiDelimiter);
+ return await WriteReadAsync(query, ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询指定通道的实时有功功率值 (单位: W)
+ ///
+ /// 通道号 (例如: 1, 2, 3...)
+ public virtual async Task 查询实际功率(int channel, CancellationToken ct = default)
+ {
+ string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? P,{0}{1}", channel, ScpiDelimiter);
+ return await WriteReadAsync(query, ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询指定通道的实时频率值 (单位: Hz)
+ ///
+ /// 通道号 (例如: 1, 2, 3...)
+ public virtual async Task 查询频率(int channel, CancellationToken ct = default)
+ {
+ string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? FREQuency,{0}{1}", channel, ScpiDelimiter);
+ return await WriteReadAsync(query, ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 查询指定通道的功率因数 (Power Factor)
+ ///
+ /// 通道号 (例如: 1, 2, 3...)
+ public virtual async Task 查询功率因数(int channel, CancellationToken ct = default)
+ {
+ string query = string.Format(CultureInfo.InvariantCulture, ":MEASure:NUMeric:VALue? PF,{0}{1}", channel, ScpiDelimiter);
+ return await WriteReadAsync(query, ScpiDelimiter, ct);
+ }
+
+ ///
+ /// 自定义组合参数批量读取接口
+ ///
+ /// 参数助记符 (如 "U,I,P" 或 "S,Q,LAMBda")
+ /// 通道号
+ public virtual async Task 查询自定义测量组合(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);
+ }
+
+ #endregion
+
+ #region 3. INPut 通道电气参数配置
+
+ ///
+ /// 设置指定通道的电压量程 (例如: 15, 30, 60, 150, 300, 600, 1000)
+ ///
+ public virtual async Task 设置电压量程(int channel, double range, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:VOLTage:RANGe {0},{1:F1}{2}", channel, range, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置指定通道的电流量程 (取决于接线单元或传感器输入类型)
+ ///
+ public virtual async Task 设置电流量程(int channel, double range, CancellationToken ct = default)
+ {
+ string cmd = string.Format(CultureInfo.InvariantCulture, ":INPut:CURRent:RANGe {0},{1:F3}{2}", channel, range, ScpiDelimiter);
+ await SendAsync(cmd, ct);
+ }
+
+ ///
+ /// 设置通道的耦合模式 (AC, DC, ACDC)
+ ///
+ 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
+
+ #region 4. SYSTem 系统设置与状态查询
+
+ ///
+ /// 14. 查询仪器型号名称
+ ///
+ public virtual async Task 查询设备型号(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":SYSTem:MODel?{ScpiDelimiter}", ScpiDelimiter, ct); //
+ }
+
+ ///
+ /// 16. 查询仪器唯一序列号
+ ///
+ public virtual async Task 查询设备序列号(CancellationToken ct = default)
+ {
+ return await WriteReadAsync($":SYSTem:SERial?{ScpiDelimiter}", ScpiDelimiter, ct); //
+ }
+
+ ///
+ /// 15. 设置数值数据显示的分辨率 (5位或6位)
+ ///
+ /// 有效值只能为 5 或 6
+ public virtual async Task 设置显示分辨率(int resolution, CancellationToken ct = default)
+ {
+ if (resolution != 5 && resolution != 6) throw new ArgumentException("分辨率只能设置为 5 或 6 位");
+ await SendAsync($":SYSTem:RESolution {resolution}{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 13. 设置或查询屏幕 LCD 的亮度级别 (1-10)
+ ///
+ public virtual async Task 设置显示亮度(int brightness, CancellationToken ct = default)
+ {
+ if (brightness < 1 || brightness > 10) throw new ArgumentOutOfRangeException(nameof(brightness), "亮度范围必须在 1~10 之间");
+ await SendAsync($":SYSTem:LCD:BRIGhtness {brightness}{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 18. 设置或查询触摸锁的开/关状态 (锁定时防止人工误触触控屏)
+ ///
+ public virtual async Task 设置屏幕触摸锁定(bool isLocked, CancellationToken ct = default)
+ {
+ string state = isLocked ? "ON" : "OFF";
+ await SendAsync($":SYSTem:TLOCK {state}{ScpiDelimiter}", ct); //
+ }
+
+ ///
+ /// 17. 设置或读取分析仪当前的系统内部时间
+ ///
+ /// 格式必须为 "HH:MM:SS"
+ public virtual async Task 设置系统时间(string timeStr, CancellationToken ct = default)
+ {
+ await SendAsync($":SYSTem:TIME \"{timeStr}\"{ScpiDelimiter}", ct); //
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Logger/Logger.csproj b/Logger/Logger.csproj
new file mode 100644
index 0000000..7a0942c
--- /dev/null
+++ b/Logger/Logger.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Logger/LoggerHelper.cs b/Logger/LoggerHelper.cs
new file mode 100644
index 0000000..2999674
--- /dev/null
+++ b/Logger/LoggerHelper.cs
@@ -0,0 +1,114 @@
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace Logger
+{
+ public static class LoggerHelper
+ {
+ public static readonly ILogger Logger = LogManager.GetLogger("InfoLogger");
+ public static readonly ILogger sqlLogger = LogManager.GetLogger("SqlLogger");
+ public static IProgress<(string message, string color, int depth)> Progress { get; set; }
+ static LoggerHelper()
+ {
+ Progress = new Progress<(string message, string color, int depth)>();
+ }
+ public static void InfoWithNotify(string message, int depth = 0)
+ {
+ Logger.Info(message); // 写入 NLog
+ NotifyUI(message, "blue", depth); // 触发UI显示
+ }
+
+ public static void SuccessWithNotify(string message, int depth = 0)
+ {
+ Logger.Info(message);
+ NotifyUI(message, "lightgreen", depth);
+ }
+ public static void WarnWithNotify(string message, string stackTrace = null, int depth = 0)
+ {
+ if (!string.IsNullOrEmpty(stackTrace))
+ {
+ string location = GetProjectStackLine(stackTrace);
+ message = $"{message} ({location})";
+ }
+
+ Logger.Warn(message);
+ NotifyUI(message, "orange", depth);
+ }
+
+ public static void ErrorWithNotify(string message, string stackTrace = null, int depth = 0)
+ {
+ if (!string.IsNullOrEmpty(stackTrace))
+ {
+ string location = GetProjectStackLine(stackTrace);
+ message = $"{message} ({location})";
+ }
+
+ Logger.Error(message);
+ NotifyUI(message, "red", depth);
+ }
+ private static void NotifyUI(string message, string color, int depth)
+ {
+ Progress.Report((message, color, depth));
+ }
+
+ public static void Info(string message, int depth = 0)
+ {
+ Logger.Info(message);
+ }
+ public static void Success(string message, int depth = 0)
+ {
+ Logger.Info(message);
+ }
+ public static void Warn(string message, string stackTrace = null, int depth = 0)
+ {
+ if (!string.IsNullOrEmpty(stackTrace))
+ {
+ string location = GetProjectStackLine(stackTrace);
+ message = $"{message} ({location})";
+ }
+
+ Logger.Warn(message);
+ }
+
+ public static void Error(string message, string stackTrace = null, int depth = 0)
+ {
+ if (!string.IsNullOrEmpty(stackTrace))
+ {
+ string location = GetProjectStackLine(stackTrace);
+ message = $"{message} ({location})";
+ }
+
+ Logger.Error(message);
+ }
+
+ public static string GetProjectStackLine(string stackTrace)
+ {
+ if (string.IsNullOrEmpty(stackTrace))
+ return "未知位置";
+
+ var lines = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var line in lines)
+ {
+ // 匹配你项目的命名空间路径
+ if (line.Contains("ADP"))
+ {
+ // 提取 "in 文件路径:line 行号"
+ var match = Regex.Match(line, @"in (.+?):line (\d+)");
+ if (match.Success)
+ {
+ return match.Value; // 返回类似 C:\...\MainViewModel.cs:line 37
+ }
+ return line.Trim();
+ }
+ }
+
+ return lines[0].Trim(); // 如果找不到就返回第一条
+ }
+ }
+}
diff --git a/Logger/Nlog.config b/Logger/Nlog.config
new file mode 100644
index 0000000..1b31316
--- /dev/null
+++ b/Logger/Nlog.config
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LoginModule/LoginModule.cs b/LoginModule/LoginModule.cs
new file mode 100644
index 0000000..f5865e1
--- /dev/null
+++ b/LoginModule/LoginModule.cs
@@ -0,0 +1,21 @@
+using LoginModule.Views;
+using Prism.Modularity;
+using System.Reflection;
+namespace LoginModule
+{
+ public class LoginModule : IModule
+ {
+ public void OnInitialized(IContainerProvider containerProvider)
+ {
+ IRegionManager regionManager = containerProvider.Resolve();
+ regionManager.RegisterViewWithRegion("LoginRegion", typeof(LoginView));
+ regionManager.RegisterViewWithRegion("LoginRegion", typeof(RegisterView));
+ }
+
+ public void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ containerRegistry.RegisterForNavigation("LoginView");
+ containerRegistry.RegisterForNavigation("RegisterView");
+ }
+ }
+}
diff --git a/LoginModule/LoginModule.csproj b/LoginModule/LoginModule.csproj
new file mode 100644
index 0000000..3df7504
--- /dev/null
+++ b/LoginModule/LoginModule.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0-windows
+ enable
+ true
+ enable
+
+
+
+
+
+
+
diff --git a/LoginModule/ViewModels/LoginViewModel.cs b/LoginModule/ViewModels/LoginViewModel.cs
new file mode 100644
index 0000000..cffbb91
--- /dev/null
+++ b/LoginModule/ViewModels/LoginViewModel.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using UIShare.PubEvent;
+using UIShare.ViewModelBase;
+
+namespace LoginModule.ViewModels
+{
+ public class LoginViewModel: NavigateViewModelBase
+ {
+ #region 属性
+ private string _Password;
+ public string Password
+ {
+ get => _Password;
+ set => SetProperty(ref _Password, value);
+ }
+ private string _Account;
+ public string Account
+ {
+ get => _Account;
+ set => SetProperty(ref _Account, value);
+ }
+ #endregion
+ public ICommand LoginCommand { get; set; }
+ public ICommand RegisterCommand { get; set; }
+ private IEventAggregator _eventAggregator;
+ public LoginViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ {
+ _eventAggregator = containerProvider.Resolve();
+ LoginCommand = new AsyncDelegateCommand(OnLogin);
+ RegisterCommand = new AsyncDelegateCommand(OnRegister);
+ }
+
+ private async Task OnRegister()
+ {
+ _regionManager.RequestNavigate("LoginRegion", "RegisterView");
+ }
+
+ private async Task OnLogin()
+ {
+ _eventAggregator.GetEvent().Publish();
+ }
+ }
+}
diff --git a/LoginModule/ViewModels/RegisterViewModel.cs b/LoginModule/ViewModels/RegisterViewModel.cs
new file mode 100644
index 0000000..0daf043
--- /dev/null
+++ b/LoginModule/ViewModels/RegisterViewModel.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using UIShare.ViewModelBase;
+
+namespace LoginModule.ViewModels
+{
+ public class RegisterViewModel : NavigateViewModelBase
+ {
+ #region 属性
+ private string _SecondPassword;
+ public string SecondPassword
+ {
+ get => _SecondPassword;
+ set => SetProperty(ref _SecondPassword, value);
+ }
+ private string _Password;
+ public string Password
+ {
+ get => _Password;
+ set => SetProperty(ref _Password, value);
+ }
+ private string _Account;
+ public string Account
+ {
+ get => _Account;
+ set => SetProperty(ref _Account, value);
+ }
+ #endregion
+ public ICommand BackCommand { get; set; }
+ public ICommand RegisterCommand { get; set; }
+ public RegisterViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ {
+ BackCommand = new DelegateCommand(OnBack);
+ RegisterCommand = new AsyncDelegateCommand(OnRegister);
+ }
+
+ private void OnBack()
+ {
+ _regionManager.RequestNavigate("LoginRegion", "LoginView");
+ }
+
+ private async Task OnRegister()
+ {
+ _regionManager.RequestNavigate("LoginRegion", "LoginView");
+ }
+ }
+}
diff --git a/LoginModule/Views/LoginView.xaml b/LoginModule/Views/LoginView.xaml
new file mode 100644
index 0000000..542444a
--- /dev/null
+++ b/LoginModule/Views/LoginView.xaml
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LoginModule/Views/LoginView.xaml.cs b/LoginModule/Views/LoginView.xaml.cs
new file mode 100644
index 0000000..31db76b
--- /dev/null
+++ b/LoginModule/Views/LoginView.xaml.cs
@@ -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 LoginModule.Views
+{
+ ///
+ /// LoginView.xaml 的交互逻辑
+ ///
+ public partial class LoginView : UserControl
+ {
+ public LoginView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/LoginModule/Views/RegisterView.xaml b/LoginModule/Views/RegisterView.xaml
new file mode 100644
index 0000000..5874855
--- /dev/null
+++ b/LoginModule/Views/RegisterView.xaml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LoginModule/Views/RegisterView.xaml.cs b/LoginModule/Views/RegisterView.xaml.cs
new file mode 100644
index 0000000..f8d1138
--- /dev/null
+++ b/LoginModule/Views/RegisterView.xaml.cs
@@ -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 LoginModule.Views
+{
+ ///
+ /// RegisterView.xaml 的交互逻辑
+ ///
+ public partial class RegisterView : UserControl
+ {
+ public RegisterView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/MainModule/MainModule.cs b/MainModule/MainModule.cs
new file mode 100644
index 0000000..39a030c
--- /dev/null
+++ b/MainModule/MainModule.cs
@@ -0,0 +1,28 @@
+using MainModule.Views;
+using System.Reflection;
+using UIShare.GlobalVariable;
+
+namespace MainModule
+{
+ [Module(OnDemand=true)]
+ public class MainModule : IModule
+ {
+ public void OnInitialized(IContainerProvider containerProvider)
+ {
+ IRegionManager regionManager = containerProvider.Resolve();
+ regionManager.RegisterViewWithRegion("ShellViewManager", typeof(MainView));
+ }
+
+ public void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ containerRegistry.RegisterForNavigation("MainView");
+ containerRegistry.RegisterForNavigation("AutomatedTestingView");
+ containerRegistry.RegisterForNavigation("ProtocolStartView");
+ // Scoped: one ScopedContext per container scope.
+ // AutomatedTestingViewModel creates its own scope (IContainerExtension.CreateScope)
+ // and resolves the 5 child VMs from it, so all siblings inside one parent share
+ // the same ScopedContext, while different parents get isolated instances.
+
+ }
+ }
+}
diff --git a/MainModule/MainModule.csproj b/MainModule/MainModule.csproj
new file mode 100644
index 0000000..8b229f9
--- /dev/null
+++ b/MainModule/MainModule.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net8.0-windows
+ enable
+ true
+ enable
+
+
+
+
+
+
+
+
diff --git a/MainModule/ViewModels/AutomatedTestingViewModel.cs b/MainModule/ViewModels/AutomatedTestingViewModel.cs
new file mode 100644
index 0000000..d7dbbaa
--- /dev/null
+++ b/MainModule/ViewModels/AutomatedTestingViewModel.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Windows.Input;
+using Prism.Ioc;
+using TestingModule.ViewModels;
+using UIShare;
+using UIShare.GlobalVariable;
+using UIShare.PubEvent;
+using UIShare.ViewModelBase;
+
+namespace MainModule.ViewModels
+{
+ public class AutomatedTestingViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
+ {
+ #region 私有字段
+ private string _testStatus;
+ private readonly IScopedProvider _scope;
+ #endregion
+
+ #region 属性
+ public bool KeepAlive => true; // 保持存活
+
+ public string TestStatus
+ {
+ get => _testStatus;
+ set => SetProperty(ref _testStatus, value);
+ }
+
+ // 该 AutomatedTestingView 实例独占的 ScopedContext,5 个子面板共享
+ public ScopedContext _scopedContext { get; }
+ public StepRunning _stepRunning { get; }
+ public GlobalInfo _globalInfo { get; }
+ public SystemConfig _systemConfig { get; }
+
+ // 5 个子 ViewModel 全部从同一个 scope 解析,自动注入同一个 ScopedContext
+ public CommandTreeViewModel CommandTreeVM { get; }
+ public StepsManagerViewModel StepsManagerVM { get; }
+ public SingleStepEditViewModel SingleStepEditVM { get; }
+ public LogAreaViewModel LogAreaVM { get; }
+ public ParametersManagerViewModel ParametersManagerVM { get; }
+ #endregion
+
+ public ICommand RefreshCommand { get; set; }
+ public ICommand BackToProtocolCommand { get; set; }
+
+ public AutomatedTestingViewModel(IContainerExtension container) : base(container)
+ {
+ // 每个 AutomatedTestingViewModel 实例创建独立的容器作用域
+ _scope = container.CreateScope();
+ _globalInfo=container.Resolve();
+ // 在该作用域内解析 ScopedContext —— 当前作用域唯一
+ _scopedContext = _scope.Resolve();
+ _stepRunning = _scope.Resolve();
+ _systemConfig=_scope.Resolve();
+ // 关键:从同一个 _scope 解析 5 个子 VM,DI 会把同一个 ScopedContext 注入它们
+ CommandTreeVM = _scope.Resolve();
+ StepsManagerVM = _scope.Resolve();
+ SingleStepEditVM = _scope.Resolve();
+ LogAreaVM = _scope.Resolve();
+ ParametersManagerVM = _scope.Resolve();
+ RefreshCommand = new DelegateCommand(OnRefresh);
+ BackToProtocolCommand = new DelegateCommand(OnBackToProtocol);
+ }
+
+ public void Dispose()
+ {
+ if (string.IsNullOrEmpty(TestStatus))
+ {
+ _scope?.Dispose();
+ return;
+ }
+ try
+ {
+ _globalInfo.ContextDic?.Remove(TestStatus);
+ _globalInfo.StepRunningDic?.Remove(TestStatus);
+ }
+ catch (Exception ex)
+ {
+ Logger.LoggerHelper.ErrorWithNotify($"卸载机台 [{TestStatus}] 全局引用失败: {ex.Message}");
+ }
+ finally
+ {
+ _scope?.Dispose();
+ }
+ }
+ #region 命令处理与事件
+ private void OnRefresh()
+ {
+ // 双击:把自己的名字扔出去
+ _eventAggregator.GetEvent().Publish(TestStatus);
+ _globalInfo.CurrentScope = TestStatus;
+ }
+
+ private void OnBackToProtocol()
+ {
+ // 返回:扔个空字符串出去
+ _eventAggregator.GetEvent().Publish("");
+ }
+ #endregion
+
+ #region 重写
+ public override void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ base.OnNavigatedTo(navigationContext);
+ if (navigationContext.Parameters.ContainsKey("Name"))
+ {
+ TestStatus = navigationContext.Parameters.GetValue("Name");
+ _globalInfo.ContextDic.Add(TestStatus, _scopedContext);
+ _globalInfo.StepRunningDic.Add(TestStatus, _stepRunning);
+ }
+
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MainModule/ViewModels/MainViewModel.cs b/MainModule/ViewModels/MainViewModel.cs
new file mode 100644
index 0000000..db8d819
--- /dev/null
+++ b/MainModule/ViewModels/MainViewModel.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using UIShare.PubEvent;
+using MainModule.ViewModels;
+using UIShare.ViewModelBase;
+
+
+namespace MainModule.ViewModels
+{
+ public class MainViewModel : NavigateViewModelBase, IRegionMemberLifetime
+ {
+ #region 私有字段
+ private bool IsInitialized = false;
+ private string _expandedCellName = string.Empty;
+ #endregion
+
+ #region 属性
+ public bool KeepAlive => true; // 保持存活
+
+ public string ExpandedCellName
+ {
+ get => _expandedCellName;
+ set => SetProperty(ref _expandedCellName, value);
+ }
+ #endregion
+
+ #region 命令
+ public ICommand LoadedCommand { get; set; }
+ #endregion
+
+ public MainViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ {
+ LoadedCommand = new DelegateCommand(OnLoaded);
+ _eventAggregator.GetEvent().Subscribe(OnCellExpandRequested);
+ }
+
+ #region 命令处理与事件
+ private void OnLoaded()
+ {
+ if (IsInitialized) return;
+ for (int i = 1; i <= 9; i++)
+ {
+ var parameters = new NavigationParameters { { "Name", $"TestCell{i}" } };
+ _regionManager.RequestNavigate($"TestCell{i}", "ProtocolStartView", parameters);
+ }
+ IsInitialized = true;
+ }
+
+ // 不再物理搬迁视图,只需修改一个字符串属性 ExpandedCellName,
+ // XAML 里每个单元的 Style.Triggers 会根据该值处理隐藏 / 跨越 3x3。
+ private void OnCellExpandRequested(string cellName)
+ {
+ ExpandedCellName = cellName ?? string.Empty;
+ }
+ #endregion
+
+ #region 重写
+ public override bool IsNavigationTarget(NavigationContext navigationContext)
+ {
+ // 之前帮你改好的单例复用逻辑(防止重复 new 和初始化视图)
+ return true;
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MainModule/ViewModels/ProtocolStartViewModel.cs b/MainModule/ViewModels/ProtocolStartViewModel.cs
new file mode 100644
index 0000000..2c12011
--- /dev/null
+++ b/MainModule/ViewModels/ProtocolStartViewModel.cs
@@ -0,0 +1,109 @@
+using Prism.Navigation.Regions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using UIShare.ViewModelBase;
+
+namespace MainModule.ViewModels
+{
+ public class ProtocolStartViewModel : NavigateViewModelBase
+ {
+ #region 私有字段
+ private string _testStatus;
+ private string _moduleColor;
+ #endregion
+
+ #region 属性
+ public string TestStatus
+ {
+ get => _testStatus;
+ set => SetProperty(ref _testStatus, value);
+ }
+
+ public string ModuleColor
+ {
+ get => _moduleColor;
+ set => SetProperty(ref _moduleColor, value);
+ }
+ #endregion
+
+ #region 命令
+ public DelegateCommand StartProtocolCommand { get; }
+ #endregion
+
+ public ProtocolStartViewModel(IContainerProvider containerProvider) : base(containerProvider)
+ {
+ StartProtocolCommand = new DelegateCommand(OnStart);
+ }
+
+ #region 命令处理与事件
+ private void OnStart()
+ {
+ // 只在当前格子所属的 Cell Region 内完成切换,不会影响其他格子位置
+ SwitchNavigate("AutomatedTestingView");
+ }
+
+ ///
+ /// 定位当前视图所在的 Cell Region(TestCell1..TestCell9),
+ /// 在同一个 Region 内完成跳转。Region 位置由 XAML Grid 锁定,永远不会错位。
+ ///
+ public void SwitchNavigate(string viewName)
+ {
+ // 1. 反向查找:哪个 Cell Region 当前托着“我”这个 ProtocolStartView
+ for (int i = 1; i <= 9; i++)
+ {
+ var regionName = $"TestCell{i}";
+ if (!_regionManager.Regions.ContainsRegionWithName(regionName)) continue;
+
+ var region = _regionManager.Regions[regionName];
+ var myView = region.Views
+ .OfType()
+ .FirstOrDefault(v => v.DataContext == this);
+
+ if (myView == null) continue;
+
+ // 2. 透传名称与颜色参数,使 AutomatedTestingViewModel 能正确初始化
+ var parameters = new NavigationParameters();
+ parameters.Add("Name", TestStatus);
+ parameters.Add("Color", ModuleColor);
+
+ // 3. 在本格子 Region 内请求导航,导航成功后再移除旧的 ProtocolStartView 以释放资源
+ _regionManager.RequestNavigate(regionName, viewName, navResult =>
+ {
+ if (navResult.Success == true)
+ {
+ region.Remove(myView);
+ }
+ }, parameters);
+
+ return;
+ }
+ }
+ #endregion
+
+ #region 重写
+ public override bool IsNavigationTarget(NavigationContext navigationContext)
+ {
+ if (navigationContext.Parameters.ContainsKey("Name"))
+ return TestStatus == navigationContext.Parameters.GetValue("Name");
+ return true;
+ }
+
+ public override void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ base.OnNavigatedTo(navigationContext);
+
+ // 接收导航传参:名称与颜色
+ if (navigationContext.Parameters.ContainsKey("Name"))
+ TestStatus = navigationContext.Parameters.GetValue("Name");
+
+ if (navigationContext.Parameters.ContainsKey("Color"))
+ ModuleColor = navigationContext.Parameters.GetValue("Color");
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MainModule/Views/AutomatedTestingView.xaml b/MainModule/Views/AutomatedTestingView.xaml
new file mode 100644
index 0000000..786cd78
--- /dev/null
+++ b/MainModule/Views/AutomatedTestingView.xaml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MainModule/Views/AutomatedTestingView.xaml.cs b/MainModule/Views/AutomatedTestingView.xaml.cs
new file mode 100644
index 0000000..3d56642
--- /dev/null
+++ b/MainModule/Views/AutomatedTestingView.xaml.cs
@@ -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 MainModule.Views
+{
+ ///
+ /// AutomatedTestingView.xaml 的交互逻辑
+ ///
+ public partial class AutomatedTestingView : UserControl
+ {
+ public AutomatedTestingView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/MainModule/Views/MainView.xaml b/MainModule/Views/MainView.xaml
new file mode 100644
index 0000000..1b411b6
--- /dev/null
+++ b/MainModule/Views/MainView.xaml
@@ -0,0 +1,258 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MainModule/Views/MainView.xaml.cs b/MainModule/Views/MainView.xaml.cs
new file mode 100644
index 0000000..f8052f9
--- /dev/null
+++ b/MainModule/Views/MainView.xaml.cs
@@ -0,0 +1,19 @@
+using MainModule.ViewModels;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MainModule.Views
+{
+ ///
+ /// MainView.xaml 的交互逻辑
+ ///
+ public partial class MainView : UserControl
+ {
+ public MainView()
+ {
+ InitializeComponent();
+ }
+
+
+ }
+}
diff --git a/MainModule/Views/ProtocolStartView.xaml b/MainModule/Views/ProtocolStartView.xaml
new file mode 100644
index 0000000..d3362bc
--- /dev/null
+++ b/MainModule/Views/ProtocolStartView.xaml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MainModule/Views/ProtocolStartView.xaml.cs b/MainModule/Views/ProtocolStartView.xaml.cs
new file mode 100644
index 0000000..2dca76e
--- /dev/null
+++ b/MainModule/Views/ProtocolStartView.xaml.cs
@@ -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 MainModule.Views
+{
+ ///
+ /// ProtocolStartView.xaml 的交互逻辑
+ ///
+ public partial class ProtocolStartView : UserControl
+ {
+ public ProtocolStartView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Model/Entity/BaseEntity.cs b/Model/Entity/BaseEntity.cs
new file mode 100644
index 0000000..8f6f7b0
--- /dev/null
+++ b/Model/Entity/BaseEntity.cs
@@ -0,0 +1,27 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Model.Entity
+{
+ public abstract class BaseEntity
+ {
+ ///
+ /// 主键Id
+ ///
+ [SugarColumn(ColumnName = "id", ColumnDescription = "主键Id", IsPrimaryKey = true, CreateTableFieldSort = 0)]
+ public virtual long Id { get; set; }
+
+ ///
+ /// 删除状态
+ ///
+ [SugarColumn(ColumnName = "IsDel", ColumnDescription = "删除状态(0、未删除;1、已删除)", ColumnDataType = "tinyint", DefaultValue = "0", CreateTableFieldSort = 106)]
+ public virtual byte IsDel { get; set; }
+
+ [SugarColumn(ColumnName = "CreateTime")]
+ public DateTime CreateTime { get; set; }
+ }
+}
diff --git a/Model/Model.csproj b/Model/Model.csproj
new file mode 100644
index 0000000..48d5f57
--- /dev/null
+++ b/Model/Model.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/Model/Result.cs b/Model/Result.cs
new file mode 100644
index 0000000..6582ea0
--- /dev/null
+++ b/Model/Result.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Model
+{
+ public class Result
+ {
+ public Result()
+ {
+
+ }
+ private int _code;
+ private readonly string _msg;
+
+ ///
+ /// 结果是否成功
+ ///
+ public bool IsSuccess => _code == 0;
+
+ ///
+ /// 结果代码
+ ///
+ public int Code { set => _code = value; get => _code; }
+
+ ///
+ /// 结果消息
+ ///
+ public string Msg => _msg;
+
+ ///
+ /// 构造方法
+ ///
+ ///
+ ///
+ ///
+ protected Result(int code, string msg, Exception? exception = null)
+ {
+ if (exception != null)
+ {
+ var listMoreMsg = new List();
+ if (exception is ResultException resultException)
+ {
+ listMoreMsg.AddRange(resultException.MessageList);
+ }
+ //else if (exception is DeviceControlException deviceControlException)
+ //{
+ // listMoreMsg.Add(deviceControlException.ErrorInfo);
+ //}
+ else
+ {
+ listMoreMsg.Add(exception.Message);
+ }
+ var strMoreMsg = string.Join("、", listMoreMsg.Where(it => !string.IsNullOrWhiteSpace(it)));
+ if (!string.IsNullOrWhiteSpace(strMoreMsg))
+ {
+ msg += $"({strMoreMsg})";
+ }
+ }
+ _code = code;
+ _msg = msg;
+ }
+
+ ///
+ /// 返回成功结果
+ ///
+ ///
+ ///
+ public static Result Success()
+ {
+ return new Result(0, "");
+ }
+
+ ///
+ /// 返回错误结果
+ ///
+ ///
+ ///
+ ///
+ public static Result Error(string msg, Exception? exception = null)
+ {
+ return new Result(-1, msg, exception);
+ }
+
+
+ }
+
+ public class Result : Result
+ {
+ public Result()
+ {
+
+ }
+ private T? _data;
+
+ ///
+ /// 结果数据
+ ///
+ public T? Data { set => _data = value; get => _data; }
+
+ ///
+ /// 构造方法
+ ///
+ ///
+ ///
+ ///
+ ///
+ private Result(int code, string msg, T? data, Exception? exception = null) : base(code, msg, exception)
+ {
+ _data = data;
+ }
+
+ ///
+ /// 返回成功数据
+ ///
+ ///
+ ///
+ public static Result Success(T data)
+ {
+ return new Result(0, "", data);
+ }
+
+ ///
+ /// 返回失败信息
+ ///
+ ///
+ ///
+ ///
+ public new static Result Error(string msg, Exception? exception = null)
+ {
+ return new Result(-1, msg, default, exception);
+ }
+
+ ///
+ /// 返回失败信息和数据
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static Result Error(string msg, T data, Exception? exception = null)
+ {
+ return new Result(-1, msg, data, exception);
+ }
+
+
+ }
+
+ public class ResultException : Exception
+ {
+ private List? _messageList;
+
+ public List MessageList
+ {
+ get
+ {
+ List messages = new();
+ if (_messageList != null)
+ {
+ messages.AddRange(_messageList.Where(it => !string.IsNullOrWhiteSpace(it)));
+ }
+ return messages;
+ }
+ }
+
+ public override string Message => string.Join(", ", MessageList);
+
+ public ResultException() : base(null)
+ {
+ }
+
+ public void AddAdditionMessage(string message)
+ {
+ _messageList ??= new();
+ _messageList.Add(message);
+ }
+ }
+}
diff --git a/MonitorModule.txt b/MonitorModule.txt
new file mode 100644
index 0000000..e69de29
diff --git a/MonitorModule/MonitorModule.cs b/MonitorModule/MonitorModule.cs
new file mode 100644
index 0000000..5099bf7
--- /dev/null
+++ b/MonitorModule/MonitorModule.cs
@@ -0,0 +1,21 @@
+
+using MonitorModule.Views;
+using System.Reflection;
+
+namespace MonitorModule
+{
+ public class MonitorModule: IModule
+ {
+ public void OnInitialized(IContainerProvider containerProvider)
+ {
+ IRegionManager regionManager = containerProvider.Resolve();
+ }
+
+ public void RegisterTypes(IContainerRegistry containerRegistry)
+ {
+ containerRegistry.RegisterForNavigation("MonitorView");
+ containerRegistry.RegisterForNavigation("RecordView");
+ }
+ }
+
+}
diff --git a/MonitorModule/MonitorModule.csproj b/MonitorModule/MonitorModule.csproj
new file mode 100644
index 0000000..45b0b00
--- /dev/null
+++ b/MonitorModule/MonitorModule.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net8.0-windows
+ enable
+ true
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MonitorModule/ViewModels/MonitorViewModel.cs b/MonitorModule/ViewModels/MonitorViewModel.cs
new file mode 100644
index 0000000..f04732b
--- /dev/null
+++ b/MonitorModule/ViewModels/MonitorViewModel.cs
@@ -0,0 +1,330 @@
+using OxyPlot;
+using OxyPlot.Axes;
+using OxyPlot.Legends;
+using OxyPlot.Series;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using System.Windows.Threading;
+using UIShare.GlobalVariable;
+using UIShare.PubEvent;
+using UIShare.ViewModelBase;
+
+namespace MonitorModule.ViewModels
+{
+ ///
+ /// 监控界面 VM —— 基于 OxyPlot 的曲线监控壳。
+ /// 当前阶段不接入真实数据源,仅搭好框架:
+ /// - 添加信号 / 删除信号
+ /// - 重置视图(按数据范围复原)
+ /// - 刷新(重绘 PlotView)
+ /// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份图表与信号列表。
+ ///
+ public class MonitorViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
+ {
+ #region 私有字段
+ private readonly IScopedProvider _scope;
+ // 颜色轮转池,给新加的信号自动分配区分色
+ 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 _signalCounter;
+
+ // ===== 模拟数据相关 =====
+ // 模拟定时器:周期性给每条信号追加新点,形成滚动效果
+ private readonly DispatcherTimer _simTimer;
+ // 全局采样时间(秒),每 tick 自增 _simInterval
+ private double _simT;
+ // 采样间隔(秒),与 DispatcherTimer.Interval 保持一致
+ private const double _simInterval = 0.1;
+ // 滚动窗口大小:每条曲线最多保留 200 个点(约 20 秒)
+ private const int _maxPoints = 200;
+ // 用于 random walk 的随机源
+ private static readonly Random _rng = new();
+ #endregion
+
+ #region 隔离 / 标题
+ public bool KeepAlive => true;
+ public ScopedContext _scopedContext { get; }
+ public GlobalInfo GlobalInfoRef { get; }
+
+ private string _testStatus = string.Empty;
+ public string TestStatus
+ {
+ get => _testStatus;
+ set => SetProperty(ref _testStatus, value);
+ }
+ #endregion
+
+ #region OxyPlot
+ /// PlotView 直接绑这个 PlotModel
+ public PlotModel Plot { get; }
+
+ /// 当前已添加的信号集合(左侧列表展示)
+ public ObservableCollection Signals { get; } = new();
+
+ private SignalItem? _selectedSignal;
+ public SignalItem? SelectedSignal
+ {
+ get => _selectedSignal;
+ set => SetProperty(ref _selectedSignal, value);
+ }
+
+ private string _statusMessage = "图表已就绪,暂无信号";
+ public string StatusMessage
+ {
+ get => _statusMessage;
+ set => SetProperty(ref _statusMessage, value);
+ }
+ #endregion
+
+ #region 命令
+ public ICommand AddSignalCommand { get; }
+ public ICommand DeleteSignalCommand { get; }
+ public ICommand ResetViewCommand { get; }
+ public ICommand RefreshDataCommand { get; }
+ // 双击展开/折叠:与 RecordView/AutomatedTestingView 共用同一套 ExpandViewEvent
+ public ICommand RefreshCommand { get; }
+ #endregion
+
+ public MonitorViewModel(IContainerExtension container) : base(container)
+ {
+ _scope = container.CreateScope();
+ GlobalInfoRef = container.Resolve();
+ _scopedContext = _scope.Resolve();
+
+ Plot = BuildEmptyPlot();
+
+ AddSignalCommand = new DelegateCommand(OnAddSignal);
+ DeleteSignalCommand = new DelegateCommand(OnDeleteSignal);
+ ResetViewCommand = new DelegateCommand(OnResetView);
+ RefreshDataCommand = new DelegateCommand(OnRefreshData);
+ RefreshCommand = new DelegateCommand(OnExpand);
+
+ // 启动模拟数据定时器:100ms 一帧,给每条信号喂一个新点
+ _simTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(_simInterval) };
+ _simTimer.Tick += OnSimTick;
+ _simTimer.Start();
+
+ // 默认预置 3 条模拟信号,便于直接看到滚动效果
+ OnAddSignal(); // sin
+ OnAddSignal(); // cos
+ OnAddSignal(); // random walk
+ }
+
+ public void Dispose()
+ {
+ _simTimer?.Stop();
+ _scope?.Dispose();
+ }
+
+ #region PlotModel 构建
+ private static PlotModel BuildEmptyPlot()
+ {
+ var pm = new PlotModel
+ {
+ Title = string.Empty,
+ PlotAreaBorderColor = OxyColors.LightGray,
+ Background = OxyColors.White
+ };
+ pm.Axes.Add(new LinearAxis
+ {
+ Position = AxisPosition.Bottom,
+ Title = "X",
+ MajorGridlineStyle = LineStyle.Dot,
+ MinorGridlineStyle = LineStyle.None
+ });
+ pm.Axes.Add(new LinearAxis
+ {
+ Position = AxisPosition.Left,
+ Title = "Y",
+ 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 命令处理
+ ///
+ /// 添加一条信号:按 _signalCounter 轮转选择不同波形 generator,
+ /// 后续 _simTimer 每帧会调用 generator(t) 给曲线追加新点形成滚动效果。
+ ///
+ private void OnAddSignal()
+ {
+ _signalCounter++;
+ var color = _palette[(_signalCounter - 1) % _palette.Length];
+
+ // 6 种波形轮转:sin / cos / 锯齿 / 方波 / 衰减正弦 / random walk
+ var (waveName, generator) = BuildGenerator(_signalCounter);
+ var name = $"{waveName}_{_signalCounter}";
+
+ var series = new LineSeries
+ {
+ Title = name,
+ Color = color,
+ StrokeThickness = 1.5
+ };
+
+ var item = new SignalItem(name, series, generator);
+ Signals.Add(item);
+ SelectedSignal = item;
+
+ Plot.Series.Add(series);
+ Plot.InvalidatePlot(true);
+
+ StatusMessage = $"已添加信号 [{name}],当前共 {Signals.Count} 条";
+ }
+
+ ///
+ /// 按索引返回一种模拟波形发生器。
+ /// 振幅 / 频率 / 相位都做了区分,让多条曲线视觉上分开。
+ ///
+ private static (string waveName, Func generator) BuildGenerator(int index)
+ {
+ // 给同种波形不同实例一些随机偏移,避免完全重叠
+ double phase = (index * 0.7) % (2 * Math.PI);
+ double amp = 1.0 + (index % 3) * 0.3;
+ double freq = 0.5 + (index % 4) * 0.2;
+
+ return (index % 6) switch
+ {
+ 0 => ("Sin", t => amp * Math.Sin(2 * Math.PI * freq * t + phase)),
+ 1 => ("Cos", t => amp * Math.Cos(2 * Math.PI * freq * t + phase)),
+ 2 => ("Saw", t => amp * (2 * ((t * freq) - Math.Floor(t * freq + 0.5)))),
+ 3 => ("Square", t => amp * Math.Sign(Math.Sin(2 * Math.PI * freq * t + phase))),
+ 4 => ("Decay", t => amp * Math.Exp(-0.05 * t) * Math.Sin(2 * Math.PI * freq * t + phase)),
+ _ => RandomWalkGenerator(amp),
+ };
+ }
+
+ /// 构造一个随机游走 generator:在前一次值基础上叠加高斯噪声。
+ private static (string, Func) RandomWalkGenerator(double amp)
+ {
+ double last = 0;
+ return ("Walk", _ =>
+ {
+ last += (_rng.NextDouble() - 0.5) * 0.2 * amp;
+ // 软约束在 [-amp*3, amp*3],避免一直跑偏
+ if (last > amp * 3) last = amp * 3;
+ if (last < -amp * 3) last = -amp * 3;
+ return last;
+ });
+ }
+
+ /// 删除当前选中信号;若未选中则删除最后一条。
+ private void OnDeleteSignal()
+ {
+ var target = SelectedSignal ?? Signals.LastOrDefault();
+ if (target == null)
+ {
+ StatusMessage = "无可删除的信号";
+ return;
+ }
+
+ Plot.Series.Remove(target.Series);
+ Signals.Remove(target);
+ SelectedSignal = Signals.LastOrDefault();
+
+ Plot.InvalidatePlot(true);
+ StatusMessage = $"已删除信号 [{target.Name}],剩余 {Signals.Count} 条";
+ }
+
+ /// 按数据范围复原视图(重置所有坐标轴的缩放/平移)。
+ private void OnResetView()
+ {
+ Plot.ResetAllAxes();
+ Plot.InvalidatePlot(false);
+ StatusMessage = "视图已按数据范围复原";
+ }
+
+ /// 刷新:触发 PlotView 重绘;后续接数据源时可在此重拉数据。
+ private void OnRefreshData()
+ {
+ Plot.InvalidatePlot(true);
+ StatusMessage = $"已刷新({DateTime.Now:HH:mm:ss})";
+ }
+
+ /// 双击展开 / 折叠九宫格。
+ private void OnExpand()
+ {
+ if (string.IsNullOrEmpty(TestStatus)) return;
+ _eventAggregator.GetEvent().Publish(TestStatus);
+ GlobalInfoRef.CurrentScope = TestStatus;
+ }
+
+ ///
+ /// 模拟数据 tick:每条信号按当前 _simT 算出新点并追加。
+ /// 超过 _maxPoints 时丢弃最旧的点形成滚动窗口。
+ ///
+ private void OnSimTick(object? sender, EventArgs e)
+ {
+ _simT += _simInterval;
+
+ if (Signals.Count == 0)
+ {
+ return;
+ }
+
+ foreach (var item in Signals)
+ {
+ double y = item.Generator(_simT);
+ item.Series.Points.Add(new DataPoint(_simT, y));
+ if (item.Series.Points.Count > _maxPoints)
+ {
+ item.Series.Points.RemoveAt(0);
+ }
+ }
+
+ // 让 X 轴跟着最新数据滚动
+ var xAxis = Plot.Axes.FirstOrDefault(a => a.Position == AxisPosition.Bottom);
+ if (xAxis != null)
+ {
+ double window = _maxPoints * _simInterval;
+ xAxis.Minimum = Math.Max(0, _simT - window);
+ xAxis.Maximum = _simT + 0.5;
+ }
+
+ Plot.InvalidatePlot(true);
+ }
+ #endregion
+
+ #region 导航
+ public override void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ base.OnNavigatedTo(navigationContext);
+ if (navigationContext.Parameters.ContainsKey("Name"))
+ {
+ TestStatus = navigationContext.Parameters.GetValue("Name");
+ Plot.Title = $"监控 - {TestStatus}";
+ Plot.InvalidatePlot(false);
+ }
+ }
+ #endregion
+ }
+
+ ///
+ /// 信号列表项:UI 显示名 + 对应的 OxyPlot LineSeries 引用 + 模拟数据 generator。
+ /// generator(t) 接收当前模拟时间,返回该时刻 y 值。
+ ///
+ public class SignalItem
+ {
+ public string Name { get; }
+ public LineSeries Series { get; }
+ public Func Generator { get; }
+ public SignalItem(string name, LineSeries series, Func generator)
+ {
+ Name = name;
+ Series = series;
+ Generator = generator;
+ }
+ }
+}
diff --git a/MonitorModule/ViewModels/RecordViewModel.cs b/MonitorModule/ViewModels/RecordViewModel.cs
new file mode 100644
index 0000000..d11bf99
--- /dev/null
+++ b/MonitorModule/ViewModels/RecordViewModel.cs
@@ -0,0 +1,362 @@
+using Logger;
+using SqlSugar;
+using System.Collections.ObjectModel;
+using System.Data;
+using System.IO;
+using System.Text;
+using System.Windows.Input;
+using UIShare.GlobalVariable;
+using UIShare.PubEvent;
+using UIShare.ViewModelBase;
+
+namespace MonitorModule.ViewModels
+{
+ ///
+ /// 记录界面:用于查看运行目录下 SQL/ADP.db 中的数据。
+ /// 功能:表选择 / 关键字 WHERE 查询 / 分页 / 导出 CSV。
+ /// 仍延续 ScopedContext 隔离 + 双击展开 的范式,每个工位独立一份查询状态。
+ ///
+ public class RecordViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
+ {
+ #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 readonly IScopedProvider _scope;
+ #endregion
+
+ #region 隔离演示属性(保留 ScopedContext 验证用)
+ public bool KeepAlive => true;
+ public ScopedContext _scopedContext { get; }
+ // 公开一份 GlobalInfo 供双击展开逻辑使用(基类的 _globalInfo 是 private)
+ public GlobalInfo GlobalInfoRef { get; }
+ #endregion
+
+ #region 顶部工位标题
+ private string _testStatus = string.Empty;
+ public string TestStatus
+ {
+ get => _testStatus;
+ set => SetProperty(ref _testStatus, value);
+ }
+ #endregion
+
+ #region 数据库 / 表 / 查询条件
+ private ObservableCollection _tableNames = new();
+ public ObservableCollection TableNames
+ {
+ get => _tableNames;
+ set => SetProperty(ref _tableNames, value);
+ }
+
+ private string? _selectedTable;
+ public string? SelectedTable
+ {
+ get => _selectedTable;
+ set
+ {
+ if (SetProperty(ref _selectedTable, value))
+ {
+ PageIndex = 1;
+ Query();
+ }
+ }
+ }
+
+ // 用户填写的 WHERE 子句(不带 WHERE 关键字),例如:Status='OK' AND Id>10
+ private string _whereClause = string.Empty;
+ public string WhereClause
+ {
+ get => _whereClause;
+ set => SetProperty(ref _whereClause, value);
+ }
+
+ private DataTable _resultTable = new();
+ public DataTable ResultTable
+ {
+ get => _resultTable;
+ set => SetProperty(ref _resultTable, value);
+ }
+
+ private string _statusMessage = "未连接";
+ public string StatusMessage
+ {
+ get => _statusMessage;
+ set => SetProperty(ref _statusMessage, value);
+ }
+ #endregion
+
+ #region 分页
+ private int _pageIndex = 1;
+ public int PageIndex
+ {
+ get => _pageIndex;
+ set => SetProperty(ref _pageIndex, value);
+ }
+
+ private int _pageSize = 50;
+ public int PageSize
+ {
+ get => _pageSize;
+ set
+ {
+ if (SetProperty(ref _pageSize, value <= 0 ? 50 : value))
+ {
+ RaisePropertyChanged(nameof(TotalPages));
+ PageIndex = 1;
+ Query();
+ }
+ }
+ }
+
+ private long _totalCount;
+ public long TotalCount
+ {
+ get => _totalCount;
+ set
+ {
+ SetProperty(ref _totalCount, value);
+ RaisePropertyChanged(nameof(TotalPages));
+ }
+ }
+
+ public int TotalPages
+ {
+ get
+ {
+ if (PageSize <= 0) return 1;
+ var pages = (int)((TotalCount + PageSize - 1) / PageSize);
+ return pages <= 0 ? 1 : pages;
+ }
+ }
+ #endregion
+
+ #region 命令
+ public ICommand LoadedCommand { get; }
+ public ICommand RefreshTablesCommand { get; }
+ public ICommand QueryCommand { get; }
+ public ICommand FirstPageCommand { get; }
+ public ICommand PrevPageCommand { get; }
+ public ICommand NextPageCommand { get; }
+ public ICommand LastPageCommand { get; }
+ public ICommand ExportCsvCommand { get; }
+ // 双击展开/折叠:与 MonitorView/AutomatedTestingView 共用
+ public ICommand RefreshCommand { get; }
+ #endregion
+
+ public RecordViewModel(IContainerExtension container) : base(container)
+ {
+ _scope = container.CreateScope();
+ GlobalInfoRef = container.Resolve();
+ _scopedContext = _scope.Resolve();
+
+ LoadedCommand = new DelegateCommand(LoadTables);
+ RefreshTablesCommand = new DelegateCommand(LoadTables);
+ QueryCommand = new DelegateCommand(() => { PageIndex = 1; Query(); });
+ FirstPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex = 1; Query(); } });
+ PrevPageCommand = new DelegateCommand(() => { if (PageIndex > 1) { PageIndex--; Query(); } });
+ NextPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex++; Query(); } });
+ LastPageCommand = new DelegateCommand(() => { if (PageIndex < TotalPages) { PageIndex = TotalPages; Query(); } });
+ ExportCsvCommand = new DelegateCommand(ExportCsv);
+ RefreshCommand = new DelegateCommand(OnExpand);
+ }
+
+ public void Dispose()
+ {
+ _scope?.Dispose();
+ }
+
+ #region 数据库操作
+ private SqlSugarClient CreateClient()
+ {
+ return new SqlSugarClient(new ConnectionConfig
+ {
+ DbType = SqlSugar.DbType.Sqlite,
+ ConnectionString = ConnStr,
+ IsAutoCloseConnection = true,
+ InitKeyType = InitKeyType.Attribute
+ });
+ }
+
+ ///
+ /// 读取数据库中的所有用户表。数据库不存在时不报错,仅在状态栏提示。
+ ///
+ private void LoadTables()
+ {
+ try
+ {
+ if (!Directory.Exists(DbFolder)) Directory.CreateDirectory(DbFolder);
+ if (!File.Exists(DbPath))
+ {
+ TableNames = new ObservableCollection();
+ SelectedTable = null;
+ ResultTable = new DataTable();
+ TotalCount = 0;
+ StatusMessage = $"数据库尚未创建:{DbPath}";
+ return;
+ }
+
+ using var db = CreateClient();
+ var tables = db.DbMaintenance.GetTableInfoList(false)
+ .Select(t => t.Name)
+ .OrderBy(n => n)
+ .ToList();
+
+ TableNames = new ObservableCollection(tables);
+ if (tables.Count == 0)
+ {
+ SelectedTable = null;
+ ResultTable = new DataTable();
+ TotalCount = 0;
+ StatusMessage = "数据库已连接,但暂无数据表";
+ }
+ else
+ {
+ StatusMessage = $"已连接:{DbPath}({tables.Count} 张表)";
+ if (string.IsNullOrEmpty(SelectedTable) || !tables.Contains(SelectedTable))
+ {
+ SelectedTable = tables[0]; // setter 会触发 Query
+ }
+ else
+ {
+ Query();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"加载数据表失败:{ex.Message}");
+ StatusMessage = $"加载失败:{ex.Message}";
+ }
+ }
+
+ ///
+ /// 执行分页查询。WHERE 为空则查询全部。
+ ///
+ private void Query()
+ {
+ if (string.IsNullOrEmpty(SelectedTable) || !File.Exists(DbPath))
+ {
+ ResultTable = new DataTable();
+ TotalCount = 0;
+ return;
+ }
+
+ try
+ {
+ using var db = CreateClient();
+ string table = $"[{SelectedTable}]";
+ string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
+
+ // COUNT 总数
+ var countSql = $"SELECT COUNT(*) FROM {table} WHERE {where}";
+ TotalCount = db.Ado.GetLong(countSql);
+
+ // 修正越界
+ if (PageIndex > TotalPages) PageIndex = TotalPages;
+ if (PageIndex < 1) PageIndex = 1;
+
+ int offset = (PageIndex - 1) * PageSize;
+ var dataSql = $"SELECT * FROM {table} WHERE {where} LIMIT {PageSize} OFFSET {offset}";
+ ResultTable = db.Ado.GetDataTable(dataSql);
+
+ StatusMessage = $"表 [{SelectedTable}] 共 {TotalCount} 行 第 {PageIndex}/{TotalPages} 页";
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"查询失败:{ex.Message}");
+ StatusMessage = $"查询失败:{ex.Message}";
+ ResultTable = new DataTable();
+ TotalCount = 0;
+ }
+ }
+ #endregion
+
+ #region 导出 CSV
+ ///
+ /// 按当前 WHERE 条件导出全部结果到 CSV(不分页)。
+ ///
+ 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
+ {
+ using var db = CreateClient();
+ string table = $"[{SelectedTable}]";
+ string where = string.IsNullOrWhiteSpace(WhereClause) ? "1=1" : WhereClause;
+ var sql = $"SELECT * FROM {table} WHERE {where}";
+ var dt = db.Ado.GetDataTable(sql);
+
+ WriteCsv(dlg.FileName, dt);
+
+ StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
+ LoggerHelper.InfoWithNotify($"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行");
+ }
+ catch (Exception ex)
+ {
+ LoggerHelper.ErrorWithNotify($"导出失败:{ex.Message}");
+ StatusMessage = $"导出失败:{ex.Message}";
+ }
+ }
+
+ private static void WriteCsv(string path, DataTable dt)
+ {
+ // 写 UTF-8 BOM 让 Excel 直接识别中文
+ using var sw = new StreamWriter(path, false, new UTF8Encoding(true));
+ // 表头
+ sw.WriteLine(string.Join(",", dt.Columns.Cast().Select(c => Escape(c.ColumnName))));
+ // 行
+ foreach (DataRow row in dt.Rows)
+ {
+ sw.WriteLine(string.Join(",", row.ItemArray.Select(v => Escape(v?.ToString() ?? string.Empty))));
+ }
+ }
+
+ private static string Escape(string field)
+ {
+ if (field.Contains('"') || field.Contains(',') || field.Contains('\r') || field.Contains('\n'))
+ {
+ return "\"" + field.Replace("\"", "\"\"") + "\"";
+ }
+ return field;
+ }
+ #endregion
+
+ #region 双击展开
+ private void OnExpand()
+ {
+ if (string.IsNullOrEmpty(TestStatus)) return;
+ _eventAggregator.GetEvent().Publish(TestStatus);
+ GlobalInfoRef.CurrentScope = TestStatus;
+ }
+ #endregion
+
+ #region 导航
+ public override void OnNavigatedTo(NavigationContext navigationContext)
+ {
+ base.OnNavigatedTo(navigationContext);
+ if (navigationContext.Parameters.ContainsKey("Name"))
+ {
+ TestStatus = navigationContext.Parameters.GetValue("Name");
+ }
+ // 进入界面时自动加载表
+ LoadTables();
+ }
+ #endregion
+ }
+}
diff --git a/MonitorModule/Views/MonitorViewView.xaml b/MonitorModule/Views/MonitorViewView.xaml
new file mode 100644
index 0000000..087152e
--- /dev/null
+++ b/MonitorModule/Views/MonitorViewView.xaml
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MonitorModule/Views/MonitorViewView.xaml.cs b/MonitorModule/Views/MonitorViewView.xaml.cs
new file mode 100644
index 0000000..27a9683
--- /dev/null
+++ b/MonitorModule/Views/MonitorViewView.xaml.cs
@@ -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 MonitorModule.Views
+{
+ ///
+ /// MonitorView.xaml 的交互逻辑
+ ///
+ public partial class MonitorView : UserControl
+ {
+ public MonitorView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/MonitorModule/Views/RecordView.xaml b/MonitorModule/Views/RecordView.xaml
new file mode 100644
index 0000000..ca46630
--- /dev/null
+++ b/MonitorModule/Views/RecordView.xaml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MonitorModule/Views/RecordView.xaml.cs b/MonitorModule/Views/RecordView.xaml.cs
new file mode 100644
index 0000000..7ea4b89
--- /dev/null
+++ b/MonitorModule/Views/RecordView.xaml.cs
@@ -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 MonitorModule.Views
+{
+ ///
+ /// RecordView.xaml 的交互逻辑
+ ///
+ public partial class RecordView : UserControl
+ {
+ public RecordView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/ORM/DatabaseConfig.cs b/ORM/DatabaseConfig.cs
new file mode 100644
index 0000000..a790081
--- /dev/null
+++ b/ORM/DatabaseConfig.cs
@@ -0,0 +1,132 @@
+using SqlSugar;
+using System;
+
+using System.IO;
+using System.Security.Cryptography;
+
+namespace ORM
+{
+ public class DatabaseConfig
+ {
+ ///
+ /// 数据库连接类型
+ ///
+ public static DbType DbConnectionType { get; private set; }
+
+ ///
+ /// SQLite 数据库文件路径
+ ///
+ public static string DbConnectionString { get; private set; }
+
+ ///
+ /// 数据租户Id
+ ///
+ public static int TenantId { get; private set; }
+
+ ///
+ /// 雪花算法DatacenterId,值范围:0至31
+ ///
+ public static int SnowFlakeDatacenterId { get; private set; }
+
+ ///
+ /// 雪花算法WorkID,值范围:0至31
+ ///
+ public static int SnowFlakeWorkId { get; private set; }
+
+ #region 不同数据库初始化方法
+ public static void InitSqlite()
+ {
+ DbConnectionType = DbType.Sqlite;
+ // 获取程序运行目录
+ string baseDir = AppDomain.CurrentDomain.BaseDirectory;
+ // 确保 SQLDB 文件夹存在
+ string folder = Path.Combine(baseDir, "SQLDB");
+ if (!Directory.Exists(folder))
+ Directory.CreateDirectory(folder);
+
+ // 拼接数据库文件路径
+ string DBPath = Path.Combine(folder, "SQL.db");
+ DbConnectionString = $"Data Source={DBPath};Version=3;";
+ }
+ public static void InitMySql(string Server, int Port, string Database, string Uid,string Pwd)
+ {
+ DbConnectionType = DbType.MySql;
+ DbConnectionString =
+ $"Server={Server};Port={Port};Database={Database};Uid={Uid};Pwd={Pwd};";
+ }
+ public static void InitSqlServer(string server,int port,string database,string user,string password)
+ {
+ DbConnectionType = DbType.SqlServer;
+
+ DbConnectionString =
+ $"Data Source={server},{port};Initial Catalog={database};user={user};Password={password};";
+ }
+ public static void InitSqlServerLocalDb()
+ {
+ DbConnectionType = DbType.SqlServer;
+ string baseDir = AppDomain.CurrentDomain.BaseDirectory;
+ // 确保 SQLDB 文件夹存在
+ string folder = Path.Combine(baseDir, "SQLDB");
+ if (!Directory.Exists(folder))
+ Directory.CreateDirectory(folder);
+
+ // 拼接数据库文件路径
+ string DBPath = Path.Combine(folder, "SQL.db");
+ DbConnectionString =$@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog={DBPath};Integrated Security=True;";
+ }
+ #endregion
+ ///
+ /// 设置数据库连接字符串(可手动覆盖)
+ ///
+ public static void SetDbConnection(string strConnection)
+ {
+ if (string.IsNullOrEmpty(strConnection))
+ {
+ throw new Exception("数据库连接字符串为空");
+ }
+ DbConnectionString = strConnection;
+ }
+
+ ///
+ /// 设置数据租户Id
+ ///
+ public static void SetTenant(int nTenantId)
+ {
+ if (nTenantId <= 10000)
+ {
+ throw new Exception("数据租户Id值需大于10000");
+ }
+ TenantId = nTenantId;
+ SetSnowFlake((nTenantId - 10000) % 1024);
+ }
+
+ ///
+ /// 将指定数字转换为雪花算法DatacenterId和WorkID
+ ///
+ private static void SetSnowFlake(int nSnowFlakeId)
+ {
+ if (nSnowFlakeId > 1023 || nSnowFlakeId < 0)
+ {
+ throw new Exception("雪花算法机器码值范围0至1023");
+ }
+ SnowFlakeDatacenterId = nSnowFlakeId >> 5;
+ SnowFlakeWorkId = nSnowFlakeId & 31;
+ }
+ ///
+ /// 检测数据库连接
+ ///
+ public static void CreateDatabaseAndCheckConnection(bool createDatabase = false, bool checkConnection = false)
+ {
+ //数据库不存在则创建数据库
+ if (createDatabase)
+ {
+ SqlSugarContext.DbContext.DbMaintenance.CreateDatabase();
+ }
+ // 检查数据库连接
+ if (checkConnection && !SqlSugarContext.DbContext.Ado.IsValidConnection())
+ {
+ throw new Exception("连接数据库失败");
+ }
+ }
+ }
+}
diff --git a/ORM/ORM.csproj b/ORM/ORM.csproj
new file mode 100644
index 0000000..ba630a2
--- /dev/null
+++ b/ORM/ORM.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/ORM/SqlSugarContext.cs b/ORM/SqlSugarContext.cs
new file mode 100644
index 0000000..4fb45a9
--- /dev/null
+++ b/ORM/SqlSugarContext.cs
@@ -0,0 +1,123 @@
+using Logger;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ORM
+{
+ public class SqlSugarContext
+ {
+ ///
+ /// SqlSugarScope单例模式
+ ///
+ public static readonly SqlSugarScope DbContext = new SqlSugarScope(
+ new ConnectionConfig()
+ {
+ DbType = DatabaseConfig.DbConnectionType,
+ ConnectionString = DatabaseConfig.DbConnectionString, // 连接符字串
+ //ConfigId = "Db1",//管理多个数据库
+ IsAutoCloseConnection = true, // 自动关闭连接
+ InitKeyType = InitKeyType.Attribute // 通过实体类上的特性初始化
+ },
+ db =>
+ {
+ // 执行超时时间,单位秒
+ db.Ado.CommandTimeOut = 30;
+
+ // 每次SQL执行前事件
+ db.Aop.OnLogExecuting = (sql, pars) =>
+ {
+#if DEBUG
+ // 确保 SQL 只通过 sqlLogger 记录
+ LoggerHelper.sqlLogger.Info("Executing SQL: {0} with parameters: {1}", sql, pars); // 使用 NLog 记录 SQL
+#endif
+ };
+
+ // SQL执行完
+ db.Aop.OnLogExecuted = (sql, pars) =>
+ {
+ // 执行时间超过1秒记录慢日志
+ if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
+ {
+ LoggerHelper.sqlLogger.Warn("SQL Slow Execution. Time: {0}, File: {1}, Line: {2}, Method: {3}, SQL: {4}",
+ db.Ado.SqlExecutionTime.ToString(), // SQL 执行时间
+ db.Ado.SqlStackTrace.FirstFileName, // 代码 CS 文件名
+ db.Ado.SqlStackTrace.FirstLine, // 代码行数
+ db.Ado.SqlStackTrace.FirstMethodName, // 代码方法名
+ sql); // 使用 NLog 记录慢 SQL 日志
+ }
+ };
+
+ // SQL 报错
+ db.Aop.OnError = (exp) =>
+ {
+ // 确保 SQL 错误日志仅通过 sqlLogger 记录
+ LoggerHelper.sqlLogger.Error(exp, "SQL Error: {0}", exp.Sql); // 使用 NLog 记录 SQL 错误日志
+ };
+
+ // 数据过滤器:例如在新增数据时生成雪花 Id
+ db.Aop.DataExecuting = (oldValue, entityInfo) =>
+ {
+ // 新增操作
+ if (entityInfo.OperationType == DataFilterType.InsertByObject)
+ {
+ // 主键(long)赋值雪花 Id
+ if (entityInfo.EntityColumnInfo.IsPrimarykey && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long))
+ {
+ var id = ((dynamic)entityInfo.EntityValue).Id;
+ if (id == null || id == 0)
+ {
+ SnowFlakeSingle.WorkId = DatabaseConfig.SnowFlakeWorkId;
+ SnowFlakeSingle.DatacenterId = DatabaseConfig.SnowFlakeDatacenterId;
+ entityInfo.SetValue(SnowFlakeSingle.Instance.NextId());
+ }
+ }
+ }
+ };
+ }
+ );
+
+ ///
+ /// 初始化数据库,创建所有后缀为 Entity 的表
+ ///
+ public static void InitDatabase()
+ {
+ // 加载 Model.dll 文件
+ string modelDllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Model.dll");
+
+ if (!File.Exists(modelDllPath))
+ {
+ throw new FileNotFoundException($"未找到 Model.dll 文件: {modelDllPath}");
+ }
+
+ // 加载指定的程序集(Model.dll)
+ var modelAssembly = Assembly.LoadFrom(modelDllPath);
+
+ // 获取所有的 Model层下的Entity 类(后缀为 Entity)
+ var entityTypes = modelAssembly.GetTypes()
+ .Where(t => t.Name.EndsWith("Entity") && t.IsClass && !t.IsInterface && !t.IsAbstract)
+ .ToList();
+
+
+ // 使用 SqlSugar 自动创建表
+ using (var db = DbContext)
+ {
+ foreach (var entityType in entityTypes)
+ {
+ // 判断该类型是否为类且符合条件
+ if (entityType.IsClass)
+ {
+ // 自动创建表
+ db.CodeFirst.InitTables(entityType);
+ }
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/ORM/SqlSugarRepository.cs b/ORM/SqlSugarRepository.cs
new file mode 100644
index 0000000..92512d3
--- /dev/null
+++ b/ORM/SqlSugarRepository.cs
@@ -0,0 +1,396 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ORM
+{
+ public partial class SqlSugarRepository : SimpleClient where TEntity : class, new()
+ {
+ ///
+ /// 实体集合
+ ///
+ public ISugarQueryable Entities => Context.Queryable();
+
+ ///
+ /// 构造函数
+ ///
+ public SqlSugarRepository(ISqlSugarClient context = null) : base(context)
+ {
+ // 绑定数据库操作对象
+ Context = SqlSugarContext.DbContext;
+ // 备忘:GetConnectionScopeWithAttr会导致不能触发Aop.OnLogExecuting、Aop.OnLogExecuted等事件
+ // 详见:https://www.donet5.com/Home/Doc?typeId=2405(之2.1、方法说明)
+ // 详见:https://www.donet5.com/Home/Doc?typeId=2246(之2.2、根据特性获取)和(之4、多租户设置AOP)
+ // base.Context = SqlSugarContext.DbContext.GetConnectionScopeWithAttr();
+ // 根据特性获取,适合一个实体和库是一对一的情况
+ }
+
+
+ #region 查询
+ ///
+ /// 检查是否存在
+ ///
+ ///
+ ///
+ public bool IsExists(Expression> whereExpression)
+ {
+ return Entities.Any(whereExpression);
+ }
+
+ ///
+ /// 检查是否存在
+ ///
+ ///
+ ///
+ public async Task IsExistsAsync(Expression> whereExpression)
+ {
+ return await Entities.AnyAsync(whereExpression);
+ }
+
+ ///
+ /// 通过主键获取实体
+ ///
+ ///
+ ///
+ public TEntity Single(dynamic Id)
+ {
+ return Entities.InSingle(Id);
+ }
+
+ ///
+ /// 获取一个实体
+ ///
+ ///
+ ///
+ public TEntity Single(Expression> whereExpression)
+ {
+ return Entities.Single(whereExpression);
+ }
+
+ ///
+ /// 获取一个实体
+ ///
+ ///
+ ///
+ public Task SingleAsync(Expression> whereExpression)
+ {
+ return Entities.SingleAsync(whereExpression);
+ }
+
+ ///
+ /// 获取一个实体
+ ///
+ ///
+ ///
+ public TEntity FirstOrDefault(Expression> whereExpression)
+ {
+ return Entities.First(whereExpression);
+ }
+
+ ///
+ /// 获取一个实体
+ ///
+ ///
+ ///
+ public async Task FirstOrDefaultAsync(Expression> whereExpression)
+ {
+ return await Entities.FirstAsync(whereExpression);
+ }
+
+ ///
+ /// 获取列表
+ ///
+ ///
+ public List ToList()
+ {
+ return Entities.ToList();
+ }
+
+ ///
+ /// 获取列表
+ ///
+ ///
+ public Task> ToListAsync()
+ {
+ return Entities.ToListAsync();
+ }
+
+ ///
+ /// 获取列表
+ ///
+ ///
+ ///
+ public List ToList(Expression> whereExpression)
+ {
+ return Entities.Where(whereExpression).ToList();
+ }
+
+ ///
+ /// 获取列表
+ ///
+ ///
+ ///
+ public Task> ToListAsync(Expression> whereExpression)
+ {
+ return Entities.Where(whereExpression).ToListAsync();
+ }
+
+ ///
+ /// 获取列表
+ ///
+ ///
+ ///
+ ///
+ ///
+ public List ToList(Expression> whereExpression, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
+ {
+ return Entities.OrderByIF(orderByExpression != null, orderByExpression, orderByType).Where(whereExpression).ToList();
+ }
+
+ ///
+ /// 获取列表
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task> ToListAsync(Expression> whereExpression, Expression> orderByExpression = null, OrderByType orderByType = OrderByType.Asc)
+ {
+ return Entities.OrderByIF(orderByExpression != null, orderByExpression, orderByType).Where(whereExpression).ToListAsync();
+ }
+ #endregion 查询
+
+
+ #region 新增
+ ///
+ /// 新增多条记录
+ ///
+ ///
+ ///
+ public int Insert(TEntity[] entities)
+ {
+ return Context.Insertable(entities).ExecuteCommand();
+ }
+
+ ///
+ /// 新增多条记录
+ ///
+ ///
+ ///
+ public Task InsertAsync(TEntity[] entities)
+ {
+ return Context.Insertable(entities).ExecuteCommandAsync();
+ }
+
+ ///
+ /// 新增多条记录
+ ///
+ ///
+ ///
+ public int Insert(IEnumerable entities)
+ {
+ return Context.Insertable(entities.ToArray()).ExecuteCommand();
+ }
+
+ ///
+ /// 新增多条记录
+ ///
+ ///
+ ///
+ public Task InsertAsync(IEnumerable entities)
+ {
+ if (entities != null && entities.Any())
+ {
+ return Context.Insertable(entities.ToArray()).ExecuteCommandAsync();
+ }
+ return Task.FromResult(0);
+ }
+ #endregion 新增
+
+
+ #region 更新
+ ///
+ /// 更新单条记录指定列
+ ///
+ ///
+ ///
+ ///
+ public int Update(TEntity entity, object updateColumn)
+ {
+ return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommand();
+ }
+
+ ///
+ /// 更新单条记录指定列
+ ///
+ ///
+ ///
+ ///
+ public Task UpdateAsync(TEntity entity, object updateColumn)
+ {
+ return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommandAsync();
+ }
+
+ ///
+ /// 更新单条记录指定列
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int Update(TEntity entity, object updateColumn, Expression> whereExpression)
+ {
+ return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).Where(whereExpression).ExecuteCommand();
+ }
+
+ ///
+ /// 更新单条记录指定列
+ ///
+ ///
+ ///
+ ///
+ ///
+ public Task UpdateAsync(TEntity entity, object updateColumn, Expression> whereExpression)
+ {
+ return Context.Updateable(entity).UpdateColumns(MergeUpdateColumns(updateColumn)).Where(whereExpression).ExecuteCommandAsync();
+ }
+
+ ///
+ /// 更新多条记录
+ ///
+ ///
+ ///
+ public int Update(TEntity[] entities)
+ {
+ return Context.Updateable(entities).ExecuteCommand();
+ }
+
+ ///
+ /// 更新多条记录
+ ///
+ ///
+ ///
+ public Task UpdateAsync(TEntity[] entities)
+ {
+ return Context.Updateable(entities).ExecuteCommandAsync();
+ }
+
+ ///
+ /// 更新多条记录指定列
+ ///
+ ///
+ ///
+ ///
+ public int Update(TEntity[] entities, object updateColumn)
+ {
+ return Context.Updateable(entities).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommand();
+ }
+
+ ///
+ /// 更新多条记录指定列
+ ///
+ ///
+ ///
+ ///
+ public Task UpdateAsync(TEntity[] entities, object updateColumn)
+ {
+ return Context.Updateable(entities).UpdateColumns(MergeUpdateColumns(updateColumn)).ExecuteCommandAsync();
+ }
+
+ ///
+ /// 合并指定更新列和附加更新列
+ ///
+ ///
+ ///
+ private string[] MergeUpdateColumns(object updateColumn)
+ {
+ List columnList = new List();
+ if (updateColumn.GetType() == typeof(string))
+ {
+ columnList.Add((string)updateColumn);
+ }
+ else if (updateColumn.GetType() == typeof(string[]))
+ {
+ columnList.AddRange((string[])updateColumn);
+ }
+ return columnList.ToArray();
+ }
+ #endregion 更新
+
+
+ #region 删除
+ ///
+ /// 删除一条记录
+ ///
+ ///
+ ///
+ public int Delete(object key)
+ {
+ return Context.Deleteable().In(key).ExecuteCommand();
+ }
+
+ ///
+ /// 删除一条记录
+ ///
+ ///
+ ///
+ public Task DeleteAsync(object key)
+ {
+ return Context.Deleteable().In(key).ExecuteCommandAsync();
+ }
+
+ ///
+ /// 删除多条记录
+ ///
+ ///
+ ///
+ public int Delete(object[] keys)
+ {
+ return Context.Deleteable().In(keys).ExecuteCommand();
+ }
+
+ ///
+ /// 删除多条记录
+ ///
+ ///
+ ///
+ public Task DeleteAsync(object[] keys)
+ {
+ return Context.Deleteable().In(keys).ExecuteCommandAsync();
+ }
+ #endregion 删除
+
+
+ #region 事务
+ ///
+ /// 开启事务
+ ///
+ public void BeginTran()
+ {
+ Context.Ado.BeginTran();
+ }
+
+ ///
+ /// 提交事务
+ ///
+ public void CommitTran()
+ {
+ Context.Ado.CommitTran();
+ }
+
+ ///
+ /// 回滚事务
+ ///
+ public void RollbackTran()
+ {
+ Context.Ado.RollbackTran();
+ }
+ #endregion 事务
+
+ }
+}
diff --git a/Service/Implement/BaseService.cs b/Service/Implement/BaseService.cs
new file mode 100644
index 0000000..7df16db
--- /dev/null
+++ b/Service/Implement/BaseService.cs
@@ -0,0 +1,132 @@
+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
+{
+ public class BaseService : IBaseService where TEntity : BaseEntity, new()
+ {
+ protected readonly SqlSugarRepository _repository;
+
+ public BaseService(SqlSugarRepository repository)
+ {
+ _repository = repository;
+ }
+
+ ///
+ /// 查询全部
+ ///
+ public virtual async Task>> GetAllAsync()
+ {
+ try
+ {
+ var list = await _repository.Entities.ToListAsync();
+ return Result>.Success(list);
+ }
+ catch (Exception ex)
+ {
+ return Result>.Error("查询所有数据失败", ex);
+ }
+ }
+ ///
+ /// 根据日期查询全部
+ ///
+ public virtual async Task>> GetAllAsyncByDate(DateTime? startDate, DateTime? endDate)
+ {
+ try
+ {
+ var list = await _repository.Entities.Where(x => x.CreateTime >= startDate && x.CreateTime <= endDate).ToListAsync();
+ return Result>.Success(list);
+ }
+ catch (Exception ex)
+ {
+ return Result>.Error("查询所有数据失败", ex);
+ }
+ }
+
+ ///
+ /// 分页查询
+ ///
+ public virtual async Task>> GetPagedAsync(int pageIndex, int pageSize, RefAsync total)
+ {
+ try
+ {
+ var list = await _repository.Entities
+ .OrderBy(d => d.Id)
+ .ToPageListAsync(pageIndex, pageSize, total);
+ total.Value = (int)Math.Ceiling((double)total.Value / pageSize);
+ return Result>.Success(list);
+ }
+ catch (Exception ex)
+ {
+ return Result