Compare commits

...
2 Commits
Author SHA1 Message Date
“hsc” eabe73edd5 合并水冷机驱动冲突 2026-08-27 11:09:35 +08:00
“hsc” e144dc78a7 命令树优化,测试编辑区域优化 2026-08-27 10:50:10 +08:00
23 changed files with 1514 additions and 337 deletions
+1
View File
@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
+1
View File
@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
+1
View File
@@ -4,6 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
+12 -3
View File
@@ -11,20 +11,29 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
/// <summary>
/// 负载 一对三
/// ANEVH80 电子负载一对三控制),基于 TCP 通信,使用 SCPI 指令集。
/// 用于被测产品的放电/带载测试,可模拟真实负载工况。
/// </summary>
[ACPCommand]
public class ANEVH80 : Tcp
{
// Chroma SCPI 默认使用 \n 作为结束符
// SCPI 指令默认使用 \n 作为结束符
private const string ScpiDelimiter = "\n";
/// <summary>
/// 构造函数,初始化 TCP 连接配置。
/// </summary>
/// <param name="config">TCP 连接配置(IP、端口等)</param>
public ANEVH80(TcpConfig config) : base(config)
{
}
/// <summary>
/// 占位符,可能的操作先占位
/// 占位符方法,预留后续负载控制操作(如设置负载模式、设置电流等)。
/// 当前实现为查询频率测量值。
/// </summary>
/// <param name="ct">异步取消令牌</param>
/// <returns>查询到的频率值,解析失败返回 0.0</returns>
public virtual async Task<double> (CancellationToken ct = default)
{
string resp = await WriteReadAsync($"MEAS:FREQ?{ScpiDelimiter}", ScpiDelimiter, ct);
+52 -26
View File
@@ -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
{
}
/// <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.
/// <summary>
/// 清除错误队列 (*CLS)[cite: 1]
/// 清除错误队列 (*CLS)
/// </summary>
public virtual async Task Async(CancellationToken ct = default)
{
@@ -32,7 +50,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 重设仪器为初始状态 (*RST)[cite: 1]
/// 重设仪器为初始状态 (*RST)
/// </summary>
public virtual async Task Async(CancellationToken ct = default)
{
@@ -40,7 +58,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 查询仪器识别码 (*IDN?)[cite: 1]
/// 查询仪器识别码 (*IDN?)
/// </summary>
public virtual async Task<string> Async(CancellationToken ct = default)
{
@@ -49,10 +67,10 @@ namespace DeviceCommand.Devices
#endregion
#region 2. (OUTPut )[cite: 1]
#region 2. (OUTPut )
/// <summary>
/// 设置输出开关状态 (OUTP ON / OFF)[cite: 1]
/// 设置输出开关状态 (OUTP ON / OFF)
/// </summary>
/// <param name="isOn">true: 开启输出, false: 关闭输出</param>
public virtual async Task Async(bool isOn, CancellationToken ct = default)
@@ -62,7 +80,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置输出耦合模式 (OUTP:COUP AC | DC | ACDC)[cite: 1]
/// 设置输出耦合模式 (OUTP:COUP AC | DC | ACDC)
/// </summary>
public virtual async Task Async(string couplingMode, CancellationToken ct = default)
{
@@ -71,7 +89,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置输出继电器状态 (OUTP:REL ON | OFF)[cite: 1]
/// 设置输出继电器状态 (OUTP:REL ON | OFF)
/// </summary>
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 )
/// <summary>
/// 设置交流电压 (VOLT:AC)[cite: 1]
/// 设置交流电压 (VOLT:AC)
/// </summary>
/// <param name="voltage">电压值 (0 ~ 300V 或高压选配范围)</param>
public virtual async Task Async(double voltage, CancellationToken ct = default)
@@ -94,7 +112,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置直流电压 (VOLT:DC)[cite: 1]
/// 设置直流电压 (VOLT:DC)
/// </summary>
public virtual async Task Async(double voltage, CancellationToken ct = default)
{
@@ -103,7 +121,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置输出频率 (FREQ)[cite: 1]
/// 设置输出频率 (FREQ)
/// </summary>
/// <param name="frequency">频率值 (Hz)</param>
public virtual async Task Async(double frequency, CancellationToken ct = default)
@@ -113,7 +131,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置波形选择 (FUNC:SHAP:A SINE / SQUA / CSIN 等)[cite: 1]
/// 设置波形选择 (FUNC:SHAP:A SINE / SQUA / CSIN 等)
/// </summary>
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 )
/// <summary>
/// 设置单/三相工作模式 (INST:PHAS THREE / SINGLE)[cite: 1]
/// 设置单/三相工作模式 (INST:PHAS THREE / SINGLE)
/// </summary>
/// <param name="isThreePhase">true: 三相模式, false: 单相模式</param>
public virtual async Task Async(bool isThreePhase, CancellationToken ct = default)
@@ -135,7 +153,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置三相模式下的相互关系 (PHASE:THREE INDEPEND / SAMEFREQ / BALANCE)[cite: 1]
/// 设置三相模式下的相互关系 (PHASE:THREE INDEPEND / SAMEFREQ / BALANCE)
/// </summary>
public virtual async Task Async(string relation, CancellationToken ct = default)
{
@@ -144,7 +162,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置输出开启相位角 (PHASE:ON)[cite: 1]
/// 设置输出开启相位角 (PHASE:ON)
/// </summary>
public virtual async Task Async(double degree, CancellationToken ct = default)
{
@@ -153,7 +171,7 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置输出关闭相位角 (PHASE:OFF)[cite: 1]
/// 设置输出关闭相位角 (PHASE:OFF)
/// </summary>
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 )
/// <summary>
/// 查询交流电压均方根值 (MEAS:VOLT:AC?)[cite: 1]
/// 查询交流电压均方根值 (MEAS:VOLT:AC?)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>交流电压均方根值 (单位: V,保留两位小数)</returns>
public virtual async Task<double> 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);
}
/// <summary>
/// 查询交流电流均方根值 (MEAS:CURR:AC?)[cite: 1]
/// 查询交流电流均方根值 (MEAS:CURR:AC?)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>交流电流均方根值 (单位: A,保留两位小数)</returns>
public virtual async Task<double> 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);
}
/// <summary>
/// 查询输出频率 (MEAS:FREQ?)[cite: 1]
/// 查询输出频率 (MEAS:FREQ?)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>输出频率值 (单位: Hz,保留两位小数)</returns>
public virtual async Task<double> 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);
}
/// <summary>
/// 查询真实输出功率 (MEAS:POW:AC?)[cite: 1]
/// 查询真实输出功率 (MEAS:POW:AC?)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>真实输出功率值 (单位: W,保留两位小数)</returns>
public virtual async Task<double> 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
+136 -18
View File
@@ -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
/// <summary>
/// 信号发生器 (DG1000Z 系列) 一拖三,支持波形输出与频率计测量
/// </summary>
[ACPCommand]
public class DG1000Z : Tcp
{
// 使用换行符 \n (ASCII 0x0A) 作为 SCPI 结束符
private const string ScpiDelimiter = "\n";
/// <summary>
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化信号发生器通信参数
/// </summary>
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
/// <summary>
/// 清除标准事件状态寄存器和错误队列 (*CLS)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default)
{
await SendAsync($"*CLS{ScpiDelimiter}", ct);
}
/// <summary>
/// 查询设备识别信息 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
}
/// <summary>
/// 重设仪器为初始状态 (*RST)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default)
{
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)
{
return await WriteReadAsync($":SYSTem:VERSion?{ScpiDelimiter}", ScpiDelimiter, ct);
@@ -82,16 +124,23 @@ namespace DeviceCommand.Devices
#region 2. (CH1 / CH2)
/// <summary>
/// 打开或关闭指定通道的输出
/// 打开或关闭指定通道的输出 (OUTPut 指令)
/// </summary>
/// <param name="通道号">通道 1 或 2,默认 1</param>
/// <param name="开启">true为开启,false为关闭</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int = 1, bool = true, CancellationToken ct = default)
{
string = ? "ON" : "OFF";
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)
{
return await WriteReadAsync($":OUTPut{通道号}:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
@@ -104,51 +153,84 @@ namespace DeviceCommand.Devices
/// <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)
{
await SendAsync($":SOURce{通道号}:FUNCtion:SHAPe {波形}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设置指定通道的输出频率 (单位: Hz)
/// 设置指定通道的输出频率 (SOURce:FREQuency 指令)
/// </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)
{
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FREQuency {1:F2}{2}", , Hz, ScpiDelimiter);
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>
/// 设置指定通道的输出幅度 (单位: Vpp,峰峰值)
/// 设置指定通道的输出幅度 (SOURce:VOLTage:AMPLitude 指令)
/// </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)
{
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:VOLTage:AMPLitude {1:F2}{2}", , Vpp, ScpiDelimiter);
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>
/// 设置指定通道的直流偏移电压 (单位: Vdc)
/// 设置指定通道的直流偏移电压 (SOURce:VOLTage:OFFSet 指令)
/// </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)
{
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:VOLTage:OFFSet {1:F2}{2}", , Vdc, ScpiDelimiter);
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
@@ -156,8 +238,11 @@ namespace DeviceCommand.Devices
#region 4. / (CP信号模拟)
/// <summary>
/// 设置方波信号的占空比 (0.01% ~ 99.99%)[cite: 1]
/// 设置方波信号的占空比 (0.01% ~ 99.99%)
/// </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)
{
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FUNCtion:SQUare:DCYCle {1:F2}{2}", , , ScpiDelimiter);
@@ -165,9 +250,12 @@ namespace DeviceCommand.Devices
}
/// <summary>
/// 设置脉冲波信号的占空比 (0.01% ~ 99.99%)[cite: 1]
/// 设置脉冲波信号的占空比 (0.01% ~ 99.99%)
/// 注意:使用此命令前必须先设置为 PULSe 波形类型
/// </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)
{
string cmd = string.Format(CultureInfo.InvariantCulture, ":SOURce{0}:FUNCtion:PULSe:DCYCle {1:F2}{2}", , , ScpiDelimiter);
@@ -179,47 +267,68 @@ namespace DeviceCommand.Devices
#region 5. ()
/// <summary>
/// 打开或关闭内置频率计功能
/// 打开或关闭内置频率计功能 (COUNter:STATe 指令)
/// </summary>
/// <param name="开启">true: 开启频率计, false: 关闭频率计</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (bool = true, CancellationToken ct = default)
{
string = ? "ON" : "OFF";
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)
{
return await WriteReadAsync($":COUNter:STATe?{ScpiDelimiter}", ScpiDelimiter, ct);
}
/// <summary>
/// 设置频率计输入信号的耦合方式
/// 设置频率计输入信号的耦合方式 (COUNter:COUPling 指令)
/// </summary>
/// <param name="耦合">耦合方式枚举 (AC 交流 / DC 直流),默认 AC</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (CounterCoupling = CounterCoupling.AC, CancellationToken ct = default)
{
await SendAsync($":COUNter:COUPling {耦合}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设置频率计的触发电平 (单位: V)
/// 设置频率计的触发电平 (COUNter:LEVel 指令)
/// </summary>
/// <param name="电平V">触发电平值 (单位: V),默认 0.1</param>
/// <param name="ct">取消令牌</param>
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);
}
/// <summary>
/// 查询频率计测量值 (COUNter:MEASure? 指令)。
/// 响应格式为 "频率, 周期, 占空比, 正脉宽, 负脉宽",此方法提取第一项频率值
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>频率计测得的频率值 (单位: Hz,保留两位小数)</returns>
[Monitorable("信号发生器频率计测量值")]
public virtual async Task<string> (CancellationToken ct = default)
public virtual async Task<double> (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.
/// <summary>
/// 切换远程控制模式。信号发生器通过 LAN/USB 通信即自动进入远程模式
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default)
{
// 信号发生器通常通过 LAN/USB 和 VISA 通信即自动进入远程控制,
@@ -227,12 +336,21 @@ namespace DeviceCommand.Devices
await Task.CompletedTask;
}
/// <summary>
/// 切换本地控制模式。通常通过前面板按键退出远程
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default)
{
// 通常通过前面板按键退出远程。不过可提供 SCPI 关闭屏幕保护等。
await Task.CompletedTask;
}
/// <summary>
/// 查询系统错误信息 (SYSTem:ERRor? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>错误代码和描述信息</returns>
public virtual async Task<string> (CancellationToken ct = default)
{
return await WriteReadAsync($":SYSTem:ERRor?{ScpiDelimiter}", ScpiDelimiter, ct);
+34 -3
View File
@@ -12,22 +12,39 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
/// <summary>
/// IO板卡 一对一由于板卡映射不完全一样采用IOGroup
/// IO 继电器板卡一对一控制),基于 Modbus TCP 协议通过线圈(Coil)读写控制继电器输出。
/// 由于多块板卡的点位映射不完全一致,实际业务中使用 <see cref="IOBoardGroup"/> 统一调度三块板卡。
/// </summary>
//[ACPCommand]
public class IOBoard : ModbusTcp
{
/// <summary>
/// 构造函数,初始化 Modbus TCP 连接配置。
/// </summary>
/// <param name="config">TCP 连接配置(IP、端口等)</param>
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)
{
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)
{
var = await Modbus.ReadCoilsAsync(1, 0, 16);
@@ -40,10 +57,24 @@ namespace DeviceCommand.Devices
}
await Modbus.WriteMultipleCoilsAsync(, 0, );
}
/// <summary>
/// 读取单个输出开关(线圈)状态。
/// </summary>
/// <param name="站号">Modbus 从站站号</param>
/// <param name="开始地址">线圈地址(0 起始)</param>
public async Task (byte , ushort )
{
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 )
{
return await Modbus.ReadCoilsAsync(, , );
+25 -37
View File
@@ -48,6 +48,11 @@ namespace DeviceCommand.Devices
}
}
/// <summary>
/// IO 板卡组(三工位统一调度),封装 3 块 IO 继电器板卡的访问。
/// 根据 ACP 硬件接线图的点位映射字典,按台架序号路由到对应板卡的线圈地址,
/// 并内置充电/放电互锁保护,防止同时吸合造成短路。
/// </summary>
[ACPCommand]
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 个板块
public IOBoardGroup(IOBoard board1, IOBoard board2, IOBoard board3)
{
@@ -168,49 +180,25 @@ namespace DeviceCommand.Devices
await .(_slaveAddress, io信息.Address, );
}
// =================================================================================
// 以下为新增的原生命令透传接口,使用台架序号自动路由至对应 IOBoard
// =================================================================================
/// <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 targetBoard.(, , data);
await GetBoardInstance().(, , );
}
/// <summary>
/// 针对指定台架直接批量写入输出开关
/// </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>
/// 内部路由辅助方法:根据板卡/台架索引安全获取当前连接的实例对象
/// 内部路由辅助方法:根据板卡序号安全获取对应的 IO 板卡实例。
/// </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)
{
IOBoard board = moduleIndex switch
+23 -4
View File
@@ -133,6 +133,10 @@ namespace DeviceCommand.Devices
throw new Exception("IT6720 切换控制模式时通讯校验失败。");
}
/// <summary>
/// 切换为本地(面板)控制模式,与远程控制互斥
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default)
{
await (false, ct);
@@ -150,6 +154,11 @@ namespace DeviceCommand.Devices
throw new Exception("IT6720 设置输出时通讯校验失败。");
}
/// <summary>
/// 查询电源输出开关状态(由实时测量值的状态字节判断)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>输出开关状态字符串 (ON / OFF)</returns>
public virtual async Task<string> (CancellationToken ct = default)
{
var (_, _, status) = await (ct);
@@ -230,18 +239,28 @@ namespace DeviceCommand.Devices
return (actualCurrent, actualVoltage, statusByte);
}
/// <summary>
/// 查询电源的实时实际输出电压 (MEASure 相关寄存器)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电压值 (单位: V,保留两位小数)</returns>
[Monitorable("IT6720 实际电压")]
public virtual async Task<string> (CancellationToken ct = default)
public virtual async Task<double> (CancellationToken ct = default)
{
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 实际电流")]
public virtual async Task<string> (CancellationToken ct = default)
public virtual async Task<double> (CancellationToken ct = default)
{
var (current, _, _) = await (ct);
return current.ToString("F3", CultureInfo.InvariantCulture);
return Math.Round(current, 2);
}
/// <summary>
+260 -32
View File
@@ -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
{
/// <summary>
/// 功率分析仪(型号:HIOKI PW8001) 一拖三
/// 功率分析仪(型号:HIOKI PW8001) 一拖三,支持多通道电压/电流/功率及谐波测量
/// </summary>
[ACPCommand]
public class PW8001 : Tcp
@@ -25,23 +28,58 @@ namespace DeviceCommand.Device
/// </summary>
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
/// <summary>
/// 查询仪器识别信息 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
public virtual async Task<string> (CancellationToken ct = default)
{
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)
{
return await WriteReadAsync("*OPT?\r\n", "\n", ct);
}
/// <summary>
/// 复位仪器到默认状态 (*RST 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default)
{
await SendAsync("*RST\r\n", ct);
}
/// <summary>
/// 清除状态寄存器和错误队列 (*CLS 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task (CancellationToken ct = default)
{
await SendAsync("*CLS\r\n", ct);
@@ -51,66 +89,129 @@ namespace DeviceCommand.Device
#region 2. (System & Mode)
/// <summary>
/// 设置测试模式为 WIDE 宽带测量模式 (:MODE WIDE 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task _WIDE(CancellationToken ct = default)
{
await SendAsync(":MODE WIDE\r\n", ct);
}
/// <summary>
/// 设置测试模式为 IEC 标准测量模式 (:MODE IEC 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task _IEC(CancellationToken ct = default)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
return await WriteReadAsync(":KLOCk?\r\n", "\n", ct);
@@ -120,21 +221,43 @@ namespace DeviceCommand.Device
#region 3. (Integration)
/// <summary>
/// 设置积分控制模式 (:INTEG:CONTROL 指令)
/// </summary>
/// <param name="模式">积分控制模式</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default)
{
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)
{
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)
{
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)
{
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 ----------------
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
@@ -232,8 +458,10 @@ namespace DeviceCommand.Device
#region 5.
/// <summary>
/// 发送自定义命令
/// 发送自定义命令(透传任意 SCPI 指令)
/// </summary>
/// <param name="命令">自定义命令字符串</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (string , CancellationToken ct = default)
{
await SendAsync($"{命令}\r\n", ct);
+141 -13
View File
@@ -8,30 +8,45 @@ using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
/// <summary>
/// 环境箱(型号:RLT1000) 一拖三
/// 环境箱(型号:RLT1000) 一拖三,支持温度/湿度/水温/水流量的监测与定值控制 (Modbus TCP 协议)
/// </summary>
[ACPCommand]
public class RLT1000 : ModbusTcp
{
/// <summary>
/// 构造函数:传入 <see cref="TcpConfig"/> 初始化环境箱 Modbus TCP 通信参数
/// </summary>
public RLT1000(TcpConfig config) : base(config)
{
}
// Modbus 从站号
private const byte SlaveId = 1;
#region
/// <summary>
/// 读取环境箱运行状态监控字 (保持寄存器 5)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>状态监控字 (ushort 原始值)</returns>
public async Task<ushort> (CancellationToken ct = default)
{
ushort[] re = await ReadHoldingRegistersAsync(SlaveId, 5, 1, ct);
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
{
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<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);
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
{
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<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);
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);
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);
return ConvertToFloat(re);
return Math.Round(ConvertToFloat(re), 2);
}
#endregion
#region
/// <summary>
/// 切换环境箱为本地控制模式 (寄存器 147 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 147, 0, ct);
}
/// <summary>
/// 切换环境箱为远程控制模式 (寄存器 147 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 147, 1, ct);
}
/// <summary>
/// 远程模式下关闭环境箱 (寄存器 148 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 148, 0, ct);
}
/// <summary>
/// 远程模式下开启环境箱 (寄存器 148 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 148, 1, ct);
}
/// <summary>
/// 远程模式复位结束 (寄存器 149 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 149, 0, ct);
}
/// <summary>
/// 远程模式复位 (寄存器 149 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 149, 1, ct);
}
/// <summary>
/// 切换为程序运行模式 (寄存器 153 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 153, 0, ct);
}
/// <summary>
/// 切换为定值运行模式 (寄存器 153 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task (CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 153, 1, ct);
}
/// <summary>
/// 定值模式下设置目标温度 (寄存器 154-155)
/// </summary>
/// <param name="温度">目标温度值 (单位: ℃)</param>
/// <param name="ct">取消令牌</param>
public async Task (float , CancellationToken ct = default)
{
var tmp = ConvertFromFloat();
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)
{
var tmp = ConvertFromFloat(湿);
await WriteMultipleRegistersAsync(SlaveId, 158, tmp, ct);
}
/// <summary>
/// 定值模式下设置目标水温 (寄存器 162-163)
/// </summary>
/// <param name="水温">目标水温值 (单位: ℃)</param>
/// <param name="ct">取消令牌</param>
public async Task (float , CancellationToken ct = default)
{
var tmp = ConvertFromFloat();
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)
{
var tmp = ConvertFromFloat();
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)
{
var tmp = ConvertFromFloat();
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)
{
var tmp = ConvertFromFloat();
await WriteMultipleRegistersAsync(SlaveId, 168, tmp, ct);
}
/// <summary>
/// 设置水温模式不使用 (寄存器 1181 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 1181, 0, ct);
}
/// <summary>
/// 设置水温模式使用 (寄存器 1181 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 1181, 1, ct);
}
/// <summary>
/// 设置环境箱温度模式不使用 (寄存器 1182 写 0)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 1182, 0, ct);
}
/// <summary>
/// 设置环境箱温度模式使用 (寄存器 1182 写 1)
/// </summary>
/// <param name="ct">取消令牌</param>
public async Task 使(CancellationToken ct = default)
{
await WriteSingleRegisterAsync(SlaveId, 1182, 1, ct);
@@ -180,6 +298,11 @@ namespace DeviceCommand.Devices
#region
/// <summary>
/// 将两个 16 位寄存器(大端序)转换为 IEEE754 单精度浮点数
/// </summary>
/// <param name="values">两个 ushort 寄存器值</param>
/// <returns>转换后的浮点数值</returns>
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);
}
/// <summary>
/// 将单精度浮点数转换为两个 16 位寄存器(大端序)
/// </summary>
/// <param name="value">待转换的浮点数值</param>
/// <returns>两个 ushort 寄存器值</returns>
private ushort[] ConvertFromFloat(float value)
{
byte[] bytes = BitConverter.GetBytes(value);
+234 -36
View File
@@ -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
{
/// <summary>
/// S7200 负载工作模式枚举 (恒流/恒压/恒功率/恒阻等)
/// </summary>
public enum S7200负载模式_枚举
{
/// <summary>恒流模式</summary>
CC,
/// <summary>恒压模式</summary>
CV,
/// <summary>恒功率模式</summary>
CP,
/// <summary>恒阻模式</summary>
CR,
/// <summary>恒压+恒流组合模式</summary>
CVCC,
/// <summary>恒阻+恒流组合模式</summary>
CRCC,
/// <summary>恒压+恒阻组合模式</summary>
CVCR,
/// <summary>自动模式</summary>
AUTO
}
/// <summary>
/// S7200 直流源工作模式枚举 (电压源/电流源/电池模拟/光伏模拟等)
/// </summary>
public enum S7200直流源模式_枚举
{
/// <summary>电压源模式</summary>
VOLTage,
/// <summary>电流源模式</summary>
CURRent,
/// <summary>电池测试模式</summary>
BATTest,
/// <summary>电池模拟模式</summary>
BATSim,
/// <summary>光伏模拟模式</summary>
SOLar,
/// <summary>EN50530 标准光伏曲线模式</summary>
EN50530,
/// <summary>列表输出模式</summary>
LIST,
/// <summary>用户自定义波形模式</summary>
UDW,
/// <summary>方波输出模式</summary>
SQUare,
/// <summary>三角波输出模式</summary>
TRIangle,
/// <summary>正弦波输出模式</summary>
SINusoid,
/// <summary>DIN40839 12V 启动曲线</summary>
DIN40839_12V,
/// <summary>DIN40839 24V 启动曲线</summary>
DIN40839_24V,
/// <summary>ISO16750 短时跌落 12V</summary>
IS016750_SHORTDROP_12V,
/// <summary>ISO16750 短时跌落 24V</summary>
IS016750_SHORTDROP_24V,
/// <summary>ISO16750 复位测试</summary>
IS016750_RESETTEST,
/// <summary>ISO16750 启动曲线 12V 第 1 组</summary>
IS016750_STARTINGPROFILE_12V_1,
/// <summary>ISO16750 启动曲线 12V 第 2 组</summary>
IS016750_STARTINGPROFILE_12V_2,
/// <summary>ISO16750 启动曲线 12V 第 3 组</summary>
IS016750_STARTINGPROFILE_12V_3,
/// <summary>ISO16750 启动曲线 12V 第 4 组</summary>
IS016750_STARTINGPROFILE_12V_4,
/// <summary>ISO16750 启动曲线 24V 第 1 组</summary>
IS016750_STARTINGPROFILE_24V_1,
/// <summary>ISO16750 启动曲线 24V 第 2 组</summary>
IS016750_STARTINGPROFILE_24V_2,
/// <summary>ISO16750 启动曲线 24V 第 3 组</summary>
IS016750_STARTINGPROFILE_24V_3,
/// <summary>ISO16750 抛负载测试 A 12V</summary>
IS016750_LOADDUMP_TESTA_12V,
/// <summary>ISO16750 抛负载测试 A 24V</summary>
IS016750_LOADDUMP_TESTA_24V,
/// <summary>ISO16750 抛负载测试 B 12V</summary>
IS016750_LOADDUMP_TESTB_12V,
/// <summary>ISO16750 抛负载测试 B 24V</summary>
IS016750_LOADDUMP_TESTB_24V,
/// <summary>LV123 高压曲线 1</summary>
LV123_HV_1,
/// <summary>LV123 高压曲线 2A</summary>
LV123_HV_2A,
/// <summary>LV123 高压曲线 2B</summary>
LV123_HV_2B,
/// <summary>LV123 高压曲线 3</summary>
LV123_HV_3
}
/// <summary>
/// S7200 工作模式枚举 (电源模式 / 负载模式)
/// </summary>
public enum S7200工作模式_枚举
{
/// <summary>作为电源输出</summary>
,
/// <summary>作为负载吸收</summary>
,
}
/// <summary>
/// 高压直流源载一体机 (适配 IT6000C SCPI指令集)
/// 高压直流源载一体机 (适配 IT6000C SCPI 指令集),支持电源输出与负载吸收双向工作
/// </summary>
[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)
/// <summary>
/// 构造函数:传入 <see cref="TcpConfig"/> 一次性初始化一体机通信参数
/// </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)
{
// IT6000C 为双向无缝切换,底层硬件无需显式发送源载切换指令
// 保留此方法仅为兼容原有上位机的工步调用流程,直接返回即可
// 保留此方法仅为兼容原有上位机的工步调用流程
await Task.CompletedTask;
}
/// <summary>
/// 设置直流源工作模式 (FUNCtion 指令,如 VOLTage / CURRent / SOLar 等)
/// </summary>
/// <param name="模式">直流源工作模式枚举</param>
/// <param name="ct">取消令牌</param>
public async Task (S7200直流源模式_枚举 , CancellationToken ct = default)
{
// 原指令: MODE {模式} -> 现 IT6000C 指令: FUNCtion {模式} (如 VOLTage / CURRent)[cite: 1]
// 若枚举 ToString() 后不匹配,建议在此处加 Switch 转换
await SendAsync($"FUNCtion {模式}{ScpiDelimiter}", ct);
}
/// <summary>
/// 设置负载工作模式。IT6000C 负载控制通过负向边界实现
/// </summary>
/// <param name="模式">负载工作模式枚举</param>
/// <param name="ct">取消令牌</param>
public async Task (S7200负载模式_枚举 , CancellationToken ct = default)
{
// IT6000C 负载控制通过负向边界实现。若是想开启纯电阻模式,可调用 SINK:RESistance:STATe ON[cite: 1]
// 想开启纯电阻模式,可调用 SINK:RESistance:STATe ON
await Task.CompletedTask;
}
/// <summary>
/// 开启或关闭通道输出 (OUTPut 指令)
/// </summary>
/// <param name="开关">true: 开启输出, false: 关闭输出</param>
/// <param name="ct">取消令牌</param>
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)
{
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)
{
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)
{
// 提醒: 此处传入的参数如果是正数建议在外部或此处转换为负值,因为载模式限制需要负数[cite: 1]
// 载模式限制需要负数,若传入正数建议在外部转换为负值
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)
{
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)
{
// 提醒: 此处传入的参数必须为负数[cite: 1]
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
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)
{
await SendAsync($"{指令}{ScpiDelimiter}", ct);
@@ -185,27 +363,47 @@ namespace DeviceCommand.Devices
#region
/// <summary>
/// 查询设备识别信息 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
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)
{
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);
}
/// <summary>
/// 查询实时输出电流 (MEASure:CURRent? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电流值 (单位: A,保留两位小数)</returns>
public async Task<double> (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);
}
/// <summary>
/// 查询实时功率 (MEASure:POWer? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时功率值 (单位: W,保留两位小数)</returns>
public async Task<double> (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
+142 -41
View File
@@ -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
/// </summary>
public enum TekMeasurementType
{
/// <summary>幅度</summary>
AMPLitude,
/// <summary>频率</summary>
FREQuency,
/// <summary>平均值</summary>
MEAN,
/// <summary>峰峰值</summary>
PK2PK,
/// <summary>最大值</summary>
MAXimum,
/// <summary>最小值</summary>
MINimum
}
#endregion
/// <summary>
/// Tektronix 示波器 (MSO 4/5/6 系列),支持采集控制、通道设置、自动测量与截图导出
/// </summary>
[ACPCommand]
public class TektronixMSO : Tcp
{
// SCPI 指令结束符 (消息终止符需使用 LF)
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
public enum AcquireMode { Sample = 0, PeakDetect = 1, HiRes = 2, Average = 3, Envelope = 4 }
@@ -81,21 +108,32 @@ namespace DeviceCommand.Devices
#region 1.
/// <summary>
/// 查询仪器识别码 (*IDN? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串 (制造商, 型号, 序列号, 固件版本)</returns>
public virtual async Task<string> (CancellationToken ct = default)
{
// 返回仪器的标识代码[cite: 2]
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)
{
// 查询仪器是否处于忙碌状态,常用于同步操作[cite: 2]
return await WriteReadAsync($"BUSY?{ScpiDelimiter}", ScpiDelimiter, ct);
}
/// <summary>
/// 指示仪器执行信号路径校准 (SPC) (*CAL? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
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)
/// <summary>
/// 启动或停止采集
/// 启动或停止采集 (ACQuire:STATE 指令)
/// </summary>
/// <param name="运行">true: 启动采集 (RUN), false: 停止采集 (STOP)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (bool , CancellationToken ct = default)
{
// 启动、停止或返回采集状态[cite: 2]
string = ? "RUN" : "STOP";
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)
{
// 启动、停止或返回采集状态[cite: 2]
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)
{
// 设置或查询采集模式[cite: 2]
await SendAsync($"ACQuire:MODe {模式}{ScpiDelimiter}", ct);
}
@@ -129,23 +176,36 @@ namespace DeviceCommand.Devices
#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)
{
// 设置或查询水平刻度 (Scale)[cite: 2]
string cmd = string.Format(CultureInfo.InvariantCulture, "HORizontal:SCAle {0:E}{1}", , ScpiDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 设置水平记录长度 (HORizontal:RECOrdlength 指令)
/// </summary>
/// <param name="长度">记录长度(采样点数)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (long , CancellationToken ct = default)
{
// 设置或查询水平记录长度[cite: 2]
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]
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)
/// <summary>
/// 全局启用或关闭某通道的显示
/// 全局启用或关闭某通道的显示 (DISplay:GLObal:CH:STATE 指令)
/// </summary>
/// <param name="通道号">通道编号 (1 - 4)</param>
/// <param name="开启">true: 开启通道显示, false: 关闭通道显示</param>
/// <param name="ct">取消令牌</param>
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);
}
/// <summary>
/// 设置指定通道的垂直缩放比例 (Volts/Div)
/// 设置指定通道的垂直缩放比例 (CH:SCAle 指令)
/// </summary>
/// <param name="通道号">通道编号 (1 - 4)</param>
/// <param name="伏特每格">垂直刻度值 (单位: V/Div)</param>
/// <param name="ct">取消令牌</param>
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
}
/// <summary>
/// 设置指定通道的垂直位置 (Divisions)
/// 设置指定通道的垂直位置 (CH:POSition 指令)
/// </summary>
/// <param name="通道号">通道编号 (1 - 4)</param>
/// <param name="格数">垂直位置偏移 (单位: Div)</param>
/// <param name="ct">取消令牌</param>
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)
/// <summary>
/// 动态添加并开启一个测量项
/// 动态添加并开启一个测量项 (MEASUrement:ADDNew 指令)。
/// 4/5/6 Series MSO 必须先动态创建测量项
/// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="ct">取消令牌</param>
public virtual async Task (int , CancellationToken ct = default)
{
// 4/5/6 Series MSO 必须先动态创建测量项[cite: 2]
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)
{
// 设置或查询测量类型[cite: 2]
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)
{
// 4/5/6 Series 中设置测量源[cite: 2]
await SendAsync($"MEASUrement:MEAS{测量项编号}:SOUrce1 {源名称}{ScpiDelimiter}", ct);
}
/// <summary>
/// 查询测量项的当前采集平均值 (MEASUrement:RESUlts:CURRentacq:MEAN? 指令)
/// </summary>
/// <param name="测量项编号">测量项编号</param>
/// <param name="ct">取消令牌</param>
/// <returns>测量项当前采集的平均值 (保留两位小数)</returns>
[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]
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<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]
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<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]
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,38 +429,41 @@ namespace DeviceCommand.Devices
#region 7. (Status and Error)
/// <summary>
/// 清除所有的状态寄存器和错误/事件队列
/// 清除所有的状态寄存器和错误/事件队列 (*CLS 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task Async(CancellationToken ct = default)
{
// 发送 *CLS 指令清空状态[cite: 2]
await SendAsync($"*CLS{ScpiDelimiter}", ct);
}
/// <summary>
/// 查询错误队列中当前的事件/错误数量
/// 查询错误队列中当前的事件/错误数量 (EVQty? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>事件队列中的事件数量字符串</returns>
public virtual async Task<string> Async(CancellationToken ct = default)
{
// 返回事件队列中的事件数量
return await WriteReadAsync($"EVQty?{ScpiDelimiter}", ScpiDelimiter, ct);
}
/// <summary>
/// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出
/// 从错误队列中读取最早的一个事件/错误(包含代码和详细消息),并将其从队列中弹出 (EVMsg? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>事件代码和消息字符串</returns>
public virtual async Task<string> Async(CancellationToken ct = default)
{
// 返回事件队列中的事件代码和消息
return await WriteReadAsync($"EVMsg?{ScpiDelimiter}", ScpiDelimiter, ct);
}
/// <summary>
/// 读取并清空队列中的所有错误和事件消息
/// 读取并清空队列中的所有错误和事件消息 (ALLEv? 指令)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>所有事件及其消息字符串</returns>
public virtual async Task<string> Async(CancellationToken ct = default)
{
// 返回所有事件及其消息
return await WriteReadAsync($"ALLEv?{ScpiDelimiter}", ScpiDelimiter, ct);
}
+74 -4
View File
@@ -8,11 +8,20 @@ using System.Threading.Tasks;
namespace DeviceCommand.Flexible
{
/// <summary>
/// 灵活型 Modbus RTU 串口通信类(免实例化,每次调用临时创建串口连接)。
/// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行,
/// 适用于偶发性、无需保持长连接的 Modbus RTU 设备读写场景。
/// </summary>
[ACPCommand]
public static class FModbusRTU
{
// 通信锁:保证同一时刻只有一个 Modbus 事务在执行
private static readonly SemaphoreSlim _commLock = new(1, 1);
/// <summary>
/// 创建串口实例并配置超时参数。
/// </summary>
private static SerialPort CreatePort(
string portName,
int baudRate,
@@ -29,6 +38,9 @@ namespace DeviceCommand.Flexible
};
}
/// <summary>
/// 基于指定串口创建 Modbus RTU 主站。
/// </summary>
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<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,
int baudRate,
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,
int baudRate,
byte slaveAddress,
@@ -114,7 +155,22 @@ namespace DeviceCommand.Flexible
#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,
int baudRate,
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,
int baudRate,
byte slaveAddress,
+59 -4
View File
@@ -8,11 +8,20 @@ using System.Threading.Tasks;
namespace DeviceCommand.Flexible
{
/// <summary>
/// 灵活型 Modbus TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。
/// 支持保持寄存器与线圈的读写操作,内部使用通信锁保证同一时刻只有一个事务在执行,
/// 适用于偶发性、无需保持长连接的 Modbus TCP 设备读写场景。
/// </summary>
[ACPCommand]
public static class FModbusTCP
{
// 通信锁:保证同一时刻只有一个 Modbus 事务在执行
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)
{
var tcpClient = new TcpClient();
@@ -26,7 +35,19 @@ namespace DeviceCommand.Flexible
#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,
int port,
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,
int port,
byte slaveAddress,
@@ -79,7 +111,19 @@ namespace DeviceCommand.Flexible
#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,
int port,
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,
int port,
byte slaveAddress,
+38 -2
View File
@@ -7,11 +7,21 @@ using System.Threading.Tasks;
namespace DeviceCommand.Flexible
{
/// <summary>
/// 灵活型串口通信类(免实例化,每次调用临时创建串口连接)。
/// 支持只发送指令、发送并读取应答两种最常用操作,
/// 内部使用通信锁保证同一时刻只有一个串口事务在执行,
/// 适用于偶发性、无需保持长连接的串口设备通信场景(如示波器、电源的 SCPI 指令)。
/// </summary>
[ACPCommand]
public static class FSerialPort
{
// 通信锁:保证同一时刻只有一个串口事务在执行
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)
{
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)
/// <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);
try
@@ -52,7 +74,21 @@ namespace DeviceCommand.Flexible
#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);
try
+46 -5
View File
@@ -4,14 +4,29 @@ using System.Text;
namespace DeviceCommand.Flexible
{
/// <summary>
/// 灵活型 TCP 通信类(免实例化,每次调用临时建立 TCP 连接)。
/// 支持字节/文本发送、定长字节读取、按结束符读取文本行四种操作,
/// 内部使用通信锁保证同一时刻只有一个 TCP 事务在执行,
/// 适用于偶发性、无需保持长连接的 TCP 设备通信场景。
/// </summary>
[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)
/// <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);
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
#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);
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);
try
+60 -55
View File
@@ -10,6 +10,10 @@ using System.Text.Json;
using TSMaster;
namespace TSMasterCAN
{
/// <summary>
/// TSMaster CAN/CANFD 总线控制类,基于 TSMaster API 实现总线连接管理、
/// DBC 信号读写、报文发送(单次/循环)、日志记录等功能,暴露给命令树供测试步骤调用。
/// </summary>
[ACPCommand]
public class CAN
{
@@ -70,9 +74,10 @@ namespace TSMasterCAN
}
/// <summary>
/// 连接
/// 连接 TSMaster CAN 总线:建立总线连接并启动 RBS 仿真系统,
/// 成功后置位连接标志并触发 <see cref="ConnectEvent"/> 连接事件。
/// </summary>
/// <returns></returns>
/// <returns>返回代码,0 表示成功,非 0 表示失败(可用 <see cref="GetErrorDescription"/> 查询错误说明)</returns>
public static int Connect()
{
@@ -99,9 +104,9 @@ namespace TSMasterCAN
}
/// <summary>
/// 断开
/// 断开 TSMaster CAN 总线连接,成功后清除连接标志并触发 <see cref="DisConnectEvent"/> 断开事件。
/// </summary>
/// <returns></returns>
/// <returns>返回代码,0 表示成功,非 0 表示失败(可用 <see cref="GetErrorDescription"/> 查询错误说明)</returns>
public static int DisConnect()
{
var re = TsMasterApi.tsapp_disconnect();
@@ -161,19 +166,19 @@ namespace TSMasterCAN
}
/// <summary>
/// 开始记录日志
/// 开始记录总线日志,将总线上的报文数据记录到指定文件。
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
/// <param name="filePath">日志文件保存路径(自动转换为绝对路径)</param>
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int StartLogging(string filePath)
{
return TsMasterApi.tsapp_start_logging(Path.GetFullPath(filePath));
}
/// <summary>
/// 结束记录日志
/// 结束总线日志记录。
/// </summary>
/// <returns></returns>
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int StopLogging()
{
return TsMasterApi.tsapp_stop_logging();
@@ -191,12 +196,12 @@ namespace TSMasterCAN
}
/// <summary>
/// 获取信号值
/// 获取 DBC 中定义信号的当前物理值(从总线接收报文中解析)。
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <returns></returns>
/// <param name="channel">通道号(0 起始)</param>
/// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="ASgnName">DBC 中的信号名称</param>
/// <returns>信号物理值,获取失败时返回 <see cref="double.NaN"/></returns>
public static double GetSignalValue(byte channel, string AMsgName, string ASgnName)
{
double value = double.NaN;
@@ -237,16 +242,16 @@ namespace TSMasterCAN
}
/// <summary>
/// 设置信号值
/// 设置 DBC 中定义信号的值(按物理值编码进报文数据),可选择立即发送或周期发送。
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="ASgnName"></param>
/// <param name="AValue"></param>
/// <param name="isSend"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
/// <param name="channel">通道号(0 起始)</param>
/// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="ASgnName">DBC 中的信号名称</param>
/// <param name="AValue">要设置的信号物理值</param>
/// <param name="isSend">是否发送:false 仅更新数据不发送,true 则发送</param>
/// <param name="sendPeriod">发送周期(毫秒):0 = 只发送一次;&gt;0 = 按该周期循环发送</param>
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
/// <exception cref="Exception">信号编码写入报文失败时抛出</exception>
public static int SetSignalValue(byte channel, string AMsgName, string ASgnName, double AValue, bool isSend = false, float sendPeriod = 0)
{
var find = DBCParse.MsgDatabase[channel].First(s => s.msg_name == AMsgName);
@@ -271,12 +276,12 @@ namespace TSMasterCAN
/// <summary>
/// 设置信号值
/// 按报文名称发送 DBC 中定义的报文(使用该报文在 DBC 数据库中的当前数据)。
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
/// <param name="channel">通道号</param>
/// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="sendPeriod">发送周期(毫秒):0 = 只发送一次;&gt;0 = 按该周期循环发送</param>
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int SetSignalValue(APP_CHANNEL channel, string AMsgName, float sendPeriod = 0)
{
var find = DBCParse.MsgDatabase[(byte)channel].First(s => s.msg_name == AMsgName);
@@ -302,11 +307,11 @@ namespace TSMasterCAN
}
/// <summary>
/// 设置报文
/// 按帧 ID 更新 DBC 数据库中指定报文的数据区(仅更新内存中的数据,不发送)。
/// </summary>
/// <param name="channel">通道</param>
/// <param name="ID">DBC数据库ID</param>
/// <param name="bytes">报文数组</param>
/// <param name="channel">通道</param>
/// <param name="ID">报文帧 IDDBC 中定义的 FIdentifier</param>
/// <param name="bytes">要写入的报文数据数组,超出报文数据长度的部分会被截断</param>
public static void SetMsg(APP_CHANNEL channel, int ID, byte[] bytes)
{
var find = DBCParse.MsgDatabase[(byte)channel].FirstOrDefault(s => s.ACANFD.FIdentifier == ID);
@@ -317,20 +322,20 @@ namespace TSMasterCAN
}
/// <summary>
/// 发送自定义报文
/// 构造并发送自定义 CAN/CANFD 报文,可选择单次发送或周期发送,并可同步更新 DBC 数据库中同 ID 报文的数据。
/// </summary>
/// <param name="AIdxChn"></param>
/// <param name="AID"></param>
/// <param name="AIsTx"></param>
/// <param name="AIsExt"></param>
/// <param name="AIsRemote"></param>
/// <param name="ADLC"></param>
/// <param name="ADataArray"></param>
/// <param name="AIsFD"></param>
/// <param name="AIsBRS"></param>
/// <param name="isUpdateDBCDatabase"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
/// <param name="AIdxChn">通道号</param>
/// <param name="AID">报文帧 ID</param>
/// <param name="AIsTx">是否为发送帧</param>
/// <param name="AIsExt">是否为扩展帧(29 位 ID</param>
/// <param name="AIsRemote">是否为远程帧</param>
/// <param name="ADLC">数据长度代码(数据字节数,最大 64</param>
/// <param name="ADataArray">报文数据数组,超过 64 字节的部分会被截断</param>
/// <param name="AIsFD">是否为 CANFD 帧,默认 true</param>
/// <param name="AIsBRS">是否切换数据段波特率(仅 CANFD 有效),默认 false</param>
/// <param name="isUpdateDBCDatabase">是否同步更新 DBC 数据库中同帧 ID 报文的数据,默认 false</param>
/// <param name="sendPeriod">发送周期(毫秒):0 = 只发送一次;&gt;0 = 按该周期循环发送</param>
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int SendMsg(APP_CHANNEL AIdxChn, int AID, bool AIsTx, bool AIsExt,
bool AIsRemote, byte ADLC, byte[] ADataArray, bool AIsFD = true, bool AIsBRS = false,
bool isUpdateDBCDatabase = false, float sendPeriod = 0)
@@ -375,12 +380,12 @@ namespace TSMasterCAN
}
/// <summary>
/// 添加循环发送报文
/// 将 DBC 中定义的报文加入循环发送列表,按指定周期持续发送。
/// </summary>
/// <param name="channel"></param>
/// <param name="AMsgName"></param>
/// <param name="sendPeriod"></param>
/// <returns></returns>
/// <param name="channel">通道号(0 起始)</param>
/// <param name="AMsgName">DBC 中的报文名称</param>
/// <param name="sendPeriod">发送周期(毫秒)</param>
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
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
}
/// <summary>
/// 清除循环发送的报文
/// 清除所有循环发送的报文,停止全部周期发送任务。
/// </summary>
/// <returns></returns>
/// <returns>返回代码,0 表示成功,非 0 表示失败</returns>
public static int DeleteCyclicMsgs()
{
return TsMasterApi.tsapp_delete_cyclic_msgs();
}
/// <summary>
/// 获取错误提示
/// 根据错误代码获取对应的错误文字描述,用于诊断 TSMaster API 返回码。
/// </summary>
/// <param name="errorCode">错误代码</param>
/// <returns></returns>
/// <param name="errorCode">TSMaster API 返回的错误代码</param>
/// <returns>错误描述文本,未知错误代码时返回提示信息</returns>
public static string GetErrorDescription(int errorCode)
{
IntPtr ADesc = IntPtr.Zero;
+1
View File
@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<UseWPF>false</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
+111 -15
View File
@@ -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("<see cref=\"", "")
.Replace("\"/>", "")
.Replace("<para>", "\n")
.Replace("</para>", "")
.Replace("<seealso", "")
.Replace("/>", "")
.Replace("<c>", "") // 处理代码标签
.Replace("</c>", "")
.Replace("<code>", "")
.Replace("</code>", "")
.Trim();
}
#endregion
#region
@@ -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<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()
{
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();
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<StepVM> { SelectedStep } : null);
if (source == null || !source.Any()) return;
foreach (var item in source)
{
// 创建一个副本进行循环,防止在 Remove 过程中集合变化导致的问题
var toDelete = SelectedItems.ToList();
_eventAggregator.GetEvent<DeletedStepEvent>().Publish(item.ID);
foreach (var item in toDelete)
{
_eventAggregator.GetEvent<DeletedStepEvent>().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
/// </summary>
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<AlarmEvent>()?.Unsubscribe(null);
_eventAggregator?.GetEvent<SubProgramNavigateEvent>()?.Unsubscribe(null);
+2 -1
View File
@@ -53,7 +53,8 @@
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:InstructionNodeVM}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Name}" ToolTip="{Binding Tooltip}"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
<!-- 双击 -->
+7
View File
@@ -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);
}
}
}