pELoad 连接后从设备回读真实配置同步状态

程序重启/刷新后页面状态与设备实际状态错配,导致按页面流程下发指令反馈错误。
新增 syncDeviceConfigOnce:连接建立进入远控后同步查询设备 OUTP/FUNC/FUNC:MODE
及各限值/设定值,以设备真实回读为准覆盖内存状态;设备输出仍为开时置
loadOn=true 并由 applyEnable 重新下发完整使能序列(接管恢复控制)。

- IT6000C 新增 7 个配置回读查询封装(CURR?/VOLT?/VOLT:LIM?/CURR:LIM?*2/POW:LIM?*2)
- ELoad 实现 syncDeviceConfigOnce 并接入 doControlCycle(连接后自动,覆盖重连/复位/本控→远控)
- 抽出 normFunc 辅助函数复用于 pollError;快照新增 cfgSynced/cfgSyncTime
- 页面卡2 增加设备配置同步状态显示
This commit is contained in:
zjk
2026-09-07 13:03:08 +08:00
parent 237e17ea50
commit c610190c54
5 changed files with 126 additions and 7 deletions
+99 -7
View File
@@ -519,6 +519,7 @@ void ELoad::doControlCycle(double now) {
// 每次连接后进入远控(仅当需要远控;用户切本控后不自动切回)
if (m_remoteRequested && !m_remoteApplied) {
m_devCfgSyncDone = false; // 重连/复位/本控后需重读设备真实配置
if (m_dev.remote()) {
m_remoteApplied = true;
std::lock_guard<std::mutex> lock(m_stateMutex);
@@ -532,6 +533,12 @@ void ELoad::doControlCycle(double now) {
}
}
// 连接建立并进入远控后,回读一次设备真实配置(接管恢复控制)
if (m_remoteApplied && !m_devCfgSyncDone) {
syncDeviceConfigOnce(now);
m_devCfgSyncDone = true;
}
// 输出状态切换
bool needOn = m_loadOn;
if (needOn && !m_cfgApplied)
@@ -768,8 +775,97 @@ std::string normFuncMode(const std::string& m) {
if (u.rfind("CAR", 0) == 0) return "CAR";
return u;
}
// 规范化环路优先 FUNC 回读:CURRent/VOLTage 长格式 -> CURR/VOLT。
std::string normFunc(const std::string& m) {
std::string u;
u.reserve(m.size());
for (size_t i = 0; i < m.size(); ++i)
u += static_cast<char>(::toupper(static_cast<unsigned char>(m[i])));
if (u.rfind("CURR", 0) == 0) return "CURR";
if (u.rfind("VOLT", 0) == 0) return "VOLT";
return u;
}
} // namespace
//---------------------------------------------------------
// syncDeviceConfigOnce:连接后从设备回读真实配置并接管状态。
// 程序重启/重连/复位后,设备可能保持着与 .moos 默认值不同的实际状态;
// 这里以设备真实回读为准覆盖内存状态,避免页面与设备错配。
// 在控制线程内同步调用(仅连接建立时执行一次,低频);查询在锁外进行,
// 全部收集完毕后一次性加锁写回共享状态。
void ELoad::syncDeviceConfigOnce(double now) {
bool outpOn = false;
std::string func, mode;
double voltLimit = 0, currPos = 0, currNeg = 0, powPos = 0, powNeg = 0;
double curr = 0, volt = 0;
bool hasOutp = false, hasFunc = false, hasMode = false;
bool hasVoltLim = false, hasCurrPos = false, hasCurrNeg = false;
bool hasPowPos = false, hasPowNeg = false;
bool hasCurr = false, hasVolt = false;
if (m_dev.queryOutput(outpOn)) hasOutp = true;
if (m_dev.queryFunction(func)) hasFunc = true;
if (m_dev.queryFunctionMode(mode)) hasMode = true;
if (m_dev.queryVoltageLimit(voltLimit)) hasVoltLim = true;
if (m_dev.queryCurrentLimitPos(currPos)) hasCurrPos = true;
if (m_dev.queryCurrentLimitNeg(currNeg)) hasCurrNeg = true;
if (m_dev.queryPowerLimitPos(powPos)) hasPowPos = true;
if (m_dev.queryPowerLimitNeg(powNeg)) hasPowNeg = true;
if (m_dev.queryCurrent(curr)) hasCurr = true;
if (m_dev.queryVoltage(volt)) hasVolt = true;
std::lock_guard<std::mutex> lock(m_stateMutex);
// 输出:设备保持开 -> 接管恢复控制(置 loadOn=true,cfgApplied=false,
// 由 applyEnable 重新下发完整使能序列)
if (hasOutp) {
m_loadOn = outpOn;
m_cfgApplied = false;
}
// 环路优先 FUNC:以设备为准,镜像 WCMD_MODE_CC/CV 的联动逻辑
if (hasFunc) {
std::string nf = normFunc(func);
if (!nf.empty()) {
bool cc = (nf != "VOLT");
m_devFunc = nf;
if (m_ccPriority != cc) {
m_ccPriority = cc;
if (!cc) m_mode = "volt";
else if (m_mode == "volt") m_mode = "follow";
}
}
}
// 功能模式 FUNC:MODE
if (hasMode) {
std::string nm = normFuncMode(trimScpiStr(mode));
if (!nm.empty()) {
m_devFuncMode = nm;
m_funcModeFixWarn = (nm != "FIX");
}
}
// 限值 / 设定值:以设备实际值为准
if (hasVoltLim) m_voltLimit = voltLimit;
if (hasCurrPos) m_currLimitPos = currPos;
if (hasCurrNeg) m_currLimitNeg = currNeg;
if (hasPowPos) m_powLimitPos = powPos;
if (hasPowNeg) m_powLimitNeg = powNeg;
if (hasCurr) m_cmdCurrent = curr;
if (hasVolt) m_voltSet = volt;
m_devCfgSyncTime = now;
LOG_F(INFO, "[ELoad] device config synced: outp=%s func=%s mode=%s "
"voltLim=%.1f currLim=%.3f/%.3f powLim=%.1f/%.1f curr=%.3f volt=%.1f",
(hasOutp ? (outpOn ? "ON" : "OFF") : "?"),
(hasFunc ? m_devFunc.c_str() : "?"),
(hasMode ? m_devFuncMode.c_str() : "?"),
voltLimit, currPos, currNeg, powPos, powNeg, curr, volt);
}
void ELoad::pollDevice() {
if (m_measPending) return; // 上一轮查询仍在 IO 线程处理中
if (!m_scpi.isConnected()) {
@@ -820,14 +916,8 @@ void ELoad::pollError(double now) {
case 0:
m_scpi.queryAsync("FUNC?", [this](bool ok, const std::string& r) {
if (!ok) return;
std::string clean = trimScpiStr(r);
std::string clean = normFunc(trimScpiStr(r));
if (clean.empty()) return;
// 兼容 CURRent/VOLTage 长格式 -> CURR/VOLT
std::string u;
for (size_t i = 0; i < clean.size(); ++i)
u += static_cast<char>(::toupper(static_cast<unsigned char>(clean[i])));
if (u.rfind("CURR", 0) == 0) clean = "CURR";
else if (u.rfind("VOLT", 0) == 0) clean = "VOLT";
std::lock_guard<std::mutex> lock(m_stateMutex);
m_devFunc = clean;
});
@@ -993,6 +1083,8 @@ std::string ELoad::buildSnapshot() {
dev["funcModeFeedOk"] = m_funcModeFeedOk ? 1 : 0;
dev["funcModeFeedTime"]= m_funcModeFeedTime;
dev["funcModeFeedValid"] = m_funcModeFeedTime > 0 ? 1 : 0;
dev["cfgSynced"] = m_devCfgSyncDone ? 1 : 0;
dev["cfgSyncTime"] = m_devCfgSyncTime;
dev["remoteState"] = m_remoteState; // remote / local / unknown
dev["remoteFeed"] = m_remoteFeed; // 最近远控/本控指令执行结果
dev["remoteFeedTime"] = m_remoteFeedTime;
+3
View File
@@ -54,6 +54,7 @@ private:
void applyDisable(); // 关输出
double computeTargetPower(double now); // 目标功率(含超时/斜率处理)
double computeCmdCurrent(double targetP); // 功率折算电流(含限幅)
void syncDeviceConfigOnce(double now); // 连接后从设备回读真实配置并接管
void pollDevice(); // 异步测量轮询(不阻塞控制循环)
void pollError(double now); // 异步错误/FUNC 回读(1Hz 节流)
void requestFuncModeReadback(); // 异步回读 FUNC:MODE?(真实反馈驱动显示)
@@ -166,6 +167,8 @@ private:
//---- 控制线程私有(无需加锁)----
bool m_remoteApplied = false;
bool m_cfgApplied = false;
bool m_devCfgSyncDone = false; // 连接后是否已回读设备真实配置
double m_devCfgSyncTime = 0; // 最近一次配置回读时刻
double m_lastSentCurrent = 0;
double m_lastSentVolt = 0;
double m_lastSendTime = 0;
+8
View File
@@ -100,6 +100,14 @@ bool IT6000C::setCurrentLimitNeg(double amp) { return setDouble("CURR:LIM:NEG",
bool IT6000C::setPowerLimitPos(double watt) { return setDouble("POW:LIM", watt); }
bool IT6000C::setPowerLimitNeg(double watt) { return setDouble("POW:LIM:NEG", watt); }
bool IT6000C::queryCurrent(double& amp) { return queryDouble("CURR?", amp); }
bool IT6000C::queryVoltage(double& volt) { return queryDouble("VOLT?", volt); }
bool IT6000C::queryVoltageLimit(double& volt) { return queryDouble("VOLT:LIM?", volt); }
bool IT6000C::queryCurrentLimitPos(double& amp) { return queryDouble("CURR:LIM?", amp); }
bool IT6000C::queryCurrentLimitNeg(double& amp) { return queryDouble("CURR:LIM:NEG?", amp); }
bool IT6000C::queryPowerLimitPos(double& watt) { return queryDouble("POW:LIM?", watt); }
bool IT6000C::queryPowerLimitNeg(double& watt) { return queryDouble("POW:LIM:NEG?", watt); }
//============================================================================
// 测量
//============================================================================
+9
View File
@@ -62,6 +62,15 @@ public:
bool setPowerLimitPos(double watt); // POW:LIM
bool setPowerLimitNeg(double watt); // POW:LIM:NEG
// 配置回读(程序重启/重连后从设备同步真实状态)
bool queryCurrent(double& amp); // CURR?
bool queryVoltage(double& volt); // VOLT?
bool queryVoltageLimit(double& volt); // VOLT:LIM?
bool queryCurrentLimitPos(double& amp); // CURR:LIM?
bool queryCurrentLimitNeg(double& amp); // CURR:LIM:NEG?
bool queryPowerLimitPos(double& watt); // POW:LIM?
bool queryPowerLimitNeg(double& watt); // POW:LIM:NEG?
// 测量
bool measureVoltage(double& v); // MEAS:VOLT?(触发一次测量,较慢)
bool measureCurrent(double& i); // MEAS:CURR?
+7
View File
@@ -154,6 +154,7 @@ td.l,th.l{text-align:left;}
<div class="errline" id="measDiagErr" style="display:none"></div>
<div class="row"><span class="k">环路优先(回读)</span><span class="v" id="devFunc">-</span></div>
<div class="row"><span class="k">功能模式(回读)</span><span class="v" id="devFuncMode">-</span></div>
<div class="row"><span class="k">设备配置同步</span><span class="v" id="cfgSync">-</span></div>
<div class="row"><span class="k">切换结果</span><span class="v" id="funcModeFeed">-</span></div>
<div class="sub">功能模式切换(FUNC:MODE)</div>
<div class="inline">
@@ -419,6 +420,12 @@ function render(s){
? '<b style="font-size:15px">' + fmName + '</b>'
+ (dev.funcModeFixWarn ? ' <span class="badge err">非FIX</span>' : ' <span class="badge ok">FIX</span>')
: '<span class="dim">回读中...</span>';
const cfs = $('cfgSync');
if(dev.cfgSynced){
cfs.innerHTML = '<span style="color:var(--ok)">已同步</span>('+ageText(now - dev.cfgSyncTime)+')';
} else {
cfs.innerHTML = '<span style="color:var(--warn)">同步中…</span>';
}
const fmf = $('funcModeFeed');
if(dev.funcModeFeedValid){
const ok = dev.funcModeFeedOk ? '已发送成功' : '发送失败(未连接或超时)';