#ifndef PELOAD_SCPI_CLIENT_H #define PELOAD_SCPI_CLIENT_H #include #include #include #include #include #include #include #include #include #include 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; // 连接状态变化回调(IO 线程内调用) using StateCallback = std::function; 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; 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 m_running{false}; std::atomic m_connected{false}; std::thread m_thread; // 命令队列:cmd() 与 query() 共用;IO 线程逐条处理 std::mutex m_queueMutex; std::condition_variable m_queueCv; std::deque > m_queue; // 同一时刻仅允许一个同步查询 std::mutex m_queryMutex; LogSink m_logSink; // 任意线程注册,IO 线程调用(start 前 set) StateCallback m_stateCb; std::vector m_buf; // 流式接收缓冲(半包/粘包按 \n 切分) std::atomic m_txCount{0}; std::atomic m_txErrorCount{0}; std::atomic m_rxCount{0}; std::atomic m_errorCount{0}; std::atomic m_reconnectCount{0}; std::atomic m_lastRxTime{0.0}; }; } // namespace eload #endif // PELOAD_SCPI_CLIENT_H