命令树优化,测试编辑区域优化

This commit is contained in:
“hsc”
2026-08-27 10:50:10 +08:00
parent 99ac3f2b8d
commit e144dc78a7
24 changed files with 1520 additions and 338 deletions
+1
View File
@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+1
View File
@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+1
View File
@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+12 -3
View File
@@ -11,20 +11,29 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices namespace DeviceCommand.Devices
{ {
/// <summary> /// <summary>
/// 负载 一对三 /// ANEVH80 电子负载一对三控制),基于 TCP 通信,使用 SCPI 指令集。
/// 用于被测产品的放电/带载测试,可模拟真实负载工况。
/// </summary> /// </summary>
[ACPCommand] [ACPCommand]
public class ANEVH80 : Tcp public class ANEVH80 : Tcp
{ {
// Chroma SCPI 默认使用 \n 作为结束符 // SCPI 指令默认使用 \n 作为结束符
private const string ScpiDelimiter = "\n"; private const string ScpiDelimiter = "\n";
/// <summary>
/// 构造函数,初始化 TCP 连接配置。
/// </summary>
/// <param name="config">TCP 连接配置(IP、端口等)</param>
public ANEVH80(TcpConfig config) : base(config) public ANEVH80(TcpConfig config) : base(config)
{ {
} }
/// <summary> /// <summary>
/// 占位符,可能的操作先占位 /// 占位符方法,预留后续负载控制操作(如设置负载模式、设置电流等)。
/// 当前实现为查询频率测量值。
/// </summary> /// </summary>
/// <param name="ct">异步取消令牌</param>
/// <returns>查询到的频率值,解析失败返回 0.0</returns>
public virtual async Task<double> (CancellationToken ct = default) public virtual async Task<double> (CancellationToken ct = default)
{ {
string resp = await WriteReadAsync($"MEAS:FREQ?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($"MEAS:FREQ?{ScpiDelimiter}", ScpiDelimiter, ct);
+52 -26
View File
@@ -3,6 +3,7 @@ using DeviceCommand.Base;
using Model.Models; using Model.Models;
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -21,10 +22,27 @@ namespace DeviceCommand.Devices
{ {
} }
/// <summary>
/// 从设备返回的 SCPI 响应字符串中提取数值部分并转换为 double。
/// 支持科学计数法(如 2.48E-03),结果保留两位小数。
/// </summary>
/// <param name="raw">设备返回的原始字符串</param>
/// <returns>提取到的数值(保留两位小数),解析失败时返回 0</returns>
private static double ExtractDouble(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) return 0;
// 从响应中提取第一个数值(含正负号、小数点、科学计数法),可自动忽略单位后缀与命令头
var match = Regex.Match(raw, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?");
return match.Success && double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double val)
? Math.Round(val, 2)
: 0;
}
#region 1. #region 1.
/// <summary> /// <summary>
/// 清除错误队列 (*CLS)[cite: 1] /// 清除错误队列 (*CLS)
/// </summary> /// </summary>
public virtual async Task Async(CancellationToken ct = default) public virtual async Task Async(CancellationToken ct = default)
{ {
@@ -32,7 +50,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 重设仪器为初始状态 (*RST)[cite: 1] /// 重设仪器为初始状态 (*RST)
/// </summary> /// </summary>
public virtual async Task Async(CancellationToken ct = default) public virtual async Task Async(CancellationToken ct = default)
{ {
@@ -40,7 +58,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 查询仪器识别码 (*IDN?)[cite: 1] /// 查询仪器识别码 (*IDN?)
/// </summary> /// </summary>
public virtual async Task<string> Async(CancellationToken ct = default) public virtual async Task<string> Async(CancellationToken ct = default)
{ {
@@ -49,10 +67,10 @@ namespace DeviceCommand.Devices
#endregion #endregion
#region 2. (OUTPut )[cite: 1] #region 2. (OUTPut )
/// <summary> /// <summary>
/// 设置输出开关状态 (OUTP ON / OFF)[cite: 1] /// 设置输出开关状态 (OUTP ON / OFF)
/// </summary> /// </summary>
/// <param name="isOn">true: 开启输出, false: 关闭输出</param> /// <param name="isOn">true: 开启输出, false: 关闭输出</param>
public virtual async Task Async(bool isOn, CancellationToken ct = default) public virtual async Task Async(bool isOn, CancellationToken ct = default)
@@ -62,7 +80,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置输出耦合模式 (OUTP:COUP AC | DC | ACDC)[cite: 1] /// 设置输出耦合模式 (OUTP:COUP AC | DC | ACDC)
/// </summary> /// </summary>
public virtual async Task Async(string couplingMode, CancellationToken ct = default) public virtual async Task Async(string couplingMode, CancellationToken ct = default)
{ {
@@ -71,7 +89,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置输出继电器状态 (OUTP:REL ON | OFF)[cite: 1] /// 设置输出继电器状态 (OUTP:REL ON | OFF)
/// </summary> /// </summary>
public virtual async Task Async(bool isOn, CancellationToken ct = default) public virtual async Task Async(bool isOn, CancellationToken ct = default)
{ {
@@ -81,10 +99,10 @@ namespace DeviceCommand.Devices
#endregion #endregion
#region 3. (SOURCE )[cite: 1] #region 3. (SOURCE )
/// <summary> /// <summary>
/// 设置交流电压 (VOLT:AC)[cite: 1] /// 设置交流电压 (VOLT:AC)
/// </summary> /// </summary>
/// <param name="voltage">电压值 (0 ~ 300V 或高压选配范围)</param> /// <param name="voltage">电压值 (0 ~ 300V 或高压选配范围)</param>
public virtual async Task Async(double voltage, CancellationToken ct = default) public virtual async Task Async(double voltage, CancellationToken ct = default)
@@ -94,7 +112,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置直流电压 (VOLT:DC)[cite: 1] /// 设置直流电压 (VOLT:DC)
/// </summary> /// </summary>
public virtual async Task Async(double voltage, CancellationToken ct = default) public virtual async Task Async(double voltage, CancellationToken ct = default)
{ {
@@ -103,7 +121,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置输出频率 (FREQ)[cite: 1] /// 设置输出频率 (FREQ)
/// </summary> /// </summary>
/// <param name="frequency">频率值 (Hz)</param> /// <param name="frequency">频率值 (Hz)</param>
public virtual async Task Async(double frequency, CancellationToken ct = default) public virtual async Task Async(double frequency, CancellationToken ct = default)
@@ -113,7 +131,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置波形选择 (FUNC:SHAP:A SINE / SQUA / CSIN 等)[cite: 1] /// 设置波形选择 (FUNC:SHAP:A SINE / SQUA / CSIN 等)
/// </summary> /// </summary>
public virtual async Task Async(string shape, CancellationToken ct = default) public virtual async Task Async(string shape, CancellationToken ct = default)
{ {
@@ -122,10 +140,10 @@ namespace DeviceCommand.Devices
#endregion #endregion
#region 4. (INST & PHASE )[cite: 1] #region 4. (INST & PHASE )
/// <summary> /// <summary>
/// 设置单/三相工作模式 (INST:PHAS THREE / SINGLE)[cite: 1] /// 设置单/三相工作模式 (INST:PHAS THREE / SINGLE)
/// </summary> /// </summary>
/// <param name="isThreePhase">true: 三相模式, false: 单相模式</param> /// <param name="isThreePhase">true: 三相模式, false: 单相模式</param>
public virtual async Task Async(bool isThreePhase, CancellationToken ct = default) public virtual async Task Async(bool isThreePhase, CancellationToken ct = default)
@@ -135,7 +153,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置三相模式下的相互关系 (PHASE:THREE INDEPEND / SAMEFREQ / BALANCE)[cite: 1] /// 设置三相模式下的相互关系 (PHASE:THREE INDEPEND / SAMEFREQ / BALANCE)
/// </summary> /// </summary>
public virtual async Task Async(string relation, CancellationToken ct = default) public virtual async Task Async(string relation, CancellationToken ct = default)
{ {
@@ -144,7 +162,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置输出开启相位角 (PHASE:ON)[cite: 1] /// 设置输出开启相位角 (PHASE:ON)
/// </summary> /// </summary>
public virtual async Task Async(double degree, CancellationToken ct = default) public virtual async Task Async(double degree, CancellationToken ct = default)
{ {
@@ -153,7 +171,7 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置输出关闭相位角 (PHASE:OFF)[cite: 1] /// 设置输出关闭相位角 (PHASE:OFF)
/// </summary> /// </summary>
public virtual async Task Async(double degree, CancellationToken ct = default) public virtual async Task Async(double degree, CancellationToken ct = default)
{ {
@@ -163,42 +181,50 @@ namespace DeviceCommand.Devices
#endregion #endregion
#region 5. (MEASure )[cite: 1] #region 5. (MEASure )
/// <summary> /// <summary>
/// 查询交流电压均方根值 (MEAS:VOLT:AC?)[cite: 1] /// 查询交流电压均方根值 (MEAS:VOLT:AC?)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>交流电压均方根值 (单位: V,保留两位小数)</returns>
public virtual async Task<double> Async(CancellationToken ct = default) public virtual async Task<double> Async(CancellationToken ct = default)
{ {
string resp = await WriteReadAsync($"MEAS:VOLT:AC?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($"MEAS:VOLT:AC?{ScpiDelimiter}", ScpiDelimiter, ct);
return double.TryParse(resp, NumberStyles.Any, CultureInfo.InvariantCulture, out double val) ? val : 0.0; return ExtractDouble(resp);
} }
/// <summary> /// <summary>
/// 查询交流电流均方根值 (MEAS:CURR:AC?)[cite: 1] /// 查询交流电流均方根值 (MEAS:CURR:AC?)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>交流电流均方根值 (单位: A,保留两位小数)</returns>
public virtual async Task<double> Async(CancellationToken ct = default) public virtual async Task<double> Async(CancellationToken ct = default)
{ {
string resp = await WriteReadAsync($"MEAS:CURR:AC?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($"MEAS:CURR:AC?{ScpiDelimiter}", ScpiDelimiter, ct);
return double.TryParse(resp, NumberStyles.Any, CultureInfo.InvariantCulture, out double val) ? val : 0.0; return ExtractDouble(resp);
} }
/// <summary> /// <summary>
/// 查询输出频率 (MEAS:FREQ?)[cite: 1] /// 查询输出频率 (MEAS:FREQ?)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>输出频率值 (单位: Hz,保留两位小数)</returns>
public virtual async Task<double> Async(CancellationToken ct = default) public virtual async Task<double> Async(CancellationToken ct = default)
{ {
string resp = await WriteReadAsync($"MEAS:FREQ?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($"MEAS:FREQ?{ScpiDelimiter}", ScpiDelimiter, ct);
return double.TryParse(resp, NumberStyles.Any, CultureInfo.InvariantCulture, out double val) ? val : 0.0; return ExtractDouble(resp);
} }
/// <summary> /// <summary>
/// 查询真实输出功率 (MEAS:POW:AC?)[cite: 1] /// 查询真实输出功率 (MEAS:POW:AC?)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>真实输出功率值 (单位: W,保留两位小数)</returns>
public virtual async Task<double> Async(CancellationToken ct = default) public virtual async Task<double> Async(CancellationToken ct = default)
{ {
string resp = await WriteReadAsync($"MEAS:POW:AC?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($"MEAS:POW:AC?{ScpiDelimiter}", ScpiDelimiter, ct);
return double.TryParse(resp, NumberStyles.Any, CultureInfo.InvariantCulture, out double val) ? val : 0.0; return ExtractDouble(resp);
} }
#endregion #endregion
+137 -19
View File
@@ -3,6 +3,7 @@ using DeviceCommand.Base;
using Model.Models; using Model.Models;
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -45,33 +46,74 @@ namespace DeviceCommand.Devices
#endregion #endregion
/// <summary>
/// 信号发生器 (DG1000Z 系列) 一拖三,支持波形输出与频率计测量
/// </summary>
[ACPCommand] [ACPCommand]
public class DG1000Z : Tcp public class DG1000Z : Tcp
{ {
// 使用换行符 \n (ASCII 0x0A) 作为 SCPI 结束符 // 使用换行符 \n (ASCII 0x0A) 作为 SCPI 结束符
private const string ScpiDelimiter = "\n"; private const string ScpiDelimiter = "\n";
/// <summary>
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化信号发生器通信参数
/// </summary>
public DG1000Z(TcpConfig config) : base(config) public DG1000Z(TcpConfig config) : base(config)
{ {
} }
/// <summary>
/// 从设备返回的 SCPI 响应字符串中提取数值部分并转换为 double。
/// 支持科学计数法(如 2.48E-03),结果保留两位小数。
/// </summary>
/// <param name="raw">设备返回的原始字符串</param>
/// <returns>提取到的数值(保留两位小数),解析失败时返回 0</returns>
private static double ExtractDouble(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) return 0;
// 从响应中提取第一个数值(含正负号、小数点、科学计数法),可自动忽略单位后缀与命令头
var match = Regex.Match(raw, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?");
return match.Success && double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double val)
? Math.Round(val, 2)
: 0;
}
#region 1. IEEE 488.2 #region 1. IEEE 488.2
/// <summary>
/// 清除标准事件状态寄存器和错误队列 (*CLS)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
await SendAsync($"*CLS{ScpiDelimiter}", ct); await SendAsync($"*CLS{ScpiDelimiter}", ct);
} }
/// <summary>
/// 查询设备识别信息 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary>
/// 重设仪器为初始状态 (*RST)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
await SendAsync($"*RST{ScpiDelimiter}", ct); await SendAsync($"*RST{ScpiDelimiter}", ct);
} }
/// <summary>
/// 查询仪器支持的 SCPI 版本 (SYSTem:VERSion? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>SCPI 版本号字符串</returns>
public virtual async Task<string> SCPI版本(CancellationToken ct = default) public virtual async Task<string> SCPI版本(CancellationToken ct = default)
{ {
return await WriteReadAsync($":SYSTem:VERSion?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($":SYSTem:VERSion?{ScpiDelimiter}", ScpiDelimiter, ct);
@@ -82,16 +124,23 @@ namespace DeviceCommand.Devices
#region 2. (CH1 / CH2) #region 2. (CH1 / CH2)
/// <summary> /// <summary>
/// 打开或关闭指定通道的输出 /// 打开或关闭指定通道的输出 (OUTPut 指令)
/// </summary> /// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param> /// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="开启">true为开启,false为关闭</param> /// <param name="开启">true为开启,false为关闭</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, bool = true, CancellationToken ct = default) public virtual async Task (int = 1, bool = true, CancellationToken ct = default)
{ {
string = ? "ON" : "OFF"; string = ? "ON" : "OFF";
await SendAsync($":OUTPut{通道号}:STATe {参数}{ScpiDelimiter}", ct); await SendAsync($":OUTPut{通道号}:STATe {参数}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 查询指定通道的输出开关状态 (OUTPut:STATe? 指令)
/// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="ct">取消令牌</param>
/// <returns>通道输出状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (int = 1, CancellationToken ct = default) public virtual async Task<string> (int = 1, CancellationToken ct = default)
{ {
return await WriteReadAsync($":OUTPut{通道号}:STATe?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($":OUTPut{通道号}:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
@@ -104,51 +153,84 @@ namespace DeviceCommand.Devices
/// <summary> /// <summary>
/// 设置指定通道的波形类型 (如正弦波、方波等) /// 设置指定通道的波形类型 (如正弦波、方波等)
/// </summary> /// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="波形">波形类型枚举,默认正弦波</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, SignalWaveform = SignalWaveform.SINusoid, CancellationToken ct = default) public virtual async Task (int = 1, SignalWaveform = SignalWaveform.SINusoid, CancellationToken ct = default)
{ {
await SendAsync($":SOURce{通道号}:FUNCtion:SHAPe {波形}{ScpiDelimiter}", ct); await SendAsync($":SOURce{通道号}:FUNCtion:SHAPe {波形}{ScpiDelimiter}", ct);
} }
/// <summary> /// <summary>
/// 设置指定通道的输出频率 (单位: Hz) /// 设置指定通道的输出频率 (SOURce:FREQuency 指令)
/// </summary> /// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="频率Hz">输出频率值 (单位: Hz),默认 1000</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, double Hz = 1000, CancellationToken ct = default) public virtual async Task (int = 1, double Hz = 1000, CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FREQuency {1:F2}{2}", , Hz, ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FREQuency {1:F2}{2}", , Hz, ScpiDelimiter);
await SendAsync(cmd, ct); await SendAsync(cmd, ct);
} }
public virtual async Task<string> (int = 1, CancellationToken ct = default) /// <summary>
/// 查询指定通道的当前输出频率 (SOURce:FREQuency? 指令)
/// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="ct">取消令牌</param>
/// <returns>输出频率值 (单位: Hz,保留两位小数)</returns>
public virtual async Task<double> (int = 1, CancellationToken ct = default)
{ {
return await WriteReadAsync($":SOURce{通道号}:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($":SOURce{通道号}:FREQuency?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDouble(resp);
} }
/// <summary> /// <summary>
/// 设置指定通道的输出幅度 (单位: Vpp,峰峰值) /// 设置指定通道的输出幅度 (SOURce:VOLTage:AMPLitude 指令)
/// </summary> /// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="幅度Vpp">输出幅度 (单位: Vpp 峰峰值),默认 5</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, double Vpp = 5, CancellationToken ct = default) public virtual async Task (int = 1, double Vpp = 5, CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:VOLTage:AMPLitude {1:F2}{2}", , Vpp, ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:VOLTage:AMPLitude {1:F2}{2}", , Vpp, ScpiDelimiter);
await SendAsync(cmd, ct); await SendAsync(cmd, ct);
} }
public virtual async Task<string> (int = 1, CancellationToken ct = default) /// <summary>
/// 查询指定通道的当前输出幅度 (SOURce:VOLTage:AMPLitude? 指令)
/// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="ct">取消令牌</param>
/// <returns>输出幅度 (单位: Vpp,保留两位小数)</returns>
public virtual async Task<double> (int = 1, CancellationToken ct = default)
{ {
return await WriteReadAsync($":SOURce{通道号}:VOLTage:AMPLitude?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($":SOURce{通道号}:VOLTage:AMPLitude?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDouble(resp);
} }
/// <summary> /// <summary>
/// 设置指定通道的直流偏移电压 (单位: Vdc) /// 设置指定通道的直流偏移电压 (SOURce:VOLTage:OFFSet 指令)
/// </summary> /// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="偏移Vdc">直流偏移电压 (单位: Vdc),默认 0</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, double Vdc = 0, CancellationToken ct = default) public virtual async Task (int = 1, double Vdc = 0, CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:VOLTage:OFFSet {1:F2}{2}", , Vdc, ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:VOLTage:OFFSet {1:F2}{2}", , Vdc, ScpiDelimiter);
await SendAsync(cmd, ct); await SendAsync(cmd, ct);
} }
public virtual async Task<string> (int = 1, CancellationToken ct = default) /// <summary>
/// 查询指定通道的当前直流偏移电压 (SOURce:VOLTage:OFFSet? 指令)
/// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="ct">取消令牌</param>
/// <returns>直流偏移电压 (单位: Vdc,保留两位小数)</returns>
public virtual async Task<double> (int = 1, CancellationToken ct = default)
{ {
return await WriteReadAsync($":SOURce{通道号}:VOLTage:OFFSet?{ScpiDelimiter}", ScpiDelimiter, ct); string resp = await WriteReadAsync($":SOURce{通道号}:VOLTage:OFFSet?{ScpiDelimiter}", ScpiDelimiter, ct);
return ExtractDouble(resp);
} }
#endregion #endregion
@@ -156,8 +238,11 @@ namespace DeviceCommand.Devices
#region 4. / (CP信号模拟) #region 4. / (CP信号模拟)
/// <summary> /// <summary>
/// 设置方波信号的占空比 (0.01% ~ 99.99%)[cite: 1] /// 设置方波信号的占空比 (0.01% ~ 99.99%)
/// </summary> /// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="占空比">方波占空比百分比,默认 50.0</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, double = 50.0, CancellationToken ct = default) public virtual async Task (int = 1, double = 50.0, CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FUNCtion:SQUare:DCYCle {1:F2}{2}", , , ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FUNCtion:SQUare:DCYCle {1:F2}{2}", , , ScpiDelimiter);
@@ -165,9 +250,12 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置脉冲波信号的占空比 (0.01% ~ 99.99%)[cite: 1] /// 设置脉冲波信号的占空比 (0.01% ~ 99.99%)
/// 注意:使用此命令前必须先设置为 PULSe 波形类型 /// 注意:使用此命令前必须先设置为 PULSe 波形类型
/// </summary> /// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="占空比">脉冲波占空比百分比,默认 50.0</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, double = 50.0, CancellationToken ct = default) public virtual async Task (int = 1, double = 50.0, CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FUNCtion:PULSe:DCYCle {1:F2}{2}", , , ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FUNCtion:PULSe:DCYCle {1:F2}{2}", , , ScpiDelimiter);
@@ -179,47 +267,68 @@ namespace DeviceCommand.Devices
#region 5. () #region 5. ()
/// <summary> /// <summary>
/// 打开或关闭内置频率计功能 /// 打开或关闭内置频率计功能 (COUNter:STATe 指令)
/// </summary> /// </summary>
/// <param name="开启">true: 开启频率计, false: 关闭频率计</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (bool = true, CancellationToken ct = default) public virtual async Task (bool = true, CancellationToken ct = default)
{ {
string = ? "ON" : "OFF"; string = ? "ON" : "OFF";
await SendAsync($":COUNter:STATe {参数}{ScpiDelimiter}", ct); await SendAsync($":COUNter:STATe {参数}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 查询内置频率计的开关状态 (COUNter:STATe? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>频率计开关状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync($":COUNter:STATe?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($":COUNter:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary> /// <summary>
/// 设置频率计输入信号的耦合方式 /// 设置频率计输入信号的耦合方式 (COUNter:COUPling 指令)
/// </summary> /// </summary>
/// <param name="耦合">耦合方式枚举 (AC 交流 / DC 直流),默认 AC</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (CounterCoupling = CounterCoupling.AC, CancellationToken ct = default) public virtual async Task (CounterCoupling = CounterCoupling.AC, CancellationToken ct = default)
{ {
await SendAsync($":COUNter:COUPling {耦合}{ScpiDelimiter}", ct); await SendAsync($":COUNter:COUPling {耦合}{ScpiDelimiter}", ct);
} }
/// <summary> /// <summary>
/// 设置频率计的触发电平 (单位: V) /// 设置频率计的触发电平 (COUNter:LEVel 指令)
/// </summary> /// </summary>
/// <param name="电平V">触发电平值 (单位: V),默认 0.1</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (double V = 0.1, CancellationToken ct = default) public virtual async Task (double V = 0.1, CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, ":COUNter:LEVel {0:F2}{1}", V, ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, ":COUNter:LEVel {0:F2}{1}", V, ScpiDelimiter);
await SendAsync(cmd, ct); await SendAsync(cmd, ct);
} }
/// <summary>
/// 查询频率计测量值 (COUNter:MEASure? 指令)。
/// 响应格式为 "频率, 周期, 占空比, 正脉宽, 负脉宽",此方法提取第一项频率值
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>频率计测得的频率值 (单位: Hz,保留两位小数)</returns>
[Monitorable("信号发生器频率计测量值")] [Monitorable("信号发生器频率计测量值")]
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<double> (CancellationToken ct = default)
{ {
// 返回格式: 频率, 周期, 占空比, 正脉宽, 负脉宽 string resp = await WriteReadAsync($":COUNter:MEASure?{ScpiDelimiter}", ScpiDelimiter, ct);
return await WriteReadAsync($":COUNter:MEASure?{ScpiDelimiter}", ScpiDelimiter, ct); return ExtractDouble(resp);
} }
#endregion #endregion
#region 6. #region 6.
/// <summary>
/// 切换远程控制模式。信号发生器通过 LAN/USB 通信即自动进入远程模式
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
// 信号发生器通常通过 LAN/USB 和 VISA 通信即自动进入远程控制, // 信号发生器通常通过 LAN/USB 和 VISA 通信即自动进入远程控制,
@@ -227,12 +336,21 @@ namespace DeviceCommand.Devices
await Task.CompletedTask; await Task.CompletedTask;
} }
/// <summary>
/// 切换本地控制模式。通常通过前面板按键退出远程
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
// 通常通过前面板按键退出远程。不过可提供 SCPI 关闭屏幕保护等。 // 通常通过前面板按键退出远程。不过可提供 SCPI 关闭屏幕保护等。
await Task.CompletedTask; await Task.CompletedTask;
} }
/// <summary>
/// 查询系统错误信息 (SYSTem:ERRor? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>错误代码和描述信息</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
@@ -240,4 +358,4 @@ namespace DeviceCommand.Devices
#endregion #endregion
} }
} }
+34 -3
View File
@@ -12,22 +12,39 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices namespace DeviceCommand.Devices
{ {
/// <summary> /// <summary>
/// IO板卡 一对一由于板卡映射不完全一样采用IOGroup /// IO 继电器板卡一对一控制),基于 Modbus TCP 协议通过线圈(Coil)读写控制继电器输出。
/// 由于多块板卡的点位映射不完全一致,实际业务中使用 <see cref="IOBoardGroup"/> 统一调度三块板卡。
/// </summary> /// </summary>
//[ACPCommand] //[ACPCommand]
public class IOBoard : ModbusTcp public class IOBoard : ModbusTcp
{ {
/// <summary>
/// 构造函数,初始化 Modbus TCP 连接配置。
/// </summary>
/// <param name="config">TCP 连接配置(IP、端口等)</param>
public IOBoard(TcpConfig config) : base(config) public IOBoard(TcpConfig config) : base(config)
{ {
} }
/// <summary>
/// 写入单个输出开关(线圈)状态。
/// </summary>
/// <param name="站号">Modbus 从站站号</param>
/// <param name="开始地址">线圈地址(0 起始)</param>
/// <param name="data">开关状态(true: 吸合/开启, false: 断开/关闭)</param>
public async Task (byte , ushort , bool data) public async Task (byte , ushort , bool data)
{ {
await Modbus.WriteSingleCoilAsync(, , data); await Modbus.WriteSingleCoilAsync(, , data);
} }
/// <summary>
/// 批量写入输出开关状态。先读出全部 16 个线圈当前状态,
/// 在内存中修改指定偏移段的值后再整体写回,避免影响其他点位。
/// </summary>
/// <param name="站号">Modbus 从站站号</param>
/// <param name="开始地址">起始线圈地址(0 起始)</param>
/// <param name="datas">要写入的开关状态数组,按顺序从开始地址写入</param>
public async Task (byte , ushort , bool[] datas) public async Task (byte , ushort , bool[] datas)
{ {
var = await Modbus.ReadCoilsAsync(1, 0, 16); var = await Modbus.ReadCoilsAsync(1, 0, 16);
@@ -40,10 +57,24 @@ namespace DeviceCommand.Devices
} }
await Modbus.WriteMultipleCoilsAsync(, 0, ); await Modbus.WriteMultipleCoilsAsync(, 0, );
} }
/// <summary>
/// 读取单个输出开关(线圈)状态。
/// </summary>
/// <param name="站号">Modbus 从站站号</param>
/// <param name="开始地址">线圈地址(0 起始)</param>
public async Task (byte , ushort ) public async Task (byte , ushort )
{ {
await Modbus.ReadCoilsAsync(, , 1); await Modbus.ReadCoilsAsync(, , 1);
} }
/// <summary>
/// 批量读取输出开关(线圈)状态。
/// </summary>
/// <param name="站号">Modbus 从站站号</param>
/// <param name="开始地址">起始线圈地址(0 起始)</param>
/// <param name="数量">要读取的线圈数量</param>
/// <returns>线圈状态数组,true 表示吸合/开启</returns>
public async Task<bool[]> (byte , ushort ,ushort ) public async Task<bool[]> (byte , ushort ,ushort )
{ {
return await Modbus.ReadCoilsAsync(, , ); return await Modbus.ReadCoilsAsync(, , );
+25 -37
View File
@@ -48,6 +48,11 @@ namespace DeviceCommand.Devices
} }
} }
/// <summary>
/// IO 板卡组(三工位统一调度),封装 3 块 IO 继电器板卡的访问。
/// 根据 ACP 硬件接线图的点位映射字典,按台架序号路由到对应板卡的线圈地址,
/// 并内置充电/放电互锁保护,防止同时吸合造成短路。
/// </summary>
[ACPCommand] [ACPCommand]
public class IOBoardGroup public class IOBoardGroup
{ {
@@ -121,6 +126,13 @@ namespace DeviceCommand.Devices
}} }}
}; };
/// <summary>
/// 构造函数,注入三个工位的 IO 板卡实例。
/// </summary>
/// <param name="board1">1# 工位 IO 板卡实例</param>
/// <param name="board2">2# 工位 IO 板卡实例</param>
/// <param name="board3">3# 工位 IO 板卡实例</param>
/// <exception cref="ArgumentNullException">任一块板卡实例为空时抛出</exception>
// 修改构造函数以支持 3 个板块 // 修改构造函数以支持 3 个板块
public IOBoardGroup(IOBoard board1, IOBoard board2, IOBoard board3) public IOBoardGroup(IOBoard board1, IOBoard board2, IOBoard board3)
{ {
@@ -168,49 +180,25 @@ namespace DeviceCommand.Devices
await .(_slaveAddress, io信息.Address, ); await .(_slaveAddress, io信息.Address, );
} }
// =================================================================================
// 以下为新增的原生命令透传接口,使用台架序号自动路由至对应 IOBoard
// =================================================================================
/// <summary> /// <summary>
/// 针对指定台架直接写入单个输出开关 /// 向指定板卡批量写入输出开关状态,直接透传到对应板卡的批量写线圈操作。
/// </summary> /// </summary>
public async Task (int , byte , ushort , bool data) /// <param name="板卡序号">板卡序号 (1 - 3)</param>
/// <param name="站号">Modbus 从站站号</param>
/// <param name="开始地址">线圈起始地址 (0-15)</param>
/// <param name="数据">开关状态数组 (true: 开启吸合, false: 关闭断开),例如 [true, true, false]</param>
/// <param name="取消令牌">异步取消令牌</param>
public async Task (int , byte , ushort , bool[] , CancellationToken = default)
{ {
IOBoard targetBoard = GetBoardInstance(); await GetBoardInstance().(, , );
await targetBoard.(, , data);
} }
/// <summary> /// <summary>
/// 针对指定台架直接批量写入输出开关 /// 内部路由辅助方法:根据板卡序号安全获取对应的 IO 板卡实例。
/// </summary>
public async Task (int , byte , ushort , bool[] datas)
{
IOBoard targetBoard = GetBoardInstance();
await targetBoard.(, , datas);
}
/// <summary>
/// 针对指定台架直接读取单个输出开关状态
/// </summary>
public async Task (int , byte , ushort )
{
IOBoard targetBoard = GetBoardInstance();
await targetBoard.(, );
}
/// <summary>
/// 针对指定台架直接批量读取输出开关状态
/// </summary>
public async Task<bool[]> (int , byte , ushort , ushort )
{
IOBoard targetBoard = GetBoardInstance();
return await targetBoard.(, , );
}
/// <summary>
/// 内部路由辅助方法:根据板卡/台架索引安全获取当前连接的实例对象
/// </summary> /// </summary>
/// <param name="moduleIndex">板卡序号 (1 - 3)</param>
/// <returns>对应的 IO 板卡实例</returns>
/// <exception cref="ArgumentOutOfRangeException">板卡序号不在 1-3 范围内时抛出</exception>
/// <exception cref="InvalidOperationException">板卡实例未初始化时抛出</exception>
private IOBoard GetBoardInstance(int moduleIndex) private IOBoard GetBoardInstance(int moduleIndex)
{ {
IOBoard board = moduleIndex switch IOBoard board = moduleIndex switch
+23 -4
View File
@@ -133,6 +133,10 @@ namespace DeviceCommand.Devices
throw new Exception("IT6720 切换控制模式时通讯校验失败。"); throw new Exception("IT6720 切换控制模式时通讯校验失败。");
} }
/// <summary>
/// 切换为本地(面板)控制模式,与远程控制互斥
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
await (false, ct); await (false, ct);
@@ -150,6 +154,11 @@ namespace DeviceCommand.Devices
throw new Exception("IT6720 设置输出时通讯校验失败。"); throw new Exception("IT6720 设置输出时通讯校验失败。");
} }
/// <summary>
/// 查询电源输出开关状态(由实时测量值的状态字节判断)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>输出开关状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
var (_, _, status) = await (ct); var (_, _, status) = await (ct);
@@ -230,18 +239,28 @@ namespace DeviceCommand.Devices
return (actualCurrent, actualVoltage, statusByte); return (actualCurrent, actualVoltage, statusByte);
} }
/// <summary>
/// 查询电源的实时实际输出电压 (MEASure 相关寄存器)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电压值 (单位: V,保留两位小数)</returns>
[Monitorable("IT6720 实际电压")] [Monitorable("IT6720 实际电压")]
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<double> (CancellationToken ct = default)
{ {
var (_, voltage, _) = await (ct); var (_, voltage, _) = await (ct);
return voltage.ToString("F2", CultureInfo.InvariantCulture); return Math.Round(voltage, 2);
} }
/// <summary>
/// 查询电源的实时实际输出电流 (MEASure 相关寄存器)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电流值 (单位: A,保留两位小数)</returns>
[Monitorable("IT6720 实际电流")] [Monitorable("IT6720 实际电流")]
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<double> (CancellationToken ct = default)
{ {
var (current, _, _) = await (ct); var (current, _, _) = await (ct);
return current.ToString("F3", CultureInfo.InvariantCulture); return Math.Round(current, 2);
} }
/// <summary> /// <summary>
+6 -1
View File
@@ -9,10 +9,15 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices namespace DeviceCommand.Devices
{ {
/// <summary> /// <summary>
/// 水冷机 一拖三 /// MCc30W 水冷机一拖三控制),基于 Modbus TCP 协议通信。
/// 用于被测产品的冷却温控,预留后续读写温度设定、启停控制等寄存器操作。
/// </summary> /// </summary>
public class MCc30W:ModbusTcp public class MCc30W:ModbusTcp
{ {
/// <summary>
/// 构造函数,初始化 Modbus TCP 连接配置。
/// </summary>
/// <param name="config">TCP 连接配置(IP、端口等)</param>
public MCc30W(TcpConfig config) : base(config) public MCc30W(TcpConfig config) : base(config)
{ {
} }
+261 -33
View File
@@ -2,13 +2,16 @@
using Common.Attributes; using Common.Attributes;
using DeviceCommand.Base; using DeviceCommand.Base;
using Model.Models; using Model.Models;
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace DeviceCommand.Device namespace DeviceCommand.Device
{ {
/// <summary> /// <summary>
/// 功率分析仪(型号:HIOKI PW8001) 一拖三 /// 功率分析仪(型号:HIOKI PW8001) 一拖三,支持多通道电压/电流/功率及谐波测量
/// </summary> /// </summary>
[ACPCommand] [ACPCommand]
public class PW8001 : Tcp public class PW8001 : Tcp
@@ -25,23 +28,58 @@ namespace DeviceCommand.Device
/// </summary> /// </summary>
public string? SessionKey { get; set; } public string? SessionKey { get; set; }
/// <summary>
/// 从设备返回的 SCPI 响应字符串中提取数值部分并转换为 double。
/// 支持科学计数法(如 2.48E-03),结果保留两位小数。
/// </summary>
/// <param name="raw">设备返回的原始字符串</param>
/// <returns>提取到的数值(保留两位小数),解析失败时返回 0</returns>
private static double ExtractDouble(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) return 0;
// 从响应中提取第一个数值(含正负号、小数点、科学计数法),可自动忽略单位后缀与命令头
var match = Regex.Match(raw, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?");
return match.Success && double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double val)
? Math.Round(val, 2)
: 0;
}
#region 1. IEEE 488.2 #region 1. IEEE 488.2
/// <summary>
/// 查询仪器识别信息 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync("*IDN?\r\n", "\n", ct); return await WriteReadAsync("*IDN?\r\n", "\n", ct);
} }
/// <summary>
/// 查询仪器已安装的硬件选件 (*OPT? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>硬件选件列表字符串</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync("*OPT?\r\n", "\n", ct); return await WriteReadAsync("*OPT?\r\n", "\n", ct);
} }
/// <summary>
/// 复位仪器到默认状态 (*RST 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
await SendAsync("*RST\r\n", ct); await SendAsync("*RST\r\n", ct);
} }
/// <summary>
/// 清除状态寄存器和错误队列 (*CLS 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
await SendAsync("*CLS\r\n", ct); await SendAsync("*CLS\r\n", ct);
@@ -51,66 +89,129 @@ namespace DeviceCommand.Device
#region 2. (System & Mode) #region 2. (System & Mode)
/// <summary>
/// 设置测试模式为 WIDE 宽带测量模式 (:MODE WIDE 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task _WIDE(CancellationToken ct = default) public virtual async Task _WIDE(CancellationToken ct = default)
{ {
await SendAsync(":MODE WIDE\r\n", ct); await SendAsync(":MODE WIDE\r\n", ct);
} }
/// <summary>
/// 设置测试模式为 IEC 标准测量模式 (:MODE IEC 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task _IEC(CancellationToken ct = default) public virtual async Task _IEC(CancellationToken ct = default)
{ {
await SendAsync(":MODE IEC\r\n", ct); await SendAsync(":MODE IEC\r\n", ct);
} }
/// <summary>
/// 查询当前测试模式 (:MODE? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>测试模式字符串 (WIDE / IEC)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync(":MODE?\r\n", "\n", ct); return await WriteReadAsync(":MODE?\r\n", "\n", ct);
} }
/// <summary>
/// 设置同步源 (:SYNC:SOURce 指令)
/// </summary>
/// <param name="源">同步源标识</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default) public virtual async Task (string , CancellationToken ct = default)
{ {
await SendAsync($":SYNC:SOURce {源}\r\n", ct); await SendAsync($":SYNC:SOURce {源}\r\n", ct);
} }
/// <summary>
/// 查询当前同步源 (:SYNC:SOURce? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>同步源标识字符串</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync(":SYNC:SOURce?\r\n", "\n", ct); return await WriteReadAsync(":SYNC:SOURce?\r\n", "\n", ct);
} }
/// <summary>
/// 设置零点调整状态 (:ZERO 指令)
/// </summary>
/// <param name="状态">零点调整状态 (ON / OFF)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default) public virtual async Task (string , CancellationToken ct = default)
{ {
await SendAsync($":ZERO {状态}\r\n", ct); await SendAsync($":ZERO {状态}\r\n", ct);
} }
/// <summary>
/// 查询零点调整状态 (:ZERO? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>零点调整状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync(":ZERO?\r\n", "\n", ct); return await WriteReadAsync(":ZERO?\r\n", "\n", ct);
} }
/// <summary>
/// 设置零点抑制状态 (:ZSP 指令)
/// </summary>
/// <param name="状态">零点抑制状态 (ON / OFF)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default) public virtual async Task (string , CancellationToken ct = default)
{ {
await SendAsync($":ZSP {状态}\r\n", ct); await SendAsync($":ZSP {状态}\r\n", ct);
} }
/// <summary>
/// 查询零点抑制状态 (:ZSP? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>零点抑制状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync(":ZSP?\r\n", "\n", ct); return await WriteReadAsync(":ZSP?\r\n", "\n", ct);
} }
/// <summary>
/// 设置响应消息头开关 (:HEADer 指令)
/// </summary>
/// <param name="状态">消息头开关状态 (ON / OFF)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default) public virtual async Task (string , CancellationToken ct = default)
{ {
await SendAsync($":HEADer {状态}\r\n", ct); await SendAsync($":HEADer {状态}\r\n", ct);
} }
/// <summary>
/// 查询响应消息头开关状态 (:HEADer? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>消息头开关状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync(":HEADer?\r\n", "\n", ct); return await WriteReadAsync(":HEADer?\r\n", "\n", ct);
} }
/// <summary>
/// 设置按键锁定状态 (:KLOCk 指令)
/// </summary>
/// <param name="状态">按键锁定状态 (ON / OFF)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default) public virtual async Task (string , CancellationToken ct = default)
{ {
await SendAsync($":KLOCk {状态}\r\n", ct); await SendAsync($":KLOCk {状态}\r\n", ct);
} }
/// <summary>
/// 查询按键锁定状态 (:KLOCk? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>按键锁定状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync(":KLOCk?\r\n", "\n", ct); return await WriteReadAsync(":KLOCk?\r\n", "\n", ct);
@@ -120,21 +221,43 @@ namespace DeviceCommand.Device
#region 3. (Integration) #region 3. (Integration)
/// <summary>
/// 设置积分控制模式 (:INTEG:CONTROL 指令)
/// </summary>
/// <param name="模式">积分控制模式</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default) public virtual async Task (string , CancellationToken ct = default)
{ {
await SendAsync($":INTEG:CONTROL {模式}\r\n", ct); await SendAsync($":INTEG:CONTROL {模式}\r\n", ct);
} }
/// <summary>
/// 查询积分控制模式 (:INTEG:CONTROL? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>积分控制模式字符串</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync(":INTEG:CONTROL?\r\n", "\n", ct); return await WriteReadAsync(":INTEG:CONTROL?\r\n", "\n", ct);
} }
/// <summary>
/// 设置指定通道的积分模式 (:INTEG:MODE 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="模式">积分模式</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , string , CancellationToken ct = default) public virtual async Task (string , string , CancellationToken ct = default)
{ {
await SendAsync($":INTEG:MODE{通道号} {模式}\r\n", ct); await SendAsync($":INTEG:MODE{通道号} {模式}\r\n", ct);
} }
/// <summary>
/// 查询指定通道的积分模式 (:INTEG:MODE? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>积分模式字符串</returns>
public virtual async Task<string> (string , CancellationToken ct = default) public virtual async Task<string> (string , CancellationToken ct = default)
{ {
return await WriteReadAsync($":INTEG:MODE{通道号}?\r\n", "\n", ct); return await WriteReadAsync($":INTEG:MODE{通道号}?\r\n", "\n", ct);
@@ -146,85 +269,188 @@ namespace DeviceCommand.Device
// ---------------- 基础测量(不含变比) ---------------- // ---------------- 基础测量(不含变比) ----------------
public virtual async Task<string> _不含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电压测量值(不含变比) (:MEAS:U? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电压测量值 (单位: V,保留两位小数)</returns>
public virtual async Task<double> _不含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:U{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:U{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> _不含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电流测量值(不含变比) (:MEAS:I? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电流测量值 (单位: A,保留两位小数)</returns>
public virtual async Task<double> _不含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:I{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:I{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> _不含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的功率测量值(不含变比) (:MEAS:P? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>功率测量值 (单位: W,保留两位小数)</returns>
public virtual async Task<double> _不含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:P{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:P{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
// ---------------- 基础测量(含变比 CT ---------------- // ---------------- 基础测量(含变比 CT ----------------
public virtual async Task<string> _含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电压测量值(含变比) (:MEAS:U:CT? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电压测量值 (单位: V,保留两位小数)</returns>
public virtual async Task<double> _含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:U{通道号}:CT?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:U{通道号}:CT?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> _含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电流测量值(含变比) (:MEAS:I:CT? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电流测量值 (单位: A,保留两位小数)</returns>
public virtual async Task<double> _含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:I{通道号}:CT?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:I{通道号}:CT?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> _含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的功率测量值(含变比) (:MEAS:P:CT? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>功率测量值 (单位: W,保留两位小数)</returns>
public virtual async Task<double> _含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:P{通道号}:CT?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:P{通道号}:CT?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
// ---------------- 其他高阶参数测量 ---------------- // ---------------- 其他高阶参数测量 ----------------
public virtual async Task<string> (int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的有功功率积分值 (:MEAS:WP? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>有功功率积分值 (单位: Wh,保留两位小数)</returns>
public virtual async Task<double> (int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:WP{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:WP{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> THD(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电压总谐波失真 (THD) (:MEAS:UTHD? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电压 THD 值 (单位: %,保留两位小数)</returns>
public virtual async Task<double> THD(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:UTHD{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:UTHD{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> THD(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电流总谐波失真 (THD) (:MEAS:ITHD? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电流 THD 值 (单位: %,保留两位小数)</returns>
public virtual async Task<double> THD(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:ITHD{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:ITHD{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> 线U12(CancellationToken ct = default) /// <summary>
/// 查询线电压 U12 (:MEAS:U12? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>线电压 U12 值 (单位: V,保留两位小数)</returns>
public virtual async Task<double> 线U12(CancellationToken ct = default)
{ {
return await WriteReadAsync(":MEAS:U12?\r\n", "\n", ct); string resp = await WriteReadAsync(":MEAS:U12?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> P123(CancellationToken ct = default) /// <summary>
/// 查询三相总功率 P123 (:MEAS:P123? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>三相总功率值 (单位: W,保留两位小数)</returns>
public virtual async Task<double> P123(CancellationToken ct = default)
{ {
return await WriteReadAsync(":MEAS:P123?\r\n", "\n", ct); string resp = await WriteReadAsync(":MEAS:P123?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
// ---------------- 整流平均值 ---------------- // ---------------- 整流平均值 ----------------
public virtual async Task<string> (int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电压整流平均值(不含变比) (:MEAS:UMN? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电压整流平均值 (单位: V,保留两位小数)</returns>
public virtual async Task<double> (int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:UMN{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:UMN{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> (int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电流整流平均值(不含变比) (:MEAS:IMN? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电流整流平均值 (单位: A,保留两位小数)</returns>
public virtual async Task<double> (int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:IMN{通道号}?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:IMN{通道号}?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> _含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电压整流平均值(含变比) (:MEAS:UMN:CT? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电压整流平均值 (单位: V,保留两位小数)</returns>
public virtual async Task<double> _含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:UMN{通道号}:CT?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:UMN{通道号}:CT?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
public virtual async Task<string> _含变比(int , CancellationToken ct = default) /// <summary>
/// 查询指定通道的电流整流平均值(含变比) (:MEAS:IMN:CT? 指令)
/// </summary>
/// <param name="通道号">通道编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>电流整流平均值 (单位: A,保留两位小数)</returns>
public virtual async Task<double> _含变比(int , CancellationToken ct = default)
{ {
return await WriteReadAsync($":MEAS:IMN{通道号}:CT?\r\n", "\n", ct); string resp = await WriteReadAsync($":MEAS:IMN{通道号}:CT?\r\n", "\n", ct);
return ExtractDouble(resp);
} }
#endregion #endregion
@@ -232,8 +458,10 @@ namespace DeviceCommand.Device
#region 5. #region 5.
/// <summary> /// <summary>
/// 发送自定义命令 /// 发送自定义命令(透传任意 SCPI 指令)
/// </summary> /// </summary>
/// <param name="命令">自定义命令字符串</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default) public virtual async Task (string , CancellationToken ct = default)
{ {
await SendAsync($"{命令}\r\n", ct); await SendAsync($"{命令}\r\n", ct);
@@ -241,4 +469,4 @@ namespace DeviceCommand.Device
#endregion #endregion
} }
} }
+142 -14
View File
@@ -8,30 +8,45 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices namespace DeviceCommand.Devices
{ {
/// <summary> /// <summary>
/// 环境箱(型号:RLT1000) 一拖三 /// 环境箱(型号:RLT1000) 一拖三,支持温度/湿度/水温/水流量的监测与定值控制 (Modbus TCP 协议)
/// </summary> /// </summary>
[ACPCommand] [ACPCommand]
public class RLT1000 : ModbusTcp public class RLT1000 : ModbusTcp
{ {
/// <summary>
/// 构造函数:传入 <see cref="TcpConfig"/> 初始化环境箱 Modbus TCP 通信参数
/// </summary>
public RLT1000(TcpConfig config) : base(config) public RLT1000(TcpConfig config) : base(config)
{ {
} }
// Modbus 从站号
private const byte SlaveId = 1; private const byte SlaveId = 1;
#region #region
/// <summary>
/// 读取环境箱运行状态监控字 (保持寄存器 5)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>状态监控字 (ushort 原始值)</returns>
public async Task<ushort> (CancellationToken ct = default) public async Task<ushort> (CancellationToken ct = default)
{ {
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 5, 1, ct); ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 5, 1, ct);
return re[0]; return re[0];
} }
public async Task<float> (CancellationToken ct = default) /// <summary>
/// 读取环境箱当前温度值 (保持寄存器 10-11)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>当前温度值 (单位: ℃,保留两位小数)</returns>
public async Task<double> (CancellationToken ct = default)
{ {
try try
{ {
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 10, 2, ct); ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 10, 2, ct);
return ConvertToFloat(re); return Math.Round(ConvertToFloat(re), 2);
} }
catch (Exception) catch (Exception)
{ {
@@ -39,18 +54,28 @@ namespace DeviceCommand.Devices
} }
} }
public async Task<float> 湿(CancellationToken ct = default) /// <summary>
/// 读取环境箱当前湿度值 (保持寄存器 16-17)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>当前湿度值 (单位: %RH,保留两位小数)</returns>
public async Task<double> 湿(CancellationToken ct = default)
{ {
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 16, 2, ct); ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 16, 2, ct);
return ConvertToFloat(re); return Math.Round(ConvertToFloat(re), 2);
} }
public async Task<float> (CancellationToken ct = default) /// <summary>
/// 读取环境箱当前水温值 (保持寄存器 22-23)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>当前水温值 (单位: ℃,保留两位小数)</returns>
public async Task<double> (CancellationToken ct = default)
{ {
try try
{ {
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 22, 2, ct); ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 22, 2, ct);
return ConvertToFloat(re); return Math.Round(ConvertToFloat(re), 2);
} }
catch (Exception) catch (Exception)
{ {
@@ -58,119 +83,212 @@ namespace DeviceCommand.Devices
} }
} }
public async Task<float> 1(CancellationToken ct = default) /// <summary>
/// 读取当前 1 号水流量值 (保持寄存器 28-29)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>1 号水流量值 (保留两位小数)</returns>
public async Task<double> 1(CancellationToken ct = default)
{ {
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 28, 2, ct); ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 28, 2, ct);
return ConvertToFloat(re); return Math.Round(ConvertToFloat(re), 2);
} }
public async Task<float> 2(CancellationToken ct = default) /// <summary>
/// 读取当前 2 号水流量值 (保持寄存器 34-35)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>2 号水流量值 (保留两位小数)</returns>
public async Task<double> 2(CancellationToken ct = default)
{ {
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 34, 2, ct); ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 34, 2, ct);
return ConvertToFloat(re); return Math.Round(ConvertToFloat(re), 2);
} }
public async Task<float> 3(CancellationToken ct = default) /// <summary>
/// 读取当前 3 号水流量值 (保持寄存器 40-41)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>3 号水流量值 (保留两位小数)</returns>
public async Task<double> 3(CancellationToken ct = default)
{ {
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 40, 2, ct); ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 40, 2, ct);
return ConvertToFloat(re); return Math.Round(ConvertToFloat(re), 2);
} }
#endregion #endregion
#region #region
/// <summary>
/// 切换环境箱为本地控制模式 (寄存器 147 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 147, 0, ct); await WriteSingleRegisterAsync(SlaveId, 147, 0, ct);
} }
/// <summary>
/// 切换环境箱为远程控制模式 (寄存器 147 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 147, 1, ct); await WriteSingleRegisterAsync(SlaveId, 147, 1, ct);
} }
/// <summary>
/// 远程模式下关闭环境箱 (寄存器 148 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 148, 0, ct); await WriteSingleRegisterAsync(SlaveId, 148, 0, ct);
} }
/// <summary>
/// 远程模式下开启环境箱 (寄存器 148 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 148, 1, ct); await WriteSingleRegisterAsync(SlaveId, 148, 1, ct);
} }
/// <summary>
/// 远程模式复位结束 (寄存器 149 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 149, 0, ct); await WriteSingleRegisterAsync(SlaveId, 149, 0, ct);
} }
/// <summary>
/// 远程模式复位 (寄存器 149 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 149, 1, ct); await WriteSingleRegisterAsync(SlaveId, 149, 1, ct);
} }
/// <summary>
/// 切换为程序运行模式 (寄存器 153 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 153, 0, ct); await WriteSingleRegisterAsync(SlaveId, 153, 0, ct);
} }
/// <summary>
/// 切换为定值运行模式 (寄存器 153 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 153, 1, ct); await WriteSingleRegisterAsync(SlaveId, 153, 1, ct);
} }
/// <summary>
/// 定值模式下设置目标温度 (寄存器 154-155)
/// </summary>
/// <param name="温度">目标温度值 (单位: ℃)</param>
/// <param name="ct">取消令牌</param>
public async Task (float , CancellationToken ct = default) public async Task (float , CancellationToken ct = default)
{ {
var tmp = ConvertFromFloat(); var tmp = ConvertFromFloat();
await WriteMultipleRegistersAsync(SlaveId, 154, tmp, ct); await WriteMultipleRegistersAsync(SlaveId, 154, tmp, ct);
} }
/// <summary>
/// 定值模式下设置目标湿度 (寄存器 158-159)
/// </summary>
/// <param name="湿度">目标湿度值 (单位: %RH)</param>
/// <param name="ct">取消令牌</param>
public async Task 湿(float 湿, CancellationToken ct = default) public async Task 湿(float 湿, CancellationToken ct = default)
{ {
var tmp = ConvertFromFloat(湿); var tmp = ConvertFromFloat(湿);
await WriteMultipleRegistersAsync(SlaveId, 158, tmp, ct); await WriteMultipleRegistersAsync(SlaveId, 158, tmp, ct);
} }
/// <summary>
/// 定值模式下设置目标水温 (寄存器 162-163)
/// </summary>
/// <param name="水温">目标水温值 (单位: ℃)</param>
/// <param name="ct">取消令牌</param>
public async Task (float , CancellationToken ct = default) public async Task (float , CancellationToken ct = default)
{ {
var tmp = ConvertFromFloat(); var tmp = ConvertFromFloat();
await WriteMultipleRegistersAsync(SlaveId, 162, tmp, ct); await WriteMultipleRegistersAsync(SlaveId, 162, tmp, ct);
} }
/// <summary>
/// 定值模式下设置 1 号水流量目标值 (寄存器 164-165)
/// </summary>
/// <param name="水流量">1 号水流量目标值</param>
/// <param name="ct">取消令牌</param>
public async Task 1(float , CancellationToken ct = default) public async Task 1(float , CancellationToken ct = default)
{ {
var tmp = ConvertFromFloat(); var tmp = ConvertFromFloat();
await WriteMultipleRegistersAsync(SlaveId, 164, tmp, ct); await WriteMultipleRegistersAsync(SlaveId, 164, tmp, ct);
} }
/// <summary>
/// 定值模式下设置 2 号水流量目标值 (寄存器 166-167)
/// </summary>
/// <param name="水流量">2 号水流量目标值</param>
/// <param name="ct">取消令牌</param>
public async Task 2(float , CancellationToken ct = default) public async Task 2(float , CancellationToken ct = default)
{ {
var tmp = ConvertFromFloat(); var tmp = ConvertFromFloat();
await WriteMultipleRegistersAsync(SlaveId, 166, tmp, ct); await WriteMultipleRegistersAsync(SlaveId, 166, tmp, ct);
} }
/// <summary>
/// 定值模式下设置 3 号水流量目标值 (寄存器 168-169)
/// </summary>
/// <param name="水流量">3 号水流量目标值</param>
/// <param name="ct">取消令牌</param>
public async Task 3(float , CancellationToken ct = default) public async Task 3(float , CancellationToken ct = default)
{ {
var tmp = ConvertFromFloat(); var tmp = ConvertFromFloat();
await WriteMultipleRegistersAsync(SlaveId, 168, tmp, ct); await WriteMultipleRegistersAsync(SlaveId, 168, tmp, ct);
} }
/// <summary>
/// 设置水温模式不使用 (寄存器 1181 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default) public async Task 使(CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 1181, 0, ct); await WriteSingleRegisterAsync(SlaveId, 1181, 0, ct);
} }
/// <summary>
/// 设置水温模式使用 (寄存器 1181 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default) public async Task 使(CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 1181, 1, ct); await WriteSingleRegisterAsync(SlaveId, 1181, 1, ct);
} }
/// <summary>
/// 设置环境箱温度模式不使用 (寄存器 1182 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default) public async Task 使(CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 1182, 0, ct); await WriteSingleRegisterAsync(SlaveId, 1182, 0, ct);
} }
/// <summary>
/// 设置环境箱温度模式使用 (寄存器 1182 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default) public async Task 使(CancellationToken ct = default)
{ {
await WriteSingleRegisterAsync(SlaveId, 1182, 1, ct); await WriteSingleRegisterAsync(SlaveId, 1182, 1, ct);
@@ -180,6 +298,11 @@ namespace DeviceCommand.Devices
#region #region
/// <summary>
/// 将两个 16 位寄存器(大端序)转换为 IEEE754 单精度浮点数
/// </summary>
/// <param name="values">两个 ushort 寄存器值</param>
/// <returns>转换后的浮点数值</returns>
private float ConvertToFloat(ushort[] values) private float ConvertToFloat(ushort[] values)
{ {
if (values == null || values.Length < 2) return 0f; if (values == null || values.Length < 2) return 0f;
@@ -192,6 +315,11 @@ namespace DeviceCommand.Devices
return BitConverter.ToSingle(bytes, 0); return BitConverter.ToSingle(bytes, 0);
} }
/// <summary>
/// 将单精度浮点数转换为两个 16 位寄存器(大端序)
/// </summary>
/// <param name="value">待转换的浮点数值</param>
/// <returns>两个 ushort 寄存器值</returns>
private ushort[] ConvertFromFloat(float value) private ushort[] ConvertFromFloat(float value)
{ {
byte[] bytes = BitConverter.GetBytes(value); byte[] bytes = BitConverter.GetBytes(value);
@@ -203,4 +331,4 @@ namespace DeviceCommand.Devices
#endregion #endregion
} }
} }
+235 -37
View File
@@ -1,65 +1,119 @@
using Common.Attributes; using Common.Attributes;
using DeviceCommand.Base; using DeviceCommand.Base;
using Model.Models;
using System; using System;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
// 根据您的项目实际路径保留枚举的引用
// using 通讯驱动Base.枚举;
namespace DeviceCommand.Devices namespace DeviceCommand.Devices
{ {
/// <summary>
/// S7200 负载工作模式枚举 (恒流/恒压/恒功率/恒阻等)
/// </summary>
public enum S7200负载模式_枚举 public enum S7200负载模式_枚举
{ {
/// <summary>恒流模式</summary>
CC, CC,
/// <summary>恒压模式</summary>
CV, CV,
/// <summary>恒功率模式</summary>
CP, CP,
/// <summary>恒阻模式</summary>
CR, CR,
/// <summary>恒压+恒流组合模式</summary>
CVCC, CVCC,
/// <summary>恒阻+恒流组合模式</summary>
CRCC, CRCC,
/// <summary>恒压+恒阻组合模式</summary>
CVCR, CVCR,
/// <summary>自动模式</summary>
AUTO AUTO
} }
/// <summary>
/// S7200 直流源工作模式枚举 (电压源/电流源/电池模拟/光伏模拟等)
/// </summary>
public enum S7200直流源模式_枚举 public enum S7200直流源模式_枚举
{ {
/// <summary>电压源模式</summary>
VOLTage, VOLTage,
/// <summary>电流源模式</summary>
CURRent, CURRent,
/// <summary>电池测试模式</summary>
BATTest, BATTest,
/// <summary>电池模拟模式</summary>
BATSim, BATSim,
/// <summary>光伏模拟模式</summary>
SOLar, SOLar,
/// <summary>EN50530 标准光伏曲线模式</summary>
EN50530, EN50530,
/// <summary>列表输出模式</summary>
LIST, LIST,
/// <summary>用户自定义波形模式</summary>
UDW, UDW,
/// <summary>方波输出模式</summary>
SQUare, SQUare,
/// <summary>三角波输出模式</summary>
TRIangle, TRIangle,
/// <summary>正弦波输出模式</summary>
SINusoid, SINusoid,
/// <summary>DIN40839 12V 启动曲线</summary>
DIN40839_12V, DIN40839_12V,
/// <summary>DIN40839 24V 启动曲线</summary>
DIN40839_24V, DIN40839_24V,
/// <summary>ISO16750 短时跌落 12V</summary>
IS016750_SHORTDROP_12V, IS016750_SHORTDROP_12V,
/// <summary>ISO16750 短时跌落 24V</summary>
IS016750_SHORTDROP_24V, IS016750_SHORTDROP_24V,
/// <summary>ISO16750 复位测试</summary>
IS016750_RESETTEST, IS016750_RESETTEST,
/// <summary>ISO16750 启动曲线 12V 第 1 组</summary>
IS016750_STARTINGPROFILE_12V_1, IS016750_STARTINGPROFILE_12V_1,
/// <summary>ISO16750 启动曲线 12V 第 2 组</summary>
IS016750_STARTINGPROFILE_12V_2, IS016750_STARTINGPROFILE_12V_2,
/// <summary>ISO16750 启动曲线 12V 第 3 组</summary>
IS016750_STARTINGPROFILE_12V_3, IS016750_STARTINGPROFILE_12V_3,
/// <summary>ISO16750 启动曲线 12V 第 4 组</summary>
IS016750_STARTINGPROFILE_12V_4, IS016750_STARTINGPROFILE_12V_4,
/// <summary>ISO16750 启动曲线 24V 第 1 组</summary>
IS016750_STARTINGPROFILE_24V_1, IS016750_STARTINGPROFILE_24V_1,
/// <summary>ISO16750 启动曲线 24V 第 2 组</summary>
IS016750_STARTINGPROFILE_24V_2, IS016750_STARTINGPROFILE_24V_2,
/// <summary>ISO16750 启动曲线 24V 第 3 组</summary>
IS016750_STARTINGPROFILE_24V_3, IS016750_STARTINGPROFILE_24V_3,
/// <summary>ISO16750 抛负载测试 A 12V</summary>
IS016750_LOADDUMP_TESTA_12V, IS016750_LOADDUMP_TESTA_12V,
/// <summary>ISO16750 抛负载测试 A 24V</summary>
IS016750_LOADDUMP_TESTA_24V, IS016750_LOADDUMP_TESTA_24V,
/// <summary>ISO16750 抛负载测试 B 12V</summary>
IS016750_LOADDUMP_TESTB_12V, IS016750_LOADDUMP_TESTB_12V,
/// <summary>ISO16750 抛负载测试 B 24V</summary>
IS016750_LOADDUMP_TESTB_24V, IS016750_LOADDUMP_TESTB_24V,
/// <summary>LV123 高压曲线 1</summary>
LV123_HV_1, LV123_HV_1,
/// <summary>LV123 高压曲线 2A</summary>
LV123_HV_2A, LV123_HV_2A,
/// <summary>LV123 高压曲线 2B</summary>
LV123_HV_2B, LV123_HV_2B,
/// <summary>LV123 高压曲线 3</summary>
LV123_HV_3 LV123_HV_3
} }
/// <summary>
/// S7200 工作模式枚举 (电源模式 / 负载模式)
/// </summary>
public enum S7200工作模式_枚举 public enum S7200工作模式_枚举
{ {
/// <summary>作为电源输出</summary>
, ,
/// <summary>作为负载吸收</summary>
, ,
} }
/// <summary> /// <summary>
/// 高压直流源载一体机 (适配 IT6000C SCPI指令集) /// 高压直流源载一体机 (适配 IT6000C SCPI 指令集),支持电源输出与负载吸收双向工作
/// </summary> /// </summary>
[ACPCommand] [ACPCommand]
public class S7200 : Tcp public class S7200 : Tcp
@@ -67,115 +121,239 @@ namespace DeviceCommand.Devices
// SCPI 指令结束符 // SCPI 指令结束符
private const string ScpiDelimiter = "\n"; private const string ScpiDelimiter = "\n";
#region /// <summary>
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化一体机通信参数
public async Task (CancellationToken ct = default) /// </summary>
public S7200(TcpConfig config) : base(config)
{ {
await SendAsync($"SYSTem:REMote{ScpiDelimiter}", ct); //[cite: 1]
} }
/// <summary>
/// 从设备返回的 SCPI 响应字符串中提取数值部分并转换为 double。
/// 支持科学计数法(如 2.48E-03),结果保留两位小数。
/// </summary>
/// <param name="raw">设备返回的原始字符串</param>
/// <returns>提取到的数值(保留两位小数),解析失败时返回 0</returns>
private static double ExtractDouble(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) return 0;
// 从响应中提取第一个数值(含正负号、小数点、科学计数法),可自动忽略单位后缀与命令头
var match = Regex.Match(raw, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?");
return match.Success && double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double val)
? Math.Round(val, 2)
: 0;
}
#region
/// <summary>
/// 设置仪器为远程控制模式 (SYSTem:REMote)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await SendAsync($"SYSTem:REMote{ScpiDelimiter}", ct);
}
/// <summary>
/// 设置电源工作模式 (电源/负载)。IT6000C 支持双向无缝切换,无需显式下发源载切换指令
/// </summary>
/// <param name="开关">工作模式枚举 (电源模式 / 负载模式)</param>
/// <param name="ct">取消令牌</param>
public async Task (S7200工作模式_枚举 , CancellationToken ct = default) public async Task (S7200工作模式_枚举 , CancellationToken ct = default)
{ {
// IT6000C 为双向无缝切换,底层硬件无需显式发送源载切换指令 // IT6000C 为双向无缝切换,底层硬件无需显式发送源载切换指令
// 保留此方法仅为兼容原有上位机的工步调用流程,直接返回即可 // 保留此方法仅为兼容原有上位机的工步调用流程
await Task.CompletedTask; await Task.CompletedTask;
} }
/// <summary>
/// 设置直流源工作模式 (FUNCtion 指令,如 VOLTage / CURRent / SOLar 等)
/// </summary>
/// <param name="模式">直流源工作模式枚举</param>
/// <param name="ct">取消令牌</param>
public async Task (S7200直流源模式_枚举 , CancellationToken ct = default) public async Task (S7200直流源模式_枚举 , CancellationToken ct = default)
{ {
// 原指令: MODE {模式} -> 现 IT6000C 指令: FUNCtion {模式} (如 VOLTage / CURRent)[cite: 1]
// 若枚举 ToString() 后不匹配,建议在此处加 Switch 转换
await SendAsync($"FUNCtion {模式}{ScpiDelimiter}", ct); await SendAsync($"FUNCtion {模式}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置负载工作模式。IT6000C 负载控制通过负向边界实现
/// </summary>
/// <param name="模式">负载工作模式枚举</param>
/// <param name="ct">取消令牌</param>
public async Task (S7200负载模式_枚举 , CancellationToken ct = default) public async Task (S7200负载模式_枚举 , CancellationToken ct = default)
{ {
// IT6000C 负载控制通过负向边界实现。若是想开启纯电阻模式,可调用 SINK:RESistance:STATe ON[cite: 1] // 想开启纯电阻模式,可调用 SINK:RESistance:STATe ON
await Task.CompletedTask; await Task.CompletedTask;
} }
/// <summary>
/// 开启或关闭通道输出 (OUTPut 指令)
/// </summary>
/// <param name="开关">true: 开启输出, false: 关闭输出</param>
/// <param name="ct">取消令牌</param>
public async Task (bool , CancellationToken ct = default) public async Task (bool , CancellationToken ct = default)
{ {
await SendAsync($"OUTPut {(开关 ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"OUTPut {(开关 ? 1 : 0)}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置恒流 (CC) 模式的电流值 (CURRent 指令)
/// </summary>
/// <param name="电流">电流设定值 (单位: A)</param>
/// <param name="ct">取消令牌</param>
public async Task CC模式电流(double , CancellationToken ct = default) public async Task CC模式电流(double , CancellationToken ct = default)
{ {
await SendAsync($"CURRent {电流}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"CURRent {电流}{ScpiDelimiter}", ct);
} }
public async Task CC电压H(double , CancellationToken ct = default) // 沿用原参数名“电流”,实际是设定电压上限 /// <summary>
/// 设置恒流 (CC) 模式的电压上限 (VOLTage:LIMit:POSitive 指令)
/// </summary>
/// <param name="电流">电压上限设定值 (单位: V),沿用原参数名</param>
/// <param name="ct">取消令牌</param>
public async Task CC电压H(double , CancellationToken ct = default)
{ {
await SendAsync($"VOLTage:LIMit:POSitive {电流}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"VOLTage:LIMit:POSitive {电流}{ScpiDelimiter}", ct);
} }
public async Task CC电压L(double , CancellationToken ct = default) // 沿用原参数名“电流”,实际是设定电压下限 /// <summary>
/// 设置恒流 (CC) 模式的电压下限 (VOLTage:LIMit:NEGative 指令)
/// </summary>
/// <param name="电流">电压下限设定值 (单位: V),沿用原参数名</param>
/// <param name="ct">取消令牌</param>
public async Task CC电压L(double , CancellationToken ct = default)
{ {
await SendAsync($"VOLTage:LIMit:NEGative {电流}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"VOLTage:LIMit:NEGative {电流}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置恒压 (CV) 模式的正向电流限制 (CURRent:LIMit:POSitive 指令)
/// </summary>
/// <param name="电流">正向电流限制值 (单位: A)</param>
/// <param name="ct">取消令牌</param>
public async Task CV正向电流(double , CancellationToken ct = default) public async Task CV正向电流(double , CancellationToken ct = default)
{ {
await SendAsync($"CURRent:LIMit:POSitive {电流}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"CURRent:LIMit:POSitive {电流}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置恒压 (CV) 模式的反向电流限制 (CURRent:LIMit:NEGative 指令)
/// </summary>
/// <param name="电流">反向电流限制值 (单位: A),负载模式需传负值</param>
/// <param name="ct">取消令牌</param>
public async Task CV反向电流(double , CancellationToken ct = default) public async Task CV反向电流(double , CancellationToken ct = default)
{ {
// 提醒: 此处传入的参数如果是正数建议在外部或此处转换为负值,因为载模式限制需要负数[cite: 1] // 载模式限制需要负数,若传入正数建议在外部转换为负值
await SendAsync($"CURRent:LIMit:NEGative {电流}{ScpiDelimiter}", ct); await SendAsync($"CURRent:LIMit:NEGative {电流}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置正向功率阈值 (POWer:LIMit:POSitive 指令)
/// </summary>
/// <param name="功率">正向功率阈值 (单位: W)</param>
/// <param name="ct">取消令牌</param>
public async Task (double , CancellationToken ct = default) public async Task (double , CancellationToken ct = default)
{ {
await SendAsync($"POWer:LIMit:POSitive {功率}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"POWer:LIMit:POSitive {功率}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置反向功率阈值 (POWer:LIMit:NEGative 指令)
/// </summary>
/// <param name="功率">反向功率阈值 (单位: W),必须为负数</param>
/// <param name="ct">取消令牌</param>
public async Task (double , CancellationToken ct = default) public async Task (double , CancellationToken ct = default)
{ {
// 提醒: 此处传入的参数必须为负数[cite: 1]
await SendAsync($"POWer:LIMit:NEGative {功率}{ScpiDelimiter}", ct); await SendAsync($"POWer:LIMit:NEGative {功率}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置输出电压值 (VOLTage 指令)
/// </summary>
/// <param name="电压">电压设定值 (单位: V)</param>
/// <param name="ct">取消令牌</param>
public async Task (double , CancellationToken ct = default) public async Task (double , CancellationToken ct = default)
{ {
await SendAsync($"VOLTage {电压}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"VOLTage {电压}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 清除保护状态 (OUTPut:PROTection:CLEar 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default) public async Task (CancellationToken ct = default)
{ {
await SendAsync($"OUTPut:PROTection:CLEar{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"OUTPut:PROTection:CLEar{ScpiDelimiter}", ct);
} }
/// <summary>
/// 开启或关闭过流保护 (OCP) 功能 (CURRent:PROTection:STATe 指令)
/// </summary>
/// <param name="v">true: 开启过流保护, false: 关闭过流保护</param>
/// <param name="ct">取消令牌</param>
public async Task OCP开关(bool v, CancellationToken ct = default) public async Task OCP开关(bool v, CancellationToken ct = default)
{ {
await SendAsync($"CURRent:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"CURRent:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 开启或关闭过压保护 (OVP) 功能 (VOLTage:PROTection:STATe 指令)
/// </summary>
/// <param name="v">true: 开启过压保护, false: 关闭过压保护</param>
/// <param name="ct">取消令牌</param>
public async Task OVP开关(bool v, CancellationToken ct = default) public async Task OVP开关(bool v, CancellationToken ct = default)
{ {
await SendAsync($"VOLTage:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"VOLTage:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置过压保护 (OVP) 的触发电压值 (VOLTage:PROTection 指令)
/// </summary>
/// <param name="oVP电压">过压保护触发电压值 (单位: V)</param>
/// <param name="ct">取消令牌</param>
public async Task OVP电压(double oVP电压, CancellationToken ct = default) public async Task OVP电压(double oVP电压, CancellationToken ct = default)
{ {
await SendAsync($"VOLTage:PROTection {oVP电压}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"VOLTage:PROTection {oVP电压}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置过流保护 (OCP) 的触发电流值 (CURRent:PROTection 指令)
/// </summary>
/// <param name="oCP电流">过流保护触发电流值 (单位: A)</param>
/// <param name="ct">取消令牌</param>
public async Task OCP电流(double oCP电流, CancellationToken ct = default) public async Task OCP电流(double oCP电流, CancellationToken ct = default)
{ {
await SendAsync($"CURRent:PROTection {oCP电流}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"CURRent:PROTection {oCP电流}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 开启或关闭过功率保护 (OPP) 功能 (POWer:PROTection:STATe 指令)
/// </summary>
/// <param name="v">true: 开启过功率保护, false: 关闭过功率保护</param>
/// <param name="ct">取消令牌</param>
public async Task (bool v, CancellationToken ct = default) public async Task (bool v, CancellationToken ct = default)
{ {
await SendAsync($"POWer:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"POWer:PROTection:STATe {(v ? 1 : 0)}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置过功率保护 (OPP) 的触发功率值 (POWer:PROTection 指令)
/// </summary>
/// <param name="oPP功率">过功率保护触发功率值 (单位: W)</param>
/// <param name="ct">取消令牌</param>
public async Task (double oPP功率, CancellationToken ct = default) public async Task (double oPP功率, CancellationToken ct = default)
{ {
await SendAsync($"POWer:PROTection {oPP功率}{ScpiDelimiter}", ct); //[cite: 1] await SendAsync($"POWer:PROTection {oPP功率}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 发送自定义 SCPI 指令
/// </summary>
/// <param name="指令">自定义指令字符串</param>
/// <param name="ct">取消令牌</param>
public async Task (string , CancellationToken ct = default) public async Task (string , CancellationToken ct = default)
{ {
await SendAsync($"{指令}{ScpiDelimiter}", ct); await SendAsync($"{指令}{ScpiDelimiter}", ct);
@@ -185,29 +363,49 @@ namespace DeviceCommand.Devices
#region #region
/// <summary>
/// 查询设备识别信息 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
public async Task<string> (CancellationToken ct = default) public async Task<string> (CancellationToken ct = default)
{ {
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary>
/// 查询实时输出电压 (MEASure:VOLTage? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电压值 (单位: V,保留两位小数)</returns>
public async Task<double> (CancellationToken ct = default) public async Task<double> (CancellationToken ct = default)
{ {
var str = await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] var str = await WriteReadAsync($"MEASure:VOLTage?{ScpiDelimiter}", ScpiDelimiter, ct);
return double.TryParse(str, out double result) ? result : 0; return ExtractDouble(str);
} }
/// <summary>
/// 查询实时输出电流 (MEASure:CURRent? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电流值 (单位: A,保留两位小数)</returns>
public async Task<double> (CancellationToken ct = default) public async Task<double> (CancellationToken ct = default)
{ {
var str = await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] var str = await WriteReadAsync($"MEASure:CURRent?{ScpiDelimiter}", ScpiDelimiter, ct);
return double.TryParse(str, out double result) ? result : 0; return ExtractDouble(str);
} }
/// <summary>
/// 查询实时功率 (MEASure:POWer? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时功率值 (单位: W,保留两位小数)</returns>
public async Task<double> (CancellationToken ct = default) public async Task<double> (CancellationToken ct = default)
{ {
var str = await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct); //[cite: 1] var str = await WriteReadAsync($"MEASure:POWer?{ScpiDelimiter}", ScpiDelimiter, ct);
return double.TryParse(str, out double result) ? result : 0; return ExtractDouble(str);
} }
#endregion #endregion
} }
} }
+143 -42
View File
@@ -5,6 +5,7 @@ using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -37,22 +38,48 @@ namespace DeviceCommand.Devices
/// </summary> /// </summary>
public enum TekMeasurementType public enum TekMeasurementType
{ {
/// <summary>幅度</summary>
AMPLitude, AMPLitude,
/// <summary>频率</summary>
FREQuency, FREQuency,
/// <summary>平均值</summary>
MEAN, MEAN,
/// <summary>峰峰值</summary>
PK2PK, PK2PK,
/// <summary>最大值</summary>
MAXimum, MAXimum,
/// <summary>最小值</summary>
MINimum MINimum
} }
#endregion #endregion
/// <summary>
/// Tektronix 示波器 (MSO 4/5/6 系列),支持采集控制、通道设置、自动测量与截图导出
/// </summary>
[ACPCommand] [ACPCommand]
public class TektronixMSO : Tcp public class TektronixMSO : Tcp
{ {
// SCPI 指令结束符 (消息终止符需使用 LF) // SCPI 指令结束符 (消息终止符需使用 LF)
private const string ScpiDelimiter = "\n"; private const string ScpiDelimiter = "\n";
/// <summary>
/// 从设备返回的 SCPI 响应字符串中提取数值部分并转换为 double。
/// 支持科学计数法(如 2.48E-03),结果保留两位小数。
/// </summary>
/// <param name="raw">设备返回的原始字符串</param>
/// <returns>提取到的数值(保留两位小数),解析失败时返回 0</returns>
private static double ExtractDouble(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) return 0;
// 从响应中提取第一个数值(含正负号、小数点、科学计数法),可自动忽略单位后缀与命令头
var match = Regex.Match(raw, @"[+-]?(?:\d+\.?\d*|\.\d+)(?:[Ee][+-]?\d+)?");
return match.Success && double.TryParse(match.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double val)
? Math.Round(val, 2)
: 0;
}
#region MSO44B ACP #region MSO44B ACP
public enum AcquireMode { Sample = 0, PeakDetect = 1, HiRes = 2, Average = 3, Envelope = 4 } public enum AcquireMode { Sample = 0, PeakDetect = 1, HiRes = 2, Average = 3, Envelope = 4 }
@@ -81,21 +108,32 @@ namespace DeviceCommand.Devices
#region 1. #region 1.
/// <summary>
/// 查询仪器识别码 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
// 返回仪器的标识代码[cite: 2]
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary>
/// 查询仪器是否处于忙碌状态,常用于同步操作 (BUSY? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>忙碌状态字符串 (0: 空闲, 1: 忙碌)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
// 查询仪器是否处于忙碌状态,常用于同步操作[cite: 2]
return await WriteReadAsync($"BUSY?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($"BUSY?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary>
/// 指示仪器执行信号路径校准 (SPC) (*CAL? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default) public virtual async Task (CancellationToken ct = default)
{ {
// 指示仪器执行信号路径校准 (SPC)[cite: 2]
await SendAsync($"*CAL?{ScpiDelimiter}", ct); await SendAsync($"*CAL?{ScpiDelimiter}", ct);
} }
@@ -104,24 +142,33 @@ namespace DeviceCommand.Devices
#region 2. (Acquisition) #region 2. (Acquisition)
/// <summary> /// <summary>
/// 启动或停止采集 /// 启动或停止采集 (ACQuire:STATE 指令)
/// </summary> /// </summary>
/// <param name="运行">true: 启动采集 (RUN), false: 停止采集 (STOP)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (bool , CancellationToken ct = default) public virtual async Task (bool , CancellationToken ct = default)
{ {
// 启动、停止或返回采集状态[cite: 2]
string = ? "RUN" : "STOP"; string = ? "RUN" : "STOP";
await SendAsync($"ACQuire:STATE {参数}{ScpiDelimiter}", ct); await SendAsync($"ACQuire:STATE {参数}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 查询当前采集状态 (ACQuire:STATE? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>采集状态字符串 (RUN / STOP)</returns>
public virtual async Task<string> (CancellationToken ct = default) public virtual async Task<string> (CancellationToken ct = default)
{ {
// 启动、停止或返回采集状态[cite: 2]
return await WriteReadAsync($"ACQuire:STATE?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($"ACQuire:STATE?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary>
/// 设置采集模式 (ACQuire:MODe 指令)
/// </summary>
/// <param name="模式">采集模式枚举 (采样/峰值检测/高分辨率/平均/包络)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (TekAcquisitionMode , CancellationToken ct = default) public virtual async Task (TekAcquisitionMode , CancellationToken ct = default)
{ {
// 设置或查询采集模式[cite: 2]
await SendAsync($"ACQuire:MODe {模式}{ScpiDelimiter}", ct); await SendAsync($"ACQuire:MODe {模式}{ScpiDelimiter}", ct);
} }
@@ -129,23 +176,36 @@ namespace DeviceCommand.Devices
#region 3. (Horizontal) #region 3. (Horizontal)
/// <summary>
/// 设置水平刻度 (Scale),即每格代表的时间 (HORizontal:SCAle 指令)
/// </summary>
/// <param name="秒每格">水平刻度值 (单位: s/Div)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (double , CancellationToken ct = default) public virtual async Task (double , CancellationToken ct = default)
{ {
// 设置或查询水平刻度 (Scale)[cite: 2]
string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SCAle {0:E}{1}", , ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SCAle {0:E}{1}", , ScpiDelimiter);
await SendAsync(cmd, ct); await SendAsync(cmd, ct);
} }
/// <summary>
/// 设置水平记录长度 (HORizontal:RECOrdlength 指令)
/// </summary>
/// <param name="长度">记录长度(采样点数)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (long , CancellationToken ct = default) public virtual async Task (long , CancellationToken ct = default)
{ {
// 设置或查询水平记录长度[cite: 2]
await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct); await SendAsync($"HORizontal:RECOrdlength {长度}{ScpiDelimiter}", ct);
} }
public virtual async Task<string> (CancellationToken ct = default) /// <summary>
/// 查询水平采样率 (HORizontal:SAMPLERate? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>水平采样率 (单位: Sa/s,保留两位小数)</returns>
public virtual async Task<double> (CancellationToken ct = default)
{ {
// 查询水平采样率[cite: 2] string resp = await WriteReadAsync($"HORizontal:SAMPLERate?{ScpiDelimiter}", ScpiDelimiter, ct);
return await WriteReadAsync($"HORizontal:SAMPLERate?{ScpiDelimiter}", ScpiDelimiter, ct); return ExtractDouble(resp);
} }
#endregion #endregion
@@ -153,18 +213,23 @@ namespace DeviceCommand.Devices
#region 4. (Vertical) #region 4. (Vertical)
/// <summary> /// <summary>
/// 全局启用或关闭某通道的显示 /// 全局启用或关闭某通道的显示 (DISplay:GLObal:CH:STATE 指令)
/// </summary> /// </summary>
/// <param name="通道号">通道编号 (1 - 4)</param>
/// <param name="开启">true: 开启通道显示, false: 关闭通道显示</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int , bool , CancellationToken ct = default) public virtual async Task (int , bool , CancellationToken ct = default)
{ {
// 4/5/6 Series MSO 标准通道显示指令[cite: 2]
string = ? "ON" : "OFF"; string = ? "ON" : "OFF";
await SendAsync($"DISplay:GLObal:CH{通道号}:STATE {参数}{ScpiDelimiter}", ct); await SendAsync($"DISplay:GLObal:CH{通道号}:STATE {参数}{ScpiDelimiter}", ct);
} }
/// <summary> /// <summary>
/// 设置指定通道的垂直缩放比例 (Volts/Div) /// 设置指定通道的垂直缩放比例 (CH:SCAle 指令)
/// </summary> /// </summary>
/// <param name="通道号">通道编号 (1 - 4)</param>
/// <param name="伏特每格">垂直刻度值 (单位: V/Div)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int , double , CancellationToken ct = default) public virtual async Task (int , double , CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:SCAle {1:E}{2}", , , ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:SCAle {1:E}{2}", , , ScpiDelimiter);
@@ -172,8 +237,11 @@ namespace DeviceCommand.Devices
} }
/// <summary> /// <summary>
/// 设置指定通道的垂直位置 (Divisions) /// 设置指定通道的垂直位置 (CH:POSition 指令)
/// </summary> /// </summary>
/// <param name="通道号">通道编号 (1 - 4)</param>
/// <param name="格数">垂直位置偏移 (单位: Div)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int , double , CancellationToken ct = default) public virtual async Task (int , double , CancellationToken ct = default)
{ {
string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:POSition {1:F2}{2}", , , ScpiDelimiter); string cmd = string.Format(CultureInfo.InvariantCulture, "CH{0}:POSition {1:F2}{2}", , , ScpiDelimiter);
@@ -185,43 +253,73 @@ namespace DeviceCommand.Devices
#region 5. (Measurement) #region 5. (Measurement)
/// <summary> /// <summary>
/// 动态添加并开启一个测量项 /// 动态添加并开启一个测量项 (MEASUrement:ADDNew 指令)。
/// 4/5/6 Series MSO 必须先动态创建测量项
/// </summary> /// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int , CancellationToken ct = default) public virtual async Task (int , CancellationToken ct = default)
{ {
// 4/5/6 Series MSO 必须先动态创建测量项[cite: 2]
await SendAsync($"MEASUrement:ADDNew \"MEAS{}\"{ScpiDelimiter}", ct); await SendAsync($"MEASUrement:ADDNew \"MEAS{}\"{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置测量项的测量类型 (MEASUrement:TYPe 指令)
/// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="类型">测量类型枚举 (幅度/频率/平均值/峰峰值等)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int , TekMeasurementType , CancellationToken ct = default) public virtual async Task (int , TekMeasurementType , CancellationToken ct = default)
{ {
// 设置或查询测量类型[cite: 2]
await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct); await SendAsync($"MEASUrement:MEAS{测量项编号}:TYPe {类型}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 设置测量项的测量源 (MEASUrement:SOUrce1 指令)
/// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="源名称">测量源名称 (如 "CH1", "CH2")</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int , string , CancellationToken ct = default) public virtual async Task (int , string , CancellationToken ct = default)
{ {
// 4/5/6 Series 中设置测量源[cite: 2]
await SendAsync($"MEASUrement:MEAS{测量项编号}:SOUrce1 {源名称}{ScpiDelimiter}", ct); await SendAsync($"MEASUrement:MEAS{测量项编号}:SOUrce1 {源名称}{ScpiDelimiter}", ct);
} }
/// <summary>
/// 查询测量项的当前采集平均值 (MEASUrement:RESUlts:CURRentacq:MEAN? 指令)
/// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>测量项当前采集的平均值 (保留两位小数)</returns>
[Monitorable("测量项的当前采集平均值")] [Monitorable("测量项的当前采集平均值")]
public virtual async Task<string> (int , CancellationToken ct = default) public virtual async Task<double> (int , CancellationToken ct = default)
{ {
// 4/5/6 Series 获取当前采集值的标准方式,查询平均值[cite: 2] string resp = await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:CURRentacq:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct); return ExtractDouble(resp);
} }
public virtual async Task<string> (int , CancellationToken ct = default) /// <summary>
/// 查询测量项的全局历史累积平均值 (MEASUrement:RESUlts:ALLAcqs:MEAN? 指令)
/// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>测量项全局平均值 (保留两位小数)</returns>
public virtual async Task<double> (int , CancellationToken ct = default)
{ {
// 获取所有采集(AllAcqs)历史累积的平均值[cite: 2] string resp = await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct);
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MEAN?{ScpiDelimiter}", ScpiDelimiter, ct); return ExtractDouble(resp);
} }
public virtual async Task<string> (int , CancellationToken ct = default) /// <summary>
/// 查询测量项的全局历史累积最大值 (MEASUrement:RESUlts:ALLAcqs:MAXimum? 指令)
/// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>测量项全局最大值 (保留两位小数)</returns>
public virtual async Task<double> (int , CancellationToken ct = default)
{ {
// 获取所有采集(AllAcqs)历史累积的最大值[cite: 2] string resp = await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct);
return await WriteReadAsync($"MEASUrement:MEAS{测量项编号}:RESUlts:ALLAcqs:MAXimum?{ScpiDelimiter}", ScpiDelimiter, ct); return ExtractDouble(resp);
} }
#endregion #endregion
@@ -237,13 +335,13 @@ namespace DeviceCommand.Devices
{ {
string = "E:/temp1.png"; string = "E:/temp1.png";
// 1. 直接保存截图到示波器U盘 (自动识别 .png 扩展名)[cite: 2] // 1. 直接保存截图到示波器U盘 (自动识别 .png 扩展名)
await SendAsync($"SAVe:IMAGe \"{}\"{ScpiDelimiter}", ct); await SendAsync($"SAVe:IMAGe \"{}\"{ScpiDelimiter}", ct);
// 2. 发送 *OPC? 并等待返回 1,确保图像文件已经完全写入磁盘[cite: 2] // 2. 发送 *OPC? 并等待返回 1,确保图像文件已经完全写入磁盘
await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct); await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
// 3. 将文件内容读取到当前通信接口[cite: 2] // 3. 将文件内容读取到当前通信接口
await SendAsync($"FILESystem:READFile \"{}\"{ScpiDelimiter}", ct); await SendAsync($"FILESystem:READFile \"{}\"{ScpiDelimiter}", ct);
// 4. 提取底层的二进制块 (Block Data) // 4. 提取底层的二进制块 (Block Data)
@@ -252,7 +350,7 @@ namespace DeviceCommand.Devices
// 5. 将提取的二进制字节流写入上位机本地磁盘 // 5. 将提取的二进制字节流写入上位机本地磁盘
await File.WriteAllBytesAsync(, imageBytes, ct); await File.WriteAllBytesAsync(, imageBytes, ct);
// 6. 删除示波器上的临时文件,保持设备存储干净[cite: 2] // 6. 删除示波器上的临时文件,保持设备存储干净
await SendAsync($"FILESystem:DELEte \"{}\"{ScpiDelimiter}", ct); await SendAsync($"FILESystem:DELEte \"{}\"{ScpiDelimiter}", ct);
} }
@@ -331,41 +429,44 @@ namespace DeviceCommand.Devices
#region 7. (Status and Error) #region 7. (Status and Error)
/// <summary> /// <summary>
/// 清除所有的状态寄存器和错误/事件队列 /// 清除所有的状态寄存器和错误/事件队列 (*CLS 指令)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task Async(CancellationToken ct = default) public virtual async Task Async(CancellationToken ct = default)
{ {
// 发送 *CLS 指令清空状态[cite: 2]
await SendAsync($"*CLS{ScpiDelimiter}", ct); await SendAsync($"*CLS{ScpiDelimiter}", ct);
} }
/// <summary> /// <summary>
/// 查询错误队列中当前的事件/错误数量 /// 查询错误队列中当前的事件/错误数量 (EVQty? 指令)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>事件队列中的事件数量字符串</returns>
public virtual async Task<string> Async(CancellationToken ct = default) public virtual async Task<string> Async(CancellationToken ct = default)
{ {
// 返回事件队列中的事件数量
return await WriteReadAsync($"EVQty?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($"EVQty?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary> /// <summary>
/// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出 /// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出 (EVMsg? 指令)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>事件代码和消息字符串</returns>
public virtual async Task<string> Async(CancellationToken ct = default) public virtual async Task<string> Async(CancellationToken ct = default)
{ {
// 返回事件队列中的事件代码和消息
return await WriteReadAsync($"EVMsg?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($"EVMsg?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
/// <summary> /// <summary>
/// 读取并清空队列中的所有错误和事件消息 /// 读取并清空队列中的所有错误和事件消息 (ALLEv? 指令)
/// </summary> /// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>所有事件及其消息字符串</returns>
public virtual async Task<string> Async(CancellationToken ct = default) public virtual async Task<string> Async(CancellationToken ct = default)
{ {
// 返回所有事件及其消息
return await WriteReadAsync($"ALLEv?{ScpiDelimiter}", ScpiDelimiter, ct); return await WriteReadAsync($"ALLEv?{ScpiDelimiter}", ScpiDelimiter, ct);
} }
#endregion #endregion
} }
} }
+74 -4
View File
@@ -8,11 +8,20 @@ using System.Threading.Tasks;
namespace DeviceCommand.Flexible namespace DeviceCommand.Flexible
{ {
/// <summary>
/// 灵活型 Modbus RTU 串口通信类(免实例化,每次调用临时创建串口连接)。
/// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行,
/// 适用于偶发性、无需保持长连接的 Modbus RTU 设备读写场景。
/// </summary>
[ACPCommand] [ACPCommand]
public static class FModbusRTU public static class FModbusRTU
{ {
// 通信锁:保证同一时刻只有一个 Modbus 事务在执行
private static readonly SemaphoreSlim _commLock = new(1, 1); private static readonly SemaphoreSlim _commLock = new(1, 1);
/// <summary>
/// 创建串口实例并配置超时参数。
/// </summary>
private static SerialPort CreatePort( private static SerialPort CreatePort(
string portName, string portName,
int baudRate, int baudRate,
@@ -29,6 +38,9 @@ namespace DeviceCommand.Flexible
}; };
} }
/// <summary>
/// 基于指定串口创建 Modbus RTU 主站。
/// </summary>
private static IModbusMaster CreateMaster(SerialPort port) private static IModbusMaster CreateMaster(SerialPort port)
{ {
return new ModbusFactory().CreateRtuMaster(port); return new ModbusFactory().CreateRtuMaster(port);
@@ -36,7 +48,22 @@ namespace DeviceCommand.Flexible
#region Holding Register #region Holding Register
public static async Task<ushort[]> ReadHoldingRegistersAsync( /// <summary>
/// 读取保持寄存器(功能码 03),返回指定数量的寄存器值数组。
/// </summary>
/// <param name="portName">串口名称,如 "COM1"</param>
/// <param name="baudRate">波特率,如 9600、115200</param>
/// <param name="slaveAddress">Modbus 从站站号 (1-247)</param>
/// <param name="startAddress">起始寄存器地址</param>
/// <param name="numberOfPoints">读取的寄存器数量</param>
/// <param name="dataBits">数据位,默认 8</param>
/// <param name="stopBits">停止位,默认 1 位</param>
/// <param name="parity">校验位,默认无校验</param>
/// <param name="readTimeout">读取超时时间(毫秒),默认 3000</param>
/// <param name="writeTimeout">写入超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
/// <returns>读取到的寄存器值数组</returns>
public static async Task<ushort[]> (
string portName, string portName,
int baudRate, int baudRate,
byte slaveAddress, byte slaveAddress,
@@ -73,7 +100,21 @@ namespace DeviceCommand.Flexible
} }
} }
public static async Task WriteSingleRegisterAsync( /// <summary>
/// 写入单个保持寄存器(功能码 06)。
/// </summary>
/// <param name="portName">串口名称,如 "COM1"</param>
/// <param name="baudRate">波特率,如 9600、115200</param>
/// <param name="slaveAddress">Modbus 从站站号 (1-247)</param>
/// <param name="registerAddress">要写入的寄存器地址</param>
/// <param name="value">要写入的寄存器值 (0-65535)</param>
/// <param name="dataBits">数据位,默认 8</param>
/// <param name="stopBits">停止位,默认 1 位</param>
/// <param name="parity">校验位,默认无校验</param>
/// <param name="readTimeout">读取超时时间(毫秒),默认 3000</param>
/// <param name="writeTimeout">写入超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
public static async Task (
string portName, string portName,
int baudRate, int baudRate,
byte slaveAddress, byte slaveAddress,
@@ -114,7 +155,22 @@ namespace DeviceCommand.Flexible
#region Coil #region Coil
public static async Task<bool[]> ReadCoilsAsync( /// <summary>
/// 读取线圈状态(功能码 01),返回指定数量的线圈开关状态数组。
/// </summary>
/// <param name="portName">串口名称,如 "COM1"</param>
/// <param name="baudRate">波特率,如 9600、115200</param>
/// <param name="slaveAddress">Modbus 从站站号 (1-247)</param>
/// <param name="startAddress">起始线圈地址</param>
/// <param name="numberOfPoints">读取的线圈数量</param>
/// <param name="dataBits">数据位,默认 8</param>
/// <param name="stopBits">停止位,默认 1 位</param>
/// <param name="parity">校验位,默认无校验</param>
/// <param name="readTimeout">读取超时时间(毫秒),默认 3000</param>
/// <param name="writeTimeout">写入超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
/// <returns>线圈状态数组,true 表示吸合/导通</returns>
public static async Task<bool[]> 线(
string portName, string portName,
int baudRate, int baudRate,
byte slaveAddress, byte slaveAddress,
@@ -151,7 +207,21 @@ namespace DeviceCommand.Flexible
} }
} }
public static async Task WriteSingleCoilAsync( /// <summary>
/// 写入单个线圈(功能码 05),控制单个开关量输出。
/// </summary>
/// <param name="portName">串口名称,如 "COM1"</param>
/// <param name="baudRate">波特率,如 9600、115200</param>
/// <param name="slaveAddress">Modbus 从站站号 (1-247)</param>
/// <param name="coilAddress">要写入的线圈地址</param>
/// <param name="value">线圈状态(true: 吸合/导通, false: 断开)</param>
/// <param name="dataBits">数据位,默认 8</param>
/// <param name="stopBits">停止位,默认 1 位</param>
/// <param name="parity">校验位,默认无校验</param>
/// <param name="readTimeout">读取超时时间(毫秒),默认 3000</param>
/// <param name="writeTimeout">写入超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
public static async Task 线(
string portName, string portName,
int baudRate, int baudRate,
byte slaveAddress, byte slaveAddress,
+59 -4
View File
@@ -8,11 +8,20 @@ using System.Threading.Tasks;
namespace DeviceCommand.Flexible namespace DeviceCommand.Flexible
{ {
/// <summary>
/// 灵活型 Modbus TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。
/// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行,
/// 适用于偶发性、无需保持长连接的 Modbus TCP 设备读写场景。
/// </summary>
[ACPCommand] [ACPCommand]
public static class FModbusTCP public static class FModbusTCP
{ {
// 通信锁:保证同一时刻只有一个 Modbus 事务在执行
private static readonly SemaphoreSlim _commLock = new(1, 1); private static readonly SemaphoreSlim _commLock = new(1, 1);
/// <summary>
/// 建立 TCP 连接并创建 Modbus TCP 主站。
/// </summary>
private static async Task<IModbusMaster> ConnectAsync(string ipAddress, int port, int sendTimeout, int receiveTimeout, CancellationToken ct) private static async Task<IModbusMaster> ConnectAsync(string ipAddress, int port, int sendTimeout, int receiveTimeout, CancellationToken ct)
{ {
var tcpClient = new TcpClient(); var tcpClient = new TcpClient();
@@ -26,7 +35,19 @@ namespace DeviceCommand.Flexible
#region Holding Registers #region Holding Registers
public static async Task<ushort[]> ReadHoldingRegistersAsync( /// <summary>
/// 读取保持寄存器(功能码 03),返回指定数量的寄存器值数组。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">Modbus TCP 端口号,默认常用 502</param>
/// <param name="slaveAddress">Modbus 从站站号(单元标识符)</param>
/// <param name="startAddress">起始寄存器地址</param>
/// <param name="numberOfPoints">读取的寄存器数量</param>
/// <param name="sendTimeout">发送/连接超时时间(毫秒),默认 3000</param>
/// <param name="receiveTimeout">接收超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
/// <returns>读取到的寄存器值数组</returns>
public static async Task<ushort[]> (
string ipAddress, string ipAddress,
int port, int port,
byte slaveAddress, byte slaveAddress,
@@ -51,7 +72,18 @@ namespace DeviceCommand.Flexible
} }
} }
public static async Task WriteSingleRegisterAsync( /// <summary>
/// 写入单个保持寄存器(功能码 06)。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">Modbus TCP 端口号,默认常用 502</param>
/// <param name="slaveAddress">Modbus 从站站号(单元标识符)</param>
/// <param name="registerAddress">要写入的寄存器地址</param>
/// <param name="value">要写入的寄存器值 (0-65535)</param>
/// <param name="sendTimeout">发送/连接超时时间(毫秒),默认 3000</param>
/// <param name="receiveTimeout">接收超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
public static async Task (
string ipAddress, string ipAddress,
int port, int port,
byte slaveAddress, byte slaveAddress,
@@ -79,7 +111,19 @@ namespace DeviceCommand.Flexible
#region Coils #region Coils
public static async Task<bool[]> ReadCoilsAsync( /// <summary>
/// 读取线圈状态(功能码 01),返回指定数量的线圈开关状态数组。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">Modbus TCP 端口号,默认常用 502</param>
/// <param name="slaveAddress">Modbus 从站站号(单元标识符)</param>
/// <param name="startAddress">起始线圈地址</param>
/// <param name="numberOfPoints">读取的线圈数量</param>
/// <param name="sendTimeout">发送/连接超时时间(毫秒),默认 3000</param>
/// <param name="receiveTimeout">接收超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
/// <returns>线圈状态数组,true 表示吸合/导通</returns>
public static async Task<bool[]> 线(
string ipAddress, string ipAddress,
int port, int port,
byte slaveAddress, byte slaveAddress,
@@ -104,7 +148,18 @@ namespace DeviceCommand.Flexible
} }
} }
public static async Task WriteSingleCoilAsync( /// <summary>
/// 写入单个线圈(功能码 05),控制单个开关量输出。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">Modbus TCP 端口号,默认常用 502</param>
/// <param name="slaveAddress">Modbus 从站站号(单元标识符)</param>
/// <param name="coilAddress">要写入的线圈地址</param>
/// <param name="value">线圈状态(true: 吸合/导通, false: 断开)</param>
/// <param name="sendTimeout">发送/连接超时时间(毫秒),默认 3000</param>
/// <param name="receiveTimeout">接收超时时间(毫秒),默认 3000</param>
/// <param name="ct">异步取消令牌</param>
public static async Task 线(
string ipAddress, string ipAddress,
int port, int port,
byte slaveAddress, byte slaveAddress,
+38 -2
View File
@@ -7,11 +7,21 @@ using System.Threading.Tasks;
namespace DeviceCommand.Flexible namespace DeviceCommand.Flexible
{ {
/// <summary>
/// 灵活型串口通信类(免实例化,每次调用临时创建串口连接)。
/// 支持只发送指令、发送并读取应答两种最常用操作,
/// 内部使用通信锁保证同一时刻只有一个串口事务在执行,
/// 适用于偶发性、无需保持长连接的串口设备通信场景(如示波器、电源的 SCPI 指令)。
/// </summary>
[ACPCommand] [ACPCommand]
public static class FSerialPort public static class FSerialPort
{ {
// 通信锁:保证同一时刻只有一个串口事务在执行
private static readonly SemaphoreSlim _commLock = new(1, 1); private static readonly SemaphoreSlim _commLock = new(1, 1);
/// <summary>
/// 创建串口实例并配置 UTF8 编码与超时参数。
/// </summary>
private static SerialPort CreatePort(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, int sendTimeout,int receiveTimeout) 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) return new SerialPort(portName, baudRate, parity, dataBits, stopBits)
@@ -24,7 +34,19 @@ namespace DeviceCommand.Flexible
#region #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) /// <summary>
/// 向串口发送一条字符串指令(只发送,不等待应答)。
/// </summary>
/// <param name="portName">串口名称,如 "COM1"</param>
/// <param name="baudRate">波特率,如 9600、115200</param>
/// <param name="parity">校验位</param>
/// <param name="dataBits">数据位,常用 8</param>
/// <param name="stopBits">停止位</param>
/// <param name="sendTimeout">发送超时时间(毫秒)</param>
/// <param name="receiveTimeout">接收超时时间(毫秒)</param>
/// <param name="command">要发送的指令字符串</param>
/// <param name="ct">异步取消令牌</param>
public static async Task (string portName,int baudRate,Parity parity,int dataBits,StopBits stopBits,int sendTimeout,int receiveTimeout,string command,CancellationToken ct = default)
{ {
await _commLock.WaitAsync(ct); await _commLock.WaitAsync(ct);
try try
@@ -52,7 +74,21 @@ namespace DeviceCommand.Flexible
#region #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) /// <summary>
/// 向串口发送一条字符串指令并读取应答,读取到结束符为止(应答不含结束符)。
/// </summary>
/// <param name="portName">串口名称,如 "COM1"</param>
/// <param name="baudRate">波特率,如 9600、115200</param>
/// <param name="parity">校验位</param>
/// <param name="dataBits">数据位,常用 8</param>
/// <param name="stopBits">停止位</param>
/// <param name="sendTimeout">发送超时时间(毫秒)</param>
/// <param name="receiveTimeout">接收超时时间(毫秒),超时未收到结束符抛出超时异常</param>
/// <param name="command">要发送的指令字符串</param>
/// <param name="delimiter">应答结束符,默认换行符 "\n"</param>
/// <param name="ct">异步取消令牌</param>
/// <returns>去除结束符并去除首尾空白后的应答字符串</returns>
public static async Task<string> (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); await _commLock.WaitAsync(ct);
try try
+46 -5
View File
@@ -4,14 +4,29 @@ using System.Text;
namespace DeviceCommand.Flexible namespace DeviceCommand.Flexible
{ {
/// <summary>
/// 灵活型 TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。
/// 支持字节/文本发送、定长字节读取、按结束符读取文本行四种操作,
/// 内部使用通信锁保证同一时刻只有一个 TCP 事务在执行,
/// 适用于偶发性、无需保持长连接的 TCP 设备通信场景。
/// </summary>
[ACPCommand] [ACPCommand]
public static class FTCP public static class FTCP
{ {
// 通信锁:保证同一时刻只有一个 TCP 事务在执行
private static readonly SemaphoreSlim _commLock = new(1, 1); private static readonly SemaphoreSlim _commLock = new(1, 1);
#region Send #region Send
public static async Task SendAsync(string ipAddress,int port,int sendTimeout, byte[] buffer, CancellationToken ct = default) /// <summary>
/// 向指定 TCP 端点发送一段字节数据(发送完成后立即断开)。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">TCP 端口号</param>
/// <param name="sendTimeout">发送超时时间(毫秒)</param>
/// <param name="buffer">要发送的字节数组</param>
/// <param name="ct">异步取消令牌</param>
public static async Task (string ipAddress,int port,int sendTimeout, byte[] buffer, CancellationToken ct = default)
{ {
await _commLock.WaitAsync(ct); await _commLock.WaitAsync(ct);
try try
@@ -30,16 +45,33 @@ namespace DeviceCommand.Flexible
} }
} }
public static Task SendAsync(string ipAddress, int port,int sendTimeout, string text,CancellationToken ct = default) /// <summary>
/// 向指定 TCP 端点发送一段文本(以 UTF8 编码为字节后发送,发送完成后立即断开)。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">TCP 端口号</param>
/// <param name="sendTimeout">发送超时时间(毫秒)</param>
/// <param name="text">要发送的文本字符串</param>
/// <param name="ct">异步取消令牌</param>
public static Task (string ipAddress, int port,int sendTimeout, string text,CancellationToken ct = default)
{ {
return SendAsync( ipAddress, port, sendTimeout, Encoding.UTF8.GetBytes(text),ct); return ( ipAddress, port, sendTimeout, Encoding.UTF8.GetBytes(text),ct);
} }
#endregion #endregion
#region Read #region Read
public static async Task<byte[]> ReadAsync(string ipAddress,int port,int receiveTimeout,int length,CancellationToken ct = default) /// <summary>
/// 连接指定 TCP 端点并读取指定长度的字节数据(连接关闭或读满为止)。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">TCP 端口号</param>
/// <param name="receiveTimeout">接收超时时间(毫秒)</param>
/// <param name="length">要读取的字节长度</param>
/// <param name="ct">异步取消令牌</param>
/// <returns>实际读取到的字节数组(对端提前关闭时可能短于请求长度)</returns>
public static async Task<byte[]> (string ipAddress,int port,int receiveTimeout,int length,CancellationToken ct = default)
{ {
await _commLock.WaitAsync(ct); await _commLock.WaitAsync(ct);
try try
@@ -72,7 +104,16 @@ namespace DeviceCommand.Flexible
} }
} }
public static async Task<string> ReadLineAsync( string ipAddress, int port, int receiveTimeout, string delimiter = "\n",CancellationToken ct = default) /// <summary>
/// 连接指定 TCP 端点并读取一行文本,读取到结束符为止(返回内容不含结束符)。
/// </summary>
/// <param name="ipAddress">设备 IP 地址</param>
/// <param name="port">TCP 端口号</param>
/// <param name="receiveTimeout">接收超时时间(毫秒),超时未收到结束符抛出超时异常</param>
/// <param name="delimiter">文本行结束符,默认换行符 "\n"</param>
/// <param name="ct">异步取消令牌</param>
/// <returns>去除结束符并去除首尾空白后的文本行</returns>
public static async Task<string> ( string ipAddress, int port, int receiveTimeout, string delimiter = "\n",CancellationToken ct = default)
{ {
await _commLock.WaitAsync(ct); await _commLock.WaitAsync(ct);
try try
+60 -55
View File
@@ -10,6 +10,10 @@ using System.Text.Json;
using TSMaster; using TSMaster;
namespace TSMasterCAN namespace TSMasterCAN
{ {
/// <summary>
/// TSMaster CAN/CANFD 总线控制类,基于 TSMaster API 实现总线连接管理、
/// DBC 信号读写、报文发送(单次/循环)、日志记录等功能,暴露给命令树供测试步骤调用。
/// </summary>
[ACPCommand] [ACPCommand]
public class CAN public class CAN
{ {
@@ -70,9 +74,10 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 连接 /// 连接 TSMaster CAN 总线:建立总线连接并启动 RBS 仿真系统,
/// 成功后置位连接标志并触发 <see cref="ConnectEvent"/> 连接事件。
/// </summary> /// </summary>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败(可用 <see cref="GetErrorDescription"/> 查询错误说明)</returns>
public static int Connect() public static int Connect()
{ {
@@ -99,9 +104,9 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 断开 /// 断开 TSMaster CAN 总线连接,成功后清除连接标志并触发 <see cref="DisConnectEvent"/> 断开事件。
/// </summary> /// </summary>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败(可用 <see cref="GetErrorDescription"/> 查询错误说明)</returns>
public static int DisConnect() public static int DisConnect()
{ {
var re = TsMasterApi.tsapp_disconnect(); var re = TsMasterApi.tsapp_disconnect();
@@ -161,19 +166,19 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 开始记录日志 /// 开始记录总线日志,将总线上的报文数据记录到指定文件。
/// </summary> /// </summary>
/// <param name="filePath"></param> /// <param name="filePath">日志文件保存路径(自动转换为绝对路径)</param>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int StartLogging(string filePath) public static int StartLogging(string filePath)
{ {
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath)); return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
} }
/// <summary> /// <summary>
/// 结束记录日志 /// 结束总线日志记录。
/// </summary> /// </summary>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int StopLogging() public static int StopLogging()
{ {
return TsMasterApi.tsapp_stop_logging(); return TsMasterApi.tsapp_stop_logging();
@@ -191,12 +196,12 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 获取信号值 /// 获取 DBC 中定义信号的当前物理值(从总线接收报文中解析)。
/// </summary> /// </summary>
/// <param name="channel"></param> /// <param name="channel">通道号(0 起始)</param>
/// <param name="AMsgName"></param> /// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="ASgnName"></param> /// <param name="ASgnName">DBC 中的信号名称</param>
/// <returns></returns> /// <returns>信号物理值,获取失败时返回 <see cref="double.NaN"/></returns>
public static double GetSignalValue(byte channel, string AMsgName, string ASgnName) public static double GetSignalValue(byte channel, string AMsgName, string ASgnName)
{ {
double value = double.NaN; double value = double.NaN;
@@ -237,16 +242,16 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 设置信号值 /// 设置 DBC 中定义信号的值(按物理值编码进报文数据),可选择立即发送或周期发送。
/// </summary> /// </summary>
/// <param name="channel"></param> /// <param name="channel">通道号(0 起始)</param>
/// <param name="AMsgName"></param> /// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="ASgnName"></param> /// <param name="ASgnName">DBC 中的信号名称</param>
/// <param name="AValue"></param> /// <param name="AValue">要设置的信号物理值</param>
/// <param name="isSend"></param> /// <param name="isSend">是否发送:false 仅更新数据不发送,true 则发送</param>
/// <param name="sendPeriod"></param> /// <param name="sendPeriod">发送周期(毫秒):0 = 只发送一次;&gt;0 = 按该周期循环发送</param>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
/// <exception cref="Exception"></exception> /// <exception cref="Exception">信号编码写入报文失败时抛出</exception>
public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0) public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0)
{ {
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName); var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
@@ -271,12 +276,12 @@ namespace TSMasterCAN
/// <summary> /// <summary>
/// 设置信号值 /// 按报文名称发送 DBC 中定义的报文(使用该报文在 DBC 数据库中的当前数据)。
/// </summary> /// </summary>
/// <param name="channel"></param> /// <param name="channel">通道号</param>
/// <param name="AMsgName"></param> /// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="sendPeriod"></param> /// <param name="sendPeriod">发送周期(毫秒):0 = 只发送一次;&gt;0 = 按该周期循环发送</param>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0) public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0)
{ {
var find = DBCParse.MsgDatabase[(byte)channel].First(s => s.msg_name == AMsgName); var find = DBCParse.MsgDatabase[(byte)channel].First(s => s.msg_name == AMsgName);
@@ -302,11 +307,11 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 设置报文 /// 按帧 ID 更新 DBC 数据库中指定报文的数据区(仅更新内存中的数据,不发送)。
/// </summary> /// </summary>
/// <param name="channel">通道</param> /// <param name="channel">通道</param>
/// <param name="ID">DBC数据库ID</param> /// <param name="ID">报文帧 IDDBC 中定义的 FIdentifier</param>
/// <param name="bytes">报文数组</param> /// <param name="bytes">要写入的报文数据数组,超出报文数据长度的部分会被截断</param>
public static void SetMsg(APP_CHANNEL channel, int ID, byte[] bytes) public static void SetMsg(APP_CHANNEL channel, int ID, byte[] bytes)
{ {
var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.ACANFD.FIdentifier == ID); var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.ACANFD.FIdentifier == ID);
@@ -317,20 +322,20 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 发送自定义报文 /// 构造并发送自定义 CAN/CANFD 报文,可选择单次发送或周期发送,并可同步更新 DBC 数据库中同 ID 报文的数据。
/// </summary> /// </summary>
/// <param name="AIdxChn"></param> /// <param name="AIdxChn">通道号</param>
/// <param name="AID"></param> /// <param name="AID">报文帧 ID</param>
/// <param name="AIsTx"></param> /// <param name="AIsTx">是否为发送帧</param>
/// <param name="AIsExt"></param> /// <param name="AIsExt">是否为扩展帧(29 位 ID</param>
/// <param name="AIsRemote"></param> /// <param name="AIsRemote">是否为远程帧</param>
/// <param name="ADLC"></param> /// <param name="ADLC">数据长度代码(数据字节数,最大 64</param>
/// <param name="ADataArray"></param> /// <param name="ADataArray">报文数据数组,超过 64 字节的部分会被截断</param>
/// <param name="AIsFD"></param> /// <param name="AIsFD">是否为 CANFD 帧,默认 true</param>
/// <param name="AIsBRS"></param> /// <param name="AIsBRS">是否切换数据段波特率(仅 CANFD 有效),默认 false</param>
/// <param name="isUpdateDBCDatabase"></param> /// <param name="isUpdateDBCDatabase">是否同步更新 DBC 数据库中同帧 ID 报文的数据,默认 false</param>
/// <param name="sendPeriod"></param> /// <param name="sendPeriod">发送周期(毫秒):0 = 只发送一次;&gt;0 = 按该周期循环发送</param>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int SendMsg(APP_CHANNEL AIdxChn, int AID, bool AIsTx, bool AIsExt, public static int SendMsg(APP_CHANNEL AIdxChn, int AID, bool AIsTx, bool AIsExt,
bool AIsRemote, byte ADLC, byte[] ADataArray, bool AIsFD = true, bool AIsBRS = false, bool AIsRemote, byte ADLC, byte[] ADataArray, bool AIsFD = true, bool AIsBRS = false,
bool isUpdateDBCDatabase = false, float sendPeriod = 0) bool isUpdateDBCDatabase = false, float sendPeriod = 0)
@@ -375,12 +380,12 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 添加循环发送报文 /// 将 DBC 中定义的报文加入循环发送列表,按指定周期持续发送。
/// </summary> /// </summary>
/// <param name="channel"></param> /// <param name="channel">通道号(0 起始)</param>
/// <param name="AMsgName"></param> /// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="sendPeriod"></param> /// <param name="sendPeriod">发送周期(毫秒)</param>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod) public static int AddCyclicMsg(byte channel, string AMsgName, float sendPeriod)
{ {
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName); var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
@@ -389,19 +394,19 @@ namespace TSMasterCAN
} }
/// <summary> /// <summary>
/// 清除循环发送的报文 /// 清除所有循环发送的报文,停止全部周期发送任务。
/// </summary> /// </summary>
/// <returns></returns> /// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int DeleteCyclicMsgs() public static int DeleteCyclicMsgs()
{ {
return TsMasterApi.tsapp_delete_cyclic_msgs(); return TsMasterApi.tsapp_delete_cyclic_msgs();
} }
/// <summary> /// <summary>
/// 获取错误提示 /// 根据错误代码获取对应的错误文字描述,用于诊断 TSMaster API 返回码。
/// </summary> /// </summary>
/// <param name="errorCode">错误代码</param> /// <param name="errorCode">TSMaster API 返回的错误代码</param>
/// <returns></returns> /// <returns>错误描述文本,未知错误代码时返回提示信息</returns>
public static string GetErrorDescription(int errorCode) public static string GetErrorDescription(int errorCode)
{ {
IntPtr ADesc = IntPtr.Zero; IntPtr ADesc = IntPtr.Zero;
+1
View File
@@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<UseWPF>false</UseWPF> <UseWPF>false</UseWPF>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
+111 -15
View File
@@ -192,21 +192,26 @@ namespace TestingModule.ViewModels
var assembly = Assembly.LoadFrom(dllPath); var assembly = Assembly.LoadFrom(dllPath);
Assemblies.Add(assembly); Assemblies.Add(assembly);
// 加载对应的XML注释文件 (项目没有用到) //加载对应的XML注释文件
//string xmlPath = Path.ChangeExtension(dllPath, ".xml"); string xmlPath = Path.ChangeExtension(dllPath, ".xml");
//if (File.Exists(xmlPath)) if (File.Exists(xmlPath))
//{ {
// try try
// { {
// XmlDocument xmlDoc = new XmlDocument(); XmlDocument xmlDoc = new XmlDocument();
// xmlDoc.Load(xmlPath); xmlDoc.Load(xmlPath);
// _xmlDocumentCache[assembly.FullName!] = xmlDoc; XmlDocumentCache[assembly.FullName!] = xmlDoc;
// } LoggerHelper.Info($"[XML注释] 成功加载: {Path.GetFileName(xmlPath)} -> 程序集 [{assembly.FullName}]");
// catch (Exception xmlEx) }
// { catch (Exception xmlEx)
// LoggerHelper.WarnWithNotify($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}"); {
// } LoggerHelper.Warn($"加载XML注释失败: {Path.GetFileName(xmlPath)} - {xmlEx.Message}");
//} }
}
else
{
LoggerHelper.Warn($"[XML注释] XML文件不存在: {xmlPath}");
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -284,6 +289,7 @@ namespace TestingModule.ViewModels
{ {
Name = subProgram.Name, Name = subProgram.Name,
Tag = subProgram, Tag = subProgram,
Tooltip = subProgram.FilePath
}); });
} }
@@ -368,6 +374,7 @@ namespace TestingModule.ViewModels
{ {
Name = $"{method.Name}({paramText})", Name = $"{method.Name}({paramText})",
Tag = method, Tag = method,
Tooltip = GetMethodDocumentation(method),
}; };
typeNode.Children.Add(methodNode); typeNode.Children.Add(methodNode);
@@ -433,6 +440,95 @@ namespace TestingModule.ViewModels
} }
} }
} }
// 添加获取注释的方法
private string? GetMethodDocumentation(MethodInfo method)
{
if (method.DeclaringType == null) return null;
try
{
string assemblyName = method.DeclaringType.Assembly.FullName!;
if (!_xmlDocumentCache.TryGetValue(assemblyName, out XmlDocument? xmlDoc))
{
LoggerHelper.Warn($"[XML注释] 缓存未命中: 程序集 [{assemblyName}],缓存包含 { _xmlDocumentCache.Count} 个条目: [{string.Join(", ", _xmlDocumentCache.Keys)}]");
return null;
}
// 生成XML文档中的成员ID
string memberName = $"M:{method.DeclaringType.FullName}.{method.Name}";
var parameters = method.GetParameters();
if (parameters.Length > 0)
{
memberName += "(" + string.Join(",", parameters.Select(p => p.ParameterType.FullName)) + ")";
}
// 查找注释节点
XmlNode? memberNode = xmlDoc.SelectSingleNode($"//member[@name='{memberName}']");
if (memberNode == null)
{
LoggerHelper.Warn($"[XML注释] 节点未找到: {memberName}");
return null;
}
// 获取摘要(summary
var summaryNode = memberNode.SelectSingleNode("summary");
string documentation = "";
if (summaryNode != null)
{
documentation += CleanXmlContent(summaryNode.InnerXml);
}
// 获取参数注释(param
var paramNodes = memberNode.SelectNodes("param");
if (paramNodes != null && paramNodes.Count > 0)
{
documentation += "\n\n参数:";
foreach (XmlNode paramNode in paramNodes)
{
string? paramName = paramNode.Attributes?["name"]?.Value;
if (!string.IsNullOrEmpty(paramName))
{
documentation += $"\n • {paramName}: {CleanXmlContent(paramNode.InnerXml)}";
}
}
}
// 获取返回值注释(returns
var returnsNode = memberNode.SelectSingleNode("returns");
if (returnsNode != null)
{
documentation += $"\n\n返回值: {CleanXmlContent(returnsNode.InnerXml)}";
}
return string.IsNullOrWhiteSpace(documentation)
? null
: System.Net.WebUtility.HtmlDecode(documentation.Trim());
}
catch (Exception ex)
{
LoggerHelper.Warn($"获取注释失败: {method.Name} - {ex.Message}");
return null;
}
}
// 辅助方法:清理XML内容
private string CleanXmlContent(string xmlContent)
{
return xmlContent
.Replace("<see cref=\"", "")
.Replace("\"/>", "")
.Replace("<para>", "\n")
.Replace("</para>", "")
.Replace("<seealso", "")
.Replace("/>", "")
.Replace("<c>", "") // 处理代码标签
.Replace("</c>", "")
.Replace("<code>", "")
.Replace("</code>", "")
.Trim();
}
#endregion #endregion
#region #region
@@ -178,10 +178,15 @@ namespace TestingModule.ViewModels
private void SelectionChanged(object parameter) private void SelectionChanged(object parameter)
{ {
var selectedList = parameter as IList; if (parameter is IList list && list.Count > 0)
if (selectedList != null)
{ {
SelectedItems = selectedList.Cast<StepVM>().ToList(); SelectedItems = list.Cast<StepVM>().ToList();
}
else if (parameter is IEnumerable enumerable && parameter is not string)
{
var items = enumerable.Cast<StepVM>().ToList();
if (items.Count > 0)
SelectedItems = items;
} }
} }
@@ -200,13 +205,19 @@ namespace TestingModule.ViewModels
} }
private void CopyStep() private void CopyStep()
{ {
if (_globalInfo.IsAdmin && SelectedItems.Any()) if (!_globalInfo.IsAdmin) return;
// 优先用多选列表,回退到单选
var source = (SelectedItems != null && SelectedItems.Any())
? SelectedItems
: (SelectedStep != null ? new List<StepVM> { SelectedStep } : null);
if (source == null || !source.Any()) return;
tmpCopyList.Clear();
foreach (var item in source)
{ {
tmpCopyList.Clear(); tmpCopyList.Add(item);
foreach (var item in SelectedItems)
{
tmpCopyList.Add(item);
}
} }
} }
private void PasteStep() private void PasteStep()
@@ -243,31 +254,29 @@ namespace TestingModule.ViewModels
} }
private void DeleteStep() private void DeleteStep()
{ {
// 确保有选中的项 if (!_globalInfo.IsAdmin) return;
if (_globalInfo.IsAdmin && SelectedItems != null && SelectedItems.Any())
// 优先用多选列表,回退到单选
var source = (SelectedItems != null && SelectedItems.Any())
? SelectedItems.ToList()
: (SelectedStep != null ? new List<StepVM> { SelectedStep } : null);
if (source == null || !source.Any()) return;
foreach (var item in source)
{ {
// 创建一个副本进行循环,防止在 Remove 过程中集合变化导致的问题 _eventAggregator.GetEvent<DeletedStepEvent>().Publish(item.ID);
var toDelete = SelectedItems.ToList();
foreach (var item in toDelete) if (_ScopedContext.SelectedStepList == "主程序")
{ Program.StepCollection.Remove(item);
_eventAggregator.GetEvent<DeletedStepEvent>().Publish(item.ID); else if (_ScopedContext.SelectedStepList == "错误程序")
Program.ErrorStepCollection.Remove(item);
if (_ScopedContext.SelectedStepList == "主程序")
{
Program.StepCollection.Remove(item);
}
else if (_ScopedContext.SelectedStepList == "错误程序")
{
Program.ErrorStepCollection.Remove(item);
}
}
// 3. 清空 ViewModel 的选中状态,避免悬挂引用
SelectedStep = null;
SelectedItems.Clear();
_ScopedContext.SelectedStep = null;
} }
// 清空 ViewModel 的选中状态,避免悬挂引用
SelectedStep = null;
SelectedItems?.Clear();
_ScopedContext.SelectedStep = null;
} }
#endregion #endregion
@@ -313,6 +322,7 @@ namespace TestingModule.ViewModels
/// </summary> /// </summary>
private void OnSubProgramNavigate(SubProgramNavigatePayload payload) private void OnSubProgramNavigate(SubProgramNavigatePayload payload)
{ {
if (_ScopedContext == null || payload == null) return;
var nav = payload.IsErrorProgram ? _ScopedContext.ErrorNav : _ScopedContext.MainNav; var nav = payload.IsErrorProgram ? _ScopedContext.ErrorNav : _ScopedContext.MainNav;
if (payload.Action == NavigateAction.Enter && payload.SubProgram != null) if (payload.Action == NavigateAction.Enter && payload.SubProgram != null)
{ {
@@ -381,6 +391,12 @@ namespace TestingModule.ViewModels
UnsubscribeStepCollections(); UnsubscribeStepCollections();
SubscribeStepCollections(); SubscribeStepCollections();
// 通知 UI 刷新 DisplaySteps(导航状态虽然引用同一 Program,但集合实例已变)
RaisePropertyChanged(nameof(MainDisplaySteps));
RaisePropertyChanged(nameof(ErrorDisplaySteps));
RaisePropertyChanged(nameof(MainCurrentProgram));
RaisePropertyChanged(nameof(ErrorCurrentProgram));
// 集合被整体替换后,对新集合重新编号 // 集合被整体替换后,对新集合重新编号
Application.Current?.Dispatcher.BeginInvoke(new Action(() => Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
{ {
@@ -433,7 +449,7 @@ namespace TestingModule.ViewModels
Program.PropertyChanged -= Program_PropertyChanged; Program.PropertyChanged -= Program_PropertyChanged;
} }
// 3. 【核心修复】必须显式退订 Prism 全局事件 // 3. 显式退订全局 Prism 事件
_eventAggregator?.GetEvent<AlarmEvent>()?.Unsubscribe(null); _eventAggregator?.GetEvent<AlarmEvent>()?.Unsubscribe(null);
_eventAggregator?.GetEvent<SubProgramNavigateEvent>()?.Unsubscribe(null); _eventAggregator?.GetEvent<SubProgramNavigateEvent>()?.Unsubscribe(null);
+2 -1
View File
@@ -53,7 +53,8 @@
<TreeView.Resources> <TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:InstructionNodeVM}" <HierarchicalDataTemplate DataType="{x:Type model:InstructionNodeVM}"
ItemsSource="{Binding Children}"> ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" /> <TextBlock Text="{Binding Name}" ToolTip="{Binding Tooltip}"/>
</HierarchicalDataTemplate> </HierarchicalDataTemplate>
</TreeView.Resources> </TreeView.Resources>
<!-- 双击 --> <!-- 双击 -->
+8 -1
View File
@@ -12,7 +12,7 @@ namespace UIShare.UIViewModel
public class InstructionNodeVM : BindableBase public class InstructionNodeVM : BindableBase
{ {
private string _name = string.Empty; private string _name = string.Empty;
public string Name public string Name
{ {
get => _name; get => _name;
@@ -32,5 +32,12 @@ namespace UIShare.UIViewModel
get => _tag; get => _tag;
set => SetProperty(ref _tag, value); set => SetProperty(ref _tag, value);
} }
private string? _tooltip;
public string? Tooltip
{
get => _tooltip;
set => SetProperty(ref _tooltip, value);
}
} }
} }