diff --git a/src/EnergyPlus/DataRuntimeLanguage.hh b/src/EnergyPlus/DataRuntimeLanguage.hh index 5bfc09c0425..906fa15b1e8 100644 --- a/src/EnergyPlus/DataRuntimeLanguage.hh +++ b/src/EnergyPlus/DataRuntimeLanguage.hh @@ -327,6 +327,7 @@ namespace DataRuntimeLanguage { bool CheckedOkay; // set to true once matched to available actuator int ErlVariableNum; // points to global Erl variable, matches Name int ActuatorVariableNum; // points to index match in EMSActuatorAvailable structure + bool wasActuated = false; // issue #10944: true once any Erl program has set this actuator // Default Constructor ActuatorUsedType() : CheckedOkay(false), ErlVariableNum(0), ActuatorVariableNum(0) diff --git a/src/EnergyPlus/EMSManager.cc b/src/EnergyPlus/EMSManager.cc index 4818b53dec8..206d1a28574 100644 --- a/src/EnergyPlus/EMSManager.cc +++ b/src/EnergyPlus/EMSManager.cc @@ -325,7 +325,7 @@ namespace EMSManager { state.dataRuntimeLang->NumExternalInterfaceFunctionalMockupUnitImportActuatorsUsed + state.dataRuntimeLang->NumExternalInterfaceFunctionalMockupUnitExportActuatorsUsed; ++ActuatorUsedLoop) { - auto const &thisActuatorUsed = state.dataRuntimeLang->EMSActuatorUsed(ActuatorUsedLoop); + auto &thisActuatorUsed = state.dataRuntimeLang->EMSActuatorUsed(ActuatorUsedLoop); int ErlVariableNum = thisActuatorUsed.ErlVariableNum; if (ErlVariableNum <= 0) { @@ -343,6 +343,7 @@ namespace EMSManager { if (thisErlVar.Value.Type == DataRuntimeLanguage::Value::Null) { *thisActuatorAvail.Actuated = false; } else { + thisActuatorUsed.wasActuated = true; // issue #10944 // Set the value and the actuated flag remotely on the actuated object via the pointer switch (thisActuatorAvail.PntrVarTypeUsed) { case DataRuntimeLanguage::PtrDataType::Real: { @@ -1983,9 +1984,9 @@ namespace EMSManager { void checkForUnusedActuatorsAtEnd(EnergyPlusData &state) { // call at end of simulation to check if any of the user's actuators were never initialized. - // Could be a mistake we want to help users catch // Issue #4404. + // Could be a mistake we want to help users catch // Issues #4404, #10944. for (int actuatorUsedLoop = 1; actuatorUsedLoop <= state.dataRuntimeLang->numActuatorsUsed; ++actuatorUsedLoop) { - if (!state.dataRuntimeLang->ErlVariable(state.dataRuntimeLang->EMSActuatorUsed(actuatorUsedLoop).ErlVariableNum).Value.initialized) { + if (!state.dataRuntimeLang->EMSActuatorUsed(actuatorUsedLoop).wasActuated) { ShowWarningError(state, "checkForUnusedActuatorsAtEnd: Unused EMS Actuator detected, suggesting possible unintended programming error or " "spelling mistake."); diff --git a/tst/EnergyPlus/unit/EMSManager.unit.cc b/tst/EnergyPlus/unit/EMSManager.unit.cc index 3d2ce940cc8..f6320a38641 100644 --- a/tst/EnergyPlus/unit/EMSManager.unit.cc +++ b/tst/EnergyPlus/unit/EMSManager.unit.cc @@ -3019,3 +3019,50 @@ TEST_F(EnergyPlusFixture, EMSManager_Sensor_On_ScheduleConstant) EXPECT_FALSE(schedDirect->EMSActuatedOn); EXPECT_EQ(0.0, schedDirect->EMSVal); } + +TEST_F(EnergyPlusFixture, UnusedActuatorWarning) +{ + // Issue #10944: user declares actuator A1, typos it as Al in the program. + // Al becomes a fresh local Erl variable; A1 is never assigned. + // End-of-sim check must warn about unused actuator A1. + std::string const idf_objects = delimited_string({ + + "OutdoorAir:Node, Test node;", + + "EnergyManagementSystem:Actuator,", + "A1, !- Name", + "Test node, !- Actuated Component Unique Name", + "System Node Setpoint, !- Actuated Component Type", + "Temperature Setpoint; !- Actuated Component Control Type", + + "EnergyManagementSystem:ProgramCallingManager,", + "Typo Test Manager, !- Name", + "BeginTimestepBeforePredictor, !- EnergyPlus Model Calling Point", + "TypoProgram; !- Program Name 1", + + "EnergyManagementSystem:Program,", + "TypoProgram,", + "Set Al = 2;", + + }); + + ASSERT_TRUE(process_idf(idf_objects)); + state->init_state(*state); + + OutAirNodeManager::SetOutAirNodes(*state); + + EMSManager::CheckIfAnyEMS(*state); + + state->dataEMSMgr->FinishProcessingUserInput = true; + + bool anyRan; + EMSManager::ManageEMS(*state, EMSManager::EMSCallFrom::SetupSimulation, anyRan, ObjexxFCL::Optional_int_const()); + EMSManager::ManageEMS(*state, EMSManager::EMSCallFrom::BeginTimestepBeforePredictor, anyRan, ObjexxFCL::Optional_int_const()); + + compare_err_stream(""); // drop any setup chatter + + EMSManager::checkForUnusedActuatorsAtEnd(*state); + + EXPECT_TRUE(compare_err_stream_substring("Unused EMS Actuator detected", false)); + EXPECT_TRUE(compare_err_stream_substring("A1", true)); +}