diff --git a/mb_override_component/CMakeLists.txt b/mb_override_component/CMakeLists.txt new file mode 100644 index 0000000..72f9e6f --- /dev/null +++ b/mb_override_component/CMakeLists.txt @@ -0,0 +1,9 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +#set(EXTRA_COMPONENT_DIRS "/home/esp-man/esp32/esp-modbus") +# override_path: + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(modbus_master) diff --git a/mb_override_component/README.md b/mb_override_component/README.md new file mode 100644 index 0000000..613409e --- /dev/null +++ b/mb_override_component/README.md @@ -0,0 +1,162 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | + +# Modbus Master Example + +Note: this is original example of modbus master which uses patched component esp-modbus dowloaded from component registry. +The patched component "patched_esp-modbus" is located in components folder and allows to override the files of original component downloaded by component manager. +These source files may incude additional or changed functionality on appropriate level of code and need to be on the same place and be named the same as original component files. +The folder with the files may include the header files with the same name which will be added into the public include path and then included from user application. +This may allow to share some overriden data with user application. This code cn be used to override any source code of original component and described approach can be used for slave implementation as well. + +This example demonstrates using of FreeModbus stack port implementation for ESP32 as a master device. +This implementation is able to read/write values of slave devices connected into Modbus segment. All parameters to be accessed are defined in data dictionary of the modbus master example source file. +The values represented as characteristics with its name and characteristic CID which are linked into registers of slave devices connected into Modbus segment. +The example implements simple control algorithm and checks parameters from slave device and gets alarm (relay in the slave device) when value of holding_data0 parameter exceeded limit. +The instances for the modbus parameters are common for master and slave examples and located in `examples/protocols/modbus/mb_example_common` folder. + +Example parameters definition: +-------------------------------------------------------------------------------------------------- +| Slave Address | Characteristic ID | Characteristic name | Description | +|---------------------|----------------------|----------------------|----------------------------| +| MB_DEVICE_ADDR1 | CID_INP_DATA_0, | Data_channel_0 | Data channel 1 | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_0, | Humidity_1 | Humidity 1 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_1 | Temperature_1 | Sensor temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_1, | Humidity_2 | Humidity 2 | +| MB_DEVICE_ADDR1 | CID_INP_DATA_2 | Temperature_2 | Ambient temperature | +| MB_DEVICE_ADDR1 | CID_HOLD_DATA_2 | Humidity_3 | Humidity 3 | +| MB_DEVICE_ADDR1 | CID_RELAY_P1 | RelayP1 | Alarm Relay outputs on/off | +| MB_DEVICE_ADDR1 | CID_RELAY_P2 | RelayP2 | Alarm Relay outputs on/off | +-------------------------------------------------------------------------------------------------- +Note: The Slave Address is the same for all parameters for example test but it can be changed in the ```Example Data (Object) Dictionary``` table of master example to address parameters from other slaves. +The Kconfig ```Modbus slave address``` - CONFIG_MB_SLAVE_ADDR parameter in slave example can be configured to create Modbus multi slave segment. + +Simplified Modbus connection schematic for example test: + ``` + MB_DEVICE_ADDR1 + ------------- ------------- + | | RS485 network | | + | Slave 1 |---<>--+---<>---| Master | + | | | | + ------------- ------------- +``` +Modbus multi slave segment connection schematic: +``` + MB_DEVICE_ADDR1 + ------------- + | | + | Slave 1 |---<>--+ + | | | + ------------- | + MB_DEVICE_ADDR2 | + ------------- | ------------- + | | | | | + | Slave 2 |---<>--+---<>---| Master | + | | | | | + ------------- | ------------- + MB_DEVICE_ADDR3 | + ------------- RS485 network + | | | + | Slave 3 |---<>--+ + | | + ------------- +``` + +## Hardware required : +Option 1: +PC (Modbus Slave app) + USB Serial adapter connected to USB port + RS485 line drivers + ESP32 based board + +Option 2: +Several ESP32 boards flashed with modbus_slave example software to represent slave device with specific slave address (See CONFIG_MB_SLAVE_ADDR). The slave addresses for each board have to be configured as defined in "connection schematic" above. +One ESP32 board flashed with modbus_master example. All the boards require connection of RS485 line drivers (see below). + +The MAX485 line driver is used as an example below but other similar chips can be used as well. +RS485 example circuit schematic for connection of master and slave devices into segment: +``` + VCC ---------------+ +--------------- VCC + | | + +-------x-------+ +-------x-------+ + RXD <------| RO | DIFFERENTIAL | RO|-----> RXD + | B|---------------|B | + TXD ------>| DI MAX485 | \ / | MAX485 DI|<----- TXD +ESP32 BOARD | | RS-485 side | | External PC (emulator) with USB to serial or + RTS --+--->| DE | / \ | DE|---+ ESP32 BOARD (slave) + | | A|---------------|A | | + +----| /RE | PAIR | /RE|---+-- RTS + +-------x-------+ +-------x-------+ + | | + --- --- + Modbus Master device Modbus Slave device + +``` + +## How to setup and use an example: + +### Configure the application +Start the command below to setup configuration: +``` +idf.py menuconfig +``` +Configure the UART pins used for modbus communication using and table below. +Define the communication mode parameter for master and slave in Kconfig - CONFIG_MB_COMM_MODE (must be the same for master and slave devices in one segment). +Configure the slave address for each slave in the Modbus segment (the CONFIG_MB_SLAVE_ADDR in Kconfig). +``` + ------------------------------------------------------------------------------------------------------------------------------ + | UART Interface | #define | Default pins for | Default pins for | External RS485 Driver Pin | + | | | ESP32 (C6) | ESP32-S2 (S3, C3, C2, H2) | | + | ----------------------|--------------------|-----------------------|---------------------------|---------------------------| + | Transmit Data (TxD) | CONFIG_MB_UART_TXD | GPIO23 | GPIO9 | DI | + | Receive Data (RxD) | CONFIG_MB_UART_RXD | GPIO22 | GPIO8 | RO | + | Request To Send (RTS) | CONFIG_MB_UART_RTS | GPIO18 | GPIO10 | ~RE/DE | + | Ground | n/a | GND | GND | GND | + ------------------------------------------------------------------------------------------------------------------------------ +``` +Note: Each target chip has different GPIO pins available for UART connection. Please refer to UART documentation for selected target for more information. + +Connect a USB-to-RS485 adapter to a computer, then connect the adapter's A/B output lines with the corresponding A/B output lines of the RS485 line driver connected to the ESP32 chip (see figure above). + +The communication parameters of Modbus stack allow to configure it appropriately but usually it is enough to use default settings. +See the help string of parameters for more information. + +### Setup external Modbus slave devices or emulator +Option 1: +Configure the external Modbus master software according to port configuration parameters used in the example. The Modbus Slave application can be used with this example to emulate slave devices with its parameters. Use official documentation for software to setup emulation of slave devices. + +Option 2: +Other option is to have the modbus_slave example application flashed into ESP32 based board and connect boards together as showed on the Modbus connection schematic above. See the Modbus slave API documentation to configure communication parameters and slave addresses as defined in "Example parameters definition" table above. + +### Build and flash software of master device +Build the project and flash it to the board, then run monitor tool to view serial output: +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +Example output of the application: +``` +I (9035) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (9045) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.539999 (0x40b147ac) read successful. +I (9045) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +I (9055) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful. +I (9065) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (9075) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (9085) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (9095) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = OFF (0xaa) read successful. +I (9605) MASTER_TEST: Characteristic #0 Data_channel_0 (Volts) value = 1.120000 (0x3f8f5c29) read successful. +I (9615) MASTER_TEST: Characteristic #1 Humidity_1 (%rH) value = 5.739999 (0x40b7ae12) read successful. +I (9615) MASTER_TEST: Characteristic #2 Temperature_1 (C) value = 2.340000 (0x4015c28f) read successful. +I (9625) MASTER_TEST: Characteristic #3 Humidity_2 (%rH) value = 2.560000 (0x4023d70a) read successful. +I (9635) MASTER_TEST: Characteristic #4 Temperature_2 (C) value = 3.560000 (0x4063d70a) read successful. +I (9645) MASTER_TEST: Characteristic #5 Humidity_3 (%rH) value = 3.780000 (0x4071eb85) read successful. +I (9655) MASTER_TEST: Characteristic #6 RelayP1 (on/off) value = OFF (0x55) read successful. +I (9665) MASTER_TEST: Characteristic #7 RelayP2 (on/off) value = ON (0xff) read successful. +I (10175) MASTER_TEST: Alarm triggered by cid #7. +I (10175) MASTER_TEST: Destroy master... + +``` +The example reads the characteristics from slave device(s), while alarm is not triggered in the slave device (See the "Example parameters definition"). The output line describes Timestamp, Cid of characteristic, Characteristic name (Units), Characteristic value (Hex). + diff --git a/mb_override_component/component.mk b/mb_override_component/component.mk new file mode 100644 index 0000000..14d428f --- /dev/null +++ b/mb_override_component/component.mk @@ -0,0 +1,26 @@ +INCLUDEDIRS := common/include +PRIV_INCLUDEDIRS := common port modbus modbus/ascii modbus/functions +PRIV_INCLUDEDIRS += modbus/rtu modbus/tcp modbus/include +PRIV_INCLUDEDIRS += serial_slave/port serial_slave/modbus_controller +PRIV_INCLUDEDIRS += serial_master/port serial_master/modbus_controller +PRIV_INCLUDEDIRS += tcp_slave/port tcp_slave/modbus_controller +PRIV_INCLUDEDIRS += tcp_master/port tcp_master/modbus_controller +SRCDIRS := common +SRCDIRS += modbus modbus/ascii modbus/functions modbus/rtu modbus/tcp +SRCDIRS += serial_slave/port serial_slave/modbus_controller +SRCDIRS += serial_master/port serial_master/modbus_controller +SRCDIRS += tcp_slave/port tcp_slave/modbus_controller +SRCDIRS += tcp_master/port tcp_master/modbus_controller +SRCDIRS += port + +COMPONENT_PRIV_INCLUDEDIRS = $(addprefix freemodbus/, \ + $(PRIV_INCLUDEDIRS) \ + ) + +COMPONENT_SRCDIRS = $(addprefix freemodbus/, \ + $(SRCDIRS) \ + ) + +COMPONENT_ADD_INCLUDEDIRS = $(addprefix freemodbus/, \ + $(INCLUDEDIRS) \ + ) diff --git a/mb_override_component/components/patched_esp-modbus/CMakeLists.txt b/mb_override_component/components/patched_esp-modbus/CMakeLists.txt new file mode 100644 index 0000000..5c045fd --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/CMakeLists.txt @@ -0,0 +1,84 @@ +# The following cmake file allows to patch the esp-modbus component files of original component. +# If the project needs to override any of the standard sources, the source +# file needs to be added into freemodbus folder to its place where it is located +# in original component. The patched file will be compiled instead of original one +# located in the official component freemodbus folder. + +set(MB_PATCH_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + +set(override_srcs "${MB_PATCH_DIR}/freemodbus/patch_lib.c") +set(comp_dir) +set(comp_name) +set(comp_kconfig) +set(optional_reqs freemodbus espressif__esp-modbus) +idf_build_get_property(build_components BUILD_COMPONENTS) + +foreach(req ${optional_reqs}) + if(req IN_LIST build_components) + idf_component_get_property(comp_lib ${req} COMPONENT_LIB) + idf_component_get_property(comp_srcs ${req} SRCS) + idf_component_get_property(comp_dir ${req} COMPONENT_DIR) + idf_component_get_property(comp_name ${req} COMPONENT_NAME) + idf_component_get_property(comp_kconfig ${req} KCONFIG) + message(STATUS "The library ${comp_lib}, name: [${comp_name}], path: ${comp_dir} is found and will be overriden by files from [${COMPONENT_NAME}] component.") + endif() +endforeach() + +set(include_dirs common/include) + +set(priv_include_dirs common port modbus modbus/ascii modbus/functions + modbus/rtu modbus/tcp modbus/include) + +if(EXISTS "${comp_dir}") + + list(APPEND priv_include_dirs serial_slave/port serial_slave/modbus_controller + serial_master/port serial_master/modbus_controller + tcp_slave/port tcp_slave/modbus_controller + tcp_master/port tcp_master/modbus_controller) + + add_prefix(include_dirs "${comp_dir}/freemodbus/" ${include_dirs}) + add_prefix(priv_include_dirs "${comp_dir}/freemodbus/" ${priv_include_dirs}) + + if(IS_DIRECTORY ${MB_PATCH_DIR}) + foreach(src ${comp_srcs}) + get_filename_component(src_wo_ext ${src} NAME_WE) + file(RELATIVE_PATH src_rel "${comp_dir}" "${src}") + get_filename_component(src_dir ${src_rel} DIRECTORY) + # message(STATUS "Source relative path:${src_rel} ") + if(EXISTS "${MB_PATCH_DIR}/${src_rel}") + message(STATUS "Override source: ${MB_PATCH_DIR}/${src_dir}/${src_wo_ext}.c") + list(APPEND override_srcs ${src_dir}/${src_wo_ext}.c) + # message(STATUS "header check: ${src_dir}/${src_wo_ext}.h") + if((EXISTS "${MB_PATCH_DIR}/${src_dir}/${src_wo_ext}.h") AND (NOT ${MB_PATCH_DIR}/src_dir IN_LIST priv_include_dirs)) + list(APPEND include_dirs "${MB_PATCH_DIR}/${src_dir}") + message(STATUS "Add override include: ${MB_PATCH_DIR}/${src_dir}/${src_wo_ext}.h ") + endif() + else() + if(NOT src_dir IN_LIST priv_include_dirs) + list(APPEND priv_include_dirs "${comp_dir}/${src_dir}") + endif() + list(APPEND override_srcs "${comp_dir}/${src_rel}") + endif() + endforeach() + endif() + + message(STATUS "DEBUG: Use esp-modbus component folder: ${CMAKE_CURRENT_LIST_DIR}.") +endif() + +set(requires driver lwip) + +# esp_timer component was introduced in v4.2 +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "4.1") + list(APPEND requires esp_timer) +endif() + +idf_component_register(SRCS "${override_srcs}" + INCLUDE_DIRS "${include_dirs}" + PRIV_INCLUDE_DIRS "${priv_include_dirs}" + KCONFIG "${comp_kconfig}" + REQUIRES "${requires}" + PRIV_REQUIRES esp_netif "${comp_name}") + +# This workaround allows to link this patched library into project in spite of availability of espressif_esp-modbus library +set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u mb_patch_lib_include") + diff --git a/mb_override_component/components/patched_esp-modbus/README.md b/mb_override_component/components/patched_esp-modbus/README.md new file mode 100644 index 0000000..8908311 --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/README.md @@ -0,0 +1,10 @@ +This component represents the patched component of esp-modbus located in "patched_esp-modbus" folder. + +How it works: + +The original component version selected by project idf_component.yml file will be dowloaded +by component manager but its sources will be overriden by sources from "patched_esp-modbus/freemodbus/" folder. +Only the files that exist in this folder will be overriden during compilation. All other sources of the original component +will be used unchanged. This technique allows to override the code of original component without changing +the official esp-modbus component files. The source files to be patched needs to be added into +the same place where they located in the original component. diff --git a/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding.c b/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding.c new file mode 100644 index 0000000..a1ed21b --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding.c @@ -0,0 +1,323 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (c) 2006 Christian Walter + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: mbfuncholding.c,v 1.12 2007/02/18 23:48:22 wolti Exp $ + */ + +/* ----------------------- System includes ----------------------------------*/ +#include "stdlib.h" +#include "string.h" + +/* ----------------------- Platform includes --------------------------------*/ +#include "port.h" + +/* ----------------------- Modbus includes ----------------------------------*/ +#include "mb.h" +#include "mbframe.h" +#include "mbproto.h" +#include "mbconfig.h" +#include "esp_log.h" + +#warning "This file is overriden in the component" + +/* ----------------------- Defines ------------------------------------------*/ +#define MB_PDU_FUNC_READ_ADDR_OFF ( MB_PDU_DATA_OFF + 0) +#define MB_PDU_FUNC_READ_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_READ_SIZE ( 4 ) +#define MB_PDU_FUNC_READ_REGCNT_MAX ( 0x007D ) + +#define MB_PDU_FUNC_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 0) +#define MB_PDU_FUNC_WRITE_VALUE_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_WRITE_SIZE ( 4 ) + +#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF ( MB_PDU_DATA_OFF + 4 ) +#define MB_PDU_FUNC_WRITE_MUL_VALUES_OFF ( MB_PDU_DATA_OFF + 5 ) +#define MB_PDU_FUNC_WRITE_MUL_SIZE_MIN ( 5 ) +#define MB_PDU_FUNC_WRITE_MUL_REGCNT_MAX ( 0x0078 ) + +#define MB_PDU_FUNC_READWRITE_READ_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_READWRITE_READ_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_READWRITE_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 4 ) +#define MB_PDU_FUNC_READWRITE_WRITE_REGCNT_OFF ( MB_PDU_DATA_OFF + 6 ) +#define MB_PDU_FUNC_READWRITE_BYTECNT_OFF ( MB_PDU_DATA_OFF + 8 ) +#define MB_PDU_FUNC_READWRITE_WRITE_VALUES_OFF ( MB_PDU_DATA_OFF + 9 ) +#define MB_PDU_FUNC_READWRITE_SIZE_MIN ( 9 ) + +/* ----------------------- Static functions ---------------------------------*/ +eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); + +/* ----------------------- Start implementation -----------------------------*/ +#if MB_SLAVE_RTU_ENABLED || MB_SLAVE_ASCII_ENABLED || MB_TCP_ENABLED + +#if MB_FUNC_WRITE_HOLDING_ENABLED + +eMBException +eMBFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + USHORT usRegAddress; + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + if( *usLen == ( MB_PDU_FUNC_WRITE_SIZE + MB_PDU_SIZE_MIN ) ) + { + usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_ADDR_OFF] << 8 ); + usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_ADDR_OFF + 1] ); + usRegAddress++; + + ESP_LOGW("HACK", "Hard hack: override %s in the component!!!!!!!!!", __func__); + + /* Make callback to update the value. */ + eRegStatus = eMBRegHoldingCB( &pucFrame[MB_PDU_FUNC_WRITE_VALUE_OFF], + usRegAddress, 1, MB_REG_WRITE ); + + /* If an error occured convert it into a Modbus exception. */ + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + } + else + { + /* Can't be a valid request because the length is incorrect. */ + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + return eStatus; +} +#endif + +#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0 +eMBException +eMBFuncWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + USHORT usRegAddress; + USHORT usRegCount; + UCHAR ucRegByteCount; + + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + if( *usLen >= ( MB_PDU_FUNC_WRITE_MUL_SIZE_MIN + MB_PDU_SIZE_MIN ) ) + { + usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF] << 8 ); + usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_ADDR_OFF + 1] ); + usRegAddress++; + + usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF] << 8 ); + usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF + 1] ); + + ucRegByteCount = pucFrame[MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF]; + + if( ( usRegCount >= 1 ) && + ( usRegCount <= MB_PDU_FUNC_WRITE_MUL_REGCNT_MAX ) && + ( ucRegByteCount == ( UCHAR ) ( 2 * usRegCount ) ) ) + { + /* Make callback to update the register values. */ + eRegStatus = + eMBRegHoldingCB( &pucFrame[MB_PDU_FUNC_WRITE_MUL_VALUES_OFF], + usRegAddress, usRegCount, MB_REG_WRITE ); + + /* If an error occured convert it into a Modbus exception. */ + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + else + { + /* The response contains the function code, the starting + * address and the quantity of registers. We reuse the + * old values in the buffer because they are still valid. + */ + *usLen = MB_PDU_FUNC_WRITE_MUL_BYTECNT_OFF; + } + } + else + { + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + } + else + { + /* Can't be a valid request because the length is incorrect. */ + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + return eStatus; +} +#endif + +#if MB_FUNC_READ_HOLDING_ENABLED > 0 + +eMBException +eMBFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + USHORT usRegAddress; + USHORT usRegCount; + UCHAR *pucFrameCur; + + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) ) + { + usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF] << 8 ); + usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_ADDR_OFF + 1] ); + usRegAddress++; + + usRegCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF] << 8 ); + usRegCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_READ_REGCNT_OFF + 1] ); + + /* Check if the number of registers to read is valid. If not + * return Modbus illegal data value exception. + */ + if( ( usRegCount >= 1 ) && ( usRegCount <= MB_PDU_FUNC_READ_REGCNT_MAX ) ) + { + /* Set the current PDU data pointer to the beginning. */ + pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF]; + *usLen = MB_PDU_FUNC_OFF; + + /* First byte contains the function code. */ + *pucFrameCur++ = MB_FUNC_READ_HOLDING_REGISTER; + *usLen += 1; + + /* Second byte in the response contain the number of bytes. */ + *pucFrameCur++ = ( UCHAR ) ( usRegCount * 2 ); + *usLen += 1; + + /* Make callback to fill the buffer. */ + eRegStatus = eMBRegHoldingCB( pucFrameCur, usRegAddress, usRegCount, MB_REG_READ ); + /* If an error occured convert it into a Modbus exception. */ + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + else + { + *usLen += usRegCount * 2; + } + } + else + { + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + } + else + { + /* Can't be a valid request because the length is incorrect. */ + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + return eStatus; +} + +#endif + +#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0 + +eMBException +eMBFuncReadWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + USHORT usRegReadAddress; + USHORT usRegReadCount; + USHORT usRegWriteAddress; + USHORT usRegWriteCount; + UCHAR ucRegWriteByteCount; + UCHAR *pucFrameCur; + + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + if( *usLen >= ( MB_PDU_FUNC_READWRITE_SIZE_MIN + MB_PDU_SIZE_MIN ) ) + { + usRegReadAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_READ_ADDR_OFF] << 8U ); + usRegReadAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_READ_ADDR_OFF + 1] ); + usRegReadAddress++; + + usRegReadCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_READ_REGCNT_OFF] << 8U ); + usRegReadCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_READ_REGCNT_OFF + 1] ); + + usRegWriteAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_WRITE_ADDR_OFF] << 8U ); + usRegWriteAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_WRITE_ADDR_OFF + 1] ); + usRegWriteAddress++; + + usRegWriteCount = ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_WRITE_REGCNT_OFF] << 8U ); + usRegWriteCount |= ( USHORT )( pucFrame[MB_PDU_FUNC_READWRITE_WRITE_REGCNT_OFF + 1] ); + + ucRegWriteByteCount = pucFrame[MB_PDU_FUNC_READWRITE_BYTECNT_OFF]; + + if( ( usRegReadCount >= 1 ) && ( usRegReadCount <= 0x7D ) && + ( usRegWriteCount >= 1 ) && ( usRegWriteCount <= 0x79 ) && + ( ( 2 * usRegWriteCount ) == ucRegWriteByteCount ) ) + { + /* Make callback to update the register values. */ + eRegStatus = eMBRegHoldingCB( &pucFrame[MB_PDU_FUNC_READWRITE_WRITE_VALUES_OFF], + usRegWriteAddress, usRegWriteCount, MB_REG_WRITE ); + + if( eRegStatus == MB_ENOERR ) + { + /* Set the current PDU data pointer to the beginning. */ + pucFrameCur = &pucFrame[MB_PDU_FUNC_OFF]; + *usLen = MB_PDU_FUNC_OFF; + + /* First byte contains the function code. */ + *pucFrameCur++ = MB_FUNC_READWRITE_MULTIPLE_REGISTERS; + *usLen += 1; + + /* Second byte in the response contain the number of bytes. */ + *pucFrameCur++ = ( UCHAR ) ( usRegReadCount * 2 ); + *usLen += 1; + + /* Make the read callback. */ + eRegStatus = + eMBRegHoldingCB( pucFrameCur, usRegReadAddress, usRegReadCount, MB_REG_READ ); + if( eRegStatus == MB_ENOERR ) + { + *usLen += 2 * usRegReadCount; + } + } + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + } + else + { + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + } + return eStatus; +} + +#endif + +#endif diff --git a/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding_m.c b/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding_m.c new file mode 100644 index 0000000..bf475bf --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding_m.c @@ -0,0 +1,464 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (C) 2013 Armink + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: mbfuncholding_m.c,v 1.60 2013/09/02 14:13:40 Armink Add Master Functions Exp $ + */ + +/* ----------------------- System includes ----------------------------------*/ +#include "stdlib.h" +#include "string.h" + +/* ----------------------- Platform includes --------------------------------*/ +#include "port.h" + +/* ----------------------- Modbus includes ----------------------------------*/ +//#include "mb.h" +#include "mb_m.h" +#include "mbframe.h" +#include "mbproto.h" +#include "mbconfig.h" + +/* ----------------------- Defines ------------------------------------------*/ +#define MB_PDU_REQ_READ_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_REQ_READ_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_READ_SIZE ( 4 ) +#define MB_PDU_FUNC_READ_REGCNT_MAX ( 0x007D ) +#define MB_PDU_FUNC_READ_BYTECNT_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_READ_VALUES_OFF ( MB_PDU_DATA_OFF + 1 ) +#define MB_PDU_FUNC_READ_SIZE_MIN ( 1 ) + +#define MB_PDU_REQ_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 0) +#define MB_PDU_REQ_WRITE_VALUE_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_WRITE_SIZE ( 4 ) +#define MB_PDU_FUNC_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 0) +#define MB_PDU_FUNC_WRITE_VALUE_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_WRITE_SIZE ( 4 ) + +#define MB_PDU_REQ_WRITE_MUL_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_REQ_WRITE_MUL_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF ( MB_PDU_DATA_OFF + 4 ) +#define MB_PDU_REQ_WRITE_MUL_VALUES_OFF ( MB_PDU_DATA_OFF + 5 ) +#define MB_PDU_REQ_WRITE_MUL_SIZE_MIN ( 5 ) +#define MB_PDU_REQ_WRITE_MUL_REGCNT_MAX ( 0x0078 ) +#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_WRITE_MUL_SIZE ( 4 ) + +#define MB_PDU_REQ_READWRITE_READ_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_REQ_READWRITE_READ_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 4 ) +#define MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF ( MB_PDU_DATA_OFF + 6 ) +#define MB_PDU_REQ_READWRITE_WRITE_BYTECNT_OFF ( MB_PDU_DATA_OFF + 8 ) +#define MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF ( MB_PDU_DATA_OFF + 9 ) +#define MB_PDU_REQ_READWRITE_SIZE_MIN ( 9 ) +#define MB_PDU_FUNC_READWRITE_READ_BYTECNT_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_READWRITE_READ_VALUES_OFF ( MB_PDU_DATA_OFF + 1 ) +#define MB_PDU_FUNC_READWRITE_SIZE_MIN ( 1 ) + +/* ----------------------- Static functions ---------------------------------*/ +eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); + +/* ----------------------- Start implementation -----------------------------*/ +#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED + +#if MB_FUNC_WRITE_HOLDING_ENABLED + +/** + * This function will request write holding register. + * + * @param ucSndAddr salve address + * @param usRegAddr register start address + * @param usRegData register data to be written + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqWriteHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usRegData, LONG lTimeOut ) +{ + UCHAR *ucMBFrame; + eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR; + + if ( ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM ) eErrStatus = MB_MRE_ILL_ARG; + else if ( xMBMasterRunResTake( lTimeOut ) == FALSE ) eErrStatus = MB_MRE_MASTER_BUSY; + else + { + vMBMasterGetPDUSndBuf(&ucMBFrame); + vMBMasterSetDestAddress(ucSndAddr); + ucMBFrame[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_REGISTER; + ucMBFrame[MB_PDU_REQ_WRITE_ADDR_OFF] = usRegAddr >> 8; + ucMBFrame[MB_PDU_REQ_WRITE_ADDR_OFF + 1] = usRegAddr; + ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF] = usRegData >> 8; + ucMBFrame[MB_PDU_REQ_WRITE_VALUE_OFF + 1] = usRegData ; + vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_SIZE ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT | EV_MASTER_TRANS_START ); + eErrStatus = eMBMasterWaitRequestFinish( ); + } + return eErrStatus; +} + +eMBException +eMBMasterFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + USHORT usRegAddress; + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + if( *usLen == ( MB_PDU_SIZE_MIN + MB_PDU_FUNC_WRITE_SIZE ) ) + { + usRegAddress = ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_ADDR_OFF] << 8 ); + usRegAddress |= ( USHORT )( pucFrame[MB_PDU_FUNC_WRITE_ADDR_OFF + 1] ); + usRegAddress++; + + /* Make callback to update the value. */ + eRegStatus = eMBMasterRegHoldingCB( &pucFrame[MB_PDU_FUNC_WRITE_VALUE_OFF], + usRegAddress, 1, MB_REG_WRITE ); + + /* If an error occured convert it into a Modbus exception. */ + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + } + else + { + /* Can't be a valid request because the length is incorrect. */ + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + return eStatus; +} +#endif + +#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0 + +/** + * This function will request write multiple holding register. + * + * @param ucSndAddr salve address + * @param usRegAddr register start address + * @param usNRegs register total number + * @param pusDataBuffer data to be written + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqWriteMultipleHoldingRegister( UCHAR ucSndAddr, + USHORT usRegAddr, USHORT usNRegs, USHORT * pusDataBuffer, LONG lTimeOut ) +{ + UCHAR *ucMBFrame; + USHORT usRegIndex = 0; + eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR; + + ESP_LOGW("HACK", "Custom reg write holding registers function: %u, %u, %u\r\n", ucSndAddr, usRegAddr, usNRegs); + + if ( ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM ) eErrStatus = MB_MRE_ILL_ARG; + else if ( xMBMasterRunResTake( lTimeOut ) == FALSE ) eErrStatus = MB_MRE_MASTER_BUSY; + else + { + vMBMasterGetPDUSndBuf(&ucMBFrame); + vMBMasterSetDestAddress(ucSndAddr); + ucMBFrame[MB_PDU_FUNC_OFF] = MB_FUNC_WRITE_MULTIPLE_REGISTERS; + ucMBFrame[MB_PDU_REQ_WRITE_MUL_ADDR_OFF] = usRegAddr >> 8; + ucMBFrame[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1] = usRegAddr; + ucMBFrame[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF] = usNRegs >> 8; + ucMBFrame[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF + 1] = usNRegs ; + ucMBFrame[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF] = usNRegs * 2; + ucMBFrame += MB_PDU_REQ_WRITE_MUL_VALUES_OFF; + while( usNRegs > usRegIndex) + { + *ucMBFrame++ = pusDataBuffer[usRegIndex] >> 8; + *ucMBFrame++ = pusDataBuffer[usRegIndex++] ; + } + vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_WRITE_MUL_SIZE_MIN + 2*usNRegs ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT | EV_MASTER_TRANS_START ); + eErrStatus = eMBMasterWaitRequestFinish( ); + } + return eErrStatus; +} + +eMBException +eMBMasterFuncWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + UCHAR *ucMBFrame; + USHORT usRegAddress; + USHORT usRegCount; + UCHAR ucRegByteCount; + + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + /* If this request is broadcast, the *usLen is not need check. */ + if( ( *usLen == MB_PDU_SIZE_MIN + MB_PDU_FUNC_WRITE_MUL_SIZE ) || xMBMasterRequestIsBroadcast() ) + { + vMBMasterGetPDUSndBuf(&ucMBFrame); + usRegAddress = ( USHORT )( ucMBFrame[MB_PDU_REQ_WRITE_MUL_ADDR_OFF] << 8 ); + usRegAddress |= ( USHORT )( ucMBFrame[MB_PDU_REQ_WRITE_MUL_ADDR_OFF + 1] ); + usRegAddress++; + + usRegCount = ( USHORT )( ucMBFrame[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF] << 8 ); + usRegCount |= ( USHORT )( ucMBFrame[MB_PDU_REQ_WRITE_MUL_REGCNT_OFF + 1] ); + + ucRegByteCount = ucMBFrame[MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF]; + + if( ucRegByteCount == 2 * usRegCount ) + { + /* Make callback to update the register values. */ + eRegStatus = eMBMasterRegHoldingCB( &ucMBFrame[MB_PDU_REQ_WRITE_MUL_VALUES_OFF], + usRegAddress, usRegCount, MB_REG_WRITE ); + + /* If an error occured convert it into a Modbus exception. */ + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + } + else + { + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + } + else + { + /* Can't be a valid request because the length is incorrect. */ + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + return eStatus; +} +#endif + +#if MB_FUNC_READ_HOLDING_ENABLED > 0 + +/** + * This function will request read holding register. + * + * @param ucSndAddr salve address + * @param usRegAddr register start address + * @param usNRegs register total number + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqReadHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usNRegs, LONG lTimeOut ) +{ + UCHAR *ucMBFrame; + eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR; + + if ( ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM ) eErrStatus = MB_MRE_ILL_ARG; + else if ( xMBMasterRunResTake( lTimeOut ) == FALSE ) eErrStatus = MB_MRE_MASTER_BUSY; + else + { + vMBMasterGetPDUSndBuf(&ucMBFrame); + ESP_LOGW("HACK", "Custom reg write holding registers function: %u, %u, %u\r\n", ucSndAddr, usRegAddr, usNRegs); + vMBMasterSetDestAddress(ucSndAddr); + ucMBFrame[MB_PDU_FUNC_OFF] = MB_FUNC_READ_HOLDING_REGISTER; + ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF] = usRegAddr >> 8; + ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF + 1] = usRegAddr; + ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF] = usNRegs >> 8; + ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF + 1] = usNRegs; + vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_READ_SIZE ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT | EV_MASTER_TRANS_START ); + eErrStatus = eMBMasterWaitRequestFinish( ); + } + return eErrStatus; +} + +eMBException +eMBMasterFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + UCHAR *ucMBFrame; + USHORT usRegAddress; + USHORT usRegCount; + + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + /* If this request is broadcast, and it's read mode. This request don't need execute. */ + if ( xMBMasterRequestIsBroadcast() ) + { + eStatus = MB_EX_NONE; + } + else if( *usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READ_SIZE_MIN ) + { + vMBMasterGetPDUSndBuf(&ucMBFrame); + usRegAddress = ( USHORT )( ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF] << 8 ); + usRegAddress |= ( USHORT )( ucMBFrame[MB_PDU_REQ_READ_ADDR_OFF + 1] ); + usRegAddress++; + + usRegCount = ( USHORT )( ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF] << 8 ); + usRegCount |= ( USHORT )( ucMBFrame[MB_PDU_REQ_READ_REGCNT_OFF + 1] ); + + /* Check if the number of registers to read is valid. If not + * return Modbus illegal data value exception. + */ + if( ( usRegCount >= 1 ) && ( 2 * usRegCount == pucFrame[MB_PDU_FUNC_READ_BYTECNT_OFF] ) ) + { + /* Make callback to fill the buffer. */ + eRegStatus = eMBMasterRegHoldingCB( &pucFrame[MB_PDU_FUNC_READ_VALUES_OFF], usRegAddress, usRegCount, MB_REG_READ ); + /* If an error occured convert it into a Modbus exception. */ + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + } + else + { + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + } + else + { + /* Can't be a valid request because the length is incorrect. */ + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + return eStatus; +} + +#endif + +#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0 + +/** + * This function will request read and write holding register. + * + * @param ucSndAddr salve address + * @param usReadRegAddr read register start address + * @param usNReadRegs read register total number + * @param pusDataBuffer data to be written + * @param usWriteRegAddr write register start address + * @param usNWriteRegs write register total number + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqReadWriteMultipleHoldingRegister( UCHAR ucSndAddr, + USHORT usReadRegAddr, USHORT usNReadRegs, USHORT * pusDataBuffer, + USHORT usWriteRegAddr, USHORT usNWriteRegs, LONG lTimeOut ) +{ + UCHAR *ucMBFrame; + USHORT usRegIndex = 0; + eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR; + + if ( ucSndAddr > MB_MASTER_TOTAL_SLAVE_NUM ) eErrStatus = MB_MRE_ILL_ARG; + else if ( xMBMasterRunResTake( lTimeOut ) == FALSE ) eErrStatus = MB_MRE_MASTER_BUSY; + else + { + vMBMasterGetPDUSndBuf(&ucMBFrame); + vMBMasterSetDestAddress(ucSndAddr); + ucMBFrame[MB_PDU_FUNC_OFF] = MB_FUNC_READWRITE_MULTIPLE_REGISTERS; + ucMBFrame[MB_PDU_REQ_READWRITE_READ_ADDR_OFF] = usReadRegAddr >> 8; + ucMBFrame[MB_PDU_REQ_READWRITE_READ_ADDR_OFF + 1] = usReadRegAddr; + ucMBFrame[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF] = usNReadRegs >> 8; + ucMBFrame[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF + 1] = usNReadRegs ; + ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF] = usWriteRegAddr >> 8; + ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF + 1] = usWriteRegAddr; + ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF] = usNWriteRegs >> 8; + ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF + 1] = usNWriteRegs ; + ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_BYTECNT_OFF] = usNWriteRegs * 2; + ucMBFrame += MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF; + while( usNWriteRegs > usRegIndex) + { + *ucMBFrame++ = pusDataBuffer[usRegIndex] >> 8; + *ucMBFrame++ = pusDataBuffer[usRegIndex++] ; + } + vMBMasterSetPDUSndLength( MB_PDU_SIZE_MIN + MB_PDU_REQ_READWRITE_SIZE_MIN + 2*usNWriteRegs ); + ( void ) xMBMasterPortEventPost( EV_MASTER_FRAME_TRANSMIT | EV_MASTER_TRANS_START ); + eErrStatus = eMBMasterWaitRequestFinish( ); + } + return eErrStatus; +} + +eMBException +eMBMasterFuncReadWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen ) +{ + USHORT usRegReadAddress; + USHORT usRegReadCount; + USHORT usRegWriteAddress; + USHORT usRegWriteCount; + UCHAR *ucMBFrame; + + eMBException eStatus = MB_EX_NONE; + eMBErrorCode eRegStatus; + + /* If this request is broadcast, and it's read mode. This request don't need execute. */ + if ( xMBMasterRequestIsBroadcast() ) + { + eStatus = MB_EX_NONE; + } + else if( *usLen >= MB_PDU_SIZE_MIN + MB_PDU_FUNC_READWRITE_SIZE_MIN ) + { + vMBMasterGetPDUSndBuf(&ucMBFrame); + usRegReadAddress = ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_READ_ADDR_OFF] << 8U ); + usRegReadAddress |= ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_READ_ADDR_OFF + 1] ); + usRegReadAddress++; + + usRegReadCount = ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF] << 8U ); + usRegReadCount |= ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_READ_REGCNT_OFF + 1] ); + + usRegWriteAddress = ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF] << 8U ); + usRegWriteAddress |= ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF + 1] ); + usRegWriteAddress++; + + usRegWriteCount = ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF] << 8U ); + usRegWriteCount |= ( USHORT )( ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF + 1] ); + + if( ( 2 * usRegReadCount ) == pucFrame[MB_PDU_FUNC_READWRITE_READ_BYTECNT_OFF] ) + { + /* Make callback to update the register values. */ + eRegStatus = eMBMasterRegHoldingCB( &ucMBFrame[MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF], + usRegWriteAddress, usRegWriteCount, MB_REG_WRITE ); + + if( eRegStatus == MB_ENOERR ) + { + /* Make the read callback. */ + eRegStatus = eMBMasterRegHoldingCB(&pucFrame[MB_PDU_FUNC_READWRITE_READ_VALUES_OFF], + usRegReadAddress, usRegReadCount, MB_REG_READ); + } + if( eRegStatus != MB_ENOERR ) + { + eStatus = prveMBError2Exception( eRegStatus ); + } + } + else + { + eStatus = MB_EX_ILLEGAL_DATA_VALUE; + } + } + return eStatus; +} + +#endif +#endif // #if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED diff --git a/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding_m.h b/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding_m.h new file mode 100644 index 0000000..e2f4bbe --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/freemodbus/modbus/functions/mbfuncholding_m.h @@ -0,0 +1,179 @@ +/* + * SPDX-FileCopyrightText: 2013 Armink + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2021 Espressif Systems (Shanghai) CO LTD + */ +/* + * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. + * Copyright (C) 2013 Armink + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: mbfuncholding_m.c,v 1.60 2013/09/02 14:13:40 Armink Add Master Functions Exp $ + */ + +/* ----------------------- System includes ----------------------------------*/ +#include "stdlib.h" +#include "string.h" + +/* ----------------------- Platform includes --------------------------------*/ +#include "port.h" + +/* ----------------------- Modbus includes ----------------------------------*/ +//#include "mb.h" +#include "mb_m.h" +#include "mbframe.h" +#include "mbproto.h" +#include "mbconfig.h" + +/* ----------------------- Defines ------------------------------------------*/ +#define MB_PDU_REQ_READ_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_REQ_READ_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_READ_SIZE ( 4 ) +#define MB_PDU_FUNC_READ_REGCNT_MAX ( 0x007D ) +#define MB_PDU_FUNC_READ_BYTECNT_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_READ_VALUES_OFF ( MB_PDU_DATA_OFF + 1 ) +#define MB_PDU_FUNC_READ_SIZE_MIN ( 1 ) + +#define MB_PDU_REQ_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 0) +#define MB_PDU_REQ_WRITE_VALUE_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_WRITE_SIZE ( 4 ) +#define MB_PDU_FUNC_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 0) +#define MB_PDU_FUNC_WRITE_VALUE_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_WRITE_SIZE ( 4 ) + +#define MB_PDU_REQ_WRITE_MUL_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_REQ_WRITE_MUL_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_WRITE_MUL_BYTECNT_OFF ( MB_PDU_DATA_OFF + 4 ) +#define MB_PDU_REQ_WRITE_MUL_VALUES_OFF ( MB_PDU_DATA_OFF + 5 ) +#define MB_PDU_REQ_WRITE_MUL_SIZE_MIN ( 5 ) +#define MB_PDU_REQ_WRITE_MUL_REGCNT_MAX ( 0x0078 ) +#define MB_PDU_FUNC_WRITE_MUL_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_WRITE_MUL_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_FUNC_WRITE_MUL_SIZE ( 4 ) + +#define MB_PDU_REQ_READWRITE_READ_ADDR_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_REQ_READWRITE_READ_REGCNT_OFF ( MB_PDU_DATA_OFF + 2 ) +#define MB_PDU_REQ_READWRITE_WRITE_ADDR_OFF ( MB_PDU_DATA_OFF + 4 ) +#define MB_PDU_REQ_READWRITE_WRITE_REGCNT_OFF ( MB_PDU_DATA_OFF + 6 ) +#define MB_PDU_REQ_READWRITE_WRITE_BYTECNT_OFF ( MB_PDU_DATA_OFF + 8 ) +#define MB_PDU_REQ_READWRITE_WRITE_VALUES_OFF ( MB_PDU_DATA_OFF + 9 ) +#define MB_PDU_REQ_READWRITE_SIZE_MIN ( 9 ) +#define MB_PDU_FUNC_READWRITE_READ_BYTECNT_OFF ( MB_PDU_DATA_OFF + 0 ) +#define MB_PDU_FUNC_READWRITE_READ_VALUES_OFF ( MB_PDU_DATA_OFF + 1 ) +#define MB_PDU_FUNC_READWRITE_SIZE_MIN ( 1 ) + +/* ----------------------- Static functions ---------------------------------*/ +eMBException prveMBError2Exception( eMBErrorCode eErrorCode ); + +/* ----------------------- Start implementation -----------------------------*/ +#if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED + +#if MB_FUNC_WRITE_HOLDING_ENABLED + +/** + * This function will request write holding register. + * + * @param ucSndAddr salve address + * @param usRegAddr register start address + * @param usRegData register data to be written + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqWriteHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usRegData, LONG lTimeOut ); + +eMBException +eMBMasterFuncWriteHoldingRegister( UCHAR * pucFrame, USHORT * usLen ); + +#endif + +#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED > 0 + +/** + * This function will request write multiple holding register. + * + * @param ucSndAddr salve address + * @param usRegAddr register start address + * @param usNRegs register total number + * @param pusDataBuffer data to be written + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqWriteMultipleHoldingRegister( UCHAR ucSndAddr, + USHORT usRegAddr, USHORT usNRegs, USHORT * pusDataBuffer, LONG lTimeOut ); + +eMBException +eMBMasterFuncWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen ); +#endif + +#if MB_FUNC_READ_HOLDING_ENABLED > 0 + +/** + * This function will request read holding register. + * + * @param ucSndAddr salve address + * @param usRegAddr register start address + * @param usNRegs register total number + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqReadHoldingRegister( UCHAR ucSndAddr, USHORT usRegAddr, USHORT usNRegs, LONG lTimeOut ); + +eMBException +eMBMasterFuncReadHoldingRegister( UCHAR * pucFrame, USHORT * usLen ); +#endif + +#if MB_FUNC_READWRITE_HOLDING_ENABLED > 0 + +/** + * This function will request read and write holding register. + * + * @param ucSndAddr salve address + * @param usReadRegAddr read register start address + * @param usNReadRegs read register total number + * @param pusDataBuffer data to be written + * @param usWriteRegAddr write register start address + * @param usNWriteRegs write register total number + * @param lTimeOut timeout (-1 will waiting forever) + * + * @return error code + */ +eMBMasterReqErrCode +eMBMasterReqReadWriteMultipleHoldingRegister( UCHAR ucSndAddr, + USHORT usReadRegAddr, USHORT usNReadRegs, USHORT * pusDataBuffer, + USHORT usWriteRegAddr, USHORT usNWriteRegs, LONG lTimeOut ); + +eMBException +eMBMasterFuncReadWriteMultipleHoldingRegister( UCHAR * pucFrame, USHORT * usLen ); + +#endif +#endif // #if MB_MASTER_RTU_ENABLED || MB_MASTER_ASCII_ENABLED || MB_MASTER_TCP_ENABLED diff --git a/mb_override_component/components/patched_esp-modbus/freemodbus/patch_lib.c b/mb_override_component/components/patched_esp-modbus/freemodbus/patch_lib.c new file mode 100644 index 0000000..d023be1 --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/freemodbus/patch_lib.c @@ -0,0 +1,10 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include + +// The workaround to statically link whole test library +__attribute__((unused)) bool mb_patch_lib_include = true; \ No newline at end of file diff --git a/mb_override_component/components/patched_esp-modbus/freemodbus/tcp_master/port/port_tcp_master.c b/mb_override_component/components/patched_esp-modbus/freemodbus/tcp_master/port/port_tcp_master.c new file mode 100644 index 0000000..b588367 --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/freemodbus/tcp_master/port/port_tcp_master.c @@ -0,0 +1,1039 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ +/* + * FreeModbus Libary: ESP32 TCP Port + * Copyright (C) 2006 Christian Walter + * Parts of crt0.S Copyright (c) 1995, 1996, 1998 Cygnus Support + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * IF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: port.c,v 1.2 2006/09/04 14:39:20 wolti Exp $ + */ + +/* ----------------------- System includes ----------------------------------*/ +#include +#include +#include "esp_err.h" +#include "esp_timer.h" + +/* ----------------------- lwIP includes ------------------------------------*/ +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/netdb.h" +#include "esp_netif.h" + +/* ----------------------- Modbus includes ----------------------------------*/ +#include "mb_m.h" +#include "port.h" +#include "mbport.h" +#include "mbframe.h" +#include "port_tcp_master.h" + +#warning "This file is overriden in the component" + +#if MB_MASTER_TCP_ENABLED + +/* ----------------------- Defines -----------------------------------------*/ +#define MB_TCP_CONNECTION_TIMEOUT_MS ( 20 ) // Connection timeout in mS +#define MB_TCP_RECONNECT_TIMEOUT ( 5000000 ) // Connection timeout in uS + +#define MB_EVENT_REQ_DONE_MASK ( EV_MASTER_PROCESS_SUCCESS | \ + EV_MASTER_ERROR_RESPOND_TIMEOUT | \ + EV_MASTER_ERROR_RECEIVE_DATA | \ + EV_MASTER_ERROR_EXECUTE_FUNCTION ) + +#define MB_EVENT_REQ_ERR_MASK ( EV_MASTER_PROCESS_SUCCESS ) + +#define MB_EVENT_WAIT_TOUT_MS ( 3000 ) +#define MB_SHDN_WAIT_TOUT_MS ( 5000 ) + +#define MB_TCP_READ_TICK_MS ( 1 ) +#define MB_TCP_READ_BUF_RETRY_CNT ( 4 ) +#define MB_SLAVE_FMT(fmt) "Slave #%d, Socket(#%d)(%s)"fmt + +/* ----------------------- Types & Prototypes --------------------------------*/ +void vMBPortEventClose(void); + +/* ----------------------- Static variables ---------------------------------*/ +static const char *TAG = "MB_TCP_MASTER_PORT"; +static MbPortConfig_t xMbPortConfig; +static EventGroupHandle_t xMasterEventHandle = NULL; +static SemaphoreHandle_t xShutdownSema = NULL; +static EventBits_t xMasterEvent = 0; + +/* ----------------------- Static functions ---------------------------------*/ +static void vMBTCPPortMasterTask(void *pvParameters); + +/* ----------------------- Begin implementation -----------------------------*/ + +// Waits for stack start event to start Modbus event processing +BOOL xMBTCPPortMasterWaitEvent(EventGroupHandle_t xEventHandle, EventBits_t xEvent, USHORT usTimeout) +{ + xMasterEventHandle = xEventHandle; + xMasterEvent = xEvent; + BaseType_t status = xEventGroupWaitBits(xMasterEventHandle, + (BaseType_t)(xEvent), + pdFALSE, // do not clear start bit + pdFALSE, + usTimeout); + return (BOOL)(status & xEvent); +} + +BOOL +xMBMasterTCPPortInit( USHORT usTCPPort ) +{ + BOOL bOkay = FALSE; + + xMbPortConfig.pxMbSlaveInfo = calloc(MB_TCP_PORT_MAX_CONN, sizeof(MbSlaveInfo_t*)); + if (!xMbPortConfig.pxMbSlaveInfo) { + ESP_LOGE(TAG, "TCP slave info alloc failure."); + return FALSE; + } + for(int idx = 0; idx < MB_TCP_PORT_MAX_CONN; xMbPortConfig.pxMbSlaveInfo[idx] = NULL, idx++); + + xMbPortConfig.xConnectQueue = NULL; + xMbPortConfig.usPort = usTCPPort; + xMbPortConfig.usMbSlaveInfoCount = 0; + xMbPortConfig.ucCurSlaveIndex = 1; + xMbPortConfig.pxMbSlaveCurrInfo = NULL; + + xMbPortConfig.xConnectQueue = xQueueCreate(2, sizeof(MbSlaveAddrInfo_t)); + if (xMbPortConfig.xConnectQueue == 0) + { + // Queue was not created and must not be used. + ESP_LOGE(TAG, "TCP master queue creation failure."); + return FALSE; + } + + // Create task for packet processing + BaseType_t xErr = xTaskCreatePinnedToCore(vMBTCPPortMasterTask, + "mbm_port_tcp_task", + MB_TCP_STACK_SIZE, + NULL, + MB_TCP_TASK_PRIO, + &xMbPortConfig.xMbTcpTaskHandle, + MB_PORT_TASK_AFFINITY); + if (xErr != pdTRUE) + { + ESP_LOGE(TAG, "TCP master task creation failure."); + (void)vTaskDelete(xMbPortConfig.xMbTcpTaskHandle); + } else { + ESP_LOGI(TAG, "TCP master stack initialized."); + bOkay = TRUE; + } + return bOkay; +} + +static MbSlaveInfo_t *vMBTCPPortMasterFindSlaveInfo(UCHAR ucSlaveAddr) +{ + int xIndex; + BOOL xFound = false; + for (xIndex = 0; xIndex < xMbPortConfig.usMbSlaveInfoCount; xIndex++) { + if (xMbPortConfig.pxMbSlaveInfo[xIndex]->ucSlaveAddr == ucSlaveAddr) { + xMbPortConfig.pxMbSlaveCurrInfo = xMbPortConfig.pxMbSlaveInfo[xIndex]; + xFound = TRUE; + xMbPortConfig.ucCurSlaveIndex = xIndex; + } + } + if (!xFound) { + xMbPortConfig.pxMbSlaveCurrInfo = NULL; + ESP_LOGE(TAG, "Slave info for short address %u not found.", ucSlaveAddr); + } + return xMbPortConfig.pxMbSlaveCurrInfo; +} + +static MbSlaveInfo_t *vMBTCPPortMasterGetCurrInfo(void) +{ + if (!xMbPortConfig.pxMbSlaveCurrInfo) { + ESP_LOGE(TAG, "Incorrect current slave info."); + } + return xMbPortConfig.pxMbSlaveCurrInfo; +} + +// Start Modbus event state machine +static void vMBTCPPortMasterStartPoll(void) +{ + if (xMasterEventHandle) { + // Set the mbcontroller start flag + EventBits_t xFlags = xEventGroupSetBits(xMasterEventHandle, + (EventBits_t)xMasterEvent); + if (!(xFlags & xMasterEvent)) { + ESP_LOGE(TAG, "Fail to start TCP stack."); + } + } else { + ESP_LOGE(TAG, "Fail to start polling. Incorrect event handle..."); + } +} + +// Stop Modbus event state machine +static void vMBTCPPortMasterStopPoll(void) +{ + if (xMasterEventHandle) { + // Set the mbcontroller start flag + EventBits_t xFlags = xEventGroupClearBits(xMasterEventHandle, + (EventBits_t)xMasterEvent); + if (!(xFlags & xMasterEvent)) { + ESP_LOGE(TAG, "Fail to stop polling."); + } + } else { + ESP_LOGE(TAG, "Fail to stop polling. Incorrect event handle..."); + } +} + +// The helper function to get time stamp in microseconds +static int64_t xMBTCPGetTimeStamp(void) +{ + int64_t xTimeStamp = esp_timer_get_time(); + return xTimeStamp; +} + +static void vMBTCPPortMasterMStoTimeVal(USHORT usTimeoutMs, struct timeval *tv) +{ + tv->tv_sec = usTimeoutMs / 1000; + tv->tv_usec = (usTimeoutMs - (tv->tv_sec * 1000)) * 1000; +} + +static BOOL xMBTCPPortMasterCloseConnection(MbSlaveInfo_t *pxInfo) +{ + if (!pxInfo) { + return FALSE; + } + if (pxInfo->xSockId == -1) { + ESP_LOGE(TAG, "Wrong socket info or disconnected socket: %d, skip.", (int)pxInfo->xSockId); + return FALSE; + } + if (shutdown(pxInfo->xSockId, SHUT_RDWR) == -1) { + ESP_LOGV(TAG, "Shutdown failed sock %d, errno=%u", (int)pxInfo->xSockId, (unsigned)errno); + } + close(pxInfo->xSockId); + pxInfo->xSockId = -1; + return TRUE; +} + +static void xMBTCPPortMasterShutdown(void) +{ + xSemaphoreGive(xShutdownSema); + + for (USHORT ucCnt = 0; ucCnt < MB_TCP_PORT_MAX_CONN; ucCnt++) { + MbSlaveInfo_t* pxInfo = xMbPortConfig.pxMbSlaveInfo[ucCnt]; + if (pxInfo) { + xMBTCPPortMasterCloseConnection(pxInfo); + if (pxInfo->pucRcvBuf) { + free(pxInfo->pucRcvBuf); + } + free(pxInfo); + xMbPortConfig.pxMbSlaveInfo[ucCnt] = NULL; + } + } + free(xMbPortConfig.pxMbSlaveInfo); + vTaskDelete(NULL); + xMbPortConfig.xMbTcpTaskHandle = NULL; +} + +void vMBTCPPortMasterSetNetOpt(void *pvNetIf, eMBPortIpVer xIpVersion, eMBPortProto xProto) +{ + xMbPortConfig.pvNetIface = pvNetIf; + xMbPortConfig.eMbProto = xProto; + xMbPortConfig.eMbIpVer = xIpVersion; +} + +// Function returns time left for response processing according to response timeout +static int64_t xMBTCPPortMasterGetRespTimeLeft(MbSlaveInfo_t *pxInfo) +{ + if (!pxInfo) { + return 0; + } + int64_t xTimeStamp = xMBTCPGetTimeStamp() - pxInfo->xSendTimeStamp; + return (xTimeStamp > (1000 * MB_MASTER_TIMEOUT_MS_RESPOND)) ? 0 : + (MB_MASTER_TIMEOUT_MS_RESPOND - (xTimeStamp / 1000) - 1); +} + +// Wait socket ready to read state +static int vMBTCPPortMasterRxCheck(int xSd, fd_set *pxFdSet, int xTimeMs) +{ + fd_set xReadSet = *pxFdSet; + fd_set xErrorSet = *pxFdSet; + int xRes = 0; + struct timeval xTimeout; + + vMBTCPPortMasterMStoTimeVal(xTimeMs, &xTimeout); + xRes = select(xSd + 1, &xReadSet, NULL, &xErrorSet, &xTimeout); + if (xRes == 0) { + // No respond from slave during timeout + xRes = ERR_TIMEOUT; + } else if ((xRes < 0) || FD_ISSET(xSd, &xErrorSet)) { + xRes = -1; + } + + *pxFdSet = xReadSet; + return xRes; +} + +static int xMBTCPPortMasterGetBuf(MbSlaveInfo_t *pxInfo, UCHAR *pucDstBuf, USHORT usLength, uint16_t xTimeMs) +{ + int xLength = 0; + UCHAR *pucBuf = pucDstBuf; + USHORT usBytesLeft = usLength; + struct timeval xTime; + + MB_PORT_CHECK((pxInfo && pxInfo->xSockId > -1), -1, "Try to read incorrect socket = #%d.", (int)pxInfo->xSockId); + + // Set receive timeout for socket <= slave respond time + xTime.tv_sec = xTimeMs / 1000; + xTime.tv_usec = (xTimeMs % 1000) * 1000; + setsockopt(pxInfo->xSockId, SOL_SOCKET, SO_RCVTIMEO, &xTime, sizeof(xTime)); + + // Receive data from connected client + while (usBytesLeft > 0) { + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + xLength = recv(pxInfo->xSockId, pucBuf, usBytesLeft, 0); + if (xLength < 0) { + if (errno == EAGAIN) { + // Read timeout occurred, check the timeout and return + } else if (errno == ENOTCONN) { + // Socket connection closed + ESP_LOGE(TAG, "Socket(#%d)(%s) connection closed.", + (int)pxInfo->xSockId, pxInfo->pcIpAddr); + return ERR_CONN; + } else { + // Other error occurred during receiving + ESP_LOGE(TAG, "Socket(#%d)(%s) receive error, length=%u, errno=%u", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (unsigned)xLength, (unsigned)errno); + return -1; + } + } else if (xLength) { + pucBuf += xLength; + usBytesLeft -= xLength; + } + if (xMBTCPPortMasterGetRespTimeLeft(pxInfo) == 0) { + return ERR_TIMEOUT; + } + } + return usLength; +} + +static int vMBTCPPortMasterReadPacket(MbSlaveInfo_t *pxInfo) +{ + int xLength = 0; + int xRet = 0; + USHORT usTidRcv = 0; + + // Receive data from connected client + if (pxInfo) { + MB_PORT_CHECK((pxInfo->xSockId > 0), -1, "Try to read incorrect socket = #%d.", pxInfo->xSockId); + // Read packet header + xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[0], + MB_TCP_UID, xMBTCPPortMasterGetRespTimeLeft(pxInfo)); + if (xRet < 0) { + pxInfo->xRcvErr = xRet; + return xRet; + } else if (xRet != MB_TCP_UID) { + ESP_LOGD(TAG, "Socket (#%d)(%s), Fail to read modbus header. ret=%d", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)xRet); + pxInfo->xRcvErr = ERR_VAL; + return ERR_VAL; + } + // If we have received the MBAP header we can analyze it and calculate + // the number of bytes left to complete the current request. + xLength = (int)MB_TCP_GET_FIELD(pxInfo->pucRcvBuf, MB_TCP_LEN); + xRet = xMBTCPPortMasterGetBuf(pxInfo, &pxInfo->pucRcvBuf[MB_TCP_UID], + xLength, xMBTCPPortMasterGetRespTimeLeft(pxInfo)); + if (xRet < 0) { + pxInfo->xRcvErr = xRet; + return xRet; + } else if (xRet != xLength) { + // Received incorrect or fragmented packet. + ESP_LOGD(TAG, "Socket(#%d)(%s) incorrect packet, length=%u, TID=0x%02x, errno=%u(%s)", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)pxInfo->usRcvPos, + (int)usTidRcv, (unsigned)errno, strerror(errno)); + pxInfo->xRcvErr = ERR_VAL; + return ERR_VAL; + } + usTidRcv = MB_TCP_GET_FIELD(pxInfo->pucRcvBuf, MB_TCP_TID); + + // Check transaction identifier field in the incoming packet. + if ((pxInfo->usTidCnt - 1) != usTidRcv) { + ESP_LOGD(TAG, "Socket (#%d)(%s), incorrect TID(0x%02x)!=(0x%02x) received, discard data.", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)usTidRcv, (int)(pxInfo->usTidCnt - 1)); + pxInfo->xRcvErr = ERR_BUF; + return ERR_BUF; + } + pxInfo->usRcvPos += xRet + MB_TCP_UID; + ESP_LOGD(TAG, "Socket(#%d)(%s) get data, length=%u, TID=0x%02x, errno=%u(%s)", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (unsigned)pxInfo->usRcvPos, + (unsigned)usTidRcv, (unsigned)errno, strerror(errno)); + pxInfo->xRcvErr = ERR_OK; + return pxInfo->usRcvPos; + } + return -1; +} + +static err_t xMBTCPPortMasterSetNonBlocking(MbSlaveInfo_t *pxInfo) +{ + if (!pxInfo) { + return ERR_CONN; + } + // Set non blocking attribute for socket + ULONG ulFlags = fcntl(pxInfo->xSockId, F_GETFL); + if (fcntl(pxInfo->xSockId, F_SETFL, ulFlags | O_NONBLOCK) == -1) { + ESP_LOGE(TAG, "Socket(#%d)(%s), fcntl() call error=%u", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (unsigned)errno); + return ERR_WOULDBLOCK; + } + return ERR_OK; +} + +static void vMBTCPPortSetKeepAlive(MbSlaveInfo_t *pxInfo) +{ + int optval = 1; + setsockopt(pxInfo->xSockId, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); +} + +// Check connection for timeout helper +static err_t xMBTCPPortMasterCheckAlive(MbSlaveInfo_t *pxInfo, ULONG xTimeoutMs) +{ + fd_set xWriteSet; + fd_set xErrorSet; + err_t xErr = -1; + struct timeval xTimeVal; + + if (pxInfo && pxInfo->xSockId != -1) { + FD_ZERO(&xWriteSet); + FD_ZERO(&xErrorSet); + FD_SET(pxInfo->xSockId, &xWriteSet); + FD_SET(pxInfo->xSockId, &xErrorSet); + vMBTCPPortMasterMStoTimeVal(xTimeoutMs, &xTimeVal); + // Check if the socket is writable + xErr = select(pxInfo->xSockId + 1, NULL, &xWriteSet, &xErrorSet, &xTimeVal); + if ((xErr < 0) || FD_ISSET(pxInfo->xSockId, &xErrorSet)) { + if (errno == EINPROGRESS) { + xErr = ERR_INPROGRESS; + } else { + ESP_LOGV(TAG, MB_SLAVE_FMT(" connection, select write err(errno) = 0x%x(%u)."), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)xErr, (unsigned)errno); + xErr = ERR_CONN; + } + } else if (xErr == 0) { + ESP_LOGV(TAG, "Socket(#%d)(%s), connection timeout occurred, err(errno) = 0x%x(%u).", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)xErr, (unsigned)errno); + return ERR_INPROGRESS; + } else { + int xOptErr = 0; + ULONG ulOptLen = sizeof(xOptErr); + // Check socket error + xErr = getsockopt(pxInfo->xSockId, SOL_SOCKET, SO_ERROR, (void*)&xOptErr, (socklen_t*)&ulOptLen); + if (xOptErr != 0) { + ESP_LOGD(TAG, "Socket(#%d)(%s), sock error occurred (%u).", + (int)pxInfo->xSockId, pxInfo->pcIpAddr, (unsigned)xOptErr); + return ERR_CONN; + } + ESP_LOGV(TAG, "Socket(#%d)(%s), is alive.", + (int)pxInfo->xSockId, pxInfo->pcIpAddr); + return ERR_OK; + } + } else { + xErr = ERR_CONN; + } + return xErr; +} + +// Resolve host name and/or fill the IP address structure +static BOOL xMBTCPPortMasterCheckHost(const CHAR *pcHostStr, ip_addr_t *pxHostAddr) +{ + MB_PORT_CHECK((pcHostStr), FALSE, "Wrong host name or IP."); + CHAR cStr[45]; + CHAR *pcStr = &cStr[0]; + ip_addr_t xTargetAddr; + struct addrinfo xHint; + struct addrinfo *pxAddrList; + memset(&xHint, 0, sizeof(xHint)); + // Do name resolution for both protocols + xHint.ai_family = AF_UNSPEC; + xHint.ai_flags = AI_ADDRCONFIG | AI_CANONNAME; // get IPV6 address if supported, otherwise IPV4 + memset(&xTargetAddr, 0, sizeof(xTargetAddr)); + + ESP_LOGE(TAG, "Patched library esp-modbus: %s", __func__); + + // convert domain name to IP address + // Todo: check EAI_FAIL error when resolve host name + int xRet = getaddrinfo(pcHostStr, NULL, &xHint, &pxAddrList); + + if (xRet != 0) { + ESP_LOGE(TAG, "Incorrect host name or IP: %s", pcHostStr); + return FALSE; + } + if (pxAddrList->ai_family == AF_INET) { + struct in_addr addr4 = ((struct sockaddr_in *) (pxAddrList->ai_addr))->sin_addr; + inet_addr_to_ip4addr(ip_2_ip4(&xTargetAddr), &addr4); + pcStr = ip4addr_ntoa_r(ip_2_ip4(&xTargetAddr), cStr, sizeof(cStr)); + } +#if CONFIG_LWIP_IPV6 + else { + struct in6_addr addr6 = ((struct sockaddr_in6 *) (pxAddrList->ai_addr))->sin6_addr; + inet6_addr_to_ip6addr(ip_2_ip6(&xTargetAddr), &addr6); + pcStr = ip6addr_ntoa_r(ip_2_ip6(&xTargetAddr), cStr, sizeof(cStr)); + } +#endif + if (pxHostAddr) { + *pxHostAddr = xTargetAddr; + } + //ESP_LOGI(TAG, "Host[IP]: %s [%s]", pxAddrList->ai_canonname ? pxAddrList->ai_canonname : '\0', pcStr); + freeaddrinfo(pxAddrList); + return TRUE; +} + +BOOL xMBTCPPortMasterAddSlaveIp(const USHORT usIndex, const CHAR *pcIpStr, UCHAR ucSlaveAddress) +{ + BOOL xRes = FALSE; + MbSlaveAddrInfo_t xSlaveAddrInfo = {0}; + MB_PORT_CHECK(xMbPortConfig.xConnectQueue != NULL, FALSE, "Wrong slave IP address to add."); + if (pcIpStr && (usIndex != 0xFF)) { + xRes = xMBTCPPortMasterCheckHost(pcIpStr, NULL); + } + if (xRes || !pcIpStr) { + xSlaveAddrInfo.pcIPAddr = pcIpStr; + xSlaveAddrInfo.usIndex = usIndex; + xSlaveAddrInfo.ucSlaveAddr = ucSlaveAddress; + BaseType_t xStatus = xQueueSend(xMbPortConfig.xConnectQueue, (void *)&xSlaveAddrInfo, 100); + MB_PORT_CHECK((xStatus == pdTRUE), FALSE, "FAIL to add slave IP address: [%s].", pcIpStr); + } + return xRes; +} + +// Unblocking connect function +static err_t xMBTCPPortMasterConnect(MbSlaveInfo_t *pxInfo) +{ + if (!pxInfo) { + return ERR_CONN; + } + + err_t xErr = ERR_OK; + CHAR cStr[128]; + CHAR *pcStr = NULL; + ip_addr_t xTargetAddr; + struct addrinfo xHint; + struct addrinfo *pxAddrList; + struct addrinfo *pxCurAddr; + + memset(&xHint, 0, sizeof(xHint)); + // Do name resolution for both protocols + // xHint.ai_family = AF_UNSPEC; Todo: Find a reason why AF_UNSPEC does not work + xHint.ai_flags = AI_ADDRCONFIG; // get IPV6 address if supported, otherwise IPV4 + xHint.ai_family = (xMbPortConfig.eMbIpVer == MB_PORT_IPV4) ? AF_INET : AF_INET6; + xHint.ai_socktype = (pxInfo->xMbProto == MB_PROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM; + xHint.ai_protocol = (pxInfo->xMbProto == MB_PROTO_UDP) ? IPPROTO_UDP : IPPROTO_TCP; + memset(&xTargetAddr, 0, sizeof(xTargetAddr)); + + if (asprintf(&pcStr, "%u", xMbPortConfig.usPort) == -1) { + abort(); + } + + // convert domain name to IP address + int xRet = getaddrinfo(pxInfo->pcIpAddr, pcStr, &xHint, &pxAddrList); + free(pcStr); + if (xRet != 0) { + ESP_LOGE(TAG, "Cannot resolve host: %s", pxInfo->pcIpAddr); + return ERR_CONN; + } + + for (pxCurAddr = pxAddrList; pxCurAddr != NULL; pxCurAddr = pxCurAddr->ai_next) { + if (pxCurAddr->ai_family == AF_INET) { + struct in_addr addr4 = ((struct sockaddr_in *) (pxCurAddr->ai_addr))->sin_addr; + inet_addr_to_ip4addr(ip_2_ip4(&xTargetAddr), &addr4); + pcStr = ip4addr_ntoa_r(ip_2_ip4(&xTargetAddr), cStr, sizeof(cStr)); + } +#if CONFIG_LWIP_IPV6 + else if (pxCurAddr->ai_family == AF_INET6) { + struct in6_addr addr6 = ((struct sockaddr_in6 *) (pxCurAddr->ai_addr))->sin6_addr; + inet6_addr_to_ip6addr(ip_2_ip6(&xTargetAddr), &addr6); + pcStr = ip6addr_ntoa_r(ip_2_ip6(&xTargetAddr), cStr, sizeof(cStr)); + // Set scope id to fix routing issues with local address + ((struct sockaddr_in6 *)(pxCurAddr->ai_addr))->sin6_scope_id = + esp_netif_get_netif_impl_index(xMbPortConfig.pvNetIface); + } +#endif + if (pxInfo->xSockId <= 0) { + pxInfo->xSockId = socket(pxCurAddr->ai_family, pxCurAddr->ai_socktype, pxCurAddr->ai_protocol); + if (pxInfo->xSockId < 0) { + ESP_LOGE(TAG, "Unable to create socket: #%d, errno %u", (int)pxInfo->xSockId, (unsigned)errno); + xErr = ERR_IF; + continue; + } + } else { + ESP_LOGV(TAG, "Socket (#%d)(%s) created.", (int)pxInfo->xSockId, cStr); + } + + // Set non blocking attribute for socket + xMBTCPPortMasterSetNonBlocking(pxInfo); + + // Can return EINPROGRESS as an error which means + // that connection is in progress and should be checked later + xErr = connect(pxInfo->xSockId, (struct sockaddr*)pxCurAddr->ai_addr, pxCurAddr->ai_addrlen); + if ((xErr < 0) && (errno == EINPROGRESS || errno == EALREADY)) { + // The unblocking connect is pending (check status later) or already connected + ESP_LOGV(TAG, "Socket(#%d)(%s) connection is pending, errno %u (%s).", + (int)pxInfo->xSockId, cStr, (unsigned)errno, strerror(errno)); + + // Set keep alive flag in socket options + vMBTCPPortSetKeepAlive(pxInfo); + xErr = xMBTCPPortMasterCheckAlive(pxInfo, MB_TCP_CONNECTION_TIMEOUT_MS); + continue; + } else if ((xErr < 0) && (errno == EISCONN)) { + // Socket already connected + xErr = ERR_OK; + continue; + } else if (xErr != ERR_OK) { + // Other error occurred during connection + ESP_LOGV(TAG, MB_SLAVE_FMT(" unable to connect, error=0x%x, errno %u (%s)"), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, cStr, (int)xErr, (unsigned)errno, strerror(errno)); + xMBTCPPortMasterCloseConnection(pxInfo); + xErr = ERR_CONN; + } else { + ESP_LOGI(TAG, MB_SLAVE_FMT(", successfully connected."), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, cStr); + continue; + } + } + freeaddrinfo(pxAddrList); + return xErr; +} + +// Find the first slave info whose descriptor is set in xFdSet +static MbSlaveInfo_t *xMBTCPPortMasterGetSlaveReady(fd_set *pxFdSet) +{ + MbSlaveInfo_t *pxInfo = NULL; + + // Slave connection loop + for (int xIndex = 0; (xIndex < MB_TCP_PORT_MAX_CONN); xIndex++) { + pxInfo = xMbPortConfig.pxMbSlaveInfo[xIndex]; + if (pxInfo) { + // Is this response for current processing slave + if (FD_ISSET(pxInfo->xSockId, pxFdSet)) { + FD_CLR(pxInfo->xSockId, pxFdSet); + return pxInfo; + } + } + } + return (MbSlaveInfo_t *)NULL; +} + +static int xMBTCPPortMasterCheckConnState(fd_set *pxFdSet) +{ + fd_set xConnSetCheck = *pxFdSet; + MbSlaveInfo_t *pxInfo = NULL; + int64_t xTime = 0; + int xErr = 0; + int xCount = 0; + do { + xTime = xMBTCPGetTimeStamp(); + pxInfo = xMBTCPPortMasterGetSlaveReady(&xConnSetCheck); + if (pxInfo) { + xErr = xMBTCPPortMasterCheckAlive(pxInfo, 0); + if ((xErr < 0) && (((xTime - pxInfo->xRecvTimeStamp) > MB_TCP_RECONNECT_TIMEOUT) || + ((xTime - pxInfo->xSendTimeStamp) > MB_TCP_RECONNECT_TIMEOUT))) { + ESP_LOGI(TAG, MB_SLAVE_FMT(", slave is down, off_time[r][w](us) = [%" PRIu64 "][%" PRIu64 "]."), + (int)pxInfo->xIndex, + (int)pxInfo->xSockId, + pxInfo->pcIpAddr, + (int64_t)(xTime - pxInfo->xRecvTimeStamp), + (int64_t)(xTime - pxInfo->xSendTimeStamp)); + xCount++; + } + } + } while (pxInfo && (xCount < MB_TCP_PORT_MAX_CONN)); + return xCount; +} + +static void xMBTCPPortMasterFsmSetError(eMBMasterErrorEventType xErrType, eMBMasterEventEnum xPostEvent) +{ + vMBMasterPortTimersDisable(); + vMBMasterSetErrorType(xErrType); + xMBMasterPortEventPost(xPostEvent); +} + +static void vMBTCPPortMasterTask(void *pvParameters) +{ + MbSlaveInfo_t *pxInfo; + MbSlaveInfo_t *pxCurrInfo; + + fd_set xConnSet; + fd_set xReadSet; + int xMaxSd = 0; + err_t xErr = ERR_ABRT; + USHORT usSlaveConnCnt = 0; + int64_t xTime = 0; + + // Register each slave in the connection info structure + while (1) { + MbSlaveAddrInfo_t xSlaveAddrInfo = { 0 }; + BaseType_t xStatus = xQueueReceive(xMbPortConfig.xConnectQueue, (void*)&xSlaveAddrInfo, pdMS_TO_TICKS(MB_EVENT_WAIT_TOUT_MS)); + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + if (xStatus != pdTRUE) { + ESP_LOGE(TAG, "Fail to register slave IP."); + } else { + if (xSlaveAddrInfo.pcIPAddr == NULL && xMbPortConfig.usMbSlaveInfoCount && xSlaveAddrInfo.usIndex == 0xFF) { + // Init start timeout that allows to initialize the main FSM + xMBMasterPortEventPost(EV_MASTER_READY); + break; + } + if (xMbPortConfig.usMbSlaveInfoCount > MB_TCP_PORT_MAX_CONN) { + ESP_LOGE(TAG, "Exceeds maximum connections limit=%u.", (unsigned)MB_TCP_PORT_MAX_CONN); + break; + } + pxInfo = calloc(1, sizeof(MbSlaveInfo_t)); + if (!pxInfo) { + ESP_LOGE(TAG, "Slave(#%u), info structure allocation fail.", + (unsigned)xMbPortConfig.usMbSlaveInfoCount); + free(pxInfo); + break; + } + pxInfo->pucRcvBuf = calloc(MB_TCP_BUF_SIZE, sizeof(UCHAR)); + if (!pxInfo->pucRcvBuf) { + ESP_LOGE(TAG, "Slave(#%u), receive buffer allocation fail.", + (unsigned)xMbPortConfig.usMbSlaveInfoCount); + free(pxInfo->pucRcvBuf); + break; + } + pxInfo->usRcvPos = 0; + pxInfo->pcIpAddr = xSlaveAddrInfo.pcIPAddr; + pxInfo->xSockId = -1; + pxInfo->xError = -1; + pxInfo->xRecvTimeStamp = xMBTCPGetTimeStamp(); + pxInfo->xSendTimeStamp = xMBTCPGetTimeStamp(); + pxInfo->xMbProto = MB_PROTO_TCP; + pxInfo->ucSlaveAddr = xSlaveAddrInfo.ucSlaveAddr; + pxInfo->xIndex = xSlaveAddrInfo.usIndex; + pxInfo->usTidCnt = (USHORT)(xMbPortConfig.usMbSlaveInfoCount << 8U); + // Register slave + xMbPortConfig.pxMbSlaveInfo[xMbPortConfig.usMbSlaveInfoCount++] = pxInfo; + ESP_LOGI(TAG, "Add slave IP: %s", xSlaveAddrInfo.pcIPAddr); + } + } + + // Main connection cycle + while (1) + { + ESP_LOGI(TAG, "Connecting to slaves..."); + xTime = xMBTCPGetTimeStamp(); + usSlaveConnCnt = 0; + CHAR ucDot = '.'; + while(usSlaveConnCnt < xMbPortConfig.usMbSlaveInfoCount) { + usSlaveConnCnt = 0; + FD_ZERO(&xConnSet); + ucDot ^= 0x03; + // Slave connection loop + for (UCHAR ucCnt = 0; (ucCnt < MB_TCP_PORT_MAX_CONN); ucCnt++) { + pxInfo = xMbPortConfig.pxMbSlaveInfo[ucCnt]; + // if slave descriptor is NULL then it is end of list or connection closed. + if (!pxInfo) { + ESP_LOGV(TAG, "Index: % is not initialized, skip.", ucCnt); + if (xMbPortConfig.usMbSlaveInfoCount) { + continue; + } + break; + } + putchar(ucDot); + // if we don't yield we run the risk of hogging CPU + vTaskDelay(pdMS_TO_TICKS(MB_TCP_CONNECTION_TIMEOUT_MS)); + xErr = xMBTCPPortMasterConnect(pxInfo); + switch(xErr) + { + case ERR_CONN: + case ERR_INPROGRESS: + // In case of connection errors remove the socket from set + if (FD_ISSET(pxInfo->xSockId, &xConnSet)) { + FD_CLR(pxInfo->xSockId, &xConnSet); + ESP_LOGE(TAG, MB_SLAVE_FMT(" connect failed, error = 0x%x."), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, + (char*)pxInfo->pcIpAddr, (int)xErr); + if (usSlaveConnCnt) { + usSlaveConnCnt--; + } + } + break; + case ERR_OK: + // if connection is successful, add the descriptor into set + if (!FD_ISSET(pxInfo->xSockId, &xConnSet)) { + FD_SET(pxInfo->xSockId, &xConnSet); + usSlaveConnCnt++; + xMaxSd = (pxInfo->xSockId > xMaxSd) ? pxInfo->xSockId : xMaxSd; + ESP_LOGD(TAG, MB_SLAVE_FMT(", connected %u slave(s), error = 0x%x."), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, + pxInfo->pcIpAddr, + (unsigned)usSlaveConnCnt, (int)xErr); + // Update time stamp for connected slaves + pxInfo->xRecvTimeStamp = xMBTCPGetTimeStamp(); + pxInfo->xSendTimeStamp = xMBTCPGetTimeStamp(); + } + break; + default: + ESP_LOGE(TAG, MB_SLAVE_FMT(", unexpected error = 0x%x."), + (int)pxInfo->xIndex, + (int)pxInfo->xSockId, + pxInfo->pcIpAddr, (int)xErr); + break; + } + if (pxInfo) { + pxInfo->xError = xErr; + } + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + } + } + ESP_LOGI(TAG, "Connected %u slaves, start polling...", (unsigned)usSlaveConnCnt); + + vMBTCPPortMasterStartPoll(); // Send event to start stack + + // Slave receive data loop + while(usSlaveConnCnt) { + xReadSet = xConnSet; + // Check transmission event to clear appropriate bit. + xMBMasterPortFsmWaitConfirmation(EV_MASTER_FRAME_TRANSMIT, pdMS_TO_TICKS(MB_EVENT_WAIT_TOUT_MS)); + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + // Synchronize state machine with send packet event + if (xMBMasterPortFsmWaitConfirmation(EV_MASTER_FRAME_SENT, pdMS_TO_TICKS(MB_EVENT_WAIT_TOUT_MS))) { + ESP_LOGD(TAG, "FSM Synchronized with sent event."); + } + // Get slave info for the current slave. + pxCurrInfo = vMBTCPPortMasterGetCurrInfo(); + if (!pxCurrInfo) { + ESP_LOGE(TAG, "Incorrect connection options for slave index: %d.", + (int)xMbPortConfig.ucCurSlaveIndex); + vMBTCPPortMasterStopPoll(); + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + break; // incorrect slave descriptor, reconnect. + } + xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo); + ESP_LOGD(TAG, "Set select timeout, left time: %" PRIu64 " ms.", + xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo)); + // Wait respond from current slave during respond timeout + int xRes = vMBTCPPortMasterRxCheck(pxCurrInfo->xSockId, &xReadSet, xTime); + if (xRes == ERR_TIMEOUT) { + // No respond from current slave, process timeout. + // Need to drop response later if it is received after timeout. + ESP_LOGD(TAG, "Select timeout, left time: %" PRIu64 " ms.", + xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo)); + xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo); + // Wait completion of last transaction + xMBMasterPortFsmWaitConfirmation(MB_EVENT_REQ_DONE_MASK, pdMS_TO_TICKS(xTime + 1)); + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + continue; + } else if (xRes < 0) { + // Select error (slave connection or r/w failure). + ESP_LOGD(TAG, MB_SLAVE_FMT(", socket select error. Slave disconnected?"), + (int)pxCurrInfo->xIndex, (int)pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); + xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo); + // Wait completion of last transaction + xMBMasterPortFsmWaitConfirmation(MB_EVENT_REQ_DONE_MASK, pdMS_TO_TICKS(xTime)); + // Stop polling process + vMBTCPPortMasterStopPoll(); + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + // Check disconnected slaves, do not need a result just to print information. + xMBTCPPortMasterCheckConnState(&xConnSet); + break; + } else { + // Check to make sure that active slave data is ready + if (FD_ISSET(pxCurrInfo->xSockId, &xReadSet)) { + int xRet = ERR_BUF; + for (int retry = 0; (xRet == ERR_BUF) && (retry < MB_TCP_READ_BUF_RETRY_CNT); retry++) { + xRet = vMBTCPPortMasterReadPacket(pxCurrInfo); + // The error ERR_BUF means received response to previous request + // (due to timeout) with the same socket ID and incorrect TID, + // then ignore it and try to get next response buffer. + } + if (xRet > 0) { + // Response received correctly, send an event to stack + xMBTCPPortMasterFsmSetError(EV_ERROR_INIT, EV_MASTER_FRAME_RECEIVED); + ESP_LOGD(TAG, MB_SLAVE_FMT(", frame received."), + (int)pxCurrInfo->xIndex, (int)pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); + } else if ((xRet == ERR_TIMEOUT) || (xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo) == 0)) { + // Timeout occurred when receiving frame, process respond timeout + xMBTCPPortMasterFsmSetError(EV_ERROR_RESPOND_TIMEOUT, EV_MASTER_ERROR_PROCESS); + ESP_LOGD(TAG, MB_SLAVE_FMT(", frame read timeout."), + (int)pxCurrInfo->xIndex, (int)pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); + } else if (xRet == ERR_BUF) { + // After retries a response with incorrect TID received, process failure. + xMBTCPPortMasterFsmSetError(EV_ERROR_RECEIVE_DATA, EV_MASTER_ERROR_PROCESS); + ESP_LOGD(TAG, MB_SLAVE_FMT(", frame error."), + (int)pxCurrInfo->xIndex, (int)pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); + } else { + ESP_LOGE(TAG, MB_SLAVE_FMT(", critical error=%d."), + (int)pxCurrInfo->xIndex, (int)pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr, (int)xRet); + // Stop polling process + vMBTCPPortMasterStopPoll(); + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + // Check disconnected slaves, do not need a result just to print information. + xMBTCPPortMasterCheckConnState(&xConnSet); + break; + } + xTime = xMBTCPPortMasterGetRespTimeLeft(pxCurrInfo); + ESP_LOGD(TAG, "Slave #%d, data processing left time %" PRIu64 " [ms].", (int)pxCurrInfo->xIndex, xTime); + // Wait completion of Modbus frame processing before start of new transaction. + if (xMBMasterPortFsmWaitConfirmation(MB_EVENT_REQ_DONE_MASK, pdMS_TO_TICKS(xTime))) { + ESP_LOGD(TAG, MB_SLAVE_FMT(", data processing completed."), + (int)pxCurrInfo->xIndex, (int)pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr); + } + xTime = xMBTCPGetTimeStamp() - pxCurrInfo->xSendTimeStamp; + ESP_LOGD(TAG, MB_SLAVE_FMT(", processing time[us] = %" PRIu64 "."), + (int)pxCurrInfo->xIndex, (int)pxCurrInfo->xSockId, pxCurrInfo->pcIpAddr, xTime); + } + } + TCP_PORT_CHECK_SHDN(xShutdownSema, xMBTCPPortMasterShutdown); + } // while(usMbSlaveInfoCount) + } // while (1) + vTaskDelete(NULL); +} + +extern void vMBMasterPortEventClose(void); +extern void vMBMasterPortTimerClose(void); + +void vMBMasterTCPPortEnable(void) +{ + +} + +void vMBMasterTCPPortDisable(void) +{ + // Try to exit the task gracefully, so select could release its internal callbacks + // that were allocated on the stack of the task we're going to delete + xShutdownSema = xSemaphoreCreateBinary(); + // if no semaphore (alloc issues) or couldn't acquire it, just delete the task + if (xShutdownSema == NULL || xSemaphoreTake(xShutdownSema, pdMS_TO_TICKS(MB_SHDN_WAIT_TOUT_MS)) != pdTRUE) { + ESP_LOGW(TAG, "Modbus port task couldn't exit gracefully within timeout -> abruptly deleting the task."); + vTaskDelete(xMbPortConfig.xMbTcpTaskHandle); + } + if (xShutdownSema) { + vSemaphoreDelete(xShutdownSema); + xShutdownSema = NULL; + } +} + +void vMBMasterTCPPortClose(void) +{ + vQueueDelete(xMbPortConfig.xConnectQueue); + vMBMasterPortTimerClose(); + // Release resources for the event queue. + vMBMasterPortEventClose(); +} + +BOOL xMBMasterTCPPortGetRequest(UCHAR **ppucMBTCPFrame, USHORT *usTCPLength) +{ + MbSlaveInfo_t *pxInfo = vMBTCPPortMasterGetCurrInfo(); + *ppucMBTCPFrame = pxInfo->pucRcvBuf; + *usTCPLength = pxInfo->usRcvPos; + + // Reset the buffer. + pxInfo->usRcvPos = 0; + // Save slave receive timestamp + if (pxInfo->xRcvErr == ERR_OK && *usTCPLength > 0) { + pxInfo->xRecvTimeStamp = xMBTCPGetTimeStamp(); + return TRUE; + } + return FALSE; +} + +int xMBMasterTCPPortWritePoll(MbSlaveInfo_t *pxInfo, const UCHAR *pucMBTCPFrame, USHORT usTCPLength, ULONG xTimeout) +{ + // Check if the socket is alive (writable and SO_ERROR == 0) + int xRes = (int)xMBTCPPortMasterCheckAlive(pxInfo, xTimeout); + if ((xRes < 0) && (xRes != ERR_INPROGRESS)) { + ESP_LOGE(TAG, MB_SLAVE_FMT(", is not writable, error: %d, errno %u"), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)xRes, (unsigned)errno); + return xRes; + } + xRes = send(pxInfo->xSockId, pucMBTCPFrame, usTCPLength, TCP_NODELAY); + if (xRes < 0) { + ESP_LOGE(TAG, MB_SLAVE_FMT(", send data error: %d, errno %u"), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)xRes, (unsigned)errno); + } + return xRes; +} + +BOOL xMBMasterTCPPortSendResponse(UCHAR *pucMBTCPFrame, USHORT usTCPLength) +{ + BOOL bFrameSent = FALSE; + USHORT ucCurSlaveIndex = ucMBMasterGetDestAddress(); + MbSlaveInfo_t *pxInfo = vMBTCPPortMasterFindSlaveInfo(ucCurSlaveIndex); + + // If the slave is correct and active then send data + // otherwise treat slave as died and skip + if (pxInfo != NULL) { + if (pxInfo->xSockId < 0) { + ESP_LOGD(TAG, MB_SLAVE_FMT(", send to died slave, error = %u"), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, pxInfo->pcIpAddr, (unsigned)pxInfo->xError); + } else { + // Apply TID field to the frame before send + pucMBTCPFrame[MB_TCP_TID] = (UCHAR)(pxInfo->usTidCnt >> 8U); + pucMBTCPFrame[MB_TCP_TID + 1] = (UCHAR)(pxInfo->usTidCnt & 0xFF); + + int xRes = xMBMasterTCPPortWritePoll(pxInfo, pucMBTCPFrame, usTCPLength, MB_TCP_SEND_TIMEOUT_MS); + if (xRes < 0) { + ESP_LOGE(TAG, MB_SLAVE_FMT(", send data failure, err(errno) = %d(%u)."), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, pxInfo->pcIpAddr, (int)xRes, (unsigned)errno); + bFrameSent = FALSE; + pxInfo->xError = xRes; + } else { + bFrameSent = TRUE; + ESP_LOGD(TAG, MB_SLAVE_FMT(", send data successful: TID=0x%02x, %d (bytes), errno %u"), + (int)pxInfo->xIndex, (int)pxInfo->xSockId, pxInfo->pcIpAddr, pxInfo->usTidCnt, (int)xRes, (unsigned)errno); + pxInfo->xError = 0; + pxInfo->usRcvPos = 0; + if (pxInfo->usTidCnt < (USHRT_MAX - 1)) { + pxInfo->usTidCnt++; + } else { + pxInfo->usTidCnt = (USHORT)(pxInfo->xIndex << 8U); + } + } + pxInfo->xSendTimeStamp = xMBTCPGetTimeStamp(); + } + } else { + ESP_LOGD(TAG, "Send data to died slave, address = %u", (unsigned)ucCurSlaveIndex); + } + vMBMasterPortTimersRespondTimeoutEnable(); + xMBMasterPortEventPost(EV_MASTER_FRAME_SENT); + return bFrameSent; +} + +// Timer handler to check timeout of socket response +BOOL MB_PORT_ISR_ATTR +xMBMasterTCPTimerExpired(void) +{ + BOOL xNeedPoll = FALSE; + eMBMasterTimerMode eTimerMode = xMBMasterGetCurTimerMode(); + + vMBMasterPortTimersDisable(); + + // If timer mode is respond timeout, the master event then turns EV_MASTER_EXECUTE status. + if (eTimerMode == MB_TMODE_RESPOND_TIMEOUT) { + vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT); + xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS); + } + + return xNeedPoll; +} + +#endif // #if MB_MASTER_TCP_ENABLED diff --git a/mb_override_component/components/patched_esp-modbus/freemodbus/tcp_master/port/port_tcp_master.h b/mb_override_component/components/patched_esp-modbus/freemodbus/tcp_master/port/port_tcp_master.h new file mode 100644 index 0000000..9867bee --- /dev/null +++ b/mb_override_component/components/patched_esp-modbus/freemodbus/tcp_master/port/port_tcp_master.h @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2006 Christian Walter + * + * SPDX-License-Identifier: BSD-3-Clause + * + * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD + */ +/* + * FreeModbus Libary: ESP32 TCP Port + * Copyright (C) 2006 Christian Walter + * Parts of crt0.S Copyright (c) 1995, 1996, 1998 Cygnus Support + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * IF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * File: $Id: port.h,v 1.2 2006/09/04 14:39:20 wolti Exp $ + */ + +#ifndef _PORT_TCP_SLAVE_H +#define _PORT_TCP_SLAVE_H + +/* ----------------------- Platform includes --------------------------------*/ +#include "esp_log.h" + +#include "lwip/sys.h" +#include "freertos/event_groups.h" +#include "port.h" + +/* ----------------------- Defines ------------------------------------------*/ + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef __cplusplus +PR_BEGIN_EXTERN_C +#endif + +/* ----------------------- Type definitions ---------------------------------*/ + +typedef struct { + int xIndex; /*!< Slave information index */ + int xSockId; /*!< Socket ID of slave */ + int xError; /*!< Socket error */ + int xRcvErr; /*!< Socket receive error */ + const char* pcIpAddr; /*!< TCP/UDP IP address */ + UCHAR ucSlaveAddr; /*!< Slave short address */ + UCHAR* pucRcvBuf; /*!< Receive buffer pointer */ + USHORT usRcvPos; /*!< Receive buffer position */ + int pcPort; /*!< TCP/UDP port number */ + eMBPortProto xMbProto; /*!< Protocol type */ + int64_t xSendTimeStamp; /*!< Send request time stamp */ + int64_t xRecvTimeStamp; /*!< Receive response time stamp */ + uint16_t usTidCnt; /*!< Transaction identifier (TID) for slave */ +} MbSlaveInfo_t; + +typedef struct { + TaskHandle_t xMbTcpTaskHandle; /*!< Master TCP/UDP handling task handle */ + QueueHandle_t xConnectQueue; /*!< Master connection queue */ + USHORT usPort; /*!< Master TCP/UDP port number */ + USHORT usMbSlaveInfoCount; /*!< Master count of connected slaves */ + USHORT ucCurSlaveIndex; /*!< Master current processing slave index */ + eMBPortIpVer eMbIpVer; /*!< Master IP version */ + eMBPortProto eMbProto; /*!< Master protocol type */ + void* pvNetIface; /*!< Master netif interface pointer */ + MbSlaveInfo_t** pxMbSlaveInfo; /*!< Master information structure for each connected slave */ + MbSlaveInfo_t* pxMbSlaveCurrInfo; /*!< Master current slave information */ +} MbPortConfig_t; + +typedef struct { + USHORT usIndex; /*!< index of the address info */ + const char* pcIPAddr; /*!< represents the IP address of the slave */ + UCHAR ucSlaveAddr; /*!< slave unit ID (UID) field for MBAP frame */ +} MbSlaveAddrInfo_t; + +/* ----------------------- Function prototypes ------------------------------*/ + +// The functions below are used by Modbus controller interface to configure Modbus port. +/** + * Registers slave IP address + * + * @param usIndex index of element in the configuration + * @param pcIpStr IP address to register + * @param ucSlaveAddress slave element index + * + * @return TRUE if address registered successfully, else FALSE + */ +BOOL xMBTCPPortMasterAddSlaveIp(const USHORT usIndex, const CHAR* pcIpStr, UCHAR ucSlaveAddress); + +/** + * Keeps FSM event handle and mask then wait for Master stack to start + * + * @param xEventHandle Master event handle + * @param xEvent event mask to start Modbus stack FSM + * @param usTimeout - timeout in ticks to wait for stack to start + * + * @return TRUE if stack started, else FALSE + */ +BOOL xMBTCPPortMasterWaitEvent(EventGroupHandle_t xEventHandle, EventBits_t xEvent, USHORT usTimeout); + +/** + * Set network options for Master port + * + * @param pvNetIf netif interface pointer + * @param xIpVersion IP version option for the Master port + * @param xProto Protocol version option for the Master port + * + * @return None + */ +void vMBTCPPortMasterSetNetOpt(void* pvNetIf, eMBPortIpVer xIpVersion, eMBPortProto xProto); + +#ifdef __cplusplus +PR_END_EXTERN_C +#endif +#endif diff --git a/mb_override_component/main/CMakeLists.txt b/mb_override_component/main/CMakeLists.txt new file mode 100644 index 0000000..f9ab7f6 --- /dev/null +++ b/mb_override_component/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(PROJECT_NAME "modbus_master") + +idf_component_register(SRCS "master.c" + INCLUDE_DIRS ".") diff --git a/mb_override_component/main/Kconfig.projbuild b/mb_override_component/main/Kconfig.projbuild new file mode 100644 index 0000000..9e6ec53 --- /dev/null +++ b/mb_override_component/main/Kconfig.projbuild @@ -0,0 +1,79 @@ +menu "Modbus Example Configuration" + + orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps" + + config MB_UART_PORT_ONE + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=1) && (SOC_UART_NUM > 1) + + config MB_UART_PORT_TWO + bool + default y + depends on (ESP_CONSOLE_UART_NUM !=2) && (SOC_UART_NUM > 2) + + config MB_UART_PORT_NUM + int "UART port number" + range 0 2 if MB_UART_PORT_TWO + default 2 if MB_UART_PORT_TWO + range 0 1 if MB_UART_PORT_ONE + default 1 if MB_UART_PORT_ONE + help + UART communication port number for Modbus example. + + config MB_UART_BAUD_RATE + int "UART communication speed" + range 1200 115200 + default 115200 + help + UART communication speed for Modbus example. + + config MB_UART_RXD + int "UART RXD pin number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_IN_RANGE_MAX + default 22 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 + default 8 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 ||\ + IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 + help + GPIO number for UART RX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_TXD + int "UART TXD pin number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 23 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 + default 9 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 ||\ + IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 + help + GPIO number for UART TX pin. See UART documentation for more information + about available pin numbers for UART. + + config MB_UART_RTS + int "UART RTS pin number" + range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX + default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C6 + default 10 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32C3 ||\ + IDF_TARGET_ESP32C2 || IDF_TARGET_ESP32H2 + help + GPIO number for UART RTS pin. This pin is connected to + ~RE/DE pin of RS485 transceiver to switch direction. + See UART documentation for more information about available pin + numbers for UART. + + choice MB_COMM_MODE + prompt "Modbus communication mode" + default MB_COMM_MODE_RTU if CONFIG_FMB_COMM_MODE_RTU_EN + help + Selection of Modbus communication mode option for Modbus. + + config MB_COMM_MODE_RTU + bool "RTU mode" + depends on FMB_COMM_MODE_RTU_EN + + config MB_COMM_MODE_ASCII + bool "ASCII mode" + depends on FMB_COMM_MODE_ASCII_EN + + endchoice + +endmenu diff --git a/mb_override_component/main/idf_component.yml b/mb_override_component/main/idf_component.yml new file mode 100644 index 0000000..3eb79f2 --- /dev/null +++ b/mb_override_component/main/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: ">=4.1" + espressif/esp-modbus: + version: "^1.0" + mb_example_common: + path: ${IDF_PATH}/examples/protocols/modbus/mb_example_common diff --git a/mb_override_component/main/master.c b/mb_override_component/main/master.c new file mode 100644 index 0000000..32f3beb --- /dev/null +++ b/mb_override_component/main/master.c @@ -0,0 +1,313 @@ +/* + * SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "string.h" +#include "esp_log.h" +#include "modbus_params.h" // for modbus parameters structures +#include "mbcontroller.h" +#include "sdkconfig.h" + +#define MB_PORT_NUM (CONFIG_MB_UART_PORT_NUM) // Number of UART port used for Modbus connection +#define MB_DEV_SPEED (CONFIG_MB_UART_BAUD_RATE) // The communication speed of the UART + +// Note: Some pins on target chip cannot be assigned for UART communication. +// See UART documentation for selected board and target to configure pins using Kconfig. + +// The number of parameters that intended to be used in the particular control process +#define MASTER_MAX_CIDS num_device_parameters + +// Number of reading of parameters from slave +#define MASTER_MAX_RETRY 30 + +// Timeout to update cid over Modbus +#define UPDATE_CIDS_TIMEOUT_MS (500) +#define UPDATE_CIDS_TIMEOUT_TICS (UPDATE_CIDS_TIMEOUT_MS / portTICK_PERIOD_MS) + +// Timeout between polls +#define POLL_TIMEOUT_MS (1) +#define POLL_TIMEOUT_TICS (POLL_TIMEOUT_MS / portTICK_PERIOD_MS) + +// The macro to get offset for parameter in the appropriate structure +#define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1)) +#define INPUT_OFFSET(field) ((uint16_t)(offsetof(input_reg_params_t, field) + 1)) +#define COIL_OFFSET(field) ((uint16_t)(offsetof(coil_reg_params_t, field) + 1)) +// Discrete offset macro +#define DISCR_OFFSET(field) ((uint16_t)(offsetof(discrete_reg_params_t, field) + 1)) + +#define STR(fieldname) ((const char*)( fieldname )) +// Options can be used as bit masks or parameter limits +#define OPTS(min_val, max_val, step_val) { .opt1 = min_val, .opt2 = max_val, .opt3 = step_val } + +static const char *TAG = "MASTER_TEST"; + +// Enumeration of modbus device addresses accessed by master device +enum { + MB_DEVICE_ADDR1 = 1 // Only one slave device used for the test (add other slave addresses here) +}; + +// Enumeration of all supported CIDs for device (used in parameter definition table) +enum { + CID_INP_DATA_0 = 0, + CID_HOLD_DATA_0, + CID_INP_DATA_1, + CID_HOLD_DATA_1, + CID_INP_DATA_2, + CID_HOLD_DATA_2, + CID_HOLD_TEST_REG, + CID_RELAY_P1, + CID_RELAY_P2, + CID_DISCR_P1, + CID_COUNT +}; + +// Example Data (Object) Dictionary for Modbus parameters: +// The CID field in the table must be unique. +// Modbus Slave Addr field defines slave address of the device with correspond parameter. +// Modbus Reg Type - Type of Modbus register area (Holding register, Input Register and such). +// Reg Start field defines the start Modbus register number and Reg Size defines the number of registers for the characteristic accordingly. +// The Instance Offset defines offset in the appropriate parameter structure that will be used as instance to save parameter value. +// Data Type, Data Size specify type of the characteristic and its data size. +// Parameter Options field specifies the options that can be used to process parameter value (limits or masks). +// Access Mode - can be used to implement custom options for processing of characteristic (Read/Write restrictions, factory mode values and etc). +const mb_parameter_descriptor_t device_parameters[] = { + // { CID, Param Name, Units, Modbus Slave Addr, Modbus Reg Type, Reg Start, Reg Size, Instance Offset, Data Type, Data Size, Parameter Options, Access Mode} + { CID_INP_DATA_0, STR("Data_channel_0"), STR("Volts"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 0, 2, + INPUT_OFFSET(input_data0), PARAM_TYPE_FLOAT, 4, OPTS( -10, 10, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_0, STR("Humidity_1"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 0, 2, + HOLD_OFFSET(holding_data0), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_1, STR("Temperature_1"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 2, 2, + INPUT_OFFSET(input_data1), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_1, STR("Humidity_2"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 2, 2, + HOLD_OFFSET(holding_data1), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_INP_DATA_2, STR("Temperature_2"), STR("C"), MB_DEVICE_ADDR1, MB_PARAM_INPUT, 4, 2, + INPUT_OFFSET(input_data2), PARAM_TYPE_FLOAT, 4, OPTS( -40, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_DATA_2, STR("Humidity_3"), STR("%rH"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 4, 2, + HOLD_OFFSET(holding_data2), PARAM_TYPE_FLOAT, 4, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_HOLD_TEST_REG, STR("Test_regs"), STR("__"), MB_DEVICE_ADDR1, MB_PARAM_HOLDING, 10, 58, + HOLD_OFFSET(test_regs), PARAM_TYPE_ASCII, 116, OPTS( 0, 100, 1 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P1, STR("RelayP1"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 2, 6, + COIL_OFFSET(coils_port0), PARAM_TYPE_U8, 1, OPTS( 0xAA, 0x15, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }, + { CID_RELAY_P2, STR("RelayP2"), STR("on/off"), MB_DEVICE_ADDR1, MB_PARAM_COIL, 10, 6, + COIL_OFFSET(coils_port1), PARAM_TYPE_U8, 1, OPTS( 0x55, 0x2A, 0 ), PAR_PERMS_READ_WRITE_TRIGGER }, +}; + +// Calculate number of parameters in the table +const uint16_t num_device_parameters = (sizeof(device_parameters)/sizeof(device_parameters[0])); + +// The function to get pointer to parameter storage (instance) according to parameter description table +static void* master_get_param_data(const mb_parameter_descriptor_t* param_descriptor) +{ + assert(param_descriptor != NULL); + void* instance_ptr = NULL; + if (param_descriptor->param_offset != 0) { + switch(param_descriptor->mb_param_type) + { + case MB_PARAM_HOLDING: + instance_ptr = ((void*)&holding_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_INPUT: + instance_ptr = ((void*)&input_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_COIL: + instance_ptr = ((void*)&coil_reg_params + param_descriptor->param_offset - 1); + break; + case MB_PARAM_DISCRETE: + instance_ptr = ((void*)&discrete_reg_params + param_descriptor->param_offset - 1); + break; + default: + instance_ptr = NULL; + break; + } + } else { + ESP_LOGE(TAG, "Wrong parameter offset for CID #%u", (unsigned)param_descriptor->cid); + assert(instance_ptr != NULL); + } + return instance_ptr; +} + +// User operation function to read slave values and check alarm +static void master_operation_func(void *arg) +{ + esp_err_t err = ESP_OK; + float value = 0; + bool alarm_state = false; + const mb_parameter_descriptor_t* param_descriptor = NULL; + + ESP_LOGI(TAG, "Start modbus test..."); + + for(uint16_t retry = 0; retry <= MASTER_MAX_RETRY && (!alarm_state); retry++) { + // Read all found characteristics from slave(s) + for (uint16_t cid = 0; (err != ESP_ERR_NOT_FOUND) && cid < MASTER_MAX_CIDS; cid++) + { + // Get data from parameters description table + // and use this information to fill the characteristics description table + // and having all required fields in just one table + err = mbc_master_get_cid_info(cid, ¶m_descriptor); + if ((err != ESP_ERR_NOT_FOUND) && (param_descriptor != NULL)) { + void* temp_data_ptr = master_get_param_data(param_descriptor); + assert(temp_data_ptr); + uint8_t type = 0; + if ((param_descriptor->param_type == PARAM_TYPE_ASCII) && + (param_descriptor->cid == CID_HOLD_TEST_REG)) { + // Check for long array of registers of type PARAM_TYPE_ASCII + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = (0x%" PRIx32 ") read successful.", + param_descriptor->cid, + param_descriptor->param_key, + param_descriptor->param_units, + *(uint32_t*)temp_data_ptr); + // Initialize data of test array and write to slave + if (*(uint32_t*)temp_data_ptr != 0xAAAAAAAA) { + memset((void*)temp_data_ptr, 0xAA, param_descriptor->param_size); + *(uint32_t*)temp_data_ptr = 0xAAAAAAAA; + err = mbc_master_set_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = (0x%" PRIx32 "), write successful.", + param_descriptor->cid, + param_descriptor->param_key, + param_descriptor->param_units, + *(uint32_t*)temp_data_ptr); + } else { + ESP_LOGE(TAG, "Characteristic #%u (%s) write fail, err = 0x%x (%s).", + param_descriptor->cid, + param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } + } else { + ESP_LOGE(TAG, "Characteristic #%u (%s) read fail, err = 0x%x (%s).", + param_descriptor->cid, + param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } else { + err = mbc_master_get_parameter(cid, (char*)param_descriptor->param_key, + (uint8_t*)temp_data_ptr, &type); + if (err == ESP_OK) { + if ((param_descriptor->mb_param_type == MB_PARAM_HOLDING) || + (param_descriptor->mb_param_type == MB_PARAM_INPUT)) { + value = *(float*)temp_data_ptr; + ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = %f (0x%" PRIx32 ") read successful.", + param_descriptor->cid, + param_descriptor->param_key, + param_descriptor->param_units, + value, + *(uint32_t*)temp_data_ptr); + if (((value > param_descriptor->param_opts.max) || + (value < param_descriptor->param_opts.min))) { + alarm_state = true; + break; + } + } else { + uint8_t state = *(uint8_t*)temp_data_ptr; + const char* rw_str = (state & param_descriptor->param_opts.opt1) ? "ON" : "OFF"; + if ((state & param_descriptor->param_opts.opt2) == param_descriptor->param_opts.opt2) { + ESP_LOGI(TAG, "Characteristic #%u %s (%s) value = %s (0x%" PRIx8 ") read successful.", + param_descriptor->cid, + param_descriptor->param_key, + param_descriptor->param_units, + (const char*)rw_str, + *(uint8_t*)temp_data_ptr); + } else { + ESP_LOGE(TAG, "Characteristic #%u %s (%s) value = %s (0x%" PRIx8 "), unexpected value.", + param_descriptor->cid, + param_descriptor->param_key, + param_descriptor->param_units, + (const char*)rw_str, + *(uint8_t*)temp_data_ptr); + alarm_state = true; + break; + } + if (state & param_descriptor->param_opts.opt1) { + alarm_state = true; + break; + } + } + } else { + ESP_LOGE(TAG, "Characteristic #%u (%s) read fail, err = 0x%x (%s).", + param_descriptor->cid, + param_descriptor->param_key, + (int)err, + (char*)esp_err_to_name(err)); + } + } + vTaskDelay(POLL_TIMEOUT_TICS); // timeout between polls + } + } + vTaskDelay(UPDATE_CIDS_TIMEOUT_TICS); + } + + if (alarm_state) { + ESP_LOGI(TAG, "Alarm triggered by cid #%u.", param_descriptor->cid); + } else { + ESP_LOGE(TAG, "Alarm is not triggered after %u retries.", MASTER_MAX_RETRY); + } + ESP_LOGI(TAG, "Destroy master..."); + ESP_ERROR_CHECK(mbc_master_destroy()); +} + +// Modbus master initialization +static esp_err_t master_init(void) +{ + // Initialize and start Modbus controller + mb_communication_info_t comm = { + .port = MB_PORT_NUM, +#if CONFIG_MB_COMM_MODE_ASCII + .mode = MB_MODE_ASCII, +#elif CONFIG_MB_COMM_MODE_RTU + .mode = MB_MODE_RTU, +#endif + .baudrate = MB_DEV_SPEED, + .parity = MB_PARITY_NONE + }; + void* master_handler = NULL; + + esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &master_handler); + MB_RETURN_ON_FALSE((master_handler != NULL), ESP_ERR_INVALID_STATE, TAG, + "mb controller initialization fail."); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller initialization fail, returns(0x%x).", (int)err); + err = mbc_master_setup((void*)&comm); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller setup fail, returns(0x%x).", (int)err); + + // Set UART pin numbers + err = uart_set_pin(MB_PORT_NUM, CONFIG_MB_UART_TXD, CONFIG_MB_UART_RXD, + CONFIG_MB_UART_RTS, UART_PIN_NO_CHANGE); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb serial set pin failure, uart_set_pin() returned (0x%x).", (int)err); + + err = mbc_master_start(); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller start fail, returned (0x%x).", (int)err); + + // Set driver mode to Half Duplex + err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb serial set mode failure, uart_set_mode() returned (0x%x).", (int)err); + + vTaskDelay(5); + err = mbc_master_set_descriptor(&device_parameters[0], num_device_parameters); + MB_RETURN_ON_FALSE((err == ESP_OK), ESP_ERR_INVALID_STATE, TAG, + "mb controller set descriptor fail, returns(0x%x).", (int)err); + ESP_LOGI(TAG, "Modbus master stack initialized..."); + return err; +} + +void app_main(void) +{ + // Initialization of device peripheral and objects + ESP_ERROR_CHECK(master_init()); + vTaskDelay(10); + + master_operation_func(NULL); + ESP_LOGI("Test", "Start Modbus"); +} diff --git a/mb_override_component/sdkconfig.defaults b/mb_override_component/sdkconfig.defaults new file mode 100644 index 0000000..3c9c6de --- /dev/null +++ b/mb_override_component/sdkconfig.defaults @@ -0,0 +1,10 @@ +# +# Modbus configuration +# +CONFIG_MB_COMM_MODE_ASCII=y +CONFIG_MB_UART_BAUD_RATE=115200 +CONFIG_FMB_TIMER_PORT_ENABLED=n +CONFIG_FMB_TIMER_ISR_IN_IRAM=y +CONFIG_FMB_MASTER_DELAY_MS_CONVERT=200 +CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND=400 +CONFIG_FMB_TIMER_USE_ISR_DISPATCH_METHOD=y