First stable compilation with ModbusRTU and ModbusTCP
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# .gitignore
|
||||
.vscode/
|
||||
.pio/
|
||||
37
include/README
Normal file
37
include/README
Normal file
@@ -0,0 +1,37 @@
|
||||
|
||||
This directory is intended for project header files.
|
||||
|
||||
A header file is a file containing C declarations and macro definitions
|
||||
to be shared between several project source files. You request the use of a
|
||||
header file in your project source file (C, C++, etc) located in `src` folder
|
||||
by including it, with the C preprocessing directive `#include'.
|
||||
|
||||
```src/main.c
|
||||
|
||||
#include "header.h"
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Including a header file produces the same results as copying the header file
|
||||
into each source file that needs it. Such copying would be time-consuming
|
||||
and error-prone. With a header file, the related declarations appear
|
||||
in only one place. If they need to be changed, they can be changed in one
|
||||
place, and programs that include the header file will automatically use the
|
||||
new version when next recompiled. The header file eliminates the labor of
|
||||
finding and changing all the copies as well as the risk that a failure to
|
||||
find one copy will result in inconsistencies within a program.
|
||||
|
||||
In C, the convention is to give header files names that end with `.h'.
|
||||
|
||||
Read more about using header files in official GCC documentation:
|
||||
|
||||
* Include Syntax
|
||||
* Include Operation
|
||||
* Once-Only Headers
|
||||
* Computed Includes
|
||||
|
||||
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||
95
lib/Core/Categories/ModbusCoil.h
Normal file
95
lib/Core/Categories/ModbusCoil.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @file ModbusCoil.h
|
||||
* @brief Defines the ModbusCoil class for handling Modbus coils.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-04
|
||||
*
|
||||
* This file contains the definition of the ModbusCoil class, which is a specific
|
||||
* implementation of the ModbusPoint for handling coils (digital outputs).
|
||||
*/
|
||||
#ifndef ModbusCoil_h
|
||||
#define ModbusCoil_h
|
||||
|
||||
#include "ModbusPoint.h"
|
||||
|
||||
/**
|
||||
* @class ModbusCoil
|
||||
* @brief Represents a Modbus coil point.
|
||||
*
|
||||
* This class provides a concrete implementation for a Modbus coil,
|
||||
* which is a single bit digital output. It inherits from ModbusPoint
|
||||
* and implements its virtual functions for coil-specific operations.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusCoil : public ModbusPoint<T>{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the ModbusCoil class.
|
||||
* @param server Pointer to the ModbusIP server instance.
|
||||
* @param address The Modbus address of the coil.
|
||||
* @param value The initial value of the coil.
|
||||
* @param description A description of the coil.
|
||||
*/
|
||||
ModbusCoil(T* server, int address, int value, const char* description);
|
||||
|
||||
/**
|
||||
* @brief Adds the coil to the Modbus server.
|
||||
*/
|
||||
void addToModbusServer() override;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the internal value of the coil.
|
||||
* @param value The new value for the coil.
|
||||
*/
|
||||
void setValue(int value) override;
|
||||
|
||||
/**
|
||||
* @brief Gets the current internal value of the coil.
|
||||
* @return The current value of the coil.
|
||||
*/
|
||||
int getValue() const override;
|
||||
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ModbusCoil<T>::ModbusCoil(T* server, int address, int value, const char* description)
|
||||
: ModbusPoint<T>(server, address, value, description){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the coil to the Modbus server's register map.
|
||||
*/
|
||||
template<typename T>
|
||||
void ModbusCoil<T>::addToModbusServer(){
|
||||
this->_server->addCoil(this->_address, this->_value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the internal value of the coil.
|
||||
*
|
||||
* If the new value is different from the current value, it updates the
|
||||
* internal value and marks the point as dirty, indicating it needs to be
|
||||
* written to the Modbus server.
|
||||
*
|
||||
* @param value The new value for the coil.
|
||||
*/
|
||||
template<typename T>
|
||||
void ModbusCoil<T>::setValue(int value){
|
||||
this->_server->Coil(this->_address, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the current internal value of the coil.
|
||||
* @return The current value.
|
||||
*/
|
||||
template<typename T>
|
||||
int ModbusCoil<T>::getValue() const{
|
||||
return this->_server->Coil(this->_address);
|
||||
}
|
||||
|
||||
#endif
|
||||
116
lib/Core/Categories/ModbusFloatDecorator.h
Normal file
116
lib/Core/Categories/ModbusFloatDecorator.h
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* @file ModbusFloatDecorator.h
|
||||
* @brief Defines the ModbusFloatDecorator class for handling 32-bit float values.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a decorator that combines two 16-bit
|
||||
* Modbus registers to represent a single 32-bit floating-point value.
|
||||
*/
|
||||
#ifndef ModbusFloatDecorator_h
|
||||
#define ModbusFloatDecorator_h
|
||||
#include <stdint.h>
|
||||
#include "ModbusPointDecorator.h"
|
||||
|
||||
/**
|
||||
* @union cracked_float_t
|
||||
* @brief A union to easily convert between a 32-bit float and two 16-bit integers.
|
||||
*
|
||||
* This union allows type-punning between a `float` and an array of two `int16_t`
|
||||
* values, which simplifies splitting a float into high and low words for Modbus
|
||||
* transmission.
|
||||
*/
|
||||
typedef union{
|
||||
float v; /**< @brief The value as a 32-bit float. */
|
||||
int16_t as_int[2]; /**< @brief The value as two 16-bit integers. */
|
||||
} cracked_float_t;
|
||||
|
||||
/**
|
||||
* @class ModbusFloatDecorator
|
||||
* @brief A decorator that combines two 16-bit registers into a 32-bit float.
|
||||
*
|
||||
* This class wraps two consecutive ModbusPoint objects (a low-word and a
|
||||
* high-word point) and treats them as a single 32-bit float value. It
|
||||
* overrides the necessary methods to handle reading, writing, and value
|
||||
* conversion across both underlying registers.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusFloatDecorator : public ModbusPointDecorator<T> {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new ModbusFloatDecorator object.
|
||||
* @param point A pointer to the ModbusPoint for the low-order word.
|
||||
* @param highOrderPoint A pointer to the ModbusPoint for the high-order word.
|
||||
*/
|
||||
ModbusFloatDecorator(ModbusPoint<T>* point, ModbusPoint<T>* highOrderPoint)
|
||||
: ModbusPointDecorator<T>(point), _highOrderPoint(highOrderPoint) {}
|
||||
|
||||
/**
|
||||
* @brief Returns the logical type of the point.
|
||||
* @return Always returns `PointType::FLOAT`.
|
||||
*/
|
||||
PointType getType() const override { return PointType::FLOAT; }
|
||||
|
||||
/**
|
||||
* @brief Sets the 32-bit float value by casting an integer.
|
||||
* This method is an override for the base class and calls `setFloatValue`.
|
||||
* @param value The integer value to set (will be cast to float).
|
||||
*/
|
||||
void setValue(int value) override {
|
||||
setFloatValue(static_cast<float>(value));
|
||||
}
|
||||
/**
|
||||
* @brief Sets the 32-bit float value by splitting it into two 16-bit words.
|
||||
*
|
||||
* This method uses a union to split the float into two 16-bit integers
|
||||
* and sets the values on the two underlying Modbus points.
|
||||
* @note The order of `as_int[0]` vs `as_int[1]` might need to be swapped
|
||||
* depending on the endianness of the target Modbus device.
|
||||
*
|
||||
* @param value The 32-bit float value to set.
|
||||
*/
|
||||
void setFloatValue(float value) {
|
||||
cracked_float_t buffer;
|
||||
buffer.v = value;
|
||||
this->_point->setValue(buffer.as_int[0]);
|
||||
_highOrderPoint->setValue(buffer.as_int[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the 32-bit float value, cast to an integer.
|
||||
* @return The integer representation of the full float value.
|
||||
*/
|
||||
int getValue() const override {
|
||||
return static_cast<int>(getFloatValue());
|
||||
}
|
||||
/**
|
||||
* @brief Gets the combined 32-bit float value from the two registers.
|
||||
*
|
||||
* This method reads the 16-bit integer values from the two underlying
|
||||
* points and uses a union to reconstruct the 32-bit float value.
|
||||
*
|
||||
* @return The reconstructed 32-bit float value.
|
||||
*/
|
||||
float getFloatValue() const {
|
||||
cracked_float_t buffer;
|
||||
// Directly read the two 16-bit words from the Modbus server
|
||||
buffer.as_int[0] = this->_point->getServer()->Hreg(this->_point->getAddress());
|
||||
buffer.as_int[1] = _highOrderPoint->getServer()->Hreg(_highOrderPoint->getAddress());
|
||||
return buffer.v;
|
||||
}
|
||||
|
||||
// --- Housekeeping Methods ---
|
||||
|
||||
/** @brief Adds both underlying registers to the Modbus server. */
|
||||
void addToModbusServer() override {
|
||||
this->_point->addToModbusServer();
|
||||
_highOrderPoint->addToModbusServer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private:
|
||||
ModbusPoint<T>* _highOrderPoint; /**< @brief Pointer to the ModbusPoint for the high-order word. */
|
||||
};
|
||||
|
||||
#endif
|
||||
75
lib/Core/Categories/ModbusHreg.h
Normal file
75
lib/Core/Categories/ModbusHreg.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file ModbusHreg.h
|
||||
* @brief Defines the ModbusHreg class for handling Modbus holding registers.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-04
|
||||
*
|
||||
* This file contains the definition of the ModbusHreg class, which is a specific
|
||||
* implementation of the ModbusPoint for handling holding registers (16-bit).
|
||||
*/
|
||||
#ifndef ModbusHreg_h
|
||||
#define ModbusHreg_h
|
||||
|
||||
#include "ModbusPoint.h"
|
||||
|
||||
/**
|
||||
* @class ModbusHreg
|
||||
* @brief Represents a Modbus holding register point.
|
||||
*
|
||||
* This class provides a concrete implementation for a Modbus holding register,
|
||||
* which is a 16-bit read/write register. It inherits from ModbusPoint
|
||||
* and implements its virtual functions for holding register-specific operations.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusHreg : public ModbusPoint<T>{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the ModbusHreg class.
|
||||
* @param server Pointer to the ModbusIP server instance.
|
||||
* @param address The Modbus address of the holding register.
|
||||
* @param value The initial value of the holding register.
|
||||
* @param description A description of the holding register.
|
||||
*/
|
||||
ModbusHreg(T* server, int address, int value, const char* description);
|
||||
|
||||
/**
|
||||
* @brief Adds the holding register to the Modbus server.
|
||||
*/
|
||||
void addToModbusServer() override;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief Sets the internal value of the holding register.
|
||||
* @param value The new value for the holding register.
|
||||
*/
|
||||
void setValue(int value) override;
|
||||
|
||||
/**
|
||||
* @brief Gets the current internal value of the holding register.
|
||||
* @return The current value of the holding register.
|
||||
*/
|
||||
int getValue() const override;
|
||||
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ModbusHreg<T>::ModbusHreg(T* server, int address, int value, const char* description)
|
||||
: ModbusPoint<T>(server, address, value, description) {}
|
||||
|
||||
template<typename T>
|
||||
void ModbusHreg<T>::addToModbusServer() {
|
||||
this->_server->addHreg(this->_address, this->_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void ModbusHreg<T>::setValue(int value) {
|
||||
this->_server->Hreg(this->_address, value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int ModbusHreg<T>::getValue() const {
|
||||
return this->_server->Hreg(this->_address);
|
||||
}
|
||||
#endif
|
||||
82
lib/Core/Categories/ModbusIreg.h
Normal file
82
lib/Core/Categories/ModbusIreg.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file ModbusIreg.h
|
||||
* @brief Defines the ModbusIreg class for handling Modbus Input Registers.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-04
|
||||
*
|
||||
* This file contains the definition of the ModbusIreg class, which is a specific
|
||||
* implementation of the ModbusPoint for handling input registers (16-bit read-only).
|
||||
*/
|
||||
#ifndef ModbusIreg_h
|
||||
#define ModbusIreg_h
|
||||
|
||||
#include "ModbusPoint.h"
|
||||
|
||||
/**
|
||||
* @class ModbusIreg
|
||||
* @brief Represents a Modbus Input Register point.
|
||||
*
|
||||
* This class provides a concrete implementation for a Modbus input register,
|
||||
* which is a 16-bit read-only register. It inherits from ModbusPoint
|
||||
* and implements its virtual functions for input register-specific operations.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusIreg : public ModbusPoint<T>{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the ModbusIreg class.
|
||||
* @param server Pointer to the ModbusIP server instance.
|
||||
* @param address The Modbus address of the input register.
|
||||
* @param value The initial value of the input register.
|
||||
* @param description A description of the input register.
|
||||
*/
|
||||
ModbusIreg(T* server, int address, int value, const char* description);
|
||||
|
||||
/**
|
||||
* @brief Adds the input register to the Modbus server.
|
||||
*/
|
||||
void addToModbusServer() override;
|
||||
|
||||
/**
|
||||
* @brief Sets the internal value of the input register.
|
||||
* @param value The new value for the input register.
|
||||
*/
|
||||
void setValue(int value) override;
|
||||
/**
|
||||
* @brief Gets the current internal value of the input register.
|
||||
* @return The current value of the input register.
|
||||
*/
|
||||
int getValue() const override;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ModbusIreg<T>::ModbusIreg(T* server, int address, int value, const char* description)
|
||||
: ModbusPoint<T>(server, address, value, description){
|
||||
|
||||
}
|
||||
/**
|
||||
* @brief Sets the internal value of the input register and marks it as dirty.
|
||||
* @param value The new value for the input register.
|
||||
*/
|
||||
template<typename T>
|
||||
void ModbusIreg<T>::setValue(int value){
|
||||
this->_server->Ireg(this->_address, value);
|
||||
}
|
||||
/**
|
||||
* @brief Gets the current internal value of the input register.
|
||||
* @return The current value.
|
||||
*/
|
||||
template<typename T>
|
||||
int ModbusIreg<T>::getValue() const {
|
||||
return this->_server->Ireg(this->_address);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds the input register to the Modbus server's register map.
|
||||
*/
|
||||
template<typename T>
|
||||
void ModbusIreg<T>::addToModbusServer(){
|
||||
this->_server->addIreg(this->_address, this->_value);
|
||||
}
|
||||
|
||||
#endif
|
||||
83
lib/Core/Categories/ModbusIsts.h
Normal file
83
lib/Core/Categories/ModbusIsts.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @file ModbusIsts.h
|
||||
* @brief Defines the ModbusIsts class for handling Modbus Input Status (Discrete Inputs).
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-04
|
||||
*
|
||||
* This file contains the definition of the ModbusIsts class, which is a specific
|
||||
* implementation of the ModbusPoint for handling discrete inputs (read-only coils).
|
||||
*/
|
||||
#ifndef ModbusIsts_h
|
||||
#define ModbusIsts_h
|
||||
|
||||
#include "ModbusPoint.h"
|
||||
|
||||
/**
|
||||
* @class ModbusIsts
|
||||
* @brief Represents a Modbus Input Status (Discrete Input) point.
|
||||
*
|
||||
* This class provides a concrete implementation for a Modbus discrete input,
|
||||
* which is a single bit read-only value. It inherits from ModbusPoint
|
||||
* and implements its virtual functions for discrete input-specific operations.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusIsts : public ModbusPoint<T>{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for the ModbusIsts class.
|
||||
* @param server Pointer to the ModbusIP server instance.
|
||||
* @param address The Modbus address of the discrete input.
|
||||
* @param value The initial value of the discrete input.
|
||||
* @param description A description of the discrete input.
|
||||
*/
|
||||
ModbusIsts(T* server, int address, int value, const char* description);
|
||||
|
||||
/**
|
||||
* @brief Adds the discrete input to the Modbus server.
|
||||
*/
|
||||
void addToModbusServer() override;
|
||||
|
||||
/**
|
||||
* @brief Sets the internal value of the discrete input.
|
||||
* @param value The new value for the discrete input.
|
||||
*/
|
||||
void setValue(int value) override;
|
||||
/**
|
||||
* @brief Gets the current internal value of the discrete input.
|
||||
* @return The current value of the discrete input.
|
||||
*/
|
||||
int getValue() const override;
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ModbusIsts<T>::ModbusIsts(T* server, int address, int value, const char* description)
|
||||
: ModbusPoint<T>(server, address, value, description){
|
||||
|
||||
}
|
||||
/**
|
||||
* @brief Sets the internal value of the discrete input and marks it as dirty.
|
||||
* @param value The new value for the discrete input.
|
||||
*/
|
||||
template<typename T>
|
||||
void ModbusIsts<T>::setValue(int value){
|
||||
this->_server->Ists(this->_address, value);
|
||||
}
|
||||
/**ss
|
||||
* @brief Gets the current internal value of the discrete input.
|
||||
* @return The current value.
|
||||
*/
|
||||
template<typename T>
|
||||
int ModbusIsts<T>::getValue() const{
|
||||
return this->_server->Ists(this->_address);
|
||||
}
|
||||
/**
|
||||
* @brief Adds the discrete input to the Modbus server's register map.
|
||||
*/
|
||||
template<typename T>
|
||||
void ModbusIsts<T>::addToModbusServer(){
|
||||
this->_server->addIsts(this->_address, this->_value);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
86
lib/Core/Categories/ModbusLongDecorator.h
Normal file
86
lib/Core/Categories/ModbusLongDecorator.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @file ModbusLongDecorator.h
|
||||
* @brief Defines the ModbusLongDecorator class for handling 32-bit long values.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a decorator that combines two 16-bit
|
||||
* Modbus registers to represent a single 32-bit long integer value.
|
||||
*/
|
||||
#ifndef ModbusLongDecorator_h
|
||||
#define ModbusLongDecorator_h
|
||||
|
||||
#include "ModbusPointDecorator.h"
|
||||
|
||||
/**
|
||||
* @class ModbusLongDecorator
|
||||
* @brief A decorator that combines two 16-bit registers into a 32-bit long.
|
||||
*
|
||||
* This class wraps two consecutive ModbusPoint objects (a low-word and a
|
||||
* high-word point) and treats them as a single 32-bit long integer. It
|
||||
* overrides the necessary methods to handle reading, writing, and value
|
||||
* conversion across both underlying registers.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusLongDecorator : public ModbusPointDecorator<T> {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new ModbusLongDecorator object.
|
||||
* @param point A pointer to the ModbusPoint for the low-order word (LSB).
|
||||
* @param highOrderPoint A pointer to the ModbusPoint for the high-order word (MSB).
|
||||
*/
|
||||
ModbusLongDecorator(ModbusPoint<T>* point, ModbusPoint<T>* highOrderPoint)
|
||||
: ModbusPointDecorator<T>(point), _highOrderPoint(highOrderPoint) {}
|
||||
|
||||
/**
|
||||
* @brief Returns the logical type of the point.
|
||||
* @return Always returns `PointType::LONG`.
|
||||
*/
|
||||
PointType getType() const override { return PointType::LONG; }
|
||||
|
||||
// --- Housekeeping Methods ---
|
||||
|
||||
/** @brief Adds both underlying registers to the Modbus server. */
|
||||
void addToModbusServer() override {
|
||||
this->_point->addToModbusServer();
|
||||
_highOrderPoint->addToModbusServer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Value Getters and Setters ---
|
||||
|
||||
/**
|
||||
* @brief Sets the 32-bit long value by splitting it into two 16-bit words.
|
||||
* This method is an override for the base class and calls `setLongValue`.
|
||||
* @param value The integer value to set (will be cast to long).
|
||||
*/
|
||||
void setValue(int value) override {
|
||||
setLongValue(static_cast<long>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the 32-bit long value by splitting it into two 16-bit words.
|
||||
* @param value The 32-bit long value to set.
|
||||
*/
|
||||
void setLongValue(long value) {
|
||||
this->_point->setValue(value & 0xFFFF); // Low Word (LSB)
|
||||
_highOrderPoint->setValue((value >> 16) & 0xFFFF); // High Word (MSB)
|
||||
}
|
||||
|
||||
/** @brief Gets the combined 32-bit long value from the two registers. */
|
||||
long getLongValue() const {
|
||||
long lsb = this->_point->getValue();
|
||||
long msb = _highOrderPoint->getValue();
|
||||
return (msb << 16) | lsb;
|
||||
}
|
||||
|
||||
/** @brief Gets the 32-bit long value, cast to an integer. */
|
||||
int getValue() const override {
|
||||
return static_cast<int>(getLongValue());
|
||||
}
|
||||
private:
|
||||
ModbusPoint<T>* _highOrderPoint; /**< @brief Pointer to the ModbusPoint for the high-order word. */
|
||||
};
|
||||
|
||||
#endif
|
||||
101
lib/Core/Categories/ModbusPoint.h
Normal file
101
lib/Core/Categories/ModbusPoint.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @file ModbusPoint.h
|
||||
* @brief Defines the abstract base class for all Modbus points.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-04
|
||||
*
|
||||
* This file contains the definition of the abstract ModbusPoint class, which
|
||||
* serves as the base for all specific Modbus point types (Coils, Registers, etc.).
|
||||
* It defines the common interface for interacting with Modbus points.
|
||||
*/
|
||||
#ifndef ModbusPoint_h
|
||||
#define ModbusPoint_h
|
||||
#include <string.h>
|
||||
/**
|
||||
* @enum PointType
|
||||
* @brief Identifies the logical type of a Modbus point.
|
||||
*
|
||||
* This enum is used to distinguish between simple points and decorated points
|
||||
* that represent more complex data types like floats or longs.
|
||||
*/
|
||||
enum class PointType {
|
||||
GENERIC, /**< @brief A standard, non-decorated point. */
|
||||
FLOAT, /**< @brief A point decorated to handle 32-bit float values. */
|
||||
LONG, /**< @brief A point decorated to handle 32-bit long integer values. */
|
||||
SCALE /**< @brief A point decorated to apply a scaling factor (e.g., 10x). */
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ModbusPoint
|
||||
* @brief Abstract base class representing a single point in the Modbus map.
|
||||
*
|
||||
* This class defines the common interface and data for all types of Modbus
|
||||
* points. Concrete implementations (e.g., ModbusCoil, ModbusHreg) and decorators
|
||||
* must inherit from this class and implement its pure virtual functions.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusPoint{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new ModbusPoint object.
|
||||
* @param server Pointer to the ModbusIP server instance.
|
||||
* @param address The Modbus address of the point.
|
||||
* @param value The initial value of the point.
|
||||
* @param description A descriptive name for the point.
|
||||
*/
|
||||
ModbusPoint(T* server, int address, int value, const char* description);
|
||||
|
||||
// --- Getters ---
|
||||
/** @brief Gets a pointer to the ModbusIP server instance. */
|
||||
T* getServer() const { return _server; }
|
||||
/** @brief Gets the Modbus address of the point. */
|
||||
int getAddress() const { return _address; }
|
||||
/** @brief Gets the initial value assigned to the point at creation. */
|
||||
float getInitialValue() const { return _value; }
|
||||
/** @brief Gets the descriptive name of the point. */
|
||||
const char* getDescription() const { return _description; }
|
||||
|
||||
// --- Virtual Interface ---
|
||||
|
||||
/** @brief Returns the logical type of the point (e.g., FLOAT, LONG). */
|
||||
virtual PointType getType() const { return PointType::GENERIC; }
|
||||
/** @brief Pure virtual function to add the point to the Modbus server's registers. */
|
||||
virtual void addToModbusServer() = 0;
|
||||
/** @brief Pure virtual function to read the point's value from the Modbus server. */
|
||||
|
||||
/** @brief Pure virtual function to set the point's internal value. */
|
||||
virtual void setValue(int value) = 0;
|
||||
/** @brief Pure virtual function to get the point's internal value. */
|
||||
virtual int getValue() const = 0;
|
||||
|
||||
// --- Dirty Flag ---
|
||||
|
||||
/**
|
||||
* @brief Checks if the point's value has changed since the last write.
|
||||
* @return True if the value is dirty, false otherwise.
|
||||
*/
|
||||
bool isDirty() const { return _dirty; }
|
||||
/**
|
||||
* @brief Sets the dirty flag for the point.
|
||||
* @param dirty The new state of the dirty flag.
|
||||
*/
|
||||
void setDirty(bool dirty) { _dirty = dirty; }
|
||||
|
||||
/** @brief Virtual destructor to ensure proper cleanup of derived classes. */
|
||||
virtual ~ModbusPoint() = default;
|
||||
|
||||
protected:
|
||||
T* _server; /**< @brief Pointer to the global ModbusIP server instance. */
|
||||
int _address; /**< @brief The Modbus address of this point. */
|
||||
int _value; /**< @brief The current internal value of this point. */
|
||||
char _description[35]; /**< @brief A descriptive name for this point. */
|
||||
bool _dirty = false; /**< @brief Flag to track if the value has changed and needs to be written. */
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ModbusPoint<T>::ModbusPoint(T* server, int address, int value, const char* description)
|
||||
: _server(server), _address(address), _value(value) {
|
||||
strncpy(_description, description, sizeof(_description) - 1);
|
||||
_description[sizeof(_description) - 1] = '\0';
|
||||
}
|
||||
#endif
|
||||
60
lib/Core/Categories/ModbusPointDecorator.h
Normal file
60
lib/Core/Categories/ModbusPointDecorator.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file ModbusPointDecorator.h
|
||||
* @brief Defines the base decorator class for Modbus points.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for ModbusPointDecorator, which is the
|
||||
* abstract base class for all decorators in the Decorator design pattern.
|
||||
* It wraps a ModbusPoint and forwards all calls to it by default.
|
||||
*/
|
||||
#ifndef ModbusPointDecorator_h
|
||||
#define ModbusPointDecorator_h
|
||||
#include "ModbusPoint.h"
|
||||
|
||||
/**
|
||||
* @class ModbusPointDecorator
|
||||
* @brief An abstract base class for decorating ModbusPoint objects.
|
||||
*
|
||||
* This class follows the Decorator pattern. It wraps a `ModbusPoint` object
|
||||
* and provides a default implementation for all virtual methods that simply
|
||||
* delegate the call to the wrapped object. Concrete decorators should inherit
|
||||
* from this class and override the specific methods they need to modify.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusPointDecorator : public ModbusPoint<T>{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new ModbusPointDecorator object.
|
||||
*
|
||||
* Initializes the base ModbusPoint with the properties of the wrapped point
|
||||
* and stores a pointer to the wrapped point.
|
||||
*
|
||||
* @param point A pointer to the ModbusPoint object to be decorated.
|
||||
*/
|
||||
ModbusPointDecorator(ModbusPoint<T>* point) : ModbusPoint<T>(
|
||||
point->getServer(),
|
||||
point->getAddress(),
|
||||
point->getInitialValue(),
|
||||
point->getDescription()),
|
||||
_point(point) {}
|
||||
|
||||
// --- Delegated Methods ---
|
||||
// These methods simply forward the call to the wrapped _point object.
|
||||
// Concrete decorators can override them to add new behavior.
|
||||
|
||||
void addToModbusServer() override{
|
||||
_point->addToModbusServer();
|
||||
}
|
||||
|
||||
void setValue(int value) override {
|
||||
_point->setValue(value);
|
||||
}
|
||||
int getValue() const override{
|
||||
return _point->getValue();
|
||||
}
|
||||
|
||||
protected:
|
||||
ModbusPoint<T>* _point; /**< @brief Pointer to the wrapped ModbusPoint object. */
|
||||
};
|
||||
#endif
|
||||
103
lib/Core/Categories/ModbusPointFactory.h
Normal file
103
lib/Core/Categories/ModbusPointFactory.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @file ModbusPointFactory.h
|
||||
* @brief Defines the factory function for creating ModbusPoint objects.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file provides the interface for a factory function that simplifies the
|
||||
* creation of various ModbusPoint types (e.g., Coils, Holding Registers) and
|
||||
* their decorators (e.g., for scaling, float, or long values).
|
||||
*/
|
||||
#ifndef ModbusPointFactory_h
|
||||
#define ModbusPointFactory_h
|
||||
#include "ModbusPoint.h"
|
||||
#include "ModbusCoil.h"
|
||||
#include "ModbusIsts.h"
|
||||
#include "ModbusIreg.h"
|
||||
#include "ModbusHreg.h"
|
||||
#include "ModbusScaleDecorator.h"
|
||||
#include "ModbusLongDecorator.h"
|
||||
#include "ModbusFloatDecorator.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
/**
|
||||
* @brief Creates a specific ModbusPoint object based on a category code.
|
||||
*
|
||||
* This factory function acts as a centralized point for instantiating different
|
||||
* concrete ModbusPoint classes and applying decorators. It takes a category
|
||||
* code and other parameters, and returns a pointer to the appropriate object.
|
||||
*
|
||||
* @param server Pointer to the ModbusIP server instance.
|
||||
* @param category An integer code representing the type of Modbus point to create
|
||||
* (e.g., COIL, HR, IR_FLOAT from config.h).
|
||||
* @param address The Modbus address for the point.
|
||||
* @param value The initial value for the point.
|
||||
* @param description A descriptive name for the point.
|
||||
* @return A pointer to a newly created ModbusPoint object. The caller is
|
||||
* responsible for managing the memory of this object. Returns nullptr
|
||||
* if the category is not recognized.
|
||||
*/
|
||||
template<typename T>
|
||||
ModbusPoint<T>* createModbusPoint(T* server, int category, int address, int value, const char* description);
|
||||
|
||||
template<typename T>
|
||||
ModbusPoint<T>* createModbusPoint(T* server, int category, int address, int value, const char* description) {
|
||||
switch (category) {
|
||||
case COIL:
|
||||
Serial.printf("Creating Coil: %s\n", description);
|
||||
return new ModbusCoil<T>(server, address, value, description);
|
||||
|
||||
case DI:
|
||||
Serial.printf("Creating Digital Input: %s\n", description);
|
||||
return new ModbusIsts<T>(server, address, value, description);
|
||||
|
||||
case IR:
|
||||
Serial.printf("Creating Input Register: %s\n", description);
|
||||
return new ModbusIreg<T>(server, address, value, description);
|
||||
|
||||
case IR_10X: {
|
||||
Serial.printf("Creating Input Register 10x: %s\n", description);
|
||||
ModbusPoint<T>* point = new ModbusIreg<T>(server, address, value, description);
|
||||
return new ModbusScaleDecorator<T>(point);
|
||||
}
|
||||
case IR_LONG: {
|
||||
Serial.printf("Creating Input Register Long: %s\n", description);
|
||||
ModbusPoint<T>* point = new ModbusIreg<T>(server, address, 0, description);
|
||||
ModbusPoint<T>* highOrderPoint = new ModbusIreg<T>(server, address + 1, 0, "");
|
||||
return new ModbusLongDecorator<T>(point, highOrderPoint);
|
||||
}
|
||||
case IR_FLOAT: {
|
||||
Serial.printf("Creating Input Register Float: %s\n", description);
|
||||
ModbusPoint<T>* point = new ModbusIreg<T>(server, address, 0, description);
|
||||
ModbusPoint<T>* highOrderPoint = new ModbusIreg<T>(server, address + 1, 0, "");
|
||||
return new ModbusFloatDecorator<T>(point, highOrderPoint);
|
||||
}
|
||||
case HR:
|
||||
Serial.printf("Creating Holding Register: %s\n", description);
|
||||
return new ModbusHreg<T>(server, address, value, description);
|
||||
|
||||
case HR_10x: {
|
||||
Serial.printf("Creating Holding Register 10x: %s\n", description);
|
||||
ModbusPoint<T>* point = new ModbusHreg<T>(server, address, value, description);
|
||||
return new ModbusScaleDecorator<T>(point);
|
||||
}
|
||||
case HR_LONG: {
|
||||
Serial.printf("Creating Holding Register Long: %s\n", description);
|
||||
ModbusPoint<T>* point = new ModbusHreg<T>(server, address, 0, description);
|
||||
ModbusPoint<T>* highOrderPoint = new ModbusHreg<T>(server, address + 1 , 0, "");
|
||||
return new ModbusLongDecorator<T>(point, highOrderPoint);
|
||||
}
|
||||
case HR_FLOAT: {
|
||||
Serial.printf("Creating Holding Register Float: %s\n", description);
|
||||
ModbusPoint<T>* point = new ModbusHreg<T>(server, address, 0, description);
|
||||
ModbusPoint<T>* highOrderPoint = new ModbusHreg<T>(server, address + 1 , 0, "");
|
||||
return new ModbusFloatDecorator<T>(point, highOrderPoint);
|
||||
}
|
||||
default:
|
||||
Serial.printf("ERROR: Unknown Modbus category %d for '%s'\n", category, description);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
50
lib/Core/Categories/ModbusScaleDecorator.h
Normal file
50
lib/Core/Categories/ModbusScaleDecorator.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file ModbusScaleDecorator.h
|
||||
* @brief Defines the ModbusScaleDecorator class for scaling Modbus point values.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a decorator that applies a 10x scaling
|
||||
* factor to a Modbus point. This is useful for representing decimal values
|
||||
* in integer registers (e.g., storing 12.3 as 123).
|
||||
*/
|
||||
#ifndef ModbusScaleDecorator_h
|
||||
#define ModbusScaleDecorator_h
|
||||
|
||||
#include "ModbusPointDecorator.h"
|
||||
|
||||
/**
|
||||
* @class ModbusScaleDecorator
|
||||
* @brief A decorator that multiplies/divides a Modbus point's value by 10.
|
||||
*
|
||||
* This class wraps a ModbusPoint and intercepts its `getValue` and `setValue`
|
||||
* calls. When setting a value, it multiplies the input by 10 before storing it.
|
||||
* When getting a value, it divides the stored value by 10.
|
||||
*/
|
||||
template<typename T>
|
||||
class ModbusScaleDecorator : public ModbusPointDecorator<T> {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new ModbusScaleDecorator object.
|
||||
* @param point A pointer to the ModbusPoint object to be decorated.
|
||||
*/
|
||||
ModbusScaleDecorator<T>(ModbusPoint<T>* point) : ModbusPointDecorator<T>(point) {}
|
||||
|
||||
/**
|
||||
* @brief Sets the value of the underlying point after scaling it up by 10.
|
||||
* @param value The logical value to set (e.g., 12 for 12.0).
|
||||
*/
|
||||
void setValue(int value) override {
|
||||
this->_point->setValue(value * 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the value from the underlying point after scaling it down by 10.
|
||||
* @return The logical, scaled-down value (e.g., 12 if the stored value is 120).
|
||||
*/
|
||||
int getValue() const override {
|
||||
return this->_point->getValue() / 10;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
223
lib/Core/Equipment/Equipment.h
Normal file
223
lib/Core/Equipment/Equipment.h
Normal file
@@ -0,0 +1,223 @@
|
||||
/**
|
||||
* @file Equipment.h
|
||||
* @brief Defines the main Equipment class for the device emulator.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-02
|
||||
*
|
||||
* This file contains the definition for the Equipment class, which acts as the
|
||||
* central context for the State design pattern. It manages the current state
|
||||
* of the device and holds all of its Modbus points.
|
||||
*/
|
||||
#ifndef Equipment_h
|
||||
#define Equipment_h
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "States/State_Standby.h"
|
||||
|
||||
// Forward Declarations
|
||||
template<typename T> class State;
|
||||
template<typename T> class ModbusPoint;
|
||||
|
||||
/**
|
||||
* @class Equipment
|
||||
* @brief The main class representing the emulated device.
|
||||
*
|
||||
* This class orchestrates the device's behavior. It holds a collection of all
|
||||
* Modbus points and manages the device's current operational state (e.g.,
|
||||
* Standby, Running) by delegating actions to a concrete State object.
|
||||
*/
|
||||
template<typename T>
|
||||
class Equipment{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new Equipment object.
|
||||
* Initializes the device in the default initial state (Standby).
|
||||
*/
|
||||
Equipment();
|
||||
Equipment(T* server);
|
||||
/** @brief The main update loop for the equipment, called repeatedly. Delegates to the current state. */
|
||||
void update();
|
||||
/** @brief Delegates the enter state logic to the current state object. */
|
||||
void enterState();
|
||||
/** @brief Delegates the exit state logic to the current state object. */
|
||||
void exitState();
|
||||
/**
|
||||
* @brief Sets a simple integer identifier for the current state.
|
||||
* @param stateId The integer ID representing the state.
|
||||
*/
|
||||
void setState(int stateId);
|
||||
/**
|
||||
* @brief Gets the simple integer identifier for the current state.
|
||||
* @return The integer ID of the state.
|
||||
*/
|
||||
int getState();
|
||||
/**
|
||||
* @brief Reads all the points generated for the equipment from the Modbus server to the objects
|
||||
*/
|
||||
void readAllPoints();
|
||||
/**
|
||||
* @brief Writes all the points generated for the equipment from the objects to the Modbus server
|
||||
*/
|
||||
void writeAllPoints();
|
||||
/**
|
||||
* @brief Transitions the equipment to a new state.
|
||||
* Handles exiting the old state, deleting it, and entering the new one.
|
||||
* @param newState A pointer to the new State object. The Equipment takes ownership.
|
||||
*/
|
||||
void changeState(State<T>* newState);
|
||||
/**
|
||||
* @brief Adds a Modbus point to the equipment's internal map.
|
||||
* @param description The unique string description used as a key.
|
||||
* @param point A pointer to the ModbusPoint object.
|
||||
*/
|
||||
void addModbusPoint(const std::string& description, ModbusPoint<T>* point);
|
||||
/**
|
||||
* @brief Retrieves a Modbus point by its description.
|
||||
* @param description The string key for the Modbus point.
|
||||
* @return A pointer to the ModbusPoint object, or nullptr if not found.
|
||||
*/
|
||||
ModbusPoint<T>* getModbusPoint(const std::string& description);
|
||||
/**
|
||||
* @brief Sets the value of a specific Modbus point.
|
||||
* @param description The string key for the Modbus point.
|
||||
* @param value The value to set.
|
||||
*/
|
||||
void setModbusPoint(const std::string& description, float value);
|
||||
|
||||
private:
|
||||
State<T>* _state; /**< @brief Pointer to the current state object. */
|
||||
std::map<std::string, ModbusPoint<T>*> _points;/**< @brief Map of all Modbus points, keyed by description. */
|
||||
int _stateId; /**< @brief A simple integer identifier for the current state. */
|
||||
std::vector<ModbusPoint<T>*> _allPoints; /**< @brief Vector of all Modbus points. */
|
||||
T* _server;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
Equipment<T>::Equipment() : _server(nullptr) {
|
||||
this->_state = new StandbyState<T>();
|
||||
this->_state->enterState(this);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Equipment<T>::Equipment(T* server)
|
||||
: _server(server)
|
||||
{
|
||||
this->_state = new StandbyState<T>();
|
||||
this->_state->enterState(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The main update loop for the equipment.
|
||||
*
|
||||
* This method delegates the update logic to the current state object. If the
|
||||
* state's update method returns a pointer to a new state, this method
|
||||
* triggers a state transition.
|
||||
*/
|
||||
template<typename T>
|
||||
void Equipment<T>::update() {
|
||||
State<T>* newState = this->_state->update(this);
|
||||
if (newState != nullptr) {
|
||||
changeState(newState);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delegates the enter state logic to the current state object.
|
||||
*/
|
||||
template<typename T>
|
||||
void Equipment<T>::enterState() {
|
||||
this->_state->enterState(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Delegates the exit state logic to the current state object.
|
||||
*/
|
||||
template<typename T>
|
||||
void Equipment<T>::exitState() {
|
||||
this->_state->exitState(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a Modbus point to the equipment's internal collections.
|
||||
* The point is added to a map for quick lookup by description and to a
|
||||
* vector for simple iteration.
|
||||
* @param description The unique string description used as a key.
|
||||
* @param point A pointer to the ModbusPoint object.
|
||||
*/
|
||||
template<typename T>
|
||||
void Equipment<T>::addModbusPoint(const std::string& description, ModbusPoint<T>* point) {
|
||||
this->_points[description] = point;
|
||||
this->_allPoints.push_back(point);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieves a Modbus point by its description.
|
||||
* @param description The string key for the Modbus point.
|
||||
* @return A pointer to the ModbusPoint object, or nullptr if not found.
|
||||
*/
|
||||
template<typename T>
|
||||
ModbusPoint<T>* Equipment<T>::getModbusPoint(const std::string& description) {
|
||||
auto it = this->_points.find(description);
|
||||
if (it != this->_points.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the value of a specific Modbus point.
|
||||
* @param description The string key for the Modbus point.
|
||||
* @param value The value to set.
|
||||
*/
|
||||
template<typename T>
|
||||
void Equipment<T>::setModbusPoint(const std::string& description, float value) {
|
||||
ModbusPoint<T>* point = getModbusPoint(description);
|
||||
if (point != nullptr) {
|
||||
point->setValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the simple integer identifier for the current state.
|
||||
* @return The integer ID of the state.
|
||||
*/
|
||||
template<typename T>
|
||||
int Equipment<T>::getState() {
|
||||
return _stateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a simple integer identifier for the current state.
|
||||
* @param stateId The integer ID representing the state.
|
||||
*/
|
||||
template<typename T>
|
||||
void Equipment<T>::setState(int stateId) {
|
||||
this->_stateId = stateId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transitions the equipment to a new state.
|
||||
*
|
||||
* This method handles the full lifecycle of a state transition: it calls
|
||||
* `exitState` on the current state, deletes the old state object to prevent
|
||||
* memory leaks, assigns the new state, and finally calls `enterState` on the
|
||||
* new state.
|
||||
*
|
||||
* @param newState A pointer to the new State object. The Equipment takes ownership.
|
||||
*/
|
||||
template<typename T>
|
||||
void Equipment<T>::changeState(State<T>* newState) {
|
||||
if (this->_state != nullptr) {
|
||||
this->_state->exitState(this);
|
||||
delete this->_state;
|
||||
}
|
||||
this->_state = newState;
|
||||
|
||||
if (this->_state != nullptr) {
|
||||
this->_state->enterState(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
113
lib/Core/README.md
Normal file
113
lib/Core/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Industrial Equipment Emulator
|
||||
|
||||
This project is an Arduino-based emulator for an industrial equipment. It simulates the behavior of a real-world device (like a Computer Room Air Handler - CRAH) and communicates over Wi-Fi using the Modbus IP protocol.
|
||||
|
||||
The primary goal is to provide a flexible and extensible virtual device for testing, development, and training purposes without needing physical hardware.
|
||||
|
||||
## Core Concepts for Automation Professionals
|
||||
|
||||
This software is built using a few key Object-Oriented Programming (OOP) concepts that make it powerful and easy to modify. If you think in terms of control systems, these concepts will feel very familiar.
|
||||
|
||||
### 1. The State Pattern: "What is the machine's current operating mode?"
|
||||
|
||||
In industrial automation, a machine has different operating modes: **Standby**, **Running**, **Alarm/Fault**, **Manual Override**, etc. The machine behaves differently in each mode.
|
||||
|
||||
The **State Pattern** organizes the code to mirror these real-world machine modes.
|
||||
|
||||
* **Analogy:** Think of a PLC program. Instead of having one massive ladder logic routine with dozens of branches checking `IF machine_is_running THEN... ELSE IF machine_is_in_standby THEN...`, you create separate routines for each mode.
|
||||
|
||||
* **How it works here:**
|
||||
* The `Equipment` class is our main "machine".
|
||||
* We have separate classes for each state: `State_Standby`, `State_Running`, `State_Fail`.
|
||||
* The `Equipment` object holds onto the *current* state object (e.g., an instance of `State_Running`).
|
||||
* The main `loop()` simply tells the current state object to `update()`.
|
||||
* All the logic for the running mode is contained entirely within `State_Running.cpp`. All the logic for standby is in `State_Standby.cpp`.
|
||||
|
||||
* **Key Benefit:** To change how the machine behaves in "Running" mode, you only need to modify the `State_Running.cpp` file. You don't have to touch any other part of the system. To add a new "Maintenance" mode, you just create a new `State_Maintenance.cpp` file. This is much safer and easier than editing a giant `if/else` block.
|
||||
|
||||
* **Files to see:**
|
||||
* `States/State.h`: The "template" for all state classes.
|
||||
* `States/State_Running.cpp`: Defines all behavior when the unit is running.
|
||||
* `States/State_Standby.cpp`: Defines all behavior when the unit is in standby.
|
||||
* `Equipment/Equipment.cpp`: The main machine that `changeState()`s between different modes.
|
||||
|
||||
---
|
||||
|
||||
### 2. The Strategy Pattern: "How should this specific value behave?"
|
||||
|
||||
Within a single operating mode (like "Running"), different components might have different behaviors. For example, one fan's speed might be constant, another might ramp up and down, and a sensor reading might fluctuate randomly to simulate real-world conditions.
|
||||
|
||||
The **Strategy Pattern** lets us define these individual behaviors as interchangeable "algorithms" or "strategies".
|
||||
|
||||
* **Analogy:** Think of a function block in a PLC. You might have a `RAMP` block, a `PID` block, or a `SQUARE_WAVE_GENERATOR` block. The Strategy Pattern lets us create these as software objects. We can then "assign" a behavior strategy to a specific Modbus point.
|
||||
|
||||
* **How it works here:**
|
||||
* We have a family of "Strategy" classes: `RampStrategy`, `SawStrategy`, `RandomStrategy`, `SquareStrategy`, etc.
|
||||
* Inside a state file like `State_Running.cpp`, we assign these strategies to specific Modbus points. For example:
|
||||
```cpp
|
||||
// From State_Running.cpp
|
||||
// Assign a ramp behavior to Fan #5
|
||||
addStrategy("Speed EC Fan #5", new RampStrategy(100.0f, 1.5f, 2000));
|
||||
// Assign a random behavior to Fan #4
|
||||
addStrategy("Speed EC Fan #4", new RandomStrategy(1000));
|
||||
// Assign a sawtooth wave behavior to Fan #3
|
||||
addStrategy("Speed EC Fan #3", new SawStrategy(50.0f, 100.0f, 5.0f, 750));
|
||||
```
|
||||
|
||||
* **Key Benefit:** This makes the emulator incredibly dynamic. You can easily change the behavior of any point just by swapping out its strategy. Want Fan #4 to have a square wave pattern instead of random? Just change one line in `State_Running.cpp`. You don't have to rewrite any core logic.
|
||||
|
||||
* **Files to see:**
|
||||
* `Strategies/Strategy_Behavior.h`: The "template" for all behavior strategies.
|
||||
* `Strategies/Strategy_Ramp.h`, `Strategies/Strategy_Saw.h`, etc.: The specific, reusable behavior algorithms.
|
||||
* `States/State_Running.cpp`: Where strategies are assigned to Modbus points for that state.
|
||||
|
||||
---
|
||||
|
||||
### 3. The Decorator Pattern: "How do we handle special data types?"
|
||||
|
||||
Modbus registers are fundamentally just 16-bit integers. However, in the real world, we use these integers to represent many different data types: booleans (coils), scaled integers (e.g., `value * 10`), 32-bit long integers, and 32-bit floating-point numbers.
|
||||
|
||||
The **Decorator Pattern** lets us "wrap" a basic Modbus point to add this extra functionality for handling data types without creating a whole new class for every possible combination.
|
||||
|
||||
* **Analogy:** Think of a basic 4-20mA analog input card. That's your base object. Now, you add a "scaling block" in your PLC to convert the raw 4-20mA signal into a temperature in Celsius. That scaling block is a "Decorator". It doesn't change the input card, it just wraps its output to make it more useful.
|
||||
|
||||
* **How it works here:**
|
||||
* We start with a basic `ModbusPoint` (like `ModbusHreg` for a Holding Register).
|
||||
* If a point needs to be treated as a float, we "decorate" or "wrap" it with a `ModbusFloatDecorator`. This decorator knows how to take two 16-bit registers and combine them into a single 32-bit float value, and vice-versa.
|
||||
* If a point needs to be scaled, we can wrap it with a `ModbusScaleDecorator`.
|
||||
|
||||
* **Key Benefit:** This keeps our code clean and avoids an explosion of classes. We don't need `ModbusFloatHoldingRegister`, `ModbusScaledHoldingRegister`, `ModbusLongInputRegister`, etc. We have our basic point types (`Coil`, `Hreg`, `Ireg`) and we simply "decorate" them with the data handling logic they need. This is all handled automatically by the `ModbusPointFactory`.
|
||||
|
||||
* **Files to see:**
|
||||
* `Categories/ModbusPoint.h`: The base for all points.
|
||||
* `Categories/ModbusPointDecorator.h`: The base "wrapper" class.
|
||||
* `Categories/ModbusFloatDecorator.h`: A specific wrapper that adds floating-point logic.
|
||||
* `Categories/ModbusPointFactory.cpp`: The factory that automatically creates and decorates points based on the `config.h` map.
|
||||
|
||||
## Project Structure
|
||||
|
||||
* `BaseEmulator.ino`: The main entry point of the Arduino program. It handles Wi-Fi setup, initializes the Modbus server, and runs the main loop.
|
||||
* `config.h`: The central configuration file. **This is where you define all the Modbus points for the device.** You set the register type, address, and description here.
|
||||
* `/Equipment`: Contains the `Equipment` class, which represents the overall state machine.
|
||||
* `/Categories`: Contains the classes for different types of Modbus points (`ModbusCoil`, `ModbusHreg`) and the Decorators (`ModbusFloatDecorator`).
|
||||
* `/States`: Contains the different operating modes for the `Equipment` (e.g., `State_Running`).
|
||||
* `/Strategies`: Contains the reusable behavior algorithms for Modbus points (e.g., `Strategy_Ramp`).
|
||||
|
||||
## How to Modify or Extend the Emulator
|
||||
|
||||
1. **To add or change a Modbus point:**
|
||||
* Open `config.h`.
|
||||
* Set Wifi parameters to communicate to the network.
|
||||
* Add a new line to the `mb_map` array, defining its category (e.g., `HR_FLOAT`), address, initial value, and a unique description.
|
||||
|
||||
2. **To change how a point behaves in the "Running" state:**
|
||||
* Open `States/State_Running.cpp`.
|
||||
* In the `RunningState()` constructor, find or add an `addStrategy()` call for that point's description.
|
||||
* Assign it a new or different strategy (e.g., change `new RandomStrategy(...)` to `new SingleValueStrategy(...)`).
|
||||
|
||||
3. **To add a new operating mode (e.g., "Cleaning Cycle"):**
|
||||
* Create new files: `States/State_Cleaning.h` and `States/State_Cleaning.cpp`.
|
||||
* Implement the logic for that mode, including adding strategies for how points should behave.
|
||||
* Update the state-switching logic (e.g., in `State_Running.cpp` or `State_Standby.cpp`) to allow transitioning into your new `CleaningState`.
|
||||
|
||||
---
|
||||
167
lib/Core/States/State.h
Normal file
167
lib/Core/States/State.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @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 "Categories/ModbusFloatDecorator.h"
|
||||
#include "Categories/ModbusPoint.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 Logic to apply all strategies created.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
*/
|
||||
virtual void _applyStrategies(Equipment<T>* 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<T>* equipment, const std::string& pointName);
|
||||
void setPointValue(Equipment<T>* equipment, const std::string& pointName, float value);
|
||||
void addStrategy(const std::string& pointDescription, Strategy_Behavior* strategy);
|
||||
std::map<std::string, Strategy_Behavior*> _strategies;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
float State<T>::getPointValue(Equipment<T>* equipment, const std::string& pointName) {
|
||||
ModbusPoint<T>* 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<ModbusFloatDecorator<T>*>(point)->getFloatValue();
|
||||
} else {
|
||||
// Otherwise, get the standard integer value
|
||||
return static_cast<float>(point->getValue());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void State<T>::setPointValue(Equipment<T>* equipment, const std::string& pointName, float value) {
|
||||
ModbusPoint<T>* point = equipment->getModbusPoint(pointName);
|
||||
if (!point) return;
|
||||
|
||||
if (point->getType() == PointType::FLOAT) {
|
||||
static_cast<ModbusFloatDecorator<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)) {
|
||||
ModbusPoint<T>* outputPoint = equipment->getModbusPoint(description);
|
||||
if (!outputPoint) continue;
|
||||
|
||||
float inputValue;
|
||||
|
||||
if (strategy->isPID()) {
|
||||
PIDStrategy* pid = static_cast<PIDStrategy*>(strategy);
|
||||
inputValue = getPointValue(equipment, pid->getInputSensorName());
|
||||
|
||||
ModbusPoint<T>* 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
|
||||
50
lib/Core/States/State_Fail.h
Normal file
50
lib/Core/States/State_Fail.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @file State_Fail.h
|
||||
* @brief Defines the FailState class for the device.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for the FailState, which represents
|
||||
* a state where the equipment has encountered an error or fault condition.
|
||||
*/
|
||||
#ifndef Fail_State_h
|
||||
#define Fail_State_h
|
||||
|
||||
#include "State.h" // Include the base class header
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
template<typename T> class Equipment;
|
||||
/**
|
||||
* @class FailState
|
||||
* @brief Represents a failure or alarm state of the equipment.
|
||||
*
|
||||
* In this state, the equipment is in a non-operational fault condition.
|
||||
* It can be configured to apply specific strategies to its Modbus points to
|
||||
* simulate a particular failure scenario (e.g., setting alarm bits, stopping fans).
|
||||
* It waits for a command to transition to another state, such as returning to
|
||||
* Standby after the fault is cleared.
|
||||
*/
|
||||
template<typename T>
|
||||
class FailState : public State<T> {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new FailState object.
|
||||
* This is where strategies for failure behavior would be initialized.
|
||||
*/
|
||||
FailState(const std::vector<std::string>& activeAlarms);
|
||||
|
||||
/**
|
||||
* @brief Executes the fail state's logic for one update cycle.
|
||||
* This method applies the state's strategies and checks for a state transition command.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
State<T>* update(Equipment<T>* equipment) override;
|
||||
/** @brief Logic to execute once when entering the fail state. */
|
||||
void enterState(Equipment<T>* equipment) override;
|
||||
/** @brief Logic to execute once when exiting the fail state. */
|
||||
void exitState(Equipment<T>* equipment) override;
|
||||
};
|
||||
#endif
|
||||
47
lib/Core/States/State_Running.h
Normal file
47
lib/Core/States/State_Running.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @file State_Running.h
|
||||
* @brief Defines the RunningState class for the device.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for the RunningState, which represents
|
||||
* the state where the equipment is actively performing its primary function.
|
||||
*/
|
||||
#ifndef Running_State_h
|
||||
#define Running_State_h
|
||||
|
||||
#include "State.h" // Include the base class header
|
||||
template<typename T> class Equipment;
|
||||
|
||||
/**
|
||||
* @class RunningState
|
||||
* @brief Represents the active running state of the equipment.
|
||||
*
|
||||
* In this state, the equipment is fully operational and performing its main
|
||||
* tasks. It applies a set of predefined strategies to its Modbus points to
|
||||
* simulate active behavior (e.g., fans running at various speeds) and waits
|
||||
* for a command to transition to another state.
|
||||
*/
|
||||
template<typename T>
|
||||
class RunningState : public State<T> {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new RunningState object.
|
||||
* Initializes the strategies for various Modbus points that are active
|
||||
* during the running state, such as setting fan speed behaviors.
|
||||
*/
|
||||
RunningState();
|
||||
|
||||
/**
|
||||
* @brief Executes the running state's logic for one update cycle.
|
||||
* This method applies the state's strategies and checks for a state transition command.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
State<T>* update(Equipment<T>* equipment) override;
|
||||
/** @brief Logic to execute once when entering the running state. */
|
||||
void enterState(Equipment<T>* equipment) override;
|
||||
/** @brief Logic to execute once when exiting the running state. */
|
||||
void exitState(Equipment<T>* equipment) override;
|
||||
};
|
||||
#endif
|
||||
48
lib/Core/States/State_Standby.h
Normal file
48
lib/Core/States/State_Standby.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file State_Standby.h
|
||||
* @brief Defines the StandbyState class for the device.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for the StandbyState, which represents
|
||||
* the state where the equipment is idle but ready to start. It defines
|
||||
* specific behaviors for Modbus points while in this state.
|
||||
*/
|
||||
#ifndef Standby_State_h
|
||||
#define Standby_State_h
|
||||
|
||||
#include "State.h" // Include the base class header
|
||||
|
||||
template<typename T> class Equipment;
|
||||
/**
|
||||
* @class StandbyState
|
||||
* @brief Represents the standby state of the equipment.
|
||||
*
|
||||
* In this state, the equipment is operational but not actively running its
|
||||
* primary function. It applies a set of predefined strategies to its Modbus
|
||||
* points to simulate standby behavior and waits for a command to transition
|
||||
* to another state (e.g., Running).
|
||||
*/
|
||||
template<typename T>
|
||||
class StandbyState : public State<T> {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new StandbyState object.
|
||||
* Initializes the strategies for various Modbus points that are active
|
||||
* during the standby state.
|
||||
*/
|
||||
StandbyState();
|
||||
|
||||
/**
|
||||
* @brief Executes the standby state's logic for one update cycle.
|
||||
* This method applies the state's strategies and checks for a state transition command.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
State<T>* update(Equipment<T>* equipment) override;
|
||||
/** @brief Logic to execute once when entering the standby state. */
|
||||
void enterState(Equipment<T>* equipment) override;
|
||||
/** @brief Logic to execute once when exiting the standby state. */
|
||||
void exitState(Equipment<T>* equipment) override;
|
||||
};
|
||||
#endif
|
||||
65
lib/Core/Strategies/Strategy_Behavior.h
Normal file
65
lib/Core/Strategies/Strategy_Behavior.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @file Strategy_Behavior.h
|
||||
* @brief Defines the abstract base class for all behavior strategies.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the interface for the Strategy design pattern. All concrete
|
||||
* value-generation algorithms (e.g., Ramp, Saw, Random) will inherit from
|
||||
* the Strategy_Behavior class defined here.
|
||||
*/
|
||||
#ifndef strategy_behavior_h
|
||||
#define strategy_behavior_h
|
||||
|
||||
/**
|
||||
* @class Strategy_Behavior
|
||||
* @brief Abstract base class for a value-generation strategy.
|
||||
*
|
||||
* This class defines the common interface for all strategies. It includes a
|
||||
* pure virtual `execute` method that must be implemented by concrete strategies
|
||||
* and a helper method `isReady` to manage the update timing.
|
||||
*/
|
||||
class Strategy_Behavior {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new Strategy_Behavior object.
|
||||
* @param interval The update interval in milliseconds. The strategy will only
|
||||
* be ready to execute after this interval has passed.
|
||||
*/
|
||||
Strategy_Behavior(unsigned long interval)
|
||||
: _interval(interval), _previousMillis(0) {}
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor.
|
||||
*/
|
||||
virtual ~Strategy_Behavior() = default;
|
||||
|
||||
/**
|
||||
* @brief Pure virtual method to execute the strategy's logic.
|
||||
* Concrete classes must implement this to define their behavior.
|
||||
* @param currentValue The current value of the point, which can be used by the strategy.
|
||||
* @return The newly calculated value.
|
||||
*/
|
||||
virtual float execute(float currentValue) = 0;
|
||||
|
||||
/**
|
||||
* @brief Checks if the strategy's update interval has elapsed.
|
||||
* @param currentMillis The current time from `millis()`.
|
||||
* @return True if the strategy should be executed, false otherwise.
|
||||
*/
|
||||
bool isReady(unsigned long currentMillis) {
|
||||
if (currentMillis - _previousMillis >= _interval) {
|
||||
_previousMillis = currentMillis;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual bool isPID() const { return false; }
|
||||
|
||||
protected:
|
||||
unsigned long _previousMillis; /**< @brief The timestamp of the last execution. */
|
||||
unsigned long _interval; /**< @brief How often the strategy should run, in milliseconds. */
|
||||
};
|
||||
|
||||
#endif
|
||||
73
lib/Core/Strategies/Strategy_PID.cpp
Normal file
73
lib/Core/Strategies/Strategy_PID.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file Strategy_PID.cpp
|
||||
* @brief Implementation of the PIDStrategy class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-08
|
||||
*/
|
||||
#include "Strategy_PID.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
// CORRECTED CONSTRUCTOR
|
||||
// You should pass in your gains here, but for now we'll add setters and initialize to 0.
|
||||
PIDStrategy::PIDStrategy(const std::string& setpointName, unsigned long interval, const std::string& inputSensorName)
|
||||
: Strategy_Behavior(interval), _setpointName(setpointName), _inputSensorName(inputSensorName) {
|
||||
|
||||
_kp = -2.0;
|
||||
_ki = -0.1;
|
||||
_kd = 0.0;
|
||||
_input = 0.0;
|
||||
_lastError = 0.0;
|
||||
_integral = 0.0;
|
||||
_lastTime = millis();
|
||||
}
|
||||
|
||||
// It's good practice to have methods to set your gains
|
||||
void PIDStrategy::setGains(float kp, float ki, float kd) {
|
||||
_kp = kp;
|
||||
_ki = ki;
|
||||
_kd = kd;
|
||||
}
|
||||
|
||||
void PIDStrategy::setSetpoint(float setpoint) {
|
||||
_setpoint = setpoint;
|
||||
}
|
||||
|
||||
// This function is less necessary if execute() takes the current value, but can be used for setting an initial state.
|
||||
void PIDStrategy::setInput(float input) {
|
||||
_input = input;
|
||||
}
|
||||
|
||||
|
||||
float PIDStrategy::execute(float currentValue) {
|
||||
unsigned long now = millis();
|
||||
float timeChange = (float)(now - _lastTime);
|
||||
|
||||
if (timeChange <= 0) {
|
||||
Serial.printf("PID strategy delta time 0.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
_input = currentValue;
|
||||
float error = _setpoint - _input;
|
||||
_integral += error * timeChange;
|
||||
if (_integral > 100.0) _integral = 100.0;
|
||||
if (_integral < -100.0) _integral = -100.0;
|
||||
|
||||
float derivative = (error - _lastError) / timeChange;
|
||||
float output = (_kp * error) + (_ki * _integral) + (_kd * derivative);
|
||||
_lastError = error;
|
||||
_lastTime = now;
|
||||
|
||||
if (output > 100.0) output = 100.0;
|
||||
if (output < 0.0) output = 0.0;
|
||||
Serial.printf("PID timeChange: %f.\n", timeChange);
|
||||
Serial.printf("PID _input: %f.\n", _input);
|
||||
Serial.printf("PID error: %f.\n", error);
|
||||
Serial.printf("PID _integral: %f.\n", _integral);
|
||||
Serial.printf("PID derivative: %f.\n", derivative);
|
||||
Serial.printf("PID _kp: %f.\n", _kp);
|
||||
Serial.printf("PID _ki: %f.\n", _ki);
|
||||
Serial.printf("PID _kd: %f.\n", _kd);
|
||||
Serial.printf("PID output: %f.\n", output);
|
||||
return output;
|
||||
}
|
||||
43
lib/Core/Strategies/Strategy_PID.h
Normal file
43
lib/Core/Strategies/Strategy_PID.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// In BaseEmulator/Strategies/Strategy_PID.h
|
||||
|
||||
#ifndef PID_Strategy_h
|
||||
#define PID_Strategy_h
|
||||
|
||||
#include "Strategy_Behavior.h"
|
||||
#include <string>
|
||||
|
||||
class PIDStrategy : public Strategy_Behavior {
|
||||
public:
|
||||
// MODIFIED CONSTRUCTOR: Takes the setpoint, interval, and the name of the input sensor.
|
||||
PIDStrategy(const std::string& setpointName, unsigned long interval, const std::string& inputSensorName);
|
||||
|
||||
float execute(float currentValue) override;
|
||||
|
||||
// Add a getter for the sensor name
|
||||
std::string getInputSensorName() const { return _inputSensorName; }
|
||||
|
||||
std::string getSetpointName() const { return _setpointName; }
|
||||
|
||||
// Add this virtual function to easily identify this strategy as a PID
|
||||
bool isPID() const override { return true; }
|
||||
|
||||
// ... (other methods like setSetpoint, setGains)
|
||||
void setSetpoint(float setpoint);
|
||||
void setGains(float kp, float ki, float kd);
|
||||
void setInput(float input);
|
||||
|
||||
|
||||
private:
|
||||
// ... (PID variables like _kp, _ki, _kd, etc.)
|
||||
float _kp;
|
||||
float _ki;
|
||||
float _kd;
|
||||
unsigned long _lastTime;
|
||||
float _setpoint;
|
||||
float _input;
|
||||
float _lastError;
|
||||
float _integral;
|
||||
std::string _inputSensorName; // <-- Add this to store the name of our input
|
||||
std::string _setpointName; // <-- Add this to store the name of our input
|
||||
};
|
||||
#endif
|
||||
51
lib/Core/Strategies/Strategy_Ramp.cpp
Normal file
51
lib/Core/Strategies/Strategy_Ramp.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file Strategy_Ramp.cpp
|
||||
* @brief Implementation of the RampStrategy class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*/
|
||||
#include "Strategy_Ramp.h"
|
||||
#include <Arduino.h>
|
||||
/**
|
||||
* @brief Constructs a new RampStrategy object.
|
||||
*
|
||||
* Initializes the ramp strategy by passing the update interval to the
|
||||
* base Strategy_Behavior class and storing the target value and step.
|
||||
*
|
||||
* @param targetValue The destination value for the ramp.
|
||||
* @param step The amount to increment or decrement on each execution.
|
||||
* @param interval The time in milliseconds between each value change.
|
||||
*/
|
||||
RampStrategy::RampStrategy(float targetValue, float step, unsigned long interval)
|
||||
: Strategy_Behavior(interval), _targetValue(targetValue), _step(step) {}
|
||||
|
||||
/**
|
||||
* @brief Calculates the next value in the ramp sequence towards a target.
|
||||
*
|
||||
* This method compares the current value to the target value and returns
|
||||
* the current value incremented or decremented by the step amount. To prevent
|
||||
* overshooting, if the next step would pass the target, it returns the target
|
||||
* value directly. If the target is already reached, it returns the current value.
|
||||
*
|
||||
* @param currentValue The current value, used to determine the next step.
|
||||
* @return The next value in the ramp sequence.
|
||||
*/
|
||||
float RampStrategy::execute(float currentValue) {
|
||||
if (currentValue > _targetValue) {
|
||||
// Prevent overshooting the target when decrementing
|
||||
Serial.println("Ramp strategy going down.");
|
||||
return (currentValue - _step < _targetValue) ? _targetValue : currentValue - _step;
|
||||
} else if (currentValue < _targetValue) {
|
||||
// Prevent overshooting the target when incrementing
|
||||
Serial.println("Ramp strategy going up.");
|
||||
return (currentValue + _step > _targetValue) ? _targetValue : currentValue + _step;
|
||||
} else { // currentValue is already at the target
|
||||
Serial.println("Ramp strategy at target.");
|
||||
return _targetValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void RampStrategy::setTarget(float targetValue) {
|
||||
_targetValue = targetValue;
|
||||
}
|
||||
48
lib/Core/Strategies/Strategy_Ramp.h
Normal file
48
lib/Core/Strategies/Strategy_Ramp.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file Strategy_Ramp.h
|
||||
* @brief Defines the RampStrategy class for ramping a value towards a target.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a behavior strategy that incrementally
|
||||
* changes a value until it reaches a specified target value.
|
||||
*/
|
||||
#ifndef Ramp_Strategy_h
|
||||
#define Ramp_Strategy_h
|
||||
|
||||
#include "Strategy_Behavior.h"
|
||||
|
||||
/**
|
||||
* @class RampStrategy
|
||||
* @brief A strategy that moves a value towards a target by a fixed step.
|
||||
*
|
||||
* This class implements the Strategy_Behavior interface to produce a ramping
|
||||
* effect. On each `execute` call, it increments or decrements the current
|
||||
* value by a fixed step until the target value is reached.
|
||||
*/
|
||||
class RampStrategy : public Strategy_Behavior {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new RampStrategy object.
|
||||
* @param targetValue The destination value for the ramp.
|
||||
* @param step The amount to increment or decrement on each execution.
|
||||
* @param interval The time in milliseconds between each value change.
|
||||
*/
|
||||
RampStrategy(float targetValue, float step, unsigned long interval);
|
||||
/**
|
||||
* @brief Executes the strategy to set the target setpoint in the ramp strategy.
|
||||
* @param targetValue The target value that is going to be set.
|
||||
*/
|
||||
void setTarget(float targetValue);
|
||||
/**
|
||||
* @brief Executes the strategy to get the next value in the ramp sequence.
|
||||
* @param currentValue The current value, used to determine the next step.
|
||||
* @return The next value in the ramp sequence.
|
||||
*/
|
||||
float execute(float currentValue) override;
|
||||
|
||||
private:
|
||||
float _targetValue; /**< @brief The target value that the strategy will ramp towards. */
|
||||
float _step; /**< @brief The value to add or subtract on each execution. */
|
||||
};
|
||||
#endif
|
||||
36
lib/Core/Strategies/Strategy_Random.cpp
Normal file
36
lib/Core/Strategies/Strategy_Random.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @file Strategy_Random.cpp
|
||||
* @brief Implementation of the RandomStrategy class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*/
|
||||
#include "Strategy_Random.h"
|
||||
#include <cstdlib>
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* @brief Constructs a new RandomStrategy object.
|
||||
*
|
||||
* Initializes the random strategy by passing the update interval to the
|
||||
* base Strategy_Behavior class.
|
||||
*
|
||||
* @param interval The time in milliseconds between each value generation.
|
||||
*/
|
||||
RandomStrategy::RandomStrategy(unsigned long interval)
|
||||
: Strategy_Behavior(interval) {}
|
||||
|
||||
/**
|
||||
* @brief Generates a new random value between 0.0 and 100.0.
|
||||
*
|
||||
* This method calculates a random integer between 0 and 1000 and divides
|
||||
* it by 10.0 to produce a floating-point number. The `currentValue`
|
||||
* parameter is ignored.
|
||||
*
|
||||
* @param currentValue The current value of the Modbus point (ignored).
|
||||
* @return A random floating-point number between 0.0 and 100.0.
|
||||
*/
|
||||
float RandomStrategy::execute(float currentValue) {
|
||||
int randomNum = rand() % 1001;
|
||||
Serial.println("Random strategy.");
|
||||
return randomNum / 10.0;
|
||||
}
|
||||
37
lib/Core/Strategies/Strategy_Random.h
Normal file
37
lib/Core/Strategies/Strategy_Random.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @file Strategy_Random.h
|
||||
* @brief Defines the RandomStrategy class for generating random values.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a behavior strategy that generates
|
||||
* a random floating-point number between 0.0 and 100.0.
|
||||
*/
|
||||
#ifndef Random_Strategy_h
|
||||
#define Random_Strategy_h
|
||||
|
||||
#include "Strategy_Behavior.h"
|
||||
|
||||
/**
|
||||
* @class RandomStrategy
|
||||
* @brief A strategy that generates a random value on each execution.
|
||||
*
|
||||
* This class implements the Strategy_Behavior interface to produce a random
|
||||
* value. Each time `execute` is called, it returns a new random float
|
||||
* between 0.0 and 100.0.
|
||||
*/
|
||||
class RandomStrategy : public Strategy_Behavior {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new RandomStrategy object.
|
||||
* @param interval The time in milliseconds between each value generation.
|
||||
*/
|
||||
RandomStrategy(unsigned long interval);
|
||||
/**
|
||||
* @brief Executes the strategy to generate a new random value.
|
||||
* @param currentValue The current value of the Modbus point (ignored).
|
||||
* @return A random floating-point number between 0.0 and 100.0.
|
||||
*/
|
||||
float execute(float currentValue) override;
|
||||
};
|
||||
#endif
|
||||
54
lib/Core/Strategies/Strategy_Saw.cpp
Normal file
54
lib/Core/Strategies/Strategy_Saw.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @file Strategy_Saw.cpp
|
||||
* @brief Implementation of the SawStrategy class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*/
|
||||
#include "Strategy_Saw.h"
|
||||
#include <Arduino.h>
|
||||
/**
|
||||
* @brief Constructs a new SawStrategy object.
|
||||
*
|
||||
* Initializes the sawtooth wave strategy by passing the update interval to the
|
||||
* base Strategy_Behavior class and storing the wave's parameters.
|
||||
*
|
||||
* @param minValue The lower bound of the sawtooth wave.
|
||||
* @param maxValue The upper bound of the sawtooth wave.
|
||||
* @param step The amount to increment or decrement on each execution.
|
||||
* @param interval The time in milliseconds between each value change.
|
||||
*/
|
||||
SawStrategy::SawStrategy(float minValue, float maxValue, float step, unsigned long interval)
|
||||
: Strategy_Behavior(interval), _minValue(minValue), _maxValue(maxValue), _step(step) {}
|
||||
|
||||
void SawStrategy::setMinValue(float minValue) {
|
||||
_minValue = minValue;
|
||||
}
|
||||
|
||||
void SawStrategy::setMaxValue(float maxValue) {
|
||||
_maxValue = maxValue;
|
||||
}
|
||||
/**
|
||||
* @brief Calculates the next value in the sawtooth wave sequence.
|
||||
*
|
||||
* This method checks if the current value has reached the upper or lower
|
||||
* bounds and reverses the direction if necessary. It then returns the
|
||||
* current value incremented or decremented by the step amount.
|
||||
*
|
||||
* @param currentValue The current value, used to determine the next step.
|
||||
* @return The next value in the sawtooth sequence.
|
||||
*/
|
||||
float SawStrategy::execute(float currentValue) {
|
||||
if (currentValue >= _maxValue) {
|
||||
_goingUp = false;
|
||||
} else if (currentValue <= _minValue) {
|
||||
_goingUp = true;
|
||||
}
|
||||
|
||||
if (_goingUp) {
|
||||
Serial.println("Saw Strategy going up.");
|
||||
return currentValue + _step;
|
||||
} else {
|
||||
Serial.println("Saw Strategy going down.");
|
||||
return currentValue - _step;
|
||||
}
|
||||
}
|
||||
54
lib/Core/Strategies/Strategy_Saw.h
Normal file
54
lib/Core/Strategies/Strategy_Saw.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @file Strategy_Saw.h
|
||||
* @brief Defines the SawStrategy class for generating a sawtooth wave pattern.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a behavior strategy that creates a
|
||||
* sawtooth (or triangular) wave. The value ramps up from a minimum to a
|
||||
* maximum and then ramps back down, repeating the cycle.
|
||||
*/
|
||||
#ifndef Saw_Strategy_h
|
||||
#define Saw_Strategy_h
|
||||
|
||||
#include "Strategy_Behavior.h"
|
||||
|
||||
/**
|
||||
* @class SawStrategy
|
||||
* @brief A strategy that generates a value that moves up and down between two bounds.
|
||||
*
|
||||
* This class implements the Strategy_Behavior interface to produce a sawtooth
|
||||
* wave. On each `execute` call, it increments or decrements the current value
|
||||
* by a fixed step. The direction reverses when the value hits the minimum or
|
||||
* maximum bound.
|
||||
*/
|
||||
class SawStrategy : public Strategy_Behavior {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new SawStrategy object.
|
||||
* @param minValue The lower bound of the sawtooth wave.
|
||||
* @param maxValue The upper bound of the sawtooth wave.
|
||||
* @param step The amount to increment or decrement on each execution.
|
||||
* @param interval The time in milliseconds between each value change.
|
||||
*/
|
||||
SawStrategy(float minValue, float maxValue, float step, unsigned long interval);
|
||||
/**
|
||||
* @brief Executes the strategy to get the next value in the sawtooth wave.
|
||||
* @param currentValue The current value, used to determine the next step.
|
||||
* @return The next value in the sawtooth sequence.
|
||||
*/
|
||||
float execute(float currentValue) override;
|
||||
|
||||
void setMinValue(float minValue);
|
||||
|
||||
void setMaxValue(float maxValue);
|
||||
|
||||
|
||||
|
||||
private:
|
||||
float _minValue; /**< @brief The lower bound of the sawtooth wave. */
|
||||
float _maxValue; /**< @brief The upper bound of the sawtooth wave. */
|
||||
float _step; /**< @brief The value to add or subtract on each execution. */
|
||||
bool _goingUp = true; /**< @brief Tracks the current direction of the wave (up or down). */
|
||||
};
|
||||
#endif
|
||||
45
lib/Core/Strategies/Strategy_SingleValue.cpp
Normal file
45
lib/Core/Strategies/Strategy_SingleValue.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @file Strategy_SingleValue.cpp
|
||||
* @brief Implementation of the SingleValueStrategy class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*/
|
||||
#include "Strategy_SingleValue.h"
|
||||
#include <cstdlib>
|
||||
#include <Arduino.h>
|
||||
/**
|
||||
* @brief Constructs a new SingleValueStrategy object.
|
||||
*
|
||||
* Initializes the strategy by passing the update interval to the base
|
||||
* Strategy_Behavior class and storing the base setpoint value.
|
||||
*
|
||||
* @param setpoint The base value around which noise will be generated.
|
||||
* @param interval The time in milliseconds between each value generation.
|
||||
*/
|
||||
SingleValueStrategy::SingleValueStrategy(float setpoint, float noiseMagnitude, unsigned long interval)
|
||||
: Strategy_Behavior(interval), _setpoint(setpoint), _noiseMagnitude(noiseMagnitude) {}
|
||||
|
||||
void SingleValueStrategy::setSetpoint(float setpoint) {
|
||||
_setpoint = setpoint;
|
||||
}
|
||||
/**
|
||||
* @brief Generates a new value by adding random noise to the setpoint.
|
||||
*
|
||||
* This method calculates a random noise value between -10.0 and +10.0 and
|
||||
* adds it to the stored setpoint. The `currentValue` parameter is ignored.
|
||||
*
|
||||
* @param currentValue The current value of the Modbus point (ignored).
|
||||
* @return The setpoint with added random noise.
|
||||
*/
|
||||
float SingleValueStrategy::execute(float currentValue) {
|
||||
if(_noiseMagnitude <= 0){
|
||||
Serial.println("Single value strategy static.");
|
||||
return _setpoint;
|
||||
}
|
||||
|
||||
int noiseInt = rand() % 201;
|
||||
noiseInt -= 100;
|
||||
float noise = (noiseInt / 100) * _noiseMagnitude;
|
||||
Serial.println("Single value strategy with noise.");
|
||||
return _setpoint + noise;
|
||||
}
|
||||
44
lib/Core/Strategies/Strategy_SingleValue.h
Normal file
44
lib/Core/Strategies/Strategy_SingleValue.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @file Strategy_SingleValue.h
|
||||
* @brief Defines the SingleValueStrategy class for setup a value with noise.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a behavior strategy that sets the
|
||||
* value to a setpoint and the generates simulates noise with a random number.
|
||||
*/
|
||||
#ifndef SingleValue_strategy_h
|
||||
#define SingleValue_strategy_h
|
||||
#include "Strategy_Behavior.h"
|
||||
|
||||
/**
|
||||
* @class SingleValueStrategy
|
||||
* @brief A strategy that sets a value to a setpoint and generates noise around it.
|
||||
*
|
||||
* This class implements the Strategy_Behavior interface to produce a noise
|
||||
* pattern. Each time `execute` is called, it add a random number (+/-10) to
|
||||
* emulate noise around the value
|
||||
*/
|
||||
class SingleValueStrategy : public Strategy_Behavior {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new SquareStrategy object.
|
||||
* @param setpoiny Target value.
|
||||
* @param interval The time in milliseconds between each value toggle.
|
||||
*/
|
||||
SingleValueStrategy(float setpoint, float noiseMagnitude, unsigned long interval);
|
||||
/**
|
||||
* @brief Executes the strategy to get the next random value around setpoint.
|
||||
* @param currentValue The current value of the Modbus point (ignored in this strategy).
|
||||
* @return The next value in the sequence, either the lower or upper bound.
|
||||
*/
|
||||
float execute(float currentValue) override;
|
||||
|
||||
void setSetpoint(float setpoint);
|
||||
|
||||
|
||||
private:
|
||||
float _setpoint;
|
||||
float _noiseMagnitude;
|
||||
};
|
||||
#endif
|
||||
44
lib/Core/Strategies/Strategy_Square.cpp
Normal file
44
lib/Core/Strategies/Strategy_Square.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @file Strategy_Square.cpp
|
||||
* @brief Implementation of the SquareStrategy class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*/
|
||||
#include "Strategy_Square.h"
|
||||
#include <Arduino.h>
|
||||
/**
|
||||
* @brief Constructs a new SquareStrategy object.
|
||||
*
|
||||
* Initializes the square wave strategy by passing the update interval to the
|
||||
* base Strategy_Behavior class and storing the lower and upper bounds.
|
||||
*
|
||||
* @param lowerValue The lower value of the square wave.
|
||||
* @param upperValue The upper value of the square wave.
|
||||
* @param interval The time in milliseconds between each value toggle.
|
||||
*/
|
||||
SquareStrategy::SquareStrategy(float lowerValue, float upperValue, unsigned long interval)
|
||||
: Strategy_Behavior(interval), _lowerValue(lowerValue), _upperValue(upperValue) {}
|
||||
|
||||
/**
|
||||
* @brief Toggles between the upper and lower values on each execution.
|
||||
* @param currentValue The current value of the Modbus point (ignored).
|
||||
* @return The next value in the square wave sequence.
|
||||
*/
|
||||
void SquareStrategy::setLowerValue(float lowerValue) {
|
||||
_lowerValue = lowerValue;
|
||||
}
|
||||
|
||||
void SquareStrategy::setUpperValue(float upperValue) {
|
||||
_upperValue = upperValue;
|
||||
}
|
||||
|
||||
float SquareStrategy::execute(float currentValue) {
|
||||
_upState = !_upState;
|
||||
if(_upState){
|
||||
Serial.println("Square Strategy high state.");
|
||||
return _upperValue;
|
||||
} else {
|
||||
Serial.println("Square Strategy low state.");
|
||||
return _lowerValue;
|
||||
}
|
||||
}
|
||||
45
lib/Core/Strategies/Strategy_Square.h
Normal file
45
lib/Core/Strategies/Strategy_Square.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @file Strategy_Square.h
|
||||
* @brief Defines the SquareStrategy class for generating a square wave pattern.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a behavior strategy that alternates
|
||||
* between a lower and an upper value, creating a square wave effect.
|
||||
*/
|
||||
#ifndef square_strategy_h
|
||||
#define square_strategy_h
|
||||
#include "Strategy_Behavior.h"
|
||||
|
||||
/**
|
||||
* @class SquareStrategy
|
||||
* @brief A strategy that alternates between a lower and an upper value on each execution.
|
||||
*
|
||||
* This class implements the Strategy_Behavior interface to produce a square wave
|
||||
* pattern. Each time `execute` is called, it toggles between returning the
|
||||
* `_lowerValue` and the `_upperValue`.
|
||||
*/
|
||||
class SquareStrategy : public Strategy_Behavior {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new SquareStrategy object.
|
||||
* @param lowerValue The lower value of the square wave.
|
||||
* @param upperValue The upper value of the square wave.
|
||||
* @param interval The time in milliseconds between each value toggle.
|
||||
*/
|
||||
SquareStrategy(float lowerValue, float upperValue, unsigned long interval);
|
||||
/**
|
||||
* @brief Executes the strategy to get the next value in the square wave.
|
||||
* @param currentValue The current value of the Modbus point (ignored in this strategy).
|
||||
* @return The next value in the sequence, either the lower or upper bound.
|
||||
*/
|
||||
float execute(float currentValue) override;
|
||||
void setLowerValue(float lowerValue);
|
||||
void setUpperValue(float upperValue);
|
||||
|
||||
private:
|
||||
float _lowerValue; /**< @brief The lower bound of the square wave. */
|
||||
float _upperValue; /**< @brief The upper bound of the square wave. */
|
||||
bool _upState = true; /**< @brief The internal state to track whether to return the upper or lower value. */
|
||||
};
|
||||
#endif
|
||||
38
lib/Core/Strategies/Strategy_Totalizer.cpp
Normal file
38
lib/Core/Strategies/Strategy_Totalizer.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file Strategy_Totalizer.cpp
|
||||
* @brief Implementation of the TotalizerStrategy class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*/
|
||||
#include "Strategy_Totalizer.h"
|
||||
#include <cstdlib>
|
||||
#include <Arduino.h>
|
||||
/**
|
||||
* @brief Constructs a new TotalizerStrategy object.
|
||||
*
|
||||
* Initializes the square wave strategy by passing the update interval to the
|
||||
* base Strategy_Behavior class and storing the lower and upper bounds.
|
||||
*
|
||||
* @param lowerValue The lower value of the square wave.
|
||||
* @param upperValue The upper value of the square wave.
|
||||
* @param interval The time in milliseconds between each value toggle.
|
||||
*/
|
||||
TotalizerStrategy::TotalizerStrategy(unsigned long interval)
|
||||
: Strategy_Behavior(interval) {
|
||||
int randomNum = rand() % 1001;
|
||||
_currentValue = randomNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles between the upper and lower values on each execution.
|
||||
* @param currentValue The current value of the Modbus point (ignored).
|
||||
* @return The next value in the square wave sequence.
|
||||
*/
|
||||
float TotalizerStrategy::execute(float currentValue) {
|
||||
_currentValue += 1;
|
||||
if (_currentValue >60000) {
|
||||
_currentValue = 0;
|
||||
}
|
||||
Serial.println("Totalizer strategy adding up.");
|
||||
return _currentValue;
|
||||
}
|
||||
42
lib/Core/Strategies/Strategy_Totalizer.h
Normal file
42
lib/Core/Strategies/Strategy_Totalizer.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file Strategy_Square.h
|
||||
* @brief Defines the SquareStrategy class for generating a square wave pattern.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the definition for a behavior strategy that alternates
|
||||
* between a lower and an upper value, creating a square wave effect.
|
||||
*/
|
||||
#ifndef totalizer_strategy_h
|
||||
#define totalizer_strategy_h
|
||||
#include "Strategy_Behavior.h"
|
||||
|
||||
/**
|
||||
* @class SquareStrategy
|
||||
* @brief A strategy that alternates between a lower and an upper value on each execution.
|
||||
*
|
||||
* This class implements the Strategy_Behavior interface to produce a square wave
|
||||
* pattern. Each time `execute` is called, it toggles between returning the
|
||||
* `_lowerValue` and the `_upperValue`.
|
||||
*/
|
||||
class TotalizerStrategy : public Strategy_Behavior {
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a new SquareStrategy object.
|
||||
* @param lowerValue The lower value of the square wave.
|
||||
* @param upperValue The upper value of the square wave.
|
||||
* @param interval The time in milliseconds between each value toggle.
|
||||
*/
|
||||
TotalizerStrategy(unsigned long interval);
|
||||
/**
|
||||
* @brief Executes the strategy to get the next value in the square wave.
|
||||
* @param currentValue The current value of the Modbus point (ignored in this strategy).
|
||||
* @return The next value in the sequence, either the lower or upper bound.
|
||||
*/
|
||||
float execute(float currentValue) override;
|
||||
|
||||
private:
|
||||
float _currentValue; /**< @brief The lower bound of the square wave. */
|
||||
|
||||
};
|
||||
#endif
|
||||
69
lib/Core/core.h
Normal file
69
lib/Core/core.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @file core.h
|
||||
* @brief Core constants and definitions for the Industrial Emulator project.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-06
|
||||
*
|
||||
* This file contains shared constants, enumerations, and definitions that are
|
||||
* used across both the main application firmware and the Core library.
|
||||
* Placing these in a central, shared header prevents duplication and makes
|
||||
* configuration easier.
|
||||
* @note This file is intended to be included in the main `.ino` file.
|
||||
*/
|
||||
#ifndef CORE_H
|
||||
#define CORE_H
|
||||
#include "Equipment/Equipment.h"
|
||||
|
||||
// Include the appropriate Modbus library header based on the build flag.
|
||||
// Define USE_MODBUS_IP in your build flags (e.g., platformio.ini) for Modbus IP.
|
||||
// Otherwise, it will default to Modbus RTU.
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h> // Or your specific Modbus IP library
|
||||
#else
|
||||
#include <ModbusRTU.h> // Or your specific Modbus RTU library
|
||||
#endif
|
||||
|
||||
const int COIL = 0; /**< @brief 0x: R/W Coil */
|
||||
const int DI = 1; /**< @brief 1x: R Discrete Inputs */
|
||||
const int IR = 3; /**< @brief 3x: R Input Register - Single word */
|
||||
const int IR_10X = 31; /**< @brief 3x: R Input Register - Single word, 10x scaled */
|
||||
const int IR_LONG = 32; /**< @brief 3x: R Input Register - Double word, Long type */
|
||||
const int IR_FLOAT = 33; /**< @brief 3x: R Input Register - Double word, Float encoding */
|
||||
const int HR = 4; /**< @brief 4x: R/W Holding Register - Single word */
|
||||
const int HR_10x = 41; /**< @brief 4x: R/W Holding Register - Single word, 10x scaled */
|
||||
const int HR_LONG = 42; /**< @brief 4x: R/W Holding Register - Double word, Long type */
|
||||
const int HR_FLOAT = 43; /**< @brief 4x: R/W Holding Register - Double word, Float encoding */
|
||||
|
||||
/**
|
||||
* @struct modbusMap
|
||||
* @brief Defines the structure for a single entry in the Modbus map.
|
||||
*/
|
||||
struct modbusMap
|
||||
{
|
||||
int category; /**< @brief The Modbus category (e.g., COIL, HR, IR_FLOAT). */
|
||||
int address; /**< @brief The Modbus address (0-9999). */
|
||||
int value; /**< @brief The initial value for the point. */
|
||||
char description[35];/**< @brief A descriptive name for the point. Used as a key for access. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The main loop previous milliseconds.
|
||||
*/
|
||||
unsigned long previousMillis = 0;
|
||||
|
||||
/**
|
||||
* @brief Global Modbus object instance.
|
||||
* The type is determined at compile time based on the USE_MODBUS_IP flag.
|
||||
*/
|
||||
#if defined(USE_MODBUS_IP)
|
||||
extern ModbusIP mb; // Use ModbusIP class
|
||||
/** @brief An instance of the Equipment class, representing the emulated Equipment unit. */
|
||||
Equipment<ModbusIP> EquipmentInstance(&mb);
|
||||
#else
|
||||
extern ModbusRTU mb; // Use ModbusRTU class
|
||||
/** @brief An instance of the Equipment class, representing the emulated Equipment unit. */
|
||||
Equipment<ModbusRTU> EquipmentInstance(&mb);
|
||||
#endif
|
||||
|
||||
|
||||
#endif // CORE_H
|
||||
46
lib/README
Normal file
46
lib/README
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
This directory is intended for project specific (private) libraries.
|
||||
PlatformIO will compile them to static libraries and link into the executable file.
|
||||
|
||||
The source code of each library should be placed in a separate directory
|
||||
("lib/your_library_name/[Code]").
|
||||
|
||||
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||
|
||||
|--lib
|
||||
| |
|
||||
| |--Bar
|
||||
| | |--docs
|
||||
| | |--examples
|
||||
| | |--src
|
||||
| | |- Bar.c
|
||||
| | |- Bar.h
|
||||
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||
| |
|
||||
| |--Foo
|
||||
| | |- Foo.c
|
||||
| | |- Foo.h
|
||||
| |
|
||||
| |- README --> THIS FILE
|
||||
|
|
||||
|- platformio.ini
|
||||
|--src
|
||||
|- main.c
|
||||
|
||||
Example contents of `src/main.c` using Foo and Bar:
|
||||
```
|
||||
#include <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
int main (void)
|
||||
{
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||
libraries by scanning project source files.
|
||||
|
||||
More information about PlatformIO Library Dependency Finder
|
||||
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||
931
lib/modbus-esp8266/Modbus.cpp
Normal file
931
lib/modbus-esp8266/Modbus.cpp
Normal file
@@ -0,0 +1,931 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
Core functions
|
||||
Copyright (C) 2014 Andr<64> Sarmento Barbosa
|
||||
2017-2023 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
#include "Modbus.h"
|
||||
|
||||
#if defined(MODBUS_GLOBAL_REGS)
|
||||
#if defined(MODBUS_USE_STL)
|
||||
std::vector<TRegister> Modbus::_regs;
|
||||
std::vector<TCallback> Modbus::_callbacks;
|
||||
#if defined(MODBUS_FILES)
|
||||
std::function<Modbus::ResultCode(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> Modbus::_onFile;
|
||||
#endif
|
||||
#else
|
||||
DArray<TRegister, 1, 1> Modbus::_regs;
|
||||
DArray<TCallback, 1, 1> Modbus::_callbacks;
|
||||
#if defined(MODBUS_FILES)
|
||||
cbModbusFileOp Modbus::_onFile = nullptr;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint16_t Modbus::callback(TRegister* reg, uint16_t val, TCallback::CallbackType t) {
|
||||
#define MODBUS_COMPARE_CB [reg, t](TCallback& cb){return cb.address == reg->address && cb.type == t;}
|
||||
uint16_t newVal = val;
|
||||
#if defined(MODBUS_USE_STL)
|
||||
std::vector<TCallback>::iterator it = _callbacks.begin();
|
||||
do {
|
||||
it = std::find_if(it, _callbacks.end(), MODBUS_COMPARE_CB);
|
||||
if (it != _callbacks.end()) {
|
||||
newVal = it->cb(reg, newVal);
|
||||
it++;
|
||||
}
|
||||
} while (it != _callbacks.end());
|
||||
#else
|
||||
size_t r = 0;
|
||||
do {
|
||||
r = _callbacks.find(MODBUS_COMPARE_CB, r);
|
||||
if (r < _callbacks.size())
|
||||
newVal = _callbacks[r].cb(reg, newVal);
|
||||
r++;
|
||||
} while (r < _callbacks.size());
|
||||
#endif
|
||||
return newVal;
|
||||
}
|
||||
|
||||
TRegister* Modbus::searchRegister(TAddress address) {
|
||||
#define MODBUS_COMPARE_REG [address](TRegister& addr){return (addr.address == address);}
|
||||
#if defined(MODBUS_USE_STL)
|
||||
std::vector<TRegister>::iterator it = std::find_if(_regs.begin(), _regs.end(), MODBUS_COMPARE_REG);
|
||||
if (it != _regs.end()) return &*it;
|
||||
#else
|
||||
size_t r = _regs.find(MODBUS_COMPARE_REG);
|
||||
if (r < _regs.size()) return _regs.entry(r);
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Modbus::addReg(TAddress address, uint16_t value, uint16_t numregs) {
|
||||
#if defined(MODBUS_MAX_REGS)
|
||||
if (_regs.size() + numregs > MODBUS_MAX_REGS) return false;
|
||||
#endif
|
||||
if (0xFFFF - address.address < numregs)
|
||||
numregs = 0xFFFF - address.address;
|
||||
for (uint16_t i = 0; i < numregs; i++) {
|
||||
if (!searchRegister(address + i))
|
||||
_regs.push_back({address + i, value});
|
||||
}
|
||||
//std::sort(_regs.begin(), _regs.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Modbus::Reg(TAddress address, uint16_t value) {
|
||||
TRegister* reg;
|
||||
reg = searchRegister(address); //search for the register address
|
||||
if (reg) { //if found then assign the register value to the new value.
|
||||
if (cbEnabled) {
|
||||
reg->value = callback(reg, value, TCallback::ON_SET);
|
||||
} else {
|
||||
reg->value = value;
|
||||
}
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t Modbus::Reg(TAddress address) {
|
||||
TRegister* reg;
|
||||
reg = searchRegister(address);
|
||||
if(reg)
|
||||
if (cbEnabled) {
|
||||
return callback(reg, reg->value, TCallback::ON_GET);
|
||||
} else {
|
||||
return reg->value;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Modbus::removeReg(TAddress address, uint16_t numregs) {
|
||||
TRegister* reg;
|
||||
bool atLeastOne = false;
|
||||
if (0xFFFF - address.address < numregs)
|
||||
numregs = 0xFFFF - address.address;
|
||||
for (uint16_t i = 0; i < numregs; i++) {
|
||||
reg = searchRegister(address + i);
|
||||
if (reg) {
|
||||
atLeastOne = true;
|
||||
removeOnSet(address + i);
|
||||
removeOnGet(address + i);
|
||||
#if defined(MODBUS_USE_STL)
|
||||
_regs.erase(std::remove( _regs.begin(), _regs.end(), *reg), _regs.end() );
|
||||
#else
|
||||
_regs.remove(_regs.find(MODBUS_COMPARE_REG));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return atLeastOne;
|
||||
}
|
||||
|
||||
bool Modbus::addReg(TAddress address, uint16_t* value, uint16_t numregs) {
|
||||
if (0xFFFF - address.address < numregs)
|
||||
numregs = 0xFFFF - address.address;
|
||||
for (uint16_t k = 0; k < numregs; k++)
|
||||
addReg(address + k, value[k]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Modbus::slavePDU(uint8_t* frame) {
|
||||
FunctionCode fcode = (FunctionCode)frame[0];
|
||||
uint16_t field1 = (uint16_t)frame[1] << 8 | (uint16_t)frame[2];
|
||||
uint16_t field2 = (uint16_t)frame[3] << 8 | (uint16_t)frame[4];
|
||||
uint16_t field3 = 0;
|
||||
uint16_t field4 = 0;
|
||||
uint16_t bytecount_calc;
|
||||
uint16_t k;
|
||||
ResultCode ex;
|
||||
switch (fcode) {
|
||||
case FC_WRITE_REG:
|
||||
//field1 = reg, field2 = value
|
||||
ex = _onRequest(fcode, {HREG(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
if (!Reg(HREG(field1), field2)) { //Check Address and execute (reg exists?)
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
if (Reg(HREG(field1)) != field2) { //Check for failure
|
||||
exceptionResponse(fcode, EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
_reply = REPLY_ECHO;
|
||||
_onRequestSuccess(fcode, {HREG(field1), field2});
|
||||
break;
|
||||
|
||||
case FC_READ_REGS:
|
||||
//field1 = startreg, field2 = numregs, header len = 3
|
||||
ex = _onRequest(fcode, {HREG(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
ex = readWords(HREG(field1), field2, fcode);
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
_onRequestSuccess(fcode, {HREG(field1), field2});
|
||||
break;
|
||||
|
||||
case FC_WRITE_REGS:
|
||||
//field1 = startreg, field2 = numregs, frame[5] = data lenght, header len = 6
|
||||
ex = _onRequest(fcode, {HREG(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS || 0xFFFF - field1 < field2 || frame[5] != 2 * field2) { //Check constrains
|
||||
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs)
|
||||
if (!searchRegister(HREG(field1) + k)) {
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!setMultipleWords((uint16_t*)(frame + 6), HREG(field1), field2)) {
|
||||
exceptionResponse(fcode, EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
successResponce(HREG(field1), field2, fcode);
|
||||
_reply = REPLY_NORMAL;
|
||||
_onRequestSuccess(fcode, {HREG(field1), field2});
|
||||
break;
|
||||
|
||||
case FC_READ_COILS:
|
||||
//field1 = startreg, field2 = numregs
|
||||
ex = _onRequest(fcode, {COIL(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
ex = readBits(COIL(field1), field2, fcode);
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
_onRequestSuccess(fcode, {COIL(field1), field2});
|
||||
break;
|
||||
|
||||
case FC_READ_INPUT_STAT:
|
||||
//field1 = startreg, field2 = numregs
|
||||
ex = _onRequest(fcode, {ISTS(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
ex = readBits(ISTS(field1), field2, fcode);
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
_onRequestSuccess(fcode, {ISTS(field1), field2});
|
||||
break;
|
||||
|
||||
case FC_READ_INPUT_REGS:
|
||||
//field1 = startreg, field2 = numregs
|
||||
ex = _onRequest(fcode, {IREG(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
ex = readWords(IREG(field1), field2, fcode);
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
_onRequestSuccess(fcode, {IREG(field1), field2});
|
||||
break;
|
||||
|
||||
case FC_WRITE_COIL:
|
||||
//field1 = reg, field2 = status, header len = 3
|
||||
ex = _onRequest(fcode, {COIL(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
if (field2 != 0xFF00 && field2 != 0x0000) { //Check value (status)
|
||||
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
if (!Reg(COIL(field1), field2)) { //Check Address and execute (reg exists?)
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
if (Reg(COIL(field1)) != field2) { //Check for failure
|
||||
exceptionResponse(fcode, EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
_reply = REPLY_ECHO;
|
||||
_onRequestSuccess(fcode, {COIL(field1), field2});
|
||||
break;
|
||||
|
||||
case FC_WRITE_COILS:
|
||||
//field1 = startreg, field2 = numregs, frame[5] = bytecount, header len = 6
|
||||
ex = _onRequest(fcode, {COIL(field1), field2});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
bytecount_calc = field2 / 8;
|
||||
if (field2%8) bytecount_calc++;
|
||||
if (field2 < 0x0001 || field2 > MODBUS_MAX_BITS || 0xFFFF - field1 < field2 || frame[5] != bytecount_calc) { //Check registers range and data size maches
|
||||
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
for (k = 0; k < field2; k++) { //Check Address (startreg...startreg + numregs)
|
||||
if (!searchRegister(COIL(field1) + k)) {
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!setMultipleBits(frame + 6, COIL(field1), field2)) {
|
||||
exceptionResponse(fcode, EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
successResponce(COIL(field1), field2, fcode);
|
||||
_reply = REPLY_NORMAL;
|
||||
_onRequestSuccess(fcode, {COIL(field1), field2});
|
||||
break;
|
||||
#if defined(MODBUS_FILES)
|
||||
case FC_READ_FILE_REC:
|
||||
if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size
|
||||
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
{
|
||||
uint8_t bufSize = 2; // 2 bytes for frame header
|
||||
uint8_t* recs = frame + 2; // Begin of sub-recs blocks
|
||||
uint8_t recsCount = frame[1] / 7; // Count of sub-rec blocks
|
||||
for (uint8_t p = 0; p < recsCount; p++) { // Calc output buffer size required
|
||||
//uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2];
|
||||
uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4];
|
||||
uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6];
|
||||
//Serial.printf("%d, %d, %d\n", fileNum, recNum, recLen);
|
||||
if (recs[0] != 0x06 || recNum > 0x270F) { // Wrong ref type or count of records
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
bufSize += recLen * 2 + 2; // 4 bytes for header + data
|
||||
recs += 7;
|
||||
}
|
||||
// if (bufSize > MODBUS_MAX_FRAME) { // Frame to return too large
|
||||
// exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
// return;
|
||||
// }
|
||||
uint8_t* srcFrame = _frame;
|
||||
_frame = (uint8_t*)malloc(bufSize);
|
||||
if (!_frame) {
|
||||
free(srcFrame);
|
||||
exceptionResponse(fcode, EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
_len = bufSize;
|
||||
recs = frame + 2; // Begin of sub-recs blocks
|
||||
uint8_t* data = _frame + 2;
|
||||
for (uint8_t p = 0; p < recsCount; p++) {
|
||||
uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2];
|
||||
uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4];
|
||||
uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6];
|
||||
ResultCode res = fileOp(fcode, fileNum, recNum, recLen, data + 2);
|
||||
if (res != EX_SUCCESS) { // File read failed
|
||||
free(srcFrame);
|
||||
exceptionResponse(fcode, res);
|
||||
return;
|
||||
}
|
||||
data[0] = recLen * 2 + 1;
|
||||
data[1] = 0x06;
|
||||
data += recLen * 2 + 2;
|
||||
recs += 7;
|
||||
}
|
||||
_frame[0] = fcode;
|
||||
_frame[1] = bufSize;
|
||||
_reply = REPLY_NORMAL;
|
||||
free(srcFrame);
|
||||
}
|
||||
break;
|
||||
case FC_WRITE_FILE_REC: {
|
||||
if (frame[1] < 0x09 || frame[1] > 0xFB) { // Wrong request data size
|
||||
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
uint8_t* recs = frame + 2; // Begin of sub-recs blocks
|
||||
while (recs < frame + frame[1]) {
|
||||
if (recs[0] != 0x06) {
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
uint16_t fileNum = (uint16_t)recs[1] << 8 | (uint16_t)recs[2];
|
||||
uint16_t recNum = (uint16_t)recs[3] << 8 | (uint16_t)recs[4];
|
||||
uint16_t recLen = (uint16_t)recs[5] << 8 | (uint16_t)recs[6];
|
||||
if (recs + recLen * 2 > frame + frame[1]) {
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
ResultCode res = fileOp(fcode, fileNum, recNum, recLen, recs + 7);
|
||||
if (res != EX_SUCCESS) { // File write failed
|
||||
exceptionResponse(fcode, res);
|
||||
return;
|
||||
}
|
||||
recs += 7 + recLen * 2;
|
||||
}
|
||||
}
|
||||
_reply = REPLY_ECHO;
|
||||
break;
|
||||
#endif
|
||||
case FC_MASKWRITE_REG:
|
||||
//field1 = reg, field2 = AND mask, field3 = OR mask
|
||||
// Result = (Current Contents AND And_Mask) OR (Or_Mask AND (NOT And_Mask))
|
||||
field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6];
|
||||
ex = _onRequest(fcode, {HREG(field1), field2, field3});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
field4 = Reg(HREG(field1));
|
||||
field4 = (field4 & field2) | (field3 & ~field2);
|
||||
if (!Reg(HREG(field1), field4)) { //Check Address and execute (reg exists?)
|
||||
exceptionResponse(fcode, EX_ILLEGAL_ADDRESS);
|
||||
return;
|
||||
}
|
||||
if (Reg(HREG(field1)) != field4) { //Check for failure
|
||||
exceptionResponse(fcode, EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
_reply = REPLY_ECHO;
|
||||
_onRequestSuccess(fcode, {HREG(field1), field2, field3});
|
||||
break;
|
||||
case FC_READWRITE_REGS:
|
||||
//field1 = readreg, field2 = read count, frame[9] = data lenght, header len = 10
|
||||
//field3 = wtitereg, field4 = write count
|
||||
field3 = (uint16_t)frame[5] << 8 | (uint16_t)frame[6];
|
||||
field4 = (uint16_t)frame[7] << 8 | (uint16_t)frame[8];
|
||||
ex = _onRequest(fcode, {HREG(field1), field2, HREG(field3), field4});
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
if (field2 < 0x0001 || field2 > MODBUS_MAX_WORDS ||
|
||||
field4 < 0x0001 || field4 > MODBUS_MAX_WORDS ||
|
||||
0xFFFF - field1 < field2 || 0xFFFF - field1 < field2 ||
|
||||
frame[9] != 2 * field4) { //Check value
|
||||
exceptionResponse(fcode, EX_ILLEGAL_VALUE);
|
||||
return;
|
||||
}
|
||||
if (!setMultipleWords((uint16_t*)(frame + 10), HREG(field3), field4)) {
|
||||
exceptionResponse(fcode, EX_SLAVE_FAILURE);
|
||||
return;
|
||||
}
|
||||
ex = readWords(HREG(field1), field2, fcode);
|
||||
if (ex != EX_SUCCESS) {
|
||||
exceptionResponse(fcode, ex);
|
||||
return;
|
||||
}
|
||||
_onRequestSuccess(fcode, {HREG(field1), field2, HREG(field3), field4});
|
||||
break;
|
||||
|
||||
default:
|
||||
ex = _onRequest(fcode, {frame + 1});
|
||||
if (ex != EX_PASSTHROUGH) {
|
||||
exceptionResponse(fcode, EX_ILLEGAL_FUNCTION);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Modbus::successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn) {
|
||||
free(_frame);
|
||||
_len = 5;
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) {
|
||||
_reply = REPLY_OFF;
|
||||
return;
|
||||
}
|
||||
_frame[0] = fn;
|
||||
_frame[1] = startreg.address >> 8;
|
||||
_frame[2] = startreg.address & 0x00FF;
|
||||
_frame[3] = numoutputs >> 8;
|
||||
_frame[4] = numoutputs & 0x00FF;
|
||||
}
|
||||
|
||||
void Modbus::exceptionResponse(FunctionCode fn, ResultCode excode) {
|
||||
free(_frame);
|
||||
_len = 2;
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) {
|
||||
_reply = REPLY_OFF;
|
||||
return;
|
||||
}
|
||||
_frame[0] = fn + 0x80;
|
||||
_frame[1] = excode;
|
||||
_reply = REPLY_NORMAL;
|
||||
}
|
||||
|
||||
void Modbus::getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs) {
|
||||
uint8_t bitn = 0;
|
||||
uint16_t i = 0;
|
||||
while (numregs--) {
|
||||
if (BIT_BOOL(Reg(startreg)))
|
||||
bitSet(frame[i], bitn);
|
||||
else
|
||||
bitClear(frame[i], bitn);
|
||||
bitn++; //increment the bit index
|
||||
if (bitn == 8) {
|
||||
i++;
|
||||
bitn = 0;
|
||||
}
|
||||
startreg++; //increment the register
|
||||
}
|
||||
}
|
||||
|
||||
void Modbus::getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) {
|
||||
for (uint8_t i = 0; i < numregs; i++) {
|
||||
frame[i] = __swap_16(Reg(startreg + i));
|
||||
}
|
||||
}
|
||||
|
||||
Modbus::ResultCode Modbus::readBits(TAddress startreg, uint16_t numregs, FunctionCode fn) {
|
||||
if (numregs < 0x0001 || numregs > MODBUS_MAX_BITS || (0xFFFF - startreg.address) < numregs)
|
||||
return EX_ILLEGAL_ADDRESS;
|
||||
//Check Address
|
||||
//Check only startreg. Is this correct?
|
||||
//When I check all registers in range I got errors in ScadaBR
|
||||
//I think that ScadaBR request more than one in the single request
|
||||
//when you have more then one datapoint configured from same type.
|
||||
#if defined(MODBUS_STRICT_REG)
|
||||
for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs)
|
||||
if (!searchRegister(startreg + k))
|
||||
return EX_ILLEGAL_ADDRESS;
|
||||
}
|
||||
#else
|
||||
if (!searchRegister(startreg))
|
||||
return EX_ILLEGAL_ADDRESS;
|
||||
#endif
|
||||
free(_frame);
|
||||
//Determine the message length = function type, byte count and
|
||||
//for each group of 8 registers the message length increases by 1
|
||||
_len = 2 + numregs/8;
|
||||
if (numregs % 8) _len++; //Add 1 to the message length for the partial byte.
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame)
|
||||
return EX_SLAVE_FAILURE;
|
||||
_frame[0] = fn;
|
||||
_frame[1] = _len - 2; //byte count (_len - function code and byte count)
|
||||
_frame[_len - 1] = 0; //Clean last probably partial byte
|
||||
getMultipleBits(_frame+2, startreg, numregs);
|
||||
_reply = REPLY_NORMAL;
|
||||
return EX_SUCCESS;
|
||||
}
|
||||
|
||||
Modbus::ResultCode Modbus::readWords(TAddress startreg, uint16_t numregs, FunctionCode fn) {
|
||||
//Check value (numregs)
|
||||
if (numregs < 0x0001 || numregs > MODBUS_MAX_WORDS || 0xFFFF - startreg.address < numregs)
|
||||
return EX_ILLEGAL_ADDRESS;
|
||||
#if defined(MODBUS_STRICT_REG)
|
||||
for (k = 0; k < numregs; k++) { //Check Address (startreg...startreg + numregs)
|
||||
if (!searchRegister(startreg + k))
|
||||
return EX_ILLEGAL_ADDRESS;
|
||||
}
|
||||
#else
|
||||
if (!searchRegister(startreg))
|
||||
return EX_ILLEGAL_ADDRESS;
|
||||
#endif
|
||||
free(_frame);
|
||||
_len = 2 + numregs * 2; //calculate the query reply message length. 2 bytes per register + 2 bytes for header
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame)
|
||||
return EX_SLAVE_FAILURE;
|
||||
_frame[0] = fn;
|
||||
_frame[1] = _len - 2; //byte count
|
||||
getMultipleWords((uint16_t*)(_frame + 2), startreg, numregs);
|
||||
_reply = REPLY_NORMAL;
|
||||
return EX_SUCCESS;
|
||||
}
|
||||
|
||||
bool Modbus::setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs) {
|
||||
uint8_t bitn = 0;
|
||||
uint16_t i = 0;
|
||||
bool result = true;
|
||||
while (numoutputs--) {
|
||||
Reg(startreg, BIT_VAL(bitRead(frame[i], bitn)));
|
||||
if (Reg(startreg) != BIT_VAL(bitRead(frame[i], bitn)))
|
||||
result = false;
|
||||
bitn++; //increment the bit index
|
||||
if (bitn == 8) {
|
||||
i++;
|
||||
bitn = 0;
|
||||
}
|
||||
startreg++; //increment the register
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Modbus::setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs) {
|
||||
bool result = true;
|
||||
for (uint8_t i = 0; i < numregs; i++) {
|
||||
Reg(startreg + i, __swap_16(frame[i]));
|
||||
if (Reg(startreg + i) != __swap_16(frame[i]))
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Modbus::onGet(TAddress address, cbModbus cb, uint16_t numregs) {
|
||||
TRegister* reg;
|
||||
bool atLeastOne = false;
|
||||
if (!cb) {
|
||||
return removeOnGet(address, nullptr, numregs);
|
||||
}
|
||||
while (numregs > 0) {
|
||||
reg = searchRegister(address);
|
||||
if (reg) {
|
||||
_callbacks.push_back({TCallback::ON_GET, address, cb});
|
||||
atLeastOne = true;
|
||||
}
|
||||
address++;
|
||||
numregs--;
|
||||
}
|
||||
return atLeastOne;
|
||||
}
|
||||
bool Modbus::onSet(TAddress address, cbModbus cb, uint16_t numregs) {
|
||||
TRegister* reg;
|
||||
bool atLeastOne = false;
|
||||
if (!cb) {
|
||||
return removeOnSet(address, nullptr, numregs);
|
||||
}
|
||||
while (numregs > 0) {
|
||||
reg = searchRegister(address);
|
||||
if (reg) {
|
||||
_callbacks.push_back({TCallback::ON_SET, address, cb});
|
||||
atLeastOne = true;
|
||||
}
|
||||
address++;
|
||||
numregs--;
|
||||
}
|
||||
return atLeastOne;
|
||||
}
|
||||
|
||||
bool Modbus::removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb, uint16_t numregs) {
|
||||
size_t s = _callbacks.size();
|
||||
#if defined(MODBUS_USE_STL)
|
||||
#define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){\
|
||||
return entry.type == t && entry.address == address \
|
||||
&& (!cb || std::addressof(cb) == std::addressof(entry.cb));}
|
||||
while(numregs--) {
|
||||
_callbacks.erase(remove_if(_callbacks.begin(), _callbacks.end(), MODBUS_COMPARE_ON), _callbacks.end());
|
||||
address++;
|
||||
}
|
||||
#else
|
||||
#define MODBUS_COMPARE_ON [t, address, cb](const TCallback entry){ \
|
||||
return entry.type == t && entry.address == address \
|
||||
&& (!cb || entry.cb == cb);}
|
||||
while(numregs--) {
|
||||
size_t r = 0;
|
||||
do {
|
||||
r = _callbacks.find(MODBUS_COMPARE_ON);
|
||||
_callbacks.remove(r);
|
||||
} while (r < _callbacks.size());
|
||||
address++;
|
||||
}
|
||||
#endif
|
||||
return s == _callbacks.size();
|
||||
}
|
||||
bool Modbus::removeOnSet(TAddress address, cbModbus cb, uint16_t numregs) {
|
||||
return removeOn(TCallback::ON_SET, address, cb, numregs);
|
||||
}
|
||||
|
||||
bool Modbus::removeOnGet(TAddress address, cbModbus cb, uint16_t numregs) {
|
||||
return removeOn(TCallback::ON_GET, address, cb, numregs);
|
||||
}
|
||||
|
||||
bool Modbus::readSlave(uint16_t address, uint16_t numregs, FunctionCode fn) {
|
||||
free(_frame);
|
||||
_len = 5;
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) {
|
||||
_reply = REPLY_OFF;
|
||||
return false;
|
||||
}
|
||||
_frame[0] = fn;
|
||||
_frame[1] = address >> 8;
|
||||
_frame[2] = address & 0x00FF;
|
||||
_frame[3] = numregs >> 8;
|
||||
_frame[4] = numregs & 0x00FF;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Modbus::writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data) {
|
||||
free(_frame);
|
||||
_len = 6 + numregs/8;
|
||||
if (numregs % 8) _len++; //Add 1 to the message length for the partial byte.
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) {
|
||||
_reply = REPLY_OFF;
|
||||
return false;
|
||||
}
|
||||
_frame[0] = fn;
|
||||
_frame[1] = to >> 8;
|
||||
_frame[2] = to & 0x00FF;
|
||||
_frame[3] = numregs >> 8;
|
||||
_frame[4] = numregs & 0x00FF;
|
||||
_frame[5] = _len - 6;
|
||||
_frame[_len - 1] = 0; //Clean last probably partial byte
|
||||
if (data) {
|
||||
boolToBits(_frame + 6, data, numregs);
|
||||
} else {
|
||||
getMultipleBits(_frame + 6, startreg, numregs);
|
||||
}
|
||||
_reply = REPLY_NORMAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Modbus::writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data) {
|
||||
free(_frame);
|
||||
_len = 6 + 2 * numregs;
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) {
|
||||
_reply = REPLY_OFF;
|
||||
return false;
|
||||
}
|
||||
_frame[0] = fn;
|
||||
_frame[1] = to >> 8;
|
||||
_frame[2] = to & 0x00FF;
|
||||
_frame[3] = numregs >> 8;
|
||||
_frame[4] = numregs & 0x00FF;
|
||||
_frame[5] = _len - 6;
|
||||
if (data) {
|
||||
uint16_t* frame = (uint16_t*)(_frame + 6);
|
||||
for (uint8_t i = 0; i < numregs; i++) {
|
||||
frame[i] = __swap_16(data[i]);
|
||||
}
|
||||
} else {
|
||||
getMultipleWords((uint16_t*)(_frame + 6), startreg, numregs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Modbus::boolToBits(uint8_t* dst, bool* src, uint16_t numregs) {
|
||||
uint8_t bitn = 0;
|
||||
uint16_t i = 0;
|
||||
uint16_t j = 0;
|
||||
while (numregs--) {
|
||||
if (src[j])
|
||||
bitSet(dst[i], bitn);
|
||||
else
|
||||
bitClear(dst[i], bitn);
|
||||
bitn++; //increment the bit index
|
||||
if (bitn == 8) {
|
||||
i++;
|
||||
bitn = 0;
|
||||
}
|
||||
j++; //increment the register
|
||||
}
|
||||
}
|
||||
|
||||
void Modbus::bitsToBool(bool* dst, uint8_t* src, uint16_t numregs) {
|
||||
uint8_t bitn = 0;
|
||||
uint16_t i = 0;
|
||||
uint16_t j = 0;
|
||||
while (numregs--) {
|
||||
dst[j] = bitRead(src[i], bitn);
|
||||
bitn++; //increment the bit index
|
||||
if (bitn == 8) {
|
||||
i++;
|
||||
bitn = 0;
|
||||
}
|
||||
j++; //increment the register
|
||||
}
|
||||
}
|
||||
|
||||
void Modbus::masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output) {
|
||||
uint8_t fcode = frame[0];
|
||||
if ((fcode & 0x80) != 0) { // Check if error responce
|
||||
_reply = frame[1];
|
||||
return;
|
||||
}
|
||||
if (fcode != sourceFrame[0]) { // Check if responce matches the request
|
||||
_reply = EX_DATA_MISMACH;
|
||||
return;
|
||||
}
|
||||
_reply = EX_SUCCESS;
|
||||
uint16_t field2 = (uint16_t)sourceFrame[3] << 8 | (uint16_t)sourceFrame[4];
|
||||
uint8_t bytecount_calc;
|
||||
switch (fcode) {
|
||||
case FC_READ_REGS:
|
||||
case FC_READ_INPUT_REGS:
|
||||
case FC_READWRITE_REGS:
|
||||
//field2 = numregs, frame[1] = data lenght, header len = 2
|
||||
if (frame[1] != 2 * field2) { //Check if data size matches
|
||||
_reply = EX_DATA_MISMACH;
|
||||
break;
|
||||
}
|
||||
if (output) {
|
||||
uint16_t* from = (uint16_t*)(frame + 2);
|
||||
uint16_t* to = (uint16_t*)output;
|
||||
while(field2--) {
|
||||
*(to++) = __swap_16(*(from++));
|
||||
}
|
||||
} else {
|
||||
setMultipleWords((uint16_t*)(frame + 2), startreg, field2);
|
||||
}
|
||||
break;
|
||||
case FC_READ_COILS:
|
||||
case FC_READ_INPUT_STAT:
|
||||
//field2 = numregs, frame[1] = data length, header len = 2
|
||||
bytecount_calc = field2 / 8;
|
||||
if (field2 % 8) bytecount_calc++;
|
||||
if (frame[1] != bytecount_calc) { // check if data size matches
|
||||
_reply = EX_DATA_MISMACH;
|
||||
break;
|
||||
}
|
||||
if (output) {
|
||||
bitsToBool((bool*)output, frame + 2, field2);
|
||||
} else {
|
||||
setMultipleBits(frame + 2, startreg, field2);
|
||||
}
|
||||
break;
|
||||
#if defined(MODBUS_FILES)
|
||||
case FC_READ_FILE_REC:
|
||||
// Should check if byte order swap needed
|
||||
if (frame[1] < 0x07 || frame[1] > 0xF5) { // Wrong request data size
|
||||
_reply = EX_ILLEGAL_VALUE;
|
||||
return;
|
||||
}
|
||||
{
|
||||
uint8_t* data = frame + 2;
|
||||
uint8_t* eoFrame = frame + frame[1];
|
||||
while (data < eoFrame) {
|
||||
//data[0] - sub-req length
|
||||
//data[1] = 0x06
|
||||
if (data[1] != 0x06 || data[0] < 0x07 || data[0] > 0xF5 || data + data[0] > eoFrame) { // Wrong request data size
|
||||
_reply = EX_ILLEGAL_VALUE;
|
||||
return;
|
||||
}
|
||||
memcpy(output, data + 2, data[0]);
|
||||
data += data[0] + 1;
|
||||
output += data[0] - 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FC_WRITE_FILE_REC:
|
||||
#endif
|
||||
case FC_WRITE_REG:
|
||||
case FC_WRITE_REGS:
|
||||
case FC_WRITE_COIL:
|
||||
case FC_WRITE_COILS:
|
||||
case FC_MASKWRITE_REG:
|
||||
break;
|
||||
|
||||
default:
|
||||
_reply = EX_GENERAL_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
bool Modbus::cbEnable(const bool state) {
|
||||
const bool old_state = state;
|
||||
cbEnabled = state;
|
||||
return old_state;
|
||||
}
|
||||
bool Modbus::cbDisable() {
|
||||
return cbEnable(false);
|
||||
}
|
||||
Modbus::~Modbus() {
|
||||
free(_frame);
|
||||
}
|
||||
|
||||
#if defined(MODBUS_FILES)
|
||||
#if defined(MODBUS_USE_STL)
|
||||
bool Modbus::onFile(std::function<Modbus::ResultCode(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> cb) {
|
||||
#else
|
||||
bool Modbus::onFile(Modbus::ResultCode (*cb)(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)) {
|
||||
#endif
|
||||
_onFile = cb;
|
||||
return true;
|
||||
}
|
||||
Modbus::ResultCode Modbus::fileOp(Modbus::FunctionCode fc, uint16_t fileNum, uint16_t recNum, uint16_t recLen, uint8_t* frame) {
|
||||
if (!_onFile) return EX_ILLEGAL_ADDRESS;
|
||||
return _onFile(fc, fileNum, recNum, recLen, frame);
|
||||
}
|
||||
|
||||
bool Modbus::readSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn) {
|
||||
_len = count * 7 + 2;
|
||||
if (_len > MODBUS_MAX_FRAME) return false;
|
||||
free(_frame);
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) return false;
|
||||
_frame[0] = fn;
|
||||
_frame[1] = _len - 2;
|
||||
uint8_t* subReq = _frame + 2;
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
subReq[0] = 0x06;
|
||||
subReq[1] = fileNum[i] >> 8;
|
||||
subReq[2] = fileNum[i] & 0x00FF;
|
||||
subReq[3] = startRec[i] >> 8;
|
||||
subReq[4] = startRec[i] & 0x00FF;
|
||||
subReq[5] = len[i] >> 8;
|
||||
subReq[6] = len[i] & 0x00FF;
|
||||
subReq += 7;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool Modbus::writeSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn, uint8_t* data) {
|
||||
_len = 2;
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
_len += len[i] * 2 + 7;
|
||||
}
|
||||
if (_len > MODBUS_MAX_FRAME) return false;
|
||||
free(_frame);
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) return false;
|
||||
_frame[0] = fn;
|
||||
_frame[1] = _len - 2;
|
||||
uint8_t* subReq = _frame + 2;
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
subReq[0] = 0x06;
|
||||
subReq[1] = fileNum[i] >> 8;
|
||||
subReq[2] = fileNum[i] & 0x00FF;
|
||||
subReq[3] = startRec[i] >> 8;
|
||||
subReq[4] = startRec[i] & 0x00FF;
|
||||
subReq[5] = len[i] >> 8;
|
||||
subReq[6] = len[i] & 0x00FF;
|
||||
uint8_t clen = len[i] * 2;
|
||||
memcpy(subReq + 7, data, clen);
|
||||
subReq += 7 + clen;
|
||||
data += clen;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Modbus::onRaw(cbRaw cb) {
|
||||
_cbRaw = cb;
|
||||
return true;
|
||||
}
|
||||
Modbus::ResultCode Modbus::_onRequestDefault(Modbus::FunctionCode fc, const RequestData data) {
|
||||
return EX_SUCCESS;
|
||||
}
|
||||
bool Modbus::onRequest(cbRequest cb) {
|
||||
_onRequest = cb;
|
||||
return true;
|
||||
}
|
||||
#if defined (MODBUSAPI_OPTIONAL)
|
||||
bool Modbus::onRequestSuccess(cbRequest cb) {
|
||||
_onRequestSuccess = cb;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_SAM_DUE_STL)
|
||||
namespace std {
|
||||
void __throw_bad_function_call() {
|
||||
Serial.println(F("STL ERROR - __throw_bad_function_call"));
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
363
lib/modbus-esp8266/Modbus.h
Normal file
363
lib/modbus-esp8266/Modbus.h
Normal file
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
Core functions
|
||||
Copyright (C) 2014 Andr<64> Sarmento Barbosa
|
||||
2017-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
#pragma once
|
||||
#include "ModbusSettings.h"
|
||||
#include "Arduino.h"
|
||||
#if defined(MODBUS_USE_STL)
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#else
|
||||
#include "darray.h"
|
||||
#endif
|
||||
|
||||
static inline uint16_t __swap_16(uint16_t num) { return (num >> 8) | (num << 8); }
|
||||
|
||||
#define COIL(n) (TAddress){TAddress::COIL, n}
|
||||
#define ISTS(n) (TAddress){TAddress::ISTS, n}
|
||||
#define IREG(n) (TAddress){TAddress::IREG, n}
|
||||
#define HREG(n) (TAddress){TAddress::HREG, n}
|
||||
#define NULLREG (TAddress){TAddress::NONE, 0xFFFF}
|
||||
#define BIT_VAL(v) (v?0xFF00:0x0000)
|
||||
#define BIT_BOOL(v) (v==0xFF00)
|
||||
#define COIL_VAL(v) (v?0xFF00:0x0000)
|
||||
#define COIL_BOOL(v) (v==0xFF00)
|
||||
#define ISTS_VAL(v) (v?0xFF00:0x0000)
|
||||
#define ISTS_BOOL(v) (v==0xFF00)
|
||||
|
||||
// For depricated (v1.xx) onSet/onGet format compatibility
|
||||
#define cbDefault nullptr
|
||||
|
||||
struct TRegister;
|
||||
#if defined(MODBUS_USE_STL)
|
||||
typedef std::function<uint16_t(TRegister* reg, uint16_t val)> cbModbus; // Callback function Type
|
||||
#else
|
||||
typedef uint16_t (*cbModbus)(TRegister* reg, uint16_t val); // Callback function Type
|
||||
#endif
|
||||
|
||||
struct TAddress {
|
||||
enum RegType {COIL, ISTS, IREG, HREG, NONE = 0xFF};
|
||||
RegType type;
|
||||
uint16_t address;
|
||||
bool operator==(const TAddress &obj) const { // TAddress == TAddress
|
||||
return type == obj.type && address == obj.address;
|
||||
}
|
||||
bool operator!=(const TAddress &obj) const { // TAddress != TAddress
|
||||
return type != obj.type || address != obj.address;
|
||||
}
|
||||
TAddress& operator++() { // ++TAddress
|
||||
address++;
|
||||
return *this;
|
||||
}
|
||||
TAddress operator++(int) { // TAddress++
|
||||
TAddress result(*this);
|
||||
++(*this);
|
||||
return result;
|
||||
}
|
||||
TAddress& operator+=(const int& inc) { // TAddress += integer
|
||||
address += inc;
|
||||
return *this;
|
||||
}
|
||||
const TAddress operator+(const int& inc) const { // TAddress + integer
|
||||
TAddress result(*this);
|
||||
result.address += inc;
|
||||
return result;
|
||||
}
|
||||
bool isCoil() {
|
||||
return type == COIL;
|
||||
}
|
||||
bool isIsts() {
|
||||
return type == ISTS;
|
||||
}
|
||||
bool isIreg() {
|
||||
return type == IREG;
|
||||
}
|
||||
bool isHreg() {
|
||||
return type == HREG;
|
||||
}
|
||||
};
|
||||
|
||||
struct TCallback {
|
||||
enum CallbackType {ON_SET, ON_GET};
|
||||
CallbackType type;
|
||||
TAddress address;
|
||||
cbModbus cb;
|
||||
};
|
||||
|
||||
struct TRegister {
|
||||
TAddress address;
|
||||
uint16_t value;
|
||||
bool operator ==(const TRegister &obj) const {
|
||||
return address == obj.address;
|
||||
}
|
||||
};
|
||||
|
||||
class Modbus {
|
||||
public:
|
||||
//Function Codes
|
||||
enum FunctionCode {
|
||||
FC_READ_COILS = 0x01, // Read Coils (Output) Status
|
||||
FC_READ_INPUT_STAT = 0x02, // Read Input Status (Discrete Inputs)
|
||||
FC_READ_REGS = 0x03, // Read Holding Registers
|
||||
FC_READ_INPUT_REGS = 0x04, // Read Input Registers
|
||||
FC_WRITE_COIL = 0x05, // Write Single Coil (Output)
|
||||
FC_WRITE_REG = 0x06, // Preset Single Register
|
||||
FC_DIAGNOSTICS = 0x08, // Not implemented. Diagnostics (Serial Line only)
|
||||
FC_WRITE_COILS = 0x0F, // Write Multiple Coils (Outputs)
|
||||
FC_WRITE_REGS = 0x10, // Write block of contiguous registers
|
||||
FC_READ_FILE_REC = 0x14, // Read File Record
|
||||
FC_WRITE_FILE_REC = 0x15, // Write File Record
|
||||
FC_MASKWRITE_REG = 0x16, // Mask Write Register
|
||||
FC_READWRITE_REGS = 0x17 // Read/Write Multiple registers
|
||||
};
|
||||
//Exception Codes
|
||||
//Custom result codes used internally and for callbacks but never used for Modbus responce
|
||||
enum ResultCode {
|
||||
EX_SUCCESS = 0x00, // Custom. No error
|
||||
EX_ILLEGAL_FUNCTION = 0x01, // Function Code not Supported
|
||||
EX_ILLEGAL_ADDRESS = 0x02, // Output Address not exists
|
||||
EX_ILLEGAL_VALUE = 0x03, // Output Value not in Range
|
||||
EX_SLAVE_FAILURE = 0x04, // Slave or Master Device Fails to process request
|
||||
EX_ACKNOWLEDGE = 0x05, // Not used
|
||||
EX_SLAVE_DEVICE_BUSY = 0x06, // Not used
|
||||
EX_MEMORY_PARITY_ERROR = 0x08, // Not used
|
||||
EX_PATH_UNAVAILABLE = 0x0A, // Not used
|
||||
EX_DEVICE_FAILED_TO_RESPOND = 0x0B, // Not used
|
||||
EX_GENERAL_FAILURE = 0xE1, // Custom. Unexpected master error
|
||||
EX_DATA_MISMACH = 0xE2, // Custom. Inpud data size mismach
|
||||
EX_UNEXPECTED_RESPONSE = 0xE3, // Custom. Returned result doesn't mach transaction
|
||||
EX_TIMEOUT = 0xE4, // Custom. Operation not finished within reasonable time
|
||||
EX_CONNECTION_LOST = 0xE5, // Custom. Connection with device lost
|
||||
EX_CANCEL = 0xE6, // Custom. Transaction/request canceled
|
||||
EX_PASSTHROUGH = 0xE7, // Custom. Raw callback. Indicate to normal processing on callback exit
|
||||
EX_FORCE_PROCESS = 0xE8 // Custom. Raw callback. Indicate to force processing on callback exit
|
||||
};
|
||||
union RequestData {
|
||||
struct {
|
||||
TAddress reg;
|
||||
uint16_t regCount;
|
||||
};
|
||||
struct {
|
||||
TAddress regRead;
|
||||
uint16_t regReadCount;
|
||||
TAddress regWrite;
|
||||
uint16_t regWriteCount;
|
||||
};
|
||||
struct {
|
||||
TAddress regMask;
|
||||
uint16_t andMask;
|
||||
uint16_t orMask;
|
||||
};
|
||||
uint8_t* data;
|
||||
RequestData(TAddress r1, uint16_t c1) {
|
||||
reg = r1;
|
||||
regCount = c1;
|
||||
};
|
||||
RequestData(TAddress r1, uint16_t c1, TAddress r2, uint16_t c2) {
|
||||
regRead = r1;
|
||||
regReadCount = c1;
|
||||
regWrite = r2;
|
||||
regWriteCount = c2;
|
||||
};
|
||||
RequestData(TAddress r1, uint16_t m1, uint16_t m2) {
|
||||
regMask = r1;
|
||||
andMask = m1;
|
||||
orMask = m2;
|
||||
};
|
||||
RequestData(uint8_t* d) {
|
||||
data = d;
|
||||
};
|
||||
};
|
||||
|
||||
struct frame_arg_t {
|
||||
bool to_server;
|
||||
union {
|
||||
uint8_t slaveId;
|
||||
struct {
|
||||
uint8_t unitId;
|
||||
uint32_t ipaddr;
|
||||
uint16_t transactionId;
|
||||
};
|
||||
};
|
||||
frame_arg_t(uint8_t s, bool m = false) {
|
||||
slaveId = s;
|
||||
to_server = m;
|
||||
};
|
||||
frame_arg_t(uint8_t u, uint32_t a, uint16_t t, bool m = false) {
|
||||
unitId = u;
|
||||
ipaddr = a;
|
||||
transactionId = t;
|
||||
to_server = m;
|
||||
};
|
||||
};
|
||||
|
||||
~Modbus();
|
||||
|
||||
bool cbEnable(const bool state = true);
|
||||
bool cbDisable();
|
||||
|
||||
private:
|
||||
ResultCode readBits(TAddress startreg, uint16_t numregs, FunctionCode fn);
|
||||
ResultCode readWords(TAddress startreg, uint16_t numregs, FunctionCode fn);
|
||||
|
||||
bool setMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numoutputs);
|
||||
bool setMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numoutputs);
|
||||
|
||||
void getMultipleBits(uint8_t* frame, TAddress startreg, uint16_t numregs);
|
||||
void getMultipleWords(uint16_t* frame, TAddress startreg, uint16_t numregs);
|
||||
|
||||
void bitsToBool(bool* dst, uint8_t* src, uint16_t numregs);
|
||||
void boolToBits(uint8_t* dst, bool* src, uint16_t numregs);
|
||||
|
||||
protected:
|
||||
//Reply Types
|
||||
enum ReplyCode {
|
||||
REPLY_OFF = 0x01,
|
||||
REPLY_ECHO = 0x02,
|
||||
REPLY_NORMAL = 0x03,
|
||||
REPLY_ERROR = 0x04,
|
||||
REPLY_UNEXPECTED = 0x05
|
||||
};
|
||||
#if defined(MODBUS_USE_STL)
|
||||
#if defined(MODBUS_GLOBAL_REGS)
|
||||
static std::vector<TRegister> _regs;
|
||||
static std::vector<TCallback> _callbacks;
|
||||
#if defined(MODBUS_FILES)
|
||||
static std::function<ResultCode(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> _onFile;
|
||||
#endif
|
||||
#else
|
||||
std::vector<TRegister> _regs;
|
||||
std::vector<TCallback> _callbacks;
|
||||
#if defined(MODBUS_FILES)
|
||||
std::function<ResultCode(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> _onFile;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if defined(MODBUS_GLOBAL_REGS)
|
||||
static DArray<TRegister, 1, 1> _regs;
|
||||
static DArray<TCallback, 1, 1> _callbacks;
|
||||
#if defined(MODBUS_FILES)
|
||||
static ResultCode (*_onFile)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*);
|
||||
#endif
|
||||
#else
|
||||
DArray<TRegister, 1, 1> _regs;
|
||||
DArray<TCallback, 1, 1> _callbacks;
|
||||
#if defined(MODBUS_FILES)
|
||||
ResultCode (*_onFile)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)= nullptr;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
uint8_t* _frame = nullptr;
|
||||
uint16_t _len = 0;
|
||||
uint8_t _reply = 0;
|
||||
bool cbEnabled = true;
|
||||
uint16_t callback(TRegister* reg, uint16_t val, TCallback::CallbackType t);
|
||||
virtual TRegister* searchRegister(TAddress addr);
|
||||
void exceptionResponse(FunctionCode fn, ResultCode excode); // Fills _frame with response
|
||||
void successResponce(TAddress startreg, uint16_t numoutputs, FunctionCode fn); // Fills frame with response
|
||||
void slavePDU(uint8_t* frame); //For Slave
|
||||
void masterPDU(uint8_t* frame, uint8_t* sourceFrame, TAddress startreg, uint8_t* output = nullptr); //For Master
|
||||
// frame - data received form slave
|
||||
// sourceFrame - data have sent fo slave
|
||||
// startreg - local register to start put data to
|
||||
// output - if not null put data to the buffer insted local registers. output assumed to by array of uint16_t or boolean
|
||||
|
||||
bool readSlave(uint16_t address, uint16_t numregs, FunctionCode fn);
|
||||
bool writeSlaveBits(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, bool* data = nullptr);
|
||||
bool writeSlaveWords(TAddress startreg, uint16_t to, uint16_t numregs, FunctionCode fn, uint16_t* data = nullptr);
|
||||
// startreg - local register to get data from
|
||||
// to - slave register to write data to
|
||||
// numregs - number of registers
|
||||
// fn - Modbus function
|
||||
// data - if null use local registers. Otherwise use data from array to erite to slave
|
||||
bool removeOn(TCallback::CallbackType t, TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
public:
|
||||
bool addReg(TAddress address, uint16_t value = 0, uint16_t numregs = 1);
|
||||
bool Reg(TAddress address, uint16_t value);
|
||||
uint16_t Reg(TAddress address);
|
||||
bool removeReg(TAddress address, uint16_t numregs = 1);
|
||||
bool addReg(TAddress address, uint16_t* value, uint16_t numregs = 1);
|
||||
bool Reg(TAddress address, uint16_t* value, uint16_t numregs = 1);
|
||||
|
||||
bool onGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnSet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnGet(TAddress address, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
|
||||
virtual uint32_t eventSource() {return 0;}
|
||||
#if defined(MODBUS_USE_STL)
|
||||
typedef std::function<ResultCode(FunctionCode, const RequestData)> cbRequest; // Callback function Type
|
||||
typedef std::function<ResultCode(uint8_t*, uint8_t, void*)> cbRaw; // Callback function Type
|
||||
#else
|
||||
typedef ResultCode (*cbRequest)(FunctionCode fc, const RequestData data); // Callback function Type
|
||||
typedef ResultCode (*cbRaw)(uint8_t*, uint8_t, void*); // Callback function Type
|
||||
#endif
|
||||
|
||||
protected:
|
||||
cbRaw _cbRaw = nullptr;
|
||||
static ResultCode _onRequestDefault(FunctionCode fc, const RequestData data);
|
||||
cbRequest _onRequest = _onRequestDefault;
|
||||
public:
|
||||
bool onRaw(cbRaw cb = nullptr);
|
||||
bool onRequest(cbRequest cb = _onRequestDefault);
|
||||
#if defined (MODBUSAPI_OPTIONAL)
|
||||
protected:
|
||||
cbRequest _onRequestSuccess = _onRequestDefault;
|
||||
public:
|
||||
bool onRequestSuccess(cbRequest cb = _onRequestDefault);
|
||||
#endif
|
||||
|
||||
#if defined(MODBUS_FILES)
|
||||
public:
|
||||
#if defined(MODBUS_USE_STL)
|
||||
bool onFile(std::function<ResultCode(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)>);
|
||||
#else
|
||||
bool onFile(ResultCode (*cb)(FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*));
|
||||
#endif
|
||||
private:
|
||||
ResultCode fileOp(FunctionCode fc, uint16_t fileNum, uint16_t recNum, uint16_t recLen, uint8_t* frame);
|
||||
protected:
|
||||
bool readSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn);
|
||||
// fileNum - sequental array of files numbers to read
|
||||
// startRec - array of strart records for each file
|
||||
// len - array of counts of records to read in terms of register size (2 bytes) for each file
|
||||
// count - count of records to be compose in the single request
|
||||
// fn - Modbus function. Assumed to be 0x14
|
||||
bool writeSlaveFile(uint16_t* fileNum, uint16_t* startRec, uint16_t* len, uint8_t count, FunctionCode fn, uint8_t* data);
|
||||
// fileNum - sequental array of files numbers to read
|
||||
// startRec - array of strart records for each file
|
||||
// len - array of counts of records to read in terms of register size (2 bytes) for each file
|
||||
// count - count of records to be compose in the single request
|
||||
// fn - Modbus function. Assumed to be 0x15
|
||||
// data - sequental set of data records
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
#if defined(MODBUS_USE_STL)
|
||||
typedef std::function<bool(Modbus::ResultCode, uint16_t, void*)> cbTransaction; // Callback skeleton for requests
|
||||
#else
|
||||
typedef bool (*cbTransaction)(Modbus::ResultCode event, uint16_t transactionId, void* data); // Callback skeleton for requests
|
||||
#endif
|
||||
//typedef Modbus::ResultCode (*cbRequest)(Modbus::FunctionCode func, TRegister* reg, uint16_t regCount); // Callback function Type
|
||||
#if defined(MODBUS_FILES)
|
||||
// Callback skeleton for file read/write
|
||||
#if defined(MODBUS_USE_STL)
|
||||
typedef std::function<Modbus::ResultCode(Modbus::FunctionCode, uint16_t, uint16_t, uint16_t, uint8_t*)> cbModbusFileOp;
|
||||
#else
|
||||
typedef Modbus::ResultCode (*cbModbusFileOp)(Modbus::FunctionCode func, uint16_t fileNum, uint16_t recNumber, uint16_t recLength, uint8_t* frame);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_SAM_DUE_STL)
|
||||
// Arduino Due STL workaround
|
||||
namespace std {
|
||||
void __throw_bad_function_call();
|
||||
}
|
||||
#endif
|
||||
507
lib/modbus-esp8266/ModbusAPI.h
Normal file
507
lib/modbus-esp8266/ModbusAPI.h
Normal file
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
Modbus public API implementation
|
||||
Copyright (C) 2014 Andr<64> Sarmento Barbosa
|
||||
2017-2021 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
#pragma once
|
||||
#include "Modbus.h"
|
||||
|
||||
template <class T>
|
||||
class ModbusAPI : public T {
|
||||
public:
|
||||
// Alternative API
|
||||
template <typename TYPEID>
|
||||
uint16_t read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t read(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t write(TYPEID id, TAddress reg, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t write(TYPEID id, TAddress reg, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
/*
|
||||
template <typename TYPEID>
|
||||
uint16_t push(TYPEID id, TAddress to, TAddress from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pull(TYPEID id, TAddress from, TAddress to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
*/
|
||||
// Classic API
|
||||
bool Hregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(HREG(offset), value);}
|
||||
bool Coils(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(COIL(offset), value);}
|
||||
bool Istss(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->Reg(ISTS(offset), value);}
|
||||
bool Iregs(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->Reg(IREG(offset), value);}
|
||||
|
||||
//bool addHreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(HREG(offset), value);}
|
||||
//bool addCoil(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(COIL(offset), value);}
|
||||
//bool addIsts(uint16_t offset, bool* value, uint16_t numregs = 1) {return this->addReg(ISTS(offset), value);}
|
||||
//bool addIreg(uint16_t offset, uint16_t* value, uint16_t numregs = 1) {return this->addReg(IREG(offset), value);}
|
||||
|
||||
bool addHreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1);
|
||||
bool addCoil(uint16_t offset, bool value = false, uint16_t numregs = 1);
|
||||
bool addIsts(uint16_t offset, bool value = false, uint16_t numregs = 1);
|
||||
bool addIreg(uint16_t offset, uint16_t value = 0, uint16_t numregs = 1);
|
||||
|
||||
bool Hreg(uint16_t offset, uint16_t value);
|
||||
bool Coil(uint16_t offset, bool value);
|
||||
bool Ists(uint16_t offset, bool value);
|
||||
bool Ireg(uint16_t offset, uint16_t value);
|
||||
|
||||
bool Coil(uint16_t offset);
|
||||
bool Ists(uint16_t offset);
|
||||
uint16_t Ireg(uint16_t offset);
|
||||
uint16_t Hreg(uint16_t offset);
|
||||
|
||||
bool removeCoil(uint16_t offset, uint16_t numregs = 1);
|
||||
bool removeIsts(uint16_t offset, uint16_t numregs = 1);
|
||||
bool removeIreg(uint16_t offset, uint16_t numregs = 1);
|
||||
bool removeHreg(uint16_t offset, uint16_t numregs = 1);
|
||||
|
||||
bool onGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool onSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
|
||||
bool removeOnGetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnSetCoil(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnGetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnSetHreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnGetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnSetIsts(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnGetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
bool removeOnSetIreg(uint16_t offset, cbModbus cb = nullptr, uint16_t numregs = 1);
|
||||
|
||||
template <typename TYPEID>
|
||||
uint16_t writeCoil(TYPEID id, uint16_t offset, bool value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t writeCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t readCoil(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t value, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t writeHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t readIsts(TYPEID id, uint16_t offset, bool* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t readHreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t readIreg(TYPEID id, uint16_t offset, uint16_t* value, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
|
||||
template <typename TYPEID>
|
||||
uint16_t pushCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pullCoil(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pullIsts(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pushHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pullHreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pullIreg(TYPEID id, uint16_t from, uint16_t to, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
|
||||
template <typename TYPEID>
|
||||
uint16_t pullHregToIreg(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pullCoilToIsts(TYPEID id, uint16_t offset, uint16_t startreg, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pushIstsToCoil(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t pushIregToHreg(TYPEID id, uint16_t to, uint16_t from, uint16_t numregs = 1, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
|
||||
template <typename TYPEID>
|
||||
uint16_t readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
|
||||
template <typename TYPEID>
|
||||
uint16_t maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t readWriteHreg(TYPEID slaveId, uint16_t readOffset, uint16_t* readValue, uint16_t readNumregs, uint16_t writeOffset, uint16_t* writeValue, uint16_t writeNumregs, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
|
||||
template <typename TYPEID>
|
||||
uint16_t rawRequest(TYPEID ip, const uint8_t* data, uint16_t len, cbTransaction cb = nullptr, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t rawResponce(TYPEID ip, const uint8_t* data, uint16_t len, uint8_t unit = MODBUSIP_UNIT);
|
||||
template <typename TYPEID>
|
||||
uint16_t errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit = MODBUSIP_UNIT);
|
||||
};
|
||||
|
||||
// FNAME writeCoil, writeIsts, writeHreg, writeIreg
|
||||
// REG COIL, ISTS, HREG, IREG
|
||||
// FUNC Modbus function
|
||||
// MAXNUM Register count limit
|
||||
// VALTYPE bool, uint16_t
|
||||
// VALUE
|
||||
#define IMPLEMENT_WRITEREG(FNAME, REG, FUNC, VALUE, VALTYPE) \
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t offset, VALTYPE value, cbTransaction cb, uint8_t unit) { \
|
||||
this->readSlave(offset, VALUE(value), Modbus::FUNC); \
|
||||
return this->send(ip, REG(offset), cb, unit); \
|
||||
}
|
||||
IMPLEMENT_WRITEREG(writeCoil, COIL, FC_WRITE_COIL, COIL_VAL, bool)
|
||||
IMPLEMENT_WRITEREG(writeHreg, HREG, FC_WRITE_REG, , uint16_t)
|
||||
|
||||
#define IMPLEMENT_WRITEREGS(FNAME, REG, FUNC, VALUE, MAXNUM, VALTYPE) \
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
|
||||
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
|
||||
this->VALUE(REG(offset), offset, numregs, Modbus::FUNC, value); \
|
||||
return this->send(ip, REG(offset), cb, unit); \
|
||||
}
|
||||
IMPLEMENT_WRITEREGS(writeCoil, COIL, FC_WRITE_COILS, writeSlaveBits, MODBUS_MAX_BITS, bool)
|
||||
IMPLEMENT_WRITEREGS(writeHreg, HREG, FC_WRITE_REGS, writeSlaveWords, MODBUS_MAX_WORDS, uint16_t)
|
||||
|
||||
#define IMPLEMENT_READREGS(FNAME, REG, FUNC, MAXNUM, VALTYPE) \
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t offset, VALTYPE* value, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
|
||||
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
|
||||
this->readSlave(offset, numregs, Modbus::FUNC); \
|
||||
return this->send(ip, REG(offset), cb, unit, (uint8_t*)value); \
|
||||
}
|
||||
IMPLEMENT_READREGS(readCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS, bool)
|
||||
IMPLEMENT_READREGS(readHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS, uint16_t)
|
||||
IMPLEMENT_READREGS(readIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS, bool)
|
||||
IMPLEMENT_READREGS(readIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS, uint16_t)
|
||||
|
||||
#if defined(MODBUS_ADD_REG)
|
||||
#define ADDREG(R) this->addReg(R(to), (uint16_t)0, numregs);
|
||||
#else
|
||||
#define ADDREG(R) ;
|
||||
#endif
|
||||
#define IMPLEMENT_PULL(FNAME, REG, FUNC, MAXNUM) \
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t from, uint16_t to, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
|
||||
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
|
||||
ADDREG(REG) \
|
||||
this->readSlave(from, numregs, Modbus::FUNC); \
|
||||
return this->send(ip, REG(to), cb, unit); \
|
||||
}
|
||||
IMPLEMENT_PULL(pullCoil, COIL, FC_READ_COILS, MODBUS_MAX_BITS)
|
||||
IMPLEMENT_PULL(pullIsts, ISTS, FC_READ_INPUT_STAT, MODBUS_MAX_BITS)
|
||||
IMPLEMENT_PULL(pullHreg, HREG, FC_READ_REGS, MODBUS_MAX_WORDS)
|
||||
IMPLEMENT_PULL(pullIreg, IREG, FC_READ_INPUT_REGS, MODBUS_MAX_WORDS)
|
||||
IMPLEMENT_PULL(pullHregToIreg, IREG, FC_READ_REGS, MODBUS_MAX_WORDS)
|
||||
IMPLEMENT_PULL(pullCoilToIsts, ISTS, FC_READ_COILS, MODBUS_MAX_BITS)
|
||||
|
||||
#define IMPLEMENT_PUSH(FNAME, REG, FUNC, MAXNUM, FINT) \
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::FNAME(TYPEID ip, uint16_t to, uint16_t from, uint16_t numregs, cbTransaction cb, uint8_t unit) { \
|
||||
if (numregs < 0x0001 || numregs > MAXNUM) return false; \
|
||||
if (!this->searchRegister(REG(from))) return false; \
|
||||
this->FINT(REG(from), to, numregs, Modbus::FUNC); \
|
||||
return this->send(ip, REG(from), cb, unit); \
|
||||
}
|
||||
IMPLEMENT_PUSH(pushCoil, COIL, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits)
|
||||
IMPLEMENT_PUSH(pushHreg, HREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords)
|
||||
IMPLEMENT_PUSH(pushIregToHreg, IREG, FC_WRITE_REGS, MODBUS_MAX_WORDS, writeSlaveWords)
|
||||
IMPLEMENT_PUSH(pushIstsToCoil, ISTS, FC_WRITE_COILS, MODBUS_MAX_BITS, writeSlaveBits)
|
||||
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::read(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
|
||||
switch (reg.type) {
|
||||
case TAddress::HREG:
|
||||
return readHreg(id, reg.address, value, numregs, cb, unit);
|
||||
case TAddress::IREG:
|
||||
return readIreg(id, reg.address, value, numregs, cb, unit);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::read(TYPEID id, TAddress reg, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
|
||||
switch (reg.type) {
|
||||
case TAddress::COIL:
|
||||
return readCoil(id, reg.address, value, numregs, cb, unit);
|
||||
case TAddress::ISTS:
|
||||
return readIsts(id, reg.address, value, numregs, cb, unit);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, uint16_t value, cbTransaction cb, uint8_t unit) {
|
||||
switch (reg.type) {
|
||||
case TAddress::COIL:
|
||||
return writeCoil(id, reg.address, value, cb, unit);
|
||||
case TAddress::HREG:
|
||||
return writeHreg(id, reg.address, value, cb, unit);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, bool value, cbTransaction cb, uint8_t unit) {
|
||||
switch (reg.type) {
|
||||
case TAddress::COIL:
|
||||
return writeCoil(id, reg.address, value, cb, unit);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, uint16_t* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
|
||||
switch (reg.type) {
|
||||
case TAddress::COIL:
|
||||
return writeCoil(id, reg.address, value, numregs, cb, unit);
|
||||
case TAddress::HREG:
|
||||
return writeHreg(id, reg.address, value, numregs, cb, unit);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::write(TYPEID id, TAddress reg, bool* value, uint16_t numregs, cbTransaction cb, uint8_t unit) {
|
||||
switch (reg.type) {
|
||||
case TAddress::COIL:
|
||||
return writeCoil(id, reg.address, value, cb, numregs, unit);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::addHreg(uint16_t offset, uint16_t value, uint16_t numregs) {
|
||||
return this->addReg(HREG(offset), value, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::Hreg(uint16_t offset, uint16_t value) {
|
||||
return this->Reg(HREG(offset), value);
|
||||
}
|
||||
template <class T> \
|
||||
uint16_t ModbusAPI<T>::Hreg(uint16_t offset) {
|
||||
return this->Reg(HREG(offset));
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeHreg(uint16_t offset, uint16_t numregs) {
|
||||
return this->removeReg(HREG(offset), numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::addCoil(uint16_t offset, bool value, uint16_t numregs) {
|
||||
return this->addReg(COIL(offset), COIL_VAL(value), numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::addIsts(uint16_t offset, bool value, uint16_t numregs) {
|
||||
return this->addReg(ISTS(offset), ISTS_VAL(value), numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::addIreg(uint16_t offset, uint16_t value, uint16_t numregs) {
|
||||
return this->addReg(IREG(offset), value, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::Coil(uint16_t offset, bool value) {
|
||||
return this->Reg(COIL(offset), COIL_VAL(value));
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::Ists(uint16_t offset, bool value) {
|
||||
return this->Reg(ISTS(offset), ISTS_VAL(value));
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::Ireg(uint16_t offset, uint16_t value) {
|
||||
return this->Reg(IREG(offset), value);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::Coil(uint16_t offset) {
|
||||
return COIL_BOOL(this->Reg(COIL(offset)));
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::Ists(uint16_t offset) {
|
||||
return ISTS_BOOL(this->Reg(ISTS(offset)));
|
||||
}
|
||||
template <class T> \
|
||||
uint16_t ModbusAPI<T>::Ireg(uint16_t offset) {
|
||||
return this->Reg(IREG(offset));
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeCoil(uint16_t offset, uint16_t numregs) {
|
||||
return this->removeReg(COIL(offset), numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeIsts(uint16_t offset, uint16_t numregs) {
|
||||
return this->removeReg(ISTS(offset), numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeIreg(uint16_t offset, uint16_t numregs) {
|
||||
return this->removeReg(IREG(offset), numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onGet(COIL(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onSet(COIL(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onGet(HREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onSet(HREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onGet(ISTS(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onSet(ISTS(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onGet(IREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::onSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->onSet(IREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnGetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnGet(COIL(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnSetCoil(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnSet(COIL(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnGetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnGet(HREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnSetHreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnSet(HREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnGetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnGet(ISTS(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnSetIsts(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnSet(ISTS(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnGetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnGet(IREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
bool ModbusAPI<T>::removeOnSetIreg(uint16_t offset, cbModbus cb, uint16_t numregs) {
|
||||
return this->removeOnSet(IREG(offset), cb, numregs);
|
||||
}
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::readFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) {
|
||||
if (startRec > MODBUS_MAX_FILES) return 0;
|
||||
if (!this->readSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_READ_FILE_REC)) return 0;
|
||||
return this->send(slaveId, NULLREG, cb, unit, data);
|
||||
};
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::writeFileRec(TYPEID slaveId, uint16_t fileNum, uint16_t startRec, uint16_t len, uint8_t* data, cbTransaction cb, uint8_t unit) {
|
||||
if (startRec > MODBUS_MAX_FILES) return 0;
|
||||
if (!this->writeSlaveFile(&fileNum, &startRec, &len, 1, Modbus::FC_WRITE_FILE_REC, data)) return 0;
|
||||
return this->send(slaveId, NULLREG, cb, unit);
|
||||
};
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::maskHreg(TYPEID slaveId, uint16_t offset, uint16_t andMask, uint16_t orMask, cbTransaction cb, uint8_t unit) {
|
||||
free(this->_frame);
|
||||
this->_len = 7;
|
||||
this->_frame = (uint8_t*) malloc(this->_len);
|
||||
this->_frame[0] = Modbus::FC_MASKWRITE_REG;
|
||||
this->_frame[1] = offset >> 8;
|
||||
this->_frame[2] = offset & 0x00FF;
|
||||
this->_frame[3] = andMask >> 8;
|
||||
this->_frame[4] = andMask & 0x00FF;
|
||||
this->_frame[5] = orMask >> 8;
|
||||
this->_frame[6] = orMask & 0x00FF;
|
||||
return this->send(slaveId, HREG(offset), cb, unit);
|
||||
};
|
||||
|
||||
template <class T> \
|
||||
template <typename TYPEID> \
|
||||
uint16_t ModbusAPI<T>::readWriteHreg(TYPEID ip, \
|
||||
uint16_t readOffset, uint16_t* readValue, uint16_t readNumregs, \
|
||||
uint16_t writeOffset, uint16_t* writeValue, uint16_t writeNumregs, \
|
||||
cbTransaction cb, uint8_t unit) {
|
||||
const uint8_t _header = 10;
|
||||
if (readNumregs < 0x0001 || readNumregs > MODBUS_MAX_WORDS || writeNumregs < 0x0001 || writeNumregs > 0X0079 || !readValue || !writeValue) return 0;
|
||||
|
||||
free(this->_frame);
|
||||
this->_len = _header + 2 * writeNumregs;
|
||||
this->_frame = (uint8_t*) malloc(this->_len);
|
||||
if (!this->_frame) {
|
||||
this->_reply = Modbus::REPLY_OFF;
|
||||
return 0;
|
||||
}
|
||||
this->_frame[0] = Modbus::FC_READWRITE_REGS;
|
||||
this->_frame[1] = readOffset >> 8;
|
||||
this->_frame[2] = readOffset & 0x00FF;
|
||||
this->_frame[3] = readNumregs >> 8;
|
||||
this->_frame[4] = readNumregs & 0x00FF;
|
||||
|
||||
this->_frame[5] = writeOffset >> 8;
|
||||
this->_frame[6] = writeOffset & 0x00FF;
|
||||
this->_frame[7] = writeNumregs >> 8;
|
||||
this->_frame[8] = writeNumregs & 0x00FF;
|
||||
this->_frame[9] = this->_len - _header;
|
||||
|
||||
uint16_t* frame = (uint16_t*)(this->_frame + _header);
|
||||
for (uint8_t i = 0; i < writeNumregs; i++) {
|
||||
frame[i] = __swap_16(writeValue[i]);
|
||||
}
|
||||
return this->send(ip, HREG(readOffset), cb, unit, (uint8_t*)readValue);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::rawRequest(TYPEID ip, \
|
||||
const uint8_t* data, uint16_t len,
|
||||
cbTransaction cb, uint8_t unit) {
|
||||
free(this->_frame);
|
||||
this->_frame = (uint8_t*)malloc(len);
|
||||
if (!this->_frame)
|
||||
return 0;
|
||||
this->_len = len;
|
||||
memcpy(this->_frame, data, len);
|
||||
return this->send(ip, NULLREG, cb, unit);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::rawResponce(TYPEID ip, \
|
||||
const uint8_t* data, uint16_t len, uint8_t unit) {
|
||||
free(this->_frame);
|
||||
this->_frame = (uint8_t*)malloc(len);
|
||||
if (!this->_frame)
|
||||
return 0;
|
||||
this->_len = len;
|
||||
memcpy(this->_frame, data, len);
|
||||
return this->send(ip, NULLREG, nullptr, unit, nullptr, false);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
template <typename TYPEID>
|
||||
uint16_t ModbusAPI<T>::errorResponce(TYPEID ip, Modbus::FunctionCode fn, Modbus::ResultCode excode, uint8_t unit) {
|
||||
this->exceptionResponse(fn, excode);
|
||||
return this->send(ip, NULLREG, nullptr, unit, nullptr, false);
|
||||
}
|
||||
59
lib/modbus-esp8266/ModbusEthernet.h
Normal file
59
lib/modbus-esp8266/ModbusEthernet.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
ModbusTCP for W5x00 Ethernet
|
||||
Copyright (C) 2022 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
#include <Dns.h>
|
||||
#endif
|
||||
#include "ModbusAPI.h"
|
||||
#include "ModbusTCPTemplate.h"
|
||||
#if defined(ARDUINO_PORTENTA_H7_M4) || defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_X8)
|
||||
#define MODBUS_ETH_WRAP_ACCEPT
|
||||
#undef MODBUS_ETH_WRAP_BEGIN
|
||||
#elif defined(ESP32)
|
||||
#undef MODBUS_ETH_WRAP_ACCEPT
|
||||
#define MODBUS_ETH_WRAP_BEGIN
|
||||
#else
|
||||
#undef MODBUS_ETH_WRAP_ACCEPT
|
||||
#undef MODBUS_ETH_WRAP_BEGIN
|
||||
#endif
|
||||
// Ethernet class wrapper to be able to compile for ESP32
|
||||
class EthernetServerWrapper : public EthernetServer {
|
||||
public:
|
||||
EthernetServerWrapper(uint16_t port) : EthernetServer(port) {
|
||||
|
||||
}
|
||||
#if defined(MODBUS_ETH_WRAP_BEGIN)
|
||||
void begin(uint16_t port=0) {
|
||||
EthernetServer::begin();
|
||||
}
|
||||
#endif
|
||||
#if defined(MODBUS_ETH_WRAP_ACCEPT)
|
||||
inline EthernetClient accept() {
|
||||
return available();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class ModbusEthernet : public ModbusAPI<ModbusTCPTemplate<EthernetServerWrapper, EthernetClient>> {
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
private:
|
||||
static IPAddress resolver (const char* host) {
|
||||
DNSClient dns;
|
||||
IPAddress ip;
|
||||
|
||||
dns.begin(Ethernet.dnsServerIP());
|
||||
if (dns.getHostByName(host, ip) == 1)
|
||||
return ip;
|
||||
else
|
||||
return IPADDR_NONE;
|
||||
}
|
||||
public:
|
||||
ModbusEthernet() : ModbusAPI() {
|
||||
resolve = resolver;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
11
lib/modbus-esp8266/ModbusIP_ESP8266.h
Normal file
11
lib/modbus-esp8266/ModbusIP_ESP8266.h
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
ModbusIP class compatibility wrapper
|
||||
Copyright (C) 2014 Andr<64> Sarmento Barbosa
|
||||
2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "ModbusTCP.h"
|
||||
|
||||
class ModbusIP : public ModbusTCP {};
|
||||
329
lib/modbus-esp8266/ModbusRTU.cpp
Normal file
329
lib/modbus-esp8266/ModbusRTU.cpp
Normal file
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
ModbusRTU implementation
|
||||
Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
https://github.com/emelianov/modbus-esp8266
|
||||
This code is licensed under the BSD New License. See LICENSE.txt for more info.
|
||||
*/
|
||||
#include "ModbusRTU.h"
|
||||
|
||||
// Table of CRC values
|
||||
static const uint16_t _auchCRC[] PROGMEM = {
|
||||
0x0000, 0xC1C0, 0x81C1, 0x4001, 0x01C3, 0xC003, 0x8002, 0x41C2, 0x01C6, 0xC006, 0x8007, 0x41C7, 0x0005, 0xC1C5, 0x81C4,
|
||||
0x4004, 0x01CC, 0xC00C, 0x800D, 0x41CD, 0x000F, 0xC1CF, 0x81CE, 0x400E, 0x000A, 0xC1CA, 0x81CB, 0x400B, 0x01C9, 0xC009,
|
||||
0x8008, 0x41C8, 0x01D8, 0xC018, 0x8019, 0x41D9, 0x001B, 0xC1DB, 0x81DA, 0x401A, 0x001E, 0xC1DE, 0x81DF, 0x401F, 0x01DD,
|
||||
0xC01D, 0x801C, 0x41DC, 0x0014, 0xC1D4, 0x81D5, 0x4015, 0x01D7, 0xC017, 0x8016, 0x41D6, 0x01D2, 0xC012, 0x8013, 0x41D3,
|
||||
0x0011, 0xC1D1, 0x81D0, 0x4010, 0x01F0, 0xC030, 0x8031, 0x41F1, 0x0033, 0xC1F3, 0x81F2, 0x4032, 0x0036, 0xC1F6, 0x81F7,
|
||||
0x4037, 0x01F5, 0xC035, 0x8034, 0x41F4, 0x003C, 0xC1FC, 0x81FD, 0x403D, 0x01FF, 0xC03F, 0x803E, 0x41FE, 0x01FA, 0xC03A,
|
||||
0x803B, 0x41FB, 0x0039, 0xC1F9, 0x81F8, 0x4038, 0x0028, 0xC1E8, 0x81E9, 0x4029, 0x01EB, 0xC02B, 0x802A, 0x41EA, 0x01EE,
|
||||
0xC02E, 0x802F, 0x41EF, 0x002D, 0xC1ED, 0x81EC, 0x402C, 0x01E4, 0xC024, 0x8025, 0x41E5, 0x0027, 0xC1E7, 0x81E6, 0x4026,
|
||||
0x0022, 0xC1E2, 0x81E3, 0x4023, 0x01E1, 0xC021, 0x8020, 0x41E0, 0x01A0, 0xC060, 0x8061, 0x41A1, 0x0063, 0xC1A3, 0x81A2,
|
||||
0x4062, 0x0066, 0xC1A6, 0x81A7, 0x4067, 0x01A5, 0xC065, 0x8064, 0x41A4, 0x006C, 0xC1AC, 0x81AD, 0x406D, 0x01AF, 0xC06F,
|
||||
0x806E, 0x41AE, 0x01AA, 0xC06A, 0x806B, 0x41AB, 0x0069, 0xC1A9, 0x81A8, 0x4068, 0x0078, 0xC1B8, 0x81B9, 0x4079, 0x01BB,
|
||||
0xC07B, 0x807A, 0x41BA, 0x01BE, 0xC07E, 0x807F, 0x41BF, 0x007D, 0xC1BD, 0x81BC, 0x407C, 0x01B4, 0xC074, 0x8075, 0x41B5,
|
||||
0x0077, 0xC1B7, 0x81B6, 0x4076, 0x0072, 0xC1B2, 0x81B3, 0x4073, 0x01B1, 0xC071, 0x8070, 0x41B0, 0x0050, 0xC190, 0x8191,
|
||||
0x4051, 0x0193, 0xC053, 0x8052, 0x4192, 0x0196, 0xC056, 0x8057, 0x4197, 0x0055, 0xC195, 0x8194, 0x4054, 0x019C, 0xC05C,
|
||||
0x805D, 0x419D, 0x005F, 0xC19F, 0x819E, 0x405E, 0x005A, 0xC19A, 0x819B, 0x405B, 0x0199, 0xC059, 0x8058, 0x4198, 0x0188,
|
||||
0xC048, 0x8049, 0x4189, 0x004B, 0xC18B, 0x818A, 0x404A, 0x004E, 0xC18E, 0x818F, 0x404F, 0x018D, 0xC04D, 0x804C, 0x418C,
|
||||
0x0044, 0xC184, 0x8185, 0x4045, 0x0187, 0xC047, 0x8046, 0x4186, 0x0182, 0xC042, 0x8043, 0x4183, 0x0041, 0xC181, 0x8180,
|
||||
0x4040, 0x0000
|
||||
};
|
||||
|
||||
uint16_t ModbusRTUTemplate::crc16(uint8_t address, uint8_t* frame, uint8_t pduLen) {
|
||||
uint8_t i = 0xFF ^ address;
|
||||
uint16_t val = pgm_read_word(_auchCRC + i);
|
||||
uint8_t CRCHi = 0xFF ^ highByte(val); // Hi
|
||||
uint8_t CRCLo = lowByte(val); //Low
|
||||
while (pduLen--) {
|
||||
i = CRCHi ^ *frame++;
|
||||
val = pgm_read_word(_auchCRC + i);
|
||||
CRCHi = CRCLo ^ highByte(val); // Hi
|
||||
CRCLo = lowByte(val); //Low
|
||||
}
|
||||
return (CRCHi << 8) | CRCLo;
|
||||
}
|
||||
/*
|
||||
uint16_t ModbusRTUTemplate::crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen) {
|
||||
uint16_t temp, temp2, flag;
|
||||
temp = 0xFFFF ^ address;
|
||||
for (uint8_t i = 0; i < pduLen; i++)
|
||||
{
|
||||
temp = temp ^ frame[i];
|
||||
for (uint8_t j = 1; j <= 8; j++)
|
||||
{
|
||||
flag = temp & 0x0001;
|
||||
temp >>= 1;
|
||||
if (flag)
|
||||
temp ^= 0xA001;
|
||||
}
|
||||
}
|
||||
// Reverse byte order.
|
||||
temp2 = temp >> 8;
|
||||
temp = (temp << 8) | temp2;
|
||||
temp &= 0xFFFF;
|
||||
return temp;
|
||||
}
|
||||
*/
|
||||
|
||||
uint32_t ModbusRTUTemplate::charSendTime(uint32_t baud, uint8_t char_bits) {
|
||||
return (uint32_t)char_bits * 1000000UL / baud;
|
||||
}
|
||||
|
||||
uint32_t ModbusRTUTemplate::calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits) {
|
||||
// baud = baudrate of the serial port
|
||||
// char_bits = size of 1 modbus character (defined a 11 bits in modbus specificacion)
|
||||
// Returns: The minimum time between frames (defined as 3.5 characters time in modbus specification)
|
||||
|
||||
// According to standard, the Modbus frame is always 11 bits long:
|
||||
// 1 start + 8 data + 1 parity + 1 stop
|
||||
// 1 start + 8 data + 2 stops
|
||||
// And the minimum time between frames is defined as 3.5 characters time in modbus specification.
|
||||
// This means the time between frames (in microseconds) should be calculated as follows:
|
||||
// _t = 3.5 x 11 x 1000000 / baudrate = 38500000 / baudrate
|
||||
|
||||
// Eg: For 9600 baudrate _t = 38500000 / 9600 = 4010 us
|
||||
// For baudrates grater than 19200 the _t should be fixed at 1750 us.
|
||||
|
||||
// If the used modbus frame length is 10 bits (out of standard - 1 start + 8 data + 1 stop), then
|
||||
// it can be set using char_bits = 10.
|
||||
|
||||
if (baud > 19200) {
|
||||
return 1750UL;
|
||||
} else {
|
||||
return 3.5 * charSendTime(baud, char_bits);
|
||||
}
|
||||
}
|
||||
|
||||
// Kept for backward compatibility
|
||||
void ModbusRTUTemplate::setBaudrate(uint32_t baud) {
|
||||
setInterFrameTime(calculateMinimumInterFrameTime(baud));
|
||||
}
|
||||
|
||||
void ModbusRTUTemplate::setInterFrameTime(uint32_t t_us) {
|
||||
// This function sets the inter frame time. This time is the time that task() waits before considering that the frame being transmitted on the RS485 bus has finished.
|
||||
// If the interframe calculated by calculateMinimumInterFrameTime() is not enough, you can set the interframe time manually with this function.
|
||||
// The time must be set in micro seconds.
|
||||
// This is useful when you are receiving data as a slave and you notice that the slave is dividing a frame in two or more pieces (and obviously the CRC is failing on all pieces).
|
||||
// This is because it is detecting an interframe time inbetween bytes of the frame and thus it interprets one single frame as two or more frames.
|
||||
// In that case it is useful to be able to set a more "permissive" interframe time.
|
||||
_t = t_us;
|
||||
}
|
||||
|
||||
bool ModbusRTUTemplate::begin(Stream* port, int16_t txEnablePin, bool txEnableDirect) {
|
||||
_port = port;
|
||||
_t = 1750UL;
|
||||
#if defined(MODBUSRTU_FLUSH_DELAY)
|
||||
_t1 = charSendTime(0);
|
||||
#endif
|
||||
if (txEnablePin >= 0) {
|
||||
_txEnablePin = txEnablePin;
|
||||
_direct = txEnableDirect;
|
||||
pinMode(_txEnablePin, OUTPUT);
|
||||
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModbusRTUTemplate::rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len) {
|
||||
uint16_t newCrc = crc16(slaveId, frame, len);
|
||||
#if defined(MODBUSRTU_DEBUG)
|
||||
for (uint8_t i=0 ; i < _len ; i++) {
|
||||
Serial.print(_frame[i], HEX);
|
||||
Serial.print(" ");
|
||||
}
|
||||
Serial.println();
|
||||
#endif
|
||||
#if defined(MODBUSRTU_REDE)
|
||||
if (_txEnablePin >= 0 || _rxPin >= 0) {
|
||||
if (_txEnablePin >= 0)
|
||||
digitalWrite(_txEnablePin, _direct?HIGH:LOW);
|
||||
if (_rxPin >= 0)
|
||||
digitalWrite(_rxPin, _direct?HIGH:LOW);
|
||||
#if !defined(ESP32)
|
||||
delayMicroseconds(MODBUSRTU_REDE_SWITCH_US);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
if (_txEnablePin >= 0) {
|
||||
digitalWrite(_txEnablePin, _direct?HIGH:LOW);
|
||||
#if !defined(ESP32)
|
||||
delayMicroseconds(MODBUSRTU_REDE_SWITCH_US);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if defined(ESP32)
|
||||
vTaskDelay(0);
|
||||
#endif
|
||||
_port->write(slaveId); //Send slaveId
|
||||
_port->write(frame, len); // Send PDU
|
||||
_port->write(newCrc >> 8); //Send CRC
|
||||
_port->write(newCrc & 0xFF);//Send CRC
|
||||
_port->flush();
|
||||
#if defined(MODBUSRTU_REDE)
|
||||
if (_txEnablePin >= 0 || _rxPin >= 0) {
|
||||
#if defined(MODBUSRTU_FLUSH_DELAY)
|
||||
delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY);
|
||||
#endif
|
||||
if (_txEnablePin >= 0)
|
||||
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
|
||||
if (_rxPin >= 0)
|
||||
digitalWrite(_rxPin, _direct?LOW:HIGH);
|
||||
}
|
||||
#else
|
||||
if (_txEnablePin >= 0) {
|
||||
#if defined(MODBUSRTU_FLUSH_DELAY)
|
||||
delayMicroseconds(_t1 * MODBUSRTU_FLUSH_DELAY);
|
||||
#endif
|
||||
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t ModbusRTUTemplate::send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
|
||||
bool result = false;
|
||||
if ((!isMaster || !_slaveId) && _len && _frame) { // Check if waiting for previous request result and _frame filled
|
||||
//if (_len && _frame) { // Check if waiting for previous request result and _frame filled
|
||||
rawSend(slaveId, _frame, _len);
|
||||
if (waitResponse && slaveId) {
|
||||
_slaveId = slaveId;
|
||||
_timestamp = micros();
|
||||
_cb = cb;
|
||||
_data = data;
|
||||
_sentFrame = _frame;
|
||||
_sentReg = startreg;
|
||||
_frame = nullptr;
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
free(_frame);
|
||||
_frame = nullptr;
|
||||
_len = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void ModbusRTUTemplate::task() {
|
||||
#if defined(ESP32)
|
||||
vTaskDelay(0);
|
||||
#endif
|
||||
if (_port->available() > _len) {
|
||||
_len = _port->available();
|
||||
t = micros();
|
||||
}
|
||||
if (_len == 0) {
|
||||
if (isMaster) cleanup();
|
||||
return;
|
||||
}
|
||||
if (isMaster) {
|
||||
if (micros() - t < _t) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else { // For slave wait for whole message to come (unless MODBUSRTU_MAX_READMS reached)
|
||||
uint32_t taskStart = micros();
|
||||
while (micros() - t < _t) { // Wait data whitespace
|
||||
if (_port->available() > _len) {
|
||||
_len = _port->available();
|
||||
t = micros();
|
||||
}
|
||||
if (micros() - taskStart > MODBUSRTU_MAX_READ_US) { // Prevent from task() executed too long
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool valid_frame = true;
|
||||
address = _port->read(); //first byte of frame = address
|
||||
_len--; // Decrease by slaveId byte
|
||||
if (isMaster && _slaveId == 0) { // Check if slaveId is set
|
||||
valid_frame = false;
|
||||
}
|
||||
if (address != MODBUSRTU_BROADCAST && address != _slaveId) { // SlaveId Check
|
||||
valid_frame = false;
|
||||
}
|
||||
if (!valid_frame && !_cbRaw) {
|
||||
for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if SlaveId doesn't mach
|
||||
_len = 0;
|
||||
if (isMaster) cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
free(_frame); //Just in case
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) { // Fail to allocate buffer
|
||||
for (uint8_t i=0 ; i < _len ; i++) _port->read(); // Skip packet if can't allocate buffer
|
||||
_len = 0;
|
||||
if (isMaster) cleanup();
|
||||
return;
|
||||
}
|
||||
for (uint8_t i=0 ; i < _len ; i++) {
|
||||
_frame[i] = _port->read(); // read data + crc
|
||||
#if defined(MODBUSRTU_DEBUG)
|
||||
Serial.print(_frame[i], HEX);
|
||||
Serial.print(" ");
|
||||
#endif
|
||||
}
|
||||
#if defined(MODBUSRTU_DEBUG)
|
||||
Serial.println();
|
||||
#endif
|
||||
//_port->readBytes(_frame, _len);
|
||||
uint16_t frameCrc = ((_frame[_len - 2] << 8) | _frame[_len - 1]); // Last two byts = crc
|
||||
_len = _len - 2; // Decrease by CRC 2 bytes
|
||||
if (frameCrc != crc16(address, _frame, _len)) { // CRC Check
|
||||
goto cleanup;
|
||||
}
|
||||
_reply = EX_PASSTHROUGH;
|
||||
if (_cbRaw) {
|
||||
frame_arg_t header_data = { address, !isMaster };
|
||||
_reply = _cbRaw(_frame, _len, (void*)&header_data);
|
||||
}
|
||||
if (!valid_frame && _reply != EX_FORCE_PROCESS) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (isMaster) {
|
||||
if ((_frame[0] & 0x7F) == _sentFrame[0]) { // Check if function code the same as requested
|
||||
// Procass incoming frame as master
|
||||
if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS)
|
||||
masterPDU(_frame, _sentFrame, _sentReg, _data);
|
||||
if (_cb) {
|
||||
_cb((ResultCode)_reply, 0, nullptr);
|
||||
_cb = nullptr;
|
||||
}
|
||||
free(_sentFrame);
|
||||
_sentFrame = nullptr;
|
||||
_data = nullptr;
|
||||
_slaveId = 0;
|
||||
}
|
||||
_reply = Modbus::REPLY_OFF; // No reply if master
|
||||
} else {
|
||||
if (_reply == EX_PASSTHROUGH || _reply == EX_FORCE_PROCESS) {
|
||||
slavePDU(_frame);
|
||||
if (address == MODBUSRTU_BROADCAST)
|
||||
_reply = Modbus::REPLY_OFF; // No reply for Broadcasts
|
||||
if (_reply != Modbus::REPLY_OFF)
|
||||
rawSend(address, _frame, _len);
|
||||
}
|
||||
}
|
||||
// Cleanup
|
||||
cleanup:
|
||||
free(_frame);
|
||||
_frame = nullptr;
|
||||
_len = 0;
|
||||
if (isMaster) cleanup();
|
||||
}
|
||||
|
||||
bool ModbusRTUTemplate::cleanup() {
|
||||
// Remove timeouted request and forced event
|
||||
if (_slaveId && (micros() - _timestamp > MODBUSRTU_TIMEOUT_US)) {
|
||||
if (_cb) {
|
||||
_cb(Modbus::EX_TIMEOUT, 0, nullptr);
|
||||
_cb = nullptr;
|
||||
}
|
||||
free(_sentFrame);
|
||||
_sentFrame = nullptr;
|
||||
_data = nullptr;
|
||||
_slaveId = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
99
lib/modbus-esp8266/ModbusRTU.h
Normal file
99
lib/modbus-esp8266/ModbusRTU.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
ModbusRTU
|
||||
Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
https://github.com/emelianov/modbus-esp8266
|
||||
This code is licensed under the BSD New License. See LICENSE.txt for more info.
|
||||
*/
|
||||
#pragma once
|
||||
#include "ModbusAPI.h"
|
||||
|
||||
class ModbusRTUTemplate : public Modbus {
|
||||
protected:
|
||||
Stream* _port;
|
||||
int16_t _txEnablePin = -1;
|
||||
#if defined(MODBUSRTU_REDE)
|
||||
int16_t _rxPin = -1;
|
||||
#endif
|
||||
bool _direct = true; // Transmit control logic (true=txEnableDirect, false=inverse)
|
||||
uint32_t _t; // inter-frame delay in uS
|
||||
#if defined(MODBUSRTU_FLUSH_DELAY)
|
||||
uint32_t _t1; // char send time
|
||||
#endif
|
||||
uint32_t t = 0; // time sience last data byte arrived
|
||||
bool isMaster = false;
|
||||
uint8_t _slaveId;
|
||||
uint32_t _timestamp = 0;
|
||||
cbTransaction _cb = nullptr;
|
||||
uint8_t* _data = nullptr;
|
||||
uint8_t* _sentFrame = nullptr;
|
||||
TAddress _sentReg = COIL(0);
|
||||
uint16_t maxRegs = MODBUS_MAX_WORDS;
|
||||
uint8_t address = 0;
|
||||
|
||||
uint16_t send(uint8_t slaveId, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
|
||||
// Prepare and send ModbusRTU frame. _frame buffer and _len should be filled with Modbus data
|
||||
// slaveId - slave id
|
||||
// startreg - first local register to save returned data to (miningless for write to slave operations)
|
||||
// cb - transaction callback function
|
||||
// data - if not null use buffer to save returned data instead of local registers
|
||||
bool rawSend(uint8_t slaveId, uint8_t* frame, uint8_t len);
|
||||
bool cleanup(); // Free clients if not connected and remove timedout transactions and transaction with forced events
|
||||
uint16_t crc16(uint8_t address, uint8_t* frame, uint8_t pdulen);
|
||||
uint16_t crc16_alt(uint8_t address, uint8_t* frame, uint8_t pduLen);
|
||||
public:
|
||||
void setBaudrate(uint32_t baud = -1);
|
||||
uint32_t calculateMinimumInterFrameTime(uint32_t baud, uint8_t char_bits = 11);
|
||||
void setInterFrameTime(uint32_t t_us);
|
||||
uint32_t charSendTime(uint32_t baud, uint8_t char_bits = 11);
|
||||
template <class T>
|
||||
bool begin(T* port, int16_t txEnablePin = -1, bool txEnableDirect = true);
|
||||
#if defined(MODBUSRTU_REDE)
|
||||
template <class T>
|
||||
bool begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect);
|
||||
#endif
|
||||
bool begin(Stream* port, int16_t txEnablePin = -1, bool txEnableDirect = true);
|
||||
void task();
|
||||
void client() { isMaster = true; };
|
||||
inline void master() {client();}
|
||||
void server(uint8_t serverId) {_slaveId = serverId;};
|
||||
inline void slave(uint8_t slaveId) {server(slaveId);}
|
||||
uint8_t server() { return _slaveId; }
|
||||
inline uint8_t slave() { return server(); }
|
||||
uint32_t eventSource() override {return address;}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, bool txEnableDirect) {
|
||||
uint32_t baud = 0;
|
||||
#if defined(ESP32) || defined(ESP8266) // baudRate() only available with ESP32+ESP8266
|
||||
baud = port->baudRate();
|
||||
#else
|
||||
baud = 9600;
|
||||
#endif
|
||||
setInterFrameTime(calculateMinimumInterFrameTime(baud));
|
||||
#if defined(MODBUSRTU_FLUSH_DELAY)
|
||||
_t1 = charSendTime(baud);
|
||||
#endif
|
||||
_port = port;
|
||||
if (txEnablePin >= 0) {
|
||||
_txEnablePin = txEnablePin;
|
||||
_direct = txEnableDirect;
|
||||
pinMode(_txEnablePin, OUTPUT);
|
||||
digitalWrite(_txEnablePin, _direct?LOW:HIGH);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if defined(MODBUSRTU_REDE)
|
||||
template <class T>
|
||||
bool ModbusRTUTemplate::begin(T* port, int16_t txEnablePin, int16_t rxEnablePin, bool txEnableDirect) {
|
||||
begin(port, txEnablePin, txEnableDirect);
|
||||
if (rxEnablePin > 0) {
|
||||
_rxPin = rxEnablePin;
|
||||
pinMode(_rxPin, OUTPUT);
|
||||
digitalWrite(_rxPin, _direct?LOW:HIGH);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
class ModbusRTU : public ModbusAPI<ModbusRTUTemplate> {};
|
||||
148
lib/modbus-esp8266/ModbusSettings.h
Normal file
148
lib/modbus-esp8266/ModbusSettings.h
Normal file
@@ -0,0 +1,148 @@
|
||||
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
|
||||
Copyright (C) 2019-2022 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
https://github.com/emelianov/modbus-esp8266
|
||||
This code is licensed under the BSD New License. See LICENSE.txt for more info.
|
||||
|
||||
Prefixes:
|
||||
MODBUS_ Global library settings
|
||||
MODBUSIP_ Settings for TCP and TLS both
|
||||
MODBUSTCP_ Settings for TCP
|
||||
MODBUSTLS_ Settings for TLS
|
||||
MODBUSRTU_ Settings for RTU
|
||||
MODBUSAPI_ Settings for API
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
#define MODBUS_GLOBAL_REGS
|
||||
If defined Modbus registers will be shared across all Modbus* instances.
|
||||
If not defined each Modbus object will have own registers set.
|
||||
*/
|
||||
#define MODBUS_GLOBAL_REGS
|
||||
//#define MODBUS_FREE_REGS
|
||||
|
||||
/*
|
||||
#define ARDUINO_SAM_DUE_STL
|
||||
Use STL with Arduino Due. Was able to use with Arduino IDE but not with PlatformIO
|
||||
Also note STL issue workaround code in Modbus.cpp
|
||||
*/
|
||||
#if defined(ARDUINO_SAM_DUE)
|
||||
//#define ARDUINO_SAM_DUE_STL
|
||||
#endif
|
||||
|
||||
/*
|
||||
#define MODBUS_USE_STL
|
||||
If defined C STL will be used.
|
||||
*/
|
||||
#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_SAM_DUE_STL)
|
||||
#define MODBUS_USE_STL
|
||||
#endif
|
||||
|
||||
/*
|
||||
#define MODBUS_MAX_REGS 32
|
||||
If defined regisers count will be limited.
|
||||
*/
|
||||
// Add limitation for specific STL implementation
|
||||
#if defined(MODBUS_USE_STL) && (defined(ESP8266) || defined(ESP32))
|
||||
#undef MODBUS_MAX_REGS
|
||||
#define MODBUS_MAX_REGS 4000
|
||||
#endif
|
||||
|
||||
#define MODBUS_ADD_REG
|
||||
//#define MODBUS_STRICT_REG
|
||||
#define MODBUS_MAX_FRAME 256
|
||||
//#define MODBUS_STATIC_FRAME
|
||||
#define MODBUS_MAX_WORDS 0x007D
|
||||
#define MODBUS_MAX_BITS 0x07D0
|
||||
#define MODBUS_FILES
|
||||
#define MODBUS_MAX_FILES 0x270F
|
||||
#define MODBUSTCP_PORT 502
|
||||
#define MODBUSTLS_PORT 802
|
||||
#define MODBUSIP_MINFRAME 2
|
||||
#define MODBUSIP_MAXFRAME 200
|
||||
|
||||
/*
|
||||
ModbusTCP and ModbusTLS timeouts
|
||||
#define MODBUSIP_TIMEOUT 1000
|
||||
Outgoing request timeout
|
||||
#define MODBUSIP_CONNECT_TIMEOUT 1000
|
||||
ESP32 only. Outgoing connection attempt timeout
|
||||
*/
|
||||
#define MODBUSIP_TIMEOUT 1000
|
||||
//#define MODBUSIP_CONNECT_TIMEOUT 1000
|
||||
|
||||
#define MODBUSIP_UNIT 255
|
||||
#define MODBUSIP_MAX_TRANSACTIONS 16
|
||||
#if defined(ESP32)
|
||||
#define MODBUSIP_MAX_CLIENTS 8
|
||||
#else
|
||||
#define MODBUSIP_MAX_CLIENTS 4
|
||||
#endif
|
||||
#define MODBUSIP_UNIQUE_CLIENTS
|
||||
#define MODBUSIP_MAX_READMS 100
|
||||
|
||||
/*
|
||||
Use available() instead of accept() to get TCP client
|
||||
#define MODBUSIP_USE_AVAILABLE
|
||||
Used to wrap variation in Ethernet/WiFi client API implementations
|
||||
*/
|
||||
//#define MODBUSIP_USE_AVAILABLE
|
||||
|
||||
#define MODBUSIP_FULL
|
||||
//#define MODBUSIP_DEBUG
|
||||
/*
|
||||
Allows to use DNS names as target
|
||||
Otherwise IP addresses only must be used
|
||||
#define MODBUS_IP_USE_DNS
|
||||
*/
|
||||
//#define MODBUS_IP_USE_DNS
|
||||
|
||||
//#define MODBUSRTU_DEBUG
|
||||
#define MODBUSRTU_BROADCAST 0
|
||||
#define MB_RESERVE 248
|
||||
#define MB_SERIAL_BUFFER 128
|
||||
#ifndef MODBUSRTU_TIMEOUT
|
||||
#define MODBUSRTU_TIMEOUT 1000
|
||||
#endif
|
||||
#define MODBUSRTU_MAX_READMS 100
|
||||
/*
|
||||
#define MODBUSRTU_REDE
|
||||
Enable using separate pins for RE DE
|
||||
*/
|
||||
//#define MODBUSRTU_REDE
|
||||
|
||||
// Define for internal use. Do not change.
|
||||
#define MODBUSRTU_TIMEOUT_US 1000UL * MODBUSRTU_TIMEOUT
|
||||
#define MODBUSRTU_MAX_READ_US 1000UL * MODBUSRTU_MAX_READMS
|
||||
|
||||
/*
|
||||
#defone MODBUSRTU_FLUSH_DELAY 1
|
||||
Set extraa delay after serial buffer flush before changing RE/DE pin state.
|
||||
Specified in chars. That is 1 is means to add delay enough to send 1 char at current port baudrate
|
||||
*/
|
||||
//#define MODBUSRTU_FLUSH_DELAY 1
|
||||
|
||||
#define MODBUSRTU_REDE_SWITCH_US 1000
|
||||
|
||||
#define MODBUSAPI_LEGACY
|
||||
#define MODBUSAPI_OPTIONAL
|
||||
|
||||
// Workaround for RP2040 flush() bug
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
#define MODBUSRTU_FLUSH_DELAY 1
|
||||
#endif
|
||||
|
||||
// Limit resources usage for entry level boards
|
||||
#if defined(ARDUINO_UNO) || defined(ARDUINO_LEONARDO)
|
||||
#undef MODBUS_MAX_REGS
|
||||
#undef MODBUSIP_MAX_TRANSACTIONS
|
||||
#undef MODBUS_MAX_WORDS
|
||||
#undef MODBUS_MAX_BITS
|
||||
#define MODBUS_MAX_REGS 32
|
||||
#define MODBUSIP_MAX_TRANSACTIONS 4
|
||||
#define MODBUS_MAX_WORDS 0x0020
|
||||
#define MODBUS_MAX_BITS 0x0200
|
||||
#endif
|
||||
40
lib/modbus-esp8266/ModbusTCP.h
Normal file
40
lib/modbus-esp8266/ModbusTCP.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
ModbusTCP for ESP8266/ESP32
|
||||
Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#include "ModbusAPI.h"
|
||||
#include "ModbusTCPTemplate.h"
|
||||
|
||||
class WiFiServerESPWrapper : public WiFiServer {
|
||||
public:
|
||||
WiFiServerESPWrapper(uint16_t port) : WiFiServer(port) {}
|
||||
inline WiFiClient accept() {
|
||||
return available();
|
||||
}
|
||||
};
|
||||
|
||||
class ModbusTCP : public ModbusAPI<ModbusTCPTemplate<WiFiServerESPWrapper, WiFiClient>> {
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
private:
|
||||
static IPAddress resolver(const char *host) {
|
||||
IPAddress remote_addr;
|
||||
if (WiFi.hostByName(host, remote_addr))
|
||||
return remote_addr;
|
||||
return IPADDR_NONE;
|
||||
}
|
||||
|
||||
public:
|
||||
ModbusTCP() : ModbusAPI() {
|
||||
resolve = resolver;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
606
lib/modbus-esp8266/ModbusTCPTemplate.h
Normal file
606
lib/modbus-esp8266/ModbusTCPTemplate.h
Normal file
@@ -0,0 +1,606 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
ModbusTCP general implementation
|
||||
Copyright (C) 2014 Andr<64> Sarmento Barbosa
|
||||
2017-2020 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Modbus.h"
|
||||
|
||||
#define BIT_SET(a,b) ((a) |= (1ULL<<(b)))
|
||||
#define BIT_CLEAR(a,b) ((a) &= ~(1ULL<<(b)))
|
||||
#define BIT_CHECK(a,b) (!!((a) & (1ULL<<(b)))) // '!!' to make sure this returns 0 or 1
|
||||
#ifndef IPADDR_NONE
|
||||
#define IPADDR_NONE ((uint32_t)0xffffffffUL)
|
||||
#endif
|
||||
// Callback function Type
|
||||
#if defined(MODBUS_USE_STL)
|
||||
typedef std::function<bool(IPAddress)> cbModbusConnect;
|
||||
typedef std::function<IPAddress(const char*)> cbModbusResolver;
|
||||
#else
|
||||
typedef bool (*cbModbusConnect)(IPAddress ip);
|
||||
typedef IPAddress (*cbModbusResolver)(const char*);
|
||||
#endif
|
||||
|
||||
struct TTransaction {
|
||||
uint16_t transactionId;
|
||||
uint32_t timestamp;
|
||||
cbTransaction cb = nullptr;
|
||||
uint8_t* _frame = nullptr;
|
||||
uint8_t* data = nullptr;
|
||||
TAddress startreg;
|
||||
Modbus::ResultCode forcedEvent = Modbus::EX_SUCCESS; // EX_SUCCESS means no forced event here. Forced EX_SUCCESS is not possible.
|
||||
bool operator ==(const TTransaction &obj) const {
|
||||
return transactionId == obj.transactionId;
|
||||
}
|
||||
};
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
class ModbusTCPTemplate : public Modbus {
|
||||
protected:
|
||||
union MBAP_t {
|
||||
struct {
|
||||
uint16_t transactionId;
|
||||
uint16_t protocolId;
|
||||
uint16_t length;
|
||||
uint8_t unitId;
|
||||
};
|
||||
uint8_t raw[7];
|
||||
};
|
||||
cbModbusConnect cbConnect = nullptr;
|
||||
cbModbusConnect cbDisconnect = nullptr;
|
||||
SERVER* tcpserver = nullptr;
|
||||
CLIENT* tcpclient[MODBUSIP_MAX_CLIENTS];
|
||||
#if MODBUSIP_MAX_CLIENTS <= 8
|
||||
uint8_t tcpServerConnection = 0;
|
||||
#elif MODBUSIP_MAX_CLIENTS <= 16
|
||||
uint16_t tcpServerConnection = 0;
|
||||
#else
|
||||
uint32_t tcpServerConnection = 0;
|
||||
#endif
|
||||
#if defined(MODBUS_USE_STL)
|
||||
std::vector<TTransaction> _trans;
|
||||
#else
|
||||
DArray<TTransaction, 2, 2> _trans;
|
||||
#endif
|
||||
int16_t transactionId = 1; // Last started transaction. Increments on unsuccessful transaction start too.
|
||||
int8_t n = -1;
|
||||
bool autoConnectMode = false;
|
||||
uint16_t serverPort = 0;
|
||||
uint16_t defaultPort = MODBUSTCP_PORT;
|
||||
cbModbusResolver resolve = nullptr;
|
||||
TTransaction* searchTransaction(uint16_t id);
|
||||
void cleanupConnections(); // Free clients if not connected
|
||||
void cleanupTransactions(); // Remove timedout transactions and forced event
|
||||
|
||||
int8_t getFreeClient(); // Returns free slot position
|
||||
int8_t getSlave(IPAddress ip);
|
||||
int8_t getMaster(IPAddress ip);
|
||||
public:
|
||||
uint16_t send(String host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
|
||||
uint16_t send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
|
||||
uint16_t send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit = MODBUSIP_UNIT, uint8_t* data = nullptr, bool waitResponse = true);
|
||||
// Prepare and send ModbusIP frame. _frame buffer and _len should be filled with Modbus data
|
||||
// ip - slave ip address
|
||||
// startreg - first local register to save returned data to (miningless for write to slave operations)
|
||||
// cb - transaction callback function
|
||||
// unit - slave modbus unit id
|
||||
// data - if not null use buffer to save returned data instead of local registers
|
||||
public:
|
||||
ModbusTCPTemplate();
|
||||
~ModbusTCPTemplate();
|
||||
bool isTransaction(uint16_t id);
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
bool isConnected(String host);
|
||||
bool isConnected(const char* host);
|
||||
bool connect(String host, uint16_t port = 0);
|
||||
bool connect(const char* host, uint16_t port = 0);
|
||||
bool disconnect(String host);
|
||||
bool disconnect(const char* host);
|
||||
#endif
|
||||
bool isConnected(IPAddress ip);
|
||||
bool connect(IPAddress ip, uint16_t port = 0);
|
||||
bool disconnect(IPAddress ip);
|
||||
// ModbusTCP
|
||||
void server(uint16_t port = 0);
|
||||
// ModbusTCP depricated
|
||||
inline void slave(uint16_t port = 0) { server(port); } // Depricated
|
||||
inline void master() { client(); } // Depricated
|
||||
inline void begin() { server(); }; // Depricated
|
||||
void client();
|
||||
void task();
|
||||
void onConnect(cbModbusConnect cb = nullptr);
|
||||
void onDisconnect(cbModbusConnect cb = nullptr);
|
||||
uint32_t eventSource() override;
|
||||
void autoConnect(bool enabled = true);
|
||||
void dropTransactions();
|
||||
uint16_t setTransactionId(uint16_t);
|
||||
#if defined(MODBUS_USE_STL)
|
||||
static IPAddress defaultResolver(const char*) {return IPADDR_NONE;}
|
||||
#else
|
||||
static IPAddress defaultResolver(const char*) {return IPADDR_NONE;}
|
||||
#endif
|
||||
};
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
ModbusTCPTemplate<SERVER, CLIENT>::ModbusTCPTemplate() {
|
||||
//_trans.reserve(MODBUSIP_MAX_TRANSACIONS);
|
||||
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
|
||||
tcpclient[i] = nullptr;
|
||||
resolve = defaultResolver;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::client() {
|
||||
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::server(uint16_t port) {
|
||||
if (port)
|
||||
serverPort = port;
|
||||
else
|
||||
serverPort = defaultPort;
|
||||
tcpserver = new SERVER(serverPort);
|
||||
tcpserver->begin();
|
||||
}
|
||||
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::connect(String host, uint16_t port) {
|
||||
return connect(resolve(host.c_str()), port);
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::connect(const char* host, uint16_t port) {
|
||||
return connect(resolve(host), port);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::connect(IPAddress ip, uint16_t port) {
|
||||
//cleanupConnections();
|
||||
if (!ip)
|
||||
return false;
|
||||
if(getSlave(ip) != -1)
|
||||
return true;
|
||||
int8_t p = getFreeClient();
|
||||
if (p == -1)
|
||||
return false;
|
||||
tcpclient[p] = new CLIENT();
|
||||
BIT_CLEAR(tcpServerConnection, p);
|
||||
#if defined(ESP32) && defined(MODBUSIP_CONNECT_TIMEOUT)
|
||||
if (!tcpclient[p]->connect(ip, port?port:defaultPort, MODBUSIP_CONNECT_TIMEOUT)) {
|
||||
#else
|
||||
if (!tcpclient[p]->connect(ip, port?port:defaultPort)) {
|
||||
#endif
|
||||
delete(tcpclient[p]);
|
||||
tcpclient[p] = nullptr;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
uint32_t ModbusTCPTemplate<SERVER, CLIENT>::eventSource() { // Returns IP of current processing client query
|
||||
if (n >= 0 && n < MODBUSIP_MAX_CLIENTS && tcpclient[n])
|
||||
#if !defined(ethernet_h)
|
||||
return (uint32_t)tcpclient[n]->remoteIP();
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
return (uint32_t)INADDR_NONE;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
TTransaction* ModbusTCPTemplate<SERVER, CLIENT>::searchTransaction(uint16_t id) {
|
||||
#define MODBUSIP_COMPARE_TRANS [id](TTransaction& trans){return trans.transactionId == id;}
|
||||
#if defined(MODBUS_USE_STL)
|
||||
std::vector<TTransaction>::iterator it = std::find_if(_trans.begin(), _trans.end(), MODBUSIP_COMPARE_TRANS);
|
||||
if (it != _trans.end()) return &*it;
|
||||
return nullptr;
|
||||
#else
|
||||
return _trans.entry(_trans.find(MODBUSIP_COMPARE_TRANS));
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::task() {
|
||||
MBAP_t _MBAP;
|
||||
uint32_t taskStart = millis();
|
||||
cleanupConnections();
|
||||
if (tcpserver) {
|
||||
CLIENT c;
|
||||
// WiFiServer.available() == Ethernet.accept() and should wrapped to get code to be compatible with Ethernet library (See ModbusTCP.h code).
|
||||
// WiFiServer.available() != Ethernet.available() internally
|
||||
#if defined(MODBUSIP_USE_AVAILABLE)
|
||||
while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->available())) {
|
||||
#else
|
||||
while (millis() - taskStart < MODBUSIP_MAX_READMS && (c = tcpserver->accept())) {
|
||||
#endif
|
||||
#if defined(MODBUSIP_DEBUG)
|
||||
Serial.println("IP: Accepted");
|
||||
#endif
|
||||
CLIENT* currentClient = new CLIENT(c);
|
||||
if (!currentClient || !currentClient->connected()) {
|
||||
delete currentClient;
|
||||
continue;
|
||||
}
|
||||
#if defined(MODBUSIP_DEBUG)
|
||||
Serial.println("IP: Connected");
|
||||
#endif
|
||||
if (cbConnect == nullptr || cbConnect(currentClient->remoteIP())) {
|
||||
#if defined(MODBUSIP_UNIQUE_CLIENTS)
|
||||
// Disconnect previous connection from same IP if present
|
||||
n = getMaster(currentClient->remoteIP());
|
||||
if (n != -1) {
|
||||
tcpclient[n]->flush();
|
||||
delete tcpclient[n];
|
||||
tcpclient[n] = nullptr;
|
||||
}
|
||||
#endif
|
||||
n = getFreeClient();
|
||||
if (n > -1) {
|
||||
tcpclient[n] = currentClient;
|
||||
BIT_SET(tcpServerConnection, n);
|
||||
#if defined(MODBUSIP_DEBUG)
|
||||
Serial.print("IP: Conn ");
|
||||
Serial.println(n);
|
||||
#endif
|
||||
#if defined(MODBUSIP_USE_AVAILABLE)
|
||||
break; // while
|
||||
#else
|
||||
continue; // while
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// Close connection if callback returns false or MODBUSIP_MAX_CLIENTS reached
|
||||
delete currentClient;
|
||||
}
|
||||
}
|
||||
for (n = 0; n < MODBUSIP_MAX_CLIENTS; n++) {
|
||||
if (!tcpclient[n]) continue;
|
||||
if (!tcpclient[n]->connected()) continue;
|
||||
while ((size_t)tcpclient[n]->available() > sizeof(_MBAP) && millis() - taskStart < MODBUSIP_MAX_READMS) {
|
||||
#if defined(MODBUSIP_DEBUG)
|
||||
Serial.print(n);
|
||||
Serial.print(": Bytes available ");
|
||||
Serial.println(tcpclient[n]->available());
|
||||
#endif
|
||||
tcpclient[n]->readBytes(_MBAP.raw, sizeof(_MBAP.raw)); // Get MBAP
|
||||
|
||||
if (__swap_16(_MBAP.protocolId) != 0) { // Check if MODBUSIP packet. __swap is usless there.
|
||||
while (tcpclient[n]->available()) // Drop all incoming if wrong packet
|
||||
tcpclient[n]->read();
|
||||
continue;
|
||||
}
|
||||
_len = __swap_16(_MBAP.length);
|
||||
if (_len < MODBUSIP_MINFRAME) { // Length is shorter than MODBUSIP_MINFRAME
|
||||
Modbus::FunctionCode fc = FC_READ_COILS; // Just placeholder
|
||||
while (tcpclient[n]->available()) // Drop rest of the packet
|
||||
tcpclient[n]->read();
|
||||
exceptionResponse(fc, EX_ILLEGAL_VALUE);
|
||||
}
|
||||
_len--; // Do not count with last byte from MBAP
|
||||
if (_len > MODBUSIP_MAXFRAME) { // Length is over MODBUSIP_MAXFRAME
|
||||
Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read();
|
||||
_len--; // Subtract for read byte
|
||||
for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet
|
||||
tcpclient[n]->read();
|
||||
exceptionResponse(fc, EX_SLAVE_FAILURE);
|
||||
}
|
||||
else {
|
||||
free(_frame);
|
||||
_frame = (uint8_t*) malloc(_len);
|
||||
if (!_frame) {
|
||||
Modbus::FunctionCode fc = (Modbus::FunctionCode)tcpclient[n]->read();
|
||||
_len--; // Subtract for read byte
|
||||
for (uint8_t i = 0; tcpclient[n]->available() && i < _len; i++) // Drop rest of the packet
|
||||
tcpclient[n]->read();
|
||||
exceptionResponse(fc, EX_SLAVE_FAILURE);
|
||||
}
|
||||
else {
|
||||
if (tcpclient[n]->readBytes(_frame, _len) < _len) { // Try to read MODBUS frame
|
||||
exceptionResponse((Modbus::FunctionCode)_frame[0], EX_ILLEGAL_VALUE);
|
||||
//while (tcpclient[n]->available()) // Drop all incoming (if any)
|
||||
// tcpclient[n]->read();
|
||||
}
|
||||
else {
|
||||
_reply = EX_PASSTHROUGH;
|
||||
// Note on _reply usage
|
||||
// it's used and set as ReplyCode by slavePDU and as exceptionCode by masterPDU
|
||||
if (_cbRaw) {
|
||||
frame_arg_t transData = { _MBAP.unitId, tcpclient[n]->remoteIP(), __swap_16(_MBAP.transactionId), BIT_CHECK(tcpServerConnection, n) };
|
||||
_reply = _cbRaw(_frame, _len, &transData);
|
||||
}
|
||||
if (BIT_CHECK(tcpServerConnection, n)) {
|
||||
if (_reply == EX_PASSTHROUGH)
|
||||
slavePDU(_frame); // Process incoming frame as slave
|
||||
else
|
||||
_reply = REPLY_OFF;
|
||||
}
|
||||
else {
|
||||
// Process reply to master request
|
||||
TTransaction* trans = searchTransaction(__swap_16(_MBAP.transactionId));
|
||||
if (trans) { // if valid transaction id
|
||||
if ((_frame[0] & 0x7F) == trans->_frame[0]) { // Check if function code the same as requested
|
||||
if (_reply == EX_PASSTHROUGH)
|
||||
masterPDU(_frame, trans->_frame, trans->startreg, trans->data); // Process incoming frame as master
|
||||
}
|
||||
else {
|
||||
_reply = EX_UNEXPECTED_RESPONSE;
|
||||
}
|
||||
if (trans->cb) {
|
||||
trans->cb((ResultCode)_reply, trans->transactionId, nullptr);
|
||||
}
|
||||
free(trans->_frame);
|
||||
#if defined(MODBUS_USE_STL)
|
||||
//_trans.erase(std::remove(_trans.begin(), _trans.end(), *trans), _trans.end() );
|
||||
std::vector<TTransaction>::iterator it = std::find(_trans.begin(), _trans.end(), *trans);
|
||||
if (it != _trans.end())
|
||||
_trans.erase(it);
|
||||
#else
|
||||
size_t r = _trans.find([trans](TTransaction& t){return *trans == t;});
|
||||
_trans.remove(r);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!BIT_CHECK(tcpServerConnection, n)) _reply = REPLY_OFF; // No replay if it was responce to master
|
||||
if (_reply != REPLY_OFF) {
|
||||
_MBAP.length = __swap_16(_len+1); // _len+1 for last byte from MBAP
|
||||
size_t send_len = (uint16_t)_len + sizeof(_MBAP.raw);
|
||||
uint8_t sbuf[send_len];
|
||||
memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw));
|
||||
memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len);
|
||||
tcpclient[n]->write(sbuf, send_len);
|
||||
//tcpclient[n]->flush();
|
||||
}
|
||||
if (_frame) {
|
||||
free(_frame);
|
||||
_frame = nullptr;
|
||||
}
|
||||
_len = 0;
|
||||
}
|
||||
}
|
||||
n = -1;
|
||||
cleanupTransactions();
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::send(String host, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
|
||||
return send(resolve(host.c_str()), startreg, cb, unit, data, waitResponse);
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::send(const char* host, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
|
||||
return send(resolve(host), startreg, cb, unit, data, waitResponse);
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::send(IPAddress ip, TAddress startreg, cbTransaction cb, uint8_t unit, uint8_t* data, bool waitResponse) {
|
||||
MBAP_t _MBAP;
|
||||
uint16_t result = 0;
|
||||
int8_t p;
|
||||
#if defined(MODBUSIP_MAX_TRANSACTIONS)
|
||||
if (_trans.size() >= MODBUSIP_MAX_TRANSACTIONS)
|
||||
goto cleanup;
|
||||
#endif
|
||||
if (!ip)
|
||||
return 0;
|
||||
if (tcpserver) {
|
||||
p = getMaster(ip);
|
||||
} else {
|
||||
p = getSlave(ip);
|
||||
}
|
||||
if (p == -1 || !tcpclient[p]->connected()) {
|
||||
if (!autoConnectMode)
|
||||
goto cleanup;
|
||||
if (!connect(ip))
|
||||
goto cleanup;
|
||||
}
|
||||
_MBAP.transactionId = __swap_16(transactionId);
|
||||
_MBAP.protocolId = __swap_16(0);
|
||||
_MBAP.length = __swap_16(_len+1); //_len+1 for last byte from MBAP
|
||||
_MBAP.unitId = unit;
|
||||
bool writeResult;
|
||||
{ // for sbuf isolation
|
||||
size_t send_len = _len + sizeof(_MBAP.raw);
|
||||
uint8_t sbuf[send_len];
|
||||
memcpy(sbuf, _MBAP.raw, sizeof(_MBAP.raw));
|
||||
memcpy(sbuf + sizeof(_MBAP.raw), _frame, _len);
|
||||
writeResult = (tcpclient[p]->write(sbuf, send_len) == send_len);
|
||||
}
|
||||
if (!writeResult)
|
||||
goto cleanup;
|
||||
//tcpclient[p]->flush();
|
||||
if (waitResponse) {
|
||||
TTransaction tmp;
|
||||
tmp.transactionId = transactionId;
|
||||
tmp.timestamp = millis();
|
||||
tmp.cb = cb;
|
||||
tmp.data = data; // BUG: Should data be saved? It may lead to memory leak or double free.
|
||||
tmp._frame = _frame;
|
||||
tmp.startreg = startreg;
|
||||
_trans.push_back(tmp);
|
||||
_frame = nullptr;
|
||||
}
|
||||
result = transactionId;
|
||||
transactionId++;
|
||||
if (!transactionId)
|
||||
transactionId = 1;
|
||||
cleanup:
|
||||
free(_frame);
|
||||
_frame = nullptr;
|
||||
_len = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::onConnect(cbModbusConnect cb) {
|
||||
cbConnect = cb;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::onDisconnect(cbModbusConnect cb) {
|
||||
cbDisconnect = cb;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::cleanupConnections() {
|
||||
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) {
|
||||
if (tcpclient[i] && !tcpclient[i]->connected()) {
|
||||
//IPAddress ip = tcpclient[i]->remoteIP();
|
||||
tcpclient[i]->stop();
|
||||
delete tcpclient[i];
|
||||
tcpclient[i] = nullptr;
|
||||
if (cbDisconnect && cbEnabled)
|
||||
cbDisconnect(IPADDR_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::cleanupTransactions() {
|
||||
#if defined(MODBUS_USE_STL)
|
||||
for (auto it = _trans.begin(); it != _trans.end();) {
|
||||
if (millis() - it->timestamp > MODBUSIP_TIMEOUT || it->forcedEvent != Modbus::EX_SUCCESS) {
|
||||
Modbus::ResultCode res = (it->forcedEvent != Modbus::EX_SUCCESS)?it->forcedEvent:Modbus::EX_TIMEOUT;
|
||||
if (it->cb)
|
||||
it->cb(res, it->transactionId, nullptr);
|
||||
free(it->_frame);
|
||||
it = _trans.erase(it);
|
||||
} else
|
||||
it++;
|
||||
}
|
||||
#else
|
||||
size_t i = 0;
|
||||
while (i < _trans.size()) {
|
||||
TTransaction t = _trans[i];
|
||||
if (millis() - t.timestamp > MODBUSIP_TIMEOUT || t.forcedEvent != Modbus::EX_SUCCESS) {
|
||||
Modbus::ResultCode res = (t.forcedEvent != Modbus::EX_SUCCESS)?t.forcedEvent:Modbus::EX_TIMEOUT;
|
||||
if (t.cb)
|
||||
t.cb(res, t.transactionId, nullptr);
|
||||
free(t._frame);
|
||||
_trans.remove(i);
|
||||
} else
|
||||
i++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
int8_t ModbusTCPTemplate<SERVER, CLIENT>::getFreeClient() {
|
||||
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
|
||||
if (!tcpclient[i])
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
int8_t ModbusTCPTemplate<SERVER, CLIENT>::getSlave(IPAddress ip) {
|
||||
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
|
||||
if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && !BIT_CHECK(tcpServerConnection, i))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
int8_t ModbusTCPTemplate<SERVER, CLIENT>::getMaster(IPAddress ip) {
|
||||
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++)
|
||||
if (tcpclient[i] && tcpclient[i]->connected() && tcpclient[i]->remoteIP() == ip && BIT_CHECK(tcpServerConnection, i))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::isTransaction(uint16_t id) {
|
||||
return searchTransaction(id) != nullptr;
|
||||
}
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::isConnected(String host) {
|
||||
return isConnected(resolve(host.c_str()));
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::isConnected(const char* host) {
|
||||
return isConnected(resolve(host));
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::isConnected(IPAddress ip) {
|
||||
if (!ip)
|
||||
return false;
|
||||
int8_t p = getSlave(ip);
|
||||
return p != -1 && tcpclient[p]->connected();
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::autoConnect(bool enabled) {
|
||||
autoConnectMode = enabled;
|
||||
}
|
||||
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::disconnect(String host) {
|
||||
return disconnect(resolve(host.c_str()));
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::disconnect(const char* host) {
|
||||
return disconnect(resolve(host));
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
bool ModbusTCPTemplate<SERVER, CLIENT>::disconnect(IPAddress ip) {
|
||||
if (!ip)
|
||||
return false;
|
||||
int8_t p = getSlave(ip);
|
||||
if (p != -1) {
|
||||
tcpclient[p]->stop();
|
||||
delete tcpclient[p];
|
||||
tcpclient[p] = nullptr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
void ModbusTCPTemplate<SERVER, CLIENT>::dropTransactions() {
|
||||
#if defined(MODBUS_USE_STL)
|
||||
for (auto &t : _trans) t.forcedEvent = EX_CANCEL;
|
||||
#else
|
||||
for (size_t i = 0; i < _trans.size(); i++)
|
||||
_trans.entry(i)->forcedEvent = EX_CANCEL;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
ModbusTCPTemplate<SERVER, CLIENT>::~ModbusTCPTemplate() {
|
||||
free(_frame);
|
||||
_frame = nullptr;
|
||||
dropTransactions();
|
||||
cleanupConnections();
|
||||
cleanupTransactions();
|
||||
delete tcpserver;
|
||||
tcpserver = nullptr;
|
||||
for (uint8_t i = 0; i < MODBUSIP_MAX_CLIENTS; i++) {
|
||||
delete tcpclient[i];
|
||||
tcpclient[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <class SERVER, class CLIENT>
|
||||
uint16_t ModbusTCPTemplate<SERVER, CLIENT>::setTransactionId(uint16_t t) {
|
||||
transactionId = t;
|
||||
if (!transactionId)
|
||||
transactionId = 1;
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
120
lib/modbus-esp8266/ModbusTLS.h
Normal file
120
lib/modbus-esp8266/ModbusTLS.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
Modbus Library for Arduino
|
||||
ModbusTLS - ModbusTCP Security for ESP8266
|
||||
Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
*/
|
||||
#pragma once
|
||||
#if !defined(ESP8266) && !defined(ESP32)
|
||||
#error Unsupported architecture
|
||||
#endif
|
||||
#include <WiFiClientSecure.h>
|
||||
#if defined(ESP8266)
|
||||
#include <WiFiServerSecure.h>
|
||||
#else
|
||||
// Just emty stub
|
||||
class WiFiServerSecure {
|
||||
public:
|
||||
WiFiServerSecure(uint16_t){}
|
||||
WiFiClientSecure available(){}
|
||||
void begin();
|
||||
inline WiFiClientSecure accept() {
|
||||
return available();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
#include "ModbusTCPTemplate.h"
|
||||
#include "ModbusAPI.h"
|
||||
|
||||
class ModbusTLS : public ModbusAPI<ModbusTCPTemplate<WiFiServerSecure, WiFiClientSecure>> {
|
||||
private:
|
||||
int8_t _connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr) {
|
||||
int8_t p = getFreeClient();
|
||||
if (p < 0)
|
||||
return p;
|
||||
tcpclient[p] = new WiFiClientSecure();
|
||||
BIT_CLEAR(tcpServerConnection, p);
|
||||
#if defined(ESP8266)
|
||||
BearSSL::X509List *clientCertList = new BearSSL::X509List(client_cert);
|
||||
BearSSL::PrivateKey *clientPrivKey = new BearSSL::PrivateKey(client_private_key);
|
||||
tcpclient[p]->setClientRSACert(clientCertList, clientPrivKey);
|
||||
tcpclient[p]->setBufferSizes(512, 512);
|
||||
#else
|
||||
tcpclient[p]->setCertificate(client_cert);
|
||||
tcpclient[p]->setPrivateKey(client_private_key);
|
||||
#endif
|
||||
return p;
|
||||
}
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
static IPAddress resolver (const char* host) {
|
||||
IPAddress remote_addr;
|
||||
if (WiFi.hostByName(host, remote_addr))
|
||||
return remote_addr;
|
||||
return IPADDR_NONE;
|
||||
}
|
||||
#endif
|
||||
public:
|
||||
ModbusTLS() : ModbusAPI() {
|
||||
defaultPort = MODBUSTLS_PORT;
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
resolve = resolver;
|
||||
#endif
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
void server(uint16_t port, const char* server_cert = nullptr, const char* server_private_key = nullptr, const char* ca_cert = nullptr) {
|
||||
serverPort = port;
|
||||
tcpserver = new WiFiServerSecure(serverPort);
|
||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(server_cert);
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(server_private_key);
|
||||
tcpserver->setRSACert(serverCertList, serverPrivKey);
|
||||
if (ca_cert) {
|
||||
BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert);
|
||||
tcpserver->setClientTrustAnchor(trustedCA);
|
||||
}
|
||||
//tcpserver->setBufferSizes(512, 512);
|
||||
tcpserver->begin();
|
||||
}
|
||||
|
||||
bool connectWithKnownKey(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* key = nullptr) {
|
||||
if(getSlave(ip) >= 0)
|
||||
return true;
|
||||
int8_t p = _connect(ip, port, client_cert, client_private_key);
|
||||
BearSSL::PublicKey *clientPublicKey = new BearSSL::PublicKey(key);
|
||||
tcpclient[p]->setKnownKey(clientPublicKey);
|
||||
return tcpclient[p]->connect(ip, port);
|
||||
}
|
||||
|
||||
#endif
|
||||
#if defined(MODBUSIP_USE_DNS)
|
||||
bool connect(String host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) {
|
||||
return connect(resolver(host.c_str()), port, client_cert, client_private_key, ca_cert);
|
||||
}
|
||||
bool connect(const char* host, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) {
|
||||
return connect(resolver(host), port, client_cert, client_private_key, ca_cert);
|
||||
}
|
||||
#endif
|
||||
bool connect(IPAddress ip, uint16_t port, const char* client_cert = nullptr, const char* client_private_key = nullptr, const char* ca_cert = nullptr) {
|
||||
if (!ip)
|
||||
return false;
|
||||
if(getSlave(ip) >= 0)
|
||||
return false;
|
||||
int8_t p = _connect(ip, port, client_cert, client_private_key);
|
||||
if (p < 0)
|
||||
return false;
|
||||
#if defined(ESP8266)
|
||||
if (ca_cert) {
|
||||
BearSSL::X509List *trustedCA = new BearSSL::X509List(ca_cert);
|
||||
tcpclient[p]->setTrustAnchors(trustedCA);
|
||||
} else {
|
||||
tcpclient[p]->setInsecure();
|
||||
}
|
||||
#else
|
||||
if (ca_cert) {
|
||||
tcpclient[p]->setCACert(ca_cert);
|
||||
}
|
||||
#endif
|
||||
//return tcpclient[p]->connect(ip, port);
|
||||
if (!tcpclient[p]->connect(ip, port))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
70
lib/modbus-esp8266/darray.h
Normal file
70
lib/modbus-esp8266/darray.h
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
/*
|
||||
Very Basic Dynamic Array
|
||||
|
||||
Copyright (C) 2020 Alexander Emelianov (a.m.emelianov@gmail.com)
|
||||
https://github.com/emelianov/modbus-esp8266
|
||||
This code is licensed under the BSD New License. See LICENSE.txt for more info.
|
||||
*/
|
||||
template <typename T, int SIZE, int INCREMENT>
|
||||
class DArray {
|
||||
public:
|
||||
typedef bool (*Compare)(T);
|
||||
T* data = nullptr;
|
||||
size_t resSize = 0;
|
||||
size_t last = 0;
|
||||
bool isEmpty = true;
|
||||
DArray(size_t i = SIZE) {
|
||||
data = (T*)malloc(i * sizeof(T));
|
||||
if (data) resSize = i;
|
||||
}
|
||||
size_t push_back(const T& v) {
|
||||
if (!data) {
|
||||
data = (T*)malloc(resSize * sizeof(T));
|
||||
if (!data) return 1;
|
||||
}
|
||||
if (last >= resSize - 1) {
|
||||
if (INCREMENT == 0) return last + 1;
|
||||
void* tmp = realloc(data, (resSize + INCREMENT) * sizeof(T));
|
||||
if (!tmp) return last + 1;
|
||||
resSize += INCREMENT;
|
||||
data = (T*)tmp;
|
||||
}
|
||||
if (!isEmpty)
|
||||
last++;
|
||||
else
|
||||
isEmpty = false;
|
||||
data[last] = v;
|
||||
return last;
|
||||
}
|
||||
size_t size() {
|
||||
if (isEmpty) return 0;
|
||||
return last + 1;
|
||||
}
|
||||
template <class UnaryPredicate>
|
||||
size_t find(UnaryPredicate func, size_t i = 0) {
|
||||
if (isEmpty) return 1;
|
||||
for (; i <= last; i++)
|
||||
if (func(data[i])) break;
|
||||
return i;
|
||||
}
|
||||
|
||||
void remove(size_t i) {
|
||||
if (isEmpty) return;
|
||||
if (i > last) return;
|
||||
if (last == 0) {
|
||||
isEmpty = true;
|
||||
return;
|
||||
}
|
||||
if (i < last)
|
||||
memcpy(&data[i], &data[i + 1], (last - i) * sizeof(T));
|
||||
last --;
|
||||
}
|
||||
T operator[](int i) {
|
||||
return data[i];
|
||||
}
|
||||
T* entry(size_t i) {
|
||||
if (i > last) return nullptr;
|
||||
return &data[i];
|
||||
}
|
||||
};
|
||||
32
platformio.ini
Normal file
32
platformio.ini
Normal file
@@ -0,0 +1,32 @@
|
||||
; PlatformIO Project Configuration File
|
||||
;
|
||||
; Build options: build flags, source filter
|
||||
; Upload options: custom upload port, speed and extra flags
|
||||
; Library options: dependencies, extra library storages
|
||||
; Advanced options: extra scripting
|
||||
;
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = CRAH_PAHHC_600_C6_TCP
|
||||
|
||||
[common_env_options]
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
lib_ldf_mode = chain+
|
||||
lib_compat_mode = soft
|
||||
|
||||
[env:CH_Daikin_AWV026B_RTU]
|
||||
platform = espressif32
|
||||
board = dfrobot_firebeetle2_esp32e
|
||||
extends = common_env_options
|
||||
build_src_filter = -<*> +<CH_Daikin_AWV026B_RTU>
|
||||
|
||||
[env:CRAH_PAHHC_600_C6_TCP]
|
||||
platform = espressif32
|
||||
board = dfrobot_firebeetle2_esp32e
|
||||
extends = common_env_options
|
||||
build_flags =
|
||||
-D USE_MODBUS_IP
|
||||
build_src_filter = -<*> +<CRAH_PAHHC_600_C6_TCP>
|
||||
90
src/CH_Daikin_AWV026B_RTU/State_Fail.cpp
Normal file
90
src/CH_Daikin_AWV026B_RTU/State_Fail.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @file State_Fail.cpp
|
||||
* @brief Implementation of the FailState class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the implementation for the FailState, which defines
|
||||
* the behavior of the equipment when it has entered a fault condition.
|
||||
*/
|
||||
#include "States/State_Fail.h"
|
||||
#include "States/State_Standby.h"
|
||||
|
||||
#include "Categories/ModbusPoint.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
#include "Strategies/Strategy_SingleValue.h"
|
||||
#include "Strategies/Strategy_PID.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Constructs a new FailState object.
|
||||
*
|
||||
* This constructor initializes behavior strategies to simulate a failure
|
||||
* scenario. In this example, it sets a common alarm bit, triggers a specific
|
||||
* alarm for "EC Fan #1", and ramps down all fan speeds to zero.
|
||||
*/
|
||||
template<>
|
||||
FailState<ModbusRTU>::FailState(const std::vector<std::string>& activeAlarms) {
|
||||
// Simulate a failure: set common alarm and a specific fan alarm.
|
||||
|
||||
for (const auto& alarmName : activeAlarms){
|
||||
addStrategy(alarmName, new SingleValueStrategy(1.0f, 0.0f, 1000));
|
||||
}
|
||||
addStrategy("CW Valve Position", new PIDStrategy("RAT Setpoint", 1000, "RAT"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the fail state's logic for one update cycle.
|
||||
*
|
||||
* This method checks the "State Control" Modbus point for a command to
|
||||
* transition back to Standby, which would typically happen after a fault
|
||||
* is cleared. If no transition is requested, it applies the failure
|
||||
* strategies (e.g., keeping fans off and alarms active).
|
||||
*
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
template<>
|
||||
State<ModbusRTU>* FailState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
|
||||
// STATE control, add conditions if change to a different state is needed
|
||||
Serial.println("Fail update function");
|
||||
ModbusPoint<ModbusRTU>* alarmReset = equipment->getModbusPoint("Alarm Reset");
|
||||
int nextStateId = alarmReset ? alarmReset->getValue() : 0;
|
||||
if (nextStateId == 1){
|
||||
return new StandbyState<ModbusRTU>();
|
||||
}
|
||||
_applyStrategies(equipment);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when entering the fail state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void FailState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
|
||||
// Logic to run when the equipment enters this state
|
||||
Serial.println("Enter Fail State...");
|
||||
ModbusPoint<ModbusRTU>* alarm_common = equipment->getModbusPoint("Alarm Common");
|
||||
alarm_common->setValue(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when exiting the fail state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void FailState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
|
||||
// Cleanup logic to run when the equipment leaves this state
|
||||
Serial.println("Exit Fail State...");
|
||||
ModbusPoint<ModbusRTU>* alarm_common = equipment->getModbusPoint("Alarm Common");
|
||||
alarm_common->setValue(0);
|
||||
}
|
||||
167
src/CH_Daikin_AWV026B_RTU/State_Running.cpp
Normal file
167
src/CH_Daikin_AWV026B_RTU/State_Running.cpp
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @file State_Running.cpp
|
||||
* @brief Implementation of the RunningState class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the implementation for the RunningState, which defines
|
||||
* the behavior of the equipment when it is actively running.
|
||||
*/
|
||||
#include "States/State_Running.h"
|
||||
#include "States/State_Standby.h"
|
||||
#include "States/State_Fail.h"
|
||||
#include "Strategies/Strategy_Behavior.h"
|
||||
#include "Strategies/Strategy_PID.h"
|
||||
#include "Strategies/Strategy_Totalizer.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
#include "Categories/ModbusPoint.h"
|
||||
#include "Categories/ModbusFloatDecorator.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Constructs a new RunningState object.
|
||||
*
|
||||
* This constructor initializes the behavior strategies for various Modbus points
|
||||
* that are active during the running state. For example, it sets different
|
||||
* dynamic behaviors for the speeds of EC fans 1 through 5.
|
||||
*/
|
||||
template<>
|
||||
RunningState<ModbusRTU>::RunningState() {
|
||||
addStrategy("CW Valve Position", new PIDStrategy("RAT Setpoint", 1000, "RAT"));
|
||||
addStrategy("Operating Hours EC Fan #1", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #2", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #3", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #4", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #5", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #6", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #7", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #8", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #9", new TotalizerStrategy(10000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the running state's logic for one update cycle.
|
||||
*
|
||||
* This method checks the "State Control" Modbus point for a command to
|
||||
* transition to a different state (e.g., back to Standby). If no transition
|
||||
* is requested, it applies the strategies defined for the running state.
|
||||
*
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
template<>
|
||||
State<ModbusRTU>* RunningState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
|
||||
// STATE control, add conditions if change to a different state is needed
|
||||
Serial.println("Running update function");
|
||||
ModbusPoint<ModbusRTU>* On_Off_Command = equipment->getModbusPoint("ON/OFF Command By BMS");
|
||||
int nextStateId = On_Off_Command ? On_Off_Command->getValue() : 0;
|
||||
Serial.println(nextStateId);
|
||||
if (nextStateId == 0){
|
||||
return new StandbyState<ModbusRTU>();
|
||||
}
|
||||
|
||||
ModbusPoint<ModbusRTU>* faultCode = equipment->getModbusPoint("Fault Code");
|
||||
int faultCodeValue = faultCode ? faultCode->getValue() : 0;
|
||||
switch (faultCodeValue){
|
||||
case 1:
|
||||
return new FailState<ModbusRTU>({"Alarm SAT Sensor Fault"});
|
||||
case 2:
|
||||
return new FailState<ModbusRTU>({"Alarm RAH Sensor Fault"});
|
||||
case 3:
|
||||
return new FailState<ModbusRTU>({"Alarm RAT Sensor Fault"});
|
||||
case 4:
|
||||
return new FailState<ModbusRTU>({"Alarm Filter DP Sensor Fault"});
|
||||
case 5:
|
||||
return new FailState<ModbusRTU>({"Alarm Flooding"});
|
||||
case 6:
|
||||
return new FailState<ModbusRTU>({"Alarm Dirty Filter"});
|
||||
case 7:
|
||||
return new FailState<ModbusRTU>({"Alarm High RAT"});
|
||||
case 8:
|
||||
return new FailState<ModbusRTU>({"Alarm Low RAT"});
|
||||
case 9:
|
||||
return new FailState<ModbusRTU>({"Alarm High SAT"});
|
||||
case 10:
|
||||
return new FailState<ModbusRTU>({"Alarm Low SAT"});
|
||||
case 11:
|
||||
return new FailState<ModbusRTU>({"Alarm High RAH"});
|
||||
case 12:
|
||||
return new FailState<ModbusRTU>({"Alarm Low RAH"});
|
||||
case 13:
|
||||
return new FailState<ModbusRTU>({"Alarm Phase Failure"});
|
||||
case 14:
|
||||
return new FailState<ModbusRTU>({"Alarm Condensate Pump"});
|
||||
case 15:
|
||||
return new FailState<ModbusRTU>({"Alarm Smoke"});
|
||||
case 16:
|
||||
return new FailState<ModbusRTU>({"Alarm Fire"});
|
||||
case 17:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #1"});
|
||||
case 18:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #2"});
|
||||
case 19:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #3"});
|
||||
case 20:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #4"});
|
||||
case 21:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #5"});
|
||||
case 22:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #6"});
|
||||
case 23:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #7"});
|
||||
case 24:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #8"});
|
||||
case 25:
|
||||
return new FailState<ModbusRTU>({"Alarm EC Fan #9"});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ModbusPoint<ModbusRTU>* point = equipment->getModbusPoint("Setting the EC Fan Max Speed");
|
||||
point->setValue(45);
|
||||
// Apply any strategies defined for the standby state
|
||||
_applyStrategies(equipment);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when entering the running state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void RunningState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
|
||||
// Logic to run when the equipment enters this state
|
||||
Serial.println("Enter Running State...");
|
||||
// You could also update a Modbus register to show the "standby" state
|
||||
|
||||
const std::vector<std::string> motorStatusDescriptions = {
|
||||
"Run Status EC Fan #1", "Run Status EC Fan #2", "Run Status EC Fan #3",
|
||||
"Run Status EC Fan #4", "Run Status EC Fan #5", "Run Status EC Fan #6",
|
||||
"Run Status EC Fan #7", "Run Status EC Fan #8", "Run Status EC Fan #9"
|
||||
};
|
||||
|
||||
// Loop through and set all motor statuses to 0
|
||||
for (const auto& desc : motorStatusDescriptions) {
|
||||
ModbusPoint<ModbusRTU>* point = equipment->getModbusPoint(desc);
|
||||
if (point) {
|
||||
point->setValue(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when exiting the running state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void RunningState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
|
||||
// Cleanup logic to run when the equipment leaves this state
|
||||
Serial.println("Exit Running State...");
|
||||
}
|
||||
106
src/CH_Daikin_AWV026B_RTU/State_Standby.cpp
Normal file
106
src/CH_Daikin_AWV026B_RTU/State_Standby.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @file State_Standby.cpp
|
||||
* @brief Implementation of the StandbyState class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the implementation for the StandbyState, which defines
|
||||
* the behavior of the equipment when it is in an idle or standby mode.
|
||||
*/
|
||||
#include "States/State_Standby.h"
|
||||
#include "Categories/ModbusPoint.h"
|
||||
#include "Categories/ModbusFloatDecorator.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
#include "Strategies/Strategy_Random.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Constructs a new StandbyState object.
|
||||
*
|
||||
* In this state, the equipment is idle. This constructor can be used to
|
||||
* define specific behaviors for Modbus points that should occur during standby,
|
||||
* such as setting fan speeds to zero.
|
||||
*/
|
||||
template<>
|
||||
StandbyState<ModbusRTU>::StandbyState() {
|
||||
// You can add initialization code here if needed
|
||||
/*
|
||||
addStrategy("SAT Reading", new SingleValueStrategy(100.0f, 0.1f, 1000));
|
||||
addStrategy("CW Valve Position", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #1", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #2", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #3", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #4", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #5", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #6", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #7", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #8", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #9", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
*/
|
||||
addStrategy("Chiller Local-Network", new RandomStrategy(1000));
|
||||
addStrategy("Chiller Enable Output", new RandomStrategy(1000));
|
||||
addStrategy("Run Enabled", new RandomStrategy(1000));
|
||||
addStrategy("Chiller Capacity Limited", new RandomStrategy(1000));
|
||||
addStrategy("Alm Digital Output", new RandomStrategy(1000));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the running state's logic for one update cycle.
|
||||
*
|
||||
* This method checks the "State Control" Modbus point for a command to
|
||||
* transition to a different state (e.g., back to Standby). If no transition
|
||||
* is requested, it applies the strategies defined for the running state.
|
||||
*
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
template<>
|
||||
State<ModbusRTU>* StandbyState<ModbusRTU>::update(Equipment<ModbusRTU>* equipment) {
|
||||
// STATE control, add conditions if change to a different state is needed
|
||||
Serial.println("Standby update function");
|
||||
/*
|
||||
ModbusPoint* On_Off_Command = equipment->getModbusPoint("ON/OFF Command By BMS");
|
||||
int nextStateId = On_Off_Command ? On_Off_Command->getValue() : 0;
|
||||
Serial.println(nextStateId);
|
||||
if (nextStateId == 1){
|
||||
return new RunningState();
|
||||
}
|
||||
*/
|
||||
// Apply any strategies defined for the standby state
|
||||
_applyStrategies(equipment);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when entering the standby state.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
*/
|
||||
template<>
|
||||
void StandbyState<ModbusRTU>::enterState(Equipment<ModbusRTU>* equipment) {
|
||||
// Logic to run when the equipment enters this state
|
||||
// A list of all alarm descriptions
|
||||
Serial.println("Enter Standby State...");
|
||||
|
||||
|
||||
int CH_ON_OFF = getPointValue(equipment, "Chiller On-Off");
|
||||
int Ch_Sts = getPointValue(equipment, "Chiller Sts");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when exiting the standby state.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
*/
|
||||
template<>
|
||||
void StandbyState<ModbusRTU>::exitState(Equipment<ModbusRTU>* equipment) {
|
||||
// Cleanup logic to run when the equipment leaves this state
|
||||
Serial.println("Exit Standby State...");
|
||||
}
|
||||
99
src/CH_Daikin_AWV026B_RTU/config.h
Normal file
99
src/CH_Daikin_AWV026B_RTU/config.h
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @file config.h
|
||||
* @brief Main configuration file for the Equipment emulator.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-02
|
||||
*
|
||||
* This file contains two important configurations: WiFi network parameters
|
||||
* and the Modbus register map for the device.
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
#include <ModbusRTU.h>
|
||||
#include "core.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
|
||||
/**
|
||||
* @brief The Modbus map for the Equipment device.
|
||||
* This array defines all the Modbus points available on the emulated device.
|
||||
* The `description` field is crucial as it's used to look up points within the application logic.
|
||||
*/
|
||||
modbusMap mb_map[] =
|
||||
{
|
||||
{HR, 100, 0, "State Control"}, //Internal to control from Modscan
|
||||
{HR, 101, 0, "Fault Code"},
|
||||
{HR_FLOAT, 102, 0, "Supply Temp"},
|
||||
{HR, 0, 0, "Chiller Local-Network"},
|
||||
{HR, 1, 0, "Chiller Enable Output"},
|
||||
{HR, 2, 0, "Run Enabled"},
|
||||
{HR, 3, 0, "Chiller Capacity Limited"},
|
||||
{HR, 4, 0, "Alm Digital Output"},
|
||||
{HR, 6, 0, "Evap Flow Switch Sts"},
|
||||
{HR, 7, 0, "Cond Flow Switch Sts"},
|
||||
{HR, 8, 0, "Chiller On-Off"},
|
||||
{HR, 9, 0, "Chiller Enable SP"},
|
||||
{HR, 10, 0, "Clear Alm"},
|
||||
{HR, 11, 0, "Chiller Mode Output"},
|
||||
{HR_10x, 12, 0, "Active SP"},
|
||||
{HR_10x, 13, 0, "Actual Capacity"},
|
||||
{HR_10x, 14, 0, "Active Capacity Limit"},
|
||||
{HR, 15, 0, "Chiller Sts"},
|
||||
{HR_10x, 16, 0, "Evap Entering Fluid Temp"},
|
||||
{HR_10x, 17, 0, "Evap Leaving Fluid Temp"},
|
||||
{HR, 18, 0, "Evap Fluid Flow Rate"},
|
||||
{HR_10x, 19, 0, "Cond Entering Fluid Temp"},
|
||||
{HR_10x, 20, 0, "Cond Leaving Fluid Temp"},
|
||||
{HR, 21, 0, "Cond Fluid Flow Rate"},
|
||||
{HR_10x, 24, 0, "Outdoor Air Temp"},
|
||||
{HR, 25, 0, "Chiller Current"},
|
||||
{HR, 27, 0, "Total Kw"},
|
||||
{HR, 28, 0, "Warning Alm Idx"},
|
||||
{HR, 29, 0, "Problem Alm Idx"},
|
||||
{HR, 30, 0, "Fault Alm Idx"},
|
||||
{HR, 31, 0, "Warning Alm Code"},
|
||||
{HR, 32, 0, "Problem Alm Code"},
|
||||
{HR, 33, 0, "Fault Alm Code"},
|
||||
{HR, 34, 0, "Chiller Mode SP"},
|
||||
{HR_10x, 35, 0, "Cool SP - Network"},
|
||||
{HR_10x, 36, 0, "Ice SP"},
|
||||
{HR_10x, 38, 0, "Capacity Limit SP"},
|
||||
{HR_10x, 39, 0, "Cond Refrig Pressure"},
|
||||
{HR_10x, 40, 0, "Cond Saturated Refrig Temp"},
|
||||
{HR_10x, 41, 0, "Evap Refrig Pressure"},
|
||||
{HR_10x, 42, 0, "Evap Saturated Refrig Temp"},
|
||||
{HR, 65, 0, "Comp Suction Refrig Temp"},
|
||||
{HR_10x, 68, 0, "Comp Discharge Refrig Temp"},
|
||||
{HR, 69, 0, "Comp1 Percent RLA"},
|
||||
{HR, 70, 0, "Comp1 Current"},
|
||||
{HR, 71, 0, "Comp Voltage"},
|
||||
{HR, 72, 0, "Comp Power"},
|
||||
{HR, 73, 0, "Comp Starts"},
|
||||
{HR, 74, 0, "Comp Run Hours"},
|
||||
{HR, 75, 0, "Comp Run Hours"},
|
||||
{HR, 82, 0, "Comp2 Percent RLA"},
|
||||
{HR, 303, 0, "Evap Pump Run Hours"},
|
||||
{HR, 304, 0, "Evap Pump Run Hours"},
|
||||
{HR, 305, 0, "Evap Pump Sts"},
|
||||
{HR, 316, 0, "Units"},
|
||||
{HR, 317, 0, "Chiller Model"},
|
||||
{HR, 1849, 0, "Oil Feed Pessure"},
|
||||
{HR, 1854, 0, "Wtrside Econo State"},
|
||||
{HR, 1855, 0, "Wtrside Econo En SP"},
|
||||
|
||||
};
|
||||
//Size of modbus map used in FOR cycles, automatically calculated.
|
||||
/**
|
||||
* @brief The total number of entries in the `mb_map` array.
|
||||
* This is calculated at compile time and used for iterating over the map.
|
||||
*/
|
||||
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
|
||||
|
||||
/**
|
||||
* @brief The main loop update interval in milliseconds.
|
||||
*/
|
||||
int interval = 250;
|
||||
|
||||
|
||||
ModbusRTU mb;
|
||||
#endif // CONFIG_H
|
||||
79
src/CH_Daikin_AWV026B_RTU/main.cpp
Normal file
79
src/CH_Daikin_AWV026B_RTU/main.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @file BaseEmulator.ino
|
||||
* @brief Main execution program for the Arduino Emulator.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-02
|
||||
*
|
||||
* @details This file contains the main execution program for an Arduino-based emulator of a equipment unit.
|
||||
* The program uses a Wi-Fi connection to communicate via the Modbus IP protocol.
|
||||
*
|
||||
* The setup() function initializes the following:
|
||||
* - Serial communication for debugging.
|
||||
* - Wi-Fi connection using credentials from config.h.
|
||||
* - A Modbus IP server.
|
||||
* - Modbus points (Coils, Holding Registers, etc.) based on a predefined map in config.h.
|
||||
*
|
||||
* The loop() function continuously:
|
||||
* - Services the Modbus IP server.
|
||||
* - Reads values from the Modbus server into internal data structures.
|
||||
* - Updates the state of the emulated equipment.
|
||||
* - Writes updated values back to the Modbus server.
|
||||
*
|
||||
* @see config.h for Wi-Fi and Modbus configuration.
|
||||
* @see Equipment.h for the main equipment logic.
|
||||
* @see State.h for different equipment states.
|
||||
* @see Strategies/Strategy_Behavior.h for different value generation strategies.
|
||||
* @see ModbusPoint.h for the base class for all Modbus points.
|
||||
*/
|
||||
//=================================================================================================================================
|
||||
//Libraries and declaration of variables.
|
||||
#include <Arduino.h>
|
||||
#include "config.h"
|
||||
#include "Categories/ModbusPointFactory.h"
|
||||
|
||||
//=================================================================================================================================
|
||||
/**
|
||||
* @brief Initializes the application.
|
||||
* @details This function runs once at startup. It configures the serial communication,
|
||||
* Wi-Fi, and the Modbus server. It also creates and initializes all the Modbus points
|
||||
* based on the `mb_map` array in `config.h`.
|
||||
*/
|
||||
const int rtsPin = 4;
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.println("Setup function started");
|
||||
|
||||
Serial2.begin(19200, SERIAL_8N1, 17, 16);
|
||||
mb.begin(&Serial2, 4); // Start the server
|
||||
mb.slave(1); // Set the slave ID
|
||||
|
||||
for(int i = 0; i < map_size; i++){
|
||||
ModbusPoint<ModbusRTU>* point = createModbusPoint(&mb, mb_map[i].category, mb_map[i].address, mb_map[i].value, mb_map[i].description);
|
||||
if (point) {
|
||||
point->addToModbusServer();
|
||||
EquipmentInstance.addModbusPoint(mb_map[i].description, point);
|
||||
}
|
||||
}
|
||||
Serial.println("Setup function ended");
|
||||
}
|
||||
//=================================================================================================================================
|
||||
/**
|
||||
* @brief The main application loop.
|
||||
* @details This function runs repeatedly after setup() has completed. It performs the following actions in order:
|
||||
* 1. Services the Modbus server by calling `mb.task()`.
|
||||
* 2. Reads the current values from the Modbus registers into the `ModbusPoint` objects by calling `readRegisters()`.
|
||||
* 3. After a specified interval, it updates the equipment's state by calling `EquipmentInstance.update()`.
|
||||
* 4. Writes any changed values from the `ModbusPoint` objects back to the Modbus registers by calling `writeRegisters()`.
|
||||
*/
|
||||
void loop() {
|
||||
mb.task();
|
||||
unsigned long currentMillis = millis();
|
||||
if (currentMillis - previousMillis >= interval) {
|
||||
previousMillis = currentMillis;
|
||||
unsigned long startTime = millis();
|
||||
EquipmentInstance.update();
|
||||
unsigned long endTime = millis();
|
||||
unsigned long elapsedTime = endTime - startTime;
|
||||
Serial.printf("Control Execution time: %d ms\n", elapsedTime);
|
||||
}
|
||||
}
|
||||
87
src/CRAH_PAHHC_600_C6_TCP/State_Fail.cpp
Normal file
87
src/CRAH_PAHHC_600_C6_TCP/State_Fail.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file State_Fail.cpp
|
||||
* @brief Implementation of the FailState class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the implementation for the FailState, which defines
|
||||
* the behavior of the equipment when it has entered a fault condition.
|
||||
*/
|
||||
#include "Categories/ModbusPoint.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
#include "Strategies/Strategy_Ramp.h"
|
||||
#include "Strategies/Strategy_SingleValue.h"
|
||||
#include "Strategies/Strategy_PID.h"
|
||||
#include "States/State_Standby.h"
|
||||
#include "States/State_Running.h"
|
||||
#include "States/State_Fail.h"
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Constructs a new FailState object.
|
||||
*
|
||||
* This constructor initializes behavior strategies to simulate a failure
|
||||
* scenario. In this example, it sets a common alarm bit, triggers a specific
|
||||
* alarm for "EC Fan #1", and ramps down all fan speeds to zero.
|
||||
*/
|
||||
template<>
|
||||
FailState<ModbusIP>::FailState(const std::vector<std::string>& activeAlarms) {
|
||||
// Simulate a failure: set common alarm and a specific fan alarm.
|
||||
|
||||
for (const auto& alarmName : activeAlarms){
|
||||
addStrategy(alarmName, new SingleValueStrategy(1.0f, 0.0f, 1000));
|
||||
}
|
||||
addStrategy("CW Valve Position", new PIDStrategy("RAT Setpoint", 1000, "RAT"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the fail state's logic for one update cycle.
|
||||
*
|
||||
* This method checks the "State Control" Modbus point for a command to
|
||||
* transition back to Standby, which would typically happen after a fault
|
||||
* is cleared. If no transition is requested, it applies the failure
|
||||
* strategies (e.g., keeping fans off and alarms active).
|
||||
*
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
template<>
|
||||
State<ModbusIP>* FailState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
|
||||
// STATE control, add conditions if change to a different state is needed
|
||||
Serial.println("Fail update function");
|
||||
ModbusPoint<ModbusIP>* alarmReset = equipment->getModbusPoint("Alarm Reset");
|
||||
int nextStateId = alarmReset ? alarmReset->getValue() : 0;
|
||||
if (nextStateId == 1){
|
||||
return new StandbyState<ModbusIP>();
|
||||
}
|
||||
_applyStrategies(equipment);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when entering the fail state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void FailState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
|
||||
// Logic to run when the equipment enters this state
|
||||
Serial.println("Enter Fail State...");
|
||||
ModbusPoint<ModbusIP>* alarm_common = equipment->getModbusPoint("Alarm Common");
|
||||
alarm_common->setValue(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when exiting the fail state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void FailState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
|
||||
// Cleanup logic to run when the equipment leaves this state
|
||||
Serial.println("Exit Fail State...");
|
||||
ModbusPoint<ModbusIP>* alarm_common = equipment->getModbusPoint("Alarm Common");
|
||||
alarm_common->setValue(0);
|
||||
}
|
||||
169
src/CRAH_PAHHC_600_C6_TCP/State_Running.cpp
Normal file
169
src/CRAH_PAHHC_600_C6_TCP/State_Running.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* @file State_Running.cpp
|
||||
* @brief Implementation of the RunningState class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the implementation for the RunningState, which defines
|
||||
* the behavior of the equipment when it is actively running.
|
||||
*/
|
||||
#include "Categories/ModbusPoint.h"
|
||||
#include "Categories/ModbusFloatDecorator.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
#include "Strategies/Strategy_Ramp.h"
|
||||
#include "Strategies/Strategy_Random.h"
|
||||
#include "Strategies/Strategy_Saw.h"
|
||||
#include "Strategies/Strategy_SingleValue.h"
|
||||
#include "Strategies/Strategy_Square.h"
|
||||
#include "Strategies/Strategy_PID.h"
|
||||
#include "Strategies/Strategy_Totalizer.h"
|
||||
#include "States/State_Standby.h"
|
||||
#include "States/State_Running.h"
|
||||
#include "States/State_Fail.h"
|
||||
#include "States/State.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Constructs a new RunningState object.
|
||||
*
|
||||
* This constructor initializes the behavior strategies for various Modbus points
|
||||
* that are active during the running state. For example, it sets different
|
||||
* dynamic behaviors for the speeds of EC fans 1 through 5.
|
||||
*/
|
||||
template<>
|
||||
RunningState<ModbusIP>::RunningState() {
|
||||
addStrategy("CW Valve Position", new PIDStrategy("RAT Setpoint", 1000, "RAT"));
|
||||
addStrategy("Operating Hours EC Fan #1", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #2", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #3", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #4", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #5", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #6", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #7", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #8", new TotalizerStrategy(10000));
|
||||
addStrategy("Operating Hours EC Fan #9", new TotalizerStrategy(10000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the running state's logic for one update cycle.
|
||||
*
|
||||
* This method checks the "State Control" Modbus point for a command to
|
||||
* transition to a different state (e.g., back to Standby). If no transition
|
||||
* is requested, it applies the strategies defined for the running state.
|
||||
*
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
template<>
|
||||
State<ModbusIP>* RunningState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
|
||||
// STATE control, add conditions if change to a different state is needed
|
||||
Serial.println("Running update function");
|
||||
ModbusPoint<ModbusIP>* On_Off_Command = equipment->getModbusPoint("ON/OFF Command By BMS");
|
||||
int nextStateId = On_Off_Command ? On_Off_Command->getValue() : 0;
|
||||
Serial.println(nextStateId);
|
||||
if (nextStateId == 0){
|
||||
return new StandbyState<ModbusIP>();
|
||||
}
|
||||
|
||||
ModbusPoint<ModbusIP>* faultCode = equipment->getModbusPoint("Fault Code");
|
||||
int faultCodeValue = faultCode ? faultCode->getValue() : 0;
|
||||
switch (faultCodeValue){
|
||||
case 1:
|
||||
return new FailState<ModbusIP>({"Alarm SAT Sensor Fault"});
|
||||
case 2:
|
||||
return new FailState<ModbusIP>({"Alarm RAH Sensor Fault"});
|
||||
case 3:
|
||||
return new FailState<ModbusIP>({"Alarm RAT Sensor Fault"});
|
||||
case 4:
|
||||
return new FailState<ModbusIP>({"Alarm Filter DP Sensor Fault"});
|
||||
case 5:
|
||||
return new FailState<ModbusIP>({"Alarm Flooding"});
|
||||
case 6:
|
||||
return new FailState<ModbusIP>({"Alarm Dirty Filter"});
|
||||
case 7:
|
||||
return new FailState<ModbusIP>({"Alarm High RAT"});
|
||||
case 8:
|
||||
return new FailState<ModbusIP>({"Alarm Low RAT"});
|
||||
case 9:
|
||||
return new FailState<ModbusIP>({"Alarm High SAT"});
|
||||
case 10:
|
||||
return new FailState<ModbusIP>({"Alarm Low SAT"});
|
||||
case 11:
|
||||
return new FailState<ModbusIP>({"Alarm High RAH"});
|
||||
case 12:
|
||||
return new FailState<ModbusIP>({"Alarm Low RAH"});
|
||||
case 13:
|
||||
return new FailState<ModbusIP>({"Alarm Phase Failure"});
|
||||
case 14:
|
||||
return new FailState<ModbusIP>({"Alarm Condensate Pump"});
|
||||
case 15:
|
||||
return new FailState<ModbusIP>({"Alarm Smoke"});
|
||||
case 16:
|
||||
return new FailState<ModbusIP>({"Alarm Fire"});
|
||||
case 17:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #1"});
|
||||
case 18:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #2"});
|
||||
case 19:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #3"});
|
||||
case 20:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #4"});
|
||||
case 21:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #5"});
|
||||
case 22:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #6"});
|
||||
case 23:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #7"});
|
||||
case 24:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #8"});
|
||||
case 25:
|
||||
return new FailState<ModbusIP>({"Alarm EC Fan #9"});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply any strategies defined for the standby state
|
||||
_applyStrategies(equipment);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when entering the running state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void RunningState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
|
||||
// Logic to run when the equipment enters this state
|
||||
Serial.println("Enter Running State...");
|
||||
// You could also update a Modbus register to show the "standby" state
|
||||
|
||||
const std::vector<std::string> motorStatusDescriptions = {
|
||||
"Run Status EC Fan #1", "Run Status EC Fan #2", "Run Status EC Fan #3",
|
||||
"Run Status EC Fan #4", "Run Status EC Fan #5", "Run Status EC Fan #6",
|
||||
"Run Status EC Fan #7", "Run Status EC Fan #8", "Run Status EC Fan #9"
|
||||
};
|
||||
|
||||
// Loop through and set all motor statuses to 0
|
||||
for (const auto& desc : motorStatusDescriptions) {
|
||||
ModbusPoint<ModbusIP>* point = equipment->getModbusPoint(desc);
|
||||
if (point) {
|
||||
point->setValue(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when exiting the running state.
|
||||
* @param equipment Pointer to the Equipment instance (unused in this implementation).
|
||||
*/
|
||||
template<>
|
||||
void RunningState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
|
||||
// Cleanup logic to run when the equipment leaves this state
|
||||
Serial.println("Exit Running State...");
|
||||
}
|
||||
124
src/CRAH_PAHHC_600_C6_TCP/State_Standby.cpp
Normal file
124
src/CRAH_PAHHC_600_C6_TCP/State_Standby.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @file State_Standby.cpp
|
||||
* @brief Implementation of the StandbyState class.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-05
|
||||
*
|
||||
* This file contains the implementation for the StandbyState, which defines
|
||||
* the behavior of the equipment when it is in an idle or standby mode.
|
||||
*/
|
||||
#include "Categories/ModbusPoint.h"
|
||||
#include "Categories/ModbusFloatDecorator.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
#include "Strategies/Strategy_Ramp.h"
|
||||
#include "Strategies/Strategy_Random.h"
|
||||
#include "Strategies/Strategy_Saw.h"
|
||||
#include "Strategies/Strategy_SingleValue.h"
|
||||
#include "Strategies/Strategy_Square.h"
|
||||
#include "Strategies/Strategy_PID.h"
|
||||
#include "States/State_Standby.h"
|
||||
#include "States/State_Running.h"
|
||||
#include "States/State_Fail.h"
|
||||
#include "States/State.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
/**
|
||||
* @brief Constructs a new StandbyState object.
|
||||
*
|
||||
* In this state, the equipment is idle. This constructor can be used to
|
||||
* define specific behaviors for Modbus points that should occur during standby,
|
||||
* such as setting fan speeds to zero.
|
||||
*/
|
||||
template<>
|
||||
StandbyState<ModbusIP>::StandbyState() {
|
||||
// You can add initialization code here if needed
|
||||
addStrategy("SAT Reading", new SingleValueStrategy(100.0f, 0.1f, 1000));
|
||||
addStrategy("CW Valve Position", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #1", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #2", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #3", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #4", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #5", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #6", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #7", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #8", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
addStrategy("Speed EC Fan #9", new RampStrategy(0.0f, 5.0f, 1000));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the running state's logic for one update cycle.
|
||||
*
|
||||
* This method checks the "State Control" Modbus point for a command to
|
||||
* transition to a different state (e.g., back to Standby). If no transition
|
||||
* is requested, it applies the strategies defined for the running state.
|
||||
*
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
* @return A pointer to a new State if a transition should occur, otherwise nullptr.
|
||||
*/
|
||||
template<>
|
||||
State<ModbusIP>* StandbyState<ModbusIP>::update(Equipment<ModbusIP>* equipment) {
|
||||
// STATE control, add conditions if change to a different state is needed
|
||||
Serial.println("Standby update function");
|
||||
|
||||
// Apply any strategies defined for the standby state
|
||||
_applyStrategies(equipment);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when entering the standby state.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
*/
|
||||
template<>
|
||||
void StandbyState<ModbusIP>::enterState(Equipment<ModbusIP>* equipment) {
|
||||
// Logic to run when the equipment enters this state
|
||||
// A list of all alarm descriptions
|
||||
const std::vector<std::string> alarmDescriptions = {
|
||||
"Alarm SAT Sensor Fault", "Alarm RAH Sensor Fault", "Alarm RAT Sensor Fault",
|
||||
"Alarm Filter DP Sensor Fault", "Alarm Flooding", "Alarm Dirty Filter",
|
||||
"Alarm High RAT", "Alarm Low RAT", "Alarm High SAT", "Alarm Low SAT",
|
||||
"Alarm High RAH", "Alarm Low RAH", "Alarm Common", "Alarm Phase Failure",
|
||||
"Alarm Condensate Pump", "Alarm Smoke", "Alarm Fire", "Alarm EC Fan #1",
|
||||
"Alarm EC Fan #2", "Alarm EC Fan #3", "Alarm EC Fan #4", "Alarm EC Fan #5",
|
||||
"Alarm EC Fan #6", "Alarm EC Fan #7", "Alarm EC Fan #8", "Alarm EC Fan #9"
|
||||
};
|
||||
|
||||
// A list of all motor run status descriptions
|
||||
const std::vector<std::string> motorStatusDescriptions = {
|
||||
"Run Status EC Fan #1", "Run Status EC Fan #2", "Run Status EC Fan #3",
|
||||
"Run Status EC Fan #4", "Run Status EC Fan #5", "Run Status EC Fan #6",
|
||||
"Run Status EC Fan #7", "Run Status EC Fan #8", "Run Status EC Fan #9"
|
||||
};
|
||||
|
||||
// Loop through and set all alarms to 0
|
||||
for (const auto& desc : alarmDescriptions) {
|
||||
ModbusPoint<ModbusIP>* point = equipment->getModbusPoint(desc);
|
||||
if (point) {
|
||||
point->setValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through and set all motor statuses to 0
|
||||
for (const auto& desc : motorStatusDescriptions) {
|
||||
ModbusPoint<ModbusIP>* point = equipment->getModbusPoint(desc);
|
||||
if (point) {
|
||||
point->setValue(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Logic to execute once when exiting the standby state.
|
||||
* @param equipment Pointer to the Equipment instance.
|
||||
*/
|
||||
template<>
|
||||
void StandbyState<ModbusIP>::exitState(Equipment<ModbusIP>* equipment) {
|
||||
// Cleanup logic to run when the equipment leaves this state
|
||||
Serial.println("Exit Standby State...");
|
||||
}
|
||||
134
src/CRAH_PAHHC_600_C6_TCP/config.h
Normal file
134
src/CRAH_PAHHC_600_C6_TCP/config.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @file config.h
|
||||
* @brief Main configuration file for the Equipment emulator.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-02
|
||||
*
|
||||
* This file contains two important configurations: WiFi network parameters
|
||||
* and the Modbus register map for the device.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup WiFiConfig WiFi Configuration
|
||||
* @brief Network parameters for WiFi connection.
|
||||
* @{
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include "core.h"
|
||||
#include "Equipment/Equipment.h"
|
||||
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
const char *ssid = "esrlok_portable"; /**< @brief The SSID of the WiFi network. */
|
||||
const char *password = "m7g6eNMe?cy8S@z"; /**< @brief The password for the WiFi network. */
|
||||
IPAddress local_IP(192, 168, 1, 234); /**< @brief The static IP address for the device. */
|
||||
IPAddress gateway(192, 168, 1, 1); /**< @brief The gateway IP address. */
|
||||
IPAddress subnet(255, 255, 255, 0); /**< @brief The subnet mask. */
|
||||
|
||||
ModbusIP mb;
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @brief The main loop update interval in milliseconds.
|
||||
*/
|
||||
int interval = 250;
|
||||
|
||||
/**
|
||||
* @brief The Modbus map for the Equipment device.
|
||||
* This array defines all the Modbus points available on the emulated device.
|
||||
* The `description` field is crucial as it's used to look up points within the application logic.
|
||||
*/
|
||||
modbusMap mb_map[] =
|
||||
{
|
||||
{HR, 15, 0, "State Control"}, //Internal to control from Modscan
|
||||
{HR, 16, 0, "Fault Code"},
|
||||
{HR_FLOAT, 18, 0, "RAT"}, //Internal Fault code from Modscan
|
||||
{HR_FLOAT, 1, 0, "SAT Setpoint"},
|
||||
{HR_FLOAT, 681, 0, "RAT Setpoint"},
|
||||
{HR_FLOAT, 111, 0, "High RAT Limit"},
|
||||
{HR_FLOAT, 114, 0, "Low RAT Limit"},
|
||||
{HR_FLOAT, 118, 0, "High SAT Limit"},
|
||||
{HR_FLOAT, 122, 0, "Low SAT Limit"},
|
||||
{HR_FLOAT, 685, 0, "High RAH Limit"},
|
||||
{HR_FLOAT, 689, 0, "Low RAH Limit"},
|
||||
{HR, 5, 0, "Setting the EC Fan Max Speed"},
|
||||
{HR, 695, 0, "Setting the EC Fan Min Speed"},
|
||||
{HR_FLOAT, 693, 0, "Setting Room Temp"},
|
||||
{HR, 691, 0, "Setting EC Fan Speed "},
|
||||
{DI, 146, 0, "Alarm SAT Sensor Fault"},
|
||||
{DI, 1246, 0, "Alarm RAH Sensor Fault"},
|
||||
{DI, 1245, 0, "Alarm RAT Sensor Fault"},
|
||||
{DI, 1250, 0, "Alarm Filter DP Sensor Fault"},
|
||||
{DI, 51, 0, "Alarm Flooding"},
|
||||
{DI, 1096, 0, "Alarm Dirty Filter"},
|
||||
{DI, 1367, 0, "Alarm High RAT"},
|
||||
{DI, 1099, 0, "Alarm Low RAT"},
|
||||
{DI, 118, 0, "Alarm High SAT"},
|
||||
{DI, 122, 0, "Alarm Low SAT"},
|
||||
{DI, 1307, 0, "Alarm High RAH"},
|
||||
{DI, 1308, 0, "Alarm Low RAH"},
|
||||
{DI, 1342, 0, "Alarm Common"},
|
||||
{DI, 148, 0, "Alarm Phase Failure"},
|
||||
{DI, 1370, 0, "Alarm Condensate Pump"},
|
||||
{DI, 1368, 0, "Alarm Smoke"},
|
||||
{DI, 1369, 0, "Alarm Fire"},
|
||||
{DI, 131, 0, "Alarm EC Fan #1"},
|
||||
{DI, 132, 0, "Alarm EC Fan #2"},
|
||||
{DI, 133, 0, "Alarm EC Fan #3"},
|
||||
{DI, 134, 0, "Alarm EC Fan #4"},
|
||||
{DI, 135, 0, "Alarm EC Fan #5"},
|
||||
{DI, 136, 0, "Alarm EC Fan #6"},
|
||||
{DI, 1360, 0, "Alarm EC Fan #7"},
|
||||
{DI, 1361, 0, "Alarm EC Fan #8"},
|
||||
{DI, 1362, 0, "Alarm EC Fan #9"},
|
||||
{DI, 138, 0, "Run Status EC Fan #1"},
|
||||
{DI, 139, 0, "Run Status EC Fan #2"},
|
||||
{DI, 140, 0, "Run Status EC Fan #3"},
|
||||
{DI, 141, 0, "Run Status EC Fan #4"},
|
||||
{DI, 142, 0, "Run Status EC Fan #5"},
|
||||
{DI, 143, 0, "Run Status EC Fan #6"},
|
||||
{DI, 1363, 0, "Run Status EC Fan #7"},
|
||||
{DI, 1364, 0, "Run Status EC Fan #8"},
|
||||
{DI, 1365, 0, "Run Status EC Fan #9"},
|
||||
{IR_FLOAT, 99, 0, "SAT Reading"},
|
||||
{IR_FLOAT, 70, 0, "RAH Reading"},
|
||||
{IR_FLOAT, 101, 0, "RAT Reading"},
|
||||
{IR_FLOAT, 106, 0, "Filter DP Reading"},
|
||||
{IR_FLOAT, 496, 0, "CW Valve Position"},
|
||||
{IR, 53, 0, "Speed EC Fan #1"},
|
||||
{IR, 228, 0, "Speed EC Fan #2"},
|
||||
{IR, 229, 0, "Speed EC Fan #3"},
|
||||
{IR, 230, 0, "Speed EC Fan #4"},
|
||||
{IR, 231, 0, "Speed EC Fan #5"},
|
||||
{IR, 232, 0, "Speed EC Fan #6"},
|
||||
{IR, 678, 0, "Speed EC Fan #7"},
|
||||
{IR, 679, 0, "Speed EC Fan #8"},
|
||||
{IR, 680, 0, "Speed EC Fan #9"},
|
||||
{IR, 274, 0, "Operating Hours EC Fan #1"},
|
||||
{IR, 233, 0, "Operating Hours EC Fan #2"},
|
||||
{IR, 244, 0, "Operating Hours EC Fan #3"},
|
||||
{IR, 235, 0, "Operating Hours EC Fan #4"},
|
||||
{IR, 236, 0, "Operating Hours EC Fan #5"},
|
||||
{IR, 245, 0, "Operating Hours EC Fan #6"},
|
||||
{IR, 486, 0, "Operating Hours EC Fan #7"},
|
||||
{IR, 487, 0, "Operating Hours EC Fan #8"},
|
||||
{IR, 488, 0, "Operating Hours EC Fan #9"},
|
||||
{COIL, 301, 0, "ON/OFF Command By BMS"},
|
||||
{COIL, 302, 0, "Enable Off By Supervisory"},
|
||||
{COIL, 264, 0, "Alarm Reset"}
|
||||
};
|
||||
//Size of modbus map used in FOR cycles, automatically calculated.
|
||||
/**
|
||||
* @brief The total number of entries in the `mb_map` array.
|
||||
* This is calculated at compile time and used for iterating over the map.
|
||||
*/
|
||||
const int map_size = sizeof(mb_map) / sizeof(mb_map[0]);
|
||||
/** @} */
|
||||
|
||||
#endif // CONFIG_H
|
||||
87
src/CRAH_PAHHC_600_C6_TCP/main.cpp
Normal file
87
src/CRAH_PAHHC_600_C6_TCP/main.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @file BaseEmulator.ino
|
||||
* @brief Main execution program for the Arduino Emulator.
|
||||
* @author Emmanuel Hernandez Cruz
|
||||
* @date 2025-09-02
|
||||
*
|
||||
* @details This file contains the main execution program for an Arduino-based emulator of a equipment unit.
|
||||
* The program uses a Wi-Fi connection to communicate via the Modbus IP protocol.
|
||||
*
|
||||
* The setup() function initializes the following:
|
||||
* - Serial communication for debugging.
|
||||
* - Wi-Fi connection using credentials from config.h.
|
||||
* - A Modbus IP server.
|
||||
* - Modbus points (Coils, Holding Registers, etc.) based on a predefined map in config.h.
|
||||
*
|
||||
* The loop() function continuously:
|
||||
* - Services the Modbus IP server.
|
||||
* - Reads values from the Modbus server into internal data structures.
|
||||
* - Updates the state of the emulated equipment.
|
||||
* - Writes updated values back to the Modbus server.
|
||||
*
|
||||
* @see config.h for Wi-Fi and Modbus configuration.
|
||||
* @see Equipment.h for the main equipment logic.
|
||||
* @see State.h for different equipment states.
|
||||
* @see Strategy_Behavior.h for different value generation strategies.
|
||||
* @see ModbusPoint.h for the base class for all Modbus points.
|
||||
*/
|
||||
//=================================================================================================================================
|
||||
//Libraries and declaration of variables.
|
||||
#include <WiFi.h>
|
||||
#include "config.h"
|
||||
#include "Categories/ModbusPointFactory.h"
|
||||
#if defined(USE_MODBUS_IP)
|
||||
#include <ModbusIP_ESP8266.h>
|
||||
#else
|
||||
#include <ModbusRTU.h>
|
||||
#endif
|
||||
//=================================================================================================================================
|
||||
/**
|
||||
* @brief Initializes the application.
|
||||
* @details This function runs once at startup. It configures the serial communication,
|
||||
* Wi-Fi, and the Modbus server. It also creates and initializes all the Modbus points
|
||||
* based on the `mb_map` array in `config.h`.
|
||||
*/
|
||||
void setup() {
|
||||
Serial.begin(115200); //Serial comm start
|
||||
WiFi.config(local_IP, gateway, subnet); // Wifi service start
|
||||
WiFi.begin(ssid, password);
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(1000);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("Connected!!");
|
||||
mb.server(); //Modbus server start
|
||||
Serial.println("Server Created");
|
||||
Serial.println(map_size);
|
||||
for(int i = 0; i < map_size; i++){
|
||||
ModbusPoint<ModbusIP>* point = createModbusPoint(&mb, mb_map[i].category, mb_map[i].address, mb_map[i].value, mb_map[i].description);
|
||||
if (point) {
|
||||
point->addToModbusServer();
|
||||
EquipmentInstance.addModbusPoint(mb_map[i].description, point);
|
||||
}
|
||||
}
|
||||
Serial.println("All modbus Points created");
|
||||
Serial.println("Setup function ended");
|
||||
}
|
||||
//=================================================================================================================================
|
||||
/**
|
||||
* @brief The main application loop.
|
||||
* @details This function runs repeatedly after setup() has completed. It performs the following actions in order:
|
||||
* 1. Services the Modbus server by calling `mb.task()`.
|
||||
* 2. Reads the current values from the Modbus registers into the `ModbusPoint` objects by calling `readRegisters()`.
|
||||
* 3. After a specified interval, it updates the equipment's state by calling `EquipmentInstance.update()`.
|
||||
* 4. Writes any changed values from the `ModbusPoint` objects back to the Modbus registers by calling `writeRegisters()`.
|
||||
*/
|
||||
void loop() {
|
||||
mb.task();
|
||||
unsigned long currentMillis = millis();
|
||||
if (currentMillis - previousMillis >= interval) {
|
||||
previousMillis = currentMillis;
|
||||
unsigned long startTime = millis();
|
||||
EquipmentInstance.update();
|
||||
unsigned long endTime = millis();
|
||||
unsigned long elapsedTime = endTime - startTime;
|
||||
Serial.printf("Control Execution time: %d ms\n", elapsedTime);
|
||||
}
|
||||
}
|
||||
11
test/README
Normal file
11
test/README
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
This directory is intended for PlatformIO Test Runner and project tests.
|
||||
|
||||
Unit Testing is a software testing method by which individual units of
|
||||
source code, sets of one or more MCU program modules together with associated
|
||||
control data, usage procedures, and operating procedures, are tested to
|
||||
determine whether they are fit for use. Unit testing finds problems early
|
||||
in the development cycle.
|
||||
|
||||
More information about PlatformIO Unit Testing:
|
||||
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||
Reference in New Issue
Block a user