新增 pELoad 电子负载(IT6000C)控制程序:以太网 Raw Socket SCPI 直连设备,100ms 周期功率控制(订阅 MOOS 功率消息按 P/U 折算电流,CC/CV 双环路优先 + FUNC:MODE FIX 防护),网页人机交互(步骤引导/防误触确认),sqlite3 存储(100ms 高采样 V/I/P 测量表 meas_log + CSV 导出分析),并接入 scripts/build-board.sh 板卡部署与 systemd 服务、missions/h100.moos 配置

This commit is contained in:
zjk
2026-09-03 12:26:02 +08:00
parent 38c489caec
commit 2a34b79461
21 changed files with 3601 additions and 5 deletions
+117
View File
@@ -0,0 +1,117 @@
#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; }
// 写命令(无响应期待)。未连接/发送失败按错误计数并丢弃。
bool cmd(const std::string& scpi);
// 同步查询:发送 scpi 并等待一行响应。
// timeoutMs 建议不大于控制周期(100ms 循环里建议 <= 80)。
bool query(const std::string& scpi, std::string& resp, int timeoutMs = 200);
// 统计
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;
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