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