Files
ADP/DeviceCommand/Devices/N36600.cs
T
2026-08-12 18:16:50 +08:00

552 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Common.Attributes;
using DeviceCommand.Base;
using Model.Models;
using System;
using System.ComponentModel;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
[ADPCommand]
public class N36600 : Tcp
{
private CancellationTokenSource? _heartbeatCts;
private Task? _heartbeatTask;
private const int HeartbeatInterval = 3000; // 心跳间隔 3 秒
public bool IsActive { get; private set; } = false;
public int ReConnectionAttempts { get; private set; } = 0;
public const int MaxReconnectAttempts = 10;
// 手册第 4 页明确规定:每条命令后面都要加结束符 0x0A (\n)
private const string SCPIDelimiter = "\n";
public N36600(TcpConfig config) : base(config)
{
}
/// <summary>
/// 建立 TCP 连接,成功后自动激活心跳
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>连接是否成功</returns>
public new async Task<bool> ConnectAsync(CancellationToken ct = default)
{
bool isConnected = await base.ConnectAsync(ct);
if (isConnected)
{
IsActive = true;
ReConnectionAttempts = 0;
StartHeartbeat();
}
return isConnected;
}
public override void Close()
{
StopHeartbeat();
base.Close();
}
#region 心跳与断线重连逻辑
[Browsable(false)]
public void StartHeartbeat()
{
if (_heartbeatTask != null && !_heartbeatTask.IsCompleted)
return;
_heartbeatCts?.Cancel();
_heartbeatCts?.Dispose();
_heartbeatCts = new CancellationTokenSource();
_heartbeatTask = Task.Run(() => HeartbeatLoop(_heartbeatCts.Token));
}
[Browsable(false)]
public void StopHeartbeat()
{
IsActive = false;
if (_heartbeatCts != null && !_heartbeatCts.IsCancellationRequested)
{
_heartbeatCts.Cancel();
}
_heartbeatTask = null;
}
private async Task HeartbeatLoop(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
await Task.Delay(HeartbeatInterval, ct);
if (ct.IsCancellationRequested) break;
// 使用公共查询命令发送心跳,确保通道连接正常,且不破坏远程或本地锁定状态
await SendAsync($"*IDN?{SCPIDelimiter}", ct);
IsActive = true;
ReConnectionAttempts = 0;
}
catch (Exception)
{
IsActive = false;
ReConnectionAttempts++;
if (ReConnectionAttempts > MaxReconnectAttempts)
{
StopHeartbeat();
base.Close();
return;
}
await ReconnectDeviceAsync(ct);
}
}
}
private async Task ReconnectDeviceAsync(CancellationToken ct)
{
try
{
await ConnectAsync(ct);
}
catch (Exception)
{
// 静默处理,等待下一轮心跳重试
}
}
#endregion
#region 3.1 IEEE 488.2 公用命令
/// <summary>
/// 3.1.1 读取电源的相关信息(制造商, 产品标号, 产品序列号, 软件版本号)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>设备标识字符串(制造商, 产品标号, 产品序列号, 软件版本号)</returns>
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
{
return await WriteReadAsync($"*IDN?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.1.2 从指定的单位中恢复 *SAV 命令保存的设定值 (参数:1~99)
/// </summary>
/// <param name="组别">存储组编号 (范围: 1~99)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 调用存储状态(int 组别, CancellationToken ct = default)
{
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
await SendAsync($"*RCL {组别}{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.1.3 将仪器当前系统状态保存到非易失性内存中 (参数:1~99)
/// </summary>
/// <param name="组别">存储组编号 (范围: 1~99)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 保存当前状态(int 组别, CancellationToken ct = default)
{
if (组别 < 1 || 组别 > 99) throw new ArgumentOutOfRangeException(nameof(组别), "组别有效范围为 1~99");
await SendAsync($"*SAV {组别}{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.1.4 远程控制触发一次
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 远程触发(CancellationToken ct = default)
{
await SendAsync($"*TRG{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.1.5 返回状态、采集电压、采集电流 (格式: 状态码,电压值,电流值)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>状态码,电压值,电流值的组合字符串</returns>
public virtual async Task<string> 查询全部状态及采样(CancellationToken ct = default)
{
return await WriteReadAsync($"*ALL?{SCPIDelimiter}", SCPIDelimiter, ct);
}
#endregion
#region 3.2 APPLy 命令子系统
/// <summary>
/// 3.2.1 设置输出电压和输出电流值
/// </summary>
/// <param name="电压">输出电压设定值 (单位: V)</param>
/// <param name="电流">输出电流设定值 (单位: A)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 快捷设置电压电流(double 电压, double 电流, CancellationToken ct = default)
{
// 修正:去除前缀冒号,规范应用 APPL 短格式
string cmd = string.Format(CultureInfo.InvariantCulture, "APPL {0:F3},{1:F3}{2}", 电压, 电流, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.2.1 查询当前设定的输出电压和电流值
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>当前输出电压和电流设定值的组合字符串</returns>
public virtual async Task<string> 查询快捷电压电流设定(CancellationToken ct = default)
{
// 修正:去除前缀冒号
return await WriteReadAsync($"APPL?{SCPIDelimiter}", SCPIDelimiter, ct);
}
#endregion
#region 3.5 MEASure 命令子系统
/// <summary>
/// 3.5.1 查询通道输出端子上测得的直流电流值 (A)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实测直流电流值 (单位: A)</returns>
[Monitorable("低压直流电源电流")]
public virtual async Task<string> 查询实际电流(CancellationToken ct = default)
{
// 修正:去除前缀冒号,改用标准 MEAS:CURR?
return await WriteReadAsync($"MEAS:CURR?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.5.2 查询通道输出端子上测得的直流功率值 (W)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实测直流功率值 (单位: W)</returns>
[Monitorable("低压直流电源功率")]
public virtual async Task<string> 查询实际功率(CancellationToken ct = default)
{
// 修正:去除前缀冒号,改用标准 MEAS:POW?
return await WriteReadAsync($"MEAS:POW?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.5.3 查询通道输出端子上测得的直流电压值 (V)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实测直流电压值 (单位: V)</returns>
[Monitorable("低压直流电源电压")]
public virtual async Task<string> 查询实际电压(CancellationToken ct = default)
{
// 修正:去除前缀冒号,改用标准 MEAS:VOLT?
return await WriteReadAsync($"MEAS:VOLT?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.5.4 查询通道输出端子上测得的电压、电流和功率的组合值
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>电压、电流和功率的组合值字符串</returns>
public virtual async Task<string> 查询电压电流功率数组(CancellationToken ct = default)
{
// 修正:去除前缀冒号
return await WriteReadAsync($"MEAS:VAP?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.5.5 查询通道输出端子上测得的输出时间长度
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>输出时间长度</returns>
public virtual async Task<string> 查询总输出时间长度(CancellationToken ct = default)
{
// 修正:去除前缀冒号
return await WriteReadAsync($"MEAS:TIME:OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
}
#endregion
#region 3.6 OUTPut 命令子系统
/// <summary>
/// 3.6.1 设置电源输出开关
/// </summary>
/// <param name="开启">true: 开启输出, false: 关闭输出</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置DC输出(bool 开启, CancellationToken ct = default)
{
// 修正:去除前缀冒号,改用 OUTP 短格式
string 状态 = 开启 ? "ON" : "OFF";
await SendAsync($"OUTP {状态}{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.6.1 查询通道输出状态 (返回 ON 或 OFF)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>通道输出状态字符串("ON" 或 "OFF"</returns>
public virtual async Task<string> 查询DC输出状态(CancellationToken ct = default)
{
// 修正:去除前缀冒号
return await WriteReadAsync($"OUTP?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.6.2 控制电源输出定时器的开关状态
/// </summary>
/// <param name="开启">true: 开启定时器, false: 关闭定时器</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置定时器状态(bool 开启, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string 状态 = 开启 ? "ON" : "OFF";
await SendAsync($"OUTP:TIME {状态}{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.6.3 设定电源输出定时器的时间 (单位: s, 范围: 0.1 ~ 999999.9)
/// </summary>
/// <param name="秒数">定时器时间 (单位: s, 范围: 0.1 ~ 999999.9)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置定时器时间(double 秒数, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string cmd = string.Format(CultureInfo.InvariantCulture, "OUTP:TIME:DATA {0:F1}{1}", 秒数, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.6.4 设定电源输出的模式优先权 (CV优先 或 CC优先)
/// </summary>
/// <param name="是CV优先">true: CV优先, false: CC优先</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置输出模式优先(bool CV优先, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string 模式 = CV优先 ? "CV" : "CC";
await SendAsync($"OUTP:MODE {模式}{SCPIDelimiter}", ct);
}
#endregion
#region 3.7 SOURce 命令子系统
/// <summary>
/// 3.7.1.1 清除输出电流保护(OCP)电路状态
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 清除电流保护状态(CancellationToken ct = default)
{
// 修正:去除前缀冒号
await SendAsync($"CURR:PROT:CLE{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.7.1.2 使能或禁止当前输出电流保护(OCP)电路
/// </summary>
/// <param name="启用">true: 使能OCP, false: 禁止OCP</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置电流保护使能(bool 启用, CancellationToken ct = default)
{
// 修正:去除前缀冒号,将 STATE 改为更常用的 STAT 简写
string 状态 = 启用 ? "ON" : "OFF";
await SendAsync($"CURR:PROT:STAT {状态}{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.7.1.4 设置输出电流保护(OCP)阀值 (A)
/// </summary>
/// <param name="电流">OCP 保护阀值 (单位: A)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置过流保护值(double 电流, CancellationToken ct = default)
{
// 修正:去除前缀冒号。按 NGI 通用指令树,加上层级限制或者直接作用于 CURR:PROT
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR:PROT {0:F3}{1}", 电流, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.7.1.7 设置电源的输出电流值 (A)
/// </summary>
/// <param name="电流">输出电流设定值 (单位: A)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置电流(double 电流, CancellationToken ct = default)
{
// 修正:去除前缀冒号,改用标准 CURR 短格式
string cmd = string.Format(CultureInfo.InvariantCulture, "CURR {0:F3}{1}", 电流, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.7.2.1 清除输出电压保护(OVP)电路状态
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 清除电压保护状态(CancellationToken ct = default)
{
// 修正:去除前缀冒号
await SendAsync($"VOLT:PROT:CLE{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.7.2.2 使能或禁止当前输出电压保护(OVP)电路
/// </summary>
/// <param name="启用">true: 使能OVP, false: 禁止OVP</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置电压保护使能(bool 启用, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string 状态 = 启用 ? "ON" : "OFF";
await SendAsync($"VOLT:PROT:STAT {状态}{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.7.2.4 设置输出电压保护(OVP)阀值 (V)
/// </summary>
/// <param name="电压">OVP 保护阀值 (单位: V)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置过压保护值(double 电压, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:PROT {0:F3}{1}", 电压, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.7.2.7 设置电源的输出电压值 (V)
/// </summary>
/// <param name="电压">输出电压设定值 (单位: V)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置电压(double 电压, CancellationToken ct = default)
{
// 修正:去除前缀冒号,改用标准 VOLT 短格式
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT {0:F3}{1}", 电压, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.7.2.8 设定电压输出范围的上限电压值 (V)
/// </summary>
/// <param name="电压">上限电压值 (单位: V)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置电压上限限制(double 电压, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string cmd = string.Format(CultureInfo.InvariantCulture, "VOLT:LIM {0:F3}{1}", 电压, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.7.2.9 清除电压上限限制(恢复为设备物理允许的最大电压值)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 清除电压上限限制(CancellationToken ct = default)
{
// 发送 MAXimum 设为最大值以达到"清除限制"的效果
await SendAsync($"VOLT:LIMIT MAX{SCPIDelimiter}", ct); // 对应手册 3.7.2.8
}
/// <summary>
/// 3.7.3.1 设置输出功率值 (W)
/// </summary>
/// <param name="功率">输出功率设定值 (单位: W)</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置功率(double 功率, CancellationToken ct = default)
{
// 修正:去除前缀冒号,改用标准 POW 短格式
string cmd = string.Format(CultureInfo.InvariantCulture, "POW {0:F3}{1}", 功率, SCPIDelimiter);
await SendAsync(cmd, ct);
}
/// <summary>
/// 3.7.3.2 禁止或使能当前恒功率(CP)输出
/// </summary>
/// <param name="启用">true: 使能恒功率输出, false: 禁止恒功率输出</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置恒功率输出使能(bool 启用, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string 状态 = 启用 ? "ON" : "OFF";
await SendAsync($"POW:STAT {状态}{SCPIDelimiter}", ct);
}
#endregion
#region 3.9 SYSTem 命令子系统
/// <summary>
/// 3.9.1 控制蜂鸣器开关
/// </summary>
/// <param name="开启">true: 开启蜂鸣器, false: 关闭蜂鸣器</param>
/// <param name="ct">取消令牌</param>
public virtual async Task 设置蜂鸣器状态(bool 开启, CancellationToken ct = default)
{
// 修正:去除前缀冒号
string 状态 = 开启 ? "ON" : "OFF";
await SendAsync($"SYST:BEEP:STAT {状态}{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.9.2 使蜂鸣器强制鸣叫一声
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 蜂鸣器鸣叫(CancellationToken ct = default)
{
// 修正:去除前缀冒号
await SendAsync($"SYST:BEEP{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.9.4 查询仪器当前出错记录数量 (最大 18 组)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>当前出错记录数量(最大 18 组)</returns>
public virtual async Task<string> 查询错误记录数量(CancellationToken ct = default)
{
// 修正:去除前缀冒号
return await WriteReadAsync($"SYST:ERR:COUN?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.9.5 读取仪器的出错信息 (成功返回 0,"No error")
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>错误代码和描述信息(成功返回 0,"No error"</returns>
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
{
// 修正:去除前缀冒号
return await WriteReadAsync($"SYST:ERR?{SCPIDelimiter}", SCPIDelimiter, ct);
}
/// <summary>
/// 3.9.7 设置电源为面板控制模式 (本地 Local 状态,前面板按键可用)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
{
// 修正:去除前缀冒号
await SendAsync($"SYST:LOC{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.9.8 设置电源为远程控制模式 (Remote 状态)
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 切换远程控制模式(CancellationToken ct = default)
{
// 修正:去除前缀冒号
await SendAsync($"SYST:REM{SCPIDelimiter}", ct);
}
/// <summary>
/// 3.9.9 通过通信接口设置电源为远程控制锁定模式
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 远程控制模式锁定(CancellationToken ct = default)
{
// 修正:去除前缀冒号
await SendAsync($"SYST:RWL{SCPIDelimiter}", ct);
}
#endregion
}
}