diff --git a/.kbot/params.json b/.kbot/params.json index 881c50d7..a1d6ab54 100644 --- a/.kbot/params.json +++ b/.kbot/params.json @@ -3,7 +3,7 @@ "messages": [ { "role": "user", - "content": "# Context\r\n\r\n- ESP-32, Platform.io, C17\r\n- avoidance of std\r\n- industrial application, using Modbus-485\r\n\r\n# Instructions\r\n\r\n- Generate documentation for a given component in a new Markdown file located at `./docs/.md`.\r\n- The documentation is intended for an Astro static site generator.\r\n- Include frontmatter in the Markdown file with `title`, `description`, and `keywords` fields.\r\n- You will be provided with the content of the component's header file (e.g., `src/.h`).\r\n- Ensure the generated Markdown adheres to standard linting rules.\r\n- The generated Markdown document must follow the specific layout provided below.\r\n- The component's C++ source file will contain an example of how it is constructed and mounted; use this as a basis for the \"Example\" section in the documentation.\r\n- Do not comment or add thoughts, just output plain Markdown\r\n\r\n## Layout\r\n\r\nThe Markdown document must adhere to the following structure:\r\n\r\n----------------------------\r\n\r\n## COMPONENT NAME\r\n\r\n**Path**: `relative/path/to/component-name.cpp` (as a Markdown link)\r\n\r\n**Revision History**: Add a file revision entry in the header to track modifications. For the initial documentation, use \"Initial documentation\". Skip this if a revision history already exists !\r\n\r\nA short description of the component, highlight features\r\n\r\n\r\n## REQUIREMENTS\r\n\r\nDetail the hardware pins and software dependencies required by the component.\r\n\r\n## FEATURES\r\n\r\nList the key features and functionalities of the component.\r\n\r\n## DEPENDENCIES\r\n\r\n- Provide a list of dependencies as Markdown formatted links.\r\n- Include a minimal Mermaid diagram illustrating the dependencies. The diagram node names should not contain braces or brackets.\r\n\r\n## BEHAVIOUR\r\n\r\n- Include a minimal Mermaid diagram illustrating the component's behaviour or state machine. The diagram node names should not contain braces or brackets.\r\n\r\n## TODOS\r\n\r\n### PERFORMANCE\r\n\r\nOutline any performance considerations or areas for future optimization.\r\n\r\n### SECURITY\r\n\r\nDescribe potential security vulnerabilities or hardening measures.\r\n\r\n### COMPLIANCE\r\n\r\nNote any compliance standards or requirements relevant to the component.\r\n\r\n### RECOMMENDATIONS\r\n\r\nProvide any recommendations for using or extending the component.\r\n\r\n## EXAMPLE\r\n\r\nThis section should illustrate how the component is constructed and mounted.\r\nRefer to the component's C++ source file for an example.\r\nA general example structure is:\r\n\r\n#ifdef PIN_LED_FEEDBACK_0\r\n ledFeedback_0 = new LEDFeedback(\r\n this, // owner\r\n PIN_LED_FEEDBACK_0, // pin\r\n LED_PIXEL_COUNT_0, // pixelCount\r\n ID_LED_FEEDBACK_0, // id\r\n LED_FEEDBACK_0_MB_ADDR // modbusAddress\r\n );\r\n if (ledFeedback_0)\r\n {\r\n components.push_back(ledFeedback_0);\r\n Log.infoln(F(\"LEDFeedback_0 initialized. Pin:%d, Count:%d, ID:%d, MB:%d\"),\r\n PIN_LED_FEEDBACK_0, LED_PIXEL_COUNT_0,\r\n ID_LED_FEEDBACK_0, LED_FEEDBACK_0_MB_ADDR);\r\n }\r\n else\r\n {\r\n Log.errorln(F(\"LEDFeedback_0 initialization failed.\"));\r\n }\r\n#endif\r\n\r\n### References\r\n\r\n${DOXYGEN_PLACEHOLDER}\r\n\r\n${VENDOR_PLACEHOLDER}\r\n" + "content": "# Context\r\n\r\n- ESP-32, Platform.io, C17\r\n- avoidance of std\r\n- industrial application, using Modbus-485\r\n\r\n## Instructions\r\n\r\n- Generate documentation for a given class, its types, enumerations, in a new Markdown file located at `./docs/modbus/.md`.\r\n- The documentation is intended for an Astro static site generator.\r\n- Include frontmatter in the Markdown file with `title`, `description`, and `keywords` fields.\r\n- You will be provided with the content of the component's header file (e.g., `src/modbus/.h`).\r\n- Ensure the generated Markdown adheres to standard linting rules.\r\n- The generated Markdown document must follow the specific layout provided below.\r\n- The component's C++ source file will contain an example of how it is constructed and mounted; use this as a basis for the \"Example\" section in the documentation.\r\n- Do not comment or add thoughts, just output plain Markdown\r\n\r\n## Layout\r\n\r\nThe Markdown document must adhere to the following structure:\r\n\r\n----------------------------\r\n\r\n## COMPONENT NAME\r\n\r\n**Path**: `relative/path/to/component-name.cpp` (as a Markdown link)\r\n\r\n**Revision History**: Add a file revision entry in the header to track modifications. For the initial documentation, use \"Initial documentation\". Skip this if a revision history already exists !\r\n\r\nA detailed description of the component\r\n\r\n## REQUIREMENTS\r\n\r\nDetail the hardware pins and software dependencies required by the component.\r\n\r\n## PROVIDES\r\n\r\n- a list of all types, enumerations and classes\r\n\r\n## FEATURES\r\n\r\nList the key features and functionalities of the component.\r\n\r\n## DEPENDENCIES\r\n\r\n- Provide a list of dependencies as Markdown formatted links.\r\n- Include a minimal Mermaid diagram illustrating the dependencies. The diagram node names should not contain braces or brackets.\r\n\r\n## BEHAVIOUR\r\n\r\n- Include a minimal Mermaid diagram illustrating the component's behaviour or state machine. The diagram node names should not contain braces or brackets.\r\n\r\n\r\n## TODOS\r\n\r\n### PERFORMANCE\r\n\r\nOutline any performance considerations or areas for future optimization.\r\n\r\n### SECURITY\r\n\r\nDescribe potential security vulnerabilities or hardening measures.\r\n\r\n### COMPLIANCE\r\n\r\nNote any compliance standards or requirements relevant to the component.\r\n\r\n### RECOMMENDATIONS\r\n\r\nProvide any recommendations for using or extending the component.\r\n" }, { "role": "user", @@ -11,13 +11,13 @@ }, { "role": "user", - "path": "src/App.h", - "content": "#ifndef APP_H\n#define APP_H\n\n#include \n#include \n#include \n#include \n#include \n\nclass Bridge;\n\n/**\n * @brief The App class represents the main application component.\n *\n * This class inherits from the Component class and provides functionality\n * for setting up, running the main loop, debugging, and providing information\n * about the application.\n */\nclass App : public Component\n{\n\npublic:\n /**\n * @brief Default constructor for the App class.\n */\n App();\n\n App(String _name, short _id, int _flags) : Component(_name, _id, _flags) {}\n\n /**\n * @brief Retrieves a component by its ID.\n *\n * @param id The ID of the component to retrieve.\n * @return A pointer to the component with the specified ID, or nullptr if not found.\n */\n virtual Component *byId(ushort id);\n\n /**\n * @brief Performs the setup operations for the application and it's components.\n *\n * @return A status code indicating the result of the setup operation.\n */\n virtual short setup();\n\n /**\n * @brief Virtual function being called after all components have been setup.\n * @return The error code indicating the success or failure of the operation.\n */\n virtual short onRun();\n \n /**\n * @brief Runs the main loop of the application.\n *\n * @return A status code indicating the result of the loop operation.\n */\n virtual short loop();\n\n /**\n * @brief Performs debugging operations for the application.\n *\n * @return A status code indicating the result of the debug operation.\n */\n virtual short debug();\n\n /**\n * @brief Retrieves information about the application.\n *\n * @return A status code indicating the result of the info operation.\n */\n virtual short info();\n\n /**\n * @brief Retrieves the number of components with a specific flag.\n *\n * @param flag The flag to filter the components by.\n * @return The number of components with the specified flag.\n */\n virtual short numByFlag(ushort flag);\n\n /**\n * @brief The list of components in the application.\n */\n Vector components;\n\n /**\n * @brief The timestamp for the last debug operation.\n */\n millis_t debugTS;\n\n /**\n * @brief The timestamp for the last loop operation.\n */\n millis_t loopTS;\n\n /**\n * @brief The wait time between loop iterations.\n */\n millis_t wait;\n\n /**\n * @brief The timestamp for the last wait operation.\n */\n millis_t waitTS;\n\n /**\n * @brief The interval for debugging operations.\n */\n millis_t DEBUG_INTERVAL;\n\n //////////////////////////////////////////\n //\n // Messaging / Binding\n //\n /**\n * @brief Register all components in `Bridge`\n *\n * @return A status code indicating the result of the setup operation.\n */\n virtual short registerComponents(Bridge *bridge);\n\n Bridge *bridge;\n\n virtual void printRegisters() { }\n\n //////////////////////////////////////////\n //\n // Debugging\n //\n virtual short setDebugParams(short val0, short val1);\n};\n\n#endif" + "path": "src/modbus/ModbusTypes.h", + "content": "#ifndef MODBUS_TYPES_H\r\n#define MODBUS_TYPES_H\r\n#include \r\n#include \r\n#include \r\n#include \r\n#include \r\n\r\n#include \"macros.h\"\r\n#include \"constants.h\"\r\n#include \"config-modbus.h\"\r\n\r\nenum E_FN_CODE : uint8_t\r\n{\r\n FN_ANY_FUNCTION_CODE = 0x00, // Only valid for server to register function codes\r\n FN_READ_COIL = 0x01,\r\n FN_READ_DISCR_INPUT = 0x02,\r\n FN_READ_HOLD_REGISTER = 0x03,\r\n FN_READ_INPUT_REGISTER = 0x04,\r\n FN_WRITE_COIL = 0x05,\r\n FN_WRITE_HOLD_REGISTER = 0x06,\r\n FN_READ_EXCEPTION_SERIAL = 0x07,\r\n FN_DIAGNOSTICS_SERIAL = 0x08,\r\n FN_READ_COMM_CNT_SERIAL = 0x0B,\r\n FN_READ_COMM_LOG_SERIAL = 0x0C,\r\n FN_WRITE_MULT_COILS = 0x0F,\r\n FN_WRITE_MULT_REGISTERS = 0x10,\r\n FN_REPORT_SERVER_ID_SERIAL = 0x11,\r\n FN_READ_FILE_RECORD = 0x14,\r\n FN_WRITE_FILE_RECORD = 0x15,\r\n FN_MASK_WRITE_REGISTER = 0x16,\r\n FN_R_W_MULT_REGISTERS = 0x17,\r\n FN_READ_FIFO_QUEUE = 0x18,\r\n FN_ENCAPSULATED_INTERFACE = 0x2B,\r\n FN_USER_DEFINED_41 = 0x41,\r\n FN_USER_DEFINED_42 = 0x42,\r\n FN_USER_DEFINED_43 = 0x43,\r\n FN_USER_DEFINED_44 = 0x44,\r\n FN_USER_DEFINED_45 = 0x45,\r\n FN_USER_DEFINED_46 = 0x46,\r\n FN_USER_DEFINED_47 = 0x47,\r\n FN_USER_DEFINED_48 = 0x48,\r\n FN_USER_DEFINED_64 = 0x64,\r\n FN_USER_DEFINED_65 = 0x65,\r\n FN_USER_DEFINED_66 = 0x66,\r\n FN_USER_DEFINED_67 = 0x67,\r\n FN_USER_DEFINED_68 = 0x68,\r\n FN_USER_DEFINED_69 = 0x69,\r\n FN_USER_DEFINED_6A = 0x6A,\r\n FN_USER_DEFINED_6B = 0x6B,\r\n FN_USER_DEFINED_6C = 0x6C,\r\n FN_USER_DEFINED_6D = 0x6D,\r\n FN_USER_DEFINED_6E = 0x6E,\r\n FN_NONE = 0xFF,\r\n};\r\n\r\n// Define Modbus operation types\r\nenum E_MB_OpType\r\n{\r\n MB_READ_COIL,\r\n MB_READ_REGISTER,\r\n MB_WRITE_COIL,\r\n MB_WRITE_REGISTER,\r\n MB_WRITE_MULTIPLE_REGISTERS\r\n};\r\n\r\n// Define operation status\r\nenum E_MB_OpStatus\r\n{\r\n MB_PENDING,\r\n MB_SUCCESS,\r\n MB_FAILED,\r\n MB_RETRYING\r\n};\r\n\r\n// Define operation error codes - incorporates internal errors and eModbus errors\r\nenum class MB_Error : uint8_t\r\n{\r\n // Standard Modbus Exception Codes (as defined by eModbus)\r\n Success = 0x00, // Also used for internal success\r\n IllegalFunction = 0x01,\r\n IllegalDataAddress = 0x02,\r\n IllegalDataValue = 0x03,\r\n ServerDeviceFailure = 0x04,\r\n Acknowledge = 0x05,\r\n ServerDeviceBusy = 0x06,\r\n NegativeAcknowledge = 0x07,\r\n MemoryParityError = 0x08,\r\n GatewayPathUnavailable = 0x0A,\r\n GatewayTargetNoResp = 0x0B,\r\n\r\n // Internal Operation/Queue Errors (remapped to avoid conflicts)\r\n OpNotReady = 0x10, // Renamed from NotReady\r\n OpQueueFull = 0x11, // Renamed from QueueFull\r\n OpClientQueueFull = 0x12, // Renamed from ClientQueueFull\r\n OpExecutionFailed = 0x13, // Renamed from ExecutionFailed\r\n OpInvalidParameter = 0x14, // Renamed from InvalidParameter\r\n OpRetrying = 0x15, // Renamed from Retrying\r\n OpMaxRetriesExceeded = 0x16, // Renamed from MaxRetriesExceeded\r\n\r\n // eModbus Specific Communication Errors\r\n Timeout = 0xE0,\r\n InvalidServer = 0xE1,\r\n CrcError = 0xE2, // RTU only\r\n FcMismatch = 0xE3,\r\n ServerIdMismatch = 0xE4,\r\n PacketLengthError = 0xE5,\r\n ParameterCountError = 0xE6,\r\n ParameterLimitError = 0xE7,\r\n RequestQueueFull = 0xE8, // eModbus client queue\r\n IllegalIpOrPort = 0xE9,\r\n IpConnectionFailed = 0xEA,\r\n TcpHeadMismatch = 0xEB,\r\n EmptyMessage = 0xEC,\r\n AsciiFrameError = 0xED,\r\n AsciiCrcError = 0xEE,\r\n AsciiInvalidChar = 0xEF,\r\n BroadcastError = 0xF0,\r\n UndefinedError = 0xFF // Other communication error\r\n};\r\n\r\n/**\r\n * @brief Structure to hold Modbus registration details for a component.\r\n */\r\nstruct MB_Registers\r\n{\r\n ushort startAddress = -1; // Starting address (-1 indicates invalid/not set)\r\n ushort count = 0; // Number of consecutive addresses\r\n ushort slaveId = 0; // Slave ID of the device\r\n E_FN_CODE type = E_FN_CODE::FN_NONE; // Type of Modbus object\r\n E_ModbusAccess access = MB_ACCESS_NONE; // Read/Write access\r\n ushort componentId = 0; // ID of the owning component (MAY NOT BE SET in data returned by mb_tcp_blocks)\r\n const char *name = nullptr; // Optional descriptive name for the register block\r\n const char *group = nullptr; // Optional group name for the register block\r\n ComponentFnPtr writeCallbackFn = nullptr; // Optional component member function pointer for write callbacks\r\n \r\n MB_Registers(ushort addr = 0xFFFF, // Use 0xFFFF for unsigned invalid marker\r\n ushort ct = 0,\r\n E_FN_CODE t = E_FN_CODE::FN_NONE,\r\n E_ModbusAccess a = MB_ACCESS_NONE,\r\n ushort cId = 0,\r\n ushort sId = 0,\r\n const char *n = nullptr,\r\n const char *g = nullptr,\r\n ComponentFnPtr cb = nullptr) // Add to constructor\r\n : startAddress(addr), count(ct), type(t), access(a), componentId(cId), slaveId(sId), name(n), group(g), writeCallbackFn(cb)\r\n {\r\n }\r\n};\r\n\r\n/**\r\n * @brief A non-owning view of a collection of MB_Registers blocks.\r\n */\r\nstruct ModbusBlockView\r\n{\r\n const MB_Registers *data; // Pointer to the first block. Never null if count > 0.\r\n int count; // Total number of blocks in the view.\r\n};\r\n\r\n// Forward declarations\r\nclass ModbusRTU;\r\nclass ModbusOperation;\r\n\r\n// Define filter types for type identification without RTTI\r\nenum E_FilterType\r\n{\r\n FILTER_DUPLICATE,\r\n FILTER_RATE_LIMIT,\r\n FILTER_PRIORITY,\r\n FILTER_LIFECYCLE,\r\n FILTER_CUSTOM\r\n};\r\n\r\n// Filter chain base class\r\nclass ModbusOperationFilter\r\n{\r\npublic:\r\n ModbusOperationFilter() : nextFilter(nullptr) {}\r\n virtual ~ModbusOperationFilter() = default;\r\n\r\n // Returns true if operation should be queued, false if it should be dropped\r\n virtual bool filter(const ModbusOperation &op) = 0;\r\n\r\n // Get the filter type\r\n virtual E_FilterType getType() const = 0;\r\n\r\n // Set the next filter in the chain\r\n void setNext(ModbusOperationFilter *next) { nextFilter = next; }\r\n\r\n // Get the next filter in the chain\r\n ModbusOperationFilter *getNext() const { return nextFilter; }\r\n\r\n // Process the operation through this filter and subsequent filters\r\n bool process(const ModbusOperation &op)\r\n {\r\n if (!filter(op))\r\n return false;\r\n return nextFilter ? nextFilter->process(op) : true;\r\n }\r\n\r\n // Method to notify filter that an operation was executed\r\n // Used by filters that need to track operation status\r\n virtual void notifyOperationExecuted(const ModbusOperation &op) {}\r\n\r\n // Method to notify filter that an operation was completed\r\n // Used by filters that need to track operation status\r\n virtual void notifyOperationCompleted(const ModbusOperation &op) {}\r\n\r\nprivate:\r\n ModbusOperationFilter *nextFilter;\r\n};\r\n\r\n// Structure to hold a Modbus operation\r\n// Optimized memory layout: larger members first, booleans last\r\nstruct ModbusOperation\r\n{\r\n unsigned long timestamp; // 4 bytes\r\n uint32_t token; // 4 bytes\r\n uint16_t address; // 2 bytes\r\n uint16_t value; // 2 bytes\r\n uint16_t quantity; // 2 bytes\r\n uint8_t slaveId; // 1 byte\r\n uint8_t retries; // 1 byte\r\n uint8_t flags; // 1 byte for all boolean flags\r\n E_FN_CODE type; // 1 byte enum\r\n E_MB_OpStatus status; // 1 byte enum\r\n\r\n ModbusOperation()\r\n : timestamp(0), token(0), address(0), value(0), quantity(1),\r\n slaveId(0), retries(0), flags(0), type(E_FN_CODE::FN_READ_COIL), status(MB_PENDING) {}\r\n\r\n ModbusOperation(E_FN_CODE t, uint8_t s, uint16_t a, uint16_t v = 0, uint16_t q = 1, bool hp = false)\r\n : timestamp(millis()), token(0), address(a), value(v), quantity(q),\r\n slaveId(s), retries(0), flags(0), type(t), status(MB_PENDING)\r\n {\r\n if (hp)\r\n flags |= OP_HIGH_PRIORITY_BIT;\r\n }\r\n\r\n // Getter/setter methods for flags - Update to use TEST/SET_BIT_TO\r\n bool isUsed() const { return TEST(flags, OP_USED_BIT); }\r\n bool isHighPriority() const { return TEST(flags, OP_HIGH_PRIORITY_BIT); }\r\n bool isInProgress() const { return TEST(flags, OP_IN_PROGRESS_BIT); }\r\n bool isBroadcast() const { return TEST(flags, OP_BROADCAST_BIT); }\r\n bool isSynchronized() const { return TEST(flags, OP_SYNCHRONIZED_BIT); }\r\n\r\n void setUsed(bool value) { SET_BIT_TO(flags, OP_USED_BIT, value); }\r\n void setHighPriority(bool value) { SET_BIT_TO(flags, OP_HIGH_PRIORITY_BIT, value); }\r\n void setInProgress(bool value) { SET_BIT_TO(flags, OP_IN_PROGRESS_BIT, value); }\r\n void setBroadcast(bool value) { SET_BIT_TO(flags, OP_BROADCAST_BIT, value); }\r\n void setSynchronized(bool value) { SET_BIT_TO(flags, OP_SYNCHRONIZED_BIT, value); }\r\n};\r\n\r\n// Structure to represent a register or coil value entry\r\nstruct ModbusValueEntry\r\n{\r\n unsigned long lastUpdate; // 4 bytes\r\n uint16_t address; // 2 bytes\r\n uint16_t value; // 2 bytes\r\n uint8_t flags; // 1 byte for flags (used, synchronized)\r\n\r\n ModbusValueEntry()\r\n : lastUpdate(0), address(0), value(0), flags(0) {} // Default: not used, not synchronized\r\n\r\n ModbusValueEntry(uint16_t addr, uint16_t val, bool sync = true)\r\n : lastUpdate(millis()), address(addr), value(val), flags(0)\r\n { // Initialize flags to 0 first\r\n SBI(flags, VALUE_USED_BIT); // Set used bit\r\n if (sync)\r\n SBI(flags, VALUE_SYNCHRONIZED_BIT); // Set synchronized bit if needed\r\n }\r\n};\r\n\r\n// Structure to represent a Modbus slave's data\r\nstruct SlaveData\r\n{\r\n ModbusValueEntry coils[MAX_ADDRESSES_PER_SLAVE];\r\n ModbusValueEntry registers[MAX_ADDRESSES_PER_SLAVE];\r\n uint8_t coilCount;\r\n uint8_t registerCount;\r\n\r\n SlaveData() : coilCount(0), registerCount(0)\r\n {\r\n clear();\r\n }\r\n\r\n void clear()\r\n {\r\n coilCount = 0;\r\n registerCount = 0;\r\n for (int i = 0; i < MAX_ADDRESSES_PER_SLAVE; ++i)\r\n {\r\n CBI(coils[i].flags, VALUE_USED_BIT); // Use CBI\r\n CBI(registers[i].flags, VALUE_USED_BIT); // Use CBI\r\n CBI(coils[i].flags, VALUE_SYNCHRONIZED_BIT); // Use CBI\r\n CBI(registers[i].flags, VALUE_SYNCHRONIZED_BIT); // Use CBI\r\n }\r\n }\r\n};\r\n\r\n// Add callback function type definition\r\ntypedef void (*ResponseCallback)(uint8_t slaveId);\r\n\r\n// Callback function types for notifications\r\ntypedef void (*OnRegisterChangeCallback)(const ModbusOperation &op, uint16_t oldValue, uint16_t newValue);\r\ntypedef void (*OnWriteCallback)(const ModbusOperation &op);\r\ntypedef void (*OnErrorCallback)(const ModbusOperation &op, int errorCode, const char *errorMessage);\r\n\r\n// Struct for passing RTU update data via onMessage(void*)\r\n// Used for synchronous message passing - data copied immediately by receiver.\r\nstruct MB_UpdateData {\r\n uint8_t slaveId; // Original RTU Slave ID\r\n uint16_t address; // RTU Address OR Calculated TCP Address (depending on context)\r\n uint16_t value;\r\n uint16_t componentId;\r\n E_FN_CODE functionCode;\r\n};\r\n\r\n// Define empty callbacks for default behavior\r\ninline void emptyRegisterChangeCallback(const ModbusOperation &, uint16_t, uint16_t) {}\r\ninline void emptyWriteCallback(const ModbusOperation &) {}\r\ninline void emptyErrorCallback(const ModbusOperation &, int, const char *) {}\r\n\r\n// Function to convert Modbus Error enum to human-readable string\r\nconst char *modbusErrorToString(MB_Error error);\r\n\r\n// Structure for defining mandatory read blocks\r\nstruct ModbusReadBlock\r\n{\r\n uint16_t startAddress; // Starting address of the block\r\n uint16_t count; // Number of registers/coils in the block\r\n E_FN_CODE type; // Modbus function code for reading (e.g., FN_READ_HOLD_REGISTER)\r\n unsigned long readInterval; // Minimum interval between reads (ms)\r\n unsigned long lastReadTime; // Timestamp of the last read attempt (millis())\r\n uint8_t flags; // Status flags (e.g., used)\r\n\r\n ModbusReadBlock()\r\n : startAddress(0), count(0), type(E_FN_CODE::FN_NONE), readInterval(0), lastReadTime(0), flags(0) {}\r\n\r\n ModbusReadBlock(uint16_t start, uint16_t ct, E_FN_CODE t, unsigned long interval = 1000)\r\n : startAddress(start), count(ct), type(t), readInterval(interval), lastReadTime(0), flags(0)\r\n {\r\n SBI(flags, BLOCK_USED_BIT);\r\n }\r\n\r\n bool isUsed() const { return TEST(flags, BLOCK_USED_BIT); }\r\n void setUsed(bool value) { SET_BIT_TO(flags, BLOCK_USED_BIT, value); }\r\n};\r\n\r\n// Base class for register states\r\nclass RegisterState\r\n{\r\npublic:\r\n RegisterState(E_FN_CODE type, uint16_t address, uint16_t value = 0)\r\n : type(type), address(address), value(value),\r\n priority(PRIORITY_MEDIUM) {}\r\n\r\n E_FN_CODE type;\r\n uint16_t address;\r\n uint16_t value;\r\n uint8_t priority;\r\n\r\n bool getBoolValue() const\r\n {\r\n return (type == E_FN_CODE::FN_READ_COIL || type == E_FN_CODE::FN_READ_DISCR_INPUT) && value != 0;\r\n }\r\n\r\n void setBoolValue(bool boolValue)\r\n {\r\n // Only applicable for Coil types (writeable)\r\n if (type == E_FN_CODE::FN_WRITE_COIL)\r\n {\r\n value = boolValue ? 1 : 0; // Simplified - Modbus uses 0xFF00/0x0000 but internal might use 1/0\r\n }\r\n }\r\n\r\n // Read value from device\r\n MB_Error readFromDevice(ModbusRTU &manager, uint8_t slaveId);\r\n\r\n // Write value to device\r\n MB_Error writeToDevice(ModbusRTU &manager, uint8_t slaveId, bool forceWrite = false);\r\n\r\n // Print the current state\r\n void printState(ModbusRTU &manager, uint8_t slaveId);\r\n};\r\n\r\nclass RTU_Base : public Component\r\n{\r\npublic:\r\n // Device state enum\r\n typedef enum\r\n {\r\n UNINITIALIZED,\r\n INITIALIZING,\r\n IDLE,\r\n RUNNING,\r\n ERROR\r\n } E_DeviceState;\r\n\r\n // Constructor with device identification & owner\r\n RTU_Base(Component* owner, uint8_t _slaveId = 1, short _componentId = 1000) \r\n : Component(\"RTU_Base_Device\", _componentId, COMPONENT_DEFAULT, owner),\r\n slaveId(_slaveId),\r\n state(UNINITIALIZED),\r\n errorCount(0),\r\n lastResponseTime(0),\r\n responseTimeout(5000),\r\n lastSyncTime(0),\r\n syncInterval(10),\r\n componentId(_componentId),\r\n registerCount(0),\r\n lastErrorCode(0),\r\n mandatoryReadBlocks(readBlockStorage)\r\n {\r\n // Initialize name more specifically if desired, e.g., after this->id is set by Component base\r\n // For now, Component base takes care of ID and a generic name if not overridden.\r\n // If a more specific name like \"RTU_Device_sidXX_cidYY\" is needed, do it after base construction.\r\n for (int i = 0; i < MAX_REGISTERS; ++i)\r\n registers[i] = nullptr;\r\n }\r\n\r\n virtual ~RTU_Base()\r\n {\r\n // Clean up register objects\r\n for (int i = 0; i < registerCount; i++)\r\n {\r\n if (registers[i] != nullptr)\r\n {\r\n delete registers[i];\r\n registers[i] = nullptr;\r\n }\r\n }\r\n }\r\n\r\n // Timing control\r\n unsigned long lastResponseTime;\r\n unsigned long responseTimeout;\r\n unsigned long lastSyncTime;\r\n unsigned long syncInterval;\r\n\r\n // Error tracking\r\n unsigned int errorCount;\r\n\r\n // Device identification\r\n uint8_t slaveId;\r\n\r\n // Component mapping\r\n uint16_t componentId;\r\n\r\n // State tracking\r\n E_DeviceState state;\r\n\r\n RegisterState *registers[MAX_REGISTERS];\r\n int registerCount;\r\n virtual const char *getStateString() const;\r\n\r\n virtual void setState(E_DeviceState newState);\r\n\r\n void handleResponseReceived();\r\n\r\n bool addInputRegister(uint16_t address, E_FN_CODE type, uint8_t priority)\r\n {\r\n if (registerCount >= MAX_REGISTERS)\r\n {\r\n Log.warningln(\"Max total registers (%d) reached for device %d\", MAX_REGISTERS, slaveId);\r\n return false;\r\n }\r\n // Use appropriate E_FN_CODE for input register (assuming FN_READ_INPUT_REGISTER)\r\n RegisterState *reg = new RegisterState(type, address, 0);\r\n reg->priority = priority;\r\n reg->type = type;\r\n registers[registerCount++] = reg;\r\n Log.traceln(\"Added input register addr=%d, priority=%d to device %d\", address, priority, slaveId);\r\n return true;\r\n }\r\n\r\n // Add an output register using MAX_REGISTERS from constants.h\r\n bool addOutputRegister(uint16_t address, E_FN_CODE type, uint16_t defaultValue, uint8_t priority)\r\n {\r\n if (registerCount >= MAX_REGISTERS)\r\n {\r\n Log.warningln(\"Max total registers (%d) reached for device %d\", MAX_REGISTERS, slaveId);\r\n return false;\r\n }\r\n // Use appropriate E_FN_CODE for holding register (assuming FN_WRITE_HOLD_REGISTER)\r\n RegisterState *reg = new RegisterState(type, address, defaultValue);\r\n reg->priority = priority;\r\n registers[registerCount++] = reg;\r\n Log.traceln(\"Added output register addr=%d, default=%d, priority=%d to device %d\", address, defaultValue, priority, slaveId);\r\n return true;\r\n }\r\n\r\n // Update device state based on Modbus operations\r\n void updateState(ModbusRTU &manager);\r\n\r\n // Initialize state by writing output values to the device\r\n bool initialize(ModbusRTU &manager);\r\n\r\n // Read readable registers from device\r\n virtual void read(ModbusRTU &manager);\r\n\r\n // Get a pointer to a register by its address (const version)\r\n const RegisterState *getRegisterByAddress(uint16_t address) const;\r\n\r\n // Set a specific output register value by ADDRESS\r\n void setOutputRegisterValue(uint16_t address, uint16_t value);\r\n\r\n virtual void onRegisterUpdate(uint16_t address, uint16_t newValue);\r\n\r\n virtual void onError(ushort errorCode, const char *errorMessage);\r\n ushort mb_tcp_error(MB_Registers *reg) { return lastErrorCode; }\r\n\r\n // Write writable registers to the device (queues writes based on local state)\r\n virtual void write(ModbusRTU &manager);\r\n\r\n // Print current state values\r\n void printState();\r\n\r\n // Reset state\r\n void reset();\r\n\r\n // Run a test sequence (Declaration might be removed if implementation is gone)\r\n // void runTestSequence(ModbusRTU& manager); // Keep commented/removed if impl is gone\r\n\r\n // --- Modbus TCP Mapping Support ---\r\n /**\r\n * @brief Gets the base Modbus TCP address allocated for this RTU device instance.\r\n *\r\n * This is the starting address in the TCP address space from which the\r\n * device's own TCP register offsets are calculated.\r\n *\r\n * @return The base TCP address for this device instance.\r\n * Returns 0 if TCP mapping is not applicable or configured.\r\n */\r\n virtual uint16_t mb_tcp_base_address() const { return 0; } // Default: No TCP mapping\r\n\r\n /**\r\n * @brief Calculates the Modbus TCP offset corresponding to a given RTU address update.\r\n *\r\n * This function is intended to map an address from the RTU bus (which triggered an update)\r\n * back to the corresponding offset within the device's allocated TCP address block.\r\n *\r\n * @param rtuAddress The RTU register address that was updated.\r\n * @return The corresponding TCP offset (relative to mb_tcp_base_address()), or 0 if no direct mapping exists for broadcast.\r\n */\r\n virtual uint16_t mb_tcp_offset_for_rtu_address(uint16_t rtuAddress) const { return 0; } // Default: No mapping\r\n\r\n // --- End Modbus TCP Mapping Support ---\r\n\r\n // Add a mandatory read block definition\r\n ModbusReadBlock *addMandatoryReadBlock(uint16_t startAddress, uint16_t count, E_FN_CODE type, unsigned long interval = 1000);\r\n\r\n ushort lastErrorCode;\r\n \r\n bool triggerRTUWrite();\r\n \r\nprotected:\r\n\r\nprivate:\r\n // Storage for the mandatory read blocks Vector\r\n ModbusReadBlock readBlockStorage[MAX_READ_BLOCKS];\r\n // Vector to hold mandatory read block definitions\r\n Vector mandatoryReadBlocks;\r\n};\r\n\r\n// Callback type for checking if operation already exists\r\ntypedef bool (*OperationExistsCallback)(const ModbusOperation &op, void *context);\r\n\r\n// Filter that removes duplicate operations\r\nclass DuplicateOperationFilter : public ModbusOperationFilter\r\n{\r\npublic:\r\n // Update constructor to take ModbusRTU reference\r\n DuplicateOperationFilter(ModbusRTU *rtu);\r\n ~DuplicateOperationFilter();\r\n\r\n bool filter(const ModbusOperation &op) override;\r\n\r\n // These methods aren't needed anymore as we'll directly check the queues\r\n void notifyOperationExecuted(const ModbusOperation &op) override {}\r\n void notifyOperationCompleted(const ModbusOperation &op) override {}\r\n\r\n // Get the filter type\r\n E_FilterType getType() const override { return FILTER_DUPLICATE; }\r\n\r\nprivate:\r\n // Check if operation is already in the ModbusRTU queues\r\n bool isOperationAlreadyPending(const ModbusOperation &op) const;\r\n\r\n // Reference to ModbusRTU for queue access\r\n ModbusRTU *modbusRTU;\r\n\r\n // Timeout for considering operations as duplicates\r\n unsigned long operationTimeout;\r\n};\r\n\r\n// Filter that limits the rate of operations\r\nclass RateLimitFilter : public ModbusOperationFilter\r\n{\r\npublic:\r\n RateLimitFilter(unsigned long minIntervalMs = 100);\r\n\r\n bool filter(const ModbusOperation &op) override;\r\n\r\n // Get the filter type\r\n E_FilterType getType() const override { return FILTER_RATE_LIMIT; }\r\n\r\nprivate:\r\n unsigned long minInterval;\r\n unsigned long lastOperationTime;\r\n};\r\n\r\n// Filter that prioritizes operations based on type or address\r\nclass PriorityFilter : public ModbusOperationFilter\r\n{\r\npublic:\r\n PriorityFilter();\r\n\r\n bool filter(const ModbusOperation &op) override;\r\n\r\n // Get the filter type\r\n E_FilterType getType() const override { return FILTER_PRIORITY; }\r\n\r\n // Adjusts the priority based on operation properties\r\n // Returns true to continue processing, false to abort\r\n // This doesn't change filtering, but could modify the op\r\n bool adjustPriority(ModbusOperation &op);\r\n};\r\n\r\n// Filter that handles operation lifecycle (expiration, retries)\r\nclass OperationLifecycleFilter : public ModbusOperationFilter\r\n{\r\npublic:\r\n OperationLifecycleFilter(unsigned long timeoutMs = OPERATION_TIMEOUT, uint8_t maxRetries = MAX_RETRIES)\r\n : timeout(timeoutMs), maxRetries(maxRetries) {}\r\n\r\n bool filter(const ModbusOperation &op) override\r\n {\r\n if (op.retries >= maxRetries)\r\n {\r\n return false;\r\n }\r\n\r\n unsigned long now = millis();\r\n if (now - op.timestamp > timeout)\r\n {\r\n return false;\r\n }\r\n\r\n return true; // Let operation through\r\n }\r\n\r\n E_FilterType getType() const override { return FILTER_LIFECYCLE; }\r\n\r\nprivate:\r\n unsigned long timeout;\r\n uint8_t maxRetries;\r\n};\r\n\r\n#endif // MODBUS_TYPES_H\r\n" }, { "role": "user", - "path": "src/App.cpp", - "content": "#include \n#include \n\n#include \"App.h\"\n#include \"Bridge.h\"\n#include \"error_codes.h\"\n#include \"enums.h\"\n#include \"constants.h\"\n#include \"config.h\"\n\nstatic Component *componentsArray[MAX_COMPONENTS];\n\nApp::App() : Component(\"APP\", COMPONENT_KEY_APP, Component::COMPONENT_DEFAULT)\n{\n DEBUG_INTERVAL = DEFAULT_DEBUG_INTERVAL;\n components.setStorage(componentsArray);\n debugTS = 0;\n}\n////////////////////////////////////////////////\n//\n// Component related functions\n//\nshort App::onRun()\n{\n short s = components.size();\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (!component)\n {\n Log.errorln(F(\"App::onRun - Found NULL component at index %d\"), i);\n continue;\n }\n if (!component->owner)\n {\n component->owner = this;\n }\n component->onRun();\n }\n return E_OK;\n}\n\nshort App::setup()\n{\n short s = components.size();\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (!component)\n {\n Log.errorln(F(\"App::setup - Found NULL component at index %d\"), i);\n continue;\n }\n if (!component->owner)\n {\n component->owner = this;\n }\n if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_SETUP))\n {\n component->setup();\n }\n else\n {\n Log.verboseln(F(\"App::setup - Skipping setup() for component (no flag): ID=%d, Name=%s\"), component->id, component->name.c_str());\n }\n }\n return E_OK;\n}\n\nshort App::registerComponents(Bridge *bridge)\n{\n short s = components.size();\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_DISABLED))\n {\n continue;\n }\n component->serial_register(bridge);\n }\n return E_OK;\n}\n\nshort App::loop()\n{\n Component::loop();\n now = millis();\n short s = components.size();\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_LOOP))\n {\n component->now = now;\n component->loop();\n }\n }\n debug();\n return E_OK;\n}\n\nshort App::numByFlag(ushort flag)\n{\n short s = components.size();\n short l = 0;\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (!!(component->hasFlag(flag)))\n {\n l++;\n }\n }\n return l;\n}\n\nshort App::setDebugParams(short val0, short val1)\n{\n DEBUG_INTERVAL = val0;\n return E_OK;\n}\n\nshort App::debug()\n{\n if (millis() - debugTS < DEBUG_INTERVAL)\n {\n return E_OK;\n }\n\n debugTS = millis();\n short s = components.size();\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_DEBUG))\n {\n component->debug();\n }\n }\n return E_OK;\n}\n\nshort App::info()\n{\n short s = components.size();\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (component->hasFlag(OBJECT_RUN_FLAGS::E_OF_INFO))\n {\n component->info();\n }\n }\n return E_OK;\n}\n\nComponent *App::byId(ushort id)\n{\n short s = components.size();\n for (short i = 0; i < s; i++)\n {\n Component *component = components[i];\n if (component->id == id)\n {\n return component;\n }\n }\n return NULL;\n}\n" + "path": "src/modbus/ModbusTypes.cpp", + "content": "#include \r\n#include \r\n#include \r\n#include \r\n\r\nbool RTU_Base::triggerRTUWrite()\r\n{\r\n RS485 *rs485 = (RS485 *)owner;\r\n if (rs485)\r\n {\r\n write(rs485->modbus);\r\n return true;\r\n }\r\n Log.errorln(\"Device %d: Failed to cast owner to RS485*! Cannot trigger RTU write.\", slaveId);\r\n return false;\r\n}\r\n\r\nMB_Error RegisterState::readFromDevice(ModbusRTU &manager, uint8_t slaveId)\r\n{\r\n switch (type)\r\n {\r\n case E_FN_CODE::FN_READ_INPUT_REGISTER:\r\n case E_FN_CODE::FN_READ_HOLD_REGISTER:\r\n return manager.readRegister(slaveId, address);\r\n case E_FN_CODE::FN_READ_COIL:\r\n case E_FN_CODE::FN_READ_DISCR_INPUT:\r\n return manager.readCoil(slaveId, address);\r\n }\r\n return MB_Error::UndefinedError;\r\n}\r\n\r\nMB_Error RegisterState::writeToDevice(ModbusRTU &manager, uint8_t slaveId, bool forceWrite)\r\n{\r\n switch (type)\r\n {\r\n case E_FN_CODE::FN_WRITE_HOLD_REGISTER:\r\n return manager.writeRegister(slaveId, address, value, forceWrite);\r\n case E_FN_CODE::FN_WRITE_COIL:\r\n return manager.writeCoil(slaveId, address, getBoolValue());\r\n case E_FN_CODE::FN_READ_INPUT_REGISTER:\r\n case E_FN_CODE::FN_READ_DISCR_INPUT:\r\n case E_FN_CODE::FN_READ_HOLD_REGISTER:\r\n case E_FN_CODE::FN_READ_COIL:\r\n return MB_Error::IllegalDataAddress;\r\n default:\r\n Log.warningln(\"Attempted writeToDevice with unhandled/read-only type: %X\", (int)type);\r\n return MB_Error::IllegalFunction;\r\n }\r\n return MB_Error::UndefinedError;\r\n}\r\n\r\nvoid RegisterState::printState(ModbusRTU &manager, uint8_t slaveId)\r\n{\r\n switch (type)\r\n {\r\n case E_FN_CODE::FN_READ_INPUT_REGISTER:\r\n Log.noticeln(\"Input Register %d: %d (Priority: %d, %s)\",\r\n address, value, priority,\r\n manager.isRegisterSynchronized(slaveId, address) ? \"Synchronized\" : \"Not synchronized\");\r\n break;\r\n\r\n case E_FN_CODE::FN_READ_HOLD_REGISTER:\r\n Log.noticeln(\"Holding Register %d: %d (Priority: %d, %s)\",\r\n address, value, priority,\r\n manager.isRegisterSynchronized(slaveId, address) ? \"Synchronized\" : \"Not synchronized\");\r\n break;\r\n\r\n case E_FN_CODE::FN_READ_COIL:\r\n Log.noticeln(\"Coil %d: %s (Priority: %d, %s)\",\r\n address, getBoolValue() ? \"ON\" : \"OFF\", priority,\r\n manager.isCoilSynchronized(slaveId, address) ? \"Synchronized\" : \"Not synchronized\");\r\n break;\r\n\r\n case E_FN_CODE::FN_READ_DISCR_INPUT:\r\n Log.noticeln(\"Discrete Input %d: %s (Priority: %d, %s)\",\r\n address, getBoolValue() ? \"ON\" : \"OFF\", priority,\r\n manager.isCoilSynchronized(slaveId, address) ? \"Synchronized\" : \"Not synchronized\");\r\n break;\r\n\r\n default:\r\n Log.noticeln(\"Register Address %d: Value %d (Type: %X, Priority: %d, %s)\",\r\n address, value, (int)type, priority,\r\n manager.isRegisterSynchronized(slaveId, address) ? \"Synchronized\" : \"Not synchronized\");\r\n break;\r\n }\r\n}\r\n\r\nDuplicateOperationFilter::DuplicateOperationFilter(ModbusRTU *rtu)\r\n : modbusRTU(rtu), operationTimeout(OPERATION_TIMEOUT)\r\n{\r\n if (modbusRTU == nullptr)\r\n {\r\n Log.errorln(\"DuplicateOperationFilter created with null ModbusRTU pointer!\");\r\n }\r\n}\r\n\r\nDuplicateOperationFilter::~DuplicateOperationFilter()\r\n{\r\n // Nothing to clean up\r\n}\r\n\r\nbool DuplicateOperationFilter::filter(const ModbusOperation &op)\r\n{\r\n // Check if this operation is already pending in the ModbusRTU queues\r\n // Use the stored modbusRTU pointer\r\n if (modbusRTU && modbusRTU->isOperationAlreadyPending(op))\r\n {\r\n // Log.traceln(\"Filter: Dropping duplicate operation (slave: %d, type: %d, address: %d)\", op.slaveId, op.type, op.address);\r\n return false; // Filter out the operation\r\n }\r\n\r\n return true; // Let the operation through\r\n}\r\n\r\nRateLimitFilter::RateLimitFilter(unsigned long minIntervalMs)\r\n : minInterval(minIntervalMs), lastOperationTime(0) {}\r\n\r\nbool RateLimitFilter::filter(const ModbusOperation &op)\r\n{\r\n unsigned long now = millis();\r\n\r\n // Check if enough time has passed since the last operation\r\n if (now - lastOperationTime < minInterval)\r\n {\r\n Log.traceln(\"Filter: Rate limiting operation (slave: %d, type: %d, address: %d)\",\r\n op.slaveId, op.type, op.address);\r\n return false; // Filter out the operation\r\n }\r\n\r\n // Update the last operation time\r\n lastOperationTime = now;\r\n return true; // Let the operation through\r\n}\r\n\r\nPriorityFilter::PriorityFilter() {}\r\n\r\nbool PriorityFilter::filter(const ModbusOperation &op)\r\n{\r\n return true;\r\n}\r\n\r\nbool PriorityFilter::adjustPriority(ModbusOperation &op)\r\n{\r\n if (op.type == E_FN_CODE::FN_WRITE_COIL ||\r\n op.type == E_FN_CODE::FN_WRITE_HOLD_REGISTER ||\r\n op.type == E_FN_CODE::FN_WRITE_MULT_COILS ||\r\n op.type == E_FN_CODE::FN_WRITE_MULT_REGISTERS)\r\n {\r\n // Write operations are considered high priority\r\n // The actual priority value might be set elsewhere or this could return a priority level.\r\n // For now, returning true indicates it meets the high-priority condition.\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\nvoid RTU_Base::updateState(ModbusRTU &manager)\r\n{\r\n if (state != UNINITIALIZED && lastResponseTime > 0 && millis() - lastResponseTime > responseTimeout)\r\n {\r\n if (state != ERROR)\r\n {\r\n errorCount++;\r\n setState(ERROR);\r\n }\r\n return; // If timeout occurred, don't process further state changes here\r\n }\r\n bool hasPendingOps = manager.hasPendingOperations(slaveId);\r\n switch (state)\r\n {\r\n case UNINITIALIZED:\r\n break;\r\n case INITIALIZING:\r\n if (!hasPendingOps)\r\n {\r\n setState(IDLE);\r\n }\r\n break;\r\n case IDLE:\r\n if (hasPendingOps)\r\n {\r\n setState(RUNNING);\r\n }\r\n break;\r\n case RUNNING:\r\n if (!hasPendingOps)\r\n {\r\n setState(IDLE);\r\n }\r\n break;\r\n case ERROR:\r\n break;\r\n }\r\n}\r\n\r\nbool RTU_Base::initialize(ModbusRTU &manager)\r\n{\r\n if (state == UNINITIALIZED)\r\n {\r\n setState(INITIALIZING);\r\n write(manager);\r\n return true; // Return true indicating initialization was started\r\n }\r\n else\r\n {\r\n return false; // Return false as it wasn't in UNINITIALIZED state\r\n }\r\n}\r\n\r\nvoid RTU_Base::read(ModbusRTU &manager)\r\n{\r\n if (state == RUNNING)\r\n {\r\n return;\r\n }\r\n unsigned long currentTime = millis();\r\n bool readQueued = false;\r\n int numBlocks = mandatoryReadBlocks.size();\r\n for (int i = 0; i < numBlocks; ++i)\r\n {\r\n ModbusReadBlock &block = mandatoryReadBlocks[i];\r\n\r\n if (!block.isUsed())\r\n {\r\n continue;\r\n }\r\n\r\n if (currentTime - block.lastReadTime >= block.readInterval)\r\n {\r\n MB_Error err = MB_Error::Success;\r\n switch (block.type)\r\n {\r\n case E_FN_CODE::FN_READ_HOLD_REGISTER:\r\n err = manager.readHoldingRegisters(slaveId, block.startAddress, block.count);\r\n break;\r\n case E_FN_CODE::FN_READ_INPUT_REGISTER:\r\n err = manager.readInputRegisters(slaveId, block.startAddress, block.count);\r\n break;\r\n case E_FN_CODE::FN_READ_COIL:\r\n err = manager.readCoils(slaveId, block.startAddress, block.count);\r\n break;\r\n case E_FN_CODE::FN_READ_DISCR_INPUT:\r\n err = manager.readDiscreteInputs(slaveId, block.startAddress, block.count);\r\n break;\r\n default:\r\n Log.warningln(\"Device %d: Mandatory read block has invalid type (%X). Skipping.\", slaveId, (int)block.type);\r\n err = MB_Error::IllegalFunction; // Mark as error to prevent state change if this was the only block\r\n break;\r\n }\r\n\r\n if (err == MB_Error::Success)\r\n {\r\n readQueued = true;\r\n block.lastReadTime = currentTime;\r\n }\r\n else\r\n {\r\n if (err != MB_Error::OpNotReady)\r\n {\r\n Log.warningln(\"Device %d: Read queue is full. Skipping read for block - Start: %d, Count: %d.\",\r\n slaveId, block.startAddress, block.count);\r\n }\r\n }\r\n }\r\n }\r\n if (readQueued && state == IDLE)\r\n {\r\n setState(RUNNING);\r\n }\r\n}\r\n\r\nvoid RTU_Base::write(ModbusRTU &manager)\r\n{\r\n if (state == RUNNING)\r\n {\r\n // Log.infoln(\"Device %d: Write operation already in progress. Skipping.\", slaveId);\r\n // return;\r\n }\r\n bool writeQueued = false;\r\n for (int i = 0; i < registerCount; i++)\r\n {\r\n if (registers[i] != nullptr)\r\n {\r\n if (registers[i]->type == E_FN_CODE::FN_WRITE_HOLD_REGISTER ||\r\n registers[i]->type == E_FN_CODE::FN_WRITE_COIL)\r\n {\r\n MB_Error err = registers[i]->writeToDevice(manager, slaveId, registers[i]->priority == PRIORITY_HIGHEST);\r\n if (err == MB_Error::Success)\r\n {\r\n // Log.traceln(\"Device %d: Write operation queued for register %d (addr %d) | Value: %d.\", slaveId, i, registers[i]->address, registers[i]->value);\r\n writeQueued = true;\r\n }\r\n else\r\n {\r\n Log.warningln(\"Device %d: Failed to queue write for register %d (addr %d). Error: %u\",\r\n slaveId, i, registers[i]->address, (unsigned int)err);\r\n }\r\n }\r\n }\r\n else\r\n {\r\n Log.errorln(\"Device %d: Register %d (addr %d) is not writable.\", slaveId, i, registers[i]->address);\r\n }\r\n }\r\n if (writeQueued && state == IDLE)\r\n {\r\n setState(RUNNING);\r\n }\r\n}\r\n\r\nvoid RTU_Base::printState()\r\n{\r\n for (int i = 0; i < registerCount; i++)\r\n {\r\n if (registers[i] != nullptr)\r\n {\r\n Log.traceln(\"Register %d: Type %d, Address %d, Value %d\", i, registers[i]->type, registers[i]->address, registers[i]->value);\r\n }\r\n }\r\n}\r\n\r\nvoid RTU_Base::reset()\r\n{\r\n Log.noticeln(\"Device %d: Resetting state...\", slaveId);\r\n setState(UNINITIALIZED);\r\n lastResponseTime = 0;\r\n errorCount = 0;\r\n lastSyncTime = 0;\r\n}\r\n\r\nconst char *RTU_Base::getStateString() const\r\n{\r\n switch (state)\r\n {\r\n case UNINITIALIZED:\r\n return \"Uninitialized\";\r\n case INITIALIZING:\r\n return \"Initializing\";\r\n case IDLE:\r\n return \"Idle\";\r\n case RUNNING:\r\n return \"Running\";\r\n case ERROR:\r\n return \"Error\";\r\n default:\r\n return \"Unknown\";\r\n }\r\n}\r\n\r\nvoid RTU_Base::setState(E_DeviceState newState)\r\n{\r\n if (state != newState)\r\n {\r\n E_DeviceState oldState = state;\r\n const char *oldStateStr = getStateString();\r\n state = newState;\r\n if ((oldState == ERROR) && (newState == IDLE || newState == RUNNING))\r\n {\r\n if (errorCount > 0)\r\n {\r\n errorCount = 0;\r\n }\r\n }\r\n }\r\n}\r\n\r\nvoid RTU_Base::handleResponseReceived()\r\n{\r\n lastResponseTime = millis();\r\n if (state == ERROR)\r\n {\r\n setState(IDLE);\r\n }\r\n else if (state == IDLE || state == RUNNING || state == INITIALIZING)\r\n {\r\n // Log.traceln(\"Device %d received response in state %s.\", slaveId, getStateString());\r\n // Log.traceln(\"Device %d received response in state %s. = %d\", slaveId, getStateString());\r\n // printState();\r\n }\r\n}\r\n\r\nconst RegisterState *RTU_Base::getRegisterByAddress(uint16_t address) const\r\n{\r\n for (int i = 0; i < registerCount; i++)\r\n {\r\n if (registers[i] != nullptr && registers[i]->address == address)\r\n {\r\n return registers[i];\r\n }\r\n }\r\n return nullptr;\r\n}\r\n\r\nvoid RTU_Base::setOutputRegisterValue(uint16_t address, uint16_t value)\r\n{\r\n bool found = false;\r\n for (int i = 0; i < registerCount; i++)\r\n {\r\n if (registers[i] != nullptr && registers[i]->address == address)\r\n {\r\n if (registers[i]->type == E_FN_CODE::FN_WRITE_HOLD_REGISTER || registers[i]->type == E_FN_CODE::FN_WRITE_COIL)\r\n {\r\n registers[i]->value = value;\r\n found = true;\r\n break;\r\n }\r\n else\r\n {\r\n Log.warningln(\"Device %d: Register with address %d found, but cannot set value - not a Holding/Coil type (%d).\",\r\n slaveId, address, registers[i]->type);\r\n found = true;\r\n break;\r\n }\r\n }\r\n }\r\n if (!found)\r\n {\r\n Log.warningln(\"Device %d: Writable register with address %d not found for setting value.\", slaveId, address);\r\n }\r\n else\r\n {\r\n triggerRTUWrite();\r\n }\r\n}\r\n\r\nModbusReadBlock *RTU_Base::addMandatoryReadBlock(uint16_t startAddress, uint16_t count, E_FN_CODE type, unsigned long interval)\r\n{\r\n if (count == 0)\r\n {\r\n Log.warningln(\"Device %d: Cannot add mandatory read block with count 0.\", slaveId);\r\n return nullptr;\r\n }\r\n if (type != E_FN_CODE::FN_READ_HOLD_REGISTER &&\r\n type != E_FN_CODE::FN_READ_INPUT_REGISTER &&\r\n type != E_FN_CODE::FN_READ_COIL &&\r\n type != E_FN_CODE::FN_READ_DISCR_INPUT)\r\n {\r\n Log.warningln(\"Device %d: Invalid type (%X) for mandatory read block.\", slaveId, (int)type);\r\n return nullptr;\r\n }\r\n if (mandatoryReadBlocks.full())\r\n {\r\n Log.errorln(\"Device %d: Cannot add mandatory read block - maximum (%d) reached.\", slaveId, MAX_READ_BLOCKS);\r\n return nullptr;\r\n }\r\n ModbusReadBlock newBlock(startAddress, count, type, interval);\r\n mandatoryReadBlocks.push_back(newBlock);\r\n Log.traceln(\"Device %d: Added mandatory read block - Start: %d, Count: %d, Type: %X, Interval: %lu ms\",\r\n slaveId, startAddress, count, (int)type, interval);\r\n\r\n newBlock.setUsed(true);\r\n return &mandatoryReadBlocks.back();\r\n}\r\nvoid RTU_Base::onError(ushort errorCode, const char *errorMessage)\r\n{\r\n lastErrorCode = errorCode;\r\n}\r\nvoid RTU_Base::onRegisterUpdate(uint16_t address, uint16_t newValue)\r\n{\r\n bool updated = false;\r\n for (int i = 0; i < registerCount; ++i)\r\n {\r\n if (registers[i] != nullptr && registers[i]->address == address)\r\n {\r\n if (registers[i]->value != newValue)\r\n {\r\n registers[i]->value = newValue;\r\n }\r\n updated = true;\r\n break;\r\n }\r\n }\r\n}\r\nconst char *modbusErrorToString(MB_Error error)\r\n{\r\n switch (error)\r\n {\r\n // Standard Modbus Exceptions\r\n case MB_Error::Success:\r\n return \"Success\";\r\n case MB_Error::IllegalFunction:\r\n return \"Illegal Function\";\r\n case MB_Error::IllegalDataAddress:\r\n return \"Illegal Data Address\";\r\n case MB_Error::IllegalDataValue:\r\n return \"Illegal Data Value\";\r\n case MB_Error::ServerDeviceFailure:\r\n return \"Server Device Failure\";\r\n case MB_Error::Acknowledge:\r\n return \"Acknowledge\";\r\n case MB_Error::ServerDeviceBusy:\r\n return \"Server Device Busy\";\r\n case MB_Error::NegativeAcknowledge:\r\n return \"Negative Acknowledge\";\r\n case MB_Error::MemoryParityError:\r\n return \"Memory Parity Error\";\r\n case MB_Error::GatewayPathUnavailable:\r\n return \"Gateway Path Unavailable\";\r\n case MB_Error::GatewayTargetNoResp:\r\n return \"Gateway Target Device Failed to Respond\";\r\n\r\n // Internal Operation/Queue Errors\r\n case MB_Error::OpNotReady:\r\n return \"Operation Not Ready\";\r\n case MB_Error::OpQueueFull:\r\n return \"ModbusRTU Operation Queue Full\";\r\n case MB_Error::OpClientQueueFull:\r\n return \"eModbus Client Queue Full\";\r\n case MB_Error::OpExecutionFailed:\r\n return \"Operation Execution Failed\";\r\n case MB_Error::OpInvalidParameter:\r\n return \"Invalid Parameter\";\r\n case MB_Error::OpRetrying:\r\n return \"Operation Retrying\";\r\n case MB_Error::OpMaxRetriesExceeded:\r\n return \"Max Retries Exceeded\";\r\n\r\n // eModbus Specific Communication Errors\r\n case MB_Error::Timeout:\r\n return \"Timeout\";\r\n case MB_Error::InvalidServer:\r\n return \"Invalid Server Response\";\r\n case MB_Error::CrcError:\r\n return \"CRC Error\"; // RTU\r\n case MB_Error::FcMismatch:\r\n return \"Function Code Mismatch\";\r\n case MB_Error::ServerIdMismatch:\r\n return \"Server ID Mismatch\";\r\n case MB_Error::PacketLengthError:\r\n return \"Packet Length Error\";\r\n case MB_Error::ParameterCountError:\r\n return \"Parameter Count Error\";\r\n case MB_Error::ParameterLimitError:\r\n return \"Parameter Limit Error\";\r\n case MB_Error::RequestQueueFull:\r\n return \"eModbus Request Queue Full\"; // Same as OpClientQueueFull? Check enum vals\r\n case MB_Error::IllegalIpOrPort:\r\n return \"Illegal IP or Port\"; // TCP\r\n case MB_Error::IpConnectionFailed:\r\n return \"IP Connection Failed\"; // TCP\r\n case MB_Error::TcpHeadMismatch:\r\n return \"TCP Header Mismatch\"; // TCP\r\n case MB_Error::EmptyMessage:\r\n return \"Empty Message Received\";\r\n case MB_Error::AsciiFrameError:\r\n return \"ASCII Frame Error\"; // ASCII\r\n case MB_Error::AsciiCrcError:\r\n return \"ASCII LRC Error\"; // ASCII\r\n case MB_Error::AsciiInvalidChar:\r\n return \"ASCII Invalid Character\"; // ASCII\r\n case MB_Error::BroadcastError:\r\n return \"Broadcast Error\";\r\n\r\n case MB_Error::UndefinedError:\r\n default:\r\n return \"Undefined or Unknown Error\";\r\n }\r\n}" } ], "tools": [] diff --git a/docs-c/modbus/Modbus.md b/docs-c/modbus/Modbus.md new file mode 100644 index 00000000..3207f42c --- /dev/null +++ b/docs-c/modbus/Modbus.md @@ -0,0 +1,71 @@ +# Modbus + +**Path**: [src/modbus/Modbus.h](src/modbus/Modbus.h) + +**Revision History**: Initial documentation + +A header file providing standardized macros for initializing Modbus communication blocks in ESP-32 industrial applications. + +## Requirements + +- ESP-32 microcontroller +- PlatformIO development environment +- C++17 support + +## Provides + +- **INIT_MODBUS_BLOCK_TCP**: Macro for initializing Modbus blocks with TCP base address +- **INIT_MODBUS_BLOCK**: Macro for initializing standard Modbus blocks +- **LOW_WORD / HIGH_WORD**: Macros for handling 32-bit word operations + +## Features + +- Standardized initialization of Modbus communication blocks +- Automated address calculations based on specified offsets +- Consistent parameter setting for both TCP and standard interfaces +- Streamlined documentation and organization through group identifiers + +## Dependencies + +- [ModbusTypes.h](src/modbus/ModbusTypes.h) + +```mermaid +graph TD + Modbus[Modbus.h] --> ModbusTypes[ModbusTypes.h] +``` + +## Behaviour + +The Modbus header provides initialization macros that ensure consistent formatting and values when setting up Modbus blocks. These macros handle address calculations, function codes, and access permissions. + +```mermaid +graph TD + Start[Application code] --> UsesMacros[Uses INIT_MODBUS_BLOCK macros] + UsesMacros --> TCP{Is TCP?} + TCP -->|Yes| TCPMACRO[INIT_MODBUS_BLOCK_TCP] + TCP -->|No| STDMACRO[INIT_MODBUS_BLOCK] + TCPMACRO --> ModbusBlock[Initialized Modbus Block] + STDMACRO --> ModbusBlock +``` + +## TODOs + +### Performance + +- Consider pre-computing constant addresses at compile time where possible to reduce runtime calculations + +### Security + +- Add validation macros to ensure proper function code and access flag combinations +- Implement range checking for addresses to prevent potential buffer overflows + +### Compliance + +- Validate against Modbus specifications for both RTU and TCP implementations +- Ensure error handling complies with industrial standards + +### Recommendations + +- Use these macros consistently across the codebase to maintain uniformity +- Document the address range and offset enumerations carefully to avoid address conflicts +- Consider adding specific macros for different function types (read vs. write operations) \ No newline at end of file diff --git a/docs-c/modbus/ModbusRTU.md b/docs-c/modbus/ModbusRTU.md new file mode 100644 index 00000000..d03a54aa --- /dev/null +++ b/docs-c/modbus/ModbusRTU.md @@ -0,0 +1,126 @@ +--- +title: "ModbusRTU - Industrial Modbus-485 RTU Interface" +description: "A robust Modbus RTU interface for ESP32 industrial applications" +keywords: ["modbus", "modbus-rtu", "rs485", "industrial", "esp32", "communication"] +--- + +## ModbusRTU + +**Path**: [`src/modbus/ModbusRTU.h`](../../src/modbus/ModbusRTU.h) + +**Revision History**: Initial documentation + +ModbusRTU is a comprehensive class that provides a robust interface for Modbus RTU communication over RS-485. It is designed for industrial applications on ESP32, offering efficient queue management, adaptive timeouts, and extensive error handling capabilities. + +## REQUIREMENTS + +- Hardware: + - ESP32 microcontroller + - RS-485 transceiver (with DE/RE pin for direction control) + - Serial interface for communication + +- Software: + - Arduino framework + - ModbusClientRTU library + - HardwareSerial for serial communication + - ArduinoLog for logging + +## PROVIDES + +- **Enumerations**: + - `E_InitState`: Tracks initialization states (`INIT_NOT_STARTED`, `INIT_SERIAL_STARTED`, `INIT_CLIENT_STARTED`, `INIT_READY`, `INIT_FAILED`) + +- **Classes**: + - `ModbusRTU`: Main class for Modbus RTU communication + - `Manager`: Device management class for handling multiple Modbus slave devices + +## FEATURES + +- Non-blocking initialization and operation +- Flexible queue management with priority levels +- Read/write operations for coils and registers (single and multiple) +- Automatic value caching with synchronization status tracking +- Comprehensive error handling and retry mechanisms +- Adaptive minimum operation interval to adjust for network conditions +- Filter chain for operation validation and duplicate prevention +- Callback system for register changes, write completions, and error handling +- Status reporting and debugging utilities + +## DEPENDENCIES + +- [Arduino.h](https://www.arduino.cc/reference/en/) +- [ModbusClientRTU.h](https://github.com/eModbus/eModbus) +- [HardwareSerial.h](https://www.arduino.cc/reference/en/language/functions/communication/serial/) +- [ArduinoLog.h](https://github.com/thijse/Arduino-Log) +- [ModbusTypes.h](../../src/modbus/ModbusTypes.h) + +```mermaid +graph TD + ModbusRTU --> Arduino + ModbusRTU --> ModbusClientRTU + ModbusRTU --> HardwareSerial + ModbusRTU --> ArduinoLog + ModbusRTU --> ModbusTypes + Manager --> ArduinoLog + Manager --> ModbusTypes + Manager --> ModbusRTU +``` + +## BEHAVIOUR + +```mermaid +stateDiagram-v2 + [*] --> INIT_NOT_STARTED + INIT_NOT_STARTED --> INIT_SERIAL_STARTED: begin() + INIT_SERIAL_STARTED --> INIT_CLIENT_STARTED: Serial init delay elapsed + INIT_CLIENT_STARTED --> INIT_READY: Client init delay elapsed + INIT_READY --> [*]: end() + + INIT_NOT_STARTED --> INIT_FAILED: Error + INIT_SERIAL_STARTED --> INIT_FAILED: Error + INIT_CLIENT_STARTED --> INIT_FAILED: Error + + state Operations { + [*] --> Idle + Idle --> QueueOperation: Read/Write request + QueueOperation --> FilterOperation: Apply filters + FilterOperation --> ExecuteOperation: If passed filters + FilterOperation --> Idle: If rejected + ExecuteOperation --> WaitForResponse: Operation sent + WaitForResponse --> ProcessResponse: Response received + WaitForResponse --> HandleError: Timeout/Error + ProcessResponse --> Idle: Update cache + HandleError --> Retry: If retries < max + HandleError --> Idle: If max retries exceeded + Retry --> ExecuteOperation: After backoff + } + + INIT_READY --> Operations: Ready for operations +``` + +## TODOS + +### PERFORMANCE + +- Consider buffer pooling for multiple register operations to reduce memory fragmentation +- Implement batch processing for adjacent registers to reduce number of transactions +- Explore more granular timeouts for different device types or operation types + +### SECURITY + +- Implement authentication mechanisms for secure Modbus communication +- Add validation for incoming data values to prevent exploitation +- Consider adding packet encryption for sensitive industrial applications + +### COMPLIANCE + +- Ensure full compliance with Modbus RTU protocol specification +- Add support for additional function codes to cover all Modbus operations +- Implement proper exception code handling according to the standard + +### RECOMMENDATIONS + +- Monitor operation success rates and adjust retry limits based on network reliability +- Configure adaptive timeouts based on device response characteristics +- Implement device-specific wrappers for common industrial equipment +- Use high priority flag for critical operations (safety or timing-sensitive commands) \ No newline at end of file diff --git a/docs-c/modbus/ModbusTCP.md b/docs-c/modbus/ModbusTCP.md new file mode 100644 index 00000000..34676de3 --- /dev/null +++ b/docs-c/modbus/ModbusTCP.md @@ -0,0 +1,134 @@ +--- +title: "ModbusTCP Class Documentation" +description: "Documentation for the ModbusTCP class that manages Modbus TCP communication in an industrial environment" +keywords: ["ESP32", "ModbusTCP", "Modbus Server", "Industrial", "Communication"] +--- + +## ModbusTCP + +**Path**: [`src/modbus/ModbusTCP.h`](../src/modbus/ModbusTCP.h) + +**Revision History**: Initial documentation + +The ModbusTCP class manages Modbus TCP communication, acting as a Server/Slave. It listens for Modbus TCP requests, finds the target component, and uses the Component's network interface (read/mb_tcp_write) to process the request. + +## REQUIREMENTS + +- ESP32 with TCP/IP connectivity +- ModbusServerTCPasync instance (from eModbus library) +- Component hierarchy with a parent owner component (typically PHApp) + +## PROVIDES + +- **ModbusTCP Class**: Main class for managing Modbus TCP server functionality +- **Address Mapping Functionality**: Maps Modbus addresses to component functions + +## FEATURES + +- Handles Modbus TCP slave/server functionality +- Supports standard Modbus function codes: + - Read Coils (01) + - Read Holding Registers (03) + - Write Single Coil (05) + - Write Single Register (06) + - Write Multiple Coils (15) + - Write Multiple Registers (16) +- Manages address mapping between Modbus addresses and components +- Routes Modbus requests to appropriate component handlers +- Provides component registration system for Modbus address ranges +- Maps internal errors to Modbus exception codes + +## DEPENDENCIES + +- [Component.h](../src/core/Component.h) +- [enums.h](../src/util/enums.h) +- [config-modbus.h](../src/config/config-modbus.h) +- [ArduinoLog.h](../lib/ArduinoLog/ArduinoLog.h) +- [Vector.h](../lib/Vector/Vector.h) +- [ModbusServerTCPasync.h](../lib/eModbus/ModbusServerTCPasync.h) +- [ModbusTypes.h](../src/modbus/ModbusTypes.h) + +```mermaid +graph TD + ModbusTCP --> Component + ModbusTCP --> enums + ModbusTCP --> ModbusTypes + ModbusTCP --> ModbusServerTCPasync + ModbusTCP --> Vector + ModbusTCP --> ArduinoLog + ModbusTCP --> configmodbus[config-modbus.h] +``` + +## BEHAVIOUR + +```mermaid +sequenceDiagram + participant Client as Modbus TCP Client + participant Server as ModbusTCP Server + participant Component as Component + + Client->>Server: TCP Connection + Client->>Server: Modbus Request (Function Code + Address) + Server->>Server: Find component for address + alt Component found + Server->>Component: mb_tcp_read() or mb_tcp_write() + Component->>Server: Return data/status + Server->>Client: Modbus Response with data + else No component or mapping found + Server->>Client: Exception Response + end +``` + +## EXAMPLE + +```cpp +#include "ModbusTCP.h" + +// Create ModbusServerTCPasync instance +ModbusServerTCPasync mbTCPServer; + +// Create and initialize ModbusTCP +ModbusTCP modbusTCP(appComponent, &mbTCPServer); + +// Register component with Modbus address block +MB_Registers tempSensorRegisters = { + .startAddress = 1000, + .count = 10, + .type = FN_READ_HOLD_REGISTER, + .access = MB_ACCESS_READ_WRITE, + .componentId = 0 // Will be filled by ModbusTCP +}; +modbusTCP.registerModbus(temperatureComponent, tempSensorRegisters); + +// Setup and start the Modbus TCP manager +modbusTCP.setup(); + +// Call this regularly in the main loop +modbusTCP.loop(); +``` + +## TODOS + +### PERFORMANCE + +- Consider implementing a more efficient lookup method for address mappings (e.g., hash map) for projects with many components +- Add optional caching for frequently read values to reduce component calls +- Optimize byte packing/unpacking for multi-coil operations + +### SECURITY + +- Add IP filtering capability to restrict access to authorized clients +- Implement authentication mechanism for Modbus TCP connections +- Consider adding encrypted Modbus TCP support for sensitive applications + +### COMPLIANCE + +- Ensure full compliance with Modbus Application Protocol Specification V1.1b3 +- Add support for other function codes (Read Input Registers, Read Discrete Inputs) if needed + +### RECOMMENDATIONS + +- Create unit tests to validate mapping and routing functionality +- Maintain detailed logs of Modbus communications for debugging +- Implement a strict mode that returns exceptions for unmapped addresses (currently configurable) +- Consider adding diagnostic counters for monitoring communication quality \ No newline at end of file diff --git a/docs-c/modbus/ModbusTypes.md b/docs-c/modbus/ModbusTypes.md new file mode 100644 index 00000000..31d5dc37 --- /dev/null +++ b/docs-c/modbus/ModbusTypes.md @@ -0,0 +1,131 @@ +--- +title: "ModbusTypes Reference" +description: "Comprehensive reference for the ModbusTypes component used in Modbus RS-485 industrial applications" +keywords: ["modbus", "industrial", "esp32", "rs485", "communication"] +--- + +# ModbusTypes + +**Path**: [`src/modbus/ModbusTypes.h`](../../../src/modbus/ModbusTypes.h) + +**Revision History**: Initial documentation + +A collection of enumerations, structures, and classes that define the foundational types used by the Modbus implementation. This component provides the core data types and interfaces for working with Modbus communication in industrial applications. + +## REQUIREMENTS + +- ESP32 microcontroller +- Platform.io development environment +- C++17 compatible compiler + +## PROVIDES + +- **Enumerations**: + - `E_FN_CODE`: Modbus function codes + - `E_MB_OpType`: Modbus operation types + - `E_MB_OpStatus`: Operation status + - `MB_Error`: Error codes + - `E_FilterType`: Filter types for operation filtering + - `E_ModbusAccess`: Access types (from config-modbus.h) + +- **Structures**: + - `MB_Registers`: Register definition structure + - `ModbusBlockView`: Non-owning view of register blocks + - `MB_UpdateData`: RTU update data + - `ModbusOperation`: Modbus operation structure + - `ModbusValueEntry`: Register or coil value entry + - `SlaveData`: Modbus slave data + - `ModbusReadBlock`: Mandatory read block definition + +- **Classes**: + - `ModbusOperationFilter`: Base class for operation filters + - `DuplicateOperationFilter`: Filter to remove duplicate operations + - `RateLimitFilter`: Filter to limit operation rates + - `PriorityFilter`: Filter for prioritizing operations + - `OperationLifecycleFilter`: Filter for managing operation lifecycle + - `RegisterState`: Class for register state management + - `RTU_Base`: Base class for RTU devices + +- **Callback Types**: + - `ResponseCallback`: Callback for slave responses + - `OnRegisterChangeCallback`: Callback for register changes + - `OnWriteCallback`: Callback for write operations + - `OnErrorCallback`: Callback for error handling + - `OperationExistsCallback`: Callback for checking if operation exists + +## FEATURES + +- Comprehensive Modbus function code support +- Error handling with detailed error codes +- Operation filtering system for queue management +- Register state management +- Device state management through RTU_Base +- Support for mandatory read blocks +- Priority-based operation scheduling +- Callback systems for events + +## DEPENDENCIES + +- [Arduino.h](https://www.arduino.cc/reference/en/) +- [ArduinoLog.h](https://github.com/thijse/Arduino-Log) +- [Component.h](../../../src/Component.h) +- [Vector.h](../../../src/Vector.h) +- [enums.h](../../../src/enums.h) +- [macros.h](../../../src/macros.h) +- [constants.h](../../../src/constants.h) +- [config-modbus.h](../../../src/config-modbus.h) + +```mermaid +graph TD + ModbusTypes --> ArduinoLog + ModbusTypes --> Component + ModbusTypes --> Vector + ModbusTypes --> macros + ModbusTypes --> constants + ModbusTypes --> config-modbus + ModbusTypes --> enums + ModbusRTU --> ModbusTypes + RTUDevices --> ModbusTypes + RS485 --> ModbusTypes +``` + +## BEHAVIOUR + +```mermaid +stateDiagram-v2 + [*] --> UNINITIALIZED + UNINITIALIZED --> INITIALIZING: initialize() + INITIALIZING --> IDLE: initialization complete + IDLE --> RUNNING: operations pending + RUNNING --> IDLE: operations complete + IDLE --> ERROR: timeout + RUNNING --> ERROR: timeout + ERROR --> IDLE: response received +``` + +## TODOS + +### PERFORMANCE + +- Consider more efficient data structures for queuing operations +- Optimize memory usage in filter chain implementations +- Implement operation batching for improved throughput + +### SECURITY + +- Add validation for input parameters to prevent buffer overflows +- Consider implementing authentication mechanisms for Modbus TCP +- Add checks for unauthorized register access + +### COMPLIANCE + +- Ensure full compliance with Modbus specification +- Review handling of exceptional responses +- Validate operation against industrial standards + +### RECOMMENDATIONS + +- Implement appropriate error handling strategies +- Set appropriate timeouts based on network characteristics +- Use the filter chain to customize operation processing +- Leverage the callback system for event-based programming \ No newline at end of file diff --git a/docs-c/src/LEDFeedback.cpp b/docs-c/src/LEDFeedback.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/scripts/docs-m.md b/scripts/docs-m.md new file mode 100644 index 00000000..72cf8b0d --- /dev/null +++ b/scripts/docs-m.md @@ -0,0 +1,70 @@ +# Context + +- ESP-32, Platform.io, C17 +- avoidance of std +- industrial application, using Modbus-485 + +## Instructions + +- Generate documentation for a given class, its types, enumerations, in a new Markdown file located at `./docs/modbus/.md`. +- The documentation is intended for an Astro static site generator. +- Include frontmatter in the Markdown file with `title`, `description`, and `keywords` fields. +- You will be provided with the content of the component's header file (e.g., `src/modbus/.h`). +- Ensure the generated Markdown adheres to standard linting rules. +- The generated Markdown document must follow the specific layout provided below. +- The component's C++ source file will contain an example of how it is constructed and mounted; use this as a basis for the "Example" section in the documentation. +- Do not comment or add thoughts, just output plain Markdown + +## Layout + +The Markdown document must adhere to the following structure: + +---------------------------- + +## COMPONENT NAME + +**Path**: `relative/path/to/component-name.cpp` (as a Markdown link) + +**Revision History**: Add a file revision entry in the header to track modifications. For the initial documentation, use "Initial documentation". Skip this if a revision history already exists ! + +A detailed description of the component + +## REQUIREMENTS + +Detail the hardware pins and software dependencies required by the component. + +## PROVIDES + +- a list of all types, enumerations and classes + +## FEATURES + +List the key features and functionalities of the component. + +## DEPENDENCIES + +- Provide a list of dependencies as Markdown formatted links. +- Include a minimal Mermaid diagram illustrating the dependencies. The diagram node names should not contain braces or brackets. + +## BEHAVIOUR + +- Include a minimal Mermaid diagram illustrating the component's behaviour or state machine. The diagram node names should not contain braces or brackets. + + +## TODOS + +### PERFORMANCE + +Outline any performance considerations or areas for future optimization. + +### SECURITY + +Describe potential security vulnerabilities or hardening measures. + +### COMPLIANCE + +Note any compliance standards or requirements relevant to the component. + +### RECOMMENDATIONS + +Provide any recommendations for using or extending the component. diff --git a/scripts/docs-m.sh b/scripts/docs-m.sh new file mode 100644 index 00000000..bb9e5af8 --- /dev/null +++ b/scripts/docs-m.sh @@ -0,0 +1,10 @@ +kbot-d --model=anthropic/claude-3.7-sonnet \ + --prompt=./scripts/docs-m.md \ + --each=./src/modbus/*.h \ + --globExtension=match-cpp \ + --mode=completion \ + --filters=markdown \ + --preferences=none \ + --exclude='./docs-c/modbus/Modbus.h' \ + --exclude='./docs-c/modbus/${SRC_NAME}.md' \ + --dst='./docs-c/modbus/${SRC_NAME}.md'