添加项目文件。
This commit is contained in:
163
ACP.sln
Normal file
163
ACP.sln
Normal file
@@ -0,0 +1,163 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.12.35527.113
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeviceEditModule", "DeviceEditModule\DeviceEditModule.csproj", "{170AD4C1-189D-4FBE-B10D-2A4304527834}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZLGUSBCANFD", "ZLGUSBCANFD\ZLGUSBCANFD.csproj", "{542FE380-2344-4343-9AC6-F0C587C13B88}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchMovementModule", "BenchMovementModule\BenchMovementModule.csproj", "{31F84D0E-3634-4C70-A830-21AF412E3BBF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CANModule", "CANModule\CANModule.csproj", "{AF2533DD-599D-49D7-942B-B0C26534D15F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "加密狗", "加密狗\加密狗.csproj", "{A35CA316-7B5B-4183-8636-4ECA532B1489}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CurveModule", "CurveModule\CurveModule.csproj", "{84006890-9288-44C4-9D17-FA88F3FDECFA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportModule", "ExportModule\ExportModule.csproj", "{8923122F-F7D3-4AE7-9E86-9E78F1296A11}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ACP", "ACP\ACP.csproj", "{CC810066-06A3-4635-96C7-1D30D78C2130}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{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
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{542FE380-2344-4343-9AC6-F0C587C13B88}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{542FE380-2344-4343-9AC6-F0C587C13B88}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{31F84D0E-3634-4C70-A830-21AF412E3BBF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AF2533DD-599D-49D7-942B-B0C26534D15F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A35CA316-7B5B-4183-8636-4ECA532B1489}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84006890-9288-44C4-9D17-FA88F3FDECFA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8923122F-F7D3-4AE7-9E86-9E78F1296A11}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CC810066-06A3-4635-96C7-1D30D78C2130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CC810066-06A3-4635-96C7-1D30D78C2130}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CC810066-06A3-4635-96C7-1D30D78C2130}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CC810066-06A3-4635-96C7-1D30D78C2130}.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}
|
||||
{170AD4C1-189D-4FBE-B10D-2A4304527834} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{31F84D0E-3634-4C70-A830-21AF412E3BBF} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{AF2533DD-599D-49D7-942B-B0C26534D15F} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{84006890-9288-44C4-9D17-FA88F3FDECFA} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
{8923122F-F7D3-4AE7-9E86-9E78F1296A11} = {1E92D601-68FC-45F6-8251-BD0F39226E5B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
53
ACP/ACP.csproj
Normal file
53
ACP/ACP.csproj
Normal file
@@ -0,0 +1,53 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Images\error.png" />
|
||||
<None Remove="Images\info.png" />
|
||||
<None Remove="Images\warning.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BenchMovementModule\BenchMovementModule.csproj" />
|
||||
<ProjectReference Include="..\CANModule\CANModule.csproj" />
|
||||
<ProjectReference Include="..\Command\Command.csproj" />
|
||||
<ProjectReference Include="..\CurveModule\CurveModule.csproj" />
|
||||
<ProjectReference Include="..\DeviceCommand\DeviceCommand.csproj" />
|
||||
<ProjectReference Include="..\DeviceEditModule\DeviceEditModule.csproj" />
|
||||
<ProjectReference Include="..\ExportModule\ExportModule.csproj" />
|
||||
<ProjectReference Include="..\LoginModule\LoginModule.csproj" />
|
||||
<ProjectReference Include="..\MainModule\MainModule.csproj" />
|
||||
<ProjectReference Include="..\MonitorModule\MonitorModule.csproj" />
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
<ProjectReference Include="..\SettingModule\SettingModule.csproj" />
|
||||
<ProjectReference Include="..\TestingModule\TestingModule.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
||||
<ProjectReference Include="..\ZLGUSBCANFD\ZLGUSBCANFD.csproj" />
|
||||
<ProjectReference Include="..\加密狗\加密狗.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Images\error.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Images\info.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Images\warning.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
16
ACP/App.xaml
Normal file
16
ACP/App.xaml
Normal file
@@ -0,0 +1,16 @@
|
||||
<prism:PrismApplication x:Class="ACP.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:ACP"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:prism="http://prismlibrary.com/">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!--自定义style-->
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/CommonStyle.xaml"></ResourceDictionary>
|
||||
<ResourceDictionary Source="/UIShare;component/Styles/WindowStyle.xaml"></ResourceDictionary>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</prism:PrismApplication>
|
||||
163
ACP/App.xaml.cs
Normal file
163
ACP/App.xaml.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using Common;
|
||||
using ACP.ViewModels;
|
||||
using ACP.ViewModels.Dialogs;
|
||||
using ACP.Views;
|
||||
using ACP.Views;
|
||||
using ACP.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;
|
||||
using System;
|
||||
using DeviceCommand.Devices;
|
||||
using DeviceCommand.Base;
|
||||
using AutoMapper;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using ACP.Profiles;
|
||||
using 加密狗;
|
||||
|
||||
namespace ACP
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : PrismApplication
|
||||
{
|
||||
protected override Window CreateShell()
|
||||
{
|
||||
//UI线程未捕获异常处理事件
|
||||
this.DispatcherUnhandledException += OnDispatcherUnhandledException;
|
||||
//Task线程内未捕获异常处理事件
|
||||
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
||||
////多线程异常
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
return Container.Resolve<ShellView>();
|
||||
}
|
||||
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()
|
||||
{
|
||||
// 配置全局日志分发器:按 CurrentScope 路由到对应 LogArea
|
||||
var globalInfo = Container.Resolve<GlobalInfo>();
|
||||
LoggerHelper.Progress = new ScopeLogDispatcher(globalInfo);
|
||||
|
||||
//初始化数据库
|
||||
DatabaseConfig.SetTenant(10001);
|
||||
DatabaseConfig.InitSqlite();
|
||||
DatabaseConfig.CreateDatabaseAndCheckConnection(createDatabase: true, checkConnection: true);
|
||||
SqlSugarContext.InitDatabase();
|
||||
//显示登录窗口
|
||||
var login = Container.Resolve<LoginModuleView>();
|
||||
var re = Container.Resolve<IRegionManager>();
|
||||
RegionManager.SetRegionManager(login, re);
|
||||
RegionManager.SetRegionManager(Application.Current.MainWindow, re);
|
||||
login.Show();
|
||||
}
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
base.OnStartup(e);
|
||||
return;
|
||||
}
|
||||
|
||||
string strUserSn = "ACA89B2C1194650D4E93D97BE189FFCC";
|
||||
string strApiKey = "C6D01BCD442347822876F8232245DE9422643C167B8C61CE4DF6049990E7395588675A8FC6AA4BF7AD353D669C993236BEA83F77551753885A47AF34185CC3A7";
|
||||
|
||||
Byte[] buffer = new Byte[256];
|
||||
buffer = System.Text.Encoding.Default.GetBytes(strUserSn);
|
||||
uint retcode;
|
||||
retcode = 加密狗驱动类.验证加密狗SN(buffer);
|
||||
if (retcode != 0)
|
||||
{
|
||||
MessageBox.Show(string.Format("设置UserSN失败 error code: 0x{0:x}", retcode));
|
||||
return;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
retcode = 加密狗API类.VikeyFind(ref count);
|
||||
if (retcode != 0 || count == 0)
|
||||
{
|
||||
MessageBox.Show("未找到加密狗");
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = System.Text.Encoding.Default.GetBytes(strApiKey);
|
||||
retcode = 加密狗驱动类.验证加密狗APIkey(0, buffer);
|
||||
if (retcode != 0)
|
||||
{
|
||||
MessageBox.Show(string.Format("设置api密钥失败 error code: 0x{0:x}", retcode));
|
||||
return;
|
||||
}
|
||||
|
||||
var remaining = 加密狗驱动类.检查剩余时间(0);
|
||||
if (remaining <= TimeSpan.Zero)
|
||||
{
|
||||
MessageBox.Show("加密狗已到期或未能读取时间,请联系供应商。", "加密狗验证失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnStartup(e);
|
||||
}
|
||||
protected override void RegisterRequiredTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
base.RegisterRequiredTypes(containerRegistry);
|
||||
//注册全局变量
|
||||
containerRegistry.RegisterScoped<SystemConfig>();
|
||||
containerRegistry.RegisterScoped<StepRunning>();
|
||||
containerRegistry.RegisterScoped<ScopedContext>();
|
||||
containerRegistry.RegisterScoped<DeviceManager>();
|
||||
containerRegistry.RegisterSingleton<GlobalInfo>();
|
||||
containerRegistry.RegisterSingleton<HardwareDataBroadcaster>();
|
||||
containerRegistry.RegisterSingleton<CANSignalBroadcaster>();
|
||||
//注册AutoMapper
|
||||
var config = new MapperConfiguration(
|
||||
cfg => cfg.AddProfile<AutoMapperProfile>(),
|
||||
NullLoggerFactory.Instance
|
||||
);
|
||||
containerRegistry.RegisterSingleton<IMapper>(() => config.CreateMapper());
|
||||
}
|
||||
protected override void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
//注册弹窗
|
||||
containerRegistry.RegisterDialog<MessageBoxView, MessageBoxViewModel>("MessageBox");
|
||||
// 注册通知管理器
|
||||
INotificationManager NotificationManager = new NotificationManager();
|
||||
containerRegistry.RegisterInstance<INotificationManager>(NotificationManager);
|
||||
// 注册仓储
|
||||
containerRegistry.RegisterScoped(typeof(SqlSugarRepository<>));
|
||||
//注册服务
|
||||
containerRegistry.Register<IMonitorValueService, MonitorValueService>();
|
||||
containerRegistry.Register<ITestReportService, TestReportService>();
|
||||
}
|
||||
//指定模块加载方式(需要手动将模块生成的dll放入Modules文件夹中)
|
||||
protected override IModuleCatalog CreateModuleCatalog()
|
||||
{
|
||||
//指定模块加载方式为从文件夹中以反射发现并加载module(推荐用法)
|
||||
return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
|
||||
}
|
||||
}
|
||||
}
|
||||
10
ACP/AssemblyInfo.cs
Normal file
10
ACP/AssemblyInfo.cs
Normal file
@@ -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)
|
||||
)]
|
||||
BIN
ACP/Images/error.png
Normal file
BIN
ACP/Images/error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
ACP/Images/info.png
Normal file
BIN
ACP/Images/info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
ACP/Images/warning.png
Normal file
BIN
ACP/Images/warning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
64
ACP/Profiles/AutoMapperProfile.cs
Normal file
64
ACP/Profiles/AutoMapperProfile.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using AutoMapper;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using UIShare.UIViewModel;
|
||||
|
||||
namespace ACP.Profiles
|
||||
{
|
||||
/// <summary>
|
||||
/// UIShare.UIViewModel ↔ Model.Models 双向映射配置。
|
||||
/// </summary>
|
||||
public class AutoMapperProfile : Profile
|
||||
{
|
||||
public AutoMapperProfile()
|
||||
{
|
||||
// ===== Parameter =====
|
||||
// ParameterModel.Type(System.Type) ↔ Parameter.TypeName(string)
|
||||
// ParameterModel.Category(enum) ↔ Parameter.Category(string)
|
||||
CreateMap<ParameterVM, Parameter>()
|
||||
.ForMember(dest => dest.TypeName,
|
||||
opt => opt.MapFrom(src => src.Type != null ? src.Type.AssemblyQualifiedName : null))
|
||||
.ForMember(dest => dest.Category,
|
||||
opt => opt.MapFrom(src => src.Category.ToString()));
|
||||
|
||||
CreateMap<Parameter, ParameterVM>()
|
||||
.ForMember(dest => dest.Type,
|
||||
opt => opt.MapFrom(src => string.IsNullOrEmpty(src.TypeName)
|
||||
? typeof(string)
|
||||
: (Type.GetType(src.TypeName) ?? typeof(string))))
|
||||
.ForMember(dest => dest.Category,
|
||||
opt => opt.MapFrom(src => ParseCategory(src.Category)));
|
||||
|
||||
// ===== Step =====
|
||||
CreateMap<StepVM, Step>().ReverseMap();
|
||||
|
||||
// ===== Program =====
|
||||
CreateMap<ProgramVM, Program>().ReverseMap();
|
||||
|
||||
// ===== Method =====
|
||||
CreateMap<MethodVM, Method>().ReverseMap();
|
||||
|
||||
// ===== DeviceInfo =====
|
||||
CreateMap<DeviceInfoVM, DeviceInfo>().ReverseMap();
|
||||
|
||||
// ===== CanMessageShow =====
|
||||
CreateMap<CanMessageShowVM, CanMessageShow>().ReverseMap();
|
||||
|
||||
|
||||
// ===== InstructionNode =====
|
||||
CreateMap<InstructionNodeVM, InstructionNode>().ReverseMap();
|
||||
|
||||
// ===== SubProgramItem =====
|
||||
CreateMap<SubProgramItemVM,SubProgramItem>().ReverseMap();
|
||||
}
|
||||
|
||||
private static ParameterVM.ParameterCategory ParseCategory(string? category)
|
||||
{
|
||||
if (string.IsNullOrEmpty(category))
|
||||
return ParameterVM.ParameterCategory.Temp;
|
||||
return Enum.TryParse<ParameterVM.ParameterCategory>(category, true, out var result)
|
||||
? result
|
||||
: ParameterVM.ParameterCategory.Temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
ACP/Resources/Images/error.png
Normal file
BIN
ACP/Resources/Images/error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
ACP/Resources/Images/info.png
Normal file
BIN
ACP/Resources/Images/info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
ACP/Resources/Images/warning.png
Normal file
BIN
ACP/Resources/Images/warning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
123
ACP/ViewModels/Dialogs/MessageBoxViewModel.cs
Normal file
123
ACP/ViewModels/Dialogs/MessageBoxViewModel.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.ViewModelBase;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace ACP.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 ImageSource _Icon = new System.Windows.Media.Imaging.BitmapImage(new System.Uri("pack://application:,,,/Images/info.png"));
|
||||
public ImageSource 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<OverlayEvent>().Publish(false);
|
||||
}
|
||||
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
_eventAggregator.GetEvent<OverlayEvent>().Publish(true);
|
||||
Title = parameters.GetValue<string>("Title");
|
||||
Message = parameters.GetValue<string>("Message");
|
||||
var iconKey = parameters.GetValue<string>("Icon"); // info / error / warn
|
||||
var bmp = (ImageSource)new System.Windows.Media.Imaging.BitmapImage(new System.Uri(iconKey switch
|
||||
{
|
||||
"info" => "pack://application:,,,/Images/info.png",
|
||||
"error" => "pack://application:,,,/Images/error.png",
|
||||
"warn" => "pack://application:,,,/Images/warning.png",
|
||||
_ => "pack://application:,,,/Images/info.png"
|
||||
}));
|
||||
bmp.Freeze();
|
||||
Icon = bmp;
|
||||
|
||||
|
||||
ShowYes = parameters.GetValue<bool>("ShowYes");
|
||||
ShowNo = parameters.GetValue<bool>("ShowNo");
|
||||
ShowOk = parameters.GetValue<bool>("ShowOk");
|
||||
ShowCancel = parameters.GetValue<bool>("ShowCancel");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
884
ACP/ViewModels/ShellViewModel.cs
Normal file
884
ACP/ViewModels/ShellViewModel.cs
Normal file
@@ -0,0 +1,884 @@
|
||||
|
||||
using DeviceCommand.Devices;
|
||||
using Logger;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Microsoft.Win32;
|
||||
using Model.Models;
|
||||
using Notifications.Wpf.Core;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
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;
|
||||
using SqlSugar;
|
||||
|
||||
namespace ACP.ViewModels
|
||||
{
|
||||
public class ShellViewModel : BindableBase
|
||||
{
|
||||
#region 私有字段
|
||||
private string _Title = "";
|
||||
private bool _IsLeftDrawerOpen;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Task> _executionTasks = new();
|
||||
private readonly ConcurrentDictionary<string, Task> _errorExecutionTasks = new();
|
||||
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IRegionManager _regionManager;
|
||||
private readonly IDialogService _dialogService;
|
||||
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);
|
||||
}
|
||||
|
||||
public TimeSpan RunningTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CurrentContext == null) return TimeSpan.Zero;
|
||||
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;
|
||||
private SystemConfig? CurrentConfig =>
|
||||
_globalInfo.ConfigDic.TryGetValue(_globalInfo.CurrentScope, out var config) ? config : 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; }
|
||||
public ICommand ShowDialogManagerViewCommand { get; set; }
|
||||
public ICommand SelectCANSignalMonitorCommand { get; set; }
|
||||
public ICommand SelectCANMessageCommand { get; set; }
|
||||
public ICommand MonitorValueSettingCommand { get; set; }
|
||||
public ICommand GetFileStringCommand { get; set; }
|
||||
public ICommand SilenceBuzzerCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public ShellViewModel(IContainerProvider containerProvider)
|
||||
{
|
||||
_containerProvider = containerProvider;
|
||||
_dialogService= containerProvider.Resolve<IDialogService>();
|
||||
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||
_eventAggregator = containerProvider.Resolve<IEventAggregator>();
|
||||
_regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
_notificationManager = containerProvider.Resolve<INotificationManager>();
|
||||
_moduleManager = containerProvider.Resolve<IModuleManager>();
|
||||
LeftDrawerOpenCommand = new DelegateCommand(LeftDrawerOpen);
|
||||
ShowDialogManagerViewCommand = new DelegateCommand(ShowDialogManagerView);
|
||||
MinimizeCommand = new DelegateCommand<Window>(MinimizeWindow);
|
||||
MaximizeCommand = new DelegateCommand<Window>(MaximizeWindow);
|
||||
CloseCommand = new DelegateCommand<Window>(CloseWindow);
|
||||
NavigateCommand = new DelegateCommand<string>(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<string>(Open);
|
||||
SaveAsCommand = new DelegateCommand(SaveAs);
|
||||
SaveCommand = new DelegateCommand(Save);
|
||||
SetDefaultCommand = new DelegateCommand(SetDefault);
|
||||
SelectCANSignalMonitorCommand = new DelegateCommand(SelectCANSignalMonitor);
|
||||
SelectCANMessageCommand = new DelegateCommand(SelectCANMessage);
|
||||
MonitorValueSettingCommand = new DelegateCommand(MonitorValueSetting);
|
||||
GetFileStringCommand = new DelegateCommand(GetFileString);
|
||||
SilenceBuzzerCommand = new AsyncDelegateCommand(SilenceBuzzer);
|
||||
|
||||
_globalInfo.ContextDic.Add("default", new ScopedContext());
|
||||
|
||||
_eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(() =>
|
||||
{
|
||||
Application.Current.MainWindow.Show();
|
||||
_regionManager.RequestNavigate("ShellViewManager", "MainView");
|
||||
});
|
||||
|
||||
_eventAggregator.GetEvent<RunSingalCompletedEvent>().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 Task SilenceBuzzer()
|
||||
{
|
||||
_eventAggregator.GetEvent<SilenceBuzzerEvent>().Publish(_globalInfo.CurrentScope);
|
||||
|
||||
}
|
||||
private void GetFileString()
|
||||
{
|
||||
var openFileDialog = new OpenFileDialog
|
||||
{
|
||||
Title = "选择文件",
|
||||
Filter = "所有文件 (*.*)|*.*",
|
||||
};
|
||||
if (openFileDialog.ShowDialog() == true)
|
||||
{
|
||||
string filePath = openFileDialog.FileName;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Clipboard.SetDataObject(filePath);
|
||||
});
|
||||
}
|
||||
}
|
||||
private void MonitorValueSetting()
|
||||
{
|
||||
if ( _globalInfo.CurrentScope == "default")
|
||||
{
|
||||
LoggerHelper.Warn("当前作用域未配置 CAN 设备,无法打开报文选择窗口。");
|
||||
return;
|
||||
}
|
||||
_dialogService.ShowDialog("ValueLimitView", new DialogParameters
|
||||
{
|
||||
{ "Scope", _globalInfo.ScopeDic[_globalInfo.CurrentScope] }
|
||||
}, _ => { });
|
||||
}
|
||||
|
||||
private void SelectCANSignalMonitor()
|
||||
{
|
||||
var canfd = CurrentConfig?.CANFD;
|
||||
if (canfd == null || _globalInfo.CurrentScope == "default")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dialogService.ShowDialog("CollectCANSignalSettingView", new DialogParameters
|
||||
{
|
||||
{ "SystemConfig", CurrentConfig }
|
||||
}, _ => { });
|
||||
}
|
||||
|
||||
private void SelectCANMessage()
|
||||
{
|
||||
var canfd = CurrentConfig?.CANFD;
|
||||
if (canfd == null||_globalInfo.CurrentScope=="default")
|
||||
{
|
||||
LoggerHelper.Warn("当前作用域未配置 CAN 设备,无法打开报文选择窗口。");
|
||||
return;
|
||||
}
|
||||
|
||||
_dialogService.ShowDialog("SelectCanMessageView", new DialogParameters
|
||||
{
|
||||
{ "CANFD", canfd }
|
||||
}, _ => { });
|
||||
}
|
||||
|
||||
private void ShowDialogManagerView()
|
||||
{
|
||||
_eventAggregator.GetEvent<CancelMinimizeEvent>().Publish();
|
||||
}
|
||||
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(runningScope, $"{_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, $"工位 [{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(runningScope, $"{_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(runningScope, $"{_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, $"工位 [{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(runningScope, $"{_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(runningScope, $"{_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, $"工位 [{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()
|
||||
{
|
||||
if (CurrentConfig == null) return;
|
||||
// 💡 1. 抓取触发瞬间的 Scope 与 Context 快照
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||
|
||||
if (targetContext.CurrentFilePath != null)
|
||||
{
|
||||
CurrentConfig.DefaultProgramFilePath = targetContext.CurrentFilePath;
|
||||
ConfigService.Save(CurrentConfig);
|
||||
LoggerHelper.SuccessWithNotify(runningScope, $"工位 [{runningScope}] 已成功将当前程序设为默认启动程序");
|
||||
}
|
||||
}
|
||||
|
||||
private void New()
|
||||
{
|
||||
// 💡 1. 抓取快照
|
||||
string runningScope = _globalInfo.CurrentScope;
|
||||
ScopedContext? targetContext = CurrentContext;
|
||||
|
||||
if (runningScope == "default" || string.IsNullOrEmpty(runningScope) || targetContext == null) return;
|
||||
CurrentConfig.CurrentACPFile = "新建ACP文件";
|
||||
// 💡 2. 严格对快照隔离的 Context 数据进行清理,不影响其他工位
|
||||
targetContext.CurrentFilePath = null;
|
||||
targetContext.Program.Parameters.Clear();
|
||||
targetContext.Program.StepCollection.Clear();
|
||||
targetContext.Program.ErrorStepCollection.Clear();
|
||||
foreach (var item in CurrentConfig.ParameterList)
|
||||
{
|
||||
var param = CurrentConfig.SharedParameterList.FirstOrDefault(x => x.ParameterName == item.Name);
|
||||
if (param != null)
|
||||
{
|
||||
item.Value = param.Value;
|
||||
}
|
||||
targetContext.Program.Parameters.Add(item);
|
||||
}
|
||||
LoggerHelper.InfoWithNotify(runningScope, $"工位 [{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 = "ACP程序文件|*.ACP|所有文件|*.*",
|
||||
Title = $"工位 [{runningScope}] 打开程序",
|
||||
};
|
||||
|
||||
if (openFileDialog.ShowDialog() != true)
|
||||
return; // 用户取消选择
|
||||
|
||||
filePath = openFileDialog.FileName;
|
||||
}
|
||||
|
||||
// 确认文件存在
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(runningScope, $"文件不存在: {filePath}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取 JSON 文件
|
||||
string json = File.ReadAllText(filePath);
|
||||
|
||||
// 反序列化为 ProgramVM
|
||||
var program = Newtonsoft.Json.JsonConvert.DeserializeObject<ProgramVM>(json);
|
||||
|
||||
if (program == null)
|
||||
{
|
||||
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
|
||||
return;
|
||||
}
|
||||
CurrentConfig.CurrentACPFile = filePath;
|
||||
// 💡 2. 严格赋值给快照锁定下的当前工位上下文,实现数据完全隔离
|
||||
targetContext.Program.Parameters = program.Parameters;
|
||||
targetContext.Program.StepCollection = program.StepCollection;
|
||||
targetContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
||||
targetContext.CurrentFilePath = filePath;
|
||||
|
||||
foreach (var item in CurrentConfig.SharedParameterList)
|
||||
{
|
||||
var parameter = targetContext?.Program?.Parameters?.FirstOrDefault(x => x.Name == item.ParameterName);
|
||||
|
||||
if (parameter != null)
|
||||
{
|
||||
parameter.Value = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
LoggerHelper.SuccessWithNotify(runningScope, $"工位 [{runningScope}] 成功打开文件: {filePath}");
|
||||
|
||||
// 💡 3. 安全调用异步复位,确保重置的是对应工位的数据
|
||||
// 注意:由于在 OnRestoration 内部第一行也做了快照拦截,
|
||||
// 如果用户此时正好在看这个工位,它会自动完美执行
|
||||
await OnRestoration();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{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:\ACP\子程序";
|
||||
if (!Directory.Exists(defaultPath))
|
||||
Directory.CreateDirectory(defaultPath);
|
||||
|
||||
var saveFileDialog = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Filter = "ACP程序文件|*.ACP|所有文件|*.*",
|
||||
Title = $"工位 [{runningScope}] 程序另存为",
|
||||
FileName = "NewProgram.ACP",
|
||||
InitialDirectory = defaultPath
|
||||
};
|
||||
|
||||
if (saveFileDialog.ShowDialog() == true)
|
||||
{
|
||||
// 💡 2. 将新路径安全写入快照工位
|
||||
CurrentConfig.CurrentACPFile = saveFileDialog.FileName;
|
||||
targetContext.CurrentFilePath = saveFileDialog.FileName;
|
||||
|
||||
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
||||
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
||||
|
||||
LoggerHelper.InfoWithNotify(runningScope, $"{_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(_globalInfo.CurrentScope,$"工位 [{runningScope}] 程序保存成功!");
|
||||
}
|
||||
|
||||
// 💡 辅助方法:建议将你的通用序列化落盘代码调整为接收 ProgramVM 参数,提高复用性
|
||||
private void SaveProgramToFile(string filePath, ProgramVM ProgramVM)
|
||||
{
|
||||
try
|
||||
{
|
||||
string json = Newtonsoft.Json.JsonConvert.SerializeObject(ProgramVM, Newtonsoft.Json.Formatting.Indented);
|
||||
File.WriteAllText(filePath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,$"写入程序文件失败: {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<ExpandViewEvent>().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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var parameters = new NavigationParameters { { "Name", targetRegionName } };
|
||||
_regionManager.RequestNavigate(targetRegionName, "ProtocolStartView", parameters);
|
||||
_eventAggregator.GetEvent<ExpandViewEvent>().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 "台架移动界面":
|
||||
if (_globalInfo.CurrentScope != "default") return;
|
||||
_regionManager.RequestNavigate("ShellViewManager", "BenchMovementView");
|
||||
break;
|
||||
case "曲线回读界面":
|
||||
if (_globalInfo.CurrentScope != "default") return;
|
||||
_regionManager.RequestNavigate("ShellViewManager", "CurveRecallView");
|
||||
break;
|
||||
case "测试报告导出界面":
|
||||
if (_globalInfo.CurrentScope != "default") return;
|
||||
_regionManager.RequestNavigate("ShellViewManager", "ExportView");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
87
ACP/Views/Dialogs/MessageBoxView.xaml
Normal file
87
ACP/Views/Dialogs/MessageBoxView.xaml
Normal file
@@ -0,0 +1,87 @@
|
||||
<UserControl x:Class="ACP.Views.Dialogs.MessageBoxView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:ACP.Views.Dialogs"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
Background="Transparent"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Height="250"
|
||||
Width="300">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<Border CornerRadius="20"
|
||||
Background="white"
|
||||
MouseLeftButtonDown="Border_MouseLeftButtonDown">
|
||||
<Grid Background="Transparent">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Margin="15 5 5 5"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Title}"
|
||||
Foreground="#333" />
|
||||
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
Grid.Row="1">
|
||||
<!-- Icon -->
|
||||
<Image
|
||||
Width="100"
|
||||
Height="100"
|
||||
VerticalAlignment="Top"
|
||||
Margin="5 15 0 0"
|
||||
Source="{Binding Icon}" />
|
||||
<!-- Message -->
|
||||
<TextBlock
|
||||
FontSize="20"
|
||||
Margin="15 0 0 0"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding Message}" />
|
||||
</StackPanel>
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="2"
|
||||
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
|
||||
<Button Content="Yes"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowYes, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding YesCommand}" />
|
||||
|
||||
<Button Content="No"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowNo, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding NoCommand}" />
|
||||
|
||||
<Button Content="OK"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowOk, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding OkCommand}" />
|
||||
|
||||
<Button Content="Cancel"
|
||||
Width="80"
|
||||
Margin="10 10"
|
||||
Visibility="{Binding ShowCancel, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Command="{Binding CancelCommand}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
36
ACP/Views/Dialogs/MessageBoxView.xaml.cs
Normal file
36
ACP/Views/Dialogs/MessageBoxView.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace ACP.Views.Dialogs
|
||||
{
|
||||
/// <summary>
|
||||
/// MessageBoxView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
ACP/Views/LoginModuleView.xaml
Normal file
21
ACP/Views/LoginModuleView.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<mah:MetroWindow xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:helpers="clr-namespace:UIShare.Helpers;assembly=UIShare"
|
||||
x:Class="ACP.Views.LoginModuleView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:local="clr-namespace:ACP.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="ACP"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Height="315"
|
||||
Width="420"
|
||||
ResizeMode="NoResize">
|
||||
<Grid>
|
||||
<ContentControl prism:RegionManager.RegionName="LoginRegion" />
|
||||
</Grid>
|
||||
</mah:MetroWindow>
|
||||
38
ACP/Views/LoginModuleView.xaml.cs
Normal file
38
ACP/Views/LoginModuleView.xaml.cs
Normal file
@@ -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 ACP.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Login.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LoginModuleView : MetroWindow
|
||||
{
|
||||
public LoginModuleView(IEventAggregator eventAggregator)
|
||||
{
|
||||
InitializeComponent();
|
||||
//订阅登录成功事件
|
||||
eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(() =>
|
||||
{
|
||||
this.Close();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
400
ACP/Views/ShellView.xaml
Normal file
400
ACP/Views/ShellView.xaml
Normal file
@@ -0,0 +1,400 @@
|
||||
<Window x:Class="ACP.Views.ShellView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converter="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Topmost="false"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
WindowStyle="None"
|
||||
WindowState="Maximized"
|
||||
Title="ShellView"
|
||||
d:DesignHeight="1080"
|
||||
d:DesignWidth="1920">
|
||||
<WindowChrome.WindowChrome>
|
||||
<WindowChrome GlassFrameThickness="-1" />
|
||||
</WindowChrome.WindowChrome>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<Window.Resources>
|
||||
<converter:InverseBooleanConverter x:Key="InverseBooleanConverter" />
|
||||
<converter:TimeSpanToStringConverter x:Key="TimeSpanConverter" />
|
||||
</Window.Resources>
|
||||
<materialDesign:DrawerHost x:Name="MainDrawerHost"
|
||||
IsLeftDrawerOpen="{Binding IsLeftDrawerOpen, Mode=TwoWay}">
|
||||
|
||||
<!-- ✅ 左侧抽屉内容 -->
|
||||
<materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<StackPanel Width="220"
|
||||
Background="{DynamicResource MaterialDesignPaper}">
|
||||
<TextBlock Text="导航菜单"
|
||||
FontSize="18"
|
||||
Margin="16"
|
||||
Foreground="{DynamicResource PrimaryHueMidBrush}" />
|
||||
<Separator Margin="0,0,0,8" />
|
||||
<Button Content="主界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="监控界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="记录界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="设置界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
|
||||
<Button Content="台架移动界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="曲线回读界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
<Button Content="测试报告导出界面"
|
||||
Command="{Binding NavigateCommand}"
|
||||
CommandParameter="{Binding Content, RelativeSource={RelativeSource Self}}"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Margin="8" />
|
||||
</StackPanel>
|
||||
</materialDesign:DrawerHost.LeftDrawerContent>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<!-- 顶部工具栏 -->
|
||||
<materialDesign:ColorZone Mode="PrimaryMid"
|
||||
MouseLeftButtonDown="ColorZone_MouseLeftButtonDown">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Menu Grid.Column="0"
|
||||
Background="Transparent"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center">
|
||||
<!-- 文件菜单 -->
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="菜单"
|
||||
Foreground="White"
|
||||
Command="{Binding DataContext.LeftDrawerOpenCommand, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Menu"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
|
||||
<!-- 工具菜单 -->
|
||||
<MenuItem Header="工具"
|
||||
FontSize="13"
|
||||
Height="50"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Tools"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
|
||||
<!-- 新建 -->
|
||||
<MenuItem Header="新建"
|
||||
Foreground="Black"
|
||||
Command="{Binding NewCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="FilePlus"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 打开 -->
|
||||
<MenuItem Header="打开"
|
||||
Foreground="Black"
|
||||
Command="{Binding OpenCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="FolderOpen"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 保存 -->
|
||||
<MenuItem Header="保存"
|
||||
Foreground="Black"
|
||||
Command="{Binding SaveCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ContentSave"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 另存为 -->
|
||||
<MenuItem Header="另存为"
|
||||
Foreground="Black"
|
||||
Command="{Binding SaveAsCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ContentSaveEdit"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 设置默认程序 -->
|
||||
<MenuItem Header="设置默认程序"
|
||||
Foreground="Black"
|
||||
Command="{Binding SetDefaultCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Cog"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<!-- CAN信号监控 -->
|
||||
<MenuItem Header="CAN信号监控"
|
||||
Foreground="Black"
|
||||
Command="{Binding SelectCANSignalMonitorCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Message"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<!-- 监控设置 -->
|
||||
<MenuItem Header="监控设置"
|
||||
Foreground="Black"
|
||||
Command="{Binding MonitorValueSettingCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Monitor"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="CAN报文选择"
|
||||
Foreground="Black"
|
||||
Command="{Binding SelectCANMessageCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Message"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 获得文件路径字符串 -->
|
||||
<MenuItem Header="获得文件路径字符串"
|
||||
Foreground="Black"
|
||||
Command="{Binding GetFileStringCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="File"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!-- 弹窗管理器 -->
|
||||
<MenuItem Header="弹窗管理器"
|
||||
FontSize="14"
|
||||
Height="50"
|
||||
Foreground="Black"
|
||||
Command="{Binding ShowDialogManagerViewCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="DialogueOutline"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="蜂鸣器消音"
|
||||
FontSize="14"
|
||||
Height="50"
|
||||
Foreground="Black"
|
||||
Command="{Binding SilenceBuzzerCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="BellOff"
|
||||
Foreground="Black" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem Header="切换九宫格"
|
||||
FontSize="13"
|
||||
Height="50"
|
||||
Foreground="White"
|
||||
Command="{Binding RefreshCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Refresh"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="销毁作用域"
|
||||
FontSize="13"
|
||||
Height="50"
|
||||
Foreground="White"
|
||||
Command="{Binding DestroyCommand}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Death"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="运行"
|
||||
IsEnabled="{Binding IsTerminate, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Command="{Binding RunningCommand}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="{Binding RunIcon}"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="单步执行"
|
||||
IsEnabled="{Binding IsTerminate, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Command="{Binding RunSingleCommand}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="ArrowRight"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="异常流程"
|
||||
Command="{Binding RunAbnormalStepsCommand}"
|
||||
IsEnabled="{Binding IsTerminate, Converter={StaticResource InverseBooleanConverter}}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="AlertCircle"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Header="复位"
|
||||
Command="{Binding RestorationCommand}"
|
||||
Foreground="White">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Restart"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem FontSize="14"
|
||||
Height="50"
|
||||
Foreground="White">
|
||||
<MenuItem.Header>
|
||||
<TextBlock>
|
||||
<Run Text="运行时间:" />
|
||||
<TextBlock Text="{Binding RunningTime, Converter={StaticResource TimeSpanConverter}}" />
|
||||
</TextBlock>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Clock"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Menu Grid.Column="2"
|
||||
Margin="0 0 20 0">
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="最小化"
|
||||
Foreground="White"
|
||||
Command="{Binding MinimizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Minimize"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
|
||||
<!--<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="最大化"
|
||||
Foreground="White"
|
||||
Command="{Binding MaximizeCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Maximize"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>-->
|
||||
|
||||
<MenuItem FontSize="13"
|
||||
Height="50"
|
||||
Header="关闭"
|
||||
Foreground="White"
|
||||
Command="{Binding CloseCommand}"
|
||||
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
<MenuItem.Icon>
|
||||
<materialDesign:PackIcon Kind="Close"
|
||||
Foreground="White" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
</Grid>
|
||||
<!-- 左侧菜单 -->
|
||||
|
||||
|
||||
|
||||
</materialDesign:ColorZone>
|
||||
|
||||
<materialDesign:DialogHost Grid.Row="1"
|
||||
x:Name="DialogHost"
|
||||
DialogBackground="Transparent"
|
||||
Background="Transparent"
|
||||
Identifier="Root">
|
||||
<!-- 主内容区 -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="31*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ContentControl prism:RegionManager.RegionName="ShellViewManager" Grid.ColumnSpan="2" />
|
||||
<Border x:Name="Overlay"
|
||||
Background="#40000000"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1" Grid.ColumnSpan="2">
|
||||
<StackPanel Width="150"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 100">
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Border x:Name="Waitinglay"
|
||||
Background="#40000000"
|
||||
Visibility="Collapsed"
|
||||
Panel.ZIndex="1" Grid.ColumnSpan="2">
|
||||
<StackPanel Width="150"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 100">
|
||||
<ProgressBar Width="80"
|
||||
Height="80"
|
||||
Margin="20"
|
||||
IsIndeterminate="True"
|
||||
Style="{StaticResource MaterialDesignCircularProgressBar}" />
|
||||
<TextBlock FontSize="30"
|
||||
Text="加载中......"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</materialDesign:DialogHost>
|
||||
</Grid>
|
||||
</materialDesign:DrawerHost>
|
||||
|
||||
</Window>
|
||||
51
ACP/Views/ShellView.xaml.cs
Normal file
51
ACP/Views/ShellView.xaml.cs
Normal file
@@ -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 ACP.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// ShellView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ShellView : Window
|
||||
{
|
||||
public ShellView(IEventAggregator eventAggregator)
|
||||
{
|
||||
InitializeComponent();
|
||||
//注册灰度遮罩层
|
||||
eventAggregator.GetEvent<OverlayEvent>().Subscribe(ShowOverlay);
|
||||
eventAggregator.GetEvent<WaitingEvent>().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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
BenchMovementModule/BenchMovementModule.cs
Normal file
19
BenchMovementModule/BenchMovementModule.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Reflection;
|
||||
using BenchMovementModule.Views;
|
||||
|
||||
namespace BenchMovementModule
|
||||
{
|
||||
public class BenchMovementModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
regionManager.RegisterViewWithRegion("ShellViewManager", typeof(BenchMovementView));
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<BenchMovementView>("BenchMovementView");
|
||||
}
|
||||
}
|
||||
}
|
||||
14
BenchMovementModule/BenchMovementModule.csproj
Normal file
14
BenchMovementModule/BenchMovementModule.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
164
BenchMovementModule/HardwareDrive/GantryControlBase.cs
Normal file
164
BenchMovementModule/HardwareDrive/GantryControlBase.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BenchMovementModule.HardwareDrive
|
||||
{
|
||||
public abstract class GantryControlBase
|
||||
{
|
||||
protected readonly IModbusDevice _device;
|
||||
protected readonly byte _slaveAddress;
|
||||
|
||||
// 默认 PLC Modbus 映射地址
|
||||
protected const ushort ADDR_HM_X_ENABLE = 0xC100; // HM0: X轴使能
|
||||
protected const ushort ADDR_HM_Y_ENABLE = 0xC101; // HM1: Y轴使能
|
||||
protected const ushort ADDR_HM_Z_ENABLE = 0xC102; // HM2: Z轴使能
|
||||
|
||||
protected const ushort ADDR_M_X_START = 30; // M30: X轴指定位置启动
|
||||
protected const ushort ADDR_M_Y_START = 31; // M31: Y轴指定位置启动
|
||||
protected const ushort ADDR_M_Z_START = 35; // M35: Z轴指定位置启动
|
||||
|
||||
protected const ushort ADDR_M_ALL_STOP = 40; // M40: 全停
|
||||
protected const ushort ADDR_M_X_STOP = 41; // M41: X轴停止
|
||||
protected const ushort ADDR_M_Y_STOP = 42; // M42: Y轴停止
|
||||
protected const ushort ADDR_M_Z_STOP = 43; // M43: Z轴停止
|
||||
|
||||
protected const ushort ADDR_M_HOME = 100; // M100: 回原点触发
|
||||
|
||||
protected const ushort ADDR_HD_X_SPEED = 0xA166; // HD230: X轴速度 (32位)
|
||||
protected const ushort ADDR_HD_Y_SPEED = 0xA17A; // HD250: Y轴速度 (32位)
|
||||
protected const ushort ADDR_HD_Z_SPEED = 0xA170; // HD240: Z轴速度 (32位)
|
||||
|
||||
protected const ushort ADDR_HD_X_TARGET = 0xA10C; // HD140: X轴指定位置 (32位)
|
||||
protected const ushort ADDR_HD_Y_TARGET = 0xA120; // HD160: Y轴指定位置 (32位)
|
||||
protected const ushort ADDR_HD_Z_TARGET = 0xA116; // HD150: Z轴指定位置 (32位)
|
||||
|
||||
public bool IsConnected => _device?.IsConnected ?? false;
|
||||
|
||||
protected GantryControlBase(IModbusDevice device, byte slaveAddress = 1)
|
||||
{
|
||||
_device = device ?? throw new ArgumentNullException(nameof(device));
|
||||
_slaveAddress = slaveAddress;
|
||||
}
|
||||
|
||||
public async Task<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
return await _device.ConnectAsync(ct);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_device.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轴使能控制
|
||||
/// </summary>
|
||||
public async Task SetAxisEnableAsync(int axis, bool enable, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HM_X_ENABLE, // X
|
||||
2 => ADDR_HM_Y_ENABLE, // Y
|
||||
3 => ADDR_HM_Z_ENABLE, // Z
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)")
|
||||
};
|
||||
|
||||
await _device.WriteSingleCoilAsync(_slaveAddress, address, enable, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 左右移动(X轴)
|
||||
/// </summary>
|
||||
/// <param name="position">目标绝对位置(脉冲数/单位值)</param>
|
||||
/// <param name="speed">运行速度</param>
|
||||
public async Task MoveLeftRightAsync(int position, int speed, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 写入速度与目标位置(32位数据,写入连续的2个保持寄存器)
|
||||
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_SPEED, Int32ToUshorts(speed), ct);
|
||||
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_TARGET, Int32ToUshorts(position), ct);
|
||||
|
||||
// 2. 边缘触发启动信号 M30
|
||||
await TriggerCoilAsync(ADDR_M_X_START, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上下移动(Z轴)
|
||||
/// </summary>
|
||||
/// <param name="position">目标绝对位置</param>
|
||||
/// <param name="speed">运行速度</param>
|
||||
public async Task MoveUpDownAsync(int position, int speed, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 写入速度与目标位置(32位数据)
|
||||
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_SPEED, Int32ToUshorts(speed), ct);
|
||||
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_TARGET, Int32ToUshorts(position), ct);
|
||||
|
||||
// 2. 边缘触发启动信号 M35
|
||||
await TriggerCoilAsync(ADDR_M_Z_START, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 前后移动(Y轴 - 预留拓展)
|
||||
/// </summary>
|
||||
public async Task MoveFrontBackAsync(int position, int speed, CancellationToken ct = default)
|
||||
{
|
||||
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_SPEED, Int32ToUshorts(speed), ct);
|
||||
await _device.WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_TARGET, Int32ToUshorts(position), ct);
|
||||
await TriggerCoilAsync(ADDR_M_Y_START, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局回零
|
||||
/// </summary>
|
||||
public async Task HomeAsync(CancellationToken ct = default)
|
||||
{
|
||||
await TriggerCoilAsync(ADDR_M_HOME, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轴停止
|
||||
/// </summary>
|
||||
/// <param name="axis">1:X轴, 2:Y轴, 3:Z轴, 0:全停</param>
|
||||
public async Task StopAsync(int axis = 0, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
0 => ADDR_M_ALL_STOP,
|
||||
1 => ADDR_M_X_STOP,
|
||||
2 => ADDR_M_Y_STOP,
|
||||
3 => ADDR_M_Z_STOP,
|
||||
_ => ADDR_M_ALL_STOP
|
||||
};
|
||||
|
||||
await TriggerCoilAsync(address, ct);
|
||||
}
|
||||
|
||||
#region 内部工具方法
|
||||
|
||||
/// <summary>
|
||||
/// 触发一个点动信号(置 1 后,延时 100ms 自动置 0)
|
||||
/// </summary>
|
||||
protected async Task TriggerCoilAsync(ushort address, CancellationToken ct = default)
|
||||
{
|
||||
await _device.WriteSingleCoilAsync(_slaveAddress, address, true, ct);
|
||||
await Task.Delay(100, ct); // 给予 PLC 扫描周期足够的响应时间
|
||||
await _device.WriteSingleCoilAsync(_slaveAddress, address, false, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 32 位整型数据转换为 Modbus 的 2 个 16 位无符号整数(高低字转换)
|
||||
/// </summary>
|
||||
protected ushort[] Int32ToUshorts(int value)
|
||||
{
|
||||
// 本处采用低字在前(CDAB),如果PLC高字在前(ABCD),可将返回数组顺序颠倒
|
||||
ushort lowWord = (ushort)(value & 0xFFFF);
|
||||
ushort highWord = (ushort)((value >> 16) & 0xFFFF);
|
||||
return new ushort[] { lowWord, highWord };
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
28
BenchMovementModule/HardwareDrive/GantryControlRtu.cs
Normal file
28
BenchMovementModule/HardwareDrive/GantryControlRtu.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using DeviceCommand.Base;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BenchMovementModule.HardwareDrive
|
||||
{
|
||||
public class GantryControlRtu : GantryControlBase
|
||||
{
|
||||
// 暴露出底层的 ModbusRtu 实例以便修改串口配置
|
||||
public ModbusRtu RtuDevice => (ModbusRtu)_device;
|
||||
|
||||
public GantryControlRtu(string portName, int baudRate = 9600, byte slaveAddress = 1)
|
||||
: base(new ModbusRtu(), slaveAddress)
|
||||
{
|
||||
RtuDevice.ConfigureDevice(portName, baudRate);
|
||||
}
|
||||
|
||||
public GantryControlRtu(string portName, int baudRate, int dataBits, StopBits stopBits, Parity parity, byte slaveAddress = 1)
|
||||
: base(new ModbusRtu(), slaveAddress)
|
||||
{
|
||||
RtuDevice.ConfigureDevice(portName, baudRate, dataBits, stopBits, parity);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
BenchMovementModule/HardwareDrive/GantryControlTcp.cs
Normal file
27
BenchMovementModule/HardwareDrive/GantryControlTcp.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BenchMovementModule.HardwareDrive
|
||||
{
|
||||
public class GantryControlTcp : GantryControlBase
|
||||
{
|
||||
// 暴露出底层的 ModbusTcp 实例以便修改网络配置
|
||||
public ModbusTcp TcpDevice => (ModbusTcp)_device;
|
||||
|
||||
public GantryControlTcp(TcpConfig config, byte slaveAddress = 1)
|
||||
: base(new ModbusTcp(config), slaveAddress)
|
||||
{
|
||||
}
|
||||
|
||||
public GantryControlTcp(string ipAddress, int port = 502, byte slaveAddress = 1)
|
||||
: base(new ModbusTcp(), slaveAddress)
|
||||
{
|
||||
TcpDevice.ConfigureDevice(ipAddress, port);
|
||||
}
|
||||
}
|
||||
}
|
||||
522
BenchMovementModule/ViewModels/BenchMovementViewModel.cs
Normal file
522
BenchMovementModule/ViewModels/BenchMovementViewModel.cs
Normal file
@@ -0,0 +1,522 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using DeviceCommand.Devices;
|
||||
using Logger;
|
||||
using Prism.Ioc;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace BenchMovementModule.ViewModels
|
||||
{
|
||||
public class BenchMovementViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
#region 属性
|
||||
|
||||
public bool KeepAlive => true;
|
||||
|
||||
// ===== GantryControlTcp 实例(从 DeviceManager 获取)=====
|
||||
private GantryControlTcp? _gantry;
|
||||
private DeviceManager? _deviceManager;
|
||||
private readonly GlobalInfo _globalInfo;
|
||||
|
||||
// ===== 连接状态 =====
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private string _connectionStatus = "未连接";
|
||||
public string ConnectionStatus
|
||||
{
|
||||
get => _connectionStatus;
|
||||
set => SetProperty(ref _connectionStatus, value);
|
||||
}
|
||||
|
||||
// ===== 6轴绝对位置显示(相对原点的实时位置,只读)=====
|
||||
private int _absPos1;
|
||||
public int AbsPos1 { get => _absPos1; set => SetProperty(ref _absPos1, value); }
|
||||
|
||||
private int _absPos2;
|
||||
public int AbsPos2 { get => _absPos2; set => SetProperty(ref _absPos2, value); }
|
||||
|
||||
private int _absPos3;
|
||||
public int AbsPos3 { get => _absPos3; set => SetProperty(ref _absPos3, value); }
|
||||
|
||||
private int _absPos4;
|
||||
public int AbsPos4 { get => _absPos4; set => SetProperty(ref _absPos4, value); }
|
||||
|
||||
private int _absPos5;
|
||||
public int AbsPos5 { get => _absPos5; set => SetProperty(ref _absPos5, value); }
|
||||
|
||||
private int _absPos6;
|
||||
public int AbsPos6 { get => _absPos6; set => SetProperty(ref _absPos6, value); }
|
||||
|
||||
// ===== 寸动目标位置 =====
|
||||
private int _targetX;
|
||||
public int TargetX { get => _targetX; set => SetProperty(ref _targetX, value); }
|
||||
|
||||
private int _targetY;
|
||||
public int TargetY { get => _targetY; set => SetProperty(ref _targetY, value); }
|
||||
|
||||
private int _targetZ;
|
||||
public int TargetZ { get => _targetZ; set => SetProperty(ref _targetZ, value); }
|
||||
|
||||
// ===== 扫描参数 =====
|
||||
private int _scanStepX = 1000;
|
||||
public int ScanStepX { get => _scanStepX; set => SetProperty(ref _scanStepX, value); }
|
||||
|
||||
private int _scanStartX;
|
||||
public int ScanStartX { get => _scanStartX; set => SetProperty(ref _scanStartX, value); }
|
||||
|
||||
private int _scanEndX = 50000;
|
||||
public int ScanEndX { get => _scanEndX; set => SetProperty(ref _scanEndX, value); }
|
||||
|
||||
private int _scanStepY = 1000;
|
||||
public int ScanStepY { get => _scanStepY; set => SetProperty(ref _scanStepY, value); }
|
||||
|
||||
private int _scanStartY;
|
||||
public int ScanStartY { get => _scanStartY; set => SetProperty(ref _scanStartY, value); }
|
||||
|
||||
private int _scanEndY = 50000;
|
||||
public int ScanEndY { get => _scanEndY; set => SetProperty(ref _scanEndY, value); }
|
||||
|
||||
private int _scanStepZ = 100;
|
||||
public int ScanStepZ { get => _scanStepZ; set => SetProperty(ref _scanStepZ, value); }
|
||||
|
||||
private int _scanStartZ;
|
||||
public int ScanStartZ { get => _scanStartZ; set => SetProperty(ref _scanStartZ, value); }
|
||||
|
||||
private int _scanEndZ = 20000;
|
||||
public int ScanEndZ { get => _scanEndZ; set => SetProperty(ref _scanEndZ, value); }
|
||||
|
||||
// ===== 速度设定(脉冲)=====
|
||||
private int _speedX = 5000;
|
||||
public int SpeedX { get => _speedX; set => SetProperty(ref _speedX, value); }
|
||||
|
||||
private int _speedY = 5000;
|
||||
public int SpeedY { get => _speedY; set => SetProperty(ref _speedY, value); }
|
||||
|
||||
private int _speedZ = 2000;
|
||||
public int SpeedZ { get => _speedZ; set => SetProperty(ref _speedZ, value); }
|
||||
|
||||
private int _homeSpeed = 3000;
|
||||
public int HomeSpeed { get => _homeSpeed; set => SetProperty(ref _homeSpeed, value); }
|
||||
|
||||
// ===== 零点设定 =====
|
||||
private int _homeX;
|
||||
public int HomeX { get => _homeX; set => SetProperty(ref _homeX, value); }
|
||||
|
||||
private int _homeY;
|
||||
public int HomeY { get => _homeY; set => SetProperty(ref _homeY, value); }
|
||||
|
||||
private int _homeZ;
|
||||
public int HomeZ { get => _homeZ; set => SetProperty(ref _homeZ, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand ConnectCommand { get; }
|
||||
public ICommand DisconnectCommand { get; }
|
||||
public ICommand MoveXCommand { get; }
|
||||
public ICommand MoveYCommand { get; }
|
||||
public ICommand MoveZCommand { get; }
|
||||
public ICommand MoveAllCommand { get; }
|
||||
public ICommand GoHomeCommand { get; }
|
||||
public ICommand EmergencyStopCommand { get; }
|
||||
public ICommand StopXCommand { get; }
|
||||
public ICommand StopYCommand { get; }
|
||||
public ICommand StopZCommand { get; }
|
||||
public ICommand SetSpeedCommand { get; }
|
||||
public ICommand SetHomeCommand { get; }
|
||||
public ICommand ReadStatusCommand { get; }
|
||||
#endregion
|
||||
|
||||
#region 私有字段
|
||||
private readonly DispatcherTimer _pollTimer;
|
||||
private bool _isInitiated = false;
|
||||
#endregion
|
||||
|
||||
public BenchMovementViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||
ConnectCommand = new DelegateCommand(OnConnect);
|
||||
DisconnectCommand = new DelegateCommand(OnDisconnect);
|
||||
MoveXCommand = new DelegateCommand(OnMoveX);
|
||||
MoveYCommand = new DelegateCommand(OnMoveY);
|
||||
MoveZCommand = new DelegateCommand(OnMoveZ);
|
||||
MoveAllCommand = new DelegateCommand(OnMoveAll);
|
||||
GoHomeCommand = new DelegateCommand(OnGoHome);
|
||||
EmergencyStopCommand = new DelegateCommand(OnEmergencyStop);
|
||||
StopXCommand = new DelegateCommand(OnStopX);
|
||||
StopYCommand = new DelegateCommand(OnStopY);
|
||||
StopZCommand = new DelegateCommand(OnStopZ);
|
||||
SetSpeedCommand = new DelegateCommand(OnSetSpeed);
|
||||
SetHomeCommand = new DelegateCommand(OnSetHome);
|
||||
ReadStatusCommand = new DelegateCommand(OnReadStatus);
|
||||
|
||||
// 位置轮询定时器(500ms)
|
||||
_pollTimer = new DispatcherTimer(DispatcherPriority.Background)
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(500)
|
||||
};
|
||||
_pollTimer.Tick += OnPollTick;
|
||||
}
|
||||
|
||||
#region 命令处理
|
||||
|
||||
private async void OnConnect()
|
||||
{
|
||||
if (_gantry == null) return;
|
||||
try
|
||||
{
|
||||
ConnectionStatus = "连接中...";
|
||||
bool ok = await _gantry.ConnectAsync();
|
||||
if (ok)
|
||||
{
|
||||
IsConnected = true;
|
||||
ConnectionStatus = "已连接";
|
||||
_pollTimer.Start();
|
||||
await ReadDeviceParametersAsync();
|
||||
LoggerHelper.Info("[BenchMovement] 台架连接成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectionStatus = "连接失败";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 连接失败: {ex.Message}");
|
||||
ConnectionStatus = $"连接失败: {ex.Message}";
|
||||
}
|
||||
UpdateConnectionStatus();
|
||||
}
|
||||
|
||||
private void OnDisconnect()
|
||||
{
|
||||
if (_gantry == null) return;
|
||||
try
|
||||
{
|
||||
_pollTimer.Stop();
|
||||
_gantry.Close();
|
||||
IsConnected = false;
|
||||
ConnectionStatus = "未连接";
|
||||
LoggerHelper.Info("[BenchMovement] 台架已断开");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 断开失败: {ex.Message}");
|
||||
}
|
||||
UpdateConnectionStatus();
|
||||
}
|
||||
|
||||
private async void OnMoveX()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetAxisTargetPositionAsync(1, TargetX);
|
||||
await _gantry.StartSingleAxisAsync(1);
|
||||
LoggerHelper.Info($"[BenchMovement] X轴移动到 {TargetX}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] X轴移动失败: {ex.Message}");
|
||||
ShowErrorMessageBox($"X轴移动失败: {ex.Message}", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnMoveY()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetAxisTargetPositionAsync(2, TargetY);
|
||||
await _gantry.StartSingleAxisAsync(2);
|
||||
LoggerHelper.Info($"[BenchMovement] Y轴移动到 {TargetY}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] Y轴移动失败: {ex.Message}");
|
||||
ShowErrorMessageBox($"Y轴移动失败: {ex.Message}", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnMoveZ()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetAxisTargetPositionAsync(3, TargetZ);
|
||||
await _gantry.StartSingleAxisAsync(3);
|
||||
LoggerHelper.Info($"[BenchMovement] Z轴移动到 {TargetZ}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] Z轴移动失败: {ex.Message}");
|
||||
ShowErrorMessageBox($"Z轴移动失败: {ex.Message}", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnMoveAll()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetAxisTargetPositionAsync(1, TargetX);
|
||||
await _gantry.SetAxisTargetPositionAsync(2, TargetY);
|
||||
await _gantry.SetAxisTargetPositionAsync(3, TargetZ);
|
||||
await _gantry.StartAllAxesAsync();
|
||||
LoggerHelper.Info($"[BenchMovement] 三轴同时移动: X={TargetX}, Y={TargetY}, Z={TargetZ}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 三轴移动失败: {ex.Message}");
|
||||
ShowErrorMessageBox($"三轴移动失败: {ex.Message}", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnGoHome()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.HomeAsync();
|
||||
LoggerHelper.Info("[BenchMovement] 回原点已触发");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 回原点失败: {ex.Message}");
|
||||
ShowErrorMessageBox($"回原点失败: {ex.Message}", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnEmergencyStop()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetEStopStateAsync(true);
|
||||
LoggerHelper.Info("[BenchMovement] 急停已触发");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 急停失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnStopX()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetStopStateAsync(1, true);
|
||||
await Task.Delay(100);
|
||||
await _gantry.SetStopStateAsync(1, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] X轴停止失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnStopY()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetStopStateAsync(2, true);
|
||||
await Task.Delay(100);
|
||||
await _gantry.SetStopStateAsync(2, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] Y轴停止失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnStopZ()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetStopStateAsync(3, true);
|
||||
await Task.Delay(100);
|
||||
await _gantry.SetStopStateAsync(3, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] Z轴停止失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSetSpeed()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetAxisSpeedAsync(1, SpeedX);
|
||||
await _gantry.SetAxisSpeedAsync(2, SpeedY);
|
||||
await _gantry.SetAxisSpeedAsync(3, SpeedZ);
|
||||
await _gantry.SetHomePositionSpeedAsync(HomeSpeed);
|
||||
LoggerHelper.Info($"[BenchMovement] 速度设定完成: X={SpeedX}, Y={SpeedY}, Z={SpeedZ}, 回零={HomeSpeed}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 速度设定失败: {ex.Message}");
|
||||
ShowErrorMessageBox($"速度设定失败: {ex.Message}", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSetHome()
|
||||
{
|
||||
if (!EnsureConnected()) return;
|
||||
try
|
||||
{
|
||||
await _gantry!.SetHomePositionAsync(HomeX, HomeY, HomeZ);
|
||||
LoggerHelper.Info($"[BenchMovement] 零点设定完成: X={HomeX}, Y={HomeY}, Z={HomeZ}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 零点设定失败: {ex.Message}");
|
||||
ShowErrorMessageBox($"零点设定失败: {ex.Message}", () => { });
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnReadStatus()
|
||||
{
|
||||
await ReadDeviceParametersAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
private bool EnsureConnected()
|
||||
{
|
||||
if (_gantry == null || !_gantry.IsConnected)
|
||||
{
|
||||
ShowErrorMessageBox("设备未连接,请先连接台架", () => { });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateConnectionStatus()
|
||||
{
|
||||
IsConnected = _gantry?.IsConnected ?? false;
|
||||
ConnectionStatus = IsConnected ? "已连接" : "未连接";
|
||||
}
|
||||
|
||||
private async Task ReadDeviceParametersAsync()
|
||||
{
|
||||
if (_gantry == null || !_gantry.IsConnected) return;
|
||||
try
|
||||
{
|
||||
var (x, y, z) = await _gantry.GetHomePositionAsync();
|
||||
HomeX = x; HomeY = y; HomeZ = z;
|
||||
|
||||
SpeedX = await _gantry.GetAxisSpeedAsync(1);
|
||||
SpeedY = await _gantry.GetAxisSpeedAsync(2);
|
||||
SpeedZ = await _gantry.GetAxisSpeedAsync(3);
|
||||
HomeSpeed = await _gantry.GetHomePositionSpeedAsync();
|
||||
|
||||
// 读取 6 轴绝对位置
|
||||
var positions = await _gantry.GetAllAbsolutePositionsAsync();
|
||||
AbsPos1 = positions.Axis1; AbsPos2 = positions.Axis2; AbsPos3 = positions.Axis3;
|
||||
AbsPos4 = positions.Axis4; AbsPos5 = positions.Axis5; AbsPos6 = positions.Axis6;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 读取设备参数失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnPollTick(object? sender, EventArgs e)
|
||||
{
|
||||
if (_gantry == null || !_gantry.IsConnected) return;
|
||||
try
|
||||
{
|
||||
// 批量读取 6 轴绝对位置(一次性通信,高效)
|
||||
var pos = await _gantry.GetAllAbsolutePositionsAsync();
|
||||
AbsPos1 = pos.Axis1; AbsPos2 = pos.Axis2; AbsPos3 = pos.Axis3;
|
||||
AbsPos4 = pos.Axis4; AbsPos5 = pos.Axis5; AbsPos6 = pos.Axis6;
|
||||
}
|
||||
catch { /* 忽略轮询错误 */ }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
base.OnNavigatedTo(navigationContext);
|
||||
if (!_isInitiated)
|
||||
{
|
||||
_isInitiated = true;
|
||||
InitGantryFromDeviceManager();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 DeviceManager.DeviceMap 中查找 GantryControlTcp 实例,
|
||||
/// 优先使用当前打开的作用域,找不到则取任意已注册作用域。
|
||||
/// </summary>
|
||||
private void InitGantryFromDeviceManager()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 优先使用当前打开的作用域
|
||||
IScopedProvider? scope = null;
|
||||
if (!string.IsNullOrEmpty(_globalInfo.CurrentOpeningScope)
|
||||
&& _globalInfo.ScopeDic.TryGetValue(_globalInfo.CurrentOpeningScope, out scope))
|
||||
{
|
||||
// 找到了
|
||||
}
|
||||
else
|
||||
{
|
||||
// 退而求其次,取任意可用作用域
|
||||
scope = _globalInfo.ScopeDic.Values.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (scope == null)
|
||||
{
|
||||
LoggerHelper.Warn("[BenchMovement] 无可用的作用域,无法解析 DeviceManager。");
|
||||
return;
|
||||
}
|
||||
|
||||
_deviceManager = scope.Resolve<DeviceManager>();
|
||||
_gantry = _deviceManager.DeviceMap.Values.OfType<GantryControlTcp>().FirstOrDefault();
|
||||
|
||||
if (_gantry != null)
|
||||
{
|
||||
LoggerHelper.Info($"[BenchMovement] 已从 DeviceManager 获取 GantryControlTcp 实例。");
|
||||
UpdateConnectionStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerHelper.Warn("[BenchMovement] DeviceManager 中未找到 GantryControlTcp 设备,请检查设备配置。");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"[BenchMovement] 初始化 GantryControlTcp 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pollTimer.Stop();
|
||||
// GantryControlTcp 由 DeviceManager 统一管理生命周期,此处仅停止轮询
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
394
BenchMovementModule/Views/BenchMovementView.xaml
Normal file
394
BenchMovementModule/Views/BenchMovementView.xaml
Normal file
@@ -0,0 +1,394 @@
|
||||
<UserControl x:Class="BenchMovementModule.Views.BenchMovementView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
d:DesignHeight="1080" d:DesignWidth="1920">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style TargetType="TextBlock" x:Key="HeaderStyle">
|
||||
<Setter Property="Foreground" Value="#333333"/>
|
||||
<Setter Property="FontSize" Value="18"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="Margin" Value="2,2,2,5"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="LabelStyle">
|
||||
<Setter Property="Foreground" Value="#555555"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="ValueStyle">
|
||||
<Setter Property="Foreground" Value="#0056B3"/>
|
||||
<Setter Property="FontSize" Value="26"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Foreground" Value="#333333"/>
|
||||
<Setter Property="BorderBrush" Value="#CCCCCC"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Padding" Value="4,2"/>
|
||||
<Setter Property="Height" Value="28"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="#F5F5F5"/>
|
||||
<Setter Property="Foreground" Value="#333333"/>
|
||||
<Setter Property="BorderBrush" Value="#B8B8B8"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Padding" Value="12,5"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Height" Value="34"/>
|
||||
<Setter Property="Margin" Value="0,0,0,8"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="btnBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="3">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#EAEAEA"/>
|
||||
<Setter TargetName="btnBorder" Property="BorderBrush" Value="#4A90E2"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#DCDCDC"/>
|
||||
<Setter TargetName="btnBorder" Property="BorderBrush" Value="#2A6099"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button" x:Key="StopButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="#FFF0F0"/>
|
||||
<Setter Property="Foreground" Value="#C00000"/>
|
||||
<Setter Property="BorderBrush" Value="#E0A0A0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="3">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#FFE0E0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#F0C0C0"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button" x:Key="EmergencyButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="#D9534F"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#D43F3A"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="4">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#C9302C"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#AC2925"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="Button" x:Key="ConnectButtonStyle" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="#5CB85C"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#4CAE4C"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" CornerRadius="3">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#449D44"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="btnBorder" Property="Background" Value="#3D8B3D"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="GroupBox">
|
||||
<Setter Property="BorderBrush" Value="#D0D0D0"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Margin" Value="6"/>
|
||||
<Setter Property="Padding" Value="10"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Background="#F0F2F5">
|
||||
<TabControl Background="Transparent" BorderThickness="0">
|
||||
<TabControl.Resources>
|
||||
<Style TargetType="TabItem">
|
||||
<Setter Property="Background" Value="#E0E4E8"/>
|
||||
<Setter Property="Foreground" Value="#333333"/>
|
||||
<Setter Property="FontSize" Value="15"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Padding" Value="25,6"/>
|
||||
<Setter Property="Margin" Value="2,0,2,0"/>
|
||||
</Style>
|
||||
</TabControl.Resources>
|
||||
|
||||
<!-- ======================= 1. 控制面板 ======================= -->
|
||||
<TabItem Header="控制">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="4*"/>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 顶部:连接状态条 -->
|
||||
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="White" BorderBrush="#D0D0D0" BorderThickness="1" CornerRadius="3" Padding="15,8" Margin="0,0,0,10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Ellipse Width="14" Height="14" Margin="0,0,8,0">
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="Ellipse">
|
||||
<Setter Property="Fill" Value="#999999"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Fill" Value="#5CB85C"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
</Ellipse>
|
||||
<TextBlock Text="{Binding ConnectionStatus}" FontSize="16" FontWeight="Bold" VerticalAlignment="Center" Margin="0,0,20,0"/>
|
||||
<Button Content="连接" Width="80" Margin="0" Style="{StaticResource ConnectButtonStyle}" Command="{Binding ConnectCommand}"/>
|
||||
<Button Content="断开" Width="80" Margin="8,0,0,0" Style="{StaticResource StopButtonStyle}" Command="{Binding DisconnectCommand}"/>
|
||||
<Button Content="刷新状态" Width="90" Margin="8,0,0,0" Command="{Binding ReadStatusCommand}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 左中:位置显示 & 寸动目标 -->
|
||||
<Grid Grid.Row="1" Grid.Column="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1*"/>
|
||||
<ColumnDefinition Width="1.5*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 6轴绝对位置 -->
|
||||
<GroupBox Grid.Column="0">
|
||||
<GroupBox.Header>
|
||||
<TextBlock Text="绝对位置(只读)" Style="{StaticResource HeaderStyle}"/>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="轴1" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding AbsPos1}" Style="{StaticResource ValueStyle}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="轴2" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding AbsPos2}" Style="{StaticResource ValueStyle}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="轴3" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding AbsPos3}" Style="{StaticResource ValueStyle}"/>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="轴4" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding AbsPos4}" Style="{StaticResource ValueStyle}"/>
|
||||
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="轴5" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding AbsPos5}" Style="{StaticResource ValueStyle}"/>
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="轴6" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBlock Grid.Row="5" Grid.Column="1" Text="{Binding AbsPos6}" Style="{StaticResource ValueStyle}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 寸动目标位置 -->
|
||||
<GroupBox Grid.Column="1">
|
||||
<GroupBox.Header>
|
||||
<TextBlock Text="寸动目标" Style="{StaticResource HeaderStyle}"/>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="X目标" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding TargetX}" Margin="5"/>
|
||||
<Button Grid.Row="0" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveXCommand}"/>
|
||||
<Button Grid.Row="0" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopXCommand}"/>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Y目标" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding TargetY}" Margin="5"/>
|
||||
<Button Grid.Row="1" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveYCommand}"/>
|
||||
<Button Grid.Row="1" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopYCommand}"/>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Z目标" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding TargetZ}" Margin="5"/>
|
||||
<Button Grid.Row="2" Grid.Column="2" Content="移动" Width="60" Margin="5,0,5,0" Command="{Binding MoveZCommand}"/>
|
||||
<Button Grid.Row="2" Grid.Column="3" Content="停止" Width="60" Margin="0,0,5,0" Style="{StaticResource StopButtonStyle}" Command="{Binding StopZCommand}"/>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
<!-- 右侧:控制面板 -->
|
||||
<Grid Grid.Row="1" Grid.Column="1" Margin="6,6,0,6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel Grid.Row="0">
|
||||
<Button Content="↩ 回原点" Command="{Binding GoHomeCommand}"/>
|
||||
<Button Content="⬆ 三轴同动" Command="{Binding MoveAllCommand}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1" VerticalAlignment="Center">
|
||||
<TextBlock Text="单轴控制" Style="{StaticResource LabelStyle}" HorizontalAlignment="Center" Margin="0,10,0,10"/>
|
||||
<Button Content="↕ X 移动" Command="{Binding MoveXCommand}"/>
|
||||
<Button Content="↔ Y 移动" Command="{Binding MoveYCommand}"/>
|
||||
<Button Content="↕ Z 移动" Command="{Binding MoveZCommand}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Row="2" Content="❌ 急 停" Style="{StaticResource EmergencyButtonStyle}" FontWeight="Bold" FontSize="18" Height="55" Margin="0" Command="{Binding EmergencyStopCommand}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabItem>
|
||||
|
||||
<!-- ======================= 2. 设置 ======================= -->
|
||||
<TabItem Header="设置">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Margin="10">
|
||||
|
||||
<!-- 移动速度设定(脉冲) -->
|
||||
<GroupBox>
|
||||
<GroupBox.Header>
|
||||
<TextBlock Text="移动速度设定" Style="{StaticResource HeaderStyle}"/>
|
||||
</GroupBox.Header>
|
||||
<StackPanel Margin="5">
|
||||
<Grid Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="160"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="X速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding SpeedX}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="160"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Y速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding SpeedY}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="160"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Z速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding SpeedZ}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="160"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="回零速度(脉冲)" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding HomeSpeed}"/>
|
||||
</Grid>
|
||||
<Button Content="速度设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetSpeedCommand}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 零点设定 -->
|
||||
<GroupBox>
|
||||
<GroupBox.Header>
|
||||
<TextBlock Text="零点设定" Style="{StaticResource HeaderStyle}"/>
|
||||
</GroupBox.Header>
|
||||
<StackPanel Margin="5">
|
||||
<Grid Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="X零点" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding HomeX}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,0,0,6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Y零点" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding HomeY}"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,0,0,10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Z零点" Style="{StaticResource LabelStyle}"/>
|
||||
<TextBox Grid.Column="1" Text="{Binding HomeZ}"/>
|
||||
</Grid>
|
||||
<Button Content="零点设定" Width="110" HorizontalAlignment="Left" Margin="0" Command="{Binding SetHomeCommand}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
28
BenchMovementModule/Views/BenchMovementView.xaml.cs
Normal file
28
BenchMovementModule/Views/BenchMovementView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace BenchMovementModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// BenchMovementView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class BenchMovementView : UserControl
|
||||
{
|
||||
public BenchMovementView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
CANModule/CANModule.cs
Normal file
18
CANModule/CANModule.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using CANModule.Views;
|
||||
|
||||
namespace CANModule
|
||||
{
|
||||
public class CANModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterDialog<SelectCanMessageView>("SelectCanMessageView");
|
||||
containerRegistry.RegisterDialog<CollectCANSignalSettingView>("CollectCANSignalSettingView");
|
||||
containerRegistry.RegisterForNavigation<ZLGCANFDView>("ZLGCANFDView");
|
||||
}
|
||||
}
|
||||
}
|
||||
14
CANModule/CANModule.csproj
Normal file
14
CANModule/CANModule.csproj
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
251
CANModule/ViewModels/CollectCANSingalSettingViewModel.cs
Normal file
251
CANModule/ViewModels/CollectCANSingalSettingViewModel.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.ViewModelBase;
|
||||
using ZLGUSBCANFD;
|
||||
|
||||
namespace CANModule.ViewModels
|
||||
{
|
||||
public class CollectCANSignalSettingViewModel : DialogViewModelBase
|
||||
{
|
||||
#region 字段
|
||||
private ZLGCANFD? _canfd;
|
||||
private SystemConfig? _systemConfig;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public ObservableCollection<CANSignalConfig> ConfigurationList
|
||||
{
|
||||
get => _systemConfig?.ConfigurationList ?? new ObservableCollection<CANSignalConfig>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private ObservableCollection<string> _messageList;
|
||||
public ObservableCollection<string> MessageList
|
||||
{
|
||||
get => _messageList;
|
||||
set => SetProperty(ref _messageList, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<string> _signalList;
|
||||
public ObservableCollection<string> SignalList
|
||||
{
|
||||
get => _signalList;
|
||||
set => SetProperty(ref _signalList, value);
|
||||
}
|
||||
|
||||
private string _title = "信号采集设置";
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private string _selectedMessage;
|
||||
public string SelectedMessage
|
||||
{
|
||||
get => _selectedMessage;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedMessage, value))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value) && _canfd != null)
|
||||
{
|
||||
var db = _canfd.DBCParser.MsgDatabase;
|
||||
if (MessageChannel >= 0 && MessageChannel < db.Count)
|
||||
{
|
||||
var targetMsg = db[MessageChannel]
|
||||
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
|
||||
|
||||
if (targetMsg != null && targetMsg.signal_Name != null)
|
||||
{
|
||||
SignalList = new ObservableCollection<string>(targetMsg.signal_Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
SignalList = new ObservableCollection<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SignalList = new ObservableCollection<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CANSignalConfig _SelectedItem;
|
||||
public CANSignalConfig SelectedItem
|
||||
{
|
||||
get => _SelectedItem;
|
||||
set => SetProperty(ref _SelectedItem, value);
|
||||
}
|
||||
|
||||
private string _selectedSignal;
|
||||
public string SelectedSignal
|
||||
{
|
||||
get => _selectedSignal;
|
||||
set => SetProperty(ref _selectedSignal, value);
|
||||
}
|
||||
|
||||
private int _MessageChannel=-1;
|
||||
public int MessageChannel
|
||||
{
|
||||
get => _MessageChannel;
|
||||
set
|
||||
{
|
||||
if (_MessageChannel != value)
|
||||
{
|
||||
_MessageChannel = value;
|
||||
|
||||
SelectedMessage = null;
|
||||
SelectedSignal = null;
|
||||
SignalList = new ObservableCollection<string>();
|
||||
|
||||
if (_canfd != null)
|
||||
{
|
||||
var db = _canfd.DBCParser.MsgDatabase;
|
||||
if (db != null && value >= 0 && value < db.Count)
|
||||
{
|
||||
MessageList = new ObservableCollection<string>(
|
||||
db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageList = new ObservableCollection<string>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageList = new ObservableCollection<string>();
|
||||
}
|
||||
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _collectionInterval = 1000;
|
||||
public int CollectionInterval
|
||||
{
|
||||
get => _collectionInterval;
|
||||
set => SetProperty(ref _collectionInterval, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand AddCommand { get; set; }
|
||||
public ICommand CloseCommand { get; set; }
|
||||
public ICommand SaveCommand { get; set; }
|
||||
public ICommand DeleteCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public CollectCANSignalSettingViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
CloseCommand = new DelegateCommand(Close);
|
||||
AddCommand = new DelegateCommand(Add);
|
||||
SaveCommand = new DelegateCommand(Save);
|
||||
DeleteCommand = new DelegateCommand(Delete);
|
||||
}
|
||||
|
||||
#region Dialog 生命周期
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
_systemConfig = parameters.GetValue<SystemConfig>("SystemConfig");
|
||||
_canfd = _systemConfig?.CANFD;
|
||||
string title = _systemConfig.Title;
|
||||
char lastChar = title[title.Length - 1];
|
||||
// 3. "TestCellX"转int
|
||||
MessageChannel = int.Parse(lastChar.ToString())-1;
|
||||
if (MessageChannel >= 4) MessageChannel -= 4;
|
||||
RaisePropertyChanged(nameof(ConfigurationList));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令处理
|
||||
private void Delete()
|
||||
{
|
||||
if (SelectedItem != null)
|
||||
{
|
||||
_systemConfig?.ConfigurationList.Remove(SelectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
private void Save()
|
||||
{
|
||||
ConfigService.Save(_systemConfig);
|
||||
}
|
||||
|
||||
|
||||
private void Add()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedSignal) || string.IsNullOrEmpty(SelectedMessage) || CollectionInterval < 100)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_systemConfig == null) return;
|
||||
|
||||
string hexString = ExtractHex(SelectedMessage);
|
||||
if (string.IsNullOrEmpty(hexString)) return;
|
||||
|
||||
int decimalValue = ConvertHexToDecimal(hexString);
|
||||
if (_systemConfig.ConfigurationList.Any(x => x.SignalName == SelectedSignal && x.Channel == MessageChannel))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_systemConfig.ConfigurationList.Add(new CANSignalConfig
|
||||
{
|
||||
CollectionID = Guid.NewGuid(),
|
||||
Channel = MessageChannel,
|
||||
MethodName = CANSignalBroadcaster.BuildMethodName((uint)decimalValue, SelectedSignal),
|
||||
MessageName = ExtractMessageName(SelectedMessage),
|
||||
SignalName = SelectedSignal,
|
||||
MessageID = decimalValue,
|
||||
CollectionInterval = CollectionInterval
|
||||
});
|
||||
}
|
||||
|
||||
private string ExtractHex(string input)
|
||||
{
|
||||
int startIndex = input.IndexOf("0x") + 2;
|
||||
int endIndex = input.IndexOf("]", startIndex);
|
||||
if (startIndex >= 2 && endIndex > startIndex)
|
||||
{
|
||||
return input.Substring(startIndex, endIndex - startIndex);
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private int ConvertHexToDecimal(string hex)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Convert.ToInt32(hex, 16);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private string ExtractMessageName(string input)
|
||||
{
|
||||
var parts = input.Split(']');
|
||||
return parts.Length > 1 ? parts[1] : string.Empty;
|
||||
}
|
||||
|
||||
private void Close()
|
||||
{
|
||||
RequestClose.Invoke();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
187
CANModule/ViewModels/SelectCanMessageViewModel.cs
Normal file
187
CANModule/ViewModels/SelectCanMessageViewModel.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using UIShare.ViewModelBase;
|
||||
using ZLGUSBCANFD;
|
||||
|
||||
namespace CANModule.ViewModels
|
||||
{
|
||||
public class SelectCanMessageViewModel : DialogViewModelBase
|
||||
{
|
||||
#region 字段
|
||||
private ZLGCANFD? _canfd;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
public DialogCloseListener RequestClose { get; set; }
|
||||
|
||||
private ObservableCollection<int> _ChannelList;
|
||||
public ObservableCollection<int> ChannelList
|
||||
{
|
||||
get => _ChannelList;
|
||||
set => SetProperty(ref _ChannelList, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<string> _messageList;
|
||||
public ObservableCollection<string> MessageList
|
||||
{
|
||||
get => _messageList;
|
||||
set => SetProperty(ref _messageList, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<string> _signalList;
|
||||
public ObservableCollection<string> SignalList
|
||||
{
|
||||
get => _signalList;
|
||||
set => SetProperty(ref _signalList, value);
|
||||
}
|
||||
|
||||
private string _title = "CollectCANSignalSetting";
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
set => SetProperty(ref _title, value);
|
||||
}
|
||||
|
||||
private string _selectedMessage;
|
||||
public string SelectedMessage
|
||||
{
|
||||
get => _selectedMessage;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedMessage, value))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value) && _canfd != null)
|
||||
{
|
||||
var db = _canfd.DBCParser.MsgDatabase;
|
||||
if (MessageChannel >= 0 && MessageChannel < db.Count)
|
||||
{
|
||||
var targetMsg = db[MessageChannel]
|
||||
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
|
||||
|
||||
if (targetMsg != null && targetMsg.signal_Name != null)
|
||||
{
|
||||
SignalList = new ObservableCollection<string>(targetMsg.signal_Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
SignalList = new ObservableCollection<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SignalList = new ObservableCollection<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _selectedSignal;
|
||||
public string SelectedSignal
|
||||
{
|
||||
get => _selectedSignal;
|
||||
set => SetProperty(ref _selectedSignal, value);
|
||||
}
|
||||
|
||||
private int _MessageChannel = -1;
|
||||
public int MessageChannel
|
||||
{
|
||||
get => _MessageChannel;
|
||||
set
|
||||
{
|
||||
if (_MessageChannel != value)
|
||||
{
|
||||
_MessageChannel = value;
|
||||
|
||||
// 1. 先安全清空旧的选择项和信号列表
|
||||
SelectedMessage = null;
|
||||
SelectedSignal = null;
|
||||
SignalList = new ObservableCollection<string>();
|
||||
|
||||
// 2. 刷新当前通道的报文列表
|
||||
if (_canfd != null)
|
||||
{
|
||||
var db = _canfd.DBCParser.MsgDatabase;
|
||||
if (db != null && value >= 0 && value < db.Count)
|
||||
{
|
||||
MessageList = new ObservableCollection<string>(
|
||||
db[value].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageList = new ObservableCollection<string>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageList = new ObservableCollection<string>();
|
||||
}
|
||||
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand CloseCommand { get; set; }
|
||||
public ICommand GetSelectedMessageCommand { get; set; }
|
||||
public ICommand GetSelectedSignalCommand { get; set; }
|
||||
public ICommand LoadCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public SelectCanMessageViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
CloseCommand = new DelegateCommand(Close);
|
||||
GetSelectedMessageCommand = new DelegateCommand(GetSelectedMessage);
|
||||
GetSelectedSignalCommand = new DelegateCommand(GetSelectedSignal);
|
||||
LoadCommand = new DelegateCommand(Load);
|
||||
}
|
||||
|
||||
#region Dialog 生命周期
|
||||
public override void OnDialogOpened(IDialogParameters parameters)
|
||||
{
|
||||
_canfd = parameters.GetValue<ZLGCANFD>("CANFD");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令处理
|
||||
private void Close()
|
||||
{
|
||||
RequestClose.Invoke();
|
||||
}
|
||||
|
||||
private void Load()
|
||||
{
|
||||
int maxCh = _canfd?.DBCParser.MaxChannels ?? 4;
|
||||
ChannelList = new ObservableCollection<int>(Enumerable.Range(0, maxCh));
|
||||
}
|
||||
|
||||
private void GetSelectedSignal()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SelectedSignal))
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Clipboard.SetDataObject(SelectedSignal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void GetSelectedMessage()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SelectedMessage) && SelectedMessage.Contains(']'))
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Clipboard.SetDataObject(SelectedMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
649
CANModule/ViewModels/ZLGCANFDViewModel.cs
Normal file
649
CANModule/ViewModels/ZLGCANFDViewModel.cs
Normal file
@@ -0,0 +1,649 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.ViewModelBase;
|
||||
using ZLGUSBCANFD;
|
||||
|
||||
namespace CANModule.ViewModels
|
||||
{
|
||||
public class ZLGCANFDViewModel : NavigateViewModelBase
|
||||
{
|
||||
#region 字段
|
||||
private ZLGCANFD? _canfd;
|
||||
private SystemConfig? _systemConfig;
|
||||
private CancellationTokenSource? _refreshCts;
|
||||
private readonly DeviceManager _deviceManager;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
private string _Title = "周立功CAN";
|
||||
public string Title
|
||||
{
|
||||
get => _Title;
|
||||
set => SetProperty(ref _Title, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<CanMessageShowVM> _CanMessageList = new();
|
||||
public ObservableCollection<CanMessageShowVM> CanMessageList
|
||||
{
|
||||
get => _CanMessageList;
|
||||
set => SetProperty(ref _CanMessageList, value);
|
||||
}
|
||||
|
||||
private int _DBCChannel = 0;
|
||||
public int DBCChannel
|
||||
{
|
||||
get => _DBCChannel;
|
||||
set => SetProperty(ref _DBCChannel, value);
|
||||
}
|
||||
|
||||
private bool _IsDirectlySend = false;
|
||||
public bool IsDirectlySend
|
||||
{
|
||||
get => _IsDirectlySend;
|
||||
set => SetProperty(ref _IsDirectlySend, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<string> _Message;
|
||||
public ObservableCollection<string> Message
|
||||
{
|
||||
get => _Message;
|
||||
set => SetProperty(ref _Message, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<string> _Signal;
|
||||
public ObservableCollection<string> Signal
|
||||
{
|
||||
get => _Signal;
|
||||
set => SetProperty(ref _Signal, value);
|
||||
}
|
||||
|
||||
private byte _channel;
|
||||
private string _selectedMessage;
|
||||
public string SelectedMessage
|
||||
{
|
||||
get => _selectedMessage;
|
||||
set
|
||||
{
|
||||
if (_selectedMessage != value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value) && _canfd != null)
|
||||
{
|
||||
var db = _canfd.DBCParser.MsgDatabase;
|
||||
if (_channel >= 0 && _channel < db.Count)
|
||||
{
|
||||
var targetMsg = db[(int)_channel]
|
||||
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == value);
|
||||
|
||||
if (targetMsg != null && targetMsg.signal_Name != null)
|
||||
{
|
||||
Signal = new ObservableCollection<string>(targetMsg.signal_Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Signal = new ObservableCollection<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Signal = new ObservableCollection<string>();
|
||||
}
|
||||
_selectedMessage = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string _selectedSignal;
|
||||
public string SelectedSignal
|
||||
{
|
||||
get => _selectedSignal;
|
||||
set => SetProperty(ref _selectedSignal, value);
|
||||
}
|
||||
|
||||
private string _signalValue;
|
||||
public string SignalValue
|
||||
{
|
||||
get => _signalValue;
|
||||
set => SetProperty(ref _signalValue, value);
|
||||
}
|
||||
|
||||
private string _cycleSendInterval;
|
||||
public string CycleSendInterval
|
||||
{
|
||||
get => _cycleSendInterval;
|
||||
set => SetProperty(ref _cycleSendInterval, value);
|
||||
}
|
||||
|
||||
private int _messageSendChannel = 0;
|
||||
public int MessageSendChannel
|
||||
{
|
||||
get => _messageSendChannel;
|
||||
set => SetProperty(ref _messageSendChannel, value);
|
||||
}
|
||||
|
||||
public string BLFPath
|
||||
{
|
||||
get => _systemConfig?.DefaultBLFFilePath ?? "";
|
||||
set
|
||||
{
|
||||
if (_systemConfig != null && _systemConfig.DefaultBLFFilePath != value)
|
||||
{
|
||||
_systemConfig.DefaultBLFFilePath = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DBCPath
|
||||
{
|
||||
get => _systemConfig?.DefaultDBCFilePath ?? "";
|
||||
set
|
||||
{
|
||||
if (_systemConfig != null && _systemConfig.DefaultDBCFilePath != value)
|
||||
{
|
||||
_systemConfig.DefaultDBCFilePath = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _IsSaved;
|
||||
public bool IsSaved
|
||||
{
|
||||
get => _IsSaved;
|
||||
set => SetProperty(ref _IsSaved, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<AutoDBCLoadItem> DBCAutoLoadList
|
||||
{
|
||||
get => _systemConfig?.DBCAutoLoadList ?? new ObservableCollection<AutoDBCLoadItem>();
|
||||
}
|
||||
|
||||
private AutoDBCLoadItem _SelectedDcAutoLoad;
|
||||
public AutoDBCLoadItem SelectedDcAutoLoad
|
||||
{
|
||||
get => _SelectedDcAutoLoad;
|
||||
set => SetProperty(ref _SelectedDcAutoLoad, value);
|
||||
}
|
||||
|
||||
// ===== 自定义报文发送属性 =====
|
||||
private APP_CHANNEL[] _appChannelNames = Enum.GetValues<APP_CHANNEL>();
|
||||
public APP_CHANNEL[] APP_CHANNEL
|
||||
{
|
||||
get => _appChannelNames;
|
||||
set => SetProperty(ref _appChannelNames, value);
|
||||
}
|
||||
|
||||
private APP_CHANNEL _aIdxChn;
|
||||
public APP_CHANNEL AIdxChn
|
||||
{
|
||||
get => _aIdxChn;
|
||||
set => SetProperty(ref _aIdxChn, value);
|
||||
}
|
||||
|
||||
private int _aID;
|
||||
public int AID
|
||||
{
|
||||
get => _aID;
|
||||
set => SetProperty(ref _aID, value);
|
||||
}
|
||||
|
||||
private bool _aIsTx;
|
||||
public bool AIsTx
|
||||
{
|
||||
get => _aIsTx;
|
||||
set => SetProperty(ref _aIsTx, value);
|
||||
}
|
||||
|
||||
private bool _aIsExt;
|
||||
public bool AIsExt
|
||||
{
|
||||
get => _aIsExt;
|
||||
set => SetProperty(ref _aIsExt, value);
|
||||
}
|
||||
|
||||
private bool _aIsRemote;
|
||||
public bool AIsRemote
|
||||
{
|
||||
get => _aIsRemote;
|
||||
set => SetProperty(ref _aIsRemote, value);
|
||||
}
|
||||
|
||||
private byte _aDlc;
|
||||
public byte ADLC
|
||||
{
|
||||
get => _aDlc;
|
||||
set => SetProperty(ref _aDlc, value);
|
||||
}
|
||||
|
||||
private byte[] _aDataArray;
|
||||
public byte[] ADataArray
|
||||
{
|
||||
get => _aDataArray;
|
||||
set => SetProperty(ref _aDataArray, value);
|
||||
}
|
||||
|
||||
private bool _aIsFD = true;
|
||||
public bool AIsFD
|
||||
{
|
||||
get => _aIsFD;
|
||||
set => SetProperty(ref _aIsFD, value);
|
||||
}
|
||||
|
||||
private bool _aIsBRS = false;
|
||||
public bool AIsBRS
|
||||
{
|
||||
get => _aIsBRS;
|
||||
set => SetProperty(ref _aIsBRS, value);
|
||||
}
|
||||
|
||||
private bool _AutoSetting = true;
|
||||
public bool AutoSetting
|
||||
{
|
||||
get => _AutoSetting;
|
||||
set => SetProperty(ref _AutoSetting, value);
|
||||
}
|
||||
|
||||
private string _CustomMessage = "[00,11,22,33,AA,BB,CC,DD]";
|
||||
public string CustomMessage
|
||||
{
|
||||
get => _CustomMessage;
|
||||
set => SetProperty(ref _CustomMessage, value);
|
||||
}
|
||||
|
||||
private float _CustomSendCycleInterval = 0.0f;
|
||||
public float CustomSendCycleInterval
|
||||
{
|
||||
get => _CustomSendCycleInterval;
|
||||
set => SetProperty(ref _CustomSendCycleInterval, value);
|
||||
}
|
||||
|
||||
//private bool _UpdateToDB;
|
||||
//public bool UpdateToDB
|
||||
//{
|
||||
// get => _UpdateToDB;
|
||||
// set => SetProperty(ref _UpdateToDB, value);
|
||||
//}
|
||||
|
||||
// ===== 连接状态 =====
|
||||
private bool _IsDeviceOpened;
|
||||
public bool IsDeviceOpened
|
||||
{
|
||||
get => _IsDeviceOpened;
|
||||
set => SetProperty(ref _IsDeviceOpened, value);
|
||||
}
|
||||
|
||||
private ObservableCollection<int> _ChannelList;
|
||||
public ObservableCollection<int> ChannelList
|
||||
{
|
||||
get => _ChannelList;
|
||||
set => SetProperty(ref _ChannelList, value);
|
||||
}
|
||||
|
||||
private int _SelectedChannel = -1;
|
||||
public int SelectedChannel
|
||||
{
|
||||
get => _SelectedChannel;
|
||||
set
|
||||
{
|
||||
if (_SelectedChannel != value)
|
||||
{
|
||||
_SelectedChannel = value;
|
||||
if (value >= 0 && _canfd != null)
|
||||
{
|
||||
_canfd.初始化并启动通道((uint)value);
|
||||
}
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand SelectBLFPathCommand { get; set; }
|
||||
public ICommand SaveBLFPathCommand { get; set; }
|
||||
public ICommand StartTranscribeCommand { get; set; }
|
||||
public ICommand EndTranscribeCommand { get; set; }
|
||||
public ICommand ConnectCanCommand { get; set; }
|
||||
public ICommand CloseCanCommand { get; set; }
|
||||
public ICommand ClearCycleCommand { get; set; }
|
||||
public ICommand UpLoadDBCCommand { get; set; }
|
||||
public ICommand ChangeDBCCommand { get; set; }
|
||||
public ICommand LoadDBCCommand { get; set; }
|
||||
public ICommand LoadMessageCommand { get; set; }
|
||||
public ICommand SetAndSendMessageCommand { get; set; }
|
||||
public ICommand DeleteDcAutoLoadCommand { get; set; }
|
||||
public ICommand SendCustomMessageCommand { get; set; }
|
||||
public ICommand LoadCommand { get; set; }
|
||||
#endregion
|
||||
|
||||
public ZLGCANFDViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_systemConfig = containerProvider.Resolve<SystemConfig>();
|
||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||
_canfd = _deviceManager.CANFD;
|
||||
SelectBLFPathCommand = new DelegateCommand(SelectBLFPath);
|
||||
SaveBLFPathCommand = new DelegateCommand(SaveBLFPath);
|
||||
StartTranscribeCommand = new DelegateCommand(StartTranscribe);
|
||||
EndTranscribeCommand = new DelegateCommand(EndTranscribe);
|
||||
ConnectCanCommand = new DelegateCommand(ConnectCan);
|
||||
CloseCanCommand = new DelegateCommand(CloseCan);
|
||||
ClearCycleCommand = new DelegateCommand(ClearCycle);
|
||||
UpLoadDBCCommand = new DelegateCommand(UpLoadDBC);
|
||||
ChangeDBCCommand = new DelegateCommand(ChangeDBC);
|
||||
LoadDBCCommand = new DelegateCommand(LoadDBC);
|
||||
SetAndSendMessageCommand = new DelegateCommand(SetAndSendMessage);
|
||||
LoadMessageCommand = new DelegateCommand(LoadMessage);
|
||||
DeleteDcAutoLoadCommand = new DelegateCommand(DeleteDcAutoLoad);
|
||||
SendCustomMessageCommand = new DelegateCommand(SendCustomMessage);
|
||||
_canfd.OnDbcMessageDecoded += OnDbcMessageDecoded;
|
||||
LoadCommand = new DelegateCommand(Load);
|
||||
}
|
||||
|
||||
#region Dialog 生命周期
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
if (_canfd != null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
RaisePropertyChanged(nameof(BLFPath));
|
||||
RaisePropertyChanged(nameof(DBCPath));
|
||||
RaisePropertyChanged(nameof(DBCAutoLoadList));
|
||||
}
|
||||
|
||||
public override void OnNavigatedFrom(NavigationContext navigationContext)
|
||||
{
|
||||
if (_canfd != null)
|
||||
{
|
||||
_canfd.OnDbcMessageDecoded -= OnDbcMessageDecoded;
|
||||
}
|
||||
_refreshCts?.Cancel();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令处理
|
||||
private void Load()
|
||||
{
|
||||
int maxCh = _canfd?.DBCParser.MaxChannels ?? 4;
|
||||
ChannelList = new ObservableCollection<int>(Enumerable.Range(0, maxCh));
|
||||
}
|
||||
|
||||
private void SendCustomMessage()
|
||||
{
|
||||
if (_canfd == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
ADataArray = 报文转换(CustomMessage);
|
||||
if (ADataArray.Length > byte.MaxValue) throw new Exception("报文数量超过上限!");
|
||||
if (AutoSetting)
|
||||
{
|
||||
ADLC = (byte)ADataArray.Length;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowErrorMessageBox($"报文转换失败:{ex.Message}", () => { });
|
||||
return;
|
||||
}
|
||||
|
||||
_canfd.发送自定义报文(
|
||||
(uint)AIdxChn,
|
||||
AID.ToString(),
|
||||
ADataArray,
|
||||
ADLC,
|
||||
AIsExt,
|
||||
AIsFD,
|
||||
AIsBRS);
|
||||
}
|
||||
|
||||
private void DeleteDcAutoLoad()
|
||||
{
|
||||
if (SelectedDcAutoLoad != null)
|
||||
{
|
||||
DBCAutoLoadList.Remove(SelectedDcAutoLoad);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadMessage()
|
||||
{
|
||||
if (_canfd == null) return;
|
||||
|
||||
if (MessageSendChannel >= 0)
|
||||
{
|
||||
var db = _canfd.DBCParser.MsgDatabase;
|
||||
if (MessageSendChannel < db.Count)
|
||||
{
|
||||
Message = new ObservableCollection<string>(
|
||||
db[MessageSendChannel].Select(s => $"[0x{s.msg_id:X}]{s.msg_name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetAndSendMessage()
|
||||
{
|
||||
if (_canfd == null) return;
|
||||
|
||||
if (int.TryParse(CycleSendInterval, out int sendPeriod) &&
|
||||
double.TryParse(SignalValue, out double SValue) &&
|
||||
MessageSendChannel >= 0)
|
||||
{
|
||||
_channel = (byte)MessageSendChannel;
|
||||
var message = SelectedMessage.Split(']')[1];
|
||||
|
||||
// 通过 DBC 查找帧 ID
|
||||
var db = _canfd.DBCParser.MsgDatabase;
|
||||
if (MessageSendChannel < db.Count)
|
||||
{
|
||||
var targetMsg = db[MessageSendChannel]
|
||||
.FirstOrDefault(x => $"[0x{x.msg_id:X}]{x.msg_name}" == SelectedMessage);
|
||||
|
||||
if (targetMsg != null)
|
||||
{
|
||||
_canfd.设置报文((uint)MessageSendChannel, targetMsg.msg_id.ToString(), SelectedSignal, SValue, sendPeriod, IsDirectlySend);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpLoadDBC()
|
||||
{
|
||||
if (_canfd == null) return;
|
||||
|
||||
// 释放当前通道的 DBC
|
||||
if (DBCChannel >= 0)
|
||||
{
|
||||
_canfd.释放通道DBC((uint)DBCChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeDBC()
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "更换DBC文件",
|
||||
Filter = "DBC 文件 (*.dbc)|*.dbc|所有文件 (*.*)|*.*",
|
||||
DefaultExt = ".dbc",
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
DBCPath = System.IO.Path.GetFullPath(dialog.FileName);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDBC()
|
||||
{
|
||||
if (_canfd == null || DBCChannel < 0) return;
|
||||
|
||||
bool re = _canfd.加载通道DBC文件((uint)DBCChannel, DBCPath);
|
||||
|
||||
if (re && IsSaved)
|
||||
{
|
||||
var item = DBCAutoLoadList.FirstOrDefault(s => s.DBCChannel == DBCChannel);
|
||||
if (item == null)
|
||||
{
|
||||
DBCAutoLoadList.Add(new AutoDBCLoadItem
|
||||
{
|
||||
DBCFilePath = DBCPath,
|
||||
DBCChannel = DBCChannel
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
DBCAutoLoadList.Remove(item);
|
||||
DBCAutoLoadList.Add(new AutoDBCLoadItem
|
||||
{
|
||||
DBCFilePath = DBCPath,
|
||||
DBCChannel = DBCChannel
|
||||
});
|
||||
}
|
||||
ConfigService.Save(_systemConfig);
|
||||
}
|
||||
}
|
||||
private void ClearCycle()
|
||||
{
|
||||
_canfd?.停止所有循环发送();
|
||||
}
|
||||
|
||||
private void CloseCan()
|
||||
{
|
||||
if (_canfd == null) return;
|
||||
|
||||
_canfd.关闭CAN卡设备();
|
||||
IsDeviceOpened = false;
|
||||
_refreshCts?.Cancel();
|
||||
}
|
||||
|
||||
private void ConnectCan()
|
||||
{
|
||||
if (_canfd == null) return;
|
||||
|
||||
bool re = _canfd.打开设备();
|
||||
IsDeviceOpened = re;
|
||||
|
||||
if (re)
|
||||
{
|
||||
// 启动实时报文刷新
|
||||
_refreshCts?.Cancel();
|
||||
_refreshCts = new CancellationTokenSource();
|
||||
_ = Refresh(_refreshCts.Token);
|
||||
}
|
||||
}
|
||||
|
||||
private void EndTranscribe()
|
||||
{
|
||||
// TODO: BLF 录制功能在 ZLGCANFD 中未实现,暂不支持
|
||||
ShowInfoMessageBox("BLF 录制功能尚未在此硬件驱动中实现。", () => { });
|
||||
}
|
||||
|
||||
private void StartTranscribe()
|
||||
{
|
||||
// TODO: BLF 录制功能在 ZLGCANFD 中未实现,暂不支持
|
||||
ShowInfoMessageBox("BLF 录制功能尚未在此硬件驱动中实现。", () => { });
|
||||
}
|
||||
|
||||
private void SaveBLFPath()
|
||||
{
|
||||
// 路径已直接绑定到 SystemConfig,无需额外操作
|
||||
}
|
||||
|
||||
private void SelectBLFPath()
|
||||
{
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Title = "保存 CAN报文回放 BLF 文件",
|
||||
Filter = "BLF 文件 (*.blf)|*.blf|所有文件 (*.*)|*.*",
|
||||
DefaultExt = ".blf",
|
||||
FileName = "log.blf",
|
||||
OverwritePrompt = true
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
BLFPath = System.IO.Path.GetFullPath(dialog.FileName);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
public byte[] 报文转换(string s)
|
||||
{
|
||||
s = s.Replace("[", string.Empty).Replace("]", string.Empty);
|
||||
var strs = s.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
byte[] bytes = new byte[strs.Length];
|
||||
for (int i = 0; i < strs.Length; i++)
|
||||
{
|
||||
bytes[i] = Convert.ToByte(strs[i], 16);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private async Task Refresh(CancellationToken ct = default)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
// 通过 OnDbcMessageDecoded 事件已实时更新 CanMessageList
|
||||
// 此处仅做周期性 UI 刷新触发
|
||||
await Task.Delay(50, ct);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDbcMessageDecoded(uint channel, ZDBC.DBCMessage msg)
|
||||
{
|
||||
// DBC 解码后的报文回调
|
||||
Application.Current.Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
var msgName = System.Text.Encoding.Default.GetString(msg.strName).TrimEnd('\0');
|
||||
var find = CanMessageList.FirstOrDefault(s => s.报文ID == (int)msg.nID&& s.通道==channel);
|
||||
if (find != null)
|
||||
{
|
||||
find.通道 = (byte)channel;
|
||||
find.报文ID = (int)msg.nID;
|
||||
find.时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds(); ;
|
||||
find.长度 = (byte)msg.nSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
CanMessageList.Add(new CanMessageShowVM
|
||||
{
|
||||
通道 = (byte)channel,
|
||||
报文ID = (int)msg.nID,
|
||||
时间戳 = (ulong)DateTimeOffset.Now.ToUnixTimeMilliseconds(),
|
||||
长度 = (byte)msg.nSize
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private string 十六进制字符串转换(System.Span<byte> s)
|
||||
{
|
||||
if (s == null || s.Length == 0)
|
||||
return "[]";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append('[');
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
{
|
||||
sb.Append("0x");
|
||||
sb.Append(s[i].ToString("X2"));
|
||||
if (i < s.Length - 1)
|
||||
sb.Append(", ");
|
||||
}
|
||||
sb.Append(']');
|
||||
return sb.ToString();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
120
CANModule/Views/CollectCANSignalSettingView.xaml
Normal file
120
CANModule/Views/CollectCANSignalSettingView.xaml
Normal file
@@ -0,0 +1,120 @@
|
||||
<UserControl x:Class="CANModule.Views.CollectCANSignalSettingView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:CANModule.Views"
|
||||
xmlns:oxy="http://oxyplot.org/wpf"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
Background="White"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Height="400"
|
||||
Width="1000">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<UserControl.Resources>
|
||||
<cons:HexConverter x:Key="HexConverter" />
|
||||
</UserControl.Resources>
|
||||
<GroupBox Padding="0,0,0,0"
|
||||
MouseLeftButtonDown="MouseLeftButtonDown">
|
||||
<GroupBox.Header>
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="CAN信号采集设置"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center"
|
||||
Margin="5,0,10,0" />
|
||||
<Button Content="关闭"
|
||||
Grid.Column="2"
|
||||
Margin="150 0 0 0"
|
||||
Width="60"
|
||||
Command="{Binding CloseCommand}"
|
||||
Height="25" />
|
||||
</Grid>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<DataGrid x:Name="配置表格"
|
||||
AutoGenerateColumns="False"
|
||||
ItemsSource="{Binding ConfigurationList}"
|
||||
SelectedItem="{Binding SelectedItem}"
|
||||
IsReadOnly="True">
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="删除"
|
||||
Command="{Binding DeleteCommand}"
|
||||
/>
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="通道"
|
||||
Binding="{Binding Channel}" />
|
||||
<DataGridTextColumn Header="报文ID"
|
||||
Binding="{Binding MessageID,Converter={StaticResource HexConverter}}" />
|
||||
<DataGridTextColumn Header="报文名称"
|
||||
Binding="{Binding MessageName}" />
|
||||
<DataGridTextColumn Header="信号名称"
|
||||
Binding="{Binding SignalName}" />
|
||||
<DataGridTextColumn Header="采集间隔"
|
||||
Binding="{Binding CollectionInterval}" />
|
||||
<DataGridTextColumn Header="采集id"
|
||||
Binding="{Binding CollectionID}" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Grid.Row="1"
|
||||
Margin="10">
|
||||
<Label Content="通道(从0开始)"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Name="SignalComboBox"
|
||||
Text="{Binding MessageChannel}"
|
||||
MaxWidth="200"
|
||||
MinWidth="120"
|
||||
Margin="10"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="信号通道" />
|
||||
<Label Content="报文"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox MinWidth="100"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
SelectedItem="{Binding SelectedMessage,Mode=TwoWay}"
|
||||
ItemsSource="{Binding MessageList}"
|
||||
/>
|
||||
<Label Content="信号"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox SelectedItem="{Binding SelectedSignal}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MaxWidth="200"
|
||||
ItemsSource="{Binding SignalList,Mode=TwoWay}"
|
||||
MinWidth="100" />
|
||||
<Label Content="采集间隔(ms)"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBox Text="{Binding CollectionInterval}"
|
||||
MinWidth="100"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
VerticalAlignment="Center" />
|
||||
<Button Content="添加"
|
||||
Margin="10"
|
||||
Command="{Binding AddCommand}"
|
||||
/>
|
||||
<Button Content="保存"
|
||||
Margin="10"
|
||||
Command="{Binding SaveCommand}"
|
||||
/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
25
CANModule/Views/CollectCANSignalSettingView.xaml.cs
Normal file
25
CANModule/Views/CollectCANSignalSettingView.xaml.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace CANModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// CollectCANSignalSettingView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CollectCANSignalSettingView : UserControl
|
||||
{
|
||||
public CollectCANSignalSettingView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
CANModule/Views/SelectCanMessageView.xaml
Normal file
69
CANModule/Views/SelectCanMessageView.xaml
Normal file
@@ -0,0 +1,69 @@
|
||||
<UserControl x:Class="CANModule.Views.SelectCanMessageView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:CANModule.Views"
|
||||
xmlns:oxy="http://oxyplot.org/wpf"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
Background="White"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Height="350"
|
||||
Width="350">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<GroupBox Padding="0,0,0,0"
|
||||
MouseLeftButtonDown="MouseLeftButtonDown">
|
||||
<GroupBox.Header>
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="CAN报文选择"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center"
|
||||
Margin="5,0,10,0" />
|
||||
<Button Content="关闭"
|
||||
Grid.Column="2"
|
||||
Margin="0 0 0 0"
|
||||
Width="60"
|
||||
Command="{Binding CloseCommand}"
|
||||
Height="25" />
|
||||
</Grid>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<StackPanel Grid.Row="1" Margin="10">
|
||||
<Label Content="通道" VerticalAlignment="Center"/>
|
||||
<ComboBox ItemsSource="{Binding ChannelList}"
|
||||
SelectedItem="{Binding MessageChannel,Mode=TwoWay}"
|
||||
MinWidth="100"
|
||||
VerticalAlignment="Center"
|
||||
materialDesign:HintAssist.Hint="" />
|
||||
<Label Content="报文" VerticalAlignment="Center"/>
|
||||
<ComboBox materialDesign:HintAssist.Hint=""
|
||||
MinWidth="100"
|
||||
SelectedItem="{Binding SelectedMessage,Mode=TwoWay}"
|
||||
ItemsSource="{Binding MessageList}" />
|
||||
<Label Content="信号" VerticalAlignment="Center"/>
|
||||
<ComboBox materialDesign:HintAssist.Hint=""
|
||||
SelectedItem="{Binding SelectedSignal,Mode=TwoWay}"
|
||||
ItemsSource="{Binding SignalList}" MinWidth="100" />
|
||||
<Button Content="选择报文名" Margin="10" Command="{Binding GetSelectedMessageCommand}"/>
|
||||
<Button Content="选择信号名" Margin="10" Command="{Binding GetSelectedSignalCommand}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
36
CANModule/Views/SelectCanMessageView.xaml.cs
Normal file
36
CANModule/Views/SelectCanMessageView.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace CANModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// SelectCanMessageView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class SelectCanMessageView : UserControl
|
||||
{
|
||||
public SelectCanMessageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
286
CANModule/Views/ZLGCANFDView.xaml
Normal file
286
CANModule/Views/ZLGCANFDView.xaml
Normal file
@@ -0,0 +1,286 @@
|
||||
<UserControl x:Class="CANModule.Views.ZLGCANFDView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:CANModule.Views"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:cons="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
Background="White"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Height="850"
|
||||
Width="900">
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style BasedOn="{StaticResource DialogUserManageStyle}"
|
||||
TargetType="Window" />
|
||||
</prism:Dialog.WindowStyle>
|
||||
<i:Interaction.Triggers>
|
||||
<i:EventTrigger EventName="Loaded">
|
||||
<i:InvokeCommandAction Command="{Binding LoadCommand}" />
|
||||
</i:EventTrigger>
|
||||
</i:Interaction.Triggers>
|
||||
<UserControl.Resources>
|
||||
<cons:HexConverter x:Key="HexConverter" />
|
||||
</UserControl.Resources>
|
||||
<GroupBox Padding="0,0,0,0"
|
||||
>
|
||||
<GroupBox.Header>
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="CAN手动设置"
|
||||
Foreground="White"
|
||||
VerticalAlignment="Center"
|
||||
Margin="5,0,10,0" />
|
||||
|
||||
</Grid>
|
||||
</GroupBox.Header>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- CAN卡连接 -->
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="5"
|
||||
Grid.Row="0">
|
||||
<Button Content="打开设备"
|
||||
Command="{Binding ConnectCanCommand}"
|
||||
Margin="5" />
|
||||
<Button Content="关闭设备"
|
||||
Command="{Binding CloseCanCommand}"
|
||||
Margin="5" />
|
||||
<Button Content="清空循环发送报文"
|
||||
Command="{Binding ClearCycleCommand}"
|
||||
Margin="5" />
|
||||
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="5"
|
||||
Grid.Row="1">
|
||||
<Label Content="选择通道初始化(选择前先打开设备)"
|
||||
VerticalAlignment="Center" />
|
||||
<ComboBox SelectedItem="{Binding SelectedChannel}"
|
||||
ItemsSource="{Binding ChannelList}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MinWidth="80"
|
||||
Margin="5"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<TreeView Grid.Row="2">
|
||||
<!-- 监视选项 -->
|
||||
<TreeViewItem Header="监视">
|
||||
<DataGrid x:Name="表格"
|
||||
Width="550"
|
||||
Height="350"
|
||||
VirtualizingPanel.IsVirtualizing="True"
|
||||
IsReadOnly="True"
|
||||
AutoGenerateColumns="False"
|
||||
ItemsSource="{Binding CanMessageList, Mode=OneWay}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="通道" Binding="{Binding 通道}" Width="90" />
|
||||
<DataGridTextColumn Header="报文ID" Binding="{Binding 报文ID, Converter={StaticResource HexConverter}}" Width="150" />
|
||||
<DataGridTextColumn Header="长度" Binding="{Binding 长度}" Width="90" />
|
||||
<DataGridTextColumn Header="时间戳" Binding="{Binding 时间戳}" Width="Auto" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</TreeViewItem>
|
||||
|
||||
<!-- DBC 配置 -->
|
||||
<TreeViewItem Header="DBC">
|
||||
<Label Content="请注意,要在断开CAN卡的情况下才能操作DBC!"
|
||||
Foreground="Red" />
|
||||
<Button Content="卸载当前通道DBC"
|
||||
Command="{Binding UpLoadDBCCommand}" />
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="更换DBC"
|
||||
Command="{Binding ChangeDBCCommand}" />
|
||||
<Label Content="DBC文件路径"
|
||||
VerticalContentAlignment="Center" />
|
||||
<TextBox Text="{Binding DBCPath,Mode=TwoWay}"
|
||||
MinWidth="100"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="加载DBC"
|
||||
Command="{Binding LoadDBCCommand}" />
|
||||
<Label Content="通道(从0开始)"
|
||||
VerticalContentAlignment="Center" />
|
||||
<TextBox Text="{Binding DBCChannel,Mode=TwoWay}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MinWidth="100"
|
||||
VerticalAlignment="Center" />
|
||||
<CheckBox Margin="5 0 0 0"
|
||||
IsChecked="{Binding IsSaved}" />
|
||||
<Label Content="加载记录保存"
|
||||
VerticalContentAlignment="Center" />
|
||||
</StackPanel>
|
||||
<DataGrid ItemsSource="{Binding DBCAutoLoadList}"
|
||||
Width="650"
|
||||
IsReadOnly="True"
|
||||
AutoGenerateColumns="False"
|
||||
SelectedItem="{Binding SelectedDcAutoLoad}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="通道"
|
||||
Binding="{Binding DBCChannel}"
|
||||
Width="60" />
|
||||
<DataGridTextColumn Header="文件路径"
|
||||
Binding="{Binding DBCFilePath}"
|
||||
Width="*" />
|
||||
</DataGrid.Columns>
|
||||
<DataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="删除"
|
||||
Foreground="Red"
|
||||
Command="{Binding DeleteDcAutoLoadCommand}" />
|
||||
</ContextMenu>
|
||||
</DataGrid.ContextMenu>
|
||||
</DataGrid>
|
||||
</TreeViewItem>
|
||||
|
||||
<!-- DBC报文发送 -->
|
||||
<TreeViewItem Header="DBC报文发送">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="通道(从0开始)"
|
||||
VerticalContentAlignment="Center" />
|
||||
<ComboBox SelectedValue="{Binding MessageSendChannel}"
|
||||
SelectedValuePath="Content"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MinWidth="60"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,5,5,5">
|
||||
<ComboBoxItem Content="0"/>
|
||||
<ComboBoxItem Content="1"/>
|
||||
<ComboBoxItem Content="2"/>
|
||||
<ComboBoxItem Content="3"/>
|
||||
</ComboBox>
|
||||
<Button Content="加载报文"
|
||||
Command="{Binding LoadMessageCommand}"
|
||||
Margin="5" />
|
||||
</StackPanel>
|
||||
<DockPanel>
|
||||
<Label Content="报文" />
|
||||
<ComboBox MinWidth="100"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
SelectedItem="{Binding SelectedMessage}"
|
||||
ItemsSource="{Binding Message}" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="信号" />
|
||||
<ComboBox SelectedItem="{Binding SelectedSignal}"
|
||||
ItemsSource="{Binding Signal}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MinWidth="100" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="信号值" />
|
||||
<TextBox Text="{Binding SignalValue}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MinWidth="100" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="循环发送时间间隔(ms)(0为单次发送)" />
|
||||
<TextBox Text="{Binding CycleSendInterval}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MinWidth="100" />
|
||||
</DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="设置信号并发送"
|
||||
Command="{Binding SetAndSendMessageCommand}" />
|
||||
<CheckBox Content="直接发送"
|
||||
IsChecked="{Binding IsDirectlySend}" />
|
||||
</StackPanel>
|
||||
</TreeViewItem>
|
||||
|
||||
<!-- 自定义报文发送 -->
|
||||
<TreeViewItem Header="自定义报文发送">
|
||||
<DockPanel>
|
||||
<Label Content="通道" />
|
||||
<ComboBox MinWidth="100"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
SelectedItem="{Binding AIdxChn}"
|
||||
ItemsSource="{Binding APP_CHANNEL}" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="ID" />
|
||||
<TextBox MinWidth="100"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding AID, Converter={StaticResource HexConverter}, Mode=TwoWay}" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="DLC" />
|
||||
<TextBox MinWidth="200"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding ADLC}" />
|
||||
<CheckBox Content="自动设定"
|
||||
IsChecked="{Binding AutoSetting}" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="报文" />
|
||||
<TextBox MinWidth="200"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding CustomMessage}" />
|
||||
</DockPanel>
|
||||
<DockPanel>
|
||||
<Label Content="循环发送时间间隔(ms)(0为单次发送)" />
|
||||
<TextBox Text="{Binding CustomSendCycleInterval}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
MinWidth="100" />
|
||||
</DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox Content="发送帧"
|
||||
IsChecked="{Binding AIsTx}" />
|
||||
<CheckBox Content="扩展帧"
|
||||
IsChecked="{Binding AIsExt}" />
|
||||
<CheckBox Content="远程帧"
|
||||
IsChecked="{Binding AIsRemote}" />
|
||||
<CheckBox Content="FD"
|
||||
IsChecked="{Binding AIsFD}" />
|
||||
<CheckBox Content="BRS"
|
||||
IsChecked="{Binding AIsBRS}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="发送"
|
||||
Command="{Binding SendCustomMessageCommand}" />
|
||||
<!--<CheckBox Margin="10"
|
||||
Content="更新到数据库"
|
||||
IsChecked="{Binding UpdateToDB}" />-->
|
||||
</StackPanel>
|
||||
</TreeViewItem>
|
||||
|
||||
<!-- 报文录制 -->
|
||||
<TreeViewItem Header="报文录制">
|
||||
<DockPanel>
|
||||
<Label Content="路径(开始录制时使用)"
|
||||
VerticalContentAlignment="Center" />
|
||||
<TextBox MinWidth="100"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding BLFPath}"
|
||||
VerticalContentAlignment="Center" />
|
||||
<Button Content="选择路径"
|
||||
Margin="10 0"
|
||||
Command="{Binding SelectBLFPathCommand}" />
|
||||
<Button Content="保存路径"
|
||||
Command="{Binding SaveBLFPathCommand}" />
|
||||
</DockPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="开始录制"
|
||||
Command="{Binding StartTranscribeCommand}" />
|
||||
<Button Content="结束录制"
|
||||
Margin="10 0"
|
||||
Command="{Binding EndTranscribeCommand}" />
|
||||
</StackPanel>
|
||||
</TreeViewItem>
|
||||
</TreeView>
|
||||
</Grid>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
35
CANModule/Views/ZLGCANFDView.xaml.cs
Normal file
35
CANModule/Views/ZLGCANFDView.xaml.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace CANModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// ZLGCANFDView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class ZLGCANFDView : UserControl
|
||||
{
|
||||
public ZLGCANFDView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Command/Command.csproj
Normal file
13
Command/Command.csproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
268
Command/CommandApplication.cs
Normal file
268
Command/CommandApplication.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
[ACPCommand]
|
||||
public static class CommandApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// 打开外部应用程序
|
||||
/// </summary>
|
||||
/// <param name="route">程序路径</param>
|
||||
/// <param name="waitShutdown">是否等待关闭</param>
|
||||
/// <param name="AdminRun">是否请求管理员运行</param>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 使用默认方式打开一个文件
|
||||
/// </summary>
|
||||
/// <param name="route">文件路径</param>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化python引擎
|
||||
/// </summary>
|
||||
/// <param name="py路径">Python解释器路径</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行python脚本
|
||||
/// </summary>
|
||||
/// <param name="路径">Python脚本路径</param>
|
||||
/// <returns>脚本执行结果</returns>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放python引擎
|
||||
/// </summary>
|
||||
/// <param name="py">Python解释器路径(可选,用于验证)</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
261
Command/CommandArray.cs
Normal file
261
Command/CommandArray.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
[ACPCommand]
|
||||
public static class CommandArray
|
||||
{
|
||||
#region 数组操作
|
||||
/// <summary>
|
||||
/// 从数组中获取指定位置的元素。
|
||||
/// </summary>
|
||||
/// <param name="Array">要索引的数组。</param>
|
||||
/// <param name="Index">要获取的元素的位置。</param>
|
||||
/// <returns>指定位置的元素。</returns>
|
||||
public static object IndexArray(object Array, int Index)
|
||||
{
|
||||
return ((dynamic)Array)[Index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置数组中指定位置的元素为给定值。
|
||||
/// </summary>
|
||||
/// <param name="Array">要设置元素的数组。</param>
|
||||
/// <param name="Location">要设置的元素的位置。</param>
|
||||
/// <param name="Value">要设置的值。</param>
|
||||
/// <returns>修改后的数组。</returns>
|
||||
public static object SetArrayElement(object Array, int Location, object Value)
|
||||
{
|
||||
((dynamic)Array)[Location] = (dynamic)Value;
|
||||
return Array;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将给定数组的长度设置为指定值,并返回一个新的数组。
|
||||
/// 如果指定的长度小于原始数组的长度,则截取原始数组的一部分作为新数组。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要设置长度的数组。</param>
|
||||
/// <param name="Length">新数组的长度。</param>
|
||||
/// <returns>新的数组,或者原始数组(如果输入参数不是数组类型)。</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在指定位置插入元素到数组中。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要插入元素的数组。</param>
|
||||
/// <param name="Location">要插入元素的位置。</param>
|
||||
/// <param name="InsertElement">要插入的元素。</param>
|
||||
/// <returns>插入元素后的数组。</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中删除指定的元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要删除元素的数组。</param>
|
||||
/// <param name="DeleteElement">要从数组中删除的元素。</param>
|
||||
/// <returns>删除元素后的数组。</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中删除指定位置的元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要删除元素的数组。</param>
|
||||
/// <param name="DeleteElementLocation">要从数组中删除的元素的位置。</param>
|
||||
/// <returns>删除元素后的数组。</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 在数组中寻找第一个匹配的元素,并返回其索引位置。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要搜索的数组。</param>
|
||||
/// <param name="QueryElement">要寻找的元素。</param>
|
||||
/// <returns>要寻找的元素在数组中的第一个匹配项的索引;如果未找到匹配项,则为 -1。</returns>
|
||||
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);
|
||||
}
|
||||
/// <summary>
|
||||
/// 替换数组中第一个匹配的元素为指定的新元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要进行替换操作的数组。</param>
|
||||
/// <param name="FirstMatchElement">要替换的元素。</param>
|
||||
/// <param name="ReplaceElement">要替换为的新元素。</param>
|
||||
/// <returns>替换后的数组。</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将数组中所有匹配的元素替换为指定的新元素。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要进行替换操作的数组。</param>
|
||||
/// <param name="MatchElement">要替换的元素。</param>
|
||||
/// <param name="ReplaceElement">要替换为的新元素。</param>
|
||||
/// <returns>替换后的数组。</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 反转数组中元素的顺序。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要反转的数组。</param>
|
||||
/// <returns>反转后的数组。</returns>
|
||||
public static object ArrayReverse(object Arr)
|
||||
{
|
||||
var temp = Enumerable.ToList((dynamic)Arr);
|
||||
temp.Reverse();
|
||||
if (Arr is Array)
|
||||
{
|
||||
return Enumerable.ToArray(temp);
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
/// <summary>
|
||||
/// 对数组进行排序。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要排序的数组。</param>
|
||||
/// <returns>排序后的数组。</returns>
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取数组的长度。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要获取长度的数组。</param>
|
||||
/// <returns>数组的长度。</returns>
|
||||
public static int GetArrayLength(object Arr)
|
||||
{
|
||||
if (Arr is Array)
|
||||
{
|
||||
return ((Array)Arr).Length;
|
||||
}
|
||||
return ((ICollection)Arr).Count;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 将对象转换为数组。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要转换的对象。</param>
|
||||
/// <returns>转换后的数组。</returns>
|
||||
public static object objectConvertToArray(object Arr)
|
||||
{
|
||||
return Enumerable.ToArray((dynamic)Arr);
|
||||
}
|
||||
/// <summary>
|
||||
/// 将对象转换为列表。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要转换的对象。</param>
|
||||
/// <returns>转换后的列表。</returns>
|
||||
public static object objectConvertToList(object Arr)
|
||||
{
|
||||
return Enumerable.ToList((dynamic)Arr);
|
||||
}
|
||||
/// <summary>
|
||||
/// 从数组中截取指定长度的子数组。
|
||||
/// </summary>
|
||||
/// <param name="Arr">要截取的数组。</param>
|
||||
/// <param name="StartLocation">截取开始的位置。</param>
|
||||
/// <param name="Length">要截取的长度。</param>
|
||||
/// <returns>截取得到的子数组。</returns>
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
473
Command/CommandMath.cs
Normal file
473
Command/CommandMath.cs
Normal file
@@ -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
|
||||
}
|
||||
|
||||
[ACPCommand]
|
||||
public static class CommandMath
|
||||
{
|
||||
#region 返回值是Double类型数据
|
||||
/// <summary>
|
||||
/// 两数相加(Param1 + Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:11</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamAdd(double Param1, double Param2)
|
||||
{
|
||||
return Param1 + Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相减(Param1 - Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:7</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamReduce(double Param1, double Param2)
|
||||
{
|
||||
return Param1 - Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相乘(Param1 * Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:18</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamMult(double Param1, double Param2)
|
||||
{
|
||||
return Param1 * Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相除(Param1 / Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <param name="Param3">传入值:保留几位小数</param>
|
||||
/// <example>Param1:10 Param2:3 Param3:3 返回值:3.333</example>
|
||||
/// <example>Param1:11 Param2:3 Param3:2 返回值:3.67</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamDivide(double Param1, double Param2, int Param3)
|
||||
{
|
||||
return Math.Round((Param1 / Param2), Param3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 两数相除求余(Param1 % Param2)
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数2</param>
|
||||
/// <example>Param1:9 Param2:2 返回值:1</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamRemainder(double Param1, double Param2)
|
||||
{
|
||||
return Param1 % Param2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数开根号
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:16 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamSquareRoot(double Param1)
|
||||
{
|
||||
return Math.Sqrt(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的幂运算
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <param name="Param2">传入值:实数1需要做几次方</param>
|
||||
/// <example>Param1:3 Param2:2 返回值:9</example>
|
||||
/// <example>Param1:2 Param2:3 返回值:8</example>
|
||||
/// <example>Param1:5 Param2:4 返回值:625</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamPow(double Param1, double Param2)
|
||||
{
|
||||
return Math.Pow(Param1, Param2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的绝对值
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:-3.7 返回值:3.7</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamAbs(double Param1)
|
||||
{
|
||||
return Math.Abs(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的向下取整
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:3.7 返回值:3</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamFloor(double Param1)
|
||||
{
|
||||
return Math.Floor(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实数的向上取整
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数1</param>
|
||||
/// <example>Param1:3.7 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static double ParamCeiling(double Param1)
|
||||
{
|
||||
return Math.Ceiling(Param1);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 返回值是String类型的数据
|
||||
/// <summary>
|
||||
/// A与B转换为double后比较,返回A比较B的结果
|
||||
/// </summary>
|
||||
/// <param name="a">比较数1</param>
|
||||
/// <param name="b">比较数2</param>
|
||||
/// <param name="比较方法">比较方法</param>
|
||||
/// <returns>返回A比较B</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加法指令,转换为double后返回o1+o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1+o2</returns>
|
||||
public static string TwoNumberAdd(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) + Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 减法指令,转换为double后返回o1-o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1-o2</returns>
|
||||
public static string TwoNumberReduce(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) - Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 乘法指令,转换为double后返回o1*o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1*o2</returns>
|
||||
public static string TwoNumberMult(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) * Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 除法指令,转换为double后返回o1/o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1/o2</returns>
|
||||
public static string TwoNumberDivide1(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) / Convert.ToDecimal(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 转换为double后返回o1/o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <param name="o3">保留几位小数</param>
|
||||
/// <returns>返回o1/o2</returns>
|
||||
public static string TwoNumberDivide2(string o1, string o2, int o3)
|
||||
{
|
||||
return (Math.Round((Convert.ToDecimal(o1) / Convert.ToDecimal(o2)), o3)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 返回o1%o2
|
||||
/// </summary>
|
||||
/// <param name="o1">运算数1</param>
|
||||
/// <param name="o2">运算数2</param>
|
||||
/// <returns>返回o1%o2</returns>
|
||||
public static string TwoNumberRemainder(string o1, string o2)
|
||||
{
|
||||
return (Convert.ToDecimal(o1) % (Convert.ToDecimal(o2))).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算两个数字的平方。
|
||||
/// </summary>
|
||||
/// <param name="o1">第一个数字的字符串表示。</param>
|
||||
/// <param name="o2">第二个数字的字符串表示。</param>
|
||||
/// <example>Param1:3 Param2:2 返回值:9</example>
|
||||
/// <example>Param1:2 Param2:3 返回值:8</example>
|
||||
/// <example>Param1:5 Param2:4 返回值:625</example>
|
||||
/// <returns>返回两个数字的平方的字符串表示。</returns>
|
||||
public static string NumberPow(string o1, string o2)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算它们的平方
|
||||
// 最后将结果转换为字符串并返回
|
||||
return Math.Pow(Convert.ToDouble(o1), Convert.ToDouble(o2)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的平方根。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算平方根的数字的字符串表示。</param>
|
||||
/// <returns>返回输入数字的平方根的字符串表示。</returns>
|
||||
public static string NumberSqrt(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其平方根。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Sqrt(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的绝对值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算绝对值的数字的字符串表示。</param>
|
||||
/// <returns>返回输入数字的绝对值的字符串表示。</returns>
|
||||
public static string NumberAbs(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其绝对值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Abs(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个角度的正弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算正弦值的角度的字符串表示(单位为弧度)。</param>
|
||||
/// <returns>返回输入角度的正弦值的字符串表示。</returns>
|
||||
public static string NumberSin(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其正弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Sin(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个角度的余弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算余弦值的角度的字符串表示(单位为弧度)。</param>
|
||||
/// <returns>返回输入角度的余弦值的字符串表示。</returns>
|
||||
public static string NumberCos(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其余弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Cos(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个角度的正切值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算正切值的角度的字符串表示(单位为弧度)。</param>
|
||||
/// <returns>返回输入角度的正切值的字符串表示。</returns>
|
||||
public static string NumberTan(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其正切值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Tan(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的反正弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算反正弦值的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的反正弦值的字符串表示(单位为弧度)。</returns>
|
||||
public static string NumberASin(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其反正弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Asin(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的反余弦值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算反余弦值的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的反余弦值的字符串表示(单位为弧度)。</returns>
|
||||
public static string NumberACos(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其反余弦值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Acos(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的反正切值。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算反正切值的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的反正切值的字符串表示(单位为弧度)。</returns>
|
||||
public static string NumberATan(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其反正切值。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Atan(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算 e 的给定次幂。
|
||||
/// </summary>
|
||||
/// <param name="o1">e 的指数的字符串表示。</param>
|
||||
/// <returns>返回 e 的给定次幂的字符串表示。</returns>
|
||||
public static string eIndex(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算 e 的给定次幂。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Exp(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数的自然对数。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算自然对数的数的字符串表示。</param>
|
||||
/// <returns>返回输入数的自然对数的字符串表示。</returns>
|
||||
public static string NumberNaturalLogarithm(string o1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其自然对数。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Log(Convert.ToDouble(o1)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 计算一个数在指定基数下的对数。
|
||||
/// </summary>
|
||||
/// <param name="o1">要计算对数的数的字符串表示。</param>
|
||||
/// <param name="Base">对数的基数的字符串表示。</param>
|
||||
/// <returns>返回输入数在指定基数下的对数的字符串表示。</returns>
|
||||
public static string NumberLogarithm(string o1, string Base)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并计算其在指定基数下的对数。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Log(Convert.ToDouble(o1), Convert.ToDouble(Base)).ToString();
|
||||
}
|
||||
/// <summary>
|
||||
/// 对输入数字进行四舍五入取整。
|
||||
/// </summary>
|
||||
/// <param name="n1">要进行取整操作的数字的字符串表示。</param>
|
||||
/// <returns>返回经过四舍五入取整后的结果的字符串表示。</returns>
|
||||
public static string NumberRoundToNearest(string n1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并进行四舍五入取整。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Round(Convert.ToDouble(n1)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对输入数字进行向上舍入取整。
|
||||
/// </summary>
|
||||
/// <param name="n1">要进行取整操作的数字的字符串表示。</param>
|
||||
/// <returns>返回经过向上舍入取整后的结果的字符串表示。</returns>
|
||||
public static string NumberRoundUp(string n1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并进行向上舍入取整。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Ceiling(Convert.ToDouble(n1)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对输入数字进行向下舍入取整。
|
||||
/// </summary>
|
||||
/// <param name="n1">要进行取整操作的数字的字符串表示。</param>
|
||||
/// <returns>返回经过向下舍入取整后的结果的字符串表示。</returns>
|
||||
public static string NumberRoundDown(string n1)
|
||||
{
|
||||
// 将输入的字符串转换为双精度浮点数,并进行向下舍入取整。
|
||||
// 最后将结果转换为字符串并返回。
|
||||
return Math.Floor(Convert.ToDouble(n1)).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取圆周率π的字符串表示。
|
||||
/// </summary>
|
||||
/// <returns>返回圆周率π的字符串表示。</returns>
|
||||
public static string PI()
|
||||
{
|
||||
// 返回圆周率π的字符串表示。
|
||||
return Math.PI.ToString();
|
||||
}
|
||||
|
||||
//public static Random random1 = new Random();
|
||||
/// <summary>
|
||||
/// 生成一个随机整数。
|
||||
/// </summary>
|
||||
/// <returns>返回生成的随机整数的字符串表示。</returns>
|
||||
public static string RandomNumber_Int()
|
||||
{
|
||||
Random random = new Random();
|
||||
// 使用随机数生成器生成一个随机整数,并将其转换为字符串表示。
|
||||
return random.Next().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个指定范围内的随机整数。
|
||||
/// </summary>
|
||||
/// <param name="最小">随机数生成范围的最小值。</param>
|
||||
/// <param name="最大">随机数生成范围的最大值(不包含)。</param>
|
||||
/// <returns>返回生成的指定范围内的随机整数的字符串表示。</returns>
|
||||
public static string RandomNumber_RangeInt(int 最小, int 最大)
|
||||
{
|
||||
Random random = new Random();
|
||||
// 使用随机数生成器生成一个指定范围内的随机整数,并将其转换为字符串表示。
|
||||
return random.Next(最小, 最大).ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成一个随机小数。
|
||||
/// </summary>
|
||||
/// <returns>返回生成的随机小数的字符串表示。</returns>
|
||||
public static string RandomNumber_Decimal()
|
||||
{
|
||||
Random random = new Random();
|
||||
// 使用随机数生成器生成一个随机小数,并将其转换为字符串表示。
|
||||
return random.NextDouble().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算给定算式的结果。
|
||||
/// </summary>
|
||||
/// <param name="s">要计算的算式的字符串表示。</param>
|
||||
/// <returns>返回计算结果的字符串表示。</returns>
|
||||
public static string FormulaCalculation(string s)
|
||||
{
|
||||
// 创建一个 DataTable 实例来进行算式的计算。
|
||||
DataTable dataTable = new DataTable();
|
||||
// 使用 DataTable 的 Compute 方法计算给定的算式,并将结果转换为字符串表示。
|
||||
return dataTable.Compute(s, "").ToString();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
153
Command/CommandRadixChange.cs
Normal file
153
Command/CommandRadixChange.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
[ACPCommand]
|
||||
public static class CommandRadixChange
|
||||
{
|
||||
#region 进制转换
|
||||
/// <summary>
|
||||
/// 二进制 转换成 八进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"101010" 返回值:"52"</example>
|
||||
/// <returns></returns>
|
||||
public static string TwoRadix_ConvertTo_EightRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 2),8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二进制 转换成 十进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"101010" 返回值:"42"</example>
|
||||
/// <returns></returns>
|
||||
public static string TwoRadix_ConvertTo_TenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 二进制 转换成 十六进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"101010" 返回值:"2A"</example>
|
||||
/// <returns></returns>
|
||||
public static string TwoRadix_ConvertTo_SixteenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 2), 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 八进制 转换成 二进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"88" 返回值:"1001000"</example>
|
||||
/// <returns></returns>
|
||||
public static string EightRadix_ConvertTo_TwoRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 8), 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 八进制 转换成 十进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"88" 返回值:"72"</example>
|
||||
/// <returns></returns>
|
||||
public static double EightRadix_ConvertTo_TenRadix(string Param1)
|
||||
{
|
||||
double Value = Convert.ToInt32(Param1, 8);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 八进制 转换成 十六进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"88" 返回值:"48"</example>
|
||||
/// <returns></returns>
|
||||
public static string EightRadix_ConvertTo_SixteenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 8), 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十进制 转换成 二进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:60 返回值:"111100"</example>
|
||||
/// <returns></returns>
|
||||
public static string TenRadix_ConvertTo_TwoRadix(int Param1)
|
||||
{
|
||||
return Convert.ToString(Param1, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十进制 转换成 八进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:60 返回值:"74"</example>
|
||||
/// <returns></returns>
|
||||
public static string TenRadix_ConvertTo_EightRadix(int Param1)
|
||||
{
|
||||
return Convert.ToString(Param1, 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十进制 转换成 十六进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:60 返回值:"3C"</example>
|
||||
/// <returns></returns>
|
||||
public static string TenRadix_ConvertTo_SixteenRadix(int Param1)
|
||||
{
|
||||
return Convert.ToString(Param1, 16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十六进制 转换成 二进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"6F" 返回值:"1101111"</example>
|
||||
/// <returns></returns>
|
||||
public static string SixteenRadix_ConvertTo_Radix2(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 16), 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十六进制 转换成 八进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"6F" 返回值:"157"</example>
|
||||
/// <returns></returns>
|
||||
public static string SixteenRadix_ConvertTo_EightRadix8(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 16), 8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 十六进制 转换成 十进制
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符类型</param>
|
||||
/// <example>Param1:"6F" 返回值:"111"</example>
|
||||
/// <returns></returns>
|
||||
public static string SixteenRadix_ConvertTo_TenRadix(string Param1)
|
||||
{
|
||||
return Convert.ToString(Convert.ToInt32(Param1, 16));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
196
Command/CommandStringProcessing.cs
Normal file
196
Command/CommandStringProcessing.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
[ACPCommand]
|
||||
public static class CommandStringProcessing
|
||||
{
|
||||
///<summary>
|
||||
///获取值
|
||||
///</summary>
|
||||
///<param name="obj">要获取的值。</param>
|
||||
///returns>获取值(字符串)。</returns>
|
||||
public static string GetValue(object obj)
|
||||
{
|
||||
return Convert.ToString(obj) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在原字符串中查找指定字符的位置。
|
||||
/// </summary>
|
||||
/// <param name="str">要查找的字符串。</param>
|
||||
/// <param name="findStr">要查找的字符。</param>
|
||||
/// <returns>字符在字符串中的位置,如果未找到则返回 -1。</returns>
|
||||
public static int FindStr(string str, string findStr)
|
||||
{
|
||||
return str.IndexOf(findStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将原字符串中的指定字符替换为新字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要替换的字符串。</param>
|
||||
/// <param name="ReplaceStr">要被替换的字符。</param>
|
||||
/// <param name="ReplacedStr">替换后的新字符。</param>
|
||||
/// <returns>替换后的字符串。</returns>
|
||||
public static string ReplaceStr(string str, string ReplaceStr, string ReplacedStr)
|
||||
{
|
||||
return str.Replace(ReplaceStr, ReplacedStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从原字符串中移除指定位置开始的指定个数的字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要操作的字符串。</param>
|
||||
/// <param name="startLocation">开始移除字符的位置。</param>
|
||||
/// <param name="removeQty">要移除的字符个数。</param>
|
||||
/// <returns>移除后的字符串。</returns>
|
||||
public static string RemoveStr(string str, int startLocation, int removeQty)
|
||||
{
|
||||
return str.Remove(startLocation, removeQty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在原字符串的指定位置插入新字符串。
|
||||
/// </summary>
|
||||
/// <param name="str">要操作的字符串。</param>
|
||||
/// <param name="startLocation">要插入字符串的位置。</param>
|
||||
/// <param name="newStr">要插入的字符串。</param>
|
||||
/// <returns>插入后的字符串。</returns>
|
||||
public static string InsertStr(string str, int startLocation, string newStr)
|
||||
{
|
||||
return str.Insert(startLocation, newStr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从原字符串中截取指定位置开始的指定个数的字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要操作的字符串。</param>
|
||||
/// <param name="startLocation">要截取的起始位置。</param>
|
||||
/// <param name="qty">要截取的字符个数。</param>
|
||||
/// <returns>截取后的字符串。</returns>
|
||||
public static string CaptureStr(string str, int startLocation, int qty)
|
||||
{
|
||||
return str.Substring(startLocation, qty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为小写形式。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换为小写后的字符串。</returns>
|
||||
public static string StrConvertToLower(string str)
|
||||
{
|
||||
return str.ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为大写形式。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换为大写后的字符串。</returns>
|
||||
public static string StrConvertToUpper(string str)
|
||||
{
|
||||
return str.ToUpper();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字符串的长度。
|
||||
/// </summary>
|
||||
/// <param name="str">要获取长度的字符串。</param>
|
||||
/// <returns>字符串的长度。</returns>
|
||||
public static int GetStrLength(string str)
|
||||
{
|
||||
return str.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除字符串两端的空白字符。
|
||||
/// </summary>
|
||||
/// <param name="str">要去除空白字符的字符串。</param>
|
||||
/// <returns>去除空白字符后的字符串。</returns>
|
||||
public static string StrRemoveLeadingAndTrailingWhitespaces(string str)
|
||||
{
|
||||
return str.Trim();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为字符串,使用 UTF-8 编码。
|
||||
/// </summary>
|
||||
/// <param name="arr">要转换的字节数组。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayToStr_UTF8(byte[] arr)
|
||||
{
|
||||
return Encoding.UTF8.GetString(arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为字符串,使用 GB18030 编码。
|
||||
/// </summary>
|
||||
/// <param name="arr">要转换的字节数组。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayToStr_GB18030(byte[] arr)
|
||||
{
|
||||
return Encoding.GetEncoding("gb18030").GetString(arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为字符串,使用自选的编码类型。
|
||||
/// </summary>
|
||||
/// <param name="arr">要转换的字节数组。</param>
|
||||
/// <param name="encodingType">要使用的编码类型。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayToStr_SelfSelectedEncoding(byte[] arr, string encodingType)
|
||||
{
|
||||
return Encoding.GetEncoding(encodingType).GetString(arr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节数组转换为16进制字符串
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字节数组。</param>
|
||||
/// <returns>转换后的字符串。</returns>
|
||||
public static string ByteArrayTo_HexStr(byte[] str)
|
||||
{
|
||||
return Convert.ToInt32(str.ToString(),2).ToString("X2");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为字节数组,使用 UTF-8 编码。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换后的字节数组。</returns>
|
||||
public static byte[] StrToByteArray_UTF8(string str)
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(str);
|
||||
}
|
||||
/// <summary>
|
||||
/// 将字符串转换为字节数组,使用 GB18030 编码。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <returns>转换后的字节数组。</returns>
|
||||
public static byte[] StrToByteArray_GB18030(string str)
|
||||
{
|
||||
return Encoding.GetEncoding("gb18030").GetBytes(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为字节数组,使用自选的编码类型。
|
||||
/// </summary>
|
||||
/// <param name="str">要转换的字符串。</param>
|
||||
/// <param name="encodingType">要使用的编码类型。</param>
|
||||
/// <returns>转换后的字节数组。</returns>
|
||||
public static byte[] StrToByteArray_SelfSelectedEncoding(string str, string encodingType)
|
||||
{
|
||||
return Encoding.GetEncoding(encodingType).GetBytes(str);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
463
Command/CommandSystem.cs
Normal file
463
Command/CommandSystem.cs
Normal file
@@ -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
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class CommandSystem
|
||||
{
|
||||
#region 类型转换
|
||||
/// <summary>
|
||||
/// int转化为double
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:40 返回值:40</example>
|
||||
/// <returns></returns>
|
||||
public static double Int_ConvertTo_Double(int Param1)
|
||||
{
|
||||
return Convert.ToDouble(Param1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为Int16
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:4 返回值:4</example>
|
||||
/// <example>Param1:4.67 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static Int16 Double_ConvertTo_Int16(double Param1)
|
||||
{
|
||||
Int16 Value = Convert.ToInt16(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为int,即Int32
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:4 返回值:4</example>
|
||||
/// <example>Param1:4.67 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static int Double_ConvertTo_Int(double Param1)
|
||||
{
|
||||
int Value = Convert.ToInt32(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为Int64
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:4 返回值:4</example>
|
||||
/// <example>Param1:4.67 返回值:4</example>
|
||||
/// <returns></returns>
|
||||
public static Int64 Double_ConvertTo_Int64(double Param1)
|
||||
{
|
||||
Int64 Value = Convert.ToInt64(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// float转化为double
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:浮点数值</param>
|
||||
/// <example>Param1:4.0 返回值:4</example>
|
||||
/// <example>Param1:4.2 返回值:4.2</example>
|
||||
/// <returns></returns>
|
||||
public static double Float_ConvertTo_Double(float Param1)
|
||||
{
|
||||
double Value = Convert.ToDouble(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断string转化为int,转换成功返回 true,失败返回 false
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:true</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool BoolString_ConvertTo_Int(string Param1)
|
||||
{
|
||||
bool Value = int.TryParse(Param1, out int intValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为int
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:2</example>
|
||||
/// <returns></returns>
|
||||
public static int String_ConvertTo_Int(string Param1)
|
||||
{
|
||||
int Value = Convert.ToInt32(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// int转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:整型数值</param>
|
||||
/// <example>Param1:2 返回值:"2"</example>
|
||||
/// <returns></returns>
|
||||
public static string Int_ConvertTo_String(int Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断string转化为double,转换成功返回 true,失败返回 false
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:true</example>
|
||||
/// <example>Param1:"2.1" 返回值:true</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool BoolString_ConvertTo_Double(string Param1)
|
||||
{
|
||||
bool Value = double.TryParse(Param1, out double intValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为double
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"2" 返回值:2</example>
|
||||
/// <example>Param1:"2.1" 返回值:2.1</example>
|
||||
/// <returns></returns>
|
||||
public static double String_ConvertTo_Double(string Param1)
|
||||
{
|
||||
double Value = Convert.ToDouble(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// double转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:实数数值</param>
|
||||
/// <example>Param1:2 返回值:"2"</example>
|
||||
/// <example>Param1:2.1 返回值:"2.1"</example>
|
||||
/// <returns></returns>
|
||||
public static string Double_ConvertTo_String(double Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// float转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:浮点数值</param>
|
||||
/// <example>Param1:2.0 返回值:"2.0"</example>
|
||||
/// <example>Param1:2.1 返回值:"2.1"</example>
|
||||
/// <returns></returns>
|
||||
public static string Float_ConvertTo_String(float Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断string转化为datetime,转换成功返回 true,失败返回 false
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间字符串</param>
|
||||
/// <example>Param1:"2025-08-14" 返回值:true</example>
|
||||
/// <example>Param1:"2025-08-14 13:14:15" 返回值:true</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool BoolString_ConvertTo_Datetime(string Param1)
|
||||
{
|
||||
bool Value = DateTime.TryParse(Param1, out DateTime DateTimeValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为datetime
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间字符串</param>
|
||||
/// <example>Param1:"2025-08-14" 返回值:2025/8/14 0:00:00</example>
|
||||
/// <example>Param1:"2025-08-14 13:14:15" 返回值:2025/8/14 13:14:15</example>
|
||||
/// <returns></returns>
|
||||
public static DateTime String_ConvertTo_Datetime(string Param1)
|
||||
{
|
||||
DateTime Value = Convert.ToDateTime(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// datetime转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间</param>
|
||||
/// <example>Param1:2025/8/14 13:14:15 返回值:"2025/08/14 13:14:15"</example>
|
||||
/// <returns></returns>
|
||||
public static string Datetime_ConvertTo_String1(DateTime Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// datetime转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:时间</param>
|
||||
/// <param name="DateTimeFormat">传入值:字符串时间格式</param>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd" 返回值:"2025-08-14"</example>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd hh:mm:ss" 返回值:"2025-08-14 01:14:15"</example>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyy-MM-dd HH:mm:ss" 返回值:"2025-08-14 13:14:15"</example>
|
||||
/// <example>Param1:2025/8/14 13:14:15 DateTimeFormat:"yyyyMMdd" 返回值:"20250814"</example>
|
||||
/// <returns></returns>
|
||||
public static string Datetime_ConvertTo_String2(DateTime Param1,string DateTimeFormat)
|
||||
{
|
||||
string Value = Param1.ToString(DateTimeFormat);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// bool转化为string
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:布尔值</param>
|
||||
/// <example>Param1:true 返回值:"true"</example>
|
||||
/// <example>Param1:false 返回值:"false"</example>
|
||||
/// <returns></returns>
|
||||
public static string Bool_ConvertTo_String(bool Param1)
|
||||
{
|
||||
string Value = Convert.ToString(Param1);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// string转化为bool
|
||||
/// </summary>
|
||||
/// <param name="Param1">传入值:字符串</param>
|
||||
/// <example>Param1:"true" 返回值:true</example>
|
||||
/// <example>Param1:"false" 返回值:false</example>
|
||||
/// <example>Param1:"abc" 返回值:false</example>
|
||||
/// <returns></returns>
|
||||
public static bool String_ConvertTo_Bool(string Param1)
|
||||
{
|
||||
bool Value = bool.TryParse(Param1, out bool boolValue);
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将无符号整型转换为整型
|
||||
/// </summary>
|
||||
/// <param name="u"></param>
|
||||
/// <returns></returns>
|
||||
public static int Uint_ConvertTo_Int(uint u)
|
||||
{
|
||||
return (int)u;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将对象转换为字符串
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
public static string Object_ConvertTo_String(object obj)
|
||||
{
|
||||
return Convert.ToString(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串内容转换为byte
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的内容</param>
|
||||
/// <returns></returns>
|
||||
public static byte String_ConvertTo_Byte(string s)
|
||||
{
|
||||
return Convert.ToByte(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为short(int16)
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的内容</param>
|
||||
/// <returns></returns>
|
||||
public static short String_ConvertTo_Short(string s)
|
||||
{
|
||||
return Convert.ToInt16(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字符串转换为无符号整数。
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的字符串。</param>
|
||||
/// <returns>转换后的无符号整数。</returns>
|
||||
public static uint String_ConvertTo_Uint(string s)
|
||||
{
|
||||
return Convert.ToUInt32(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定进制的字符串转换为整数。
|
||||
/// </summary>
|
||||
/// <param name="s">要转换的字符串。</param>
|
||||
/// <param name="XRadix">源字符串的进制。只能为2,8,10,16</param>
|
||||
/// <returns>转换后的整数。</returns>
|
||||
public static int XRadixString_ConvertTo_Int(string s, int XRadix)
|
||||
{
|
||||
return Convert.ToInt32(s, XRadix);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region 逻辑操作
|
||||
/// <summary>
|
||||
/// 执行整数的异或操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个整数。</param>
|
||||
/// <param name="b">第二个整数。</param>
|
||||
/// <returns>异或操作结果。</returns>
|
||||
public static int XOR(int a, int b)
|
||||
{
|
||||
return a ^ b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行整数的位或操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个整数。</param>
|
||||
/// <param name="b">第二个整数。</param>
|
||||
/// <returns>位或操作结果。</returns>
|
||||
public static int BitwiseOR(int a, int b)
|
||||
{
|
||||
return a | b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行整数的位与操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个整数。</param>
|
||||
/// <param name="b">第二个整数。</param>
|
||||
/// <returns>位与操作结果。</returns>
|
||||
public static int BitwiseAND(int a, int b)
|
||||
{
|
||||
return a & b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将整数向左移动指定位数。
|
||||
/// </summary>
|
||||
/// <param name="a">要移动的整数。</param>
|
||||
/// <param name="BitwiseLeftShift">左移的位数。</param>
|
||||
/// <returns>移动后的结果。</returns>
|
||||
public static int LeftShift(int a, int BitwiseLeftShift)
|
||||
{
|
||||
return a << BitwiseLeftShift;
|
||||
}
|
||||
/// <summary>
|
||||
/// 将整数向右移动指定位数。
|
||||
/// </summary>
|
||||
/// <param name="a">要移动的整数。</param>
|
||||
/// <param name="BitwiseRightShift">右移的位数。</param>
|
||||
/// <returns>移动后的结果。</returns>
|
||||
public static int RightShift(int a, int BitwiseRightShift)
|
||||
{
|
||||
return a >> BitwiseRightShift;
|
||||
}
|
||||
/// <summary>
|
||||
/// 执行逻辑或操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个逻辑值。</param>
|
||||
/// <param name="b">第二个逻辑值。</param>
|
||||
/// <returns>逻辑或操作结果。</returns>
|
||||
public static bool LogicalOR(bool a, bool b)
|
||||
{
|
||||
return a || b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行逻辑与操作。
|
||||
/// </summary>
|
||||
/// <param name="a">第一个逻辑值。</param>
|
||||
/// <param name="b">第二个逻辑值。</param>
|
||||
/// <returns>逻辑与操作结果。</returns>
|
||||
public static bool LogicalAND(bool a, bool b)
|
||||
{
|
||||
return a && b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将字节的高低位对调。
|
||||
/// </summary>
|
||||
/// <param name="b">要对调的字节。</param>
|
||||
/// <returns>对调后的字节。</returns>
|
||||
public static byte ByteHighAndLowBitSwap(byte b)
|
||||
{
|
||||
return (byte)(((b & 0x0F) << 4) | ((b & 0xF0) >> 4));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 翻转字节数组中的元素顺序。
|
||||
/// </summary>
|
||||
/// <param name="b">要翻转的字节数组。</param>
|
||||
/// <returns>翻转后的字节数组。</returns>
|
||||
public static byte[] ByteArrayReverse(byte[] b)
|
||||
{
|
||||
return b.Reverse().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以毫秒为单位。</param>
|
||||
public static void Delay_ms(int time)
|
||||
{
|
||||
Thread.Sleep(time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以秒为单位。</param>
|
||||
public static void Delay_s(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以分钟为单位。</param>
|
||||
public static void Delay_min(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000 * 60);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以小时为单位。</param>
|
||||
public static void Delay_hour(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000 * 60 * 60);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前线程挂起指定的时间。
|
||||
/// </summary>
|
||||
/// <param name="time">要延时的时间,以天为单位。</param>
|
||||
public static void Delay_day(int time)
|
||||
{
|
||||
Thread.Sleep(time * 1000 * 60 * 60 * 24);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
95
Command/CommandTime.cs
Normal file
95
Command/CommandTime.cs
Normal file
@@ -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
|
||||
{
|
||||
|
||||
[ACPCommand]
|
||||
public static class CommandTime
|
||||
{
|
||||
#region 时间处理
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总毫秒数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总毫秒数的时间段。</param>
|
||||
/// <returns>返回时间段的总毫秒数。</returns>
|
||||
public static double GetTimePeriodMilliseconds(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总毫秒数。
|
||||
return TimePeriod.TotalMilliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总秒数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总秒数的时间段。</param>
|
||||
/// <returns>返回时间段的总秒数。</returns>
|
||||
public static double GetTimePeriodSeconds(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总秒数。
|
||||
return TimePeriod.TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总分钟数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总分钟数的时间段。</param>
|
||||
/// <returns>返回时间段的总分钟数。</returns>
|
||||
public static double GetTimePeriodMinutes(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总分钟数。
|
||||
return TimePeriod.TotalMinutes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总小时数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总小时数的时间段。</param>
|
||||
/// <returns>返回时间段的总小时数。</returns>
|
||||
public static double GetTimePeriodHours(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总小时数。
|
||||
return TimePeriod.TotalHours;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取给定时间段的总天数。
|
||||
/// </summary>
|
||||
/// <param name="TimePeriod">要获取总天数的时间段。</param>
|
||||
/// <returns>返回时间段的总天数。</returns>
|
||||
public static double GetTimePeriodDays(TimeSpan TimePeriod)
|
||||
{
|
||||
// 返回时间段的总天数。
|
||||
return TimePeriod.TotalDays;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算两个日期时间之间的时间差。
|
||||
/// </summary>
|
||||
/// <param name="date1">第一个日期时间。</param>
|
||||
/// <param name="date2">第二个日期时间。</param>
|
||||
/// <returns>返回两个日期时间之间的时间差。</returns>
|
||||
public static TimeSpan GetDateReduce(DateTime date1, DateTime date2)
|
||||
{
|
||||
// 返回第一个日期时间减去第二个日期时间的时间差。
|
||||
return date1 - date2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前系统时间。
|
||||
/// </summary>
|
||||
/// <returns>返回当前系统时间。</returns>
|
||||
public static DateTime GetNowTime()
|
||||
{
|
||||
// 返回当前系统时间。
|
||||
return DateTime.Now;
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
58
Command/Delay.cs
Normal file
58
Command/Delay.cs
Normal file
@@ -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
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class Delay
|
||||
{
|
||||
/// <summary>
|
||||
/// 等待_毫秒
|
||||
/// </summary>
|
||||
/// <param name="millisecond"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_ms(int millisecond, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay(millisecond, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待_秒
|
||||
/// </summary>
|
||||
/// <param name="second"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_s(float second, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay((int)second * 1000, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待_分钟
|
||||
/// </summary>
|
||||
/// <param name="minnute"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_m(float minnute, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay((int)minnute * 60 * 1000, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 等待_小时
|
||||
/// </summary>
|
||||
/// <param name="minnute"></param>
|
||||
/// <param name="ct"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task Delay_h(float minnute, CancellationToken ct)
|
||||
{
|
||||
await Task.Delay((int)minnute * 60 * 1000, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Common/Attributes/ADPCommandAttribute.cs
Normal file
32
Common/Attributes/ADPCommandAttribute.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// 标记可加载到指令集中的类或方法
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
|
||||
public sealed class ACPCommandAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 指令描述(用于工具提示)
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 指令分类(用于树形视图分组)
|
||||
/// </summary>
|
||||
public string Category { get; set; } = "默认分类";
|
||||
|
||||
public ACPCommandAttribute() { }
|
||||
|
||||
public ACPCommandAttribute(string description)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Common/Attributes/MonitorableAttribute.cs
Normal file
19
Common/Attributes/MonitorableAttribute.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Common.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class MonitorableAttribute : Attribute
|
||||
{
|
||||
public string? Description { get; set; }
|
||||
|
||||
public MonitorableAttribute(string? description = null)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Common/Common.csproj
Normal file
15
Common/Common.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ncalc" Version="1.3.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
|
||||
<PackageReference Include="System.IO.Ports" Version="11.0.0-preview.4.26230.115" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
112
Common/MiniDump.cs
Normal file
112
Common/MiniDump.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
104
Common/Tool/ExpressionEvaluator.cs
Normal file
104
Common/Tool/ExpressionEvaluator.cs
Normal file
@@ -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<string, object>? 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<string, object>) PreprocessExpression(
|
||||
string expression,
|
||||
Dictionary<string, object>? variables)
|
||||
{
|
||||
if (variables == null || variables.Count == 0)
|
||||
return (expression, variables ?? new Dictionary<string, object>());
|
||||
|
||||
// 生成变量名映射 (中文 -> 英文别名)
|
||||
var chineseToAlias = new Dictionary<string, string>();
|
||||
var aliasToValue = new Dictionary<string, object>();
|
||||
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<string, object>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
CurveModule/CurveModule.cs
Normal file
20
CurveModule/CurveModule.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
using CurveModule.Views;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CurveModule
|
||||
{
|
||||
public class CurveModule: IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterForNavigation<CurveRecallView>("CurveRecallView");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
20
CurveModule/CurveModule.csproj
Normal file
20
CurveModule/CurveModule.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Service\Service.csproj" />
|
||||
<ProjectReference Include="..\UIShare\UIShare.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OxyPlot.Wpf" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
381
CurveModule/ViewModels/CurveRecallViewModel.cs
Normal file
381
CurveModule/ViewModels/CurveRecallViewModel.cs
Normal file
@@ -0,0 +1,381 @@
|
||||
using Logger;
|
||||
using OxyPlot;
|
||||
using OxyPlot.Axes;
|
||||
using OxyPlot.Legends;
|
||||
using OxyPlot.Series;
|
||||
using Prism.Commands;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace CurveModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 曲线回读:加载 RecordView 导出的 CSV 文件,按 (MonitorName, Scope) 分组绘制曲线。
|
||||
/// </summary>
|
||||
public class CurveRecallViewModel : NavigateViewModelBase, IRegionMemberLifetime, IDisposable
|
||||
{
|
||||
#region 属性
|
||||
public bool KeepAlive => true;
|
||||
|
||||
public PlotModel Plot { get; }
|
||||
|
||||
/// <summary>已加载的曲线通道列表</summary>
|
||||
public ObservableCollection<CurveRecallChannelVM> Channels { get; } = new();
|
||||
|
||||
private CurveRecallChannelVM? _selectedChannel;
|
||||
public CurveRecallChannelVM? SelectedChannel
|
||||
{
|
||||
get => _selectedChannel;
|
||||
set => SetProperty(ref _selectedChannel, value);
|
||||
}
|
||||
|
||||
private string _statusMessage = "请加载 CSV 文件以回读曲线";
|
||||
public string StatusMessage
|
||||
{
|
||||
get => _statusMessage;
|
||||
set => SetProperty(ref _statusMessage, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
public ICommand LoadCsvCommand { get; }
|
||||
public ICommand ResetViewCommand { get; }
|
||||
public ICommand ClearCommand { get; }
|
||||
#endregion
|
||||
|
||||
#region 私有字段
|
||||
// 颜色调色盘(与 MonitorView 一致)
|
||||
private static readonly OxyColor[] _palette =
|
||||
{
|
||||
OxyColors.SteelBlue, OxyColors.IndianRed, OxyColors.SeaGreen,
|
||||
OxyColors.DarkOrange, OxyColors.MediumPurple, OxyColors.Goldenrod,
|
||||
OxyColors.Teal, OxyColors.Crimson, OxyColors.OliveDrab
|
||||
};
|
||||
private int _colorIndex;
|
||||
#endregion
|
||||
|
||||
public CurveRecallViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
Plot = BuildEmptyPlot();
|
||||
LoadCsvCommand = new DelegateCommand(OnLoadCsv);
|
||||
ResetViewCommand = new DelegateCommand(OnResetView);
|
||||
ClearCommand = new DelegateCommand(OnClear);
|
||||
}
|
||||
|
||||
#region 命令处理
|
||||
/// <summary>
|
||||
/// 打开 CSV 文件对话框,解析数据并按 (MonitorName, Scope) 分组绘制曲线。
|
||||
/// </summary>
|
||||
private void OnLoadCsv()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Filter = "CSV 文件 (*.csv)|*.csv|所有文件|*.*",
|
||||
Title = "选择要加载的监测数据 CSV 文件"
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
var records = ParseCsv(dlg.FileName);
|
||||
if (records.Count == 0)
|
||||
{
|
||||
StatusMessage = "CSV 文件为空或格式不正确";
|
||||
return;
|
||||
}
|
||||
|
||||
// 按 (MonitorName, Scope) 分组
|
||||
var groups = records.GroupBy(r => (r.MonitorName, r.Scope))
|
||||
.OrderBy(g => g.Key.MonitorName)
|
||||
.ThenBy(g => g.Key.Scope);
|
||||
|
||||
// 清空旧数据
|
||||
ClearPlotAndChannels();
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var color = _palette[_colorIndex % _palette.Length];
|
||||
_colorIndex++;
|
||||
|
||||
string displayName = string.IsNullOrEmpty(group.Key.Scope)
|
||||
? group.Key.MonitorName
|
||||
: $"{group.Key.MonitorName} [{group.Key.Scope}]";
|
||||
|
||||
var channel = new CurveRecallChannelVM
|
||||
{
|
||||
MonitorName = group.Key.MonitorName,
|
||||
Scope = group.Key.Scope,
|
||||
DisplayName = displayName,
|
||||
Color = color,
|
||||
IsDisplayed = true
|
||||
};
|
||||
|
||||
// 创建 LineSeries 并填充数据点
|
||||
channel.Series = new LineSeries
|
||||
{
|
||||
Title = displayName,
|
||||
Color = color,
|
||||
StrokeThickness = 1.5
|
||||
};
|
||||
|
||||
// 按时间排序
|
||||
var sortedPoints = group.OrderBy(r => r.CreateTime);
|
||||
foreach (var point in sortedPoints)
|
||||
{
|
||||
channel.Series.Points.Add(new DataPoint(point.CreateTime.ToOADate(), point.MonitorValue));
|
||||
}
|
||||
|
||||
channel.PropertyChanged += OnChannelPropertyChanged;
|
||||
|
||||
Plot.Series.Add(channel.Series);
|
||||
Channels.Add(channel);
|
||||
}
|
||||
|
||||
Plot.InvalidatePlot(true);
|
||||
StatusMessage = $"已加载 {Path.GetFileName(dlg.FileName)},共 {Channels.Count} 条曲线,{records.Count} 个数据点";
|
||||
LoggerHelper.Info($"曲线回读:加载 {dlg.FileName},{Channels.Count} 条曲线,{records.Count} 个数据点");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Error($"加载 CSV 失败:{ex.Message}");
|
||||
StatusMessage = $"加载失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnResetView()
|
||||
{
|
||||
Plot.ResetAllAxes();
|
||||
Plot.InvalidatePlot(false);
|
||||
StatusMessage = "视图已复原";
|
||||
}
|
||||
|
||||
private void OnClear()
|
||||
{
|
||||
ClearPlotAndChannels();
|
||||
Plot.InvalidatePlot(true);
|
||||
StatusMessage = "已清空,请重新加载 CSV 文件";
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CSV 解析
|
||||
/// <summary>
|
||||
/// 解析 RecordView 导出的 CSV 文件,返回监测记录列表。
|
||||
/// </summary>
|
||||
private static List<MonitorRecord> ParseCsv(string path)
|
||||
{
|
||||
var results = new List<MonitorRecord>();
|
||||
|
||||
using var sr = new StreamReader(path, Encoding.UTF8);
|
||||
|
||||
// 读取表头
|
||||
string? headerLine = sr.ReadLine();
|
||||
if (headerLine == null) return results;
|
||||
|
||||
var headers = ParseCsvLine(headerLine);
|
||||
int monitorNameIdx = headers.FindIndex(h => h.Trim() == "MonitorName");
|
||||
int monitorValueIdx = headers.FindIndex(h => h.Trim() == "MonitorValue");
|
||||
int scopeIdx = headers.FindIndex(h => h.Trim() == "Scope");
|
||||
int createTimeIdx = headers.FindIndex(h => h.Trim() == "CreateTime");
|
||||
|
||||
if (monitorNameIdx < 0 || monitorValueIdx < 0 || scopeIdx < 0 || createTimeIdx < 0)
|
||||
return results;
|
||||
|
||||
int maxIdx = Math.Max(monitorNameIdx, Math.Max(monitorValueIdx, Math.Max(scopeIdx, createTimeIdx)));
|
||||
|
||||
string? line;
|
||||
while ((line = sr.ReadLine()) != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
var fields = ParseCsvLine(line);
|
||||
if (fields.Count <= maxIdx) continue;
|
||||
|
||||
if (!double.TryParse(fields[monitorValueIdx].Trim(), out double value)) continue;
|
||||
if (!DateTime.TryParse(fields[createTimeIdx].Trim(), out DateTime createTime)) continue;
|
||||
|
||||
results.Add(new MonitorRecord
|
||||
{
|
||||
MonitorName = fields[monitorNameIdx].Trim(),
|
||||
MonitorValue = value,
|
||||
Scope = fields[scopeIdx].Trim(),
|
||||
CreateTime = createTime
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析单行 CSV,正确处理引号转义。
|
||||
/// </summary>
|
||||
private static List<string> ParseCsvLine(string line)
|
||||
{
|
||||
var fields = new List<string>();
|
||||
bool inQuotes = false;
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < line.Length; i++)
|
||||
{
|
||||
char c = line[i];
|
||||
if (inQuotes)
|
||||
{
|
||||
if (c == '"')
|
||||
{
|
||||
if (i + 1 < line.Length && line[i + 1] == '"')
|
||||
{
|
||||
sb.Append('"');
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
inQuotes = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c == '"')
|
||||
{
|
||||
inQuotes = true;
|
||||
}
|
||||
else if (c == ',')
|
||||
{
|
||||
fields.Add(sb.ToString());
|
||||
sb.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fields.Add(sb.ToString());
|
||||
return fields;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region OxyPlot 构建
|
||||
/// <summary>
|
||||
/// 构建空的 OxyPlot 模型(与 MonitorView 完全一致的配置)。
|
||||
/// </summary>
|
||||
private static PlotModel BuildEmptyPlot()
|
||||
{
|
||||
var pm = new PlotModel
|
||||
{
|
||||
Title = "曲线回读",
|
||||
PlotAreaBorderColor = OxyColors.LightGray,
|
||||
Background = OxyColors.White
|
||||
};
|
||||
pm.Axes.Add(new DateTimeAxis
|
||||
{
|
||||
Position = AxisPosition.Bottom,
|
||||
Title = "时间",
|
||||
StringFormat = "HH:mm:ss",
|
||||
MajorGridlineStyle = LineStyle.Dot,
|
||||
MinorGridlineStyle = LineStyle.None
|
||||
});
|
||||
pm.Axes.Add(new LinearAxis
|
||||
{
|
||||
Position = AxisPosition.Left,
|
||||
Title = "值",
|
||||
MajorGridlineStyle = LineStyle.Dot,
|
||||
MinorGridlineStyle = LineStyle.None
|
||||
});
|
||||
pm.Legends.Add(new Legend
|
||||
{
|
||||
LegendPosition = LegendPosition.RightTop,
|
||||
LegendBackground = OxyColor.FromAColor(200, OxyColors.White),
|
||||
LegendBorder = OxyColors.LightGray
|
||||
});
|
||||
return pm;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 辅助方法
|
||||
private void OnChannelPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName != nameof(CurveRecallChannelVM.IsDisplayed)) return;
|
||||
if (sender is not CurveRecallChannelVM channel) return;
|
||||
|
||||
if (channel.IsDisplayed)
|
||||
{
|
||||
if (channel.Series != null && !Plot.Series.Contains(channel.Series))
|
||||
{
|
||||
Plot.Series.Add(channel.Series);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (channel.Series != null)
|
||||
{
|
||||
Plot.Series.Remove(channel.Series);
|
||||
}
|
||||
}
|
||||
Plot.InvalidatePlot(true);
|
||||
}
|
||||
|
||||
private void ClearPlotAndChannels()
|
||||
{
|
||||
foreach (var ch in Channels)
|
||||
{
|
||||
ch.PropertyChanged -= OnChannelPropertyChanged;
|
||||
}
|
||||
Plot.Series.Clear();
|
||||
Channels.Clear();
|
||||
_colorIndex = 0;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
public override void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearPlotAndChannels();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CSV 监测记录(单行数据)。
|
||||
/// </summary>
|
||||
internal class MonitorRecord
|
||||
{
|
||||
public string MonitorName { get; set; } = string.Empty;
|
||||
public double MonitorValue { get; set; }
|
||||
public string Scope { get; set; } = string.Empty;
|
||||
public DateTime CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 曲线回读通道:MonitorName + Scope 唯一标识一条曲线。
|
||||
/// </summary>
|
||||
public class CurveRecallChannelVM : Prism.Mvvm.BindableBase
|
||||
{
|
||||
public string MonitorName { get; set; } = string.Empty;
|
||||
public string Scope { get; set; } = string.Empty;
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public OxyColor Color { get; set; } = OxyColors.SteelBlue;
|
||||
|
||||
private bool _isDisplayed = true;
|
||||
public bool IsDisplayed
|
||||
{
|
||||
get => _isDisplayed;
|
||||
set => SetProperty(ref _isDisplayed, value);
|
||||
}
|
||||
|
||||
public LineSeries? Series { get; set; }
|
||||
}
|
||||
}
|
||||
158
CurveModule/Views/CurveRecallView.xaml
Normal file
158
CurveModule/Views/CurveRecallView.xaml
Normal file
@@ -0,0 +1,158 @@
|
||||
<UserControl x:Class="CurveModule.Views.CurveRecallView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:oxy="http://oxyplot.org/wpf"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="700"
|
||||
d:DesignWidth="1200">
|
||||
<UserControl.Resources>
|
||||
<converters:LessThanConverter x:Key="LessThanConverter"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border Background="#F5F7FA">
|
||||
<Grid x:Name="RootGrid" Margin="8">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Row0:标题 -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="曲线回读"
|
||||
FontSize="20" FontWeight="Bold"
|
||||
Margin="4,0,0,8"/>
|
||||
|
||||
<!-- Row1:工具栏 -->
|
||||
<Border Grid.Row="1"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4" Padding="8" Margin="0,0,0,6">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Content="📂 加载 CSV"
|
||||
Command="{Binding LoadCsvCommand}"
|
||||
Padding="12,4"/>
|
||||
<Button Content="↺ 复原视图"
|
||||
Command="{Binding ResetViewCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"
|
||||
ToolTip="按数据范围重置坐标轴缩放/平移"/>
|
||||
<Button Content="✕ 清空"
|
||||
Command="{Binding ClearCommand}"
|
||||
Padding="12,4" Margin="6,0,0,0"
|
||||
ToolTip="清空已加载的曲线数据"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Row2:主体 -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition>
|
||||
<ColumnDefinition.Style>
|
||||
<Style TargetType="ColumnDefinition">
|
||||
<Setter Property="Width" Value="260"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Width" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ColumnDefinition.Style>
|
||||
</ColumnDefinition>
|
||||
<ColumnDefinition>
|
||||
<ColumnDefinition.Style>
|
||||
<Style TargetType="ColumnDefinition">
|
||||
<Setter Property="Width" Value="6"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding ActualWidth, ElementName=RootGrid, Converter={StaticResource LessThanConverter}, ConverterParameter=600}" Value="True">
|
||||
<Setter Property="Width" Value="0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ColumnDefinition.Style>
|
||||
</ColumnDefinition>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 左侧:通道列表 -->
|
||||
<Border Grid.Column="0"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0" Background="#ECEFF4" Padding="8,4">
|
||||
<TextBlock Text="曲线通道(勾选显示/隐藏)" FontWeight="Bold"/>
|
||||
</Border>
|
||||
|
||||
<ListBox Grid.Row="1"
|
||||
ItemsSource="{Binding Channels}"
|
||||
SelectedItem="{Binding SelectedChannel}"
|
||||
BorderThickness="0">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Margin="2">
|
||||
<DockPanel>
|
||||
<CheckBox IsChecked="{Binding IsDisplayed}"
|
||||
VerticalAlignment="Center"
|
||||
ToolTip="勾选=在图表上显示,取消=隐藏"/>
|
||||
<Border Width="10" Height="10"
|
||||
CornerRadius="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="4,0,6,0">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Color="SteelBlue"/>
|
||||
</Border.Background>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
VerticalAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis"/>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding StatusMessage}"
|
||||
FontSize="11" Foreground="#666"
|
||||
Margin="4,2" TextWrapping="Wrap"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<GridSplitter Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="Transparent"/>
|
||||
|
||||
<!-- 右侧:OxyPlot 图表 -->
|
||||
<Border Grid.Column="2"
|
||||
Background="White"
|
||||
BorderBrush="#DDD" BorderThickness="1"
|
||||
CornerRadius="4">
|
||||
<oxy:PlotView Model="{Binding Plot}"
|
||||
Background="Transparent"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Row3:状态栏 -->
|
||||
<Border Grid.Row="3"
|
||||
Background="#ECEFF4"
|
||||
Padding="8,4" Margin="0,6,0,0"
|
||||
CornerRadius="2">
|
||||
<TextBlock Text="{Binding StatusMessage}"
|
||||
Foreground="#444"
|
||||
FontSize="12"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
28
CurveModule/Views/CurveRecallView.xaml.cs
Normal file
28
CurveModule/Views/CurveRecallView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace CurveModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// CurveRecallView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class CurveRecallView : UserControl
|
||||
{
|
||||
public CurveRecallView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
DeviceCommand/Base/IBaseInterface.cs
Normal file
16
DeviceCommand/Base/IBaseInterface.cs
Normal file
@@ -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<bool> ConnectAsync(CancellationToken ct = default);
|
||||
public void Close();
|
||||
}
|
||||
}
|
||||
19
DeviceCommand/Base/IModbusDevice.cs
Normal file
19
DeviceCommand/Base/IModbusDevice.cs
Normal file
@@ -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<ushort[]> ReadHoldingRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
|
||||
Task WriteSingleCoilAsync(byte slaveAddress, ushort coilAddress, bool value, CancellationToken ct = default);
|
||||
Task<bool[]> ReadCoilsAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
|
||||
Task<ushort[]> ReadInputRegistersAsync(byte slaveAddress, ushort startAddress, ushort numberOfPoints, CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
13
DeviceCommand/Base/ISerialPort.cs
Normal file
13
DeviceCommand/Base/ISerialPort.cs
Normal file
@@ -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<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default);
|
||||
Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
15
DeviceCommand/Base/ITcp.cs
Normal file
15
DeviceCommand/Base/ITcp.cs
Normal file
@@ -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<byte[]> ReadAsync(int length, CancellationToken ct = default);
|
||||
Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default);
|
||||
Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default);
|
||||
}
|
||||
}
|
||||
164
DeviceCommand/Base/ModbusRtu.cs
Normal file
164
DeviceCommand/Base/ModbusRtu.cs
Normal file
@@ -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<bool> 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<ushort[]> 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<bool[]> 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<ushort[]> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
163
DeviceCommand/Base/ModbusTcp.cs
Normal file
163
DeviceCommand/Base/ModbusTcp.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
using Model.Models;
|
||||
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(TcpConfig config) : this()
|
||||
{
|
||||
if (config == null) return;
|
||||
ConfigureDevice(config.IPAddress, config.Port, config.SendTimeout, config.ReceiveTimeout);
|
||||
}
|
||||
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<bool> 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<ushort[]> 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<bool[]> 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<ushort[]> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
227
DeviceCommand/Base/Serial_Port.cs
Normal file
227
DeviceCommand/Base/Serial_Port.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Base
|
||||
{
|
||||
public class Serial_Port : ISerialPort, IDisposable
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 <see cref="SerialPortConfig"/> 一次性配置串口通信参数。
|
||||
/// </summary>
|
||||
public Serial_Port(SerialPortConfig config) : this()
|
||||
{
|
||||
if (config == null) return;
|
||||
ConfigureDevice(config.PortName, config.BaudRate, config.DataBits, config.StopBits, config.Parity, config.ReadTimeout, config.WriteTimeout);
|
||||
}
|
||||
|
||||
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<bool> 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;
|
||||
|
||||
// 允许底层保留默认设置,控制权交给外层 WaitAsync
|
||||
_serialPort.ReadTimeout = SerialPort.InfiniteTimeout;
|
||||
_serialPort.WriteTimeout = SerialPort.InfiniteTimeout;
|
||||
|
||||
_serialPort.Open();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_serialPort?.IsOpen == true) _serialPort.Close();
|
||||
}
|
||||
|
||||
private async Task LoglessSendAsync(string data, CancellationToken ct)
|
||||
{
|
||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
||||
|
||||
try
|
||||
{
|
||||
if (WriteTimeout > 0)
|
||||
{
|
||||
// 核心修改:使用 WaitAsync 精准控制写入超时
|
||||
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
throw new TimeoutException($"串口写入数据超时({WriteTimeout} ms)", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SendAsync(string data, CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
await LoglessSendAsync(data, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
|
||||
{
|
||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||
|
||||
delimiter ??= "\n";
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
int bytesRead;
|
||||
if (ReadTimeout > 0)
|
||||
{
|
||||
// 核心修改:使用 WaitAsync 精准控制单次异步读取超时
|
||||
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (bytesRead == 0) continue;
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
string currentText = sb.ToString();
|
||||
int index = currentText.IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
return currentText.Substring(0, index).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 关键防污染策略:串口发生命令错、超时不响应时,WaitAsync 会立功捕获异常
|
||||
// 我们直接清空串口驱动内部的输入缓冲区,把残余垃圾数据物理抹除,确保下一条指令安全、不掉线
|
||||
ClearHardwareBuffer();
|
||||
throw new TimeoutException($"串口读取超时(未收到结束符 '{delimiter}'),等待时间:{ReadTimeout} ms", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
// 发送新命令前先冲洗一下缓冲区,双重保险
|
||||
ClearHardwareBuffer();
|
||||
|
||||
await LoglessSendAsync(command, ct);
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 物理强制清空串口驱动层及软件层的缓冲区
|
||||
/// </summary>
|
||||
private void ClearHardwareBuffer()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_serialPort?.IsOpen == true)
|
||||
{
|
||||
_serialPort.DiscardInBuffer(); // 清空硬件接收缓冲区
|
||||
_serialPort.DiscardOutBuffer(); // 清空硬件发送缓冲区
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略清理异常
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_serialPort != null)
|
||||
{
|
||||
if (_serialPort.IsOpen) _serialPort.Close();
|
||||
_serialPort.Dispose();
|
||||
}
|
||||
commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
322
DeviceCommand/Base/TCP.cs
Normal file
322
DeviceCommand/Base/TCP.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
using Model.Models;
|
||||
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, IDisposable
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过 <see cref="TcpConfig"/> 一次性配置 TCP 通信参数。
|
||||
/// </summary>
|
||||
public Tcp(TcpConfig config) : this()
|
||||
{
|
||||
if (config == null) return;
|
||||
ConfigureDevice(config.IPAddress, config.Port, config.SendTimeout, config.ReceiveTimeout);
|
||||
}
|
||||
|
||||
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<bool> ConnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await ResetConnectionAsync(ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 核心内部方法:无锁状态下安全重置并重建连接
|
||||
/// </summary>
|
||||
private async Task<bool> ResetConnectionAsync(CancellationToken ct)
|
||||
{
|
||||
if (_tcpClient != null)
|
||||
{
|
||||
_tcpClient.Close();
|
||||
_tcpClient.Dispose();
|
||||
}
|
||||
|
||||
_tcpClient = new TcpClient();
|
||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
if (_tcpClient?.Connected == true) _tcpClient.Close();
|
||||
}
|
||||
|
||||
private async Task LoglessSendAsync(byte[] buffer, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
try
|
||||
{
|
||||
if (SendTimeout > 0)
|
||||
{
|
||||
// 使用 WaitAsync 控制发送超时
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 发送超时,安全起见直接断开重建
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"TCP 发送数据超时({SendTimeout} ms),连接已重置以确保安全。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
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<byte[]> 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;
|
||||
|
||||
try
|
||||
{
|
||||
while (offset < length)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// 计算剩余超时时间(这里简单使用配置值,若要求极精准可加入不长计算)
|
||||
int read = ReceiveTimeout > 0
|
||||
? await stream.ReadAsync(buffer, offset, length - offset, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||
.ConfigureAwait(false)
|
||||
: await stream.ReadAsync(buffer, offset, length - offset, ct).ConfigureAwait(false);
|
||||
|
||||
if (read == 0) throw new IOException("远程主机已关闭连接");
|
||||
offset += read;
|
||||
}
|
||||
return offset == 0 ? Array.Empty<byte>() : buffer[..offset];
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 关键安全重置:WaitAsync 超时后抛弃老连接,防止未完成的 Read 污染后续 Buffer
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"TCP 读取定长数据超时({ReceiveTimeout} ms),连接已重置避免数据错乱。", ex);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> 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();
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
int bytesRead = ReceiveTimeout > 0
|
||||
? await stream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||
.ConfigureAwait(false)
|
||||
: await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
|
||||
if (bytesRead == 0) throw new IOException("远程主机已关闭连接");
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
string currentText = sb.ToString();
|
||||
int index = currentText.IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
{
|
||||
return currentText.Substring(0, index).Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
// 当 SCPI 仪表由于命令发错不响应时,WaitAsync 会立功并抛出 TimeoutException
|
||||
// 此时我们在内部秒速“重置连接”,上层业务只会收到一个干净的超时报错,但由于锁和自动重连,下一次通信直接恢复!
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"SCPI 读取超时(未收到结束符 '{delimiter}',等待:{ReceiveTimeout} ms),通信链路已自动刷新。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAsync(delimiter, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> 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();
|
||||
}
|
||||
}
|
||||
#region 扩展:读取所有可用的二进制网络字节 (无 SCPI 块头解析)
|
||||
|
||||
/// <summary>
|
||||
/// 无锁核心方法:发送命令并一次性读取所有回传的二进制原始数据包(不进行 # 协议头解析,专用于读取纯文件流如 PNG)
|
||||
/// </summary>
|
||||
private async Task<byte[]> LoglessReadAllBytesAsync(string queryCommand, CancellationToken ct)
|
||||
{
|
||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||
|
||||
// 1. 发送查询命令(例如 :PRINt? PNG\n)
|
||||
await LoglessSendAsync(Encoding.UTF8.GetBytes(queryCommand), ct);
|
||||
|
||||
NetworkStream stream = _tcpClient.GetStream();
|
||||
|
||||
// 2. 鼎阳示波器的截图数据大概在 100KB - 800KB 左右,使用动态内存流接收整个包
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
byte[] buffer = new byte[8192]; // 8KB 缓冲区
|
||||
|
||||
try
|
||||
{
|
||||
// 先给设备短暂的反应时间,等待数据到达网络缓冲区
|
||||
int delayCount = 0;
|
||||
while (!_tcpClient.GetStream().DataAvailable && delayCount < 50)
|
||||
{
|
||||
await Task.Delay(10, ct);
|
||||
delayCount++;
|
||||
}
|
||||
|
||||
// 循环读取,直到网络流中没有更多数据
|
||||
do
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
int read = ReceiveTimeout > 0
|
||||
? await stream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||
.ConfigureAwait(false)
|
||||
: await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||
|
||||
if (read == 0) break; // 远程流关闭
|
||||
|
||||
ms.Write(buffer, 0, read);
|
||||
|
||||
// 如果流里没有剩余数据了,退出读取(防止 ReadAsync 在没有数据时无限阻塞等待)
|
||||
if (!stream.DataAvailable)
|
||||
{
|
||||
// 极短延时再确认一次,防止分包网络延迟引起的“假结束”
|
||||
await Task.Delay(30, ct);
|
||||
if (!stream.DataAvailable)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
catch (TimeoutException ex)
|
||||
{
|
||||
await ResetConnectionAsync(ct);
|
||||
throw new TimeoutException($"读取二进制大包超时(等待:{ReceiveTimeout} ms),链路已重置。", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 【公开方法】发送命令并读取设备回传的全部原始二进制字节数组(不带协议头解析,直接返回整个字节缓冲区)
|
||||
/// </summary>
|
||||
public async Task<byte[]> ReadAllBytesAsync(string queryCommand, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
return await LoglessReadAllBytesAsync(queryCommand, ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
public void Dispose()
|
||||
{
|
||||
_tcpClient?.Dispose();
|
||||
_commLock?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
DeviceCommand/DeviceCommand.csproj
Normal file
19
DeviceCommand/DeviceCommand.csproj
Normal file
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NModbus" Version="3.0.83" />
|
||||
<PackageReference Include="NModbus.Serial" Version="3.0.83" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
540
DeviceCommand/Devices/GantryControlTcp.cs
Normal file
540
DeviceCommand/Devices/GantryControlTcp.cs
Normal file
@@ -0,0 +1,540 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class GantryControlTcp : ModbusTcp
|
||||
{
|
||||
private readonly byte _slaveAddress=1;
|
||||
|
||||
// 向后兼容:TcpDevice 即自身(继承自 ModbusTcp)
|
||||
public ModbusTcp TcpDevice => this;
|
||||
|
||||
public GantryControlTcp(TcpConfig config) : base(config)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// ------------------ Coil / Bit 状态与控制 (位元件) ------------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_M_ESTOP = 20496; // 0x5010: 紧停
|
||||
private const ushort ADDR_M_X_STOP = 41; // 0x29: X轴停止
|
||||
private const ushort ADDR_M_Y_STOP = 42; // 0x2A: Y轴停止
|
||||
private const ushort ADDR_M_Z_STOP = 43; // 0x2B: Z轴停止
|
||||
|
||||
private const ushort ADDR_M_X_START = 30; // 0x1E: X轴指定位置启动
|
||||
private const ushort ADDR_M_Y_START = 31; // 0x1F: Y轴指定位置启动
|
||||
private const ushort ADDR_M_Z_START = 35; // 0x23: Z轴指定位置启动
|
||||
|
||||
private const ushort ADDR_M_HOME_TRIGGER = 100; // 0x64: 回原点触发
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 轴使能控制 (HM 寄存器) ------------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_HM_AXIS1_ENABLE = 49408; // 0xC100: 轴1使能 (X轴)
|
||||
private const ushort ADDR_HM_AXIS2_ENABLE = 49409; // 0xC101: 轴2使能 (Y轴)
|
||||
private const ushort ADDR_HM_AXIS3_ENABLE = 49410; // 0xC102: 轴3使能 (Z轴)
|
||||
private const ushort ADDR_HM_AXIS4_ENABLE = 49411; // 0xC103: 轴4使能
|
||||
private const ushort ADDR_HM_AXIS5_ENABLE = 49412; // 0xC104: 轴5使能
|
||||
private const ushort ADDR_HM_AXIS6_ENABLE = 49413; // 0xC105: 轴6使能
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 速度与位置参数 (HD 32位数据寄存器) --------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_HD_HOME_POS_SPEED = 41288; // 0xA148: 原点定位速度 (32位)
|
||||
|
||||
// 轴原点位置
|
||||
private const ushort ADDR_HD_X_HOME_POS = 41198; // 0xA0EE: X轴原点位 (32位)
|
||||
private const ushort ADDR_HD_Z_HOME_POS = 41208; // 0xA0F8: Z轴原点位 (32位)
|
||||
private const ushort ADDR_HD_Y_HOME_POS = 41218; // 0xA102: Y轴原点位 (32位)
|
||||
|
||||
// 轴运行目标位置
|
||||
private const ushort ADDR_HD_Y_TARGET = 41228; // 0xA10C: Y轴指定位置 (32位)
|
||||
private const ushort ADDR_HD_Z_TARGET = 41238; // 0xA116: Z轴指定位置 (32位)
|
||||
private const ushort ADDR_HD_X_TARGET = 41248; // 0xA120: X轴指定位置 (32位)
|
||||
|
||||
// 轴运行速度
|
||||
private const ushort ADDR_HD_X_SPEED = 41318; // 0xA166: X轴速度 (32位)
|
||||
private const ushort ADDR_HD_Z_SPEED = 41328; // 0xA170: Z轴速度 (32位)
|
||||
private const ushort ADDR_HD_Y_SPEED = 41338; // 0xA17A: Y轴速度 (32位)
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 轴绝对位置(只读,相对原点)------------------
|
||||
// =================================================================
|
||||
private const ushort ADDR_ABS_AXIS1_POS = 47232; // 轴1绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS2_POS = 47236; // 轴2绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS3_POS = 47240; // 轴3绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS4_POS = 47244; // 轴4绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS5_POS = 47248; // 轴5绝对位置 (32位)
|
||||
private const ushort ADDR_ABS_AXIS6_POS = 47252; // 轴6绝对位置 (32位)
|
||||
|
||||
// =================================================================
|
||||
// ------------------ 核心控制逻辑与参数读写 ------------------------
|
||||
// =================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 轴使能控制
|
||||
/// </summary>
|
||||
/// <param name="axis">轴编号 (1 - 6)</param>
|
||||
public async Task SetAxisEnableAsync(int axis, bool enable, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HM_AXIS1_ENABLE,
|
||||
2 => ADDR_HM_AXIS2_ENABLE,
|
||||
3 => ADDR_HM_AXIS3_ENABLE,
|
||||
4 => ADDR_HM_AXIS4_ENABLE,
|
||||
5 => ADDR_HM_AXIS5_ENABLE,
|
||||
6 => ADDR_HM_AXIS6_ENABLE,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1 - 6", nameof(axis))
|
||||
};
|
||||
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, enable, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴的使能状态
|
||||
/// </summary>
|
||||
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||
public async Task<bool> GetAxisEnableAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HM_AXIS1_ENABLE,
|
||||
2 => ADDR_HM_AXIS2_ENABLE,
|
||||
3 => ADDR_HM_AXIS3_ENABLE,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
bool[] coils = await ReadCoilsAsync(_slaveAddress, address, 1, ct);
|
||||
return coils != null && coils.Length > 0 && coils[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同时设置 X/Y/Z 轴的原点位置(带各自独立的范围检查)
|
||||
/// </summary>
|
||||
/// <param name="xPos">X轴原点位置 (范围: 0 ~ 50000)</param>
|
||||
/// <param name="yPos">Y轴原点位置 (范围: 0 ~ 50000)</param>
|
||||
/// <param name="zPos">Z轴原点位置 (范围: 0 ~ 20000)</param>
|
||||
public async Task SetHomePositionAsync(int xPos, int yPos, int zPos, CancellationToken ct = default)
|
||||
{
|
||||
if (xPos < 0 || xPos > 50000)
|
||||
throw new ArgumentOutOfRangeException(nameof(xPos), $"X轴原点设置值 [{xPos}] 超出合法范围 (0 - 50000)");
|
||||
|
||||
if (yPos < 0 || yPos > 50000)
|
||||
throw new ArgumentOutOfRangeException(nameof(yPos), $"Y轴原点设置值 [{yPos}] 超出合法范围 (0 - 50000)");
|
||||
|
||||
if (zPos < 0 || zPos > 20000)
|
||||
throw new ArgumentOutOfRangeException(nameof(zPos), $"Z轴原点设置值 [{zPos}] 超出合法范围 (0 - 20000)");
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, Int32ToUshorts(xPos), ct);
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Y_HOME_POS, Int32ToUshorts(yPos), ct);
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_Z_HOME_POS, Int32ToUshorts(zPos), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量查询当前 X、Y、Z 三轴设定的原点位置值
|
||||
/// </summary>
|
||||
/// <returns>返回包含三轴原点位置的元组 (X, Y, Z)</returns>
|
||||
public async Task<(int X, int Y, int Z)> GetHomePositionAsync(CancellationToken ct = default)
|
||||
{
|
||||
// 41218(Y) - 41198(X) = 20,加Y轴自身的 2 个寄存器,共批量读取 22 个连续寄存器
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_X_HOME_POS, 22, ct);
|
||||
|
||||
if (registers == null || registers.Length < 22)
|
||||
throw new InvalidOperationException("从 PLC 读取三轴原点位置失败,返回数据长度不足。");
|
||||
|
||||
int xHomePos = UshortsToInt32(new ushort[] { registers[0], registers[1] });
|
||||
int zHomePos = UshortsToInt32(new ushort[] { registers[10], registers[11] });
|
||||
int yHomePos = UshortsToInt32(new ushort[] { registers[20], registers[21] });
|
||||
|
||||
return (xHomePos, yHomePos, zHomePos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置回原点定位速度(带 0 - 100000 范围检查)
|
||||
/// </summary>
|
||||
public async Task SetHomePositionSpeedAsync(int speed, CancellationToken ct = default)
|
||||
{
|
||||
if (speed < 0 || speed > 100000)
|
||||
throw new ArgumentOutOfRangeException(nameof(speed), $"回原点定位速度值 [{speed}] 超出合法范围 (0 - 100000)");
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, Int32ToUshorts(speed), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询当前设定的回原点定位速度
|
||||
/// </summary>
|
||||
public async Task<int> GetHomePositionSpeedAsync(CancellationToken ct = default)
|
||||
{
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_HD_HOME_POS_SPEED, 2, ct);
|
||||
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException("从 PLC 读取回原点定位速度失败,返回数据长度不足。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定轴的运行速度(带 0 - 100000 范围检查)
|
||||
/// </summary>
|
||||
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||
public async Task SetAxisSpeedAsync(int axis, int speed, CancellationToken ct = default)
|
||||
{
|
||||
if (speed < 0 || speed > 100000)
|
||||
throw new ArgumentOutOfRangeException(nameof(speed), $"轴速度设置值 [{speed}] 超出合法范围 (0 - 100000)");
|
||||
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HD_X_SPEED,
|
||||
2 => ADDR_HD_Y_SPEED,
|
||||
3 => ADDR_HD_Z_SPEED,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, address, Int32ToUshorts(speed), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴当前设定的运行速度
|
||||
/// </summary>
|
||||
public async Task<int> GetAxisSpeedAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HD_X_SPEED,
|
||||
2 => ADDR_HD_Y_SPEED,
|
||||
3 => ADDR_HD_Z_SPEED,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException($"从 PLC 读取轴 {axis} 速度失败。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入指定轴的目标位置(动态安全校验:目标位置不能超过 [轴最大物理极限 - 当前轴原点设置值])
|
||||
/// </summary>
|
||||
public async Task SetAxisTargetPositionAsync(int axis, int targetPos, CancellationToken ct = default)
|
||||
{
|
||||
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||
|
||||
ushort targetAddress;
|
||||
int maxLimit;
|
||||
int currentHomePos;
|
||||
|
||||
switch (axis)
|
||||
{
|
||||
case 1:
|
||||
targetAddress = ADDR_HD_X_TARGET;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = xHome;
|
||||
break;
|
||||
case 2:
|
||||
targetAddress = ADDR_HD_Y_TARGET;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = yHome;
|
||||
break;
|
||||
case 3:
|
||||
targetAddress = ADDR_HD_Z_TARGET;
|
||||
maxLimit = 20000;
|
||||
currentHomePos = zHome;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis));
|
||||
}
|
||||
|
||||
int allowedMaxLimit = maxLimit - currentHomePos;
|
||||
|
||||
if (targetPos < 0 || targetPos > allowedMaxLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(targetPos),
|
||||
$"轴 {axis} 目标位置 [{targetPos}] 不合法!当前原点为 [{currentHomePos}],允许的有效写入范围为: 0 - {allowedMaxLimit}"
|
||||
);
|
||||
}
|
||||
|
||||
await WriteMultipleRegistersAsync(_slaveAddress, targetAddress, Int32ToUshorts(targetPos), ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴当前设定的目标指定位置
|
||||
/// </summary>
|
||||
public async Task<int> GetAxisTargetPositionAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_HD_X_TARGET,
|
||||
2 => ADDR_HD_Y_TARGET,
|
||||
3 => ADDR_HD_Z_TARGET,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException($"从 PLC 读取轴 {axis} 目标位置失败。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统紧停锁定状态 (true: 锁定激活, false: 解除锁定)
|
||||
/// </summary>
|
||||
public async Task SetEStopStateAsync(bool state, CancellationToken ct = default)
|
||||
{
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_ESTOP, state, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定轴的停止控制状态 (true: 持续输出停止信号, false: 释放停止信号)
|
||||
/// </summary>
|
||||
public async Task SetStopStateAsync(int axis, bool state, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_M_X_STOP,
|
||||
2 => ADDR_M_Y_STOP,
|
||||
3 => ADDR_M_Z_STOP,
|
||||
_ => throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis))
|
||||
};
|
||||
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, state, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 全局回零(安全校验版:先检查回原点速度是否为 0,为0则拒绝触发)
|
||||
/// </summary>
|
||||
public async Task HomeAsync(CancellationToken ct = default)
|
||||
{
|
||||
int currentSpeed = await GetHomePositionSpeedAsync(ct);
|
||||
|
||||
if (currentSpeed == 0)
|
||||
throw new InvalidOperationException("无法触发回原点!当前回原点定位速度为 0,请先设置合法的回零速度。");
|
||||
|
||||
await TriggerCoilAsync(ADDR_M_HOME_TRIGGER, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单轴指定位置启动(含速度判定、使能自动补齐、动作目标位置独立范围校验)
|
||||
/// </summary>
|
||||
/// <param name="axis">1: X轴, 2: Y轴, 3: Z轴</param>
|
||||
public async Task StartSingleAxisAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort startAddress;
|
||||
int maxLimit;
|
||||
int currentHomePos;
|
||||
|
||||
// 1. 获取基础参数与校验边界
|
||||
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||
switch (axis)
|
||||
{
|
||||
case 1:
|
||||
startAddress = ADDR_M_X_START;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = xHome;
|
||||
break;
|
||||
case 2:
|
||||
startAddress = ADDR_M_Y_START;
|
||||
maxLimit = 50000;
|
||||
currentHomePos = yHome;
|
||||
break;
|
||||
case 3:
|
||||
startAddress = ADDR_M_Z_START;
|
||||
maxLimit = 20000;
|
||||
currentHomePos = zHome;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("轴通道错误,仅支持 1(X), 2(Y), 3(Z)", nameof(axis));
|
||||
}
|
||||
|
||||
// 2. 检查动作轴的速度
|
||||
int currentSpeed = await GetAxisSpeedAsync(axis, ct);
|
||||
if (currentSpeed == 0)
|
||||
throw new InvalidOperationException($"轴 {axis} 无法启动!当前设定速度为 0。");
|
||||
|
||||
// 3. 检查动作轴使能,若为 false 则自动补写 true 激活驱动器
|
||||
bool isEnabled = await GetAxisEnableAsync(axis, ct);
|
||||
if (!isEnabled)
|
||||
{
|
||||
await SetAxisEnableAsync(axis, true, ct);
|
||||
await Task.Delay(100, ct); // 等待硬件继电器响应与伺服驱动就绪
|
||||
}
|
||||
|
||||
// 4. 精准独立校验当前需要动作轴的目标定位是否越界
|
||||
int currentTarget = await GetAxisTargetPositionAsync(axis, ct);
|
||||
int allowedMaxLimit = maxLimit - currentHomePos;
|
||||
if (currentTarget < 0 || currentTarget > allowedMaxLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"轴 {axis} 无法启动!当前指定目标位置 [{currentTarget}] 超过了允许的最大安全极限值 [{allowedMaxLimit}]。");
|
||||
}
|
||||
|
||||
// 5. 校验完成,边缘触发点动启动
|
||||
await TriggerCoilAsync(startAddress, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三轴同时启动(全开:严格校验全部运行轴速度、批量检测与补正1~6号轴使能、联动校验三轴目标范围极限)
|
||||
/// </summary>
|
||||
public async Task StartAllAxesAsync(CancellationToken ct = default)
|
||||
{
|
||||
// 1. 一次性读取三轴速度,严禁任何一轴速度为 0
|
||||
int xSpeed = await GetAxisSpeedAsync(1, ct);
|
||||
int ySpeed = await GetAxisSpeedAsync(2, ct);
|
||||
int zSpeed = await GetAxisSpeedAsync(3, ct);
|
||||
|
||||
if (xSpeed == 0 || ySpeed == 0 || zSpeed == 0)
|
||||
throw new InvalidOperationException($"无法全开启动!存在运行速度为 0 的轴。当前速度 -> X:{xSpeed}, Y:{ySpeed}, Z:{zSpeed}");
|
||||
|
||||
// 2. 批量读取 1 ~ 6 号轴连续的使能状态线圈,极大地优化网络性能
|
||||
bool[] enableStates = await ReadCoilsAsync(_slaveAddress, ADDR_HM_AXIS1_ENABLE, 6, ct);
|
||||
if (enableStates == null || enableStates.Length < 6)
|
||||
throw new InvalidOperationException("从 PLC 读取 1~6 号轴使能状态失败。");
|
||||
|
||||
bool hasModifiedEnable = false;
|
||||
ushort[] enableAddresses = { ADDR_HM_AXIS1_ENABLE, ADDR_HM_AXIS2_ENABLE, ADDR_HM_AXIS3_ENABLE, ADDR_HM_AXIS4_ENABLE, ADDR_HM_AXIS5_ENABLE, ADDR_HM_AXIS6_ENABLE };
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
if (!enableStates[i])
|
||||
{
|
||||
await WriteSingleCoilAsync(_slaveAddress, enableAddresses[i], true, ct);
|
||||
hasModifiedEnable = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果执行过使能补齐补齐写入,在循环外进行统一延时,保证PLC时序安全
|
||||
if (hasModifiedEnable)
|
||||
await Task.Delay(100, ct);
|
||||
|
||||
// 3. 获取三轴原点及当前设定的目标指定位置
|
||||
var (xHome, yHome, zHome) = await GetHomePositionAsync(ct);
|
||||
|
||||
int xTarget = await GetAxisTargetPositionAsync(1, ct);
|
||||
int yTarget = await GetAxisTargetPositionAsync(2, ct);
|
||||
int zTarget = await GetAxisTargetPositionAsync(3, ct);
|
||||
|
||||
// X轴安全范围联动校验
|
||||
int xMaxAllowed = 50000 - xHome;
|
||||
if (xTarget < 0 || xTarget > xMaxAllowed)
|
||||
throw new ArgumentOutOfRangeException(nameof(xTarget), $"X轴目标位置 [{xTarget}] 越界,当前原点下最大范围 0-{xMaxAllowed}");
|
||||
|
||||
// Y轴安全范围联动校验
|
||||
int yMaxAllowed = 50000 - yHome;
|
||||
if (yTarget < 0 || yTarget > yMaxAllowed)
|
||||
throw new ArgumentOutOfRangeException(nameof(yTarget), $"Y轴目标位置 [{yTarget}] 越界,当前原点下最大范围 0-{yMaxAllowed}");
|
||||
|
||||
// Z轴安全范围联动校验
|
||||
int zMaxAllowed = 20000 - zHome;
|
||||
if (zTarget < 0 || zTarget > zMaxAllowed)
|
||||
throw new ArgumentOutOfRangeException(nameof(zTarget), $"Z轴目标位置 [{zTarget}] 越界,当前原点下最大范围 0-{zMaxAllowed}");
|
||||
|
||||
// 4. 全部联动校验通过,三轴点动同步启动
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, true, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, true, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, true, ct);
|
||||
|
||||
await Task.Delay(100, ct); // 保持高电平状态以适配 PLC 扫描周期
|
||||
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_X_START, false, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Y_START, false, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, ADDR_M_Z_START, false, ct);
|
||||
}
|
||||
|
||||
#region 内部高低字工具转化方法
|
||||
|
||||
/// <summary>
|
||||
/// 触发点动脉冲信号(置 1 后,等待 100ms 自动置 0 释放)
|
||||
/// </summary>
|
||||
private async Task TriggerCoilAsync(ushort address, CancellationToken ct = default)
|
||||
{
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, true, ct);
|
||||
await Task.Delay(100, ct);
|
||||
await WriteSingleCoilAsync(_slaveAddress, address, false, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 32 位整型数据转换为 Modbus 的 2 个 16 位无符号整数(低字在前 CDAB 格式)
|
||||
/// </summary>
|
||||
private ushort[] Int32ToUshorts(int value)
|
||||
{
|
||||
ushort lowWord = (ushort)(value & 0xFFFF);
|
||||
ushort highWord = (ushort)((value >> 16) & 0xFFFF);
|
||||
return new ushort[] { lowWord, highWord };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Modbus 的 2 个 16 位无符号整数转换为 32 位整型(低字在前 CDAB 格式)
|
||||
/// </summary>
|
||||
private int UshortsToInt32(ushort[] registers)
|
||||
{
|
||||
uint lowWord = registers[0];
|
||||
uint highWord = registers[1];
|
||||
return (int)((highWord << 16) | lowWord);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 轴绝对位置读取(只读)
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定轴的绝对位置(相对原点的实时位置,只读)
|
||||
/// </summary>
|
||||
/// <param name="axis">轴编号 (1-6)</param>
|
||||
public async Task<int> GetAbsolutePositionAsync(int axis, CancellationToken ct = default)
|
||||
{
|
||||
ushort address = axis switch
|
||||
{
|
||||
1 => ADDR_ABS_AXIS1_POS,
|
||||
2 => ADDR_ABS_AXIS2_POS,
|
||||
3 => ADDR_ABS_AXIS3_POS,
|
||||
4 => ADDR_ABS_AXIS4_POS,
|
||||
5 => ADDR_ABS_AXIS5_POS,
|
||||
6 => ADDR_ABS_AXIS6_POS,
|
||||
_ => throw new ArgumentException("轴编号错误,仅支持 1-6", nameof(axis))
|
||||
};
|
||||
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, address, 2, ct);
|
||||
if (registers == null || registers.Length < 2)
|
||||
throw new InvalidOperationException($"读取轴 {axis} 绝对位置失败。");
|
||||
|
||||
return UshortsToInt32(registers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量读取 1-6 轴的绝对位置(一次性读取 22 个寄存器,高效)
|
||||
/// </summary>
|
||||
/// <returns>返回 6 轴绝对位置元组</returns>
|
||||
public async Task<(int Axis1, int Axis2, int Axis3, int Axis4, int Axis5, int Axis6)> GetAllAbsolutePositionsAsync(CancellationToken ct = default)
|
||||
{
|
||||
// 从 47232 开始连续读取 22 个寄存器(覆盖 47232-47253)
|
||||
ushort[] registers = await ReadHoldingRegistersAsync(_slaveAddress, ADDR_ABS_AXIS1_POS, 22, ct);
|
||||
if (registers == null || registers.Length < 22)
|
||||
throw new InvalidOperationException("批量读取 6 轴绝对位置失败,返回数据长度不足。");
|
||||
|
||||
// 每轴间隔 4 个寄存器(2个数据 + 2个间隙),提取各轴数据
|
||||
int axis1 = UshortsToInt32(new ushort[] { registers[0], registers[1] });
|
||||
int axis2 = UshortsToInt32(new ushort[] { registers[4], registers[5] });
|
||||
int axis3 = UshortsToInt32(new ushort[] { registers[8], registers[9] });
|
||||
int axis4 = UshortsToInt32(new ushort[] { registers[12], registers[13] });
|
||||
int axis5 = UshortsToInt32(new ushort[] { registers[16], registers[17] });
|
||||
int axis6 = UshortsToInt32(new ushort[] { registers[20], registers[21] });
|
||||
|
||||
return (axis1, axis2, axis3, axis4, axis5, axis6);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
49
DeviceCommand/Devices/IOBoard.cs
Normal file
49
DeviceCommand/Devices/IOBoard.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
//[ACPCommand]
|
||||
public class IOBoard : ModbusTcp
|
||||
{
|
||||
|
||||
public IOBoard(TcpConfig config) : base(config)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public async Task 写输出开关(byte 站号, ushort 开始地址, bool data)
|
||||
{
|
||||
await Modbus.WriteSingleCoilAsync(站号, 开始地址, data);
|
||||
}
|
||||
public async Task 批量写输出开关(byte 站号, ushort 开始地址, bool[] datas)
|
||||
{
|
||||
var 批量写入 = await Modbus.ReadCoilsAsync(1, 0, 16);
|
||||
for (int i = 0; i < datas.Length; i++)
|
||||
{
|
||||
if (开始地址 + i < 批量写入.Length)
|
||||
{
|
||||
批量写入[开始地址 + i] = datas[i];
|
||||
}
|
||||
}
|
||||
await Modbus.WriteMultipleCoilsAsync(站号, 0, 批量写入);
|
||||
}
|
||||
public async Task 读输出开关(byte 站号, ushort 开始地址)
|
||||
{
|
||||
await Modbus.ReadCoilsAsync(站号, 开始地址, 1);
|
||||
}
|
||||
public async Task<bool[]> 批量读输出开关(byte 站号, ushort 开始地址,ushort 数量)
|
||||
{
|
||||
return await Modbus.ReadCoilsAsync(站号, 开始地址, 数量);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
DeviceCommand/Devices/IOBoardGroup.cs
Normal file
160
DeviceCommand/Devices/IOBoardGroup.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using Common.Attributes;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using DeviceCommand.Base;
|
||||
using DeviceCommand.Devices;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
/// <summary>
|
||||
/// 系统控制功能类型枚举 (中文)
|
||||
/// </summary>
|
||||
public enum 功能动作 : byte
|
||||
{
|
||||
单相充电,
|
||||
抛负载,
|
||||
短路测试,
|
||||
蜂鸣器报警
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 精简后的 IO 端口信息结构体
|
||||
/// </summary>
|
||||
public struct IoPortInfo
|
||||
{
|
||||
public int ModuleIndex { get; set; } // IO板卡/模块序号 (1 或 2)
|
||||
public ushort Address { get; set; } // 转换为 Modbus 的线圈相对偏移地址 (Y1->0, Y2->1)
|
||||
|
||||
public IoPortInfo(int moduleIndex, ushort address)
|
||||
{
|
||||
ModuleIndex = moduleIndex;
|
||||
Address = address;
|
||||
}
|
||||
}
|
||||
|
||||
[ACPCommand]
|
||||
public class IOBoardGroup
|
||||
{
|
||||
// 声明底层的 2 个 IO 板卡实例
|
||||
public IOBoard Board1 { get; private set; }
|
||||
public IOBoard Board2 { get; private set; }
|
||||
|
||||
private readonly byte _slaveAddress = 1; // 默认 Modbus 从站号为 1
|
||||
|
||||
// 核心:严格对齐图片数据的双层嵌套配置字典 (2个模块,1-8台架)
|
||||
private static readonly Dictionary<int, Dictionary<功能动作, IoPortInfo>> IoMapping
|
||||
= new Dictionary<int, Dictionary<功能动作, IoPortInfo>>()
|
||||
{
|
||||
{ 1, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 5) }, // Y6 -> 5
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 0) }, // Y1 -> 0
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 15) } // Y9 -> 8
|
||||
}},
|
||||
{ 2, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 6) }, // Y7 -> 6
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 2) }, // Y3 -> 2
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 14) } // Y10 -> 9
|
||||
}},
|
||||
{ 3, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 7) }, // Y8 -> 7
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 3) }, // Y4 -> 3
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 13) } // Y11 -> 10
|
||||
}},
|
||||
{ 4, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(1, 8) }, // Y9 -> 8
|
||||
{ 功能动作.抛负载, new IoPortInfo(1, 4) }, // Y5 -> 4
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 12) } // Y12 -> 11
|
||||
}},
|
||||
{ 5, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 4) }, // Y5 -> 4
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 0) }, // Y1 -> 0
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 11) } // Y13 -> 12
|
||||
}},
|
||||
{ 6, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 5) }, // Y6 -> 5
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 1) }, // Y2 -> 1
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 10) } // Y14 -> 13
|
||||
}},
|
||||
{ 7, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 6) }, // Y7 -> 6
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 2) }, // Y3 -> 2
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 9) } // Y15 -> 14
|
||||
}},
|
||||
{ 8, new Dictionary<功能动作, IoPortInfo> {
|
||||
{ 功能动作.单相充电, new IoPortInfo(2, 7) }, // Y8 -> 7
|
||||
{ 功能动作.抛负载, new IoPortInfo(2, 3) }, // Y4 -> 3
|
||||
{ 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1
|
||||
{ 功能动作.蜂鸣器报警, new IoPortInfo(2, 8) } // Y16 -> 15
|
||||
}}
|
||||
};
|
||||
|
||||
public IOBoardGroup(IOBoard board1, IOBoard board2)
|
||||
{
|
||||
Board1 = board1 ?? throw new ArgumentNullException(nameof(board1), "IOBoard Board1 实例不能为空。");
|
||||
Board2 = board2 ?? throw new ArgumentNullException(nameof(board2), "IOBoard Board2 实例不能为空。");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行指定台架的功能动作控制(带高低电平开关及底层安全互锁)
|
||||
/// </summary>
|
||||
/// <param name="台架序号">台架/工位序号 (1 - 8)</param>
|
||||
/// <param name="动作">控制功能类型 (单相充电, 抛负载, 短路测试, 蜂鸣器报警)</param>
|
||||
/// <param name="开关状态">开关状态 (true: 开启吸合, false: 关闭断开)</param>
|
||||
/// <param name="取消令牌">异步取消令牌</param>
|
||||
public async Task 执行台架动作Async(int 台架序号, 功能动作 动作, bool 开关状态, CancellationToken 取消令牌 = default)
|
||||
{
|
||||
// 1. 根据传入的台架序号和动作检索字典映射关系
|
||||
if (!IoMapping.TryGetValue(台架序号, out var 动作字典) || !动作字典.TryGetValue(动作, out var io信息))
|
||||
{
|
||||
throw new ArgumentException($"未找到台架 [{台架序号}] 对应功能 [{动作}] 的 IO 点位映射配置。");
|
||||
}
|
||||
|
||||
// 2. 软件防错高危互锁逻辑
|
||||
if (开关状态)
|
||||
{
|
||||
if (动作 == 功能动作.单相充电)
|
||||
{
|
||||
var 短路点位 = IoMapping[台架序号][功能动作.短路测试];
|
||||
await GetBoardInstance(短路点位.ModuleIndex).写输出开关(_slaveAddress, 短路点位.Address, false);
|
||||
}
|
||||
else if (动作 == 功能动作.短路测试)
|
||||
{
|
||||
var 充电点位 = IoMapping[台架序号][功能动作.单相充电];
|
||||
await GetBoardInstance(充电点位.ModuleIndex).写输出开关(_slaveAddress, 充电点位.Address, false);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 动态获取目标板卡实例并直接驱动开关端口
|
||||
IOBoard 目标板卡 = GetBoardInstance(io信息.ModuleIndex);
|
||||
await 目标板卡.写输出开关(_slaveAddress, io信息.Address, 开关状态);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部路由辅助方法:根据板卡索引安全获取当前连接的实例对象
|
||||
/// </summary>
|
||||
private IOBoard GetBoardInstance(int moduleIndex)
|
||||
{
|
||||
IOBoard board = moduleIndex switch
|
||||
{
|
||||
1 => Board1,
|
||||
2 => Board2,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(moduleIndex), "非法的板卡序号,系统仅支持 1# 和 2# 模块。")
|
||||
};
|
||||
|
||||
return board ?? throw new InvalidOperationException($"IO板卡模块 [{moduleIndex}#] 尚未完成初始化,无法建立总线通信。");
|
||||
}
|
||||
}
|
||||
}
|
||||
263
DeviceCommand/Devices/IT7800E.cs
Normal file
263
DeviceCommand/Devices/IT7800E.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
#region 枚举定义
|
||||
|
||||
/// <summary>
|
||||
/// 电源电气/耦合模式
|
||||
/// </summary>
|
||||
public enum PowerCouplingMode
|
||||
{
|
||||
/// <summary> 纯交流模式 </summary>
|
||||
AC,
|
||||
/// <summary> 纯直流模式 </summary>
|
||||
DC,
|
||||
/// <summary> 交直流混合模式 </summary>
|
||||
ACDC,
|
||||
/// <summary> 直交流混合模式 </summary>
|
||||
DCAC
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设备操作运行角色/模式
|
||||
/// </summary>
|
||||
public enum DeviceOperationMode
|
||||
{
|
||||
/// <summary> 电压源模式(常规源) </summary>
|
||||
VOLTage,
|
||||
/// <summary> 电子负载模式(放电拉载) </summary>
|
||||
LOAD,
|
||||
/// <summary> 电流源模式 </summary>
|
||||
CURRent
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
[ACPCommand]
|
||||
public class IT7800E : Tcp
|
||||
{
|
||||
// 八台产品共用一个,使用换行符 \n (ASCII 0x0A) 作为 SCPI 结束符[cite: 1]
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
public IT7800E(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 耦合模式与拉载开关控制
|
||||
|
||||
/// <summary>
|
||||
/// 设置电源输出源的电气模式/工作耦合模式 (AC, DC, ACDC, DCAC)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 设置电源模式(PowerCouplingMode 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SOURce:FUNCtion {模式}{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电源当前的工作电气模式[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电源模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SOURce:FUNCtion?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF"; //[cite: 1]
|
||||
await SendAsync($":OUTPut {参数}{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":OUTPut?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. 设定输出参数设置 (AC 电压/频率/DC 电压)
|
||||
|
||||
public virtual async Task 设置交流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置直流电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:DC {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置频率(double 频率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:FREQuency {0:F2}{1}", 频率, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
[Monitorable("交流电源电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
[Monitorable("交流电源电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
[Monitorable("交流电源功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询视在功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer:APParent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询实际频率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询功率因数(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":MEASure:POWer:PFACtor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. 保护参数设置与系统监控
|
||||
|
||||
public virtual async Task 设置过流保护_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:CURRent:PROTection:RMS {0:F3}{1}", 电流, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task 设置过压保护_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:PROTection:PEAK {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:REMote{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:LOCal{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 6. 充放电与能量统计扩展命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除最近的安时(Ah)与瓦时(Wh)统计数据[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 清除充放电电量统计(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"SENSe:AHOur:RESet{ScpiDelimiter}", ct); //[cite: 1]
|
||||
await SendAsync($"SENSe:WHOur:RESet{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前充放电累计的安时值 (单位: Ah)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询累计安时_Ah(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"FETCh:AHOur?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前充放电累计的瓦时值 (单位: Wh)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询累计瓦时_Wh(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"FETCh:WHOur?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取充放电积分持续时间 (单位: 秒)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询积分时间(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"FETCh:ENERgy:TIME?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换设备的操作模式 (VOLTage: 电源模式, LOAD: 负载模式/放电, CURRent: 电流源)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 设置操作模式(DeviceOperationMode 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"SYSTem:OPERation:MODE {模式}{ScpiDelimiter}", ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置放电截止电压/电压下限 (单位: V,用于防止电池过放)[cite: 1]
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压下限(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce:VOLTage:LIMit:LOW {0:F2}{1}", 电压, ScpiDelimiter); //[cite: 1]
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
386
DeviceCommand/Devices/N36200.cs
Normal file
386
DeviceCommand/Devices/N36200.cs
Normal file
@@ -0,0 +1,386 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
//[ACPCommand]
|
||||
public class N36200 : Tcp
|
||||
{
|
||||
//只有一个,八台产品一起用一个
|
||||
// 手册第 9 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化 N36200/N36300 设备通信参数。
|
||||
/// </summary>
|
||||
public N36200(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 3.1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.1. 清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.4. 读取直流电源相关信息(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.7. 恢复出厂设置 (注意:设备重置保存数据大约需要 10 秒)
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.8. 读取状态字节寄存器(只读寄存器,读取时不会清除位)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2. 设定输出电压与限流值
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1. 设定输出电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1. 查询输出电压设定值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"SOURce:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.2. 设置输出限流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.2. 查询输出限流值设定值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电流设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"SOURce:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.3. 设置输出模拟内阻值 (单位: mΩ)
|
||||
/// </summary>
|
||||
public virtual async Task 设置内阻(double 内阻, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "SOURce:INTErnalres {0:F1}{1}", 内阻, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.4. 保存当前测试参数到指定存储组 (范围: 1-10)
|
||||
/// </summary>
|
||||
public virtual async Task 保存测试参数(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 10) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~10");
|
||||
await SendAsync($"SOURce:FUNCtion:SAVe {组别}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.5. 调用指定存储组的测试参数 (范围: 1-10)
|
||||
/// </summary>
|
||||
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. 输出控制及状态
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.1. 控制电源输出开关 (True: 开启, False: 关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:ONOFF {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.1. 查询电源输出开关状态 (返回 "ON" 或 "OFF")
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出开关状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:ONOFF?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.2. 设定电源工作模式
|
||||
/// (NORMal: 普通模式, CHARge: 电池充电, SEQuence: 序列模式, CPOWer: 恒功率模式, CARWave: 汽车测试, APG: 外部编程)
|
||||
/// </summary>
|
||||
public virtual async Task 设置运行模式(string 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"OUTPut:MODE {模式}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.2. 查询电源当前运行工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询运行模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:MODE?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.11. 设置电源输出 CV/CC 优先权 (CV 或 CC)
|
||||
/// </summary>
|
||||
public virtual async Task 设置CVCC优先权(string 优先模式, CancellationToken ct = default)
|
||||
{
|
||||
if (优先模式 != "CV" && 优先模式 != "CC") throw new ArgumentException("优先模式只能为 'CV' 或 'CC'");
|
||||
await SendAsync($"OUTPut:PRIority {优先模式}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.12. 获取电源状态字(通过解析返回整数的二进制 Bit 位获取全状态环路及告警)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备状态字(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.13. 获取电源事件告警状态值 (返回整数通过位定义标识 UVP/OVP/OCP/OPP/OTP)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询事件告警状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"OUTPut:EVENT?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.13. 清除当前的告警状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"OUTPut:EVENT 0{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.16. 打开/关闭设备定时关机功能
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时关机开关(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:TIMing:SWITch {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.17. 设定设备定时关机倒计时时间 (单位: s)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.3.18. 控制泄放电路(Bleeder)开关状态
|
||||
/// </summary>
|
||||
public virtual async Task 设置泄放电路开关(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTPut:DISRes {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.4. 读取输出电压电流及功率值 (实时轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.1. 回读通道输出端子上的实时测得电压值 (单位: V)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流电源电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.2. 回读通道输出端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流电源电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.3. 回读通道输出端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流电源功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.4. 回读电池充电模式下当前累计已充入的容量值 (单位: mAh)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询已充电容量MAH(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:MAH?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.5. 获取当前电源的硬件额定电压上限值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 获取设备额定电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.4.6. 获取当前电源的硬件额定电流上限值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 获取设备额定电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5. 保护功能
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.1. 设置欠压保护门限值 (单位: V)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.4. 设置过压保护门限值 (单位: V)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.6. 设置硬件硬件过流保护硬指标参数 (单位: A)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.8. 设置硬件过功率保护门限值 (单位: W)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.12. 设置可调节的用户软输出电压下限值 (单位: V)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.13. 设置可调节的用户软输出电压上限值 (单位: V)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.14. 设置可调节的用户软输出电流下限值 (单位: A)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.15. 设置可调节的用户软输出电流上限值 (单位: A)
|
||||
/// </summary>
|
||||
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)
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1. 设定恒功率工作模式下的限定电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:VOLTage {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.2. 设定恒功率工作模式下的限定电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率模式电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CPOWer:CURRent {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.3. 设定恒功率工作模式下的运行功率目标值 (单位: W)
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
484
DeviceCommand/Devices/N36600.cs
Normal file
484
DeviceCommand/Devices/N36600.cs
Normal file
@@ -0,0 +1,484 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
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(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 建立 TCP 连接,成功后自动激活心跳
|
||||
/// </summary>
|
||||
public new async Task<bool> 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 公用命令
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.1 读取电源的相关信息(制造商, 产品标号, 产品序列号, 软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.2 从指定的单位中恢复 *SAV 命令保存的设定值 (参数:1~99)
|
||||
/// </summary>
|
||||
public virtual async Task 调用存储状态(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
|
||||
await SendAsync($"*RCL {组别}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.3 将仪器当前系统状态保存到非易失性内存中 (参数:1~99)
|
||||
/// </summary>
|
||||
public virtual async Task 保存当前状态(int 组别, CancellationToken ct = default)
|
||||
{
|
||||
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
|
||||
await SendAsync($"*SAV {组别}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.4 远程控制触发一次
|
||||
/// </summary>
|
||||
public virtual async Task 远程触发(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*TRG{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.1.5 返回状态、采集电压、采集电流 (格式: 状态码,电压值,电流值)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询全部状态及采样(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*ALL?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2 APPLy 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1 设置输出电压和输出电流值
|
||||
/// </summary>
|
||||
public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,规范应用 APPL 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "APPL {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.2.1 查询当前设定的输出电压和电流值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询快捷电压电流设定(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"APPL?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5 MEASure 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
|
||||
/// </summary>
|
||||
[Monitorable("低压直流电源电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:CURR?
|
||||
return await WriteReadAsync($"MEAS:CURR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
|
||||
/// </summary>
|
||||
[Monitorable("低压直流电源功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:POW?
|
||||
return await WriteReadAsync($"MEAS:POW?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
|
||||
/// </summary>
|
||||
[Monitorable("低压直流电源电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 MEAS:VOLT?
|
||||
return await WriteReadAsync($"MEAS:VOLT?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.4 查询通道输出端子上测得的电压、电流和功率的组合值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 3.5.5 查询通道输出端子上测得的输出时间长度
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"MEAS:TIME:OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6 OUTPut 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 设置电源输出开关
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用 OUTP 短格式
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTP {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.1 查询通道输出状态 (返回 ON 或 OFF)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.2 控制电源输出定时器的开关状态
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"OUTP:TIME {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.3 设定电源输出定时器的时间 (单位: s, 范围: 0.1 ~ 999999.9)
|
||||
/// </summary>
|
||||
public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "OUTP:TIME:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.6.4 设定电源输出的模式优先权 (CV优先 或 CC优先)
|
||||
/// </summary>
|
||||
public virtual async Task 设置输出模式优先(bool 是CV优先, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 模式 = 是CV优先 ? "CV" : "CC";
|
||||
await SendAsync($"OUTP:MODE {模式}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.7 SOURce 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.1 清除输出电流保护(OCP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"CURR:PROT:CLE{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.2 使能或禁止当前输出电流保护(OCP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,将 STATE 改为更常用的 STAT 简写
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($"CURR:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.4 设置输出电流保护(OCP)阀值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号。按 NGI 通用指令树,加上层级限制或者直接作用于 CURR:PROT
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR:PROT {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.1.7 设置电源的输出电流值 (A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 CURR 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR {0:F3}{1}", 电流, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.1 清除输出电压保护(OVP)电路状态
|
||||
/// </summary>
|
||||
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"VOLT:PROT:CLE{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.2 使能或禁止当前输出电压保护(OVP)电路
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($"VOLT:PROT:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.4 设置输出电压保护(OVP)阀值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:PROT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.7 设置电源的输出电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 VOLT 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.2.8 设定电压输出范围的上限电压值 (V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:LIM {0:F3}{1}", 电压, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 3.7.2.9 清除电压上限限制(恢复为设备物理允许的最大电压值)
|
||||
/// </summary>
|
||||
public virtual async Task 清除电压上限限制(CancellationToken ct = default)
|
||||
{
|
||||
// 发送 MAXimum 设为最大值以达到“清除限制”的效果
|
||||
await SendAsync($"VOLT:LIMIT MAX{SCPIDelimiter}", ct); // 对应手册 3.7.2.8
|
||||
}
|
||||
/// <summary>
|
||||
/// 3.7.3.1 设置输出功率值 (W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号,改用标准 POW 短格式
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "POW {0:F3}{1}", 功率, SCPIDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.7.3.2 禁止或使能当前恒功率(CP)输出
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 启用 ? "ON" : "OFF";
|
||||
await SendAsync($"POW:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.9 SYSTem 命令子系统
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.1 控制蜂鸣器开关
|
||||
/// </summary>
|
||||
public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
string 状态 = 开启 ? "ON" : "OFF";
|
||||
await SendAsync($"SYST:BEEP:STAT {状态}{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.2 使蜂鸣器强制鸣叫一声
|
||||
/// </summary>
|
||||
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:BEEP{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.4 查询仪器当前出错记录数量 (最大 18 组)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"SYST:ERR:COUN?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.5 读取仪器的出错信息 (成功返回 0,"No error")
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
return await WriteReadAsync($"SYST:ERR?{SCPIDelimiter}", SCPIDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.7 设置电源为面板控制模式 (本地 Local 状态,前面板按键可用)
|
||||
/// </summary>
|
||||
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:LOC{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.8 设置电源为远程控制模式 (Remote 状态)
|
||||
/// </summary>
|
||||
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:REM{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 3.9.9 通过通信接口设置电源为远程控制锁定模式
|
||||
/// </summary>
|
||||
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
|
||||
{
|
||||
// 修正:去除前缀冒号
|
||||
await SendAsync($"SYST:RWL{SCPIDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
575
DeviceCommand/Devices/N69200.cs
Normal file
575
DeviceCommand/Devices/N69200.cs
Normal file
@@ -0,0 +1,575 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class N69200 : Tcp
|
||||
{
|
||||
// 手册第 15 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化 N69200 电子负载通信参数。
|
||||
/// </summary>
|
||||
public N69200(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 3.1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列 (*CLS)
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 查询错误信息
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取电子负载设备识别字符串 (*IDN?)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使电子负载恢复到出厂默认配置状态 (*RST)
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标准状态字节寄存器 (*STB?)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前系统参数到指定的存储单元 (1~100)
|
||||
/// </summary>
|
||||
public virtual async Task 保存当前状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
if (单元 < 1 || 单元 > 100)
|
||||
throw new ArgumentOutOfRangeException(nameof(单元), "存储单元位置应在 1~100 之间");
|
||||
|
||||
await SendAsync($"*SAV {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调用指定存储单元中保存的系统参数 (1~100)
|
||||
/// </summary>
|
||||
public virtual async Task 调用存储状态(int 单元, CancellationToken ct = default)
|
||||
{
|
||||
if (单元 < 1 || 单元 > 100)
|
||||
throw new ArgumentOutOfRangeException(nameof(单元), "存储单元位置应在 1~100 之间");
|
||||
|
||||
await SendAsync($"*RCL {单元}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.2. 电子负载模式切换及拉载开关
|
||||
|
||||
/// <summary>
|
||||
/// 控制电子负载的输入控制开关(INPut:STATe <bool>)
|
||||
/// </summary>
|
||||
public virtual async Task 设置DC输入(bool 开启, CancellationToken ct = default)
|
||||
{
|
||||
string 参数 = 开启 ? "1" : "0";
|
||||
await SendAsync($"INPut:STATe {参数}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前的拉载开关状态 (返回 0 或 1)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询DC输入状态(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"INPut:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的基本工作模式 (INPut:FUNCtion <mode>)
|
||||
/// (支持 CC, CV, CR, CP, CCD, CVD, CRD, CPD, OCP, OPP, ESR, MPP, SWEEP 等)
|
||||
/// </summary>
|
||||
public enum DeviceWorkMode
|
||||
{
|
||||
CC,
|
||||
CV,
|
||||
CR,
|
||||
CP,
|
||||
CCD,
|
||||
ESR,
|
||||
AUTO,
|
||||
DISCHARGE,
|
||||
CHARGE,
|
||||
OCP,
|
||||
CVD,
|
||||
CRD,
|
||||
MPP,
|
||||
CVCC,
|
||||
CRCC,
|
||||
CPCC,
|
||||
CVCR,
|
||||
WAVE,
|
||||
SWEEP,
|
||||
OPP,
|
||||
CPD,
|
||||
SZ
|
||||
}
|
||||
public virtual async Task 设置负载模式(DeviceWorkMode 模式, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"INPut:FUNCtion {Enum.GetName(模式)}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询电子负载当前处于何种工作模式
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询负载模式(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"INPut:FUNCtion?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.3. 负载参数设置 (CC / CV / CP / CR 定态大量程)
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电流模式(CC)大量程下的拉载电流目标值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电流CC(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CC:HIGH:LEVel {0:F4}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电流模式大量程下设定的拉载电流值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电流CC设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CC:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电压模式(CV)大量程下的拉载电压目标值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电压CV(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CV:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电压模式大量程下设定的拉载电压值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电压CV设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CV:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒功率模式(CP)大量程下的拉载功率目标值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒功率CP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CP:HIGH:LEVel {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒功率模式大量程下设定的拉载功率值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒功率CP设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CP:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设定恒电阻模式(CR)大量程下的等效拉载电阻阻值 (单位: Ω)
|
||||
/// </summary>
|
||||
public virtual async Task 设置恒电阻CR(double 电阻, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "STATic:CR:HIGH:LEVel {0:F4}{1}", 电阻, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询恒电阻模式大量程下设定的拉载电阻值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询恒电阻CR设定(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"STATic:CR:HIGH:LEVel?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.4. 测量与数据回测(高频数据轮询核心)
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电压值 (单位: V)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流负载电压")]
|
||||
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得电流值 (单位: A)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流负载电流")]
|
||||
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 回读电子负载输入端子上的实时测得功率值 (单位: W)
|
||||
/// </summary>
|
||||
[Monitorable("高压直流负载功率")]
|
||||
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.5. 保护限制参数设置
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的电流软保护阈值 (单位: A, 设置为 0 表示关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过流保护值_OCP(double 电流, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:CURRent:PROTection {0:F3}{1}", 电流, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的功率软保护阈值 (单位: W, 设置为 0 表示关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过功率保护值_OPP(double 功率, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:POWer:PROTection {0:F3}{1}", 功率, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置电子负载的电压软保护阈值 (单位: V, 设置为 0 表示关闭)
|
||||
/// </summary>
|
||||
public virtual async Task 设置过压保护值_OVP(double 电压, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "INPut:VOLTage:PROTection {0:F3}{1}", 电压, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.6. 系统控制及报警清除
|
||||
|
||||
/// <summary>
|
||||
/// 读取负载报警/事件寄存器信息 (MEASure:EVENT?)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询报警事件信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"MEASure:EVENT?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除负载当前的保护触发锁定状态 (INPut:CLearPROTect)
|
||||
/// </summary>
|
||||
public virtual async Task 清除保护告警(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"INPut:CLearPROTect{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3.7. 手册第 112~124 页中的原子控制命令
|
||||
|
||||
#region 组合测试 (COMBination:*)
|
||||
|
||||
public virtual async Task 设置组合量程_CVCC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CVCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒压速率_CVCC(int 速率, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CVCC:HIGH:RATE {速率}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒压大量程电压_CVCC(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCC:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电流_CVCC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置组合量程_CRCC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CRCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒阻小量程电阻_CRCC(double 电阻, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:LEVel {0:F4}{1}", 电阻, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电流_CRCC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置上升斜率_CRCC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:SLEWRate:RAIse {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置下降斜率_CRCC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CRCC:LOW:SLEWRate:FALL {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置组合量程_CPCC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CPCC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒功率小量程功率_CPCC(double 功率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CPCC:LOW:LEVel {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电流_CPCC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CPCC:CLIMit {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置组合量程_CVCR(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"COMBination:CVCR:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置恒压大量程电压_CVCR(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCR:HIGH:LEVel {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置限定电阻_CVCR(double 电阻, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "COMBination:CVCR:CLIMit {0:F4}{1}", 电阻, ScpiDelimiter), ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 瞬态测试 (DYNAmic:*)
|
||||
|
||||
public virtual async Task 设置瞬态运行方式_CC(int 模式, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CC:MODe {模式}{ScpiDelimiter}", ct); // 0-连续, 1-单次, 2-A/B
|
||||
|
||||
public virtual async Task 设置瞬态量程_CC(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CC:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值电流_CC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:LEVel:MAIN {0:F4}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态值电流_CC(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:LEVel:TRANSient {0:F4}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态上升斜率_CC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:SLEWRate:RAIse {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态下降斜率_CC(double 斜率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:HIGH:SLEWRate:FALL {0:F1}{1}", 斜率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值脉宽_CC(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:WIDth:MAIN {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态脉宽_CC(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CC:WIDth:TRANSient {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态运行方式_CV(int 模式, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CV:MODe {模式}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置瞬态量程_CV(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"DYNAmic:CV:RANGE {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值电压_CV(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:HIGH:LEVel:MAIN {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态值电压_CV(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:HIGH:LEVel:TRANSient {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态主值脉宽_CV(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:WIDth:MAIN {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置瞬态瞬态脉宽_CV(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DYNAmic:CV:WIDth:TRANSient {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 充放电测试 (DISCHarge:* / CHARge:*)
|
||||
|
||||
public virtual async Task 设置放电电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:CURRent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置放电终止电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:VOLTage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置放电终止时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:TIMe {0:F0}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置放电终止容量(double 容量_Ah, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "DISCHarge:CAPacity {0:F3}{1}", 容量_Ah, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 查询放电实时时间(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"DISCHarge:ECHO:TIMe?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 查询放电实时容量(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"DISCHarge:ECHo:CAPacity?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task 设置充电电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "CHARge:CURRent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置充电电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "CHARge:VOLTage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置充电恒压时间(int 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync($"CHARge:TIMe {时间_s}{ScpiDelimiter}", ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region OCP / OPP 保护扫描测试 (OCP:* / OPP:*)
|
||||
|
||||
public virtual async Task 设置OCP初始电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:BCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OCP步进电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:SCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OCP单步时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:Time {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OCP终止电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OCP:EVOLtage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 查询OCP状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OCP:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取OCP结果电流(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OCP:RCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task 设置OPP初始功率(double 功率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:BPOWer {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OPP步进功率(double 功率, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:SPOWer {0:F3}{1}", 功率, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OPP单步时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:TIME {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置OPP终止电压(double 电压, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "OPP:EVOLtage {0:F3}{1}", 电压, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 查询OPP状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OPP:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取OPP结果功率(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"OPP:RPOWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 扫频 SWEEP 与 波形拉载 WAVE (SWEEP:* / WAVE:*)
|
||||
|
||||
public virtual async Task 设置扫频最小电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:TCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频最大电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:MCURrent {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频起始频率(double 频率_Hz, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:SFRequency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频结束频率(double 频率_Hz, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:EFRequency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频单步时间(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:STPTime {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置扫频占空比(double 占空比, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "SWEEP:DUTYcycle {0:F1}{1}", 占空比, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task<string> 读取扫频当前运行频率(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"SWEEP:CFRequency?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取扫频测试结果(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"SWEEP:RESult?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task 设置波形量程(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"WAVE:RANGe {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置波形类型(int 类型, CancellationToken ct = default)
|
||||
=> await SendAsync($"WAVE:TYPe {类型}{ScpiDelimiter}", ct); // 0-正弦波, 1-方波, 2-三角波, 3-锯齿波, 4-自定义
|
||||
|
||||
public virtual async Task 设置波形频率(double 频率_Hz, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:FREQuency {0:F1}{1}", 频率_Hz, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置波形幅值(double 幅值, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:SETValue {0:F3}{1}", 幅值, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置波形时间(double 时间_ms, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:TIMe {0:F1}{1}", 时间_ms, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置波形叠加值(double 叠加值, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "WAVE:BVALue {0:F3}{1}", 叠加值, ScpiDelimiter), ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内阻测试 ESR (ESR:*)
|
||||
|
||||
public virtual async Task 设置ESR带载量程(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"ESR:LOADRange {量程}{ScpiDelimiter}", ct);
|
||||
|
||||
public virtual async Task 设置ESR大量程带载电流(double 电流, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "ESR:CURRenthigh {0:F3}{1}", 电流, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置ESR测量方法(int 方法, CancellationToken ct = default)
|
||||
=> await SendAsync($"ESR:MODE {方法}{ScpiDelimiter}", ct); // 0-方波平均法, 1-单脉冲法
|
||||
|
||||
public virtual async Task 设置ESR采样量程(int 量程, CancellationToken ct = default)
|
||||
=> await SendAsync($"ESR:MEASurerange {量程}{ScpiDelimiter}", ct); // 0-1000mV, 1-100mV, 2-10mV
|
||||
|
||||
public virtual async Task<string> 查询ESR状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"ESR:STAtus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取ESR结果(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"ESR:RESult?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#region MPPT 测试 (MPP:*)
|
||||
|
||||
public virtual async Task 设置MPPT步进电压(double 电压_V, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "MPP:BVOLtage {0:F3}{1}", 电压_V, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置MPPT步进时间(double 时间_s, CancellationToken ct = default)
|
||||
=> await SendAsync(string.Format(CultureInfo.InvariantCulture, "MPP:TIME {0:F1}{1}", 时间_s, ScpiDelimiter), ct);
|
||||
|
||||
public virtual async Task 设置MPPT模式(int 模式, CancellationToken ct = default)
|
||||
=> await SendAsync($"MPP:MODE {模式}{ScpiDelimiter}", ct); // 0-扫描模式, 1-跟踪模式
|
||||
|
||||
public virtual async Task<string> 查询MPPT状态(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:STATus?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT最大功率(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:MPOWer?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT最大功率电压(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:MVOLtage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT最大功率电流(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:MCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT开路电压(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:OVOLtage?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
public virtual async Task<string> 读取MPPT短路电流(CancellationToken ct = default)
|
||||
=> await WriteReadAsync($"MPP:SCURrent?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
326
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
326
DeviceCommand/Devices/SDS2000X_HD.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class SDS2000X_HD : Tcp
|
||||
{
|
||||
// 示波器底层 Socket 字符串命令以换行符 \n 结束
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化示波器通信参数。
|
||||
/// </summary>
|
||||
public SDS2000X_HD(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除状态(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 查询错误信息
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1]
|
||||
}
|
||||
/// <summary>
|
||||
/// 读取示波器识别字符串(制造商、型号、序列号、固件版本)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使示波器恢复到默认的出厂设置
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询先前操作是否完成。
|
||||
/// </summary>
|
||||
public virtual async Task<bool> 检查操作完成_OPC(CancellationToken ct = default)
|
||||
{
|
||||
string res = await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
return res.Trim() == "1";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. 运行与捕获控制 (Run / Stop / Single)
|
||||
|
||||
/// <summary>
|
||||
/// 控制示波器开始捕获波形
|
||||
/// </summary>
|
||||
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":TRIGger:RUN{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止捕获波形
|
||||
/// </summary>
|
||||
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"TRIGger:STOP{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制示波器进入单次触发捕获模式
|
||||
/// </summary>
|
||||
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":TRIGger:MODE SINGle{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发一次波形采样
|
||||
/// </summary>
|
||||
public virtual async Task 强制触发(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"::TRIGger:MODE FTRIG{ScpiDelimiter}", ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 示波器触发控制模式(符合 SDS2000X-HD 规范)。
|
||||
/// </summary>
|
||||
public enum TriggerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动触发模式 (即使无触发信号也周期性刷屏)
|
||||
/// </summary>
|
||||
AUTO,
|
||||
|
||||
/// <summary>
|
||||
/// 普通触发模式 (仅当满足触发条件时才刷新)
|
||||
/// </summary>
|
||||
NORM,
|
||||
|
||||
/// <summary>
|
||||
/// 单次触发模式 (捕捉到一次满足条件的信号后立刻 STOP)
|
||||
/// </summary>
|
||||
SINGLE
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置触发模式 (AUTO 自动, NORM 普通, SINGLE 单次)
|
||||
/// </summary>
|
||||
/// <param name="mode">触发模式枚举</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
public virtual async Task 设置触发模式(TriggerMode mode, CancellationToken ct = default)
|
||||
{
|
||||
// 例如发送:TRMD AUTO\n、TRMD NORM\n 或 TRMD SINGLE\n
|
||||
string cmd = $"TRMD {mode}{ScpiDelimiter}";
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
||||
|
||||
/// <summary>
|
||||
/// 开启或关闭指定的模拟通道 (符合手册 57 页规范)
|
||||
/// </summary>
|
||||
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
||||
{
|
||||
string state = enable ? "ON" : "OFF";
|
||||
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的垂直电压档位 (Volts/Div)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道当前的电压档位
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询通道电压档位(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"C{channel}:VDIV?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的垂直偏移量 (Offset)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
/// <summary>
|
||||
/// 示波器通道输入阻抗类型。
|
||||
/// </summary>
|
||||
public enum ImpedanceType
|
||||
{
|
||||
/// <summary>50Ω 阻抗</summary>
|
||||
FIFty,
|
||||
|
||||
/// <summary>1MΩ 阻抗</summary>
|
||||
ONEM
|
||||
}
|
||||
public virtual async Task 设置通道阻抗(int channel, ImpedanceType impedance, CancellationToken ct = default)
|
||||
{
|
||||
// 例如发送:C1:IMPedance FIFty\n 或 C1:IMPedance ONEM\n
|
||||
string cmd = $"CHANnel{channel}:IMPedance {impedance}{ScpiDelimiter}";
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 4. Timebase 水平时基子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的水平时基档位 (Time/Div)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置示波器的触发水平延迟位置 (Horizontal Delay)
|
||||
/// </summary>
|
||||
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
||||
{
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 5. Trigger 触发子系统
|
||||
|
||||
/// <summary>
|
||||
/// 设置边沿触发的电平值 (Trigger Level)
|
||||
/// </summary>
|
||||
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
||||
{
|
||||
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:EDGE:LEVel {0:F3}{1}", level, ScpiDelimiter);
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置边沿触发的触发源 (例如: C1, C2, C3, C4, EX, LINE)
|
||||
/// </summary>
|
||||
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"TRSE EDGE,SR,{source.ToUpper()}{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 6. Measure 自动测量参数回读
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道自动测量项的当前实时测量数值
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
||||
{
|
||||
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的电压峰峰值 (Vpp)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "PKPK", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的频率值 (Frequency)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "FREQ", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的真均方根电压值 (Vrms)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
||||
{
|
||||
return await 查询通道测量项参数(channel, "RMS", ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 7. 屏幕截图导出子系统
|
||||
/// 【一键获取并保存截图】
|
||||
/// 自动下发 :PRINt? PNG 指令,接收示波器返回的纯 PNG 二进制流并直接保存到指定路径。
|
||||
/// </summary>
|
||||
/// <param name="saveFilePath">本地绝对保存路径 (例如: @"D:\Oscilloscope\Screen_01.png")</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
/// <returns>返回是否获取并保存成功</returns>
|
||||
public virtual async Task<bool> 获取屏幕图像并保存(string saveFilePath, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. 调用我们在基类 Tcp 中全新实现的 ReadRawAsync
|
||||
// 它会在内部下发 ":PRINt? PNG\n",自动接收完整的网络字节流
|
||||
byte[] pureImageBytes = await ReadAllBytesAsync($":PRINt? PNG{ScpiDelimiter}", ct);
|
||||
|
||||
// 2. 校验返回数据
|
||||
if (pureImageBytes == null || pureImageBytes.Length < 8)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("错误:接收到的图片数据为空或长度不足。");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 核心校验:验证是否为标准的 PNG 文件格式 (PNG 头通常为 89 50 4E 47)
|
||||
if (pureImageBytes[0] != 0x89 || pureImageBytes[1] != 'P' || pureImageBytes[2] != 'N' || pureImageBytes[3] != 'G')
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("错误:接收到的二进制数据不符合标准 PNG 格式头!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 如果传入的目标文件夹路径不存在,自动帮其创建
|
||||
string directory = Path.GetDirectoryName(saveFilePath);
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
// 5. 使用文件流直接将 PNG 字节数据落地写入本地硬盘
|
||||
using (FileStream fs = new FileStream(saveFilePath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
await fs.WriteAsync(pureImageBytes, 0, pureImageBytes.Length, ct);
|
||||
await fs.FlushAsync(ct); // 强制刷新,确保数据完整落地
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"成功:截图已保存至路径: {saveFilePath}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"崩溃:保存截图时发生未知异常: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
259
DeviceCommand/Devices/SPAW7000.cs
Normal file
259
DeviceCommand/Devices/SPAW7000.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using Common.Attributes;
|
||||
using DeviceCommand.Base;
|
||||
using Model.Models;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Devices
|
||||
{
|
||||
[ACPCommand]
|
||||
public class SPAW7000 : Tcp
|
||||
{
|
||||
//只有两个,八台产品公用两两分组各用一个
|
||||
// 根据通用 SCPI 与远宽指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||
private const string ScpiDelimiter = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化 SPAW7000 功率分析记录仪通信参数。
|
||||
/// </summary>
|
||||
public SPAW7000(TcpConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
#region 1. IEEE 488.2 公共命令
|
||||
|
||||
/// <summary>
|
||||
/// 清除状态命令。清除标准事件状态寄存器和错误队列
|
||||
/// </summary>
|
||||
public virtual async Task 清除错误队列和状态字节(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||
}
|
||||
/// <summary>
|
||||
/// 查询错误信息 发hsl居然可以但是手册上没有而且软件上也用不了这个命令
|
||||
/// </summary>
|
||||
//public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
|
||||
//{
|
||||
// string query = string.Format(":SYSTem:ERRor?", ScpiDelimiter);
|
||||
// return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
//}
|
||||
/// <summary>
|
||||
/// 读取功率分析仪识别字符串(制造商、产品型号、系统 SN、软件版本号)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 复位命令。使功率分析仪恢复到出厂默认配置状态
|
||||
/// </summary>
|
||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取标准状态字节寄存器
|
||||
/// </summary>
|
||||
public virtual async Task<string> 读取状态字节(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($"*STB?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 2. NUMeric 核心数据测量与轮询 (动态单项读取)
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电压值 (单位: V)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电压(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 先配置 ITEM1 为指定通道的电压有效值 (URMS)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 URMS,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 再读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时 RMS 电流值 (单位: A)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际电流(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的电流有效值 (IRMS)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 IRMS,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时有功功率值 (单位: W)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询实际功率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的有功功率 (P)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 P,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的实时频率值 (单位: Hz)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询频率(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的电压频率 (FU)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 FU,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询指定通道的功率因数 (Power Factor)
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询功率因数(int channel, CancellationToken ct = default)
|
||||
{
|
||||
// 1. 配置 ITEM1 为指定通道的功率因数 (LAMBda)
|
||||
string setItem = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:ITEM1 LAMBda,{0}{1}", channel, ScpiDelimiter);
|
||||
await SendAsync(setItem, ct);
|
||||
|
||||
// 2. 读取 ITEM1 的值
|
||||
string query = string.Format(CultureInfo.InvariantCulture, ":NUMeric:NORMal:VALue? 1{0}", ScpiDelimiter);
|
||||
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 3. INPut 通道电气参数配置
|
||||
/// <summary>
|
||||
/// SPAW7000 电压量程枚举 (单位: V)
|
||||
/// </summary>
|
||||
public enum VoltageRange
|
||||
{
|
||||
V_15 = 15,
|
||||
V_30 = 30,
|
||||
V_60 = 60,
|
||||
V_100 = 100,
|
||||
V_150 = 150,
|
||||
V_300 = 300,
|
||||
V_600 = 600,
|
||||
V_1000 = 1000
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SPAW7000 电流量程枚举 (单位: A)
|
||||
/// </summary>
|
||||
public enum CurrentRange
|
||||
{
|
||||
A_1 = 1,
|
||||
A_2 = 2,
|
||||
A_5 = 5,
|
||||
A_10 = 10,
|
||||
A_20 = 20,
|
||||
A_50 = 50
|
||||
}
|
||||
/// <summary>
|
||||
/// 设置指定通道的电压量程
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号/单元号 (1 - 7)</param>
|
||||
/// <param name="range">电压量程枚举</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
public virtual async Task 设置电压量程(int channel, VoltageRange range, CancellationToken ct = default)
|
||||
{
|
||||
// 正确格式例如: :INPut:VOLTage:RANGe:ELEMent1 300\n
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture,
|
||||
":INPut:VOLTage:RANGe:ELEMent{0} {1}{2}",
|
||||
channel, (int)range, ScpiDelimiter);
|
||||
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置指定通道的电流量程
|
||||
/// </summary>
|
||||
/// <param name="channel">通道号/单元号 (1 - 7)</param>
|
||||
/// <param name="range">电流量程枚举</param>
|
||||
/// <param name="ct">取消令牌</param>
|
||||
public virtual async Task 设置电流量程(int channel, CurrentRange range, CancellationToken ct = default)
|
||||
{
|
||||
// 正确格式例如: :INPut:CURRent:RANGe:ELEMent1 5\n
|
||||
string cmd = string.Format(CultureInfo.InvariantCulture,
|
||||
":INPut:CURRent:RANGe:ELEMent{0} {1}{2}",
|
||||
channel, (int)range, ScpiDelimiter);
|
||||
|
||||
await SendAsync(cmd, ct);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 4. SYSTem 系统设置与状态查询
|
||||
/// <summary>
|
||||
/// 14. 查询仪器型号名称
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备型号(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:MODel?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 16. 查询仪器唯一序列号
|
||||
/// </summary>
|
||||
public virtual async Task<string> 查询设备序列号(CancellationToken ct = default)
|
||||
{
|
||||
return await WriteReadAsync($":SYSTem:SERial?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 15. 设置数值数据显示的分辨率 (5位或6位)
|
||||
/// </summary>
|
||||
/// <param name="resolution">有效值只能为 5 或 6</param>
|
||||
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); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 13. 设置或查询屏幕 LCD 的亮度级别 (1-10)
|
||||
/// </summary>
|
||||
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); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 18. 设置或查询触摸锁的开/关状态 (锁定时防止人工误触触控屏)
|
||||
/// </summary>
|
||||
public virtual async Task 设置屏幕触摸锁定(bool isLocked, CancellationToken ct = default)
|
||||
{
|
||||
string state = isLocked ? "ON" : "OFF";
|
||||
await SendAsync($":SYSTem:TLOCK {state}{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 17. 设置或读取分析仪当前的系统内部时间
|
||||
/// </summary>
|
||||
/// <param name="timeStr">格式必须为 "HH:MM:SS"</param>
|
||||
public virtual async Task 设置系统时间(string timeStr, CancellationToken ct = default)
|
||||
{
|
||||
await SendAsync($":SYSTem:TIME \"{timeStr}\"{ScpiDelimiter}", ct); //
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
193
DeviceCommand/Flexible/FModbusRTU.cs
Normal file
193
DeviceCommand/Flexible/FModbusRTU.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using Common.Attributes;
|
||||
using NModbus;
|
||||
using NModbus.Serial;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FModbusRTU
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
private static SerialPort CreatePort(
|
||||
string portName,
|
||||
int baudRate,
|
||||
int dataBits,
|
||||
StopBits stopBits,
|
||||
Parity parity,
|
||||
int readTimeout,
|
||||
int writeTimeout)
|
||||
{
|
||||
return new SerialPort(portName, baudRate, parity, dataBits, stopBits)
|
||||
{
|
||||
ReadTimeout = readTimeout > 0 ? readTimeout : SerialPort.InfiniteTimeout,
|
||||
WriteTimeout = writeTimeout > 0 ? writeTimeout : SerialPort.InfiniteTimeout
|
||||
};
|
||||
}
|
||||
|
||||
private static IModbusMaster CreateMaster(SerialPort port)
|
||||
{
|
||||
return new ModbusFactory().CreateRtuMaster(port);
|
||||
}
|
||||
|
||||
#region Holding Register
|
||||
|
||||
public static async Task<ushort[]> ReadHoldingRegistersAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (readTimeout > 0)
|
||||
cts.CancelAfter(readTimeout);
|
||||
|
||||
return await master
|
||||
.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleRegisterAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort registerAddress,
|
||||
ushort value,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (writeTimeout > 0)
|
||||
cts.CancelAfter(writeTimeout);
|
||||
|
||||
await master
|
||||
.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(writeTimeout),cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coil
|
||||
|
||||
public static async Task<bool[]> ReadCoilsAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (readTimeout > 0)
|
||||
cts.CancelAfter(readTimeout);
|
||||
|
||||
return await master
|
||||
.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(readTimeout),cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleCoilAsync(
|
||||
string portName,
|
||||
int baudRate,
|
||||
byte slaveAddress,
|
||||
ushort coilAddress,
|
||||
bool value,
|
||||
int dataBits = 8,
|
||||
StopBits stopBits = StopBits.One,
|
||||
Parity parity = Parity.None,
|
||||
int readTimeout = 3000,
|
||||
int writeTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, dataBits, stopBits, parity, readTimeout, writeTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
var master = CreateMaster(port);
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (writeTimeout > 0)
|
||||
cts.CancelAfter(writeTimeout);
|
||||
|
||||
await master
|
||||
.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(writeTimeout), cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
133
DeviceCommand/Flexible/FModbusTCP.cs
Normal file
133
DeviceCommand/Flexible/FModbusTCP.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Common.Attributes;
|
||||
using NModbus;
|
||||
using System;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FModbusTCP
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
private static async Task<IModbusMaster> ConnectAsync(string ipAddress, int port, int sendTimeout, int receiveTimeout, CancellationToken ct)
|
||||
{
|
||||
var tcpClient = new TcpClient();
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (sendTimeout > 0)
|
||||
cts.CancelAfter(sendTimeout);
|
||||
|
||||
await tcpClient.ConnectAsync(ipAddress, port, cts.Token);
|
||||
return new ModbusFactory().CreateMaster(tcpClient);
|
||||
}
|
||||
|
||||
#region Holding Registers
|
||||
|
||||
public static async Task<ushort[]> ReadHoldingRegistersAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
var result = await ((IModbusMaster)master)
|
||||
.ReadHoldingRegistersAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleRegisterAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort registerAddress,
|
||||
ushort value,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
await ((IModbusMaster)master)
|
||||
.WriteSingleRegisterAsync(slaveAddress, registerAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coils
|
||||
|
||||
public static async Task<bool[]> ReadCoilsAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort startAddress,
|
||||
ushort numberOfPoints,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
var result = await ((IModbusMaster)master)
|
||||
.ReadCoilsAsync(slaveAddress, startAddress, numberOfPoints)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(receiveTimeout), ct);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task WriteSingleCoilAsync(
|
||||
string ipAddress,
|
||||
int port,
|
||||
byte slaveAddress,
|
||||
ushort coilAddress,
|
||||
bool value,
|
||||
int sendTimeout = 3000,
|
||||
int receiveTimeout = 3000,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var master = await ConnectAsync(ipAddress, port, sendTimeout, receiveTimeout, ct) as IDisposable;
|
||||
await ((IModbusMaster)master)
|
||||
.WriteSingleCoilAsync(slaveAddress, coilAddress, value)
|
||||
.WaitAsync(TimeSpan.FromMilliseconds(sendTimeout),ct);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
104
DeviceCommand/Flexible/FSerialPort.cs
Normal file
104
DeviceCommand/Flexible/FSerialPort.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Common.Attributes;
|
||||
using System;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FSerialPort
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
private static SerialPort CreatePort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int sendTimeout,int receiveTimeout)
|
||||
{
|
||||
return new SerialPort(portName, baudRate, parity, dataBits, stopBits)
|
||||
{
|
||||
Encoding = Encoding.UTF8,
|
||||
WriteTimeout = sendTimeout > 0 ? sendTimeout : SerialPort.InfiniteTimeout,
|
||||
ReadTimeout = receiveTimeout > 0 ? receiveTimeout : SerialPort.InfiniteTimeout
|
||||
};
|
||||
}
|
||||
|
||||
#region 最常用:只发送字符串
|
||||
|
||||
public static async Task SendAsync(string portName,int baudRate,Parity parity,int dataBits,StopBits stopBits,int sendTimeout,int receiveTimeout,string command,CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (sendTimeout > 0)
|
||||
cts.CancelAfter(sendTimeout);
|
||||
|
||||
var bytes = port.Encoding.GetBytes(command);
|
||||
await port.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
|
||||
await port.BaseStream.FlushAsync(cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 最常用:发送字符串并读取字符串
|
||||
|
||||
public static async Task<string> SendReadAsync(string portName,int baudRate, Parity parity, int dataBits, StopBits stopBits,int sendTimeout, int receiveTimeout,string command,string delimiter = "\n", CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var port = CreatePort(
|
||||
portName, baudRate, parity, dataBits, stopBits, sendTimeout, receiveTimeout);
|
||||
|
||||
port.Open();
|
||||
|
||||
// Send
|
||||
var sendBytes = port.Encoding.GetBytes(command);
|
||||
await port.BaseStream.WriteAsync(sendBytes, 0, sendBytes.Length, ct);
|
||||
await port.BaseStream.FlushAsync(ct);
|
||||
|
||||
// Read
|
||||
delimiter ??= "\n";
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[256];
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (receiveTimeout > 0)
|
||||
cts.CancelAfter(receiveTimeout);
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
int read = await port.BaseStream.ReadAsync(
|
||||
buffer, 0, buffer.Length, cts.Token);
|
||||
|
||||
if (read == 0)
|
||||
break;
|
||||
|
||||
sb.Append(port.Encoding.GetString(buffer, 0, read));
|
||||
|
||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
return sb.ToString(0, index).Trim();
|
||||
}
|
||||
|
||||
throw new TimeoutException("串口读取超时");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
115
DeviceCommand/Flexible/FTCP.cs
Normal file
115
DeviceCommand/Flexible/FTCP.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Common.Attributes;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace DeviceCommand.Flexible
|
||||
{
|
||||
[ACPCommand]
|
||||
public static class FTCP
|
||||
{
|
||||
private static readonly SemaphoreSlim _commLock = new(1, 1);
|
||||
|
||||
#region Send
|
||||
|
||||
public static async Task SendAsync(string ipAddress,int port,int sendTimeout, byte[] buffer, CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var client = new TcpClient();
|
||||
client.SendTimeout = sendTimeout;
|
||||
|
||||
await client.ConnectAsync(ipAddress, port, ct);
|
||||
|
||||
using NetworkStream stream = client.GetStream();
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, ct).WaitAsync(TimeSpan.FromMilliseconds(sendTimeout));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static Task SendAsync(string ipAddress, int port,int sendTimeout, string text,CancellationToken ct = default)
|
||||
{
|
||||
return SendAsync( ipAddress, port, sendTimeout, Encoding.UTF8.GetBytes(text),ct);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
|
||||
public static async Task<byte[]> ReadAsync(string ipAddress,int port,int receiveTimeout,int length,CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var client = new TcpClient();
|
||||
client.ReceiveTimeout = receiveTimeout;
|
||||
|
||||
await client.ConnectAsync(ipAddress, port, ct);
|
||||
|
||||
using NetworkStream stream = client.GetStream();
|
||||
byte[] buffer = new byte[length];
|
||||
int offset = 0;
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (receiveTimeout > 0)
|
||||
cts.CancelAfter(receiveTimeout);
|
||||
|
||||
while (offset < length)
|
||||
{
|
||||
int read = await stream.ReadAsync(buffer, offset, length - offset, cts.Token);
|
||||
if (read == 0) break;
|
||||
offset += read;
|
||||
}
|
||||
|
||||
return buffer[..offset];
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<string> ReadLineAsync( string ipAddress, int port, int receiveTimeout, string delimiter = "\n",CancellationToken ct = default)
|
||||
{
|
||||
await _commLock.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
using var client = new TcpClient();
|
||||
client.ReceiveTimeout = receiveTimeout;
|
||||
|
||||
await client.ConnectAsync(ipAddress, port, ct);
|
||||
|
||||
using NetworkStream stream = client.GetStream();
|
||||
var sb = new StringBuilder();
|
||||
byte[] buffer = new byte[1024];
|
||||
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
if (receiveTimeout > 0)
|
||||
cts.CancelAfter(receiveTimeout);
|
||||
|
||||
while (!cts.Token.IsCancellationRequested)
|
||||
{
|
||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
||||
if (bytesRead == 0) break;
|
||||
|
||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||
|
||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
||||
if (index >= 0)
|
||||
return sb.ToString(0, index).Trim();
|
||||
}
|
||||
|
||||
throw new TimeoutException("读取超时或对端关闭");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_commLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
32
DeviceEditModule/DeviceEditModule.cs
Normal file
32
DeviceEditModule/DeviceEditModule.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using DeviceEditModule.ViewModels;
|
||||
using DeviceEditModule.Views;
|
||||
|
||||
namespace DeviceEditModule
|
||||
{
|
||||
public class DeviceEditModule : IModule
|
||||
{
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
// DialogMangerView 作为弹窗宿主,注册为 Dialog(支持 _dialogService.Show)
|
||||
containerRegistry.RegisterDialog<DialogMangerView, DialogMangerViewModel>("DialogMangerView");
|
||||
|
||||
// 设备编辑 View 注册为 Navigation(被动态解析后作为 Tab 内容嵌入 DialogMangerView)
|
||||
containerRegistry.RegisterForNavigation<IT7800EView>("IT7800EView");
|
||||
containerRegistry.RegisterForNavigation<N36200View>("N36200View");
|
||||
containerRegistry.RegisterForNavigation<N36600View>("N36600View");
|
||||
containerRegistry.RegisterForNavigation<N69200View>("N69200View");
|
||||
containerRegistry.RegisterForNavigation<SDS2000X_HDView>("SDS2000X_HDView");
|
||||
containerRegistry.RegisterForNavigation<SPAW7000View>("SPAW7000View");
|
||||
containerRegistry.Register<IT7800EViewModel>();
|
||||
containerRegistry.Register<N36200ViewModel>();
|
||||
containerRegistry.Register<N36600ViewModel>();
|
||||
containerRegistry.Register<N69200ViewModel>();
|
||||
containerRegistry.Register<SDS2000X_HDViewModel>();
|
||||
containerRegistry.Register<SPAW7000ViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
DeviceEditModule/DeviceEditModule.csproj
Normal file
15
DeviceEditModule/DeviceEditModule.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include=".\..\UIShare\UIShare.csproj" />
|
||||
<ProjectReference Include=".\..\DeviceCommand\DeviceCommand.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
153
DeviceEditModule/ViewModels/DialogMangerViewModel.cs
Normal file
153
DeviceEditModule/ViewModels/DialogMangerViewModel.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using UIShare.PubEvent;
|
||||
using UIShare.UIViewModel;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace DeviceEditModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 弹窗管理器 ViewModel。
|
||||
/// <para>
|
||||
/// 职责:<br/>
|
||||
/// 1. 订阅 <see cref="AddDialogTabEvent"/>,将外部弹窗注册为 Tab;<br/>
|
||||
/// 2. 维护 <see cref="TagItems"/> 集合,控制选中/关闭逻辑;<br/>
|
||||
/// 3. 最小化:通过 <see cref="MinimizeRequested"/> 事件通知 View 执行 P/Invoke 窗口操作;<br/>
|
||||
/// 4. 关闭:调用 <see cref="RequestClose"/>。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class DialogMangerViewModel : DialogViewModelBase, IDisposable
|
||||
{
|
||||
#region 属性
|
||||
|
||||
/// <summary>所有 Tab 项集合,绑定到标签条的 ItemsControl。</summary>
|
||||
public ObservableCollection<DialogTabItemVM> TagItems { get; } = new();
|
||||
|
||||
/// <summary>以设备指纹为 key 追踪已打开的 Tab,防止同一物理设备重复打开。</summary>
|
||||
private readonly Dictionary<string, DialogTabItemVM> _tabDictionary = new();
|
||||
|
||||
private DialogTabItemVM? _selectedTag;
|
||||
/// <summary>当前激活的 Tab,其 Content 显示在内容区。</summary>
|
||||
public DialogTabItemVM? SelectedTag
|
||||
{
|
||||
get => _selectedTag;
|
||||
set => SetProperty(ref _selectedTag, value);
|
||||
}
|
||||
|
||||
/// <summary>是否没有任何 Tab(用于绑定空状态提示的 Visibility)。</summary>
|
||||
public bool HasNoTabs => TagItems.Count == 0;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand MinimizeCommand { get; }
|
||||
public ICommand CloseCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件
|
||||
|
||||
/// <summary>
|
||||
/// View 订阅此事件后,在事件回调里执行 P/Invoke 最小化。
|
||||
/// 这样 ViewModel 无需引用任何 UI 或 Win32 类型。
|
||||
/// </summary>
|
||||
public event EventHandler? MinimizeRequested;
|
||||
public event EventHandler? RestoreRequested;
|
||||
#endregion
|
||||
|
||||
public DialogMangerViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
MinimizeCommand = new DelegateCommand(OnMinimize);
|
||||
CloseCommand = new DelegateCommand(OnClose);
|
||||
|
||||
// 订阅来自其他模块的"添加 Tab"事件
|
||||
_eventAggregator.GetEvent<AddDialogTabEvent>().Subscribe(OnAddTab, ThreadOption.UIThread);
|
||||
_eventAggregator.GetEvent<CancelMinimizeEvent>().Subscribe(()=>RestoreRequested.Invoke(this, EventArgs.Empty));
|
||||
}
|
||||
|
||||
#region 命令处理
|
||||
|
||||
private void OnMinimize() => MinimizeRequested?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
private void OnClose()
|
||||
{
|
||||
// 关闭前清空所有 Tab,释放内容引用,并清除去重字典
|
||||
_tabDictionary.Clear();
|
||||
TagItems.Clear();
|
||||
SelectedTag = null;
|
||||
RequestClose.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tab 管理
|
||||
|
||||
/// <summary>收到事件:若指纹已存在则激活,否则创建并追加到集合。</summary>
|
||||
private void OnAddTab(DialogTabInfo info)
|
||||
{
|
||||
// 已有相同指纹的 Tab → 直接激活,不重复创建
|
||||
if (!string.IsNullOrEmpty(info.Fingerprint) && _tabDictionary.TryGetValue(info.Fingerprint, out var existing))
|
||||
{
|
||||
ActivateTab(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
var tab = new DialogTabItemVM(OnSelectTab, OnCloseTab)
|
||||
{
|
||||
Title = info.Title,
|
||||
Fingerprint = info.Fingerprint,
|
||||
Content = info.Content
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(info.Fingerprint))
|
||||
_tabDictionary[info.Fingerprint] = tab;
|
||||
|
||||
TagItems.Add(tab);
|
||||
RaisePropertyChanged(nameof(HasNoTabs));
|
||||
ActivateTab(tab);
|
||||
}
|
||||
|
||||
/// <summary>激活(选中)指定 Tab,其余全部取消选中。</summary>
|
||||
private void OnSelectTab(DialogTabItemVM tab) => ActivateTab(tab);
|
||||
|
||||
/// <summary>关闭指定 Tab,自动选中相邻 Tab(或清空内容区)。</summary>
|
||||
private void OnCloseTab(DialogTabItemVM tab)
|
||||
{
|
||||
int idx = TagItems.IndexOf(tab);
|
||||
if (!string.IsNullOrEmpty(tab.Fingerprint))
|
||||
_tabDictionary.Remove(tab.Fingerprint);
|
||||
TagItems.Remove(tab);
|
||||
RaisePropertyChanged(nameof(HasNoTabs));
|
||||
|
||||
if (TagItems.Count == 0)
|
||||
{
|
||||
SelectedTag = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 优先选左侧邻 Tab,无左侧则选当前索引(已向左移一位)
|
||||
ActivateTab(TagItems[Math.Max(0, idx - 1)]);
|
||||
}
|
||||
|
||||
private void ActivateTab(DialogTabItemVM tab)
|
||||
{
|
||||
foreach (var t in TagItems)
|
||||
t.IsSelected = false;
|
||||
|
||||
tab.IsSelected = true;
|
||||
SelectedTag = tab;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventAggregator.GetEvent<AddDialogTabEvent>().Unsubscribe(OnAddTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
310
DeviceEditModule/ViewModels/IT7800EViewModel.cs
Normal file
310
DeviceEditModule/ViewModels/IT7800EViewModel.cs
Normal file
@@ -0,0 +1,310 @@
|
||||
using DeviceCommand.Devices;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace DeviceEditModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// IT7800E 交直流电源控制面板 ViewModel。
|
||||
/// <para>
|
||||
/// 注册为 Navigation View,既可由 Region 导航进入,
|
||||
/// 也可由外部直接实例化后作为 Tab 内容塞入 DialogMangerView:
|
||||
/// <code>
|
||||
/// var view = container.Resolve<IT7800EView>();
|
||||
/// (view.DataContext as IT7800EViewModel)?.Initialize("IT7800E");
|
||||
/// _eventAggregator.GetEvent<AddDialogTabEvent>().Publish(
|
||||
/// new DialogTabInfo { Title = "IT7800E", Content = view });
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class IT7800EViewModel : NavigateViewModelBase, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
|
||||
private readonly DeviceManager _deviceManager;
|
||||
private IT7800E? _device;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备信息属性
|
||||
|
||||
private string _deviceName = "IT7800E";
|
||||
public string DeviceName
|
||||
{
|
||||
get => _deviceName;
|
||||
set => SetProperty(ref _deviceName, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private bool _isBusy;
|
||||
/// <summary>正在执行设备命令时为 true,用于 UI 忙碌状态指示。</summary>
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 输入参数属性
|
||||
|
||||
private double _acVoltage = 220.0;
|
||||
/// <summary>待设置的交流电压值(V)。</summary>
|
||||
public double AcVoltage
|
||||
{
|
||||
get => _acVoltage;
|
||||
set => SetProperty(ref _acVoltage, value);
|
||||
}
|
||||
|
||||
private double _dcVoltage = 0.0;
|
||||
/// <summary>待设置的直流偏置电压值(V)。</summary>
|
||||
public double DcVoltage
|
||||
{
|
||||
get => _dcVoltage;
|
||||
set => SetProperty(ref _dcVoltage, value);
|
||||
}
|
||||
|
||||
private double _frequency = 50.0;
|
||||
/// <summary>待设置的交流频率(Hz)。</summary>
|
||||
public double Frequency
|
||||
{
|
||||
get => _frequency;
|
||||
set => SetProperty(ref _frequency, value);
|
||||
}
|
||||
|
||||
private double _currentLimit = 10.0;
|
||||
/// <summary>待设置的限流值(A)。</summary>
|
||||
public double CurrentLimit
|
||||
{
|
||||
get => _currentLimit;
|
||||
set => SetProperty(ref _currentLimit, value);
|
||||
}
|
||||
|
||||
private PowerCouplingMode _selectedMode = PowerCouplingMode.AC;
|
||||
/// <summary>待设置的电源工作模式:AC / DC / ACDC。</summary>
|
||||
public PowerCouplingMode SelectedMode
|
||||
{
|
||||
get => _selectedMode;
|
||||
set => SetProperty(ref _selectedMode, value);
|
||||
}
|
||||
|
||||
private double _ovpValue = 260.0;
|
||||
/// <summary>过压保护值(V)。</summary>
|
||||
public double OvpValue
|
||||
{
|
||||
get => _ovpValue;
|
||||
set => SetProperty(ref _ovpValue, value);
|
||||
}
|
||||
|
||||
private double _ocpValue = 15.0;
|
||||
/// <summary>过流保护值(A)。</summary>
|
||||
public double OcpValue
|
||||
{
|
||||
get => _ocpValue;
|
||||
set => SetProperty(ref _ocpValue, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测量结果属性
|
||||
|
||||
private string _measuredVoltage = "—";
|
||||
public string MeasuredVoltage
|
||||
{
|
||||
get => _measuredVoltage;
|
||||
set => SetProperty(ref _measuredVoltage, value);
|
||||
}
|
||||
|
||||
private string _measuredCurrent = "—";
|
||||
public string MeasuredCurrent
|
||||
{
|
||||
get => _measuredCurrent;
|
||||
set => SetProperty(ref _measuredCurrent, value);
|
||||
}
|
||||
|
||||
private string _measuredPower = "—";
|
||||
public string MeasuredPower
|
||||
{
|
||||
get => _measuredPower;
|
||||
set => SetProperty(ref _measuredPower, value);
|
||||
}
|
||||
|
||||
private string _measuredFrequency = "—";
|
||||
public string MeasuredFrequency
|
||||
{
|
||||
get => _measuredFrequency;
|
||||
set => SetProperty(ref _measuredFrequency, value);
|
||||
}
|
||||
|
||||
private string _responseLog = string.Empty;
|
||||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||
public string ResponseLog
|
||||
{
|
||||
get => _responseLog;
|
||||
set => SetProperty(ref _responseLog, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand QueryIdentityCommand { get; }
|
||||
public ICommand ResetDeviceCommand { get; }
|
||||
public ICommand OutputOnCommand { get; }
|
||||
public ICommand OutputOffCommand { get; }
|
||||
public ICommand SetModeCommand { get; }
|
||||
public ICommand SetAcVoltageCommand { get; }
|
||||
public ICommand SetDcVoltageCommand { get; }
|
||||
public ICommand SetFrequencyCommand { get; }
|
||||
public ICommand SetCurrentCommand { get; }
|
||||
public ICommand QueryAllMeasureCommand { get; }
|
||||
public ICommand SetRemoteModeCommand { get; }
|
||||
public ICommand SetLocalModeCommand { get; }
|
||||
public ICommand SetOvpCommand { get; }
|
||||
public ICommand SetOcpCommand { get; }
|
||||
public ICommand ClearAlarmCommand { get; }
|
||||
public ICommand ClearErrorCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public IT7800EViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||
|
||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||
OutputOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(true, Ct()); AppendLog("输出已开启"); }));
|
||||
OutputOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(false, Ct()); AppendLog("输出已关闭"); }));
|
||||
SetModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电源模式(SelectedMode, Ct()); AppendLog($"模式已设为 {SelectedMode}"); }));
|
||||
SetAcVoltageCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置交流电压(AcVoltage, Ct()); AppendLog($"AC电压已设为 {AcVoltage} V"); }));
|
||||
SetDcVoltageCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置直流电压(DcVoltage, Ct()); AppendLog($"DC偏置已设为 {DcVoltage} V"); }));
|
||||
SetFrequencyCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置频率(Frequency, Ct()); AppendLog($"频率已设为 {Frequency} Hz"); }));
|
||||
SetCurrentCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流(CurrentLimit, Ct()); AppendLog($"限流已设为 {CurrentLimit} A"); }));
|
||||
SetOvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过压保护_OVP(OvpValue, Ct()); AppendLog($"OVP已设为 {OvpValue} V"); }));
|
||||
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护_OCP(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
||||
SetRemoteModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换远程控制模式(Ct()); AppendLog("已切换到远程控制模式"); }));
|
||||
SetLocalModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换本地控制模式(Ct()); AppendLog("已切换到本地控制模式"); }));
|
||||
ClearAlarmCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除保护告警(Ct()); AppendLog("保护告警已清除"); }));
|
||||
ClearErrorCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除错误队列和状态字节(Ct()); AppendLog("错误队列已清除"); }));
|
||||
|
||||
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
MeasuredVoltage = await _device!.查询实际电压(Ct());
|
||||
MeasuredCurrent = await _device!.查询实际电流(Ct());
|
||||
MeasuredPower = await _device!.查询实际功率(Ct());
|
||||
MeasuredFrequency = await _device!.查询实际频率(Ct());
|
||||
AppendLog($"测量 → 电压:{MeasuredVoltage}V 电流:{MeasuredCurrent}A 功率:{MeasuredPower}W 频率:{MeasuredFrequency}Hz");
|
||||
}));
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region 初始化 / Navigation
|
||||
|
||||
/// <summary>
|
||||
/// 从 DeviceManager 中查找 IT7800E 设备实例。
|
||||
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
|
||||
/// </summary>
|
||||
public void Initialize(string? deviceName = null)
|
||||
{
|
||||
IT7800E? found = null;
|
||||
string? foundName = null;
|
||||
|
||||
if (deviceName != null &&
|
||||
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||
d is IT7800E e)
|
||||
{
|
||||
found = e;
|
||||
foundName = deviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kv in _deviceManager.DeviceMap)
|
||||
{
|
||||
if (kv.Value is IT7800E it)
|
||||
{
|
||||
found = it;
|
||||
foundName = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_device = found;
|
||||
DeviceName = foundName ?? "IT7800E (未找到)";
|
||||
IsConnected = _device?.IsConnected ?? false;
|
||||
|
||||
AppendLog(found != null
|
||||
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||
: "未在 DeviceManager 中找到 IT7800E 设备,请先初始化设备配置。");
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext context)
|
||||
{
|
||||
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||
Initialize(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助
|
||||
|
||||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||
|
||||
private async Task Exec(Func<Task> action)
|
||||
{
|
||||
if (_device == null)
|
||||
{
|
||||
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||
return;
|
||||
}
|
||||
if (IsBusy) return;
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await action();
|
||||
IsConnected = _device.IsConnected;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
AppendLog("命令超时或已取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"错误:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
ResponseLog = ResponseLog.Length > 4000
|
||||
? line + "\n" + ResponseLog[..3000]
|
||||
: line + "\n" + ResponseLog;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
309
DeviceEditModule/ViewModels/N36200ViewModel.cs
Normal file
309
DeviceEditModule/ViewModels/N36200ViewModel.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
using DeviceCommand.Devices;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace DeviceEditModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// N36200 宽范围可编程直流电源控制面板 ViewModel。
|
||||
/// <para>
|
||||
/// 注册为 Navigation View,可通过 EventAggregator 添加为 DialogMangerView 中的 Tab:
|
||||
/// <code>
|
||||
/// var view = container.Resolve<N36200View>();
|
||||
/// (view.DataContext as N36200ViewModel)?.Initialize("N36200");
|
||||
/// _eventAggregator.GetEvent<AddDialogTabEvent>().Publish(
|
||||
/// new DialogTabInfo { Title = "N36200", Content = view });
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class N36200ViewModel : NavigateViewModelBase, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
|
||||
private readonly DeviceManager _deviceManager;
|
||||
private N36200? _device;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备信息属性
|
||||
|
||||
private string _deviceName = "N36200";
|
||||
public string DeviceName
|
||||
{
|
||||
get => _deviceName;
|
||||
set => SetProperty(ref _deviceName, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private bool _isBusy;
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 输入参数属性
|
||||
|
||||
private double _voltage = 12.0;
|
||||
/// <summary>待设置的输出电压值(V)。</summary>
|
||||
public double Voltage
|
||||
{
|
||||
get => _voltage;
|
||||
set => SetProperty(ref _voltage, value);
|
||||
}
|
||||
|
||||
private double _currentLimit = 5.0;
|
||||
/// <summary>待设置的限流值(A)。</summary>
|
||||
public double CurrentLimit
|
||||
{
|
||||
get => _currentLimit;
|
||||
set => SetProperty(ref _currentLimit, value);
|
||||
}
|
||||
|
||||
private string _selectedMode = "NORMal";
|
||||
/// <summary>待设置的运行模式:NORMal / CHARge / SEQuence / CPOWer / CARWave / APG。</summary>
|
||||
public string SelectedMode
|
||||
{
|
||||
get => _selectedMode;
|
||||
set => SetProperty(ref _selectedMode, value);
|
||||
}
|
||||
|
||||
private double _ovpValue = 15.0;
|
||||
/// <summary>过压保护值(V)。</summary>
|
||||
public double OvpValue
|
||||
{
|
||||
get => _ovpValue;
|
||||
set => SetProperty(ref _ovpValue, value);
|
||||
}
|
||||
|
||||
private double _ocpValue = 6.0;
|
||||
/// <summary>过流保护值(A)。</summary>
|
||||
public double OcpValue
|
||||
{
|
||||
get => _ocpValue;
|
||||
set => SetProperty(ref _ocpValue, value);
|
||||
}
|
||||
|
||||
private double _uvpValue = 0.0;
|
||||
/// <summary>欠压保护值(V)。</summary>
|
||||
public double UvpValue
|
||||
{
|
||||
get => _uvpValue;
|
||||
set => SetProperty(ref _uvpValue, value);
|
||||
}
|
||||
|
||||
private double _oppValue = 100.0;
|
||||
/// <summary>过功率保护值(W)。</summary>
|
||||
public double OppValue
|
||||
{
|
||||
get => _oppValue;
|
||||
set => SetProperty(ref _oppValue, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测量结果属性
|
||||
|
||||
private string _measuredVoltage = "—";
|
||||
public string MeasuredVoltage
|
||||
{
|
||||
get => _measuredVoltage;
|
||||
set => SetProperty(ref _measuredVoltage, value);
|
||||
}
|
||||
|
||||
private string _measuredCurrent = "—";
|
||||
public string MeasuredCurrent
|
||||
{
|
||||
get => _measuredCurrent;
|
||||
set => SetProperty(ref _measuredCurrent, value);
|
||||
}
|
||||
|
||||
private string _measuredPower = "—";
|
||||
public string MeasuredPower
|
||||
{
|
||||
get => _measuredPower;
|
||||
set => SetProperty(ref _measuredPower, value);
|
||||
}
|
||||
|
||||
private string _deviceStatus = "—";
|
||||
/// <summary>设备状态字(OUTPut:STATe? 查询结果)。</summary>
|
||||
public string DeviceStatus
|
||||
{
|
||||
get => _deviceStatus;
|
||||
set => SetProperty(ref _deviceStatus, value);
|
||||
}
|
||||
|
||||
private string _responseLog = string.Empty;
|
||||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||
public string ResponseLog
|
||||
{
|
||||
get => _responseLog;
|
||||
set => SetProperty(ref _responseLog, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand QueryIdentityCommand { get; }
|
||||
public ICommand ResetDeviceCommand { get; }
|
||||
public ICommand OutputOnCommand { get; }
|
||||
public ICommand OutputOffCommand { get; }
|
||||
public ICommand SetVoltageCommand { get; }
|
||||
public ICommand SetCurrentCommand { get; }
|
||||
public ICommand SetModeCommand { get; }
|
||||
public ICommand QueryAllMeasureCommand { get; }
|
||||
public ICommand QueryStatusCommand { get; }
|
||||
public ICommand SetOvpCommand { get; }
|
||||
public ICommand SetOcpCommand { get; }
|
||||
public ICommand SetUvpCommand { get; }
|
||||
public ICommand SetOppCommand { get; }
|
||||
public ICommand ClearAlarmCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public N36200ViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||
|
||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置(耗时约10s)"); }));
|
||||
OutputOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(true, Ct()); AppendLog("输出已开启"); }));
|
||||
OutputOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(false, Ct()); AppendLog("输出已关闭"); }));
|
||||
SetVoltageCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电压(Voltage, Ct()); AppendLog($"电压已设为 {Voltage} V"); }));
|
||||
SetCurrentCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流(CurrentLimit, Ct()); AppendLog($"限流已设为 {CurrentLimit} A"); }));
|
||||
SetModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置运行模式(SelectedMode, Ct()); AppendLog($"模式已设为 {SelectedMode}"); }));
|
||||
SetOvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过压保护_OVP(OvpValue, Ct()); AppendLog($"OVP已设为 {OvpValue} V"); }));
|
||||
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护_OCP(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
||||
SetUvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过欠压保护_UVP(UvpValue, Ct()); AppendLog($"UVP已设为 {UvpValue} V"); }));
|
||||
SetOppCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过功率保护_OPP(OppValue, Ct()); AppendLog($"OPP已设为 {OppValue} W"); }));
|
||||
ClearAlarmCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除告警(Ct()); AppendLog("告警已清除"); }));
|
||||
|
||||
QueryStatusCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
DeviceStatus = await _device!.查询设备状态字(Ct());
|
||||
AppendLog($"状态字: {DeviceStatus}");
|
||||
}));
|
||||
|
||||
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
MeasuredVoltage = await _device!.查询实际电压(Ct());
|
||||
MeasuredCurrent = await _device!.查询实际电流(Ct());
|
||||
MeasuredPower = await _device!.查询实际功率(Ct());
|
||||
AppendLog($"测量 → 电压:{MeasuredVoltage}V 电流:{MeasuredCurrent}A 功率:{MeasuredPower}W");
|
||||
}));
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region 初始化 / Navigation
|
||||
|
||||
/// <summary>
|
||||
/// 从 DeviceManager 中查找 N36200 设备实例。
|
||||
/// 优先按 <paramref name="deviceName"/> 查找,否则取第一个匹配类型的设备。
|
||||
/// </summary>
|
||||
public void Initialize(string? deviceName = null)
|
||||
{
|
||||
N36200? found = null;
|
||||
string? foundName = null;
|
||||
|
||||
if (deviceName != null &&
|
||||
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||
d is N36200 n)
|
||||
{
|
||||
found = n;
|
||||
foundName = deviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kv in _deviceManager.DeviceMap)
|
||||
{
|
||||
if (kv.Value is N36200 n36)
|
||||
{
|
||||
found = n36;
|
||||
foundName = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_device = found;
|
||||
DeviceName = foundName ?? "N36200 (未找到)";
|
||||
IsConnected = _device?.IsConnected ?? false;
|
||||
|
||||
AppendLog(found != null
|
||||
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||
: "未在 DeviceManager 中找到 N36200 设备,请先初始化设备配置。");
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext context)
|
||||
{
|
||||
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||
Initialize(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助
|
||||
|
||||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||
|
||||
private async Task Exec(Func<Task> action)
|
||||
{
|
||||
if (_device == null)
|
||||
{
|
||||
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||
return;
|
||||
}
|
||||
if (IsBusy) return;
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await action();
|
||||
IsConnected = _device.IsConnected;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
AppendLog("命令超时或已取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"错误:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
ResponseLog = ResponseLog.Length > 4000
|
||||
? line + "\n" + ResponseLog[..3000]
|
||||
: line + "\n" + ResponseLog;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
280
DeviceEditModule/ViewModels/N36600ViewModel.cs
Normal file
280
DeviceEditModule/ViewModels/N36600ViewModel.cs
Normal file
@@ -0,0 +1,280 @@
|
||||
using DeviceCommand.Devices;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace DeviceEditModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// N36600 便携式宽范围可编程直流电源控制面板 ViewModel。
|
||||
/// </summary>
|
||||
public class N36600ViewModel : NavigateViewModelBase, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
|
||||
private readonly DeviceManager _deviceManager;
|
||||
private N36600? _device;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备信息属性
|
||||
|
||||
private string _deviceName = "N36600";
|
||||
public string DeviceName
|
||||
{
|
||||
get => _deviceName;
|
||||
set => SetProperty(ref _deviceName, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private bool _isBusy;
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 输入参数属性
|
||||
|
||||
private double _voltage = 12.0;
|
||||
/// <summary>待设置的输出电压值(V)。</summary>
|
||||
public double Voltage
|
||||
{
|
||||
get => _voltage;
|
||||
set => SetProperty(ref _voltage, value);
|
||||
}
|
||||
|
||||
private double _currentLimit = 5.0;
|
||||
/// <summary>待设置的输出电流值(A)。</summary>
|
||||
public double CurrentLimit
|
||||
{
|
||||
get => _currentLimit;
|
||||
set => SetProperty(ref _currentLimit, value);
|
||||
}
|
||||
|
||||
private double _power = 60.0;
|
||||
/// <summary>待设置的输出功率值(W)。</summary>
|
||||
public double Power
|
||||
{
|
||||
get => _power;
|
||||
set => SetProperty(ref _power, value);
|
||||
}
|
||||
|
||||
private double _ovpValue = 15.0;
|
||||
/// <summary>过压保护值(V)。</summary>
|
||||
public double OvpValue
|
||||
{
|
||||
get => _ovpValue;
|
||||
set => SetProperty(ref _ovpValue, value);
|
||||
}
|
||||
|
||||
private double _ocpValue = 6.0;
|
||||
/// <summary>过流保护值(A)。</summary>
|
||||
public double OcpValue
|
||||
{
|
||||
get => _ocpValue;
|
||||
set => SetProperty(ref _ocpValue, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测量结果属性
|
||||
|
||||
private string _measuredVoltage = "—";
|
||||
public string MeasuredVoltage
|
||||
{
|
||||
get => _measuredVoltage;
|
||||
set => SetProperty(ref _measuredVoltage, value);
|
||||
}
|
||||
|
||||
private string _measuredCurrent = "—";
|
||||
public string MeasuredCurrent
|
||||
{
|
||||
get => _measuredCurrent;
|
||||
set => SetProperty(ref _measuredCurrent, value);
|
||||
}
|
||||
|
||||
private string _measuredPower = "—";
|
||||
public string MeasuredPower
|
||||
{
|
||||
get => _measuredPower;
|
||||
set => SetProperty(ref _measuredPower, value);
|
||||
}
|
||||
|
||||
private string _outputState = "—";
|
||||
/// <summary>当前 DC 输出状态(OUTPut? 查询结果)。</summary>
|
||||
public string OutputState
|
||||
{
|
||||
get => _outputState;
|
||||
set => SetProperty(ref _outputState, value);
|
||||
}
|
||||
|
||||
private string _responseLog = string.Empty;
|
||||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||
public string ResponseLog
|
||||
{
|
||||
get => _responseLog;
|
||||
set => SetProperty(ref _responseLog, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand QueryIdentityCommand { get; }
|
||||
public ICommand OutputOnCommand { get; }
|
||||
public ICommand OutputOffCommand { get; }
|
||||
public ICommand SetVoltageCommand { get; }
|
||||
public ICommand SetCurrentCommand { get; }
|
||||
public ICommand SetPowerCommand { get; }
|
||||
public ICommand SetOvpCommand { get; }
|
||||
public ICommand SetOcpCommand { get; }
|
||||
public ICommand QueryAllMeasureCommand { get; }
|
||||
public ICommand QueryOutputStateCommand { get; }
|
||||
public ICommand SetRemoteModeCommand { get; }
|
||||
public ICommand SetLocalModeCommand { get; }
|
||||
public ICommand ClearVoltageProtectionCommand { get; }
|
||||
public ICommand ClearCurrentProtectionCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public N36600ViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||
|
||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||
OutputOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(true, Ct()); AppendLog("输出已开启"); }));
|
||||
OutputOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(false, Ct()); AppendLog("输出已关闭"); }));
|
||||
SetVoltageCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电压(Voltage, Ct()); AppendLog($"电压已设为 {Voltage} V"); }));
|
||||
SetCurrentCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流(CurrentLimit, Ct()); AppendLog($"电流已设为 {CurrentLimit} A"); }));
|
||||
SetPowerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置功率(Power, Ct()); AppendLog($"功率已设为 {Power} W"); }));
|
||||
SetOvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过压保护值(OvpValue, Ct()); AppendLog($"OVP已设为 {OvpValue} V"); }));
|
||||
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护值(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
||||
SetRemoteModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换远程控制模式(Ct()); AppendLog("已切换到远程控制模式"); }));
|
||||
SetLocalModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换本地控制模式(Ct()); AppendLog("已切换到本地控制模式"); }));
|
||||
ClearVoltageProtectionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除电压保护状态(Ct()); AppendLog("电压保护状态已清除"); }));
|
||||
ClearCurrentProtectionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除电流保护状态(Ct()); AppendLog("电流保护状态已清除"); }));
|
||||
|
||||
QueryOutputStateCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
OutputState = await _device!.查询DC输出状态(Ct());
|
||||
AppendLog($"输出状态: {OutputState}");
|
||||
}));
|
||||
|
||||
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
MeasuredVoltage = await _device!.查询实际电压(Ct());
|
||||
MeasuredCurrent = await _device!.查询实际电流(Ct());
|
||||
MeasuredPower = await _device!.查询实际功率(Ct());
|
||||
AppendLog($"测量 → 电压:{MeasuredVoltage}V 电流:{MeasuredCurrent}A 功率:{MeasuredPower}W");
|
||||
}));
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region 初始化 / Navigation
|
||||
|
||||
public void Initialize(string? deviceName = null)
|
||||
{
|
||||
N36600? found = null;
|
||||
string? foundName = null;
|
||||
|
||||
if (deviceName != null &&
|
||||
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||
d is N36600 n)
|
||||
{
|
||||
found = n;
|
||||
foundName = deviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kv in _deviceManager.DeviceMap)
|
||||
{
|
||||
if (kv.Value is N36600 n36)
|
||||
{
|
||||
found = n36;
|
||||
foundName = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_device = found;
|
||||
DeviceName = foundName ?? "N36600 (未找到)";
|
||||
IsConnected = _device?.IsConnected ?? false;
|
||||
|
||||
AppendLog(found != null
|
||||
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||
: "未在 DeviceManager 中找到 N36600 设备,请先初始化设备配置。");
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext context)
|
||||
{
|
||||
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||
Initialize(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助
|
||||
|
||||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||
|
||||
private async Task Exec(Func<Task> action)
|
||||
{
|
||||
if (_device == null)
|
||||
{
|
||||
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||
return;
|
||||
}
|
||||
if (IsBusy) return;
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await action();
|
||||
IsConnected = _device.IsConnected;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
AppendLog("命令超时或已取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"错误:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
ResponseLog = ResponseLog.Length > 4000
|
||||
? line + "\n" + ResponseLog[..3000]
|
||||
: line + "\n" + ResponseLog;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
314
DeviceEditModule/ViewModels/N69200ViewModel.cs
Normal file
314
DeviceEditModule/ViewModels/N69200ViewModel.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
using DeviceCommand.Devices;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
using static DeviceCommand.Devices.N69200;
|
||||
|
||||
namespace DeviceEditModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// N69200 可编程直流电子负载控制面板 ViewModel。
|
||||
/// </summary>
|
||||
public class N69200ViewModel : NavigateViewModelBase, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
|
||||
private readonly DeviceManager _deviceManager;
|
||||
private N69200? _device;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备信息属性
|
||||
|
||||
private string _deviceName = "N69200";
|
||||
public string DeviceName
|
||||
{
|
||||
get => _deviceName;
|
||||
set => SetProperty(ref _deviceName, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private bool _isBusy;
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 输入参数属性
|
||||
|
||||
private DeviceWorkMode _selectedMode = DeviceWorkMode.CC;
|
||||
/// <summary>工作模式:CC / CV / CP / CR。</summary>
|
||||
public DeviceWorkMode SelectedMode
|
||||
{
|
||||
get => _selectedMode;
|
||||
set => SetProperty(ref _selectedMode, value);
|
||||
}
|
||||
|
||||
private double _loadValue = 1.0;
|
||||
/// <summary>设定值(根据 SelectedMode 代表 A / V / W / Ω)。</summary>
|
||||
public double LoadValue
|
||||
{
|
||||
get => _loadValue;
|
||||
set => SetProperty(ref _loadValue, value);
|
||||
}
|
||||
|
||||
private double _ovpValue = 60.0;
|
||||
/// <summary>过压保护值(V)。</summary>
|
||||
public double OvpValue
|
||||
{
|
||||
get => _ovpValue;
|
||||
set => SetProperty(ref _ovpValue, value);
|
||||
}
|
||||
|
||||
private double _ocpValue = 10.0;
|
||||
/// <summary>过流保护值(A)。</summary>
|
||||
public double OcpValue
|
||||
{
|
||||
get => _ocpValue;
|
||||
set => SetProperty(ref _ocpValue, value);
|
||||
}
|
||||
|
||||
private double _oppValue = 200.0;
|
||||
/// <summary>过功率保护值(W)。</summary>
|
||||
public double OppValue
|
||||
{
|
||||
get => _oppValue;
|
||||
set => SetProperty(ref _oppValue, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测量结果属性
|
||||
|
||||
private string _measuredVoltage = "—";
|
||||
public string MeasuredVoltage
|
||||
{
|
||||
get => _measuredVoltage;
|
||||
set => SetProperty(ref _measuredVoltage, value);
|
||||
}
|
||||
|
||||
private string _measuredCurrent = "—";
|
||||
public string MeasuredCurrent
|
||||
{
|
||||
get => _measuredCurrent;
|
||||
set => SetProperty(ref _measuredCurrent, value);
|
||||
}
|
||||
|
||||
private string _measuredPower = "—";
|
||||
public string MeasuredPower
|
||||
{
|
||||
get => _measuredPower;
|
||||
set => SetProperty(ref _measuredPower, value);
|
||||
}
|
||||
|
||||
private string _currentMode = "—";
|
||||
/// <summary>当前负载模式(MODE? 查询结果)。</summary>
|
||||
public string CurrentMode
|
||||
{
|
||||
get => _currentMode;
|
||||
set => SetProperty(ref _currentMode, value);
|
||||
}
|
||||
|
||||
private string _statusByte = "—";
|
||||
/// <summary>状态字节(*STB? 查询结果)。</summary>
|
||||
public string StatusByte
|
||||
{
|
||||
get => _statusByte;
|
||||
set => SetProperty(ref _statusByte, value);
|
||||
}
|
||||
|
||||
private string _responseLog = string.Empty;
|
||||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||
public string ResponseLog
|
||||
{
|
||||
get => _responseLog;
|
||||
set => SetProperty(ref _responseLog, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand QueryIdentityCommand { get; }
|
||||
public ICommand ResetDeviceCommand { get; }
|
||||
public ICommand InputOnCommand { get; }
|
||||
public ICommand InputOffCommand { get; }
|
||||
public ICommand SetModeCommand { get; }
|
||||
public ICommand QueryModeCommand { get; }
|
||||
public ICommand SetLoadValueCommand { get; }
|
||||
public ICommand QueryAllMeasureCommand { get; }
|
||||
public ICommand QueryStatusCommand { get; }
|
||||
public ICommand SetOvpCommand { get; }
|
||||
public ICommand SetOcpCommand { get; }
|
||||
public ICommand SetOppCommand { get; }
|
||||
public ICommand ClearAlarmCommand { get; }
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
public N69200ViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||
|
||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||
InputOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输入(true, Ct()); AppendLog("输入已开启"); }));
|
||||
InputOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输入(false, Ct()); AppendLog("输入已关闭"); }));
|
||||
SetModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置负载模式(SelectedMode, Ct()); AppendLog($"模式已设为 {SelectedMode}"); }));
|
||||
QueryModeCommand = new DelegateCommand(async () => await Exec(async () => { CurrentMode = await _device!.查询负载模式(Ct()); AppendLog($"当前模式: {CurrentMode}"); }));
|
||||
SetOvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过压保护值_OVP(OvpValue, Ct()); AppendLog($"OVP已设为 {OvpValue} V"); }));
|
||||
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护值_OCP(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
||||
SetOppCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过功率保护值_OPP(OppValue, Ct()); AppendLog($"OPP已设为 {OppValue} W"); }));
|
||||
ClearAlarmCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除保护告警(Ct()); AppendLog("保护告警已清除"); }));
|
||||
|
||||
|
||||
SetLoadValueCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
switch (SelectedMode)
|
||||
{
|
||||
case DeviceWorkMode.CC:
|
||||
await _device!.设置恒电流CC(LoadValue, Ct());
|
||||
AppendLog($"恒流 CC 已设为 {LoadValue} A");
|
||||
break;
|
||||
case DeviceWorkMode.CV:
|
||||
await _device!.设置恒电压CV(LoadValue, Ct());
|
||||
AppendLog($"恒压 CV 已设为 {LoadValue} V");
|
||||
break;
|
||||
case DeviceWorkMode.CP:
|
||||
await _device!.设置恒功率CP(LoadValue, Ct());
|
||||
AppendLog($"恒功率 CP 已设为 {LoadValue} W");
|
||||
break;
|
||||
case DeviceWorkMode.CR:
|
||||
await _device!.设置恒电阻CR(LoadValue, Ct());
|
||||
AppendLog($"恒电阻 CR 已设为 {LoadValue} Ω");
|
||||
break;
|
||||
default:
|
||||
AppendLog($"模式 {SelectedMode} 不支持设置设定值");
|
||||
break;
|
||||
}
|
||||
}));
|
||||
|
||||
QueryStatusCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
StatusByte = await _device!.读取状态字节(Ct());
|
||||
AppendLog($"状态字节: {StatusByte}");
|
||||
}));
|
||||
|
||||
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
MeasuredVoltage = await _device!.查询实际电压(Ct());
|
||||
MeasuredCurrent = await _device!.查询实际电流(Ct());
|
||||
MeasuredPower = await _device!.查询实际功率(Ct());
|
||||
AppendLog($"测量 → 电压:{MeasuredVoltage}V 电流:{MeasuredCurrent}A 功率:{MeasuredPower}W");
|
||||
}));
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region 初始化 / Navigation
|
||||
|
||||
public void Initialize(string? deviceName = null)
|
||||
{
|
||||
N69200? found = null;
|
||||
string? foundName = null;
|
||||
|
||||
if (deviceName != null &&
|
||||
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||
d is N69200 n)
|
||||
{
|
||||
found = n;
|
||||
foundName = deviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kv in _deviceManager.DeviceMap)
|
||||
{
|
||||
if (kv.Value is N69200 n69)
|
||||
{
|
||||
found = n69;
|
||||
foundName = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_device = found;
|
||||
DeviceName = foundName ?? "N69200 (未找到)";
|
||||
IsConnected = _device?.IsConnected ?? false;
|
||||
|
||||
AppendLog(found != null
|
||||
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||
: "未在 DeviceManager 中找到 N69200 设备,请先初始化设备配置。");
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext context)
|
||||
{
|
||||
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||
Initialize(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助
|
||||
|
||||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||
|
||||
private async Task Exec(Func<Task> action)
|
||||
{
|
||||
if (_device == null)
|
||||
{
|
||||
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||
return;
|
||||
}
|
||||
if (IsBusy) return;
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await action();
|
||||
IsConnected = _device.IsConnected;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
AppendLog("命令超时或已取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"错误:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
ResponseLog = ResponseLog.Length > 4000
|
||||
? line + "\n" + ResponseLog[..3000]
|
||||
: line + "\n" + ResponseLog;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
387
DeviceEditModule/ViewModels/SDS2000X_HDViewModel.cs
Normal file
387
DeviceEditModule/ViewModels/SDS2000X_HDViewModel.cs
Normal file
@@ -0,0 +1,387 @@
|
||||
using DeviceCommand.Devices;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
using static DeviceCommand.Devices.SDS2000X_HD;
|
||||
|
||||
namespace DeviceEditModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// SDS2000X_HD 数字存储示波器控制面板 ViewModel。
|
||||
/// </summary>
|
||||
public class SDS2000X_HDViewModel : NavigateViewModelBase, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
|
||||
private readonly DeviceManager _deviceManager;
|
||||
private SDS2000X_HD? _device;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备信息属性
|
||||
|
||||
private string _deviceName = "SDS2000X_HD";
|
||||
public string DeviceName
|
||||
{
|
||||
get => _deviceName;
|
||||
set => SetProperty(ref _deviceName, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private bool _isBusy;
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 输入参数属性
|
||||
|
||||
private int _channel = 1;
|
||||
/// <summary>当前操作通道(1~4)。</summary>
|
||||
public int Channel
|
||||
{
|
||||
get => _channel;
|
||||
set => SetProperty(ref _channel, value);
|
||||
}
|
||||
|
||||
private double _voltsPerDiv = 1.0;
|
||||
/// <summary>垂直电压档位(V/div)。</summary>
|
||||
public double VoltsPerDiv
|
||||
{
|
||||
get => _voltsPerDiv;
|
||||
set => SetProperty(ref _voltsPerDiv, value);
|
||||
}
|
||||
|
||||
private double _offset = 0.0;
|
||||
/// <summary>垂直偏移(V)。</summary>
|
||||
public double Offset
|
||||
{
|
||||
get => _offset;
|
||||
set => SetProperty(ref _offset, value);
|
||||
}
|
||||
|
||||
private bool _is50Ohm;
|
||||
/// <summary>是否使用 50Ω 输入阻抗,false 为 1MΩ。</summary>
|
||||
public bool Is50Ohm
|
||||
{
|
||||
get => _is50Ohm;
|
||||
set => SetProperty(ref _is50Ohm, value);
|
||||
}
|
||||
|
||||
private double _timeBase = 0.001;
|
||||
/// <summary>水平时基档位(s/div)。</summary>
|
||||
public double TimeBase
|
||||
{
|
||||
get => _timeBase;
|
||||
set => SetProperty(ref _timeBase, value);
|
||||
}
|
||||
|
||||
private double _triggerLevel = 0.0;
|
||||
/// <summary>触发电平(V)。</summary>
|
||||
public double TriggerLevel
|
||||
{
|
||||
get => _triggerLevel;
|
||||
set => SetProperty(ref _triggerLevel, value);
|
||||
}
|
||||
|
||||
private string _triggerSource = "C1";
|
||||
/// <summary>触发源(C1/C2/C3/C4/EX/LINE)。</summary>
|
||||
public string TriggerSource
|
||||
{
|
||||
get => _triggerSource;
|
||||
set => SetProperty(ref _triggerSource, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测量结果属性
|
||||
|
||||
private string _measuredVpp = "—";
|
||||
public string MeasuredVpp
|
||||
{
|
||||
get => _measuredVpp;
|
||||
set => SetProperty(ref _measuredVpp, value);
|
||||
}
|
||||
|
||||
private string _measuredFrequency = "—";
|
||||
public string MeasuredFrequency
|
||||
{
|
||||
get => _measuredFrequency;
|
||||
set => SetProperty(ref _measuredFrequency, value);
|
||||
}
|
||||
|
||||
private string _measuredRms = "—";
|
||||
public string MeasuredRms
|
||||
{
|
||||
get => _measuredRms;
|
||||
set => SetProperty(ref _measuredRms, value);
|
||||
}
|
||||
|
||||
private string _responseLog = string.Empty;
|
||||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||
public string ResponseLog
|
||||
{
|
||||
get => _responseLog;
|
||||
set => SetProperty(ref _responseLog, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand QueryIdentityCommand { get; }
|
||||
public ICommand ResetDeviceCommand { get; }
|
||||
public ICommand RunCommand { get; }
|
||||
public ICommand StopCommand { get; }
|
||||
public ICommand SingleCommand { get; }
|
||||
public ICommand ForceTriggerCommand { get; }
|
||||
public ICommand SetChannelOnCommand { get; }
|
||||
public ICommand SetChannelOffCommand { get; }
|
||||
public ICommand SetVoltsDivCommand { get; }
|
||||
public ICommand SetOffsetCommand { get; }
|
||||
|
||||
// 🛠️ 补全:设置阻抗的 Command
|
||||
public ICommand SetImpedance50Command { get; }
|
||||
public ICommand SetImpedance1MCommand { get; }
|
||||
|
||||
public ICommand SetTimeBaseCommand { get; }
|
||||
public ICommand SetTriggerLevelCommand { get; }
|
||||
public ICommand SetTriggerSourceCommand { get; }
|
||||
public ICommand QueryMeasurementsCommand { get; }
|
||||
public ICommand QueryVppCommand { get; }
|
||||
public ICommand QueryFrequencyCommand { get; }
|
||||
public ICommand QueryRmsCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public SDS2000X_HDViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||
|
||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始捕获"); }));
|
||||
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获"); }));
|
||||
SingleCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.单次触发_SINGLE(Ct()); AppendLog("已触发单次捕获"); }));
|
||||
ForceTriggerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.强制触发(Ct()); AppendLog("已强制触发"); }));
|
||||
SetChannelOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, true, Ct()); AppendLog($"通道 {Channel} 已开启"); }));
|
||||
SetChannelOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, false, Ct()); AppendLog($"通道 {Channel} 已关闭"); }));
|
||||
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道电压档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 电压档位已设为 {VoltsPerDiv} V/div"); }));
|
||||
SetOffsetCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道垂直偏移(Channel, Offset, Ct()); AppendLog($"C{Channel} 垂直偏移已设为 {Offset} V"); }));
|
||||
|
||||
// 🛠️ 绑定:设置 50Ω 和 1MΩ 阻抗控制
|
||||
SetImpedance50Command = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
await _device!.设置通道阻抗(Channel, ImpedanceType.FIFty, Ct());
|
||||
Is50Ohm = true;
|
||||
AppendLog($"C{Channel} 输入阻抗已设为 50Ω");
|
||||
}));
|
||||
|
||||
SetImpedance1MCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
await _device!.设置通道阻抗(Channel, ImpedanceType.ONEM, Ct());
|
||||
Is50Ohm = false;
|
||||
AppendLog($"C{Channel} 输入阻抗已设为 1MΩ");
|
||||
}));
|
||||
|
||||
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
||||
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发电平(TriggerLevel, Ct()); AppendLog($"触发电平已设为 {TriggerLevel} V"); }));
|
||||
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发源(TriggerSource, Ct()); AppendLog($"触发源已设为 {TriggerSource}"); }));
|
||||
|
||||
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
// 1. 保留设备原生吐出的完整原始字符串
|
||||
string rawVpp = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||||
string rawFreq = await _device!.查询实际频率(Channel, Ct());
|
||||
string rawRms = await _device!.查询实际电压均方根(Channel, Ct());
|
||||
|
||||
// 2. 清洗数据并将科学计数法转为常规小数后赋值给 UI 属性
|
||||
MeasuredVpp = ParseMeasurement(rawVpp);
|
||||
MeasuredFrequency = ParseMeasurement(rawFreq);
|
||||
MeasuredRms = ParseMeasurement(rawRms);
|
||||
|
||||
// 3. 在日志中同时体现设备原始报文与解析呈现值,极方便联调
|
||||
AppendLog($"C{Channel} 测量原始数据 → Vpp:{rawVpp.Trim()} Freq:{rawFreq.Trim()} RMS:{rawRms.Trim()}");
|
||||
AppendLog($"C{Channel} 界面呈现数值 → Vpp:{MeasuredVpp}V Freq:{MeasuredFrequency}Hz RMS:{MeasuredRms}V");
|
||||
}));
|
||||
|
||||
QueryVppCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
string raw = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||||
MeasuredVpp = ParseMeasurement(raw);
|
||||
AppendLog($"C{Channel} Vpp: {MeasuredVpp} (Raw: {raw.Trim()})");
|
||||
}));
|
||||
|
||||
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
string raw = await _device!.查询实际频率(Channel, Ct());
|
||||
MeasuredFrequency = ParseMeasurement(raw);
|
||||
AppendLog($"C{Channel} Freq: {MeasuredFrequency} (Raw: {raw.Trim()})");
|
||||
}));
|
||||
|
||||
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
string raw = await _device!.查询实际电压均方根(Channel, Ct());
|
||||
MeasuredRms = ParseMeasurement(raw);
|
||||
AppendLog($"C{Channel} RMS: {MeasuredRms} (Raw: {raw.Trim()})");
|
||||
}));
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region 初始化 / Navigation
|
||||
|
||||
public void Initialize(string? deviceName = null)
|
||||
{
|
||||
SDS2000X_HD? found = null;
|
||||
string? foundName = null;
|
||||
|
||||
if (deviceName != null &&
|
||||
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||
d is SDS2000X_HD s)
|
||||
{
|
||||
found = s;
|
||||
foundName = deviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kv in _deviceManager.DeviceMap)
|
||||
{
|
||||
if (kv.Value is SDS2000X_HD sds)
|
||||
{
|
||||
found = sds;
|
||||
foundName = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_device = found;
|
||||
DeviceName = foundName ?? "SDS2000X_HD (未找到)";
|
||||
IsConnected = _device?.IsConnected ?? false;
|
||||
|
||||
AppendLog(found != null
|
||||
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||
: "未在 DeviceManager 中找到 SDS2000X_HD 设备,请先初始化设备配置。");
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext context)
|
||||
{
|
||||
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||
Initialize(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助
|
||||
|
||||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||
|
||||
private async Task Exec(Func<Task> action)
|
||||
{
|
||||
if (_device == null)
|
||||
{
|
||||
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||
return;
|
||||
}
|
||||
if (IsBusy) return;
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await action();
|
||||
IsConnected = _device.IsConnected;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
AppendLog("命令超时或已取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"错误:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
ResponseLog = ResponseLog.Length > 4000
|
||||
? line + "\n" + ResponseLog[..3000]
|
||||
: line + "\n" + ResponseLog;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清洗示波器原始返回的测量字符串(例如 "C1:PAVA RMS,5.57E-03V")并安全转化为无科学计数法的小数形式。
|
||||
/// </summary>
|
||||
/// <param name="rawResponse">设备原始应答数据</param>
|
||||
/// <returns>可直接绑定到 UI 呈现的字符串数字</returns>
|
||||
private string ParseMeasurement(string rawResponse)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawResponse)) return "—";
|
||||
|
||||
try
|
||||
{
|
||||
string cleanData = rawResponse.Trim();
|
||||
|
||||
// 1. 斩断报头,提取逗号后面的具体内容(如 "5.57E-03V" 或 "****")
|
||||
int commaIndex = cleanData.IndexOf(',');
|
||||
if (commaIndex == -1) return "—";
|
||||
|
||||
string valStr = cleanData.Substring(commaIndex + 1);
|
||||
|
||||
var match = Regex.Match(valStr, @"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?");
|
||||
if (match.Success)
|
||||
{
|
||||
valStr = match.Value;
|
||||
}
|
||||
|
||||
// 3. 校验并拦截设备未测出时的无效星号 "****"
|
||||
if (valStr.Contains("*") || string.IsNullOrWhiteSpace(valStr))
|
||||
{
|
||||
return "0"; // 回归为零或 "—",防止触发数据异常
|
||||
}
|
||||
|
||||
// 4. 解析科学计数法,并重新以不带科学计数法的小数样式展开
|
||||
if (double.TryParse(valStr, NumberStyles.Any, CultureInfo.InvariantCulture, out double result))
|
||||
{
|
||||
// "0.######" 样式会自动消除末尾无用的冗余零,并显示为普通小数(如 0.00557)
|
||||
return result.ToString("0.######", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 捕获可能产生的边缘转换故障,保证轮询线程绝不崩溃
|
||||
}
|
||||
|
||||
return "—";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
345
DeviceEditModule/ViewModels/SPAW7000ViewModel.cs
Normal file
345
DeviceEditModule/ViewModels/SPAW7000ViewModel.cs
Normal file
@@ -0,0 +1,345 @@
|
||||
using DeviceCommand.Devices;
|
||||
using Prism.Commands;
|
||||
using Prism.Ioc;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using UIShare.GlobalVariable;
|
||||
using UIShare.ViewModelBase;
|
||||
|
||||
namespace DeviceEditModule.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// SPAW7000 功率分析记录仪控制面板 ViewModel。
|
||||
/// </summary>
|
||||
public class SPAW7000ViewModel : NavigateViewModelBase, IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
|
||||
private readonly DeviceManager _deviceManager;
|
||||
private SPAW7000? _device;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 设备信息属性
|
||||
|
||||
private string _deviceName = "SPAW7000";
|
||||
public string DeviceName
|
||||
{
|
||||
get => _deviceName;
|
||||
set => SetProperty(ref _deviceName, value);
|
||||
}
|
||||
|
||||
private bool _isConnected;
|
||||
public bool IsConnected
|
||||
{
|
||||
get => _isConnected;
|
||||
set => SetProperty(ref _isConnected, value);
|
||||
}
|
||||
|
||||
private bool _isBusy;
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set => SetProperty(ref _isBusy, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 输入参数属性
|
||||
|
||||
private int _channel = 1;
|
||||
/// <summary>当前操作通道。</summary>
|
||||
public int Channel
|
||||
{
|
||||
get => _channel;
|
||||
set => SetProperty(ref _channel, value);
|
||||
}
|
||||
|
||||
private SPAW7000.VoltageRange _voltageRange = SPAW7000.VoltageRange.V_300;
|
||||
/// <summary>电压量程枚举。</summary>
|
||||
public SPAW7000.VoltageRange VoltageRange
|
||||
{
|
||||
get => _voltageRange;
|
||||
set => SetProperty(ref _voltageRange, value);
|
||||
}
|
||||
|
||||
private SPAW7000.CurrentRange _currentRange = SPAW7000.CurrentRange.A_5;
|
||||
/// <summary>电流量程枚举。</summary>
|
||||
public SPAW7000.CurrentRange CurrentRange
|
||||
{
|
||||
get => _currentRange;
|
||||
set => SetProperty(ref _currentRange, value);
|
||||
}
|
||||
|
||||
public SPAW7000.VoltageRange[] VoltageRangeOptions { get; } =
|
||||
(SPAW7000.VoltageRange[])Enum.GetValues(typeof(SPAW7000.VoltageRange));
|
||||
|
||||
public SPAW7000.CurrentRange[] CurrentRangeOptions { get; } =
|
||||
(SPAW7000.CurrentRange[])Enum.GetValues(typeof(SPAW7000.CurrentRange));
|
||||
|
||||
private int _resolution = 6;
|
||||
/// <summary>显示分辨率(5 或 6)。</summary>
|
||||
public int Resolution
|
||||
{
|
||||
get => _resolution;
|
||||
set => SetProperty(ref _resolution, value);
|
||||
}
|
||||
|
||||
private int _brightness = 5;
|
||||
/// <summary>屏幕亮度(1~10)。</summary>
|
||||
public int Brightness
|
||||
{
|
||||
get => _brightness;
|
||||
set => SetProperty(ref _brightness, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 测量结果属性
|
||||
|
||||
private string _measuredVoltage = "—";
|
||||
public string MeasuredVoltage
|
||||
{
|
||||
get => _measuredVoltage;
|
||||
set => SetProperty(ref _measuredVoltage, value);
|
||||
}
|
||||
|
||||
private string _measuredCurrent = "—";
|
||||
public string MeasuredCurrent
|
||||
{
|
||||
get => _measuredCurrent;
|
||||
set => SetProperty(ref _measuredCurrent, value);
|
||||
}
|
||||
|
||||
private string _measuredPower = "—";
|
||||
public string MeasuredPower
|
||||
{
|
||||
get => _measuredPower;
|
||||
set => SetProperty(ref _measuredPower, value);
|
||||
}
|
||||
|
||||
private string _measuredFrequency = "—";
|
||||
public string MeasuredFrequency
|
||||
{
|
||||
get => _measuredFrequency;
|
||||
set => SetProperty(ref _measuredFrequency, value);
|
||||
}
|
||||
|
||||
private string _measuredPowerFactor = "—";
|
||||
public string MeasuredPowerFactor
|
||||
{
|
||||
get => _measuredPowerFactor;
|
||||
set => SetProperty(ref _measuredPowerFactor, value);
|
||||
}
|
||||
|
||||
private string _deviceModel = "—";
|
||||
public string DeviceModel
|
||||
{
|
||||
get => _deviceModel;
|
||||
set => SetProperty(ref _deviceModel, value);
|
||||
}
|
||||
|
||||
private string _deviceSerial = "—";
|
||||
public string DeviceSerial
|
||||
{
|
||||
get => _deviceSerial;
|
||||
set => SetProperty(ref _deviceSerial, value);
|
||||
}
|
||||
|
||||
private string _responseLog = string.Empty;
|
||||
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||
public string ResponseLog
|
||||
{
|
||||
get => _responseLog;
|
||||
set => SetProperty(ref _responseLog, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 命令
|
||||
|
||||
public ICommand QueryIdentityCommand { get; }
|
||||
public ICommand ResetDeviceCommand { get; }
|
||||
public ICommand QueryAllMeasureCommand { get; }
|
||||
public ICommand SetVoltageRangeCommand { get; }
|
||||
public ICommand SetCurrentRangeCommand { get; }
|
||||
public ICommand SetResolutionCommand { get; }
|
||||
public ICommand SetBrightnessCommand { get; }
|
||||
public ICommand SetTouchLockOnCommand { get; }
|
||||
public ICommand SetTouchLockOffCommand { get; }
|
||||
public ICommand QueryModelCommand { get; }
|
||||
public ICommand QuerySerialCommand { get; }
|
||||
public ICommand QueryStatusByteCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public SPAW7000ViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||
{
|
||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||
|
||||
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||
SetVoltageRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电压量程(Channel, VoltageRange, Ct()); AppendLog($"通道 {Channel} 电压量程已设为 {(int)VoltageRange} V"); }));
|
||||
SetCurrentRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流量程(Channel, CurrentRange, Ct()); AppendLog($"通道 {Channel} 电流量程已设为 {(int)CurrentRange} A"); }));
|
||||
SetResolutionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示分辨率(Resolution, Ct()); AppendLog($"显示分辨率已设为 {Resolution} 位"); }));
|
||||
SetBrightnessCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示亮度(Brightness, Ct()); AppendLog($"屏幕亮度已设为 {Brightness}"); }));
|
||||
SetTouchLockOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(true, Ct()); AppendLog("屏幕触摸已锁定"); }));
|
||||
SetTouchLockOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(false, Ct()); AppendLog("屏幕触摸已解锁"); }));
|
||||
QueryModelCommand = new DelegateCommand(async () => await Exec(async () => { DeviceModel = await _device!.查询设备型号(Ct()); AppendLog($"型号: {DeviceModel}"); }));
|
||||
QuerySerialCommand = new DelegateCommand(async () => await Exec(async () => { DeviceSerial = await _device!.查询设备序列号(Ct()); AppendLog($"序列号: {DeviceSerial}"); }));
|
||||
QueryStatusByteCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("STB: " + await _device!.读取状态字节(Ct()))));
|
||||
|
||||
// 只修改这里:保持你原本的 5 次驱动查询调用不变,仅对读回来的科学计数法进行两位小数格式化
|
||||
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||
{
|
||||
string rawU = await _device!.查询实际电压(Channel, Ct());
|
||||
string rawI = await _device!.查询实际电流(Channel, Ct());
|
||||
string rawP = await _device!.查询实际功率(Channel, Ct());
|
||||
string rawF = await _device!.查询频率(Channel, Ct());
|
||||
string rawPF = await _device!.查询功率因数(Channel, Ct());
|
||||
|
||||
// 转换科学计数法格式,如果失败会自动保持原样
|
||||
MeasuredVoltage = FormatToDecimal(rawU, "F2"); // 电压两位小数
|
||||
MeasuredCurrent = FormatToDecimal(rawI, "F3"); // 电流通常较小,建议保留3位小数
|
||||
MeasuredPower = FormatToDecimal(rawP, "F2"); // 功率两位小数
|
||||
MeasuredFrequency = FormatToDecimal(rawF, "F2"); // 频率两位小数
|
||||
MeasuredPowerFactor = FormatToDecimal(rawPF, "F3"); // 功率因数保留3位小数
|
||||
|
||||
AppendLog($"CH{Channel} 测量 → U:{MeasuredVoltage}V I:{MeasuredCurrent}A P:{MeasuredPower}W F:{MeasuredFrequency}Hz PF:{MeasuredPowerFactor}");
|
||||
}));
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
#region 初始化 / Navigation
|
||||
|
||||
public void Initialize(string? deviceName = null)
|
||||
{
|
||||
SPAW7000? found = null;
|
||||
string? foundName = null;
|
||||
|
||||
if (deviceName != null &&
|
||||
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||
d is SPAW7000 s)
|
||||
{
|
||||
found = s;
|
||||
foundName = deviceName;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var kv in _deviceManager.DeviceMap)
|
||||
{
|
||||
if (kv.Value is SPAW7000 spaw)
|
||||
{
|
||||
found = spaw;
|
||||
foundName = kv.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_device = found;
|
||||
DeviceName = foundName ?? "SPAW7000 (未找到)";
|
||||
IsConnected = _device?.IsConnected ?? false;
|
||||
|
||||
AppendLog(found != null
|
||||
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||
: "未在 DeviceManager 中找到 SPAW7000 设备,请先初始化设备配置。");
|
||||
}
|
||||
|
||||
public override void OnNavigatedTo(NavigationContext context)
|
||||
{
|
||||
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||
Initialize(name);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助
|
||||
|
||||
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||
|
||||
/// <summary>
|
||||
/// 将科学计数法字符串转换为标准小数格式
|
||||
/// </summary>
|
||||
private string FormatToDecimal(string rawInput, string decimalFormat = "F2")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawInput))
|
||||
return "—";
|
||||
|
||||
// 去除可能夹杂的命令头,保留数据部分
|
||||
string cleanInput = CleanResponseHeader(rawInput);
|
||||
|
||||
// 解析科学计数法(NumberStyles.Any 和 InvariantCulture 是关键)
|
||||
if (double.TryParse(cleanInput, NumberStyles.Any, CultureInfo.InvariantCulture, out double value))
|
||||
{
|
||||
return value.ToString(decimalFormat, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
return cleanInput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 辅助清洗:剥离可能伴随吐回的命令头或双引号
|
||||
/// </summary>
|
||||
private string CleanResponseHeader(string response)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(response)) return string.Empty;
|
||||
string result = response.Trim();
|
||||
if (result.Contains(" "))
|
||||
{
|
||||
int lastSpaceIndex = result.LastIndexOf(' ');
|
||||
result = result.Substring(lastSpaceIndex + 1).Trim();
|
||||
}
|
||||
return result.Replace("\"", "").Replace("'", "").Trim();
|
||||
}
|
||||
|
||||
private async Task Exec(Func<Task> action)
|
||||
{
|
||||
if (_device == null)
|
||||
{
|
||||
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||
return;
|
||||
}
|
||||
if (IsBusy) return;
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
await action();
|
||||
IsConnected = _device.IsConnected;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
AppendLog("命令超时或已取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendLog($"错误:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendLog(string message)
|
||||
{
|
||||
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||
ResponseLog = ResponseLog.Length > 4000
|
||||
? line + "\n" + ResponseLog[..3000]
|
||||
: line + "\n" + ResponseLog;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
284
DeviceEditModule/Views/DialogMangerView.xaml
Normal file
284
DeviceEditModule/Views/DialogMangerView.xaml
Normal file
@@ -0,0 +1,284 @@
|
||||
<UserControl x:Class="DeviceEditModule.Views.DialogMangerView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||
Height="900" Width="900">
|
||||
|
||||
<!-- 此窗口独立开启 ShowInTaskbar,以支持最小化后在任务栏恢复 -->
|
||||
<prism:Dialog.WindowStyle>
|
||||
<Style TargetType="Window">
|
||||
<Setter Property="WindowStyle" Value="None"/>
|
||||
<Setter Property="ResizeMode" Value="NoResize"/>
|
||||
<Setter Property="ShowInTaskbar" Value="True"/>
|
||||
<Setter Property="AllowsTransparency" Value="True"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="SizeToContent" Value="WidthAndHeight"/>
|
||||
</Style>
|
||||
</prism:Dialog.WindowStyle>
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
|
||||
<!-- ── 窗口拖拽阴影外框 ── -->
|
||||
<DropShadowEffect x:Key="WindowShadow"
|
||||
Color="#66000000"
|
||||
BlurRadius="16"
|
||||
ShadowDepth="4"
|
||||
Direction="315"/>
|
||||
|
||||
<!-- ── Tab 项:未选中样式 ── -->
|
||||
<Style x:Key="TabItemStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="#F0F0F0"/>
|
||||
<Setter Property="BorderBrush" Value="#BDBDBD"/>
|
||||
<Setter Property="BorderThickness" Value="1,1,1,0"/>
|
||||
<Setter Property="CornerRadius" Value="6,6,0,0"/>
|
||||
<Setter Property="Margin" Value="2,5,0,0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="BorderBrush" Value="#1976D2"/>
|
||||
<Setter Property="BorderThickness" Value="1,2,1,0"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<!-- ── 标题栏按钮 ── -->
|
||||
<Style x:Key="TitleBarButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="Width" Value="36"/>
|
||||
<Setter Property="Height" Value="28"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="bg"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="4">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="bg" Property="Background" Value="#33FFFFFF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="bg" Property="Background" Value="#55FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- ── Tab 关闭按钮(小号,无边框) ── -->
|
||||
<Style x:Key="TabCloseButtonStyle" TargetType="Button">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Setter Property="Foreground" Value="#757575"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Width" Value="18"/>
|
||||
<Setter Property="Height" Value="18"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="bg"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="9">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="bg" Property="Background" Value="#FFD32F2F"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<!-- 最外层:透明底 + 阴影 + 圆角 -->
|
||||
<Border CornerRadius="10"
|
||||
Background="White"
|
||||
Effect="{StaticResource WindowShadow}"
|
||||
Margin="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<!-- 标题栏 -->
|
||||
<RowDefinition Height="38"/>
|
||||
<!-- Tab 标签条 -->
|
||||
<RowDefinition Height="80"/>
|
||||
<!-- 内容区 -->
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0"
|
||||
CornerRadius="10,10,0,0"
|
||||
Background="#1565C0"
|
||||
MouseLeftButtonDown="MouseLeftButtonDown">
|
||||
<Grid Margin="12,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 图标 + 标题 -->
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="WindowRestore"
|
||||
Foreground="White"
|
||||
Width="18" Height="18"
|
||||
Margin="0,0,6,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="弹窗管理器"
|
||||
Foreground="White"
|
||||
FontSize="13"
|
||||
FontWeight="Medium"
|
||||
VerticalAlignment="Center"/>
|
||||
<!-- Tab 数量徽标 -->
|
||||
<Border Background="#33FFFFFF"
|
||||
CornerRadius="10"
|
||||
Padding="6,1"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Foreground="White"
|
||||
FontSize="11">
|
||||
<TextBlock.Text>
|
||||
<Binding Path="TagItems.Count"
|
||||
StringFormat="{}{0} 个窗口"/>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 最小化按钮 -->
|
||||
<Button Grid.Column="1"
|
||||
Content="—"
|
||||
ToolTip="最小化"
|
||||
Command="{Binding MinimizeCommand}"
|
||||
Style="{StaticResource TitleBarButtonStyle}"
|
||||
Margin="0,0,2,0"/>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<Button Grid.Column="2"
|
||||
Content="✕"
|
||||
ToolTip="关闭"
|
||||
Command="{Binding CloseCommand}"
|
||||
Style="{StaticResource TitleBarButtonStyle}"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Grid.Row="1"
|
||||
Background="#FAFAFA"
|
||||
BorderBrush="#E0E0E0"
|
||||
BorderThickness="0,0,0,1">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
Padding="4,0,4,0">
|
||||
<ItemsControl ItemsSource="{Binding TagItems}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Bottom"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<!-- 单个 Tab -->
|
||||
<Border Style="{StaticResource TabItemStyle}">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="8,4,4,4"
|
||||
VerticalAlignment="Center">
|
||||
<!-- Tab 标题(点击激活) -->
|
||||
<Button Command="{Binding SelectCommand}"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
Cursor="Hand"
|
||||
Padding="0"
|
||||
MaxWidth="130">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<ContentPresenter/>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
<TextBlock Text="{Binding Title}"
|
||||
FontSize="12"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
ToolTip="{Binding Title}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="#616161"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsSelected}" Value="True">
|
||||
<Setter Property="Foreground" Value="#1565C0"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<!-- 关闭按钮 -->
|
||||
<Button Command="{Binding CloseCommand}"
|
||||
Content="✕"
|
||||
Style="{StaticResource TabCloseButtonStyle}"
|
||||
Margin="4,0,0,0"
|
||||
ToolTip="关闭此窗口"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
|
||||
<!-- 空状态提示 -->
|
||||
<StackPanel HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding HasNoTabs, Converter={StaticResource BoolToVis}}">
|
||||
<materialDesign:PackIcon Kind="InboxOutline"
|
||||
Width="64" Height="64"
|
||||
Foreground="#BDBDBD"
|
||||
HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="暂无打开的弹窗"
|
||||
Foreground="#9E9E9E"
|
||||
FontSize="14"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Center"/>
|
||||
<TextBlock Text="其他窗口最小化后将在此处显示为标签页"
|
||||
Foreground="#BDBDBD"
|
||||
FontSize="11"
|
||||
Margin="0,4,0,0"
|
||||
HorizontalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 选中 Tab 的内容 -->
|
||||
<ContentControl Content="{Binding SelectedTag.Content}"
|
||||
Visibility="{Binding HasNoTabs,
|
||||
Converter={StaticResource BoolToVis},
|
||||
ConverterParameter=Invert}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
81
DeviceEditModule/Views/DialogMangerView.xaml.cs
Normal file
81
DeviceEditModule/Views/DialogMangerView.xaml.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using DeviceEditModule.ViewModels;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace DeviceEditModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// DialogMangerView 代码后置。
|
||||
/// 负责处理与 Window 相关的操作(最小化),ViewModel 通过事件通知,
|
||||
/// 此处执行 P/Invoke,绕过 WPF 在 AllowsTransparency=True 时禁止
|
||||
/// 直接设置 WindowState.Minimized 的限制。
|
||||
/// </summary>
|
||||
public partial class DialogMangerView : UserControl
|
||||
{
|
||||
#region P/Invoke
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
/// <summary>SW_MINIMIZE:最小化到任务栏。</summary>
|
||||
private const int SW_MINIMIZE = 6;
|
||||
/// <summary>SW_RESTORE:激活并显示窗口。如果窗口最小化或最大化,系统会将其还原到原始大小和位置。</summary>
|
||||
private const int SW_RESTORE = 9;
|
||||
#endregion
|
||||
|
||||
public DialogMangerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 在 Loaded 后订阅 ViewModel 的最小化请求事件
|
||||
Loaded += (_, _) =>
|
||||
{
|
||||
if (DataContext is DialogMangerViewModel vm)
|
||||
{
|
||||
vm.MinimizeRequested += OnMinimizeRequested;
|
||||
vm.RestoreRequested += OnRestoreRequested;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 在 Unloaded 时取消订阅,防止内存泄漏
|
||||
Unloaded += (_, _) =>
|
||||
{
|
||||
if (DataContext is DialogMangerViewModel vm)
|
||||
{
|
||||
vm.MinimizeRequested -= OnMinimizeRequested;
|
||||
vm.RestoreRequested -= OnRestoreRequested;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
private void OnRestoreRequested(object? sender, EventArgs e)
|
||||
{
|
||||
var window = Window.GetWindow(this);
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = new WindowInteropHelper(window).EnsureHandle();
|
||||
|
||||
// 使用 Win32 API 强行恢复窗口,绕过 WPF 的 AllowsTransparency 限制
|
||||
ShowWindow(hwnd, SW_RESTORE);
|
||||
}
|
||||
private void OnMinimizeRequested(object? sender, System.EventArgs e)
|
||||
{
|
||||
var window = Window.GetWindow(this);
|
||||
if (window == null) return;
|
||||
|
||||
var hwnd = new WindowInteropHelper(window).EnsureHandle();
|
||||
ShowWindow(hwnd, SW_MINIMIZE);
|
||||
}
|
||||
|
||||
/// <summary>标题栏拖拽移动窗口。</summary>
|
||||
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
Window.GetWindow(this)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
274
DeviceEditModule/Views/IT7800EView.xaml
Normal file
274
DeviceEditModule/Views/IT7800EView.xaml
Normal file
@@ -0,0 +1,274 @@
|
||||
<UserControl x:Class="DeviceEditModule.Views.IT7800EView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||
d:DesignHeight="760" d:DesignWidth="860">
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
|
||||
|
||||
</UserControl.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ═══ 设备信息头 ═══ -->
|
||||
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="Flash" Width="22" Height="22"
|
||||
Foreground="#1565C0" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="IT7800E 交直流可编程电源"
|
||||
FontSize="15" FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||
FontSize="13" Foreground="#757575"
|
||||
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 连接状态指示 -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Background" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="未连接"/>
|
||||
<Setter Property="Foreground" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="已连接"/>
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<!-- 忙碌指示 -->
|
||||
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||
Margin="12,0,0,0"
|
||||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<!-- ═══ 主体 2 列 ═══ -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ─── 左列 ─── -->
|
||||
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||
|
||||
<!-- 输出控制 -->
|
||||
<GroupBox Header="输出控制" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- 开关输出 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="主输出" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="开启输出" Command="{Binding OutputOnCommand}"
|
||||
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||
Background="#388E3C" Foreground="White"
|
||||
Height="32" Padding="12,0" FontSize="12" Margin="4,0"/>
|
||||
<Button Content="关闭输出" Command="{Binding OutputOffCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 工作模式 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="工作模式" Style="{StaticResource ParamLabel}"/>
|
||||
<ComboBox Width="90" Height="32" Margin="4,0"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
SelectedItem="{Binding SelectedMode}"
|
||||
VerticalContentAlignment="Center" FontSize="12">
|
||||
<ComboBoxItem Content="AC"/>
|
||||
<ComboBoxItem Content="DC"/>
|
||||
<ComboBoxItem Content="ACDC"/>
|
||||
</ComboBox>
|
||||
<Button Content="设置模式" Command="{Binding SetModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 远程/本地 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 清除 / 重置 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="系统操作" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="清除告警" Command="{Binding ClearAlarmCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
<Button Content="清除错误" Command="{Binding ClearErrorCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 参数设置 -->
|
||||
<GroupBox Header="参数设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- AC 电压 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="AC电压 (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding AcVoltage, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetAcVoltageCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- DC 偏置 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="DC偏置 (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding DcVoltage, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetDcVoltageCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 频率 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="频率 (Hz)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding Frequency, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetFrequencyCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 限流 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="限流 (A)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding CurrentLimit, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetCurrentCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 保护设置 -->
|
||||
<GroupBox Header="保护设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OVP (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OvpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OVP" Command="{Binding SetOvpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OCP (A)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OcpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OCP" Command="{Binding SetOcpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ─── 右列 ─── -->
|
||||
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||
|
||||
<!-- 实时测量 -->
|
||||
<GroupBox Header="实时测量" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电压" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
|
||||
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电流" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
|
||||
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际功率" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredPower, Mode=OneWay}"/>
|
||||
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="输出频率" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredFrequency, Mode=OneWay}"/>
|
||||
<TextBlock Text="Hz" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
|
||||
Style="{StaticResource CmdBtn}"
|
||||
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 设备信息 -->
|
||||
<GroupBox Header="设备信息" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Orientation="Horizontal" Margin="4,8">
|
||||
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 响应日志 -->
|
||||
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<ScrollViewer Height="260" VerticalScrollBarVisibility="Auto">
|
||||
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="11"
|
||||
FontFamily="Consolas"
|
||||
Background="#FAFAFA"
|
||||
BorderThickness="0"
|
||||
VerticalAlignment="Top"/>
|
||||
</ScrollViewer>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
28
DeviceEditModule/Views/IT7800EView.xaml.cs
Normal file
28
DeviceEditModule/Views/IT7800EView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace DeviceEditModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// IT7800EView.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class IT7800EView : UserControl
|
||||
{
|
||||
public IT7800EView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
264
DeviceEditModule/Views/N36200View.xaml
Normal file
264
DeviceEditModule/Views/N36200View.xaml
Normal file
@@ -0,0 +1,264 @@
|
||||
<UserControl x:Class="DeviceEditModule.Views.N36200View"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||
d:DesignHeight="760" d:DesignWidth="860">
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ═══ 设备信息头 ═══ -->
|
||||
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="BatteryCharging" Width="22" Height="22"
|
||||
Foreground="#2E7D32" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="N36200 宽范围可编程直流电源"
|
||||
FontSize="15" FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||
FontSize="13" Foreground="#757575"
|
||||
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 连接状态指示 -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Background" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="未连接"/>
|
||||
<Setter Property="Foreground" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="已连接"/>
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||
Margin="12,0,0,0"
|
||||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<!-- ═══ 主体 2 列 ═══ -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ─── 左列 ─── -->
|
||||
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||
|
||||
<!-- 输出控制 -->
|
||||
<GroupBox Header="输出控制" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- 开关输出 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="DC 输出" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="开启输出" Command="{Binding OutputOnCommand}"
|
||||
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||
Background="#388E3C" Foreground="White"
|
||||
Height="32" Padding="12,0" FontSize="12" Margin="4,0"/>
|
||||
<Button Content="关闭输出" Command="{Binding OutputOffCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 运行模式 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="运行模式" Style="{StaticResource ParamLabel}"/>
|
||||
<ComboBox Width="100" Height="32" Margin="4,0"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
SelectedItem="{Binding SelectedMode}"
|
||||
VerticalContentAlignment="Center" FontSize="12">
|
||||
<ComboBoxItem Content="NORMal"/>
|
||||
<ComboBoxItem Content="CHARge"/>
|
||||
<ComboBoxItem Content="SEQuence"/>
|
||||
<ComboBoxItem Content="CPOWer"/>
|
||||
<ComboBoxItem Content="CARWave"/>
|
||||
</ComboBox>
|
||||
<Button Content="设置" Command="{Binding SetModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 重置 / 清除 / IDN -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="系统操作" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="清除告警" Command="{Binding ClearAlarmCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 参数设置 -->
|
||||
<GroupBox Header="参数设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- 电压 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="电压 (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding Voltage, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetVoltageCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 限流 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="限流 (A)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding CurrentLimit, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetCurrentCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 保护设置 -->
|
||||
<GroupBox Header="保护设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OVP (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OvpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OVP" Command="{Binding SetOvpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OCP (A)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OcpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OCP" Command="{Binding SetOcpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="UVP (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding UvpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 UVP" Command="{Binding SetUvpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OPP (W)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OppValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OPP" Command="{Binding SetOppCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ─── 右列 ─── -->
|
||||
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||
|
||||
<!-- 实时测量 -->
|
||||
<GroupBox Header="实时测量" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电压" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
|
||||
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电流" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
|
||||
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际功率" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredPower, Mode=OneWay}"/>
|
||||
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
|
||||
Style="{StaticResource CmdBtn}"
|
||||
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 设备信息与状态 -->
|
||||
<GroupBox Header="设备信息与状态" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="查询状态字" Command="{Binding QueryStatusCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="状态字" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}" Width="200"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding DeviceStatus, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 响应日志 -->
|
||||
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<ScrollViewer Height="300" VerticalScrollBarVisibility="Auto">
|
||||
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="11"
|
||||
FontFamily="Consolas"
|
||||
Background="#FAFAFA"
|
||||
BorderThickness="0"
|
||||
VerticalAlignment="Top"/>
|
||||
</ScrollViewer>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
28
DeviceEditModule/Views/N36200View.xaml.cs
Normal file
28
DeviceEditModule/Views/N36200View.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace DeviceEditModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// N36200View.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class N36200View : UserControl
|
||||
{
|
||||
public N36200View()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
249
DeviceEditModule/Views/N36600View.xaml
Normal file
249
DeviceEditModule/Views/N36600View.xaml
Normal file
@@ -0,0 +1,249 @@
|
||||
<UserControl x:Class="DeviceEditModule.Views.N36600View"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||
d:DesignHeight="760" d:DesignWidth="860">
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ═══ 设备信息头 ═══ -->
|
||||
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="BatteryCharging" Width="22" Height="22"
|
||||
Foreground="#2E7D32" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="N36600 便携式宽范围可编程直流电源"
|
||||
FontSize="15" FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||
FontSize="13" Foreground="#757575"
|
||||
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 连接状态指示 -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Background" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="未连接"/>
|
||||
<Setter Property="Foreground" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="已连接"/>
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||
Margin="12,0,0,0"
|
||||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<!-- ═══ 主体 2 列 ═══ -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ─── 左列 ─── -->
|
||||
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||
|
||||
<!-- 输出控制 -->
|
||||
<GroupBox Header="输出控制" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- 开关输出 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="DC 输出" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="开启输出" Command="{Binding OutputOnCommand}"
|
||||
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||
Background="#388E3C" Foreground="White"
|
||||
Height="32" Padding="12,0" FontSize="12" Margin="4,0"/>
|
||||
<Button Content="关闭输出" Command="{Binding OutputOffCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 远程/本地 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 清除保护 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="清除保护" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="清除OVP" Command="{Binding ClearVoltageProtectionCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
<Button Content="清除OCP" Command="{Binding ClearCurrentProtectionCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 参数设置 -->
|
||||
<GroupBox Header="参数设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- 电压 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="电压 (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding Voltage, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetVoltageCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 电流 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="电流 (A)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding CurrentLimit, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetCurrentCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 功率 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="功率 (W)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding Power, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetPowerCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 保护设置 -->
|
||||
<GroupBox Header="保护设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OVP (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OvpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OVP" Command="{Binding SetOvpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OCP (A)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OcpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OCP" Command="{Binding SetOcpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ─── 右列 ─── -->
|
||||
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||
|
||||
<!-- 实时测量 -->
|
||||
<GroupBox Header="实时测量" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电压" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
|
||||
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电流" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
|
||||
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际功率" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredPower, Mode=OneWay}"/>
|
||||
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
|
||||
Style="{StaticResource CmdBtn}"
|
||||
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 设备信息与状态 -->
|
||||
<GroupBox Header="设备信息与状态" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="查询输出状态" Command="{Binding QueryOutputStateCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="输出状态" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}" Width="160"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OutputState, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 响应日志 -->
|
||||
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<ScrollViewer Height="300" VerticalScrollBarVisibility="Auto">
|
||||
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="11"
|
||||
FontFamily="Consolas"
|
||||
Background="#FAFAFA"
|
||||
BorderThickness="0"
|
||||
VerticalAlignment="Top"/>
|
||||
</ScrollViewer>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
28
DeviceEditModule/Views/N36600View.xaml.cs
Normal file
28
DeviceEditModule/Views/N36600View.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace DeviceEditModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// N36600View.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class N36600View : UserControl
|
||||
{
|
||||
public N36600View()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
263
DeviceEditModule/Views/N69200View.xaml
Normal file
263
DeviceEditModule/Views/N69200View.xaml
Normal file
@@ -0,0 +1,263 @@
|
||||
<UserControl x:Class="DeviceEditModule.Views.N69200View"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||
d:DesignHeight="760" d:DesignWidth="860">
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ═══ 设备信息头 ═══ -->
|
||||
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="CurrentDc" Width="22" Height="22"
|
||||
Foreground="#1565C0" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="N69200 可编程直流电子负载"
|
||||
FontSize="15" FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||
FontSize="13" Foreground="#757575"
|
||||
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 连接状态指示 -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Background" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="未连接"/>
|
||||
<Setter Property="Foreground" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="已连接"/>
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||
Margin="12,0,0,0"
|
||||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<!-- ═══ 主体 2 列 ═══ -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ─── 左列 ─── -->
|
||||
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||
|
||||
<!-- 输入控制 -->
|
||||
<GroupBox Header="输入控制" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- 开关输入 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="DC 输入" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="开启输入" Command="{Binding InputOnCommand}"
|
||||
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||
Background="#388E3C" Foreground="White"
|
||||
Height="32" Padding="12,0" FontSize="12" Margin="4,0"/>
|
||||
<Button Content="关闭输入" Command="{Binding InputOffCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 工作模式 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="工作模式" Style="{StaticResource ParamLabel}"/>
|
||||
<ComboBox xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
Width="90" Height="32" Margin="4,0"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
SelectedItem="{Binding SelectedMode}"
|
||||
VerticalContentAlignment="Center" FontSize="12">
|
||||
<sys:String>CC</sys:String>
|
||||
<sys:String>CV</sys:String>
|
||||
<sys:String>CP</sys:String>
|
||||
<sys:String>CR</sys:String>
|
||||
</ComboBox>
|
||||
<Button Content="设置" Command="{Binding SetModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="查询" Command="{Binding QueryModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<!-- 远程/本地 -->
|
||||
<!--<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>-->
|
||||
<!-- 系统操作 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="系统操作" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="清除告警" Command="{Binding ClearAlarmCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 参数设置 -->
|
||||
<GroupBox Header="参数设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<!-- 设定值 -->
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="设定值" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding LoadValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetLoadValueCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 保护设置 -->
|
||||
<GroupBox Header="保护设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OVP (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OvpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OVP" Command="{Binding SetOvpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OCP (A)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OcpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OCP" Command="{Binding SetOcpCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="OPP (W)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding OppValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置 OPP" Command="{Binding SetOppCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ─── 右列 ─── -->
|
||||
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||
|
||||
<!-- 实时测量 -->
|
||||
<GroupBox Header="实时测量" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电压" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
|
||||
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际电流" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
|
||||
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="实际功率" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding MeasuredPower, Mode=OneWay}"/>
|
||||
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
</StackPanel>
|
||||
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
|
||||
Style="{StaticResource CmdBtn}"
|
||||
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 设备信息与状态 -->
|
||||
<GroupBox Header="设备信息与状态" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="查询状态字节" Command="{Binding QueryStatusCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="当前模式" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}" Width="120"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding CurrentMode, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="状态字节" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}" Width="120"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
Text="{Binding StatusByte, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 响应日志 -->
|
||||
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<ScrollViewer Height="260" VerticalScrollBarVisibility="Auto">
|
||||
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="11"
|
||||
FontFamily="Consolas"
|
||||
Background="#FAFAFA"
|
||||
BorderThickness="0"
|
||||
VerticalAlignment="Top"/>
|
||||
</ScrollViewer>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
28
DeviceEditModule/Views/N69200View.xaml.cs
Normal file
28
DeviceEditModule/Views/N69200View.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace DeviceEditModule.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// N69200View.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class N69200View : UserControl
|
||||
{
|
||||
public N69200View()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
247
DeviceEditModule/Views/SDS2000X_HDView.xaml
Normal file
247
DeviceEditModule/Views/SDS2000X_HDView.xaml
Normal file
@@ -0,0 +1,247 @@
|
||||
<UserControl x:Class="DeviceEditModule.Views.SDS2000X_HDView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:prism="http://prismlibrary.com/"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||
mc:Ignorable="d"
|
||||
prism:ViewModelLocator.AutoWireViewModel="False"
|
||||
d:DesignHeight="760" d:DesignWidth="860">
|
||||
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||
</UserControl.Resources>
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="12">
|
||||
|
||||
<!-- ═══ 设备信息头 ═══ -->
|
||||
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<materialDesign:PackIcon Kind="Waveform" Width="22" Height="22"
|
||||
Foreground="#1565C0" Margin="0,0,8,0"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="SDS2000X_HD 数字存储示波器"
|
||||
FontSize="15" FontWeight="Bold"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||
FontSize="13" Foreground="#757575"
|
||||
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 连接状态指示 -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Background" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Background" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="未连接"/>
|
||||
<Setter Property="Foreground" Value="#F44336"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||
<Setter Property="Text" Value="已连接"/>
|
||||
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||
Margin="12,0,0,0"
|
||||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</materialDesign:Card>
|
||||
|
||||
<!-- ═══ 主体 2 列 ═══ -->
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- ─── 左列 ─── -->
|
||||
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||
|
||||
<!-- 通道选择 -->
|
||||
<GroupBox Header="通道选择" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="通道号" Style="{StaticResource ParamLabel}"/>
|
||||
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||
SelectedItem="{Binding Channel}"
|
||||
VerticalContentAlignment="Center" FontSize="12">
|
||||
<sys:Int32>1</sys:Int32>
|
||||
<sys:Int32>2</sys:Int32>
|
||||
<sys:Int32>3</sys:Int32>
|
||||
<sys:Int32>4</sys:Int32>
|
||||
</ComboBox>
|
||||
<Button Content="开启" Command="{Binding SetChannelOnCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="关闭" Command="{Binding SetChannelOffCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 运行控制 -->
|
||||
<GroupBox Header="运行控制" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<Button Content="Run" Command="{Binding RunCommand}"
|
||||
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||
Background="#388E3C" Foreground="White"
|
||||
Height="32" Padding="16,0" FontSize="12" Margin="4,0"/>
|
||||
<Button Content="Stop" Command="{Binding StopCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
<Button Content="Single" Command="{Binding SingleCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="Force Trig" Command="{Binding ForceTriggerCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
|
||||
Style="{StaticResource WarnBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 垂直设置 -->
|
||||
<GroupBox Header="垂直设置" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="V/div (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
Text="{Binding VoltsPerDiv}"/>
|
||||
<Button Content="设置" Command="{Binding SetVoltsDivCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="Offset (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
Text="{Binding Offset, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetOffsetCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="输入阻抗" Style="{StaticResource ParamLabel}"/>
|
||||
<Button Content="50Ω" Command="{Binding SetImpedance50Command}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
<Button Content="1MΩ" Command="{Binding SetImpedance1MCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 水平/触发设置 -->
|
||||
<GroupBox Header="水平与触发" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="时基 (s/div)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
Text="{Binding TimeBase}"/>
|
||||
<Button Content="设置" Command="{Binding SetTimeBaseCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="触发电平 (V)" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}"
|
||||
Text="{Binding TriggerLevel, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetTriggerLevelCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="触发源" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource NumInput}" Width="80"
|
||||
Text="{Binding TriggerSource, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Content="设置" Command="{Binding SetTriggerSourceCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- ─── 右列 ─── -->
|
||||
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||
|
||||
<!-- 自动测量 -->
|
||||
<GroupBox Header="自动测量" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<StackPanel Margin="4,4,4,4">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="Vpp" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
Text="{Binding MeasuredVpp, Mode=OneWay}"/>
|
||||
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
<Button Content="刷新" Command="{Binding QueryVppCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="频率" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
Text="{Binding MeasuredFrequency, Mode=OneWay}"/>
|
||||
<TextBlock Text="Hz" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
<Button Content="刷新" Command="{Binding QueryFrequencyCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||
<TextBlock Text="RMS" Style="{StaticResource ParamLabel}"/>
|
||||
<TextBox Style="{StaticResource MeasureBox}"
|
||||
Text="{Binding MeasuredRms, Mode=OneWay}"/>
|
||||
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||
<Button Content="刷新" Command="{Binding QueryRmsCommand}"
|
||||
Style="{StaticResource CmdBtn}"/>
|
||||
</StackPanel>
|
||||
<Button Content="刷新全部测量" Command="{Binding QueryMeasurementsCommand}"
|
||||
Style="{StaticResource CmdBtn}"
|
||||
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
<!-- 响应日志 -->
|
||||
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||
<ScrollViewer Height="460" VerticalScrollBarVisibility="Auto">
|
||||
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||
materialDesign:HintAssist.Hint=""
|
||||
IsReadOnly="True"
|
||||
TextWrapping="Wrap"
|
||||
FontSize="11"
|
||||
FontFamily="Consolas"
|
||||
Background="#FAFAFA"
|
||||
BorderThickness="0"
|
||||
VerticalAlignment="Top"/>
|
||||
</ScrollViewer>
|
||||
</GroupBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user