Files
Industrial_Emulator/lib/Core/States/State.h
2025-10-23 22:03:58 -05:00

271 lines
10 KiB
C++

/**
* @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 <string>
#include <Arduino.h>
#include <map>
#include "Strategies/Strategy_PID.h"
#include "ModbusPoints/Modbus_FloatDecorator.h"
#include "ModbusPoints/Modbus_Point.h"
// Forward Declarations
template<typename T>class 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<typename T>
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<T>* equipment) = 0;
/**
* @brief Logic to execute once when entering this state.
* @param equipment Pointer to the Equipment instance.
*/
virtual void enterState(Equipment<T>* equipment) {}
/**
* @brief Logic to execute once when exiting this state.
* @param equipment Pointer to the Equipment instance.
*/
virtual void exitState(Equipment<T>* equipment) {}
/**
* @brief Applies all registered strategies for the current state.
* @param equipment Pointer to the Equipment instance.
*/
virtual void _applyStrategies(Equipment<T>* 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<T>* 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<T>* 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 Modify specifyc bits of a Modbus point in this state.
* @param equipment Pointer to the Equipment instance.
* @param pointName The description key of the Modbus point to write to.
* @param bitPosition The position to which the function will write to.
* @param state The new state of the selected bit.
*/
void setBitValue(Equipment<T>* equipment, const std::string& pointName, int bitPosition, bool state);
/**
* @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<std::string, Strategy_Behavior*> _strategies; /**< @brief Map of strategies active in this state, keyed by point description. */
};
template<typename T>
State<T>::~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<typename T>
void State<T>::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<typename T>
Strategy_Behavior* State<T>::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<typename T>
float State<T>::getPointValue(Equipment<T>* equipment, const std::string& pointName) {
Modbus_Point<T>* 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<Modbus_FloatDecorator<T>*>(point)->getFloatValue();
} else {
// Otherwise, get the standard integer value
return static_cast<float>(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<typename T>
void State<T>::setPointValue(Equipment<T>* equipment, const std::string& pointName, float value) {
Modbus_Point<T>* point = equipment->getModbus_Point(pointName);
if (!point) return;
if (point->getType() == PointType::FLOAT) {
static_cast<Modbus_FloatDecorator<T>*>(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<typename T>
void State<T>::_applyStrategies(Equipment<T>* 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<T>* outputPoint = equipment->getModbus_Point(description);
if (!outputPoint) continue;
float inputValue;
if (strategy->isPID()) {
PIDStrategy* pid = static_cast<PIDStrategy*>(strategy);
inputValue = getPointValue(equipment, pid->getInputSensorName());
Modbus_Point<T>* 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);
}
}
}
/**
* @brief Controls a specific bit within an integer Modbus word (like a Holding or Input Register).
*
* This function bypasses the standard float logic to perform direct bit manipulation.
*
* @param equipment Pointer to the Equipment instance.
* @param pointName The description key of the Modbus point to modify.
* @param bitPosition The 0-based index of the bit to set/clear (0-15 for a 16-bit word).
* @param state If true, the bit is set (to 1); if false, the bit is cleared (to 0).
*/
template<typename T>
void State<T>::setBitValue(Equipment<T>* equipment, const std::string& pointName, int bitPosition, bool state) {
Modbus_Point<T>* point = equipment->getModbus_Point(pointName);
// Safety check: Ensure the point exists and isn't a decorated multi-word type (Float or Long)
// Note: Standard 16-bit Hreg/Ireg will return PointType::GENERIC.
if (!point || point->getType() != PointType::GENERIC || bitPosition < 0 || bitPosition > 15) {
// You can add an error logging statement here if needed, like Serial.printf(...)
return;
}
// 1. Get the current integer value directly from the point
int currentValue = point->getValue();
// 2. Create the bit mask
// '1 << bitPosition' shifts a 1 to the position we want to affect
int mask = 1 << bitPosition;
if (state) {
// 3. Set the bit (make it 1): Use the bitwise OR operator
currentValue |= mask;
} else {
// 3. Clear the bit (make it 0): Use the bitwise AND operator with the NOT (inverse) of the mask
currentValue &= ~mask;
}
// 4. Write the new integer value back
point->setValue(currentValue);
}
#endif