#include "fgc/HardwarePresence.h" #include #include namespace fgc { namespace { bool listed(const std::vector& 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 PresenceReport::missing() const { std::vector 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(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 formatPresence(const PresenceReport& report) { std::vector 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