#pragma once #include "fgc/ICameraSource.h" #include "fgc/Logger.h" #include #include #include namespace fgc { // Synthetic camera for development without hardware. On trigger() it generates // a small RGB gradient frame (varying with time so successive captures differ) // and delivers it to the frame callback. // // It also implements the deliberate-acquisition API with a crude sensor model // (brightness proportional to exposure x gain), so the quality gate's whole // shoot -> judge -> correct loop can be exercised end to end with --mock-camera. class MockCameraSource : public ICameraSource { public: explicit MockCameraSource(int count = 1, uint32_t width = 640, uint32_t height = 480) : count_(count), width_(width), height_(height) {} void open() override { LOG_INFO << "[mock] camera opened (" << count_ << ")"; } void close() override {} void start() override { started_ = true; LOG_INFO << "[mock] camera acquisition started"; } void stop() override { started_ = false; } bool trigger() override { if (!callback_) return false; LOG_TRACE_CAT(LogCat::Camera) << "TX trigger cam0 (mock)"; Frame f = synth(); LOG_TRACE_CAT(LogCat::Camera) << "RX frame cam0 " << width_ << 'x' << height_ << ' ' << f.data.size() << "B (mock)"; ++frames_; callback_(f); return true; } void setFrameCallback(FrameCallback cb) override { callback_ = std::move(cb); } int cameraCount() const override { return count_; } // ---- Deliberate acquisition, for the quality gate ---- bool acquireFrame(Frame& out, int /*timeout_ms*/) override { out = synth(); ++frames_; LOG_TRACE_CAT(LogCat::Camera) << "RX frame cam0 " << width_ << 'x' << height_ << " exp=" << exposure_us_ << "us gain=" << gain_db_ << "dB (mock)"; return true; } bool setExposure(double us) override { exposure_us_ = us; return true; } bool setGain(double db) override { gain_db_ = db; return true; } bool setAutoExposureGain(bool on) override { auto_on_ = on; return true; } double currentGain() override { return gain_db_; } // Synthetic device info so the expanded camera view renders (and is testable) // without hardware. Non-hardware fields (temperature, exposure...) stay at 0. std::vector deviceInfo() override { std::vector out; for (int i = 0; i < count_; ++i) { CameraDeviceInfo d; d.id = "MOCK" + std::to_string(i); d.model = "MockCamera"; d.serial = "SIM-0000"; d.streaming = started_; d.width = width_; d.height = height_; d.payload_bytes = static_cast(width_) * height_ * 3; d.actual_fps = 1.0; d.frames_delivered = (i == 0) ? frames_ : 0; // only cam0 produces frames out.push_back(std::move(d)); } return out; } private: // A gradient frame whose overall brightness follows exposure x gain, so the // quality gate sees its corrections take effect. The pattern varies with time // so successive captures differ. Frame synth() const { Frame f; f.width = width_; f.height = height_; f.channels = 3; f.cam_id = 0; f.timestamp_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); f.data.resize(static_cast(width_) * height_ * 3); // Exposure/gain -> brightness. Calibrated so the default 5000 us at 0 dB // lands near the policy's default target mean, making a mock run converge // the way a real one should. const double lin = exposure_us_ * std::pow(10.0, gain_db_ / 20.0); const double level = auto_on_ ? 110.0 : lin * 0.022; const uint8_t phase = static_cast(f.timestamp_ms); for (uint32_t y = 0; y < height_; ++y) { for (uint32_t x = 0; x < width_; ++x) { size_t i = (static_cast(y) * width_ + x) * 3; // Zero-mean texture around `level` so there is real detail for the // sharpness metric without shifting the mean. The cast matters: // (x+y+phase)%3 is unsigned, so subtracting 1 from the zero case // would wrap to UINT_MAX and clip a third of the frame to white. const double tex = 20.0 * (static_cast((x + y + phase) % 3) - 1); auto clamp8 = [](double v) { return static_cast(v < 0 ? 0 : (v > 255 ? 255 : v)); }; f.data[i + 0] = clamp8(level + tex); f.data[i + 1] = clamp8(level + tex * 0.8); f.data[i + 2] = clamp8(level + tex * 0.6); } } return f; } int count_; uint32_t width_; uint32_t height_; bool started_ = false; long long frames_ = 0; double exposure_us_ = 5000.0; double gain_db_ = 0.0; bool auto_on_ = true; FrameCallback callback_; }; } // namespace fgc