Files
ACP/DeviceCommand/Devices/IT6720.cs
T
2026-10-13 14:36:14 +08:00

182 lines
7.8 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.Globalization;
using System.Threading;
using System.Threading.Tasks;
namespace DeviceCommand.Devices
{
/// <summary>
/// 低压电源 一拖三(ITECH IT67xx 系列,标准 SCPI 指令集)。
/// <para>传输层为 VISA(USB VISA 资源字符串寻址,如 USB0::VID::PID::SN::INSTR)。</para>
/// <para>SCPI 命令语法参照同家族已在实机验证的实现 BOB/DeviceCommand/Devices/IT6724C.cs:
/// 设定类命令只发不读(SCPI 设置命令无应答,避免空等超时),查询类命令走 WriteReadAsync 读取一行响应。</para>
/// </summary>
[ACPCommand]
public class IT6720 : Visa
{
// SCPI 指令结束符(VISA 消息终止符使用 LF)
private const string ScpiDelimiter = "\n";
// 最近一次设定的限流值(A):IT67xx SCPI 无直接 CV/CC 查询,用"实测电流是否顶到限流"启发式判断输出模式
private double _setCurrentLimit;
public IT6720(VisaConfig config) : base(config)
{
}
#region SCPI 收发辅助
/// <summary>以不变文化格式化数值,避免小数分隔符随系统区域变化。</summary>
private static string Fmt(double value) => value.ToString(CultureInfo.InvariantCulture);
/// <summary>宽松解析仪器回传的数值字符串,失败返回 0。</summary>
private static double ParseDouble(string raw)
=> double.TryParse(raw, NumberStyles.Float, CultureInfo.InvariantCulture, out double v) ? v : 0;
/// <summary>发送设定类 SCPI 命令(只发不读:SCPI 设置命令无应答,读取会空等超时)。</summary>
private async Task SetAsync(string command, CancellationToken ct)
=> await SendAsync($"{command}{ScpiDelimiter}", ct).ConfigureAwait(false);
/// <summary>发送查询类 SCPI 命令并读取一行响应。</summary>
private async Task<string> QueryAsync(string query, CancellationToken ct)
=> await WriteReadAsync($"{query}{ScpiDelimiter}", ScpiDelimiter, ct).ConfigureAwait(false);
#endregion
#region 1. 操作模式与输出控制
/// <summary>
/// 切换电源控制模式:PC 远程控制 (SYSTem:REMote) / 面板本地控制 (SYSTem:LOCal)
/// </summary>
public virtual async Task 切换远程控制模式(bool 远程控制 = true, CancellationToken ct = default)
=> await SetAsync(远程控制 ? "SYSTem:REMote" : "SYSTem:LOCal", ct);
/// <summary>
/// 切换为本地(面板)控制模式,与远程控制互斥
/// </summary>
/// <param name="ct">取消令牌</param>
public virtual async Task 切换本地控制模式(CancellationToken ct = default)
=> await 切换远程控制模式(false, ct);
/// <summary>
/// 开启或关闭电源输出 (OUTPut 1/0)
/// </summary>
public virtual async Task 设置输出开关(bool 开启 = true, CancellationToken ct = default)
=> await SetAsync($"OUTPut {(开启 ? 1 : 0)}", ct);
/// <summary>
/// 查询电源输出开关状态 (OUTPut?)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>输出开关状态字符串 (ON / OFF)</returns>
public virtual async Task<string> 查询输出开关状态(CancellationToken ct = default)
{
string r = (await QueryAsync("OUTPut?", ct)).Trim();
return (r == "1" || r.StartsWith("ON", StringComparison.OrdinalIgnoreCase)) ? "ON" : "OFF";
}
#endregion
#region 2. 电压 / 电流设定
/// <summary>
/// 设置电源的电压上限 / 过压保护阈值 (VOLT:PROTection)
/// </summary>
public virtual async Task 设置电压上限(double 电压, CancellationToken ct = default)
=> await SetAsync($"VOLT:PROTection {Fmt(电压)}", ct);
/// <summary>
/// 设置电源的输出电压 (VOLTage)
/// </summary>
public virtual async Task 设置输出电压(double 电压, CancellationToken ct = default)
=> await SetAsync($"VOLTage {Fmt(电压)}", ct);
/// <summary>
/// 设置电源的输出电流限流值 (CURRent)
/// </summary>
public virtual async Task 设置输出电流(double 电流, CancellationToken ct = default)
{
_setCurrentLimit = 电流;
await SetAsync($"CURRent {Fmt(电流)}", ct);
}
#endregion
#region 3. 读取实时数据与状态
/// <summary>
/// 读取电源的实时电流、电压及输出开关状态
/// 返回元组:实际电流(A, MEASure:CURRent?), 实际电压(V, MEASure:VOLTage?), 状态字节(int, 位0=输出开关)
/// </summary>
[Monitorable("IT6720 实时测量数据")]
public virtual async Task<(double ActualCurrent, double ActualVoltage, int StatusByte)> 查询实时测量值(CancellationToken ct = default)
{
double current = ParseDouble(await QueryAsync("MEASure:CURRent?", ct));
double voltage = ParseDouble(await QueryAsync("MEASure:VOLTage?", ct));
string on = (await QueryAsync("OUTPut?", ct)).Trim();
int statusByte = (on == "1" || on.StartsWith("ON", StringComparison.OrdinalIgnoreCase)) ? 0x01 : 0x00;
return (current, voltage, statusByte);
}
/// <summary>
/// 查询电源的实时实际输出电压 (MEASure:VOLTage?)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电压值 (单位: V,保留两位小数)</returns>
[Monitorable("IT6720 实际电压")]
public virtual async Task<double> 查询实际电压(CancellationToken ct = default)
{
var (_, voltage, _) = await 查询实时测量值(ct);
return Math.Round(voltage, 2);
}
/// <summary>
/// 查询电源的实时实际输出电流 (MEASure:CURRent?)
/// </summary>
/// <param name="ct">取消令牌</param>
/// <returns>实时电流值 (单位: A,保留两位小数)</returns>
[Monitorable("IT6720 实际电流")]
public virtual async Task<double> 查询实际电流(CancellationToken ct = default)
{
var (current, _, _) = await 查询实时测量值(ct);
return Math.Round(current, 2);
}
/// <summary>
/// 判断输出处于恒压 (CV) 还是恒流 (CC) 模式。
/// IT67xx SCPI 无直接 CV/CC 查询命令,此处以"实测电流是否顶到限流设定值"启发式判断:
/// 输出关闭返回 OFF;实测电流达到限流值(≥98%)判为 CC,否则 CV。
/// </summary>
public virtual async Task<string> 查询输出模式(CancellationToken ct = default)
{
var (current, _, status) = await 查询实时测量值(ct);
if ((status & 0x01) == 0) return "OFF";
if (_setCurrentLimit > 0 && current >= _setCurrentLimit * 0.98) return "CC";
return "CV";
}
#endregion
#region 4. 设备信息与错误
/// <summary>
/// 读取电源的标识信息 (*IDN?:制造商, 型号, 序列号, 固件版本)
/// </summary>
public virtual async Task<string> 查询设备信息(CancellationToken ct = default)
=> await QueryAsync("*IDN?", ct);
/// <summary>
/// IT67xx 未提供标准错误队列查询,返回面板提示说明
/// </summary>
public virtual async Task<string> 查询错误信息(CancellationToken ct = default)
{
return "IT6720 无标准 SCPI 错误信息查询,请检查前面板 ERROR 指示灯。";
}
#endregion
}
}