#pragma once #include "fgc/ICameraSource.h" #include "fgc/IControlChannel.h" #include "fgc/IMotorController.h" #include #include namespace fgc { // The capture control loop, extracted from the old main() while-loop and // expressed against the I/O interfaces so it can be unit-tested with mocks. // // Each tick(): // 1. polls the control channel (updates control code / target heading, // echoes the code back as status), // 2. reads motor telemetry, // 3. runs the capture cycle: when the configured interval elapses, stops / // points the gimbal, then software-triggers the cameras. // // ControlCode 0 = automatic sweep ("p"); ControlCode 1 = drive to the // MQTT-supplied target heading ("kd"). class CaptureScheduler { public: // now_ms: monotonic millisecond clock; defaults to steady_clock. Injectable // for deterministic tests. CaptureScheduler(IMotorController& motor, ICameraSource& camera, IControlChannel& channel, double image_rate, std::function now_ms = {}); void setCaptureActive(bool active); // mirrors the old cam_started flag bool captureActive() const { return capture_active_; } void setImageRate(double rate); // images per second double imageRate() const { return image_rate_; } // Run one iteration of the control logic. void tick(); // Inspection (mainly for tests). int controlCode() const { return control_code_; } std::string targetHeading() const { return target_heading_; } private: long long elapsedMs() const; void resetTimer(); IMotorController& motor_; ICameraSource& camera_; IControlChannel& channel_; std::function now_ms_; double image_rate_; long long timer_start_ = 0; bool capture_active_ = false; int control_code_ = 0; std::string target_heading_ = "0"; bool trigger_after_stopping_ = false; }; } // namespace fgc