Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion firmware/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ or
- MAF filtering for better transient response
- Minimum injector pulse width setting
- Allow selection of DTC severity #625
- AC pressure switch & startup delay (#623, #660, #661, #662)
- AC pressure switch & startup delay (#623, #660, #661, #662, #663)
- Y axis override for VVT target
- Feature to skip initial trigger pulses for noisy triggers #634
- VVT open loop "hold" table #638
Expand Down
5 changes: 5 additions & 0 deletions firmware/controllers/actuators/ac_control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,8 @@ void AcController::onSlowCallback() {
bool AcController::isAcEnabled() const {
return m_acEnabled;
}

// accounts for A/C delay
bool AcController::isAcCompressorEnabled() const {
return acCompressorState;
}
1 change: 1 addition & 0 deletions firmware/controllers/actuators/ac_control.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class AcController : public ac_control_s, public EngineModule {
void onSlowCallback() override;

virtual bool isAcEnabled() const;
virtual bool isAcCompressorEnabled() const;
Copy link
Contributor Author

@nmschulte nmschulte Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAcCompressorState? this isn't actually used as acRelayAlternatorDutyAdder is going away


Timer timeSinceStateChange;

Expand Down
6 changes: 2 additions & 4 deletions firmware/controllers/actuators/idle_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ IIdleController::TargetInfo IdleController::getTargetRpm(float clt) {
// Why do we bump based on button not based on actual A/C relay state?
// Because AC output has a delay to allow idle bump to happen first, so that the airflow increase gets a head start on the load increase
// alternator duty cycle has a similar logic
targetRpmAcBump = (!hasAcPressure() || engine->module<AcController>().unmock().acPressureSwitchState)
&& engine->module<AcController>().unmock().acButtonState ? engineConfiguration->acIdleRpmBump : 0;
targetRpmAcBump = engine->module<AcController>().unmock().isAcEnabled() ? engineConfiguration->acIdleRpmBump : 0;

auto target = targetRpmByClt + targetRpmAcBump + luaAddRpm;

Expand Down Expand Up @@ -131,8 +130,7 @@ percent_t IdleController::getRunningOpenLoop(float rpm, float clt, SensorResult
openLoopBase = running;

// Now we bump it by the AC/fan amount if necessary
openLoopAcBump = (!hasAcPressure() || engine->module<AcController>().unmock().acPressureSwitchState)
&& engine->module<AcController>().unmock().acButtonState ? engineConfiguration->acIdleExtraOffset : 0;
openLoopAcBump = engine->module<AcController>().unmock().isAcEnabled() ? engineConfiguration->acIdleExtraOffset : 0;
openLoopFanBump =
(enginePins.fanRelay.getLogicValue() ? engineConfiguration->fan1ExtraIdle : 0)
+ (enginePins.fanRelay2.getLogicValue() ? engineConfiguration->fan2ExtraIdle : 0);
Expand Down
40 changes: 30 additions & 10 deletions unit_tests/tests/test_idle_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,16 @@ TEST(idle_v2, runningOpenLoopBasic) {

TEST(idle_v2, runningFanAcBump) {
EngineTestHelper eth(engine_type_e::TEST_ENGINE);
IdleController dut;
//IdleController dut;

engine->rpmCalculator.setRpmValue(1000);
Sensor::setMockValue(SensorType::Rpm, 1000);

engineConfiguration->manIdlePosition = 50;
engineConfiguration->maxAcRpm = 5000;
engineConfiguration->maxAcClt = 200;
engineConfiguration->maxAcTps = 100;

engineConfiguration->acIdleExtraOffset = 9;
engineConfiguration->fan1ExtraIdle = 7;
engineConfiguration->fan2ExtraIdle = 3;
Expand All @@ -193,12 +200,19 @@ TEST(idle_v2, runningFanAcBump) {
// Start with fan off
enginePins.fanRelay.setValue(0);

//auto dut = engine->module<IdleController>();

// Should be base position
EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(0, 10, 0));
EXPECT_FLOAT_EQ(50, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));

// Turn on A/C!
//engineConfiguration->acSwitch = Gpio::G1;
//setMockState(engineConfiguration->acSwitch, true);
engine->module<AcController>()->acButtonState = true;
EXPECT_FLOAT_EQ(50 + 9, dut.getRunningOpenLoop(0, 10, 0));
//engineConfiguration->acPressureSwitch = Gpio::G2;
engine->module<AcController>()->onSlowCallback();

EXPECT_FLOAT_EQ(50 + 9, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));
engine->module<AcController>()->acButtonState = false;

// Begin A/C Pressure Switch testing
Expand All @@ -207,40 +221,46 @@ TEST(idle_v2, runningFanAcBump) {
//setMockState(engineConfiguration->acSwitch, false);
//setMockState(engineConfiguration->acPressureSwitch, false);
engine->module<AcController>()->acPressureSwitchState = false;
EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(0, 10, 0));
engine->module<AcController>()->onSlowCallback();
EXPECT_FLOAT_EQ(50, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));

//setMockState(engineConfiguration->acSwitch, true);
engine->module<AcController>()->acButtonState = true;
EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(0, 10, 0));
engine->module<AcController>()->onSlowCallback();
EXPECT_FLOAT_EQ(50, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));

//setMockState(engineConfiguration->acPressureSwitch, true);
engine->module<AcController>()->acPressureSwitchState = true;
EXPECT_FLOAT_EQ(50 + 9, dut.getRunningOpenLoop(0, 10, 0));
engine->module<AcController>()->onSlowCallback();
EXPECT_FLOAT_EQ(50 + 9, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));

//setMockState(engineConfiguration->acSwitch, false);
engine->module<AcController>()->acButtonState = false;
EXPECT_FLOAT_EQ(50, dut.getRunningOpenLoop(0, 10, 0));
engine->module<AcController>()->onSlowCallback();
EXPECT_FLOAT_EQ(50, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));

//engineConfiguration->acSwitch = Gpio::Unassigned;
//setMockState(engineConfiguration->acPressureSwitch, false);
engineConfiguration->acPressureSwitch = Gpio::Unassigned;
engine->module<AcController>()->acPressureSwitchState = false;
engine->module<AcController>()->onSlowCallback();
// End A/C Pressure Switch testing

// Turn the fan on!
enginePins.fanRelay.setValue(1);
EXPECT_FLOAT_EQ(50 + 7, dut.getRunningOpenLoop(0, 10, 0));
EXPECT_FLOAT_EQ(50 + 7, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));
enginePins.fanRelay.setValue(0);

// Turn on the other fan!
enginePins.fanRelay2.setValue(1);
EXPECT_FLOAT_EQ(50 + 3, dut.getRunningOpenLoop(0, 10, 0));
EXPECT_FLOAT_EQ(50 + 3, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));

// Turn on everything!
engine->module<AcController>()->acButtonState = true;
enginePins.fanRelay.setValue(1);
enginePins.fanRelay2.setValue(1);
EXPECT_FLOAT_EQ(50 + 9 + 7 + 3, dut.getRunningOpenLoop(0, 10, 0));
engine->module<AcController>()->onSlowCallback();
EXPECT_FLOAT_EQ(50 + 9 + 7 + 3, engine->module<IdleController>()->getRunningOpenLoop(0, 10, 0));
}

TEST(idle_v2, runningOpenLoopTpsTaper) {
Expand Down
Loading