Commit d2aa504e by 树林

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

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

Co-authored-by: Cursor <cursoragent@cursor.com>
parent f51a30c8
......@@ -113,7 +113,7 @@ struct AppState {
std::map<std::string, PointValue> values; // key = point id
std::map<std::string, bool> hostOk;
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;
bool pollRunning = false;
};
......@@ -595,144 +595,103 @@ void pollOnce() {
bool anyOk = false;
int lastLat = 0;
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;
if (!forceBuiltin) {
if (mine.empty()) {
std::vector<uint16_t> tmp;
int lat = 0;
const int probeLogical = 1;
const int probePdu = std::max(0, probeLogical + addrOffset);
bool ok = modbus_tcp::readHolding(h.host, h.port, unit, probePdu, 1, tmp, &lat);
hostOk[h.id] = ok;
hostLatency[h.id] = lat;
continue;
// 只采真实 Modbus;连不上 / 读失败不造模拟数
if (mine.empty()) {
std::vector<uint16_t> tmp;
int lat = 0;
const int probeLogical = 1;
const int probePdu = std::max(0, probeLogical + addrOffset);
bool ok = modbus_tcp::readHolding(h.host, h.port, unit, probePdu, 1, tmp, &lat);
hostOk[h.id] = ok;
hostLatency[h.id] = lat;
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) {
minAddr = std::min(minAddr, p.address);
maxEnd = std::max(maxEnd, 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;
if (p.address >= start && p.address < start + chunk) {
end = std::max(end, p.address + wordCountForType(p.dataType));
}
// 覆盖该段内多字类型
for (const auto& p : mine) {
if (p.address >= start && p.address < start + chunk) {
end = std::max(end, p.address + wordCountForType(p.dataType));
}
}
int cnt = std::min(chunk, std::max(1, end - start));
const int pduStart = std::max(0, start + addrOffset);
std::vector<uint16_t> regs;
int lat = 0;
if (modbus_tcp::readHolding(h.host, h.port, unit, pduStart, cnt, regs, &lat) &&
static_cast<int>(regs.size()) == cnt) {
anyOk = true;
lastLat = lat;
++chunksOk;
for (int i = 0; i < cnt; ++i) flat[start + i] = regs[static_cast<size_t>(i)];
} else {
++chunksFail;
// 失败则逐点单读,尽量拿到写控点状态
for (size_t k = ai; k < aj; ++k) {
const int one = addrs[k];
const int onePdu = std::max(0, one + addrOffset);
std::vector<uint16_t> oneReg;
int oneLat = 0;
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;
}
}
int cnt = std::min(chunk, std::max(1, end - start));
const int pduStart = std::max(0, start + addrOffset);
std::vector<uint16_t> regs;
int lat = 0;
if (modbus_tcp::readHolding(h.host, h.port, unit, pduStart, cnt, regs, &lat) &&
static_cast<int>(regs.size()) == cnt) {
anyOk = true;
lastLat = lat;
++chunksOk;
for (int i = 0; i < cnt; ++i) flat[start + i] = regs[static_cast<size_t>(i)];
} else {
++chunksFail;
for (size_t k = ai; k < aj; ++k) {
const int one = addrs[k];
const int onePdu = std::max(0, one + addrOffset);
std::vector<uint16_t> oneReg;
int oneLat = 0;
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;
hostLatency[h.id] = lastLat;
std::cout << "[gateway] host " << h.id << " range=[" << minAddr << "," << maxEnd
<< ") pduOffset=" << addrOffset << " chunksOk=" << chunksOk
<< " fail=" << chunksFail << " flat=" << flat.size() << "\n"
ai = aj > ai ? aj : ai + 1;
}
hostOk[h.id] = anyOk;
hostLatency[h.id] = lastLat;
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;
}
// 联调兜底:本机或 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) {
PointValue v;
v.ts = ts;
bool fillBuiltin = useBuiltin;
if (!fillBuiltin) {
const int n = wordCountForType(p.dataType);
std::vector<uint16_t> regs(static_cast<size_t>(n), 0);
bool complete = true;
for (int i = 0; i < n; ++i) {
auto it = flat.find(p.address + i);
if (it == flat.end()) {
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;
v.quality = "bad";
v.raw = 0;
v.eng = 0;
const int n = wordCountForType(p.dataType);
std::vector<uint16_t> regs(static_cast<size_t>(n), 0);
bool complete = true;
for (int i = 0; i < n; ++i) {
auto it = flat.find(p.address + i);
if (it == flat.end()) {
complete = false;
break;
}
// 反推 raw,使 eng≈raw*scale(与模拟器/网关解码一致)
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;
regs[static_cast<size_t>(i)] = it->second;
}
if (complete) decodePoint(p, regs, 0, v, ts);
nextValues[p.id.empty() ? p.itemId : p.id] = v;
++idx;
}
}
......@@ -1127,8 +1086,6 @@ int main(int argc, char** argv) {
// 现场禁止线圈功能码:原 0X/-F5/Coil/Bool 一律走 Holding FC06(写 0/1)
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;
const bool localhost =
host.host == "127.0.0.1" || host.host == "localhost" || host.host == "::1";
bool ok = false;
std::string detail;
......@@ -1141,13 +1098,9 @@ int main(int argc, char** argv) {
double raw = (value - point.bias) / scale;
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);
detail = ok ? "FC06 ok" : "FC06 failed";
if (!ok && localhost) {
// 本机联调兜底:接受写入并记入值缓存,便于 UI 回读
ok = true;
detail = "accepted (builtin sim)";
}
if (ok) {
PointValue v;
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