/** * @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 "ModbusPoints/Modbus_FloatDecorator.h" #include "ModbusPoints/Modbus_Point.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 Applies all registered strategies for the current state. * @param equipment Pointer to the Equipment instance. */ virtual void _applyStrategies(Equipment* equipment); protected: /** * @brief Gets the value of a Modbus point, handling float types correctly. * This is a helper function to safely read a value from a point, whether it's * a standard integer register or a `Modbus_FloatDecorator`. * @param equipment Pointer to the Equipment instance. * @param pointName The description key of the Modbus point to read. * @return The value of the point as a float. Returns 0.0f if not found. */ float getPointValue(Equipment* equipment, const std::string& pointName); /** * @brief Sets the value of a Modbus point, handling float types correctly. * This is a helper function to safely write a value to a point, whether it's * a standard integer register or a `Modbus_FloatDecorator`. * @param equipment Pointer to the Equipment instance. * @param pointName The description key of the Modbus point to write to. * @param value The float value to set. It will be rounded for integer points. */ void setPointValue(Equipment* equipment, const std::string& pointName, float value); /** * @brief Adds a behavior strategy for a specific Modbus point in this state. * @param pointDescription The description of the Modbus point to apply the strategy to. * @param strategy A pointer to the Strategy_Behavior object. The State takes ownership. */ void addStrategy(const std::string& pointDescription, Strategy_Behavior* strategy); /** * @brief returns a behavior strategy for a specific Modbus point in this state. * @param pointDescription The description of the Modbus point your need to get. */ Strategy_Behavior* getStrategy(const std::string& pointDescription); std::map _strategies; /**< @brief Map of strategies active in this state, keyed by point description. */ }; 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; } /** * @brief returns a behavior strategy for a specific Modbus point in this state. * @param pointDescription The description of the Modbus point your need to get. */ template Strategy_Behavior* State::getStrategy(const std::string& pointDescription){ { auto it = _strategies.find(pointDescription); if (it != _strategies.end()) { return it->second; } return nullptr; } } /** * @brief Gets the value of a Modbus point, correctly handling float types. * This helper function checks if the point is a `Modbus_FloatDecorator` and calls * `getFloatValue()` if it is. Otherwise, it gets the standard integer value and * casts it to a float. * @param equipment Pointer to the Equipment instance. * @param pointName The description key of the Modbus point. * @return The point's value as a float. Returns 0.0f if the point is not found. */ template float State::getPointValue(Equipment* equipment, const std::string& pointName) { Modbus_Point* point = equipment->getModbus_Point(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()); } } /** * @brief Sets the value of a Modbus point, correctly handling float types. * This helper function checks if the point is a `Modbus_FloatDecorator` and calls * `setFloatValue()` if it is. Otherwise, it rounds the float to the nearest * integer and calls the standard `setValue()`. * @param equipment Pointer to the Equipment instance. * @param pointName The description key of the Modbus point. * @param value The value to set. */ template void State::setPointValue(Equipment* equipment, const std::string& pointName, float value) { Modbus_Point* point = equipment->getModbus_Point(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)) { Modbus_Point* outputPoint = equipment->getModbus_Point(description); if (!outputPoint) continue; float inputValue; if (strategy->isPID()) { PIDStrategy* pid = static_cast(strategy); inputValue = getPointValue(equipment, pid->getInputSensorName()); Modbus_Point* PIDsetpoint = equipment->getModbus_Point(pid->getSetpointName()); if (PIDsetpoint) { pid->setSetpoint(PIDsetpoint->getValue()); } } else { inputValue = getPointValue(equipment, description); } float newValue = strategy->execute(inputValue); setPointValue(equipment, description, newValue); } } } #endif