175 lines
7.6 KiB
C++
175 lines
7.6 KiB
C++
#include "fgc/HardwarePresence.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
namespace fgc {
|
|
namespace {
|
|
|
|
bool listed(const std::vector<std::string>& v, const std::string& name) {
|
|
return std::find(v.begin(), v.end(), name) != v.end();
|
|
}
|
|
|
|
// A sensor slot is declared when it names a model. Empty means the file said
|
|
// nothing; "none" means it said "deliberately not fitted" - and the difference
|
|
// matters, because only the second one makes a found sensor a stale-file warning.
|
|
bool names(const std::string& slot) { return !slot.empty() && slot != "none"; }
|
|
bool stated(const std::string& slot) { return !slot.empty(); }
|
|
|
|
void missing(PresenceReport& r, std::string subsystem, std::string detail) {
|
|
r.findings.push_back({PresenceIssue::MissingDeclared, std::move(subsystem), std::move(detail)});
|
|
}
|
|
|
|
void undeclared(PresenceReport& r, std::string subsystem, std::string detail) {
|
|
r.findings.push_back({PresenceIssue::UndeclaredFound, std::move(subsystem), std::move(detail)});
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool PresenceReport::anyMissing() const {
|
|
return std::any_of(findings.begin(), findings.end(),
|
|
[](const PresenceFinding& f) { return f.issue == PresenceIssue::MissingDeclared; });
|
|
}
|
|
|
|
bool PresenceReport::anyUndeclared() const {
|
|
return std::any_of(findings.begin(), findings.end(),
|
|
[](const PresenceFinding& f) { return f.issue == PresenceIssue::UndeclaredFound; });
|
|
}
|
|
|
|
std::vector<std::string> PresenceReport::missing() const {
|
|
std::vector<std::string> out;
|
|
for (const auto& f : findings)
|
|
if (f.issue == PresenceIssue::MissingDeclared) out.push_back(f.subsystem);
|
|
return out;
|
|
}
|
|
|
|
PresenceReport reconcilePresence(const HardwareConfig& declared, const FoundHardware& found) {
|
|
PresenceReport r;
|
|
|
|
// Nothing was read, so there is nothing to contradict. Every un-migrated
|
|
// tower lands here and must behave exactly as it did before.
|
|
if (!declared.declared) return r;
|
|
r.reconciled = true;
|
|
|
|
// ---- axes ----
|
|
// An empty axes list states nothing, matching hasAxis()'s "answers true for
|
|
// everything". Reconciling it would turn silence into a fault.
|
|
if (!declared.axes.empty()) {
|
|
if (!found.axes_known) {
|
|
missing(r, "motor telemetry",
|
|
"no telemetry arrived, so the declared axes (" + declared.axesLabel() +
|
|
") could not be confirmed -- check [Serial] device and that the MCU is "
|
|
"flashed and enumerating");
|
|
} else {
|
|
for (const auto& a : declared.axes)
|
|
if (!listed(found.axes, a))
|
|
missing(r, "axis " + a,
|
|
"declared in " + declared.source_path +
|
|
" but absent from telemetry -- the driver is not answering, or "
|
|
"the firmware was built for fewer axes than this rig has");
|
|
for (const auto& a : found.axes)
|
|
if (!listed(declared.axes, a))
|
|
undeclared(r, "axis " + a,
|
|
"present in telemetry but not in [Hardware] axes -- add it, or the "
|
|
"declaration is describing a different rig");
|
|
}
|
|
}
|
|
|
|
// ---- IMU ----
|
|
if (names(declared.imu) && !found.imu)
|
|
missing(r, "imu",
|
|
"declared as '" + declared.imu +
|
|
"' but no IMU came up -- check [IMU] device; declaring it here is what "
|
|
"enables it");
|
|
if (stated(declared.imu) && !names(declared.imu) && found.imu)
|
|
undeclared(r, "imu", "running, but [Hardware] imu says none -- the declaration is stale");
|
|
|
|
// ---- env sensor ----
|
|
if (names(declared.env) && !found.env)
|
|
missing(r, "env",
|
|
"declared as '" + declared.env + "' but no ambient sensor came up -- " +
|
|
(declared.env == "dht11"
|
|
? std::string("the motor MCU reads it: was the firmware built with "
|
|
"[Pins] dht?")
|
|
: std::string("check [Env] i2c_device, and that it is a real I2C "
|
|
"adapter rather than SMBus-only")));
|
|
if (stated(declared.env) && !names(declared.env) && found.env)
|
|
undeclared(r, "env", "running, but [Hardware] env says none -- the declaration is stale");
|
|
|
|
// ---- cameras ----
|
|
if (!declared.cameras.empty()) {
|
|
for (const auto& c : declared.cameras)
|
|
if (!listed(found.cameras, c))
|
|
missing(r, "camera " + c,
|
|
"declared in " + declared.source_path +
|
|
" but not found -- check the [Camera." + c +
|
|
"] block is enabled and the device is on the bus");
|
|
for (const auto& c : found.cameras)
|
|
if (!listed(declared.cameras, c))
|
|
undeclared(r, "camera " + c,
|
|
"active but not in [Hardware] cameras -- add it, or the declaration is "
|
|
"describing a different rig");
|
|
|
|
// Configured and declared is not the same as present on the bus. The
|
|
// source counts what it opened without saying which, so the shortfall is
|
|
// reported as a number and `test hardware camera` names the device.
|
|
const int want = static_cast<int>(found.cameras.size());
|
|
if (found.cameras_opened >= 0 && found.cameras_opened < want)
|
|
missing(r, "cameras",
|
|
std::to_string(want) + " configured, only " +
|
|
std::to_string(found.cameras_opened) +
|
|
" opened -- one is not on the bus; 'test hardware camera' names which");
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
PresenceVerdict presenceVerdict(const PresenceReport& report, bool strict, bool degraded) {
|
|
PresenceVerdict v;
|
|
if (report.findings.empty()) return v; // Proceed, nothing to say
|
|
|
|
if (!report.anyMissing()) {
|
|
// Only stale-file warnings. Never fatal: the rig has MORE than was
|
|
// declared, which is a documentation fault, not a broken tower.
|
|
v.action = PresenceAction::WarnOnly;
|
|
v.reason = "hardware.ini is out of date with this rig";
|
|
return v;
|
|
}
|
|
|
|
std::string gone;
|
|
for (const auto& m : report.missing()) gone += (gone.empty() ? "" : ", ") + m;
|
|
|
|
if (!strict) {
|
|
v.action = PresenceAction::WarnOnly;
|
|
v.reason = "declared hardware is missing (" + gone +
|
|
"); continuing because [Features] strict_presence is off -- set it once this "
|
|
"tower's hardware.ini is known good";
|
|
return v;
|
|
}
|
|
if (degraded) {
|
|
// Loudly, per the plan: this is field triage, and it must not become the
|
|
// way a tower is normally run.
|
|
v.action = PresenceAction::WarnOnly;
|
|
v.reason = "RUNNING DEGRADED BY REQUEST: declared hardware is missing (" + gone +
|
|
") and --degraded overrode the refusal -- this tower is not doing the job it "
|
|
"is declared to do";
|
|
return v;
|
|
}
|
|
v.action = PresenceAction::Refuse;
|
|
v.reason = "declared hardware is missing: " + gone +
|
|
" -- fix the rig, correct " +
|
|
"hardware.ini, or pass --degraded to start anyway";
|
|
return v;
|
|
}
|
|
|
|
std::vector<std::string> formatPresence(const PresenceReport& report) {
|
|
std::vector<std::string> out;
|
|
for (const auto& f : report.findings)
|
|
if (f.issue == PresenceIssue::MissingDeclared) out.push_back(f.subsystem + ": " + f.detail);
|
|
for (const auto& f : report.findings)
|
|
if (f.issue == PresenceIssue::UndeclaredFound) out.push_back(f.subsystem + ": " + f.detail);
|
|
return out;
|
|
}
|
|
|
|
} // namespace fgc
|