234 lines
8.8 KiB
C++
234 lines
8.8 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 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
|