Files
H100PowerManger/src/pELoad/driver/ScpiClient.h
T

130 lines
5.1 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.
#ifndef PELOAD_SCPI_CLIENT_H
#define PELOAD_SCPI_CLIENT_H
#include <cstdint>
#include <string>
#include <vector>
#include <deque>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <thread>
#include <functional>
namespace eload {
//============================================================================
// ScpiClient:IT6000C Raw Socket SCPI 客户端(TCP,LF 结尾,自动重连)。
//
// 协议要点(docs/电子负载说明手册/IT6000C User Manual-CN 2.5.2.3 使用套接字):
// - 仪器提供 SCPI 套接字服务(Socket Port 可配置,出厂默认 30000);
// - 所有命令必须以换行符(\n)结尾;查询响应同样以换行符结束;
// - 仪器最多同时允许 6 个套接字/telnet 连接。
//
// 线程模型:
// - 内部一个 IO 线程:负责连接/重连、命令写出、响应读取;
// - cmd() 非阻塞入队(写后无响应);
// - query() 同步等待响应(同一时刻仅允许一个查询,互斥保护);
// - 本线程不调用任何 MOOS 接口,跨线程数据仅通过回调投递。
//============================================================================
class ScpiClient {
public:
// SCPI 收发记录回调(落库用,IO 线程内调用):
// 命令型:resp 为空;查询型:resp 为响应文本(含失败标记)
using LogSink = std::function<void(const std::string& cmd, const std::string& resp)>;
// 连接状态变化回调(IO 线程内调用)
using StateCallback = std::function<void(bool connected)>;
ScpiClient();
~ScpiClient();
void configure(const std::string& host, long port); // start 前调用
void setLogSink(LogSink cb); // start 前调用
void setStateCallback(StateCallback cb); // start 前调用
bool start(); // 启动 IO 线程(自动连接/重连)
void stop();
bool isConnected() const { return m_connected; }
// 写命令(无响应期待)。未连接/发送失败按错误计数并丢弃。
// 返回 true 仅表示"已入队",不保证写入成功。
bool cmd(const std::string& scpi);
// 写命令并等待确认:阻塞到 IO 线程真正把命令写入 socket(或超时)。
// 用于需要明确成功/失败反馈的关键指令(如 SYST:REM / SYST:LOC)。
bool cmdConfirmed(const std::string& scpi, int timeoutMs = 300);
// 同步查询:发送 scpi 并等待一行响应。
// timeoutMs 建议不大于控制周期(100ms 循环里建议 <= 80)。
bool query(const std::string& scpi, std::string& resp, int timeoutMs = 200);
// 异步查询:发送 scpi,IO 线程收到响应后调用回调(IO 线程内)。
// 不阻塞调用线程——用于测量轮询,避免拖慢 100ms 功率控制循环。
// 回调注意:运行在 IO 线程,勿直接调用会阻塞的操作。
using QueryCallback = std::function<void(bool ok, const std::string& resp)>;
bool queryAsync(const std::string& scpi, QueryCallback cb);
// 统计
unsigned long txCount() const { return m_txCount; }
unsigned long txErrorCount() const { return m_txErrorCount; }
unsigned long rxCount() const { return m_rxCount; }
unsigned long errorCount() const { return m_errorCount; }
unsigned long reconnectCount() const { return m_reconnectCount; }
double lastRxTime() const { return m_lastRxTime; }
const std::string& host() const { return m_host; }
long port() const { return m_port; }
private:
struct Work {
std::string cmd;
bool expectReply;
std::string resp;
bool done;
bool ok;
QueryCallback cb; // 异步查询回调(IO 线程调用)
Work() : expectReply(false), done(false), ok(false) {}
};
void threadFunc();
bool connectOnce();
void closeSocket();
bool writeLine(const std::string& scpi);
// 从 socket 读一行(LF 结尾),deadlineMs 内未完成返回 false
bool readLine(std::string& line, int deadlineMs);
void runQueue();
std::string m_host;
long m_port = 30000;
int m_fd = -1;
std::atomic<bool> m_running{false};
std::atomic<bool> m_connected{false};
std::thread m_thread;
// 命令队列:cmd() 与 query() 共用;IO 线程逐条处理
std::mutex m_queueMutex;
std::condition_variable m_queueCv;
std::deque<std::shared_ptr<Work> > m_queue;
// 同一时刻仅允许一个同步查询
std::mutex m_queryMutex;
LogSink m_logSink; // 任意线程注册,IO 线程调用(start 前 set)
StateCallback m_stateCb;
std::vector<uint8_t> m_buf; // 流式接收缓冲(半包/粘包按 \n 切分)
std::atomic<unsigned long> m_txCount{0};
std::atomic<unsigned long> m_txErrorCount{0};
std::atomic<unsigned long> m_rxCount{0};
std::atomic<unsigned long> m_errorCount{0};
std::atomic<unsigned long> m_reconnectCount{0};
std::atomic<double> m_lastRxTime{0.0};
};
} // namespace eload
#endif // PELOAD_SCPI_CLIENT_H