Commit d2aa504e by 树林

fix: 网关取消内置模拟数据,仅上报真实 Modbus 读数

连不上或读失败的点标记 bad,不再生成假工程值;写控也不再本机假成功。

Co-authored-by: Cursor <cursoragent@cursor.com>
parent f51a30c8
...@@ -113,7 +113,7 @@ struct AppState { ...@@ -113,7 +113,7 @@ struct AppState {
std::map<std::string, PointValue> values; // key = point id std::map<std::string, PointValue> values; // key = point id
std::map<std::string, bool> hostOk; std::map<std::string, bool> hostOk;
std::map<std::string, int> hostLatencyMs; std::map<std::string, int> hostLatencyMs;
std::string version = "0.3.7-all-fc03"; std::string version = "0.3.8-real-only";
std::map<std::string, Session> sessions; std::map<std::string, Session> sessions;
bool pollRunning = false; bool pollRunning = false;
}; };
...@@ -595,144 +595,103 @@ void pollOnce() { ...@@ -595,144 +595,103 @@ void pollOnce() {
bool anyOk = false; bool anyOk = false;
int lastLat = 0; int lastLat = 0;
int unit = mine.empty() ? 1 : mine.front().unitId; int unit = mine.empty() ? 1 : mine.front().unitId;
// 本机联调:127.0.0.1 上 Windows 模拟端口异常时直接走内置公式(与 Python 模拟器一致)
const bool forceBuiltin =
h.host == "127.0.0.1" || h.host == "localhost" || h.host == "::1";
std::map<int, uint16_t> flat; std::map<int, uint16_t> flat;
if (!forceBuiltin) { // 只采真实 Modbus;连不上 / 读失败不造模拟数
if (mine.empty()) { if (mine.empty()) {
std::vector<uint16_t> tmp; std::vector<uint16_t> tmp;
int lat = 0; int lat = 0;
const int probeLogical = 1; const int probeLogical = 1;
const int probePdu = std::max(0, probeLogical + addrOffset); const int probePdu = std::max(0, probeLogical + addrOffset);
bool ok = modbus_tcp::readHolding(h.host, h.port, unit, probePdu, 1, tmp, &lat); bool ok = modbus_tcp::readHolding(h.host, h.port, unit, probePdu, 1, tmp, &lat);
hostOk[h.id] = ok; hostOk[h.id] = ok;
hostLatency[h.id] = lat; hostLatency[h.id] = lat;
continue; continue;
}
// flat 按点表逻辑地址索引;读请求地址 = 逻辑地址 + offset
int minAddr = mine.front().address;
int maxEnd = mine.front().address + wordCountForType(mine.front().dataType);
for (const auto& p : mine) {
minAddr = std::min(minAddr, p.address);
maxEnd = std::max(maxEnd, p.address + wordCountForType(p.dataType));
}
if (minAddr < 0) minAddr = 0;
std::vector<int> addrs;
addrs.reserve(mine.size());
for (const auto& p : mine) addrs.push_back(p.address);
std::sort(addrs.begin(), addrs.end());
addrs.erase(std::unique(addrs.begin(), addrs.end()), addrs.end());
int chunksOk = 0, chunksFail = 0;
for (size_t ai = 0; ai < addrs.size();) {
int start = addrs[ai];
int end = start + 1;
size_t aj = ai + 1;
while (aj < addrs.size() && addrs[aj] < start + chunk && addrs[aj] <= end + 8) {
end = addrs[aj] + 1;
++aj;
} }
// flat 仍按点表逻辑地址索引;读请求地址 = 逻辑地址 + offset(默认 -1)
int minAddr = mine.front().address;
int maxEnd = mine.front().address + wordCountForType(mine.front().dataType);
for (const auto& p : mine) { for (const auto& p : mine) {
minAddr = std::min(minAddr, p.address); if (p.address >= start && p.address < start + chunk) {
maxEnd = std::max(maxEnd, p.address + wordCountForType(p.dataType)); end = std::max(end, p.address + wordCountForType(p.dataType));
}
if (minAddr < 0) minAddr = 0;
// 按点位地址分段读,避免整段 >2000 时截断导致高地址写控点永远无值
std::vector<int> addrs;
addrs.reserve(mine.size());
for (const auto& p : mine) addrs.push_back(p.address);
std::sort(addrs.begin(), addrs.end());
addrs.erase(std::unique(addrs.begin(), addrs.end()), addrs.end());
int chunksOk = 0, chunksFail = 0;
for (size_t ai = 0; ai < addrs.size();) {
int start = addrs[ai];
int end = start + 1;
size_t aj = ai + 1;
while (aj < addrs.size() && addrs[aj] < start + chunk && addrs[aj] <= end + 8) {
end = addrs[aj] + 1;
++aj;
} }
// 覆盖该段内多字类型 }
for (const auto& p : mine) { int cnt = std::min(chunk, std::max(1, end - start));
if (p.address >= start && p.address < start + chunk) { const int pduStart = std::max(0, start + addrOffset);
end = std::max(end, p.address + wordCountForType(p.dataType)); std::vector<uint16_t> regs;
} int lat = 0;
} if (modbus_tcp::readHolding(h.host, h.port, unit, pduStart, cnt, regs, &lat) &&
int cnt = std::min(chunk, std::max(1, end - start)); static_cast<int>(regs.size()) == cnt) {
const int pduStart = std::max(0, start + addrOffset); anyOk = true;
std::vector<uint16_t> regs; lastLat = lat;
int lat = 0; ++chunksOk;
if (modbus_tcp::readHolding(h.host, h.port, unit, pduStart, cnt, regs, &lat) && for (int i = 0; i < cnt; ++i) flat[start + i] = regs[static_cast<size_t>(i)];
static_cast<int>(regs.size()) == cnt) { } else {
anyOk = true; ++chunksFail;
lastLat = lat; for (size_t k = ai; k < aj; ++k) {
++chunksOk; const int one = addrs[k];
for (int i = 0; i < cnt; ++i) flat[start + i] = regs[static_cast<size_t>(i)]; const int onePdu = std::max(0, one + addrOffset);
} else { std::vector<uint16_t> oneReg;
++chunksFail; int oneLat = 0;
// 失败则逐点单读,尽量拿到写控点状态 if (modbus_tcp::readHolding(h.host, h.port, unit, onePdu, 1, oneReg, &oneLat) &&
for (size_t k = ai; k < aj; ++k) { oneReg.size() == 1) {
const int one = addrs[k]; anyOk = true;
const int onePdu = std::max(0, one + addrOffset); lastLat = oneLat;
std::vector<uint16_t> oneReg; flat[one] = oneReg[0];
int oneLat = 0; ++chunksOk;
if (modbus_tcp::readHolding(h.host, h.port, unit, onePdu, 1, oneReg, &oneLat) &&
oneReg.size() == 1) {
anyOk = true;
lastLat = oneLat;
flat[one] = oneReg[0];
++chunksOk;
}
} }
} }
ai = aj > ai ? aj : ai + 1;
} }
hostOk[h.id] = anyOk; ai = aj > ai ? aj : ai + 1;
hostLatency[h.id] = lastLat; }
std::cout << "[gateway] host " << h.id << " range=[" << minAddr << "," << maxEnd hostOk[h.id] = anyOk;
<< ") pduOffset=" << addrOffset << " chunksOk=" << chunksOk hostLatency[h.id] = lastLat;
<< " fail=" << chunksFail << " flat=" << flat.size() << "\n" std::cout << "[gateway] host " << h.id << " range=[" << minAddr << "," << maxEnd
<< ") pduOffset=" << addrOffset << " chunksOk=" << chunksOk
<< " fail=" << chunksFail << " flat=" << flat.size() << "\n"
<< std::flush;
if (!anyOk) {
std::cout << "[gateway] host " << h.id << " Modbus unreachable → no simulated values\n"
<< std::flush; << std::flush;
} }
// 联调兜底:本机或 Modbus 不可达时,按与 Python 模拟器相同规则生成工程值
const bool useBuiltin = forceBuiltin || !anyOk;
if (useBuiltin) {
std::cout << "[gateway] host " << h.id << " Modbus unreachable → builtin sim\n" << std::flush;
}
const double tsec = std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch())
.count();
size_t idx = 0;
for (const auto& p : mine) { for (const auto& p : mine) {
PointValue v; PointValue v;
v.ts = ts; v.ts = ts;
bool fillBuiltin = useBuiltin; v.quality = "bad";
if (!fillBuiltin) { v.raw = 0;
const int n = wordCountForType(p.dataType); v.eng = 0;
std::vector<uint16_t> regs(static_cast<size_t>(n), 0); const int n = wordCountForType(p.dataType);
bool complete = true; std::vector<uint16_t> regs(static_cast<size_t>(n), 0);
for (int i = 0; i < n; ++i) { bool complete = true;
auto it = flat.find(p.address + i); for (int i = 0; i < n; ++i) {
if (it == flat.end()) { auto it = flat.find(p.address + i);
complete = false; if (it == flat.end()) {
break; complete = false;
} break;
regs[static_cast<size_t>(i)] = it->second;
}
if (complete) decodePoint(p, regs, 0, v, ts);
else fillBuiltin = true; // 单点 Holding 缺字时改走内置公式,避免整表大量 bad
}
if (fillBuiltin) {
const std::string n = p.name + p.itemId;
double base = 10.0 + (p.address % 100) * 0.1;
if (n.find("冰箱") != std::string::npos || n.find("冷") != std::string::npos) {
if (n.find("温") != std::string::npos) base = -20.0 + (p.address % 7) * 0.3;
else if (n.find("湿") != std::string::npos) base = 45.0 + (p.address % 20) * 0.2;
} else if (n.find("温") != std::string::npos || n.find("Temp") != std::string::npos) {
base = 22.0 + (p.address % 10) * 0.15;
} else if (n.find("湿") != std::string::npos || n.find("Humid") != std::string::npos) {
base = 45.0 + (p.address % 20) * 0.2;
} else if (n.find("压差") != std::string::npos) {
base = -15.0 + (p.address % 8) * 0.5;
}
const double wave = std::sin(tsec * 0.15 + idx * 0.17 + p.address * 0.01);
double eng = base * (1.0 + 0.03 * wave);
std::string dt = p.dataType;
for (char& c : dt) c = static_cast<char>(::tolower(c));
if (dt.find("bool") != std::string::npos || n.find("报警") != std::string::npos ||
n.find("故障") != std::string::npos) {
eng = ((static_cast<int>(tsec / 5) + static_cast<int>(idx)) % 4) ? 1.0 : 0.0;
} }
// 反推 raw,使 eng≈raw*scale(与模拟器/网关解码一致) regs[static_cast<size_t>(i)] = it->second;
double scale = p.scale != 0 ? p.scale : 1.0;
v.raw = (scale != 0) ? eng / scale : eng;
v.eng = eng;
v.quality = "good";
hostOk[h.id] = true;
} }
if (complete) decodePoint(p, regs, 0, v, ts);
nextValues[p.id.empty() ? p.itemId : p.id] = v; nextValues[p.id.empty() ? p.itemId : p.id] = v;
++idx;
} }
} }
...@@ -1127,8 +1086,6 @@ int main(int argc, char** argv) { ...@@ -1127,8 +1086,6 @@ int main(int argc, char** argv) {
// 现场禁止线圈功能码:原 0X/-F5/Coil/Bool 一律走 Holding FC06(写 0/1) // 现场禁止线圈功能码:原 0X/-F5/Coil/Bool 一律走 Holding FC06(写 0/1)
const bool asBool = reg.find("0X") != std::string::npos || reg.find("-F5") != std::string::npos || const bool asBool = reg.find("0X") != std::string::npos || reg.find("-F5") != std::string::npos ||
reg.find("COIL") != std::string::npos || dtype.find("bool") != std::string::npos; reg.find("COIL") != std::string::npos || dtype.find("bool") != std::string::npos;
const bool localhost =
host.host == "127.0.0.1" || host.host == "localhost" || host.host == "::1";
bool ok = false; bool ok = false;
std::string detail; std::string detail;
...@@ -1141,13 +1098,9 @@ int main(int argc, char** argv) { ...@@ -1141,13 +1098,9 @@ int main(int argc, char** argv) {
double raw = (value - point.bias) / scale; double raw = (value - point.bias) / scale;
regVal = static_cast<uint16_t>(static_cast<int32_t>(std::lround(raw)) & 0xFFFF); regVal = static_cast<uint16_t>(static_cast<int32_t>(std::lround(raw)) & 0xFFFF);
} }
// 仅真实 FC06;失败不本地假写
ok = modbus_tcp::writeSingleRegister(host.host, host.port, point.unitId, pduAddr, regVal); ok = modbus_tcp::writeSingleRegister(host.host, host.port, point.unitId, pduAddr, regVal);
detail = ok ? "FC06 ok" : "FC06 failed"; detail = ok ? "FC06 ok" : "FC06 failed";
if (!ok && localhost) {
// 本机联调兜底:接受写入并记入值缓存,便于 UI 回读
ok = true;
detail = "accepted (builtin sim)";
}
if (ok) { if (ok) {
PointValue v; PointValue v;
v.eng = value; v.eng = value;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment