/** * @file State.h * @brief Defines the abstract base class for all device states. * @author Emmanuel Hernandez Cruz * @date 2025-09-04 * * This file contains the definition of the abstract State class, which is a base * to implement the State Pattern. Concrete states (Standby, Running, Random, Fail, etc.) * will inherit from this class. */ #ifndef State_h #define State_h #include #include #include #include "Strategies/Strategy_PID.h" #include "Categories/ModbusFloatDecorator.h" #include "Categories/ModbusPoint.h" // Forward Declarations templateclass Equipment; class Strategy_Behavior; /** * @class State * @brief Abstract base class for a state in the State design pattern. * * This class defines the interface for all concrete states. It manages a * collection of "strategies" that define how Modbus points behave while the * equipment is in this state. */ template class State{ public: /** * @brief Virtual destructor. * Cleans up all associated Strategy_Behavior objects. */ virtual ~State(); /** * @brief Executes the state's logic for one update cycle. * This method applies the state's strategies and checks for transitions. * @param equipment Pointer to the Equipment instance. * @return A pointer to a new State if a transition should occur, otherwise nullptr. */ virtual State* update(Equipment* equipment) = 0; /** * @brief Logic to execute once when entering this state. * @param equipment Pointer to the Equipment instance. */ virtual void enterState(Equipment* equipment) {} /** * @brief Logic to execute once when exiting this state. * @param equipment Pointer to the Equipment instance. */ virtual void exitState(Equipment* equipment) {} /** * @brief Logic to apply all strategies created. * @param equipment Pointer to the Equipment instance. */ virtual void _applyStrategies(Equipment* equipment); protected: /** * @brief Adds a behavior strategy for a specific Modbus point. * @param pointDescription The description of the Modbus point to apply the strategy to. * @param strategy A pointer to the Strategy_Behavior object. The State will take ownership. */ float getPointValue(Equipment* equipment, const std::string& pointName); void setPointValue(Equipment* equipment, const std::string& pointName, float value); void addStrategy(const std::string& pointDescription, Strategy_Behavior* strategy); std::map _strategies; }; template State::~State(){ for (auto const& pair : this->_strategies) { delete pair.second; // 'second' is the pointer to Strategy_Behavior } } /** * @brief Adds a new strategy to the state's behavior map. * * The State object takes ownership of the strategy pointer and will be * responsible for its deletion. * * @param pointDescription The description of the Modbus point this strategy applies to. * @param strategy A pointer to a Strategy_Behavior object. */ template void State::addStrategy(const std::string& pointDescription, Strategy_Behavior* strategy){ this->_strategies[pointDescription] = strategy; } template float State::getPointValue(Equipment* equipment, const std::string& pointName) { ModbusPoint* point = equipment->getModbusPoint(pointName); if (!point) return 0.0f; if (point->getType() == PointType::FLOAT) { // If it's a float, cast and get the full float value return static_cast*>(point)->getFloatValue(); } else { // Otherwise, get the standard integer value return static_cast(point->getValue()); } } template void State::setPointValue(Equipment* equipment, const std::string& pointName, float value) { ModbusPoint* point = equipment->getModbusPoint(pointName); if (!point) return; if (point->getType() == PointType::FLOAT) { static_cast*>(point)->setFloatValue(value); } else { point->setValue(round(value)); } } /** * @brief Applies all registered strategies for the current state. * * This helper method iterates through all strategies associated with this state. * For each strategy that is ready to run (based on its internal timer), it * retrieves the corresponding Modbus point and applies the new value. * It handles both integer and float point types. * * @param equipment A pointer to the main Equipment object. */ template void State::_applyStrategies(Equipment* equipment) { unsigned long currentTime = millis(); // Use the C++11 compatible for-loop for std::map for (auto const& pair : this->_strategies) { const std::string& description = pair.first; Strategy_Behavior* strategy = pair.second; if (strategy->isReady(currentTime)) { ModbusPoint* outputPoint = equipment->getModbusPoint(description); if (!outputPoint) continue; float inputValue; if (strategy->isPID()) { PIDStrategy* pid = static_cast(strategy); inputValue = getPointValue(equipment, pid->getInputSensorName()); ModbusPoint* PIDsetpoint = equipment->getModbusPoint(pid->getSetpointName()); if (PIDsetpoint) { pid->setSetpoint(PIDsetpoint->getValue()); } } else { inputValue = getPointValue(equipment, description); } float newValue = strategy->execute(inputValue); setPointValue(equipment, description, newValue); } } } #endif