fwt_software/src/core/HardwareChecks.cpp

271 lines
10 KiB
C++

#include "fgc/HardwareChecks.h"
#include <algorithm>
#include <cctype>
#include <string>
namespace fgc {
namespace {
std::string trimmed(const std::string& s) {
const auto b = s.find_first_not_of(" \t\r\n");
if (b == std::string::npos) return "";
const auto e = s.find_last_not_of(" \t\r\n");
return s.substr(b, e - b + 1);
}
// A sysfs integer, or nullopt when the node was empty/unreadable/not a number.
bool asInt(const std::string& s, long& out) {
const std::string t = trimmed(s);
if (t.empty()) return false;
if (!std::all_of(t.begin(), t.end(), [](unsigned char c) { return std::isdigit(c); }))
return false;
try {
out = std::stol(t);
} catch (...) {
return false;
}
return true;
}
} // namespace
CheckResult checkUsbfsMemory(const std::string& sysfs_value, int min_mb) {
CheckResult r;
long mb = 0;
if (!asInt(sysfs_value, mb)) {
r.pass = false;
r.detail = "could not read usbfs_memory_mb -- expected a number at "
"/sys/module/usbcore/parameters/usbfs_memory_mb";
return r;
}
r.pass = mb >= min_mb;
if (r.pass) {
r.detail = "usbfs_memory_mb " + std::to_string(mb);
} else {
r.detail = "usbfs_memory_mb is " + std::to_string(mb) + ", needs >= " +
std::to_string(min_mb) +
" -- frames will stall under load. It resets on every reboot unless "
"usbcore.usbfs_memory_mb=" + std::to_string(min_mb) +
" is on the kernel cmdline (known-issues #19)";
}
return r;
}
CheckResult checkUsb3Lpm(const std::string& permit_value) {
CheckResult r;
const std::string v = trimmed(permit_value);
if (v.empty()) {
r.pass = false;
r.detail = "could not read usb3_lpm_permit for the camera's port";
return r;
}
r.pass = (v == "0");
r.detail = r.pass ? "USB3 LPM off on the camera port"
: "USB3 LPM is permitted (usb3_lpm_permit=" + v +
") -- write 0 and re-enumerate, and persist it with a udev rule; "
"the per-device usb3_hardware_lpm_u* nodes are read-only "
"(known-issues #19)";
return r;
}
CheckResult checkI2cFunctionality(unsigned long funcs) {
CheckResult r;
r.pass = (funcs & kI2cFuncI2c) != 0;
r.detail = r.pass ? "adapter supports real I2C transactions"
: "adapter is SMBus-only (I2C_FUNCS lacks I2C_FUNC_I2C) -- the SHT41 will "
"bind and then fail every read. Point [Env] i2c_device at a real I2C "
"adapter; 'i2cdetect -F <n>' is the manual check (known-issues #2)";
return r;
}
CheckResult checkNotLimpHome(const std::string& axis, long limp) {
CheckResult r;
if (limp < 0) {
r.ran = false;
r.detail = axis +
": firmware does not report the limp flag, so whether it is running "
"hardware.ini's values or its own fallback cannot be told apart here";
return r;
}
r.pass = (limp == 0);
r.detail = r.pass
? axis + ": running the host's declared values"
: axis +
": running LIMP-HOME values, which describe no real drivetrain -- slow "
"speeds, a reduced search current and a placeholder travel. Something "
"sent USE DEFAULTS, so hardware.ini is NOT in force however healthy the "
"axis looks. Restart the host to re-send it";
return r;
}
CheckResult checkDrivetrainAdopted(const std::string& axis, const DrivetrainExpectation& want,
const DrivetrainEcho& got) {
CheckResult r;
// Nothing declared for this axis means nothing was sent, and the firmware's
// compile-time defaults are the intended configuration. There is no skew to
// find.
if (!want.declared) {
r.ran = false;
r.detail = "no [Motor." + axis + "] current declared; firmware defaults are in force";
return r;
}
if (got.absent()) {
r.pass = false;
r.detail = axis +
": firmware did not echo gs/irun/irun_home -- it predates the CURRENT command, "
"so whatever hardware.ini asks for is NOT being applied. Re-flash it";
return r;
}
// A field is compared only when it was actually echoed. Comparing against -1
// would report "firmware has -1", which reads as a wrong value rather than
// as a field this firmware does not report - and sends someone to re-send a
// CURRENT that was never the problem.
int missing = 0;
auto compare = [&](const char* field, int wanted, long saw) {
if (saw < 0) { ++missing; return; }
if (saw == wanted) return;
r.pass = false;
if (!r.detail.empty()) r.detail += "; ";
r.detail += std::string(field) + " wanted " + std::to_string(wanted) + ", firmware has " +
std::to_string(saw);
};
compare("GLOBALSCALER", want.global_scaler, got.global_scaler);
compare("IRUN", want.run_irun, got.run_irun);
compare("homing IRUN", want.homing_irun, got.homing_irun);
if (missing > 0 && r.pass) {
r.pass = false;
r.detail = axis + ": firmware echoes only part of the current (" +
std::to_string(missing) +
" of 3 fields absent), so it cannot be confirmed -- it predates the full "
"CURRENT echo. Re-flash it";
return r;
}
if (r.pass) {
r.detail = axis + ": firmware has the declared current (GLOBALSCALER " +
std::to_string(want.global_scaler) + ", IRUN " + std::to_string(want.run_irun) +
", homing " + std::to_string(want.homing_irun) + ")";
} else {
r.detail = axis + ": " + r.detail +
" -- the CURRENT command did not land. A homing current that is not in force is "
"the case that damages the endstop";
}
return r;
}
CheckResult checkDeclaredValues(const std::string& axis, const DeclaredValue* values, int count) {
CheckResult r;
int checked = 0, absent = 0;
for (int i = 0; i < count; ++i) {
const DeclaredValue& v = values[i];
if (!v.want) continue; // nothing declared: nothing to be wrong about
if (v.got < 0) {
// The firmware does not echo this field at all, which is a version
// skew rather than a mismatch - and it means the value cannot be
// confirmed either way, so it must not read as a pass.
++absent;
continue;
}
++checked;
if (v.got != v.value) {
r.pass = false;
if (!r.detail.empty()) r.detail += "; ";
r.detail += std::string(v.name) + " wanted " + std::to_string(v.value) +
", firmware has " + std::to_string(v.got);
}
}
if (checked == 0 && absent == 0) {
r.ran = false;
r.detail = axis + ": nothing declared for this axis beyond the current";
return r;
}
if (absent > 0 && r.pass) {
r.pass = false;
r.detail = axis + ": the firmware does not echo " + std::to_string(absent) +
" declared value(s), so what it is running on cannot be confirmed -- it "
"predates the config handshake. Re-flash it";
return r;
}
if (!r.pass) {
r.detail = axis + ": " + r.detail +
" -- the firmware is not running what hardware.ini declares. A SET was lost, "
"or the axis never left NEEDCFG";
return r;
}
r.detail = axis + ": firmware holds all " + std::to_string(checked) + " declared values";
return r;
}
CheckResult checkPinsBuilt(const std::string& label, const DeclaredValue* values, int count) {
CheckResult r;
int checked = 0, absent = 0;
std::string diff;
for (int i = 0; i < count; ++i) {
const DeclaredValue& v = values[i];
if (!v.want) continue;
if (v.got < 0) { ++absent; continue; }
++checked;
if (v.got != v.value) {
if (!diff.empty()) diff += ", ";
diff += std::string(v.name) + " declared " + std::to_string(v.value) +
", built with " + std::to_string(v.got);
}
}
if (checked == 0 && absent == 0) {
r.ran = false;
r.detail = label + ": no [Pins] declared";
return r;
}
if (!diff.empty()) {
r.pass = false;
r.detail = label + ": " + diff +
" -- the firmware was built from different wiring than hardware.ini "
"declares. Rebuild and re-flash it";
return r;
}
if (absent > 0) {
r.pass = false;
r.detail = label + ": the firmware does not report the pins it was built with, so its "
"wiring cannot be confirmed -- it predates the pin echo. Re-flash it";
return r;
}
r.detail = label + ": built with the declared pins";
return r;
}
CheckResult checkTravelFitsPositive(const std::string& axis, long center, long half_travel) {
CheckResult r;
if (half_travel <= 0) {
r.ran = false;
r.detail = axis + ": not homed, so the travel range is not known yet";
return r;
}
const long lo = center - half_travel;
if (lo >= 0) {
r.detail = axis + ": travel is entirely positive (" + std::to_string(lo) + " .. " +
std::to_string(center + half_travel) + ")";
return r;
}
r.pass = false;
r.detail = axis + ": homed travel reaches " + std::to_string(lo) +
" -- below zero. [Motor] " + axis + "_center_count is " +
std::to_string(center) + " but half the travel is " +
std::to_string(half_travel) + ", so raise the centre above that (a round "
"number with headroom, e.g. " +
std::to_string(((half_travel / 1000000) + 1) * 1000000) +
"). The centre is arbitrary; only the span is physical";
return r;
}
} // namespace fgc