#pragma once #include "fgc/Config.h" // TestProfile #include "fgc/Geometry.h" #include "fgc/TestReport.h" #include #include #include #include #include #include namespace fgc { class IMotorController; class IImuSource; class IEnvSensor; class ICameraSource; struct HardwareConfig; struct DrivetrainConfig; // Leaf-test selection bitmask. Subsystem masks are unions of their leaves. enum TestLeaf : uint32_t { T_GIMBAL_HOMING = 1u << 0, T_GIMBAL_ENCODER = 1u << 1, T_GIMBAL_FRICTION = 1u << 2, T_GIMBAL_BACKLASH = 1u << 3, T_GIMBAL_BALANCE = 1u << 4, T_IMU_CONFIG = 1u << 5, T_IMU_HEALTH = 1u << 6, T_IMU_DRIFT = 1u << 7, T_HOST_THERMAL = 1u << 8, T_HOST_DISK = 1u << 9, T_HOST_MEMORY = 1u << 10, T_HOST_LOAD = 1u << 11, // `test hardware`: is this rig the one hardware.ini says it is? These are // preconditions rather than measurements - each either holds or does not, so // they carry pass/fail metrics rather than values to trend. T_HW_AXES = 1u << 12, T_HW_MOTOR = 1u << 13, T_HW_IMU = 1u << 14, T_HW_ENV = 1u << 15, T_HW_CAMERA = 1u << 16, }; constexpr uint32_t T_GIMBAL_ALL = T_GIMBAL_HOMING | T_GIMBAL_ENCODER | T_GIMBAL_FRICTION | T_GIMBAL_BACKLASH | T_GIMBAL_BALANCE; constexpr uint32_t T_IMU_ALL = T_IMU_CONFIG | T_IMU_HEALTH | T_IMU_DRIFT; constexpr uint32_t T_HOST_ALL = T_HOST_THERMAL | T_HOST_DISK | T_HOST_MEMORY | T_HOST_LOAD; constexpr uint32_t T_HW_ALL = T_HW_AXES | T_HW_MOTOR | T_HW_IMU | T_HW_ENV | T_HW_CAMERA; constexpr uint32_t T_ALL = T_GIMBAL_ALL | T_IMU_ALL | T_HOST_ALL | T_HW_ALL; // Which leaf masks need real motor / IMU hardware (used by the caller to guard). // Needs a HOMED gimbal, not merely a motor link: the caller refuses the whole // run when it is not ready. The `test hardware` leaves are deliberately NOT // here - they only read telemetry and DUMP, and they are the checks you want // on a tower that has not been homed yet. constexpr uint32_t T_NEEDS_MOTOR = T_GIMBAL_ALL; constexpr uint32_t T_NEEDS_IMU = T_IMU_ALL; // Resolve "subsystem [test]" tokens to a leaf mask. Empty subsystem => T_ALL; // a subsystem with empty test => that subsystem's leaves. Returns false and sets // `err` for an unknown subsystem/test token. bool resolveTestSelection(const std::string& subsystem, const std::string& test, uint32_t& mask, std::string& err); // Everything the `test hardware` leaves need beyond the motor and IMU the runner // already holds. Passed as one struct because the alternative is five more // constructor parameters, and because every field is optional: a leaf whose // input is absent reports "not run" rather than failing. struct HardwareTestContext { const HardwareConfig* declared = nullptr; // [Hardware], or null when undeclared const DrivetrainConfig* drive = nullptr; // [Motor.], for the current echo IEnvSensor* env = nullptr; ICameraSource* camera = nullptr; std::vector configured_cameras; // [Camera.] blocks in open order std::string i2c_device; // [Env] i2c_device, for the adapter-class check double r_sense_ohm = 0.0; // to recompute what CURRENT should have sent // sysfs nodes, overridable so the checks are exercisable off a tower. std::string usbfs_path = "/sys/module/usbcore/parameters/usbfs_memory_mb"; std::string lpm_path; // empty => discovered from the camera's USB port, or skipped int usbfs_min_mb = 1000; }; // Live progress for the activity strip (mirrors CalibProgress). struct TestProgress { bool running = false; std::string section; // current leaf id int step = 0; int total = 0; std::string phase; }; // Runs the selected hardware self-tests on a worker thread, off the control // loop. Mirrors PitchSetupRoutine's lifecycle: start()/cancel()/running(), live // progress(), and takeReport() once finished. The motor/imu/Logger interfaces are // thread-safe; the produced TestReport is handed back for the control thread to // compare against the baseline, write to disk, and surface in the UI. class TestRunner { public: TestRunner(IMotorController& motor, IImuSource* imu, Geometry geo, uint32_t selection, TestProfile profile, std::string disk_path, HardwareTestContext hw = {}); ~TestRunner(); bool start(); void cancel(); bool running() const { return running_.load(); } std::optional takeReport(); TestProgress progress() const; private: void run(); void setProgress(const std::string& section, int step, int total, const char* phase); bool cancelled() const { return cancel_.load(); } // Leaf modules — each appends one TestSection to `report_`. void testHoming(TestReport& r); void testEncoder(TestReport& r); void testFriction(TestReport& r); void testBacklash(TestReport& r); void testBalance(TestReport& r); void testImuConfig(TestReport& r); void testImuHealth(TestReport& r); void testImuDrift(TestReport& r); void testHost(TestReport& r); void testHwAxes(TestReport& r); void testHwMotor(TestReport& r); void testHwImu(TestReport& r); void testHwEnv(TestReport& r); void testHwCamera(TestReport& r); IMotorController& motor_; IImuSource* imu_; Geometry geo_; uint32_t selection_; TestProfile profile_; std::string disk_path_; HardwareTestContext hw_; std::thread thread_; std::atomic running_{false}; std::atomic cancel_{false}; mutable std::mutex result_mutex_; std::optional report_; mutable std::mutex progress_mutex_; TestProgress progress_; }; } // namespace fgc