#pragma once #include namespace fluidsim::port { // Ideal sensors and injectors; calibration parameters are not engine measurements. struct EcuConfig { enum class IdleControl { Feedback, FixedOpening }; IdleControl idle_control{IdleControl::Feedback}; struct IgnitionPoint { float rpm, degrees_btdc; }; std::vector ignition_map{{0, 10}, {15000, 40}}; float idle_rpm{1800.0f}; float idle_base_opening{0.08f}, idle_max_opening{0.25f}; float idle_kp{0.0001f}, idle_ki{0.00004f}; float throttle_time_constant{0.001f}, throttle_rate{1000.0f}; // seconds, full travel/second // Zero uses the opening calibration, preserving symmetric response. float throttle_closing_time_constant{}, throttle_closing_rate{}; float rev_limit_rpm{10000.0f}, rev_resume_rpm{9750.0f}; bool rev_limit_enabled{true}; bool rev_limit_fuel_cut_enabled{true}; float overrun_cut_rpm{3500.0f}, overrun_resume_rpm{2400.0f}; bool overrun_fuel_cut_enabled{true}; bool overrun_ignition_cut_enabled{false}; float idle_spark_gain{0.0f}, idle_minimum_advance{-5.0f}; // degrees/RPM, degrees BTDC float stoichiometric_afr{14.7f}, power_afr{12.8f}; }; class Ecu : public EcuConfig { public: void step(float dt, float pedal, float rpm); float fuel_for_air(float trapped_air_mass) const; float throttle_opening() const { return opening_; } float throttle_target() const { return target_; } float target_afr() const { return afr_; } float spark_advance() const { return spark_; } bool fuel_cut() const { return (limiter_ && rev_limit_fuel_cut_enabled) || (overrun_ && overrun_fuel_cut_enabled) || fault_; } bool ignition_cut() const { return limiter_ || (overrun_ && overrun_ignition_cut_enabled) || fault_; } bool rev_limited() const { return limiter_; } bool idle_active() const { return idle_; } bool sensor_fault() const { return fault_; } private: float filtered_rpm_{}; bool initialized_{}; float opening_{}, target_{}, integral_{}, afr_{14.7f}, spark_{10.0f}; bool limiter_{}, overrun_{}, fault_{}, idle_{}; }; } // namespace fluidsim::port