130 lines
5.1 KiB
C++
130 lines
5.1 KiB
C++
#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
|