diff --git a/Kconfig b/Kconfig index 47f0b77..5450bb0 100644 --- a/Kconfig +++ b/Kconfig @@ -33,7 +33,20 @@ menu "Modbus configuration" help Modbus TCP connection timeout in seconds. Once expired the current connection with the client will be closed - and Modbus slave will be waiting for new connection to accept. + and Modbus TCP slave will be waiting for new connection to accept. + This timeout is applicable for Modbus TCP master when it tries to connect to the Modbus TCP slaves. + + config FMB_TCP_KEEP_ALIVE_TOUT_SEC + int "Modbus TCP keep alive timeout (seconds)" + default 4 + range 1 7200 + depends on FMB_COMM_MODE_TCP_EN + help + This option is used to set keep-alive time for Modbus TCP connections. + This timeout is the interval after which the device sends keep-alive messages + to maintain a connection with a server before register the error. + The keep-alive time can be configured and should be set based on the actual needs of the application, + considering the trade-off between keeping the connection alive and minimizing system load. config FMB_TCP_UID_ENABLED bool "Modbus TCP enable UID (Unit Identifier) support" @@ -72,12 +85,12 @@ menu "Modbus configuration" then master can send next frame. config FMB_QUEUE_LENGTH - int "Modbus serial task queue length" - range 0 200 - default 20 + int "Modbus event task queue length" + range 10 500 + default 50 help - Modbus serial driver queue length. It is used by event queue task. - See the serial driver API for more information. + Modbus event queue length. It is used by event queue tasks + for corresponding communication mode. config FMB_PORT_TASK_STACK_SIZE int "Modbus port task stack size" diff --git a/docs/en/master_api_overview.rst b/docs/en/master_api_overview.rst index 0865907..cef03b9 100644 --- a/docs/en/master_api_overview.rst +++ b/docs/en/master_api_overview.rst @@ -571,6 +571,8 @@ The example to retrieve the slave identificator from slave: ESP_LOG_BUFFER_HEX_LEVEL("SLAVE_INFO", (void*)info_buf, sizeof(info_buf), ESP_LOG_WARN); } +.. note:: Please refer to :ref:`modbus_master_slave_configuration_aspects` for proper configuration. + .. _modbus_api_master_destroy: Modbus Master Teardown diff --git a/docs/en/overview_messaging_and_mapping.rst b/docs/en/overview_messaging_and_mapping.rst index dd1a71c..7fd63f8 100644 --- a/docs/en/overview_messaging_and_mapping.rst +++ b/docs/en/overview_messaging_and_mapping.rst @@ -75,14 +75,14 @@ The Modbus protocol allows devices to map data to four types of registers (Holdi .. _modbus_mapping_complex_data_types: Mapping Of Complex Data Types -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As per section 4.2 of Modbus specification, "MODBUS uses a ``big-Endian`` representation for addresses and data items. This means that when a numerical quantity larger than a single byte is transmitted, the most significant byte is sent first". The biggest official structure defined by the Modbus specification is a 16-bit word register, which is 2 bytes. However, vendors sometimes group two or even four 16-bit registers together to be interpretted as 32-bit or 64-bit values, respectively. It is also possible when the Modbus vendors group many registers together for serial numbers, text strings, time/date, etc. Regardless of how the vendor intends the data to be interpreted, the Modbus protocol itself simply transfers 16-bit word registers. These values grouped from registers may use either little-endian or big-endian register order. .. note:: Each individual 16-bit register, is encoded in big-endian order (assuming the Modbus device abides by the Modbus specification). However, the 32-bit and 64-bit types naming conventions like ABCD or ABCDEFGH, does not take into account the network format byte order of frame. For example: the ABCD prefix for 32-bit values means the common Modbus mapping format and corresponds to the CDAB on network format (order in the frame). Common Data Types Supported By Modbus Vendors -@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: Table 1 basic types used by Modbus vendors :widths: 8 3 20 @@ -276,6 +276,72 @@ The below diagrams show how the extended data types appear on network layer. The approach showed above can be used to pack the data into MBAP frames used by Modbus TCP as well as for other types with similar size. +.. _modbus_master_slave_configuration_aspects: + +Important Configuration Aspects +------------------------------- + +The slave and its behavior has some dependency with the master and its options. The master uses a timeout to determine if a slave is responding in a timely manner. This timeout is configured on the master side, not used by the slave. If the slave's response is not received within this timeout period, the master will consider the request a failure. + + * Master Timeout (``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND``): This is the duration a master will wait for a response from the slave. The default value set by the ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND`` kconfig option on the master side can be overridden for the concrete master instance in its communication options structure. + + * Slave Behavior: The slave itself does not use this timeout for its internal operations. Instead, it measures its request processing time, which is the time from receiving a master's request to sending the slave's response. If this processing time exceeds the master's configured timeout because the master sends a new request while the previous one is under processing, the slave will log a warning. + +The Race Condition +~~~~~~~~~~~~~~~~~~ + +A common issue occurs when the slave's Request Processing Time is longer than the Master Timeout. + +In this scenario: + + * The master sends a request. + + * The slave begins processing the request. + + * The master's timeout expires, and it marks the request as failed. + + * The master immediately sends a new request for the next transaction. + + * The slave finishes processing the first request and sends a response. + + * The slave then receives the master's new request, potentially before the previous response is sent. + +The TCP slave's log message, such as ``W (571368) mb_port.tcp.slave: 0x3ffc8ba4, node #1, socket(#56)(192.168.88.250), handling time [ms]: 1394, exceeds slave response time in master.``, indicates this condition. The slave is warning that its internal processing time of 1394 ms was longer than the timeout configured on the master. + +To prevent the race condition from occurring, the slave will discard the pending response to the master's first request to avoid potential errors and an overloaded input queue. On the master side, this situation results in a timeout error for the first request, followed by the successful start of the next one. If the reasons for the race condition are not addressed, this can lead to the slave's input queues becoming cluttered, potentially causing the slave to stop responding altogether. + +Corrective Actions +@@@@@@@@@@@@@@@@@@ + +To resolve this, user must: + + * Increase the Master Timeout (``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND``) to be significantly greater than the maximum expected Request Processing Time on the slave. + + * Reduce the request rate from the master nodes to give the slave more time to process each request. + +.. note:: Set the Master Timeout to a value greater than the worst-case Round-Trip Time (RTT) on your network. A reasonable starting point is at least 1000 ms, but you should perform network measurements under maximum load to determine an appropriate value. + +Keep-Alive Mechanism for TCP Slave +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Modbus TCP slave includes a `TCP keep-alive mechanism `__ to detect dead connections and free up resources. This feature is enabled by default. + +* Keep-Alive Timeout (``CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC``): This is the time after which the slave will send a keep-alive probe to the master if no data has been received. If the master does not respond to this probe, the slave considers the connection dead and closes it. The keep-alive timeout is configured using the ``CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC`` kconfig option. + +Corrective Actions +@@@@@@@@@@@@@@@@@@ + +The TCP slave logs an error, such as ``E (227779) mb_port.tcp.slave: 0x3ffc8abc, node #4, socket(#59)(192.168.88.252), communication fail, err= -11``, when a connection is closed due to a keep-alive timeout. + +.. note:: The keep-alive timeout should be configured carefully. It is recommended to set it to a value greater than the Master Timeout to avoid prematurely dropping a connection during a long-running transaction. Be mindful of the trade-off: a short timeout can drop good connections during temporary network issues and increase the RTT time, while a very long timeout can waste resources on dead connections and increase communication error detection time. + +Manage Multiple Connections +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Modbus TCP slave supports multiple connections from external master nodes. The maximum number of simultaneous connections is configured using the ``CONFIG_FMB_TCP_PORT_MAX_CONN`` kconfig option. If the number of incoming connections exceeds this value, the slave will reject new connections. The slave manages the active connections and is able to close inactive connections that do not send requests for longer than the ``CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC`` time. + +.. note:: Each additional master connection can increase the Request Processing Time on the slave. This, in turn, impacts the overall Round-Trip Time (RTT), which is the total time for a request to travel to the slave and a response to return to the master. + The following sections give an overview of how to use the ESP_Modbus component found under `components/esp-modbus`. The sections cover initialization of a Modbus port, and the setup a master or slave device accordingly: - :ref:`modbus_api_port_initialization` diff --git a/docs/en/port_initialization.rst b/docs/en/port_initialization.rst index 0ed1ad1..2717679 100644 --- a/docs/en/port_initialization.rst +++ b/docs/en/port_initialization.rst @@ -111,8 +111,6 @@ The format of slave definition following the notation `UID;slave_host_ip_or_dns_ The slave IP addresses of the slaves can be resolved automatically by the stack using mDNS service as described in the example. In this case each slave has to use the mDNS service support and define its host name appropriately. Refer to :ref:`example TCP master `, :ref:`example TCP slave ` for more information. -.. note:: The Modbus Master TCP functionality is under testing and competition status will be announced later over official channels. - .. _modbus_api_slave_setup_communication_options: Slave Communication Options @@ -168,5 +166,3 @@ This example code to initialize Modbus TCP slave: } .. note:: Refer to `esp_netif component `__ for more information about network interface initialization. - -.. note:: The Modbus Slave TCP functionality is under testing and the competition status will be announced later over official channels. diff --git a/docs/en/slave_api_overview.rst b/docs/en/slave_api_overview.rst index dda2330..453450d 100644 --- a/docs/en/slave_api_overview.rst +++ b/docs/en/slave_api_overview.rst @@ -15,7 +15,7 @@ The sections below represent typical programming workflow for the slave API whic .. _modbus_api_slave_configure_descriptor: Configuring Slave Data Access -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following functions must be called when the Modbus controller slave port is already initialized. Refer to :ref:`modbus_api_port_initialization`. @@ -74,7 +74,7 @@ The function initializes Modbus communication descriptors for each type of Modbu At least one area descriptor per each Modbus register type must be set in order to provide register access to its area. If the master tries to access an undefined area, the stack will generate a Modbus exception. -The stack supports the extended data types when enabled through the the option ``CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND`` in kconfig menu. +The stack supports the extended data types when enabled through the option ``CONFIG_FMB_EXT_TYPE_SUPPORT`` in kconfig menu. In this case the mapped data values can be initialized to specific format using :ref:`modbus_api_endianness_conversion`. Please refer to secton :ref:`modbus_mapping_complex_data_types` for more information about data types. @@ -165,7 +165,7 @@ Example to get the actual slave identificator: .. _modbus_api_slave_handler_customization: Slave Customize Function Handlers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Slave object contains the command handling table to define specific handling functionality for each supported Modbus command. The default handling functions in this table support the most common Modbus commands. However, the list of commands can be extended by adding a new command into the handling table with its custom handling behavior. It is also possible to override the function handler for a specific command. The below described API functions allow using this behavior for Slave objects. @@ -231,7 +231,7 @@ Refer to :ref:`example Serial slave ` for more information. .. _modbus_api_slave_communication: Slave Communication -^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~ The function below is used to start Modbus controller interface and allows communication. @@ -305,6 +305,8 @@ Example to get event when holding or input registers accessed in the slave: (uint32_t)reg_info.size); } +.. note:: Please refer to :ref:`modbus_master_slave_configuration_aspects` for proper configuration. + :cpp:func:`mbc_slave_lock` :cpp:func:`mbc_slave_unlock` @@ -331,10 +333,11 @@ The access to registered area shared between several slave objects from user app holding_reg_area[2] = 123; portEXIT_CRITICAL(¶m_lock); + .. _modbus_api_slave_destroy: Modbus Slave Teardown -^^^^^^^^^^^^^^^^^^^^^ +~~~~~~~~~~~~~~~~~~~~~ This function stops the Modbus communication stack, destroys the controller interface, and frees all used active objects allocated for the slave. diff --git a/modbus/mb_objects/common/mb_types.h b/modbus/mb_objects/common/mb_types.h index a765301..c4d57d1 100644 --- a/modbus/mb_objects/common/mb_types.h +++ b/modbus/mb_objects/common/mb_types.h @@ -78,11 +78,7 @@ typedef enum mb_event_enum { EV_EXECUTE = 0x0008, /*!< Execute function. */ EV_FRAME_TRANSMIT = 0x0010, /*!< Transmission started . */ EV_FRAME_SENT = 0x0020, /*!< Frame sent. */ - EV_ERROR_PROCESS = 0x0040, /*!< Error process state. */ - EV_MASTER_ERROR_RESPOND_TIMEOUT = 0x0080, /*!< Request respond timeout. */ - EV_MASTER_ERROR_RECEIVE_DATA = 0x0100, /*!< Request receive data error. */ - EV_MASTER_ERROR_EXECUTE_FUNCTION = 0x0200, /*!< Request execute function error. */ - EV_MASTER_PROCESS_SUCCESS = 0x0400 /*!< Master error process. */ + EV_ERROR_PROCESS = 0x0040 /*!< Error process state. */ } mb_event_enum_t; /*! \ingroup modbus @@ -109,11 +105,11 @@ typedef mb_exception_t (*mb_fn_handler_fp)(void *, uint8_t *frame_ptr, uint16_t * \brief Error event type */ typedef enum mb_err_event_enum { - EV_ERROR_INIT, /*!< No error, initial state. */ - EV_ERROR_RESPOND_TIMEOUT, /*!< Slave respond timeout. */ - EV_ERROR_RECEIVE_DATA, /*!< Receive frame data error. */ - EV_ERROR_EXECUTE_FUNCTION, /*!< Execute function error. */ - EV_ERROR_OK /*!< No error, processing completed. */ + EV_ERROR_INIT, /*!< No error, initial state. */ + EV_ERROR_RESPOND_TIMEOUT, /*!< Slave respond timeout. */ + EV_ERROR_RECEIVE_DATA, /*!< Receive frame data error. */ + EV_ERROR_EXECUTE_FUNCTION, /*!< Execute function error. */ + EV_ERROR_OK /*!< No error, processing completed. */ } mb_err_event_t; typedef struct mb_event_s { @@ -150,9 +146,9 @@ typedef enum */ typedef enum { - MB_TMODE_T35, /*!< Master receive frame T3.5 timeout. */ - MB_TMODE_RESPOND_TIMEOUT, /*!< Master wait respond for slave. */ - MB_TMODE_CONVERT_DELAY /*!< Master sent broadcast , then delay sometime.*/ + MB_TMODE_T35, /*!< Master receive frame T3.5 timeout. */ + MB_TMODE_RESPOND_TIMEOUT, /*!< Master wait respond for slave. */ + MB_TMODE_CONVERT_DELAY /*!< Master sent broadcast , then delay sometime.*/ } mb_timer_mode_enum_t; #ifdef __cplusplus diff --git a/modbus/mb_objects/mb_master.c b/modbus/mb_objects/mb_master.c index c399ee6..fae60ae 100644 --- a/modbus/mb_objects/mb_master.c +++ b/modbus/mb_objects/mb_master.c @@ -497,25 +497,25 @@ static uint8_t mbm_get_dest_addr(mb_base_t *inst) void mbm_error_cb_respond_timeout(mb_base_t *inst, uint8_t dest_addr, const uint8_t *pdu_data, uint16_t pdu_length) { - mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_ERROR_RESPOND_TIMEOUT); + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_RESPOND_TIMEOUT); ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); } void mbm_error_cb_receive_data(mb_base_t *inst, uint8_t dest_addr, const uint8_t *pdu_data, uint16_t pdu_length) { - mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_ERROR_RECEIVE_DATA); + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_RECEIVE_DATA); ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); } void mbm_error_cb_execute_function(mb_base_t *inst, uint8_t dest_address, const uint8_t *pdu_data, uint16_t pdu_length) { - mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_ERROR_EXECUTE_FUNCTION); + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_EXECUTE_FUNCTION); ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); } void mbm_error_cb_request_success(mb_base_t *inst, uint8_t dest_address, const uint8_t *pdu_data, uint16_t pdu_length) { - mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_MASTER_PROCESS_SUCCESS); + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_OK); ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); } @@ -553,6 +553,8 @@ mb_err_enum_t mbm_poll(mb_base_t *inst) mb_port_event_set_err_type(MB_OBJ(inst->port_obj), EV_ERROR_RESPOND_TIMEOUT); (void)mb_port_event_post(MB_OBJ(inst->port_obj), EVENT(EV_ERROR_PROCESS)); ESP_LOGE(TAG, MB_OBJ_FMT", frame send error. %d", MB_OBJ_PARENT(inst), (int)status); + } else { + (void)mb_port_event_post(MB_OBJ(inst->port_obj), EVENT(EV_FRAME_SENT)); } // Initialize modbus transaction mbm_obj->curr_trans_id = event.trans_id; diff --git a/modbus/mb_objects/mb_slave.c b/modbus/mb_objects/mb_slave.c index 0a3a546..1c35b3e 100644 --- a/modbus/mb_objects/mb_slave.c +++ b/modbus/mb_objects/mb_slave.c @@ -423,6 +423,30 @@ mb_err_enum_t mbs_disable(mb_base_t *inst) return status; } +void mbs_error_cb_respond_timeout(mb_base_t *inst, uint8_t dest_addr, const uint8_t *pdu_data, uint16_t pdu_length) +{ + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_RESPOND_TIMEOUT); + ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); +} + +void mbs_error_cb_receive_data(mb_base_t *inst, uint8_t dest_addr, const uint8_t *pdu_data, uint16_t pdu_length) +{ + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_RECEIVE_DATA); + ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); +} + +void mbs_error_cb_execute_function(mb_base_t *inst, uint8_t dest_address, const uint8_t *pdu_data, uint16_t pdu_length) +{ + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_EXECUTE_FUNCTION); + ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); +} + +void mbs_error_cb_request_success(mb_base_t *inst, uint8_t dest_address, const uint8_t *pdu_data, uint16_t pdu_length) +{ + mb_port_event_set_resp_flag(MB_BASE2PORT(inst), EV_ERROR_OK); + ESP_LOG_BUFFER_HEX_LEVEL(__func__, (void *)pdu_data, pdu_length, ESP_LOG_DEBUG); +} + mb_err_enum_t mbs_poll(mb_base_t *inst) { mbs_object_t *mbs_obj = MB_GET_OBJ_CTX(inst, mbs_object_t, base);; @@ -430,6 +454,8 @@ mb_err_enum_t mbs_poll(mb_base_t *inst) mb_exception_t exception; mb_err_enum_t status = MB_ENOERR; mb_event_t event; + mb_err_event_t error_type = EV_ERROR_INIT; + uint64_t time_div_us = 0; /* Check if the protocol stack is ready. */ if (mbs_obj->cur_state != STATE_ENABLED) { @@ -451,13 +477,19 @@ mb_err_enum_t mbs_poll(mb_base_t *inst) // Check if the frame is for us. If not ,send an error process event. if (status == MB_ENOERR) { // Check if the frame is for us. If not ignore the frame. - if((mbs_obj->rcv_addr == mbs_obj->mb_address) || (mbs_obj->rcv_addr == MB_ADDRESS_BROADCAST) + if((mbs_obj->rcv_addr == mbs_obj->mb_address) || (mbs_obj->rcv_addr == MB_ADDRESS_BROADCAST) || (mbs_obj->rcv_addr == MB_TCP_PSEUDO_ADDRESS)) { mbs_obj->curr_trans_id = event.get_ts; (void)mb_port_event_post(MB_OBJ(inst->port_obj), EVENT(EV_EXECUTE | EV_TRANS_START)); MB_PRT_BUF(inst->descr.parent_name, ":MB_RECV", &mbs_obj->frame[MB_PDU_FUNC_OFF], mbs_obj->length, ESP_LOG_DEBUG); } + } else { + ESP_LOGE(TAG, MB_OBJ_FMT":frame receive error. %d", MB_OBJ_PARENT(inst), (int)status); + // If the frame was not received correctly, post an error event. + mb_port_event_set_err_type(MB_OBJ(inst->port_obj), EV_ERROR_RECEIVE_DATA); + (void)mb_port_event_post(MB_OBJ(inst->port_obj), EVENT(EV_ERROR_PROCESS)); + mbs_obj->length = 0; // Reset length to avoid processing junk data. } break; @@ -481,7 +513,11 @@ mb_err_enum_t mbs_poll(mb_base_t *inst) (uint16_t)mbs_obj->length, ESP_LOG_DEBUG); status = MB_OBJ(inst->transp_obj)->frm_send(inst->transp_obj, mbs_obj->rcv_addr, mbs_obj->frame, mbs_obj->length); if (status != MB_ENOERR) { - ESP_LOGE(TAG, MB_OBJ_FMT":frame send error. %d", MB_OBJ_PARENT(inst), (int)status); + ESP_LOGE(TAG, MB_OBJ_FMT": frame send error: %d.", MB_OBJ_PARENT(inst), (int)status); + mb_port_event_set_err_type(MB_OBJ(inst->port_obj), EV_ERROR_RESPOND_TIMEOUT); + (void)mb_port_event_post(MB_OBJ(inst->port_obj), EVENT(EV_ERROR_PROCESS)); + } else { + (void)mb_port_event_post(MB_OBJ(inst->port_obj), EVENT(EV_FRAME_SENT)); } } break; @@ -492,9 +528,48 @@ mb_err_enum_t mbs_poll(mb_base_t *inst) case EV_FRAME_SENT: ESP_LOGD(TAG, MB_OBJ_FMT":EV_MASTER_FRAME_SENT", MB_OBJ_PARENT(inst)); - uint64_t time_div_us = mbs_obj->curr_trans_id ? (event.get_ts - mbs_obj->curr_trans_id) : 0; + error_type = mb_port_event_get_err_type(MB_OBJ(inst->port_obj)); + if (error_type == EV_ERROR_INIT) { + ESP_LOGD(TAG, MB_OBJ_FMT", set event EV_ERROR_OK", MB_OBJ_PARENT(inst)); + mb_port_event_set_err_type(MB_OBJ(inst->port_obj), EV_ERROR_OK); + (void)mb_port_event_post(MB_OBJ(inst->port_obj), EVENT(EV_ERROR_PROCESS)); + } else { + ESP_LOGD(TAG, MB_OBJ_FMT", incorrect initial error type.", MB_OBJ_PARENT(inst)); + } + break; + + case EV_ERROR_PROCESS: + ESP_LOGD(TAG, MB_OBJ_FMT":EV_ERROR_PROCESS", MB_OBJ_PARENT(inst)); + // stop timer and execute specified error process callback function. + mb_port_timer_disable(MB_OBJ(inst->port_obj)); + error_type = mb_port_event_get_err_type(MB_OBJ(inst->port_obj)); + switch (error_type) + { + case EV_ERROR_RESPOND_TIMEOUT: + mbs_error_cb_respond_timeout(inst, mbs_obj->rcv_addr, + mbs_obj->frame, mbs_obj->length); + break; + case EV_ERROR_RECEIVE_DATA: + mbs_error_cb_receive_data(inst, mbs_obj->rcv_addr, + mbs_obj->frame, mbs_obj->length); + break; + case EV_ERROR_EXECUTE_FUNCTION: + mbs_error_cb_execute_function(inst, mbs_obj->rcv_addr, + mbs_obj->frame, mbs_obj->length); + break; + case EV_ERROR_OK: + mbs_error_cb_request_success(inst, mbs_obj->rcv_addr, + mbs_obj->frame, mbs_obj->length); + break; + default: + ESP_LOGE(TAG, MB_OBJ_FMT", incorrect error type = %d.", MB_OBJ_PARENT(inst), (int)error_type); + break; + } + mb_port_event_set_err_type(MB_OBJ(inst->port_obj), EV_ERROR_INIT); + time_div_us = mbs_obj->curr_trans_id ? (event.get_ts - mbs_obj->curr_trans_id) : 0; mbs_obj->curr_trans_id = 0; ESP_LOGD(TAG, MB_OBJ_FMT", transaction processing time(us) = %" PRId64, MB_OBJ_PARENT(inst), time_div_us); + mb_port_event_res_release(MB_OBJ(inst->port_obj)); break; default: diff --git a/modbus/mb_ports/common/mb_transaction.c b/modbus/mb_ports/common/mb_transaction.c index 3b13ddc..ac9c124 100644 --- a/modbus/mb_ports/common/mb_transaction.c +++ b/modbus/mb_ports/common/mb_transaction.c @@ -16,7 +16,7 @@ typedef struct transaction_item { uint8_t *buffer; uint16_t len; int node_id; - int msg_id; + uint16_t msg_id; void *pnode; transaction_tick_t tick; _Atomic(int) state; @@ -45,8 +45,11 @@ transaction_handle_t transaction_init(void) transaction_item_handle_t transaction_enqueue(transaction_handle_t transaction, transaction_message_handle_t message, transaction_tick_t tick) { + ESP_MEM_CHECK(TAG, (message && message->buffer && message->len), { + return NULL; + }); transaction_item_handle_t item = calloc(1, sizeof(transaction_item_t)); - ESP_MEM_CHECK(TAG, item, { + ESP_MEM_CHECK(TAG, (item), { return NULL; }); CRITICAL_SECTION_LOCK(transaction->lock); @@ -56,17 +59,7 @@ transaction_item_handle_t transaction_enqueue(transaction_handle_t transaction, item->msg_id = message->msg_id; item->len = message->len; item->state = QUEUED; - if (!message->buffer) { - item->buffer = heap_caps_malloc(message->len, TRANSACTION_MEMORY); - memcpy(item->buffer, message->buffer, message->len); - } else { - item->buffer = message->buffer; - } - ESP_MEM_CHECK(TAG, item->buffer, { - free(item); - CRITICAL_SECTION_UNLOCK(transaction->lock); - return NULL; - }); + item->buffer = message->buffer; STAILQ_INSERT_TAIL(transaction->list, item, next); transaction->size += item->len; CRITICAL_SECTION_UNLOCK(transaction->lock); @@ -74,7 +67,7 @@ transaction_item_handle_t transaction_enqueue(transaction_handle_t transaction, return item; } -transaction_item_handle_t transaction_get(transaction_handle_t transaction, int msg_id) +transaction_item_handle_t transaction_get(transaction_handle_t transaction, uint16_t msg_id) { transaction_item_handle_t item; CRITICAL_SECTION_LOCK(transaction->lock); @@ -138,6 +131,14 @@ esp_err_t transaction_delete_item(transaction_handle_t transaction, transaction_ return ESP_FAIL; } +uint16_t transaction_item_get_id(transaction_item_handle_t item) +{ + if (item) { + return item->msg_id; + } + return 0xFFFF; +} + uint8_t *transaction_item_get_data(transaction_item_handle_t item, size_t *len, uint16_t *msg_id, int *node_id) { if (item) { @@ -155,7 +156,7 @@ uint8_t *transaction_item_get_data(transaction_item_handle_t item, size_t *len, return NULL; } -esp_err_t transaction_delete(transaction_handle_t transaction, int msg_id) +esp_err_t transaction_delete(transaction_handle_t transaction, uint16_t msg_id) { transaction_item_handle_t item, tmp; CRITICAL_SECTION_LOCK(transaction->lock); @@ -174,7 +175,7 @@ esp_err_t transaction_delete(transaction_handle_t transaction, int msg_id) return ESP_FAIL; } -esp_err_t transaction_set_state(transaction_handle_t transaction, int msg_id, pending_state_t state) +esp_err_t transaction_set_state(transaction_handle_t transaction, uint16_t msg_id, pending_state_t state) { transaction_item_handle_t item = transaction_get(transaction, msg_id); if (item) { @@ -201,7 +202,15 @@ esp_err_t transaction_item_set_state(transaction_item_handle_t item, pending_sta return ESP_FAIL; } -esp_err_t transaction_set_tick(transaction_handle_t transaction, int msg_id, transaction_tick_t tick) +transaction_tick_t transaction_item_get_tick(transaction_item_handle_t item) +{ + if (item) { + return item->tick; + } + return 0; +} + +esp_err_t transaction_set_tick(transaction_handle_t transaction, uint16_t msg_id, transaction_tick_t tick) { transaction_item_handle_t item = transaction_get(transaction, msg_id); if (item) { @@ -211,9 +220,9 @@ esp_err_t transaction_set_tick(transaction_handle_t transaction, int msg_id, tra return ESP_FAIL; } -int transaction_delete_single_expired(transaction_handle_t transaction, transaction_tick_t current_tick, transaction_tick_t timeout) +uint16_t transaction_delete_single_expired(transaction_handle_t transaction, transaction_tick_t current_tick, transaction_tick_t timeout) { - int msg_id = -1; + uint16_t msg_id = 0xFFFF; transaction_item_handle_t item; CRITICAL_SECTION_LOCK(transaction->lock); STAILQ_FOREACH(item, transaction->list, next) { @@ -226,12 +235,29 @@ int transaction_delete_single_expired(transaction_handle_t transaction, transact CRITICAL_SECTION_UNLOCK(transaction->lock); return msg_id; } - } CRITICAL_SECTION_UNLOCK(transaction->lock); return msg_id; } +int transaction_delete_by_node_id(transaction_handle_t transaction, int node_id) +{ + int deleted_items = 0; + transaction_item_handle_t item, tmp; + CRITICAL_SECTION_LOCK(transaction->lock); + STAILQ_FOREACH_SAFE(item, transaction->list, next, tmp) { + if (item->node_id == node_id) { + STAILQ_REMOVE(transaction->list, item, transaction_item, next); + free(item->buffer); + transaction->size -= item->len; + free(item); + deleted_items ++; + } + } + CRITICAL_SECTION_UNLOCK(transaction->lock); + return deleted_items; +} + int transaction_delete_expired(transaction_handle_t transaction, transaction_tick_t current_tick, transaction_tick_t timeout) { int deleted_items = 0; diff --git a/modbus/mb_ports/common/mb_transaction.h b/modbus/mb_ports/common/mb_transaction.h index 0dc68c5..23424ec 100644 --- a/modbus/mb_ports/common/mb_transaction.h +++ b/modbus/mb_ports/common/mb_transaction.h @@ -27,13 +27,13 @@ typedef struct transaction_item *transaction_item_handle_t; typedef struct transaction_message { uint8_t *buffer; uint16_t len; - int msg_id; + uint16_t msg_id; int node_id; void *pnode; } transaction_message_t; typedef struct transaction_message *transaction_message_handle_t; -typedef long long transaction_tick_t; +typedef uint64_t transaction_tick_t; typedef enum pending_state { INIT, @@ -48,12 +48,14 @@ typedef enum pending_state { transaction_handle_t transaction_init(void); transaction_item_handle_t transaction_enqueue(transaction_handle_t transaction, transaction_message_handle_t message, transaction_tick_t tick); -transaction_item_handle_t transaction_dequeue(transaction_handle_t transaction, pending_state_t state, transaction_tick_t *tick); -transaction_item_handle_t transaction_get(transaction_handle_t transaction, int msg_id); +transaction_item_handle_t transaction_dequeue(transaction_handle_t transaction, pending_state_t pending, transaction_tick_t *tick); +transaction_item_handle_t transaction_get(transaction_handle_t transaction, uint16_t msg_id); transaction_item_handle_t transaction_get_first(transaction_handle_t transaction); +uint16_t transaction_item_get_id(transaction_item_handle_t item); uint8_t *transaction_item_get_data(transaction_item_handle_t item, size_t *len, uint16_t *msg_id, int *node_id); -esp_err_t transaction_delete(transaction_handle_t transaction, int msg_id); +esp_err_t transaction_delete(transaction_handle_t transaction, uint16_t msg_id); esp_err_t transaction_delete_item(transaction_handle_t transaction, transaction_item_handle_t item); +int transaction_delete_by_node_id(transaction_handle_t transaction, int node_id); int transaction_delete_expired(transaction_handle_t transaction, transaction_tick_t current_tick, transaction_tick_t timeout); /** @@ -61,11 +63,12 @@ int transaction_delete_expired(transaction_handle_t transaction, transaction_tic * * @return msg id of the deleted message, -1 if no expired message in the transaction */ -int transaction_delete_single_expired(transaction_handle_t transaction, transaction_tick_t current_tick, transaction_tick_t timeout); -esp_err_t transaction_set_state(transaction_handle_t transaction, int msg_id, pending_state_t state); +uint16_t transaction_delete_single_expired(transaction_handle_t transaction, transaction_tick_t current_tick, transaction_tick_t timeout); +esp_err_t transaction_set_state(transaction_handle_t transaction, uint16_t msg_id, pending_state_t pending); pending_state_t transaction_item_get_state(transaction_item_handle_t item); esp_err_t transaction_item_set_state(transaction_item_handle_t item, pending_state_t state); -esp_err_t transaction_set_tick(transaction_handle_t transaction, int msg_id, transaction_tick_t tick); +esp_err_t transaction_set_tick(transaction_handle_t transaction, uint16_t msg_id, transaction_tick_t tick); +transaction_tick_t transaction_item_get_tick(transaction_item_handle_t item); uint64_t transaction_get_size(transaction_handle_t transaction); void transaction_destroy(transaction_handle_t transaction); void transaction_delete_all_items(transaction_handle_t transaction); diff --git a/modbus/mb_ports/common/port_common.h b/modbus/mb_ports/common/port_common.h index 99000a4..0066005 100644 --- a/modbus/mb_ports/common/port_common.h +++ b/modbus/mb_ports/common/port_common.h @@ -77,10 +77,10 @@ void unlock_obj(_lock_t *lock_ptr); spinlock_release(&lock); \ } while (0) -#define MB_EVENT_REQ_MASK (EventBits_t)(EV_MASTER_PROCESS_SUCCESS | \ - EV_MASTER_ERROR_RESPOND_TIMEOUT | \ - EV_MASTER_ERROR_RECEIVE_DATA | \ - EV_MASTER_ERROR_EXECUTE_FUNCTION) +#define MB_EVENT_REQ_MASK (EventBits_t)(EV_ERROR_OK | \ + EV_ERROR_RESPOND_TIMEOUT | \ + EV_ERROR_RECEIVE_DATA | \ + EV_ERROR_EXECUTE_FUNCTION) #define MB_PORT_CHECK_EVENT(event, mask) (event & mask) #define MB_PORT_CLEAR_EVENT(event, mask) \ diff --git a/modbus/mb_ports/common/port_event.c b/modbus/mb_ports/common/port_event.c index e442c5e..9af44ca 100644 --- a/modbus/mb_ports/common/port_event.c +++ b/modbus/mb_ports/common/port_event.c @@ -144,7 +144,7 @@ bool mb_port_event_res_take(mb_port_base_t *inst, uint32_t timeout) { MB_RETURN_ON_FALSE((inst && inst->event_obj && inst->event_obj->resource_hdl), false, TAG, "incorrect object handle."); - BaseType_t status = pdTRUE; + BaseType_t status = pdFALSE; status = xSemaphoreTake(inst->event_obj->resource_hdl, timeout); ESP_LOGD(TAG, "%s, mb take resource, (%" PRIu32 " ticks).", inst->descr.parent_name, timeout); return (bool)status; @@ -185,13 +185,13 @@ mb_err_enum_t mb_port_event_wait_req_finish(mb_port_base_t *inst) // if we wait for certain event bits but get from poll subset ESP_LOGE(TAG, "%s, %s: incorrect event set = 0x%x", inst->descr.parent_name, __func__, (int)rcv_event); } - if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_PROCESS_SUCCESS)) { + if (MB_PORT_CHECK_EVENT(rcv_event, EV_ERROR_OK)) { err_status = MB_ENOERR; - } else if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_ERROR_RESPOND_TIMEOUT)) { + } else if (MB_PORT_CHECK_EVENT(rcv_event, EV_ERROR_RESPOND_TIMEOUT)) { err_status = MB_ETIMEDOUT; - } else if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_ERROR_RECEIVE_DATA)) { + } else if (MB_PORT_CHECK_EVENT(rcv_event, EV_ERROR_RECEIVE_DATA)) { err_status = MB_ERECVDATA; - } else if (MB_PORT_CHECK_EVENT(rcv_event, EV_MASTER_ERROR_EXECUTE_FUNCTION)) { + } else if (MB_PORT_CHECK_EVENT(rcv_event, EV_ERROR_EXECUTE_FUNCTION)) { err_status = MB_EILLFUNC; } } else { diff --git a/modbus/mb_ports/common/port_other.c b/modbus/mb_ports/common/port_other.c index b005b2f..eaade6c 100644 --- a/modbus/mb_ports/common/port_other.c +++ b/modbus/mb_ports/common/port_other.c @@ -62,8 +62,12 @@ esp_err_t queue_push(QueueHandle_t queue, void *buf, size_t len, frame_entry_t * { frame_entry_t frame_info = {0}; - if (!queue) { // || !buf || (len <= 0) - return -1; + if (!queue) { + return ESP_ERR_INVALID_ARG; + } + + if (!uxQueueSpacesAvailable(queue)) { + return ESP_ERR_NO_MEM; } if (frame) { diff --git a/modbus/mb_ports/common/port_timer.c b/modbus/mb_ports/common/port_timer.c index c884311..8e4af4c 100644 --- a/modbus/mb_ports/common/port_timer.c +++ b/modbus/mb_ports/common/port_timer.c @@ -117,7 +117,6 @@ void mb_port_timer_us(mb_port_base_t *inst, uint64_t timeout_us) atomic_store(&(inst->timer_obj->timer_state), false); } - inline void mb_port_set_cur_timer_mode(mb_port_base_t *inst, mb_timer_mode_enum_t tmr_mode) { atomic_store(&(inst->timer_obj->timer_mode), tmr_mode); diff --git a/modbus/mb_ports/serial/port_serial.c b/modbus/mb_ports/serial/port_serial.c index a95068b..ef41520 100644 --- a/modbus/mb_ports/serial/port_serial.c +++ b/modbus/mb_ports/serial/port_serial.c @@ -331,7 +331,7 @@ bool mb_port_ser_send_data(mb_port_base_t *inst, uint8_t *p_ser_frame, uint16_t count = uart_write_bytes(port_obj->ser_opts.port, p_ser_frame, ser_length); // Waits while UART sending the packet esp_err_t status = uart_wait_tx_done(port_obj->ser_opts.port, MB_SERIAL_TX_TOUT_TICKS); - (void)mb_port_event_post(inst, EVENT(EV_FRAME_SENT)); + ESP_LOGD(TAG, "%s, tx buffer sent: (%d) bytes.", inst->descr.parent_name, (int)count); MB_RETURN_ON_FALSE((status == ESP_OK), false, TAG, "%s, mb serial sent buffer failure.", inst->descr.parent_name); diff --git a/modbus/mb_ports/tcp/port_tcp_common.h b/modbus/mb_ports/tcp/port_tcp_common.h index e5e4679..a01d2fa 100644 --- a/modbus/mb_ports/tcp/port_tcp_common.h +++ b/modbus/mb_ports/tcp/port_tcp_common.h @@ -22,6 +22,7 @@ extern "C" { #define MB_FRAME_QUEUE_SZ (20) #define MB_TCP_CHECK_ALIVE_TOUT_MS (20) // check alive timeout in mS #define MB_RECONNECT_TIME_MS (CONFIG_FMB_TCP_CONNECTION_TOUT_SEC * 1000UL) +#define MB_TCP_KEEP_ALIVE_TOUT_MS (CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC * 1000UL) #define MB_EVENT_SEND_RCV_TOUT_MS (500) #define MB_TCP_MBAP_GET_FIELD(buffer, field) ((uint16_t)((buffer[field] << 8U) | buffer[field + 1])) diff --git a/modbus/mb_ports/tcp/port_tcp_driver.c b/modbus/mb_ports/tcp/port_tcp_driver.c index f497e66..ca29000 100644 --- a/modbus/mb_ports/tcp/port_tcp_driver.c +++ b/modbus/mb_ports/tcp/port_tcp_driver.c @@ -302,7 +302,7 @@ int mb_drv_open(void *ctx, mb_uid_info_t addr_info, int flags) goto err; } if (drv_obj->mb_node_open_count > MB_MAX_FDS) { - ESP_LOGD(TAG, "Exceeded maximum node count: %d", drv_obj->mb_node_open_count); + ESP_LOGE(TAG, "Exceeded maximum node count: %d", drv_obj->mb_node_open_count); goto err; } drv_obj->mb_node_open_count++; @@ -576,7 +576,7 @@ err_t mb_drv_check_node_state(void *ctx, int *fd_ptr, uint32_t timeout_ms) pnode = mb_drv_get_next_node_from_set(ctx, fd_ptr, &drv_obj->conn_set); if (pnode && FD_ISSET(pnode->sock_id, &drv_obj->conn_set)) { uint64_t last_read_div_us = (esp_timer_get_time() - pnode->recv_time); - ESP_LOGD(TAG, "%p, node: %d, sock: %d, IP:%s, check connection timeout = %" PRId64 ", rcv_time: %" PRId64 " %" PRId32, + ESP_LOGD(TAG, "%p, node: %d, sock: %d, IP:%s, check connection timeout = %" PRId64 ", rcv_time: %" PRId64 " %" PRIu32, ctx, (int)pnode->index, (int)pnode->sock_id, pnode->addr_info.ip_addr_str, (esp_timer_get_time() / 1000), pnode->recv_time / 1000, timeout_ms); if (last_read_div_us >= (uint64_t)(timeout_ms * 1000)) { diff --git a/modbus/mb_ports/tcp/port_tcp_driver.h b/modbus/mb_ports/tcp/port_tcp_driver.h index 31db918..41e1b45 100644 --- a/modbus/mb_ports/tcp/port_tcp_driver.h +++ b/modbus/mb_ports/tcp/port_tcp_driver.h @@ -5,7 +5,6 @@ */ #pragma once -//#include #include #include "esp_err.h" @@ -49,6 +48,8 @@ typedef void (*mb_event_handler_fp)(void *ctx, esp_event_base_t base, int32_t id #define MB_TX_QUEUE_MAX_SIZE (CONFIG_FMB_QUEUE_LENGTH) #define MB_EVENT_QUEUE_SZ (CONFIG_FMB_QUEUE_LENGTH * MB_TCP_PORT_MAX_CONN) +#define MB_DROP_TRANSACTION_TIME_US (1000UL * (CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC * 2000UL)) // drop after twice keep alive timeout is reasonable + #define MB_WAIT_DONE_MS (5000) #define MB_SELECT_WAIT_MS (200) #define MB_TCP_SEND_TIMEOUT_MS (500) @@ -90,15 +91,15 @@ typedef struct _port_driver port_driver_t; #define MB_EVENT_BASE(context) (__extension__( \ { \ - port_driver_t *drv_obj = MB_GET_DRV_PTR(context); \ - (drv_obj->loop_name) ? (esp_event_base_t)(drv_obj->loop_name) : "UNK_BASE"; \ + port_driver_t *drv_obj = MB_GET_DRV_PTR(context); \ + (drv_obj->loop_name) ? (esp_event_base_t)(drv_obj->loop_name) : "UNK_BASE"; \ } \ )) -#define MB_ADD_FD(fd, max_fd, fdset) do { \ +#define MB_ADD_FD(fd, max_fd, fdset) do { \ if (fd) { \ (max_fd = (fd > max_fd) ? fd : max_fd); \ - FD_SET(fd, fdset); \ + FD_SET(fd, fdset); \ } \ } while(0) @@ -106,15 +107,15 @@ typedef struct _port_driver port_driver_t; // Macro for atomic operations #define MB_ATOMIC_LOAD(ctx, addr) (__extension__( \ { \ - port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); \ - (CRITICAL_LOAD(drv_obj->lock, addr)); \ + port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); \ + (CRITICAL_LOAD(drv_obj->lock, addr)); \ } \ )) #define MB_ATOMIC_STORE(ctx, addr, val) (__extension__( \ { \ - port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); \ - CRITICAL_STORE(drv_obj->lock, addr, val); \ + port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); \ + CRITICAL_STORE(drv_obj->lock, addr, val); \ } \ )) @@ -122,11 +123,11 @@ typedef struct _port_driver port_driver_t; // So, the eventfd value keeps last event and its fd. #define DRIVER_SEND_EVENT(ctx, event, fd) (__extension__( \ { \ - port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); \ + port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); \ mb_event_info_t (event_info##__FUNCTION__##__LINE__); \ (event_info##__FUNCTION__##__LINE__).event_id = (int32_t)event; \ (event_info##__FUNCTION__##__LINE__).opt_fd = fd; \ - ((write_event((void *)drv_obj, &(event_info##__FUNCTION__##__LINE__)) > 0) \ + ((write_event((void *)drv_obj, &(event_info##__FUNCTION__##__LINE__)) > 0) \ ? ((event_info##__FUNCTION__##__LINE__)).event_id : UNDEF_FD); \ } \ )) @@ -196,14 +197,16 @@ typedef struct mb_node_info_s { typedef enum _mb_sync_event { MB_SYNC_EVENT_RECV_OK = 0x0001, - MB_SYNC_EVENT_RECV_FAIL = 0x0002, - MB_SYNC_EVENT_SEND_OK = 0x0003, + MB_SYNC_EVENT_READY = 0x002, + MB_SYNC_EVENT_RECV_FAIL = 0x0003, + MB_SYNC_EVENT_SEND_OK = 0x0004, + MB_SYNC_EVENT_SEND_ERR = 0x0005, MB_SYNC_EVENT_TOUT } mb_sync_event_t; typedef enum _mb_status_flags { MB_FLAG_BLANK = 0x0000, - MB_FLAG_TRANSACTION_DONE = 0x0001, + MB_FLAG_TRANSACTION_READY = 0x0001, MB_FLAG_DISCONNECTED = 0x0002, MB_FLAG_CONNECTED = 0x0004, MB_FLAG_SUSPEND = 0x0008, diff --git a/modbus/mb_ports/tcp/port_tcp_master.c b/modbus/mb_ports/tcp/port_tcp_master.c index 706579d..b169ca0 100644 --- a/modbus/mb_ports/tcp/port_tcp_master.c +++ b/modbus/mb_ports/tcp/port_tcp_master.c @@ -453,7 +453,7 @@ MB_EVENT_HANDLER(mbm_on_connect) ctx, (int)event_info->opt_fd, (int)node_ptr->sock_id, node_ptr->addr_info.ip_addr_str); MB_SET_NODE_STATE(node_ptr, MB_SOCK_STATE_CONNECTED); - (void)port_keep_alive(node_ptr->sock_id); + (void)port_keep_alive_enable(node_ptr->sock_id, CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC); ESP_LOGD(TAG, "Opened/connected: %u, %u.", (unsigned)drv_obj->mb_node_open_count, (unsigned)drv_obj->node_conn_count); if (drv_obj->mb_node_open_count == drv_obj->node_conn_count) { @@ -595,7 +595,6 @@ MB_EVENT_HANDLER(mbm_on_send_data) info_ptr->tid_counter = (uint16_t)(info_ptr->index << 8U); } } - drv_obj->event_cbs.mb_sync_event_cb(drv_obj->event_cbs.port_arg, MB_SYNC_EVENT_SEND_OK); mb_drv_lock(ctx); drv_obj->mb_node_curr = info_ptr; drv_obj->curr_node_index = info_ptr->index; diff --git a/modbus/mb_ports/tcp/port_tcp_slave.c b/modbus/mb_ports/tcp/port_tcp_slave.c index ab3570d..c30662b 100644 --- a/modbus/mb_ports/tcp/port_tcp_slave.c +++ b/modbus/mb_ports/tcp/port_tcp_slave.c @@ -223,7 +223,7 @@ bool mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **frame, uint16_t *len memcpy(*frame, buf, len); *length = (uint16_t)len; status = true; - ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", get packet TID: 0x%04" PRIx16 ", %p."), + ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", read packet, TID: 0x%04" PRIx16 ", %p."), port_obj, pnode->index, pnode->sock_id, pnode->addr_info.ip_addr_str, (unsigned)pnode->tid_counter, *frame); if (ESP_OK != transaction_item_set_state(item, CONFIRMED)) { @@ -232,9 +232,7 @@ bool mbs_port_tcp_recv_data(mb_port_base_t *inst, uint8_t **frame, uint16_t *len } } else { // Delete expired frames - int frame_cnt = transaction_delete_expired(port_obj->transaction, - port_get_timestamp(), - (1000 * MB_MASTER_TIMEOUT_MS_RESPOND)); + int frame_cnt = transaction_delete_expired(port_obj->transaction, port_get_timestamp(), MB_DROP_TRANSACTION_TIME_US); if (frame_cnt) { ESP_LOGE(TAG, "Deleted %d expired frames.", frame_cnt); } @@ -256,13 +254,14 @@ bool mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *frame, uint16_t lengt transaction_item_handle_t item; mb_drv_lock(drv_obj); - item = transaction_dequeue(port_obj->transaction, CONFIRMED, NULL); - if (item) { + item = transaction_get_first(port_obj->transaction); + if (item && transaction_item_get_state(item) == CONFIRMED) { uint16_t msg_id = 0; int node_id = 0; + mb_node_info_t *pnode = NULL; uint8_t *buf = transaction_item_get_data(item, NULL, &msg_id, &node_id); - if (buf && (tid == msg_id)) { - mb_node_info_t *pnode = mb_drv_get_node(drv_obj, node_id); + pnode = mb_drv_get_node(drv_obj, node_id); + if (pnode && buf && (tid == msg_id)) { int write_length = mb_drv_write(drv_obj, node_id, frame, length); if (pnode && write_length) { frame_sent = true; @@ -270,22 +269,29 @@ bool mbs_port_tcp_send_data(mb_port_base_t *inst, uint8_t *frame, uint16_t lengt drv_obj, pnode->index, pnode->sock_id, pnode->addr_info.node_name_str, (unsigned)tid, (unsigned)msg_id, frame, length); } else { - ESP_LOGE(TAG, "%p, node: #%d, socket(#%d)[%s], mbs_write fail, TID: 0x%04" PRIx16 ":0x%04" PRIx16 ", %p, len: %d, ", + ESP_LOGE(TAG, "%p, node: #%d, socket(#%d)[%s], modbus write fail, TID: 0x%04" PRIx16 ":0x%04" PRIx16 ", %p, len: %d, ", drv_obj, pnode->index, pnode->sock_id, pnode->addr_info.node_name_str, (unsigned)tid, (unsigned)msg_id, frame, length); } if (ESP_OK != transaction_item_set_state(item, REPLIED)) { - ESP_LOGE(TAG, "transaction queue set state fail."); + ESP_LOGE(TAG, "transaction queue set reply state fail."); } + } else { + ESP_LOGE(TAG, "%p, node: #%d, socket(#%d)[%s], could not write transaction, TID: 0x%04" PRIx16 ":0x%04" PRIx16 ", %p, len: %d, ", + drv_obj, pnode->index, pnode->sock_id, pnode->addr_info.node_name_str, + (unsigned)tid, (unsigned)msg_id, frame, length); + if (item && transaction_delete_item(port_obj->transaction, item) != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove queued TID:0x%04" PRIx16, tid); + } else { + ESP_LOGD(TAG, "Remove the message TID:0x%04" PRIx16, tid); + } + (void)mb_drv_set_status_flag(drv_obj, MB_FLAG_TRANSACTION_READY); } } else { - ESP_LOGE(TAG, "queue can not find the item to send."); + ESP_LOGE(TAG, "can not find the confirmed transaction TID: 0x%04" PRIx16 ", drop the frame", tid); } mb_drv_unlock(drv_obj); - if (!frame_sent) { - ESP_LOGE(TAG, "incorrect frame to send."); - } return frame_sent; } @@ -299,6 +305,10 @@ static uint64_t mbs_port_tcp_sync_event(void *inst, mb_sync_event_t sync_event) mb_port_event_post(inst, EVENT(EV_FRAME_RECEIVED)); break; + case MB_SYNC_EVENT_READY: + mb_port_event_post(inst, EVENT(EV_READY)); + break; + case MB_SYNC_EVENT_RECV_FAIL: mb_port_timer_disable(inst); mb_port_event_set_err_type(inst, EV_ERROR_RECEIVE_DATA); @@ -308,6 +318,13 @@ static uint64_t mbs_port_tcp_sync_event(void *inst, mb_sync_event_t sync_event) case MB_SYNC_EVENT_SEND_OK: mb_port_event_post(inst, EVENT(EV_FRAME_SENT)); break; + + case MB_SYNC_EVENT_SEND_ERR: + mb_port_timer_disable(inst); + mb_port_event_set_err_type(inst, EV_ERROR_RESPOND_TIMEOUT); + mb_port_event_post(inst, EVENT(EV_ERROR_PROCESS)); + break; + default: break; } @@ -348,8 +365,10 @@ MB_EVENT_HANDLER(mbs_on_ready) mb_drv_lock(ctx); drv_obj->listen_sock_fd = listen_sock; // so, all accepted sockets will inherit the keep-alive feature - (void)port_keep_alive(drv_obj->listen_sock_fd); + (void)port_keep_alive_enable(drv_obj->listen_sock_fd, CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC); + (void)mb_drv_set_status_flag(drv_obj, MB_FLAG_TRANSACTION_READY); mb_drv_unlock(ctx); + drv_obj->event_cbs.mb_sync_event_cb(drv_obj->event_cbs.port_arg, MB_SYNC_EVENT_READY); ESP_LOGI(TAG, "%s %s: fd: %d, bind is done", (char *)base, __func__, (int)event_info->opt_fd); } } @@ -370,7 +389,7 @@ MB_EVENT_HANDLER(mbs_on_connect) ESP_LOGD(TAG, "%s %s: fd: %d, is closed.", (char *)base, __func__, (int)event_info->opt_fd); return; } - (void)port_keep_alive(pnode->sock_id); + (void)port_keep_alive_enable(pnode->sock_id, CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC); mb_drv_lock(ctx); MB_SET_NODE_STATE(pnode, MB_SOCK_STATE_CONNECTED); FD_SET(pnode->sock_id, &drv_obj->conn_set); @@ -390,15 +409,15 @@ MB_EVENT_HANDLER(mbs_on_recv_data) transaction_item_handle_t item = NULL; if (pnode) { if (!queue_is_empty(pnode->rx_queue)) { - ESP_LOGD(TAG, "%p, node #%d(%d) [%s], receive data ready.", ctx, (int)event_info->opt_fd, + ESP_LOGD(TAG, "%p, node #%d, socket(#%d) [%s], receive data ready.", ctx, (int)event_info->opt_fd, (int)pnode->sock_id, pnode->addr_info.ip_addr_str); frame_entry_t frame_entry; size_t sz = queue_pop(pnode->rx_queue, NULL, MB_BUFFER_SIZE, &frame_entry); if (sz > MB_TCP_FUNC) { uint16_t tid_counter = MB_TCP_MBAP_GET_FIELD(frame_entry.buf, MB_TCP_TID); - ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", received packet TID: 0x%04" PRIx16 ", %p."), + ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", received packet TID: 0x%04" PRIx16 ", frame: %p, %u"), drv_obj, pnode->index, pnode->sock_id, - pnode->addr_info.ip_addr_str, (unsigned)tid_counter, frame_entry.buf); + pnode->addr_info.ip_addr_str, (unsigned)tid_counter, frame_entry.buf, frame_entry.len); mb_drv_lock(drv_obj); transaction_message_t msg; msg.buffer = frame_entry.buf; @@ -406,44 +425,58 @@ MB_EVENT_HANDLER(mbs_on_recv_data) msg.msg_id = frame_entry.tid; msg.node_id = pnode->index; msg.pnode = pnode; + // Enqueue the transaction, keep time of receiving. item = transaction_enqueue(port_obj->transaction, &msg, port_get_timestamp()); pnode->tid_counter = tid_counter; // assign the TID from frame to use it on send mb_drv_unlock(drv_obj); } } - mb_drv_lock(drv_obj); item = transaction_get_first(port_obj->transaction); if (item) { if (transaction_item_get_state(item) == QUEUED) { + // Check if the main FSM is not busy + if (mb_port_event_res_take(&port_obj->base, TRANSACTION_TICKS)) { + (void)mb_drv_clear_status_flag(drv_obj, MB_FLAG_TRANSACTION_READY); + } else { + if (port_get_timestamp() - transaction_item_get_tick(item) > MB_DROP_TRANSACTION_TIME_US) { + ESP_LOGD(TAG, "Transaction TID:0x%04" PRIx16 " is expired.", transaction_item_get_id(item)); + } else { + // postpone the packet processing to next cycle + DRIVER_SEND_EVENT(ctx, MB_EVENT_RECV_DATA, pnode->index); + } + mb_drv_lock(drv_obj); + transaction_delete_expired(port_obj->transaction, port_get_timestamp(), MB_DROP_TRANSACTION_TIME_US); + mb_drv_unlock(drv_obj); + mb_drv_check_suspend_shutdown(ctx); + return; + } // send receive event to modbus object to get the new data - uint16_t msg_id = 0; - uint64_t tick = 0; - (void)transaction_item_get_data(item, NULL, &msg_id, NULL); - tick = port_get_timestamp(); drv_obj->event_cbs.mb_sync_event_cb(drv_obj->event_cbs.port_arg, MB_SYNC_EVENT_RECV_OK); - transaction_set_tick(port_obj->transaction, msg_id, (transaction_tick_t)tick); + mb_drv_lock(drv_obj); + uint16_t msg_id = 0; + int node_id = 0; + (void)transaction_item_get_data(item, NULL, &msg_id, &node_id); + pnode = mb_drv_get_node(drv_obj, node_id); + ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", acknoledged packet TID: 0x%04" PRIx16 ", start transaction."), + drv_obj, pnode->index, pnode->sock_id, + pnode->addr_info.ip_addr_str, (unsigned)msg_id); if (ESP_OK == transaction_item_set_state(item, ACKNOWLEDGED)) { ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", acknoledged packet TID: 0x%04" PRIx16 "."), drv_obj, pnode->index, pnode->sock_id, pnode->addr_info.ip_addr_str, (unsigned)msg_id); } + mb_drv_unlock(drv_obj); } else { if (transaction_item_get_state(item) != TRANSMITTED) { - // Todo: for test removing expired item - transaction_delete_expired(port_obj->transaction, port_get_timestamp(), 1000 * 1000); + // Transaction procesing is ongoing, just delete expired transactions + mb_drv_lock(drv_obj); + transaction_delete_expired(port_obj->transaction, port_get_timestamp(), MB_DROP_TRANSACTION_TIME_US); + mb_drv_unlock(drv_obj); } - if (MB_FLAG_TRANSACTION_DONE == mb_drv_wait_status_flag(port_obj->drv_obj, - MB_FLAG_TRANSACTION_DONE, - TRANSACTION_TICKS)) { - (void)mb_drv_clear_status_flag(drv_obj, MB_FLAG_TRANSACTION_DONE); - } - // postpone the packet processing - DRIVER_SEND_EVENT(ctx, MB_EVENT_RECV_DATA, pnode->index); } } else { - ESP_LOGE(TAG, "%p, no queued items found", ctx); + ESP_LOGD(TAG, "%p, no queued items found", ctx); } - mb_drv_unlock(drv_obj); } mb_drv_check_suspend_shutdown(ctx); } @@ -453,59 +486,103 @@ MB_EVENT_HANDLER(mbs_on_send_data) port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); mb_event_info_t *event_info = (mb_event_info_t *)data; mbs_tcp_port_t *port_obj = (mbs_tcp_port_t *)drv_obj->parent; + transaction_item_handle_t item = NULL; + esp_err_t err = ESP_ERR_INVALID_STATE; + frame_entry_t frame_entry = {0}; ESP_LOGD(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)event_info->opt_fd); mb_node_info_t *pnode = mb_drv_get_node(drv_obj, event_info->opt_fd); if (pnode && !queue_is_empty(pnode->tx_queue)) { - frame_entry_t frame_entry; - // pop the frame entry, keep the buffer + // Pop the frame entry, keep the buffer size_t sz = queue_pop(pnode->tx_queue, NULL, MB_BUFFER_SIZE, &frame_entry); - if (!sz || (MB_GET_NODE_STATE(pnode) < MB_SOCK_STATE_CONNECTED)) { - ESP_LOGE(TAG, "%p, "MB_NODE_FMT(", is invalid, drop data."), - ctx, (int)pnode->index, (int)pnode->sock_id, pnode->addr_info.ip_addr_str); - return; - } - uint16_t tid = MB_TCP_MBAP_GET_FIELD(frame_entry.buf, MB_TCP_TID); - pnode->error = 0; - int ret = port_write_poll(pnode, frame_entry.buf, sz, MB_TCP_SEND_TIMEOUT_MS); - if (ret < 0) { - ESP_LOGE(TAG, "%p, " MB_NODE_FMT(", send data failure, err(errno) = %d(%u)."), - ctx, (int)pnode->index, (int)pnode->sock_id, - pnode->addr_info.ip_addr_str, (int)ret, (unsigned)errno); - DRIVER_SEND_EVENT(ctx, MB_EVENT_ERROR, pnode->index); - pnode->error = ret; - } else { - pnode->error = 0; - if (tid != pnode->tid_counter) { - ESP_LOGE(TAG, "%p, " MB_NODE_FMT(", send incorrect frame TID:0x%04" PRIx16 "!= 0x%04" PRIx16 ", %d (bytes), errno %d"), - ctx, (int)pnode->index, (int)pnode->sock_id, - pnode->addr_info.ip_addr_str, pnode->tid_counter, tid, (int)ret, (unsigned)errno); + if (sz) { + uint16_t tid = MB_TCP_MBAP_GET_FIELD(frame_entry.buf, MB_TCP_TID); + // Try to find actual transaction for current TID, + // if not found just ignore the frame as expired + item = transaction_get_first(port_obj->transaction); + if (item && pnode) { + uint16_t msg_id = 0; + int node_id = 0; + (void)transaction_item_get_data(item, NULL, &msg_id, &node_id); + // Check if the TID is equal to the current received TID for this node. + // If not, means the slave was not able to process the previous transaction on time. + // The reason is too much active connections or incorrect response time or request rate in the master. + if ((node_id != pnode->index) || (tid != msg_id) || (tid != pnode->tid_counter) || (MB_GET_NODE_STATE(pnode) < MB_SOCK_STATE_CONNECTED)) { + mb_drv_lock(drv_obj); + err = transaction_delete(port_obj->transaction, tid); + mb_drv_unlock(drv_obj); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove queued TID:0x%04" PRIx16, (int)tid); + } else { + ESP_LOGD(TAG, "Remove the message TID:0x%04" PRIx16, (int)tid); + } + (void)mb_drv_set_status_flag(drv_obj, MB_FLAG_TRANSACTION_READY); + uint64_t tick = (transaction_tick_t)transaction_item_get_tick(item); + uint64_t time_div_us = (esp_timer_get_time() - tick); + ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", frame TID:0x%04" PRIx16 "!=0x%04" PRIx16 ", slave is busy."), + ctx, (int)pnode->index, (int)pnode->sock_id, + pnode->addr_info.ip_addr_str, pnode->tid_counter, tid); + ESP_LOGW(TAG, "%p, " MB_NODE_FMT(", handling time [ms]: %" PRIu64 ", exceeds slave response time in master."), + ctx, (int)pnode->index, (int)pnode->sock_id, + pnode->addr_info.ip_addr_str, (time_div_us / 1000)); + // Hard hack to fix the expired frames (unsafe in some cases, do not implement) + // MB_TCP_MBAP_SET_FIELD(frame_entry.buf, MB_TCP_TID, pnode->tid_counter); + } else { + mb_drv_lock(drv_obj); + int ret = port_write_poll(pnode, frame_entry.buf, sz, MB_TCP_SEND_TIMEOUT_MS); + if (ret < 0) { + ESP_LOGE(TAG, "%p, " MB_NODE_FMT(", send data failure, err(errno) = %d(%u)."), + ctx, (int)pnode->index, (int)pnode->sock_id, + pnode->addr_info.ip_addr_str, (int)ret, (unsigned)errno); + DRIVER_SEND_EVENT(ctx, MB_EVENT_ERROR, pnode->index); + pnode->error = ret; + } else { + pnode->error = 0; + ESP_LOG_BUFFER_HEX_LEVEL("SENT", frame_entry.buf, ret, ESP_LOG_DEBUG); + } + (void)mb_drv_set_status_flag(drv_obj, MB_FLAG_TRANSACTION_READY); + err = transaction_set_state(port_obj->transaction, tid, TRANSMITTED); + if (err == ESP_OK) { + ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", sent packet TID: 0x%04" PRIx16 ", %p."), + drv_obj, pnode->index, pnode->sock_id, + pnode->addr_info.ip_addr_str, tid, frame_entry.buf); + } else { + ESP_LOGE(TAG, "%p, " MB_NODE_FMT(", transaction set state fail for TID: 0x%04" PRIx16 ", %p."), + drv_obj, pnode->index, pnode->sock_id, + pnode->addr_info.ip_addr_str, tid, frame_entry.buf); + } + if (transaction_delete_item(port_obj->transaction, item) != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove queued TID:0x%04" PRIx16, tid); + } else { + ESP_LOGD(TAG, "Remove the message TID:0x%04" PRIx16, tid); + } + pnode->send_time = port_get_timestamp(); + pnode->send_counter = (pnode->send_counter < (USHRT_MAX - 1)) ? (pnode->send_counter + 1) : 0; + mb_drv_unlock(drv_obj); + } } else { - ESP_LOGD(TAG, "%p, " MB_NODE_FMT(", send data successful: TID:0x%04" PRIx16 ":0x%04" PRIx16 ", %d (bytes), errno %d"), - ctx, (int)pnode->index, (int)pnode->sock_id, - pnode->addr_info.ip_addr_str, pnode->tid_counter, tid, (int)ret, (unsigned)errno); + // It looks like no current registered transaction. It might be happen if the transaction has deleted as expired. + // Note: the transaction processing time is increased proportional to a number of connected Masters. + // If it is still needed to connect several number of Masters simultaneously, + // then the slave response time option needs to be increased in all Masters. + ESP_LOGE(TAG, "%p, " MB_NODE_FMT(", transaction not found for TID: 0x%04" PRIx16 ", drop data %p."), + ctx, (int)pnode->index, (int)pnode->sock_id, + pnode->addr_info.ip_addr_str, tid, pnode); + (void)mb_drv_set_status_flag(drv_obj, MB_FLAG_TRANSACTION_READY); } - ESP_LOG_BUFFER_HEX_LEVEL("SENT", frame_entry.buf, ret, ESP_LOG_DEBUG); - } - (void)mb_drv_set_status_flag(drv_obj, MB_FLAG_TRANSACTION_DONE); - drv_obj->event_cbs.mb_sync_event_cb(drv_obj->event_cbs.port_arg, MB_SYNC_EVENT_SEND_OK); - mb_drv_lock(drv_obj); - transaction_set_state(port_obj->transaction, tid, TRANSMITTED); - if (transaction_delete(port_obj->transaction, tid) != ESP_OK) { - ESP_LOGE(TAG, "Failed to remove queued TID:0x%04" PRIx16, tid); } else { - ESP_LOGD(TAG, "Remove the message TID:0x%04" PRIx16, tid); + ESP_LOGE(TAG, "%p, "MB_NODE_FMT(", frame is invalid, drop data."), + ctx, (int)pnode->index, (int)pnode->sock_id, pnode->addr_info.ip_addr_str); } free(frame_entry.buf); - pnode->send_time = esp_timer_get_time(); - pnode->send_counter = (pnode->send_counter < (USHRT_MAX - 1)) ? (pnode->send_counter + 1) : 0; - mb_drv_unlock(drv_obj); } + mb_drv_check_suspend_shutdown(ctx); } MB_EVENT_HANDLER(mbs_on_error) { port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); mb_event_info_t *event_info = (mb_event_info_t *)data; + mbs_tcp_port_t *port_obj = __containerof(drv_obj->parent, mbs_tcp_port_t, base); ESP_LOGD(TAG, "%s %s: fd: %d", (char *)base, __func__, (int)event_info->opt_fd); mb_node_info_t *pnode = mb_drv_get_node(drv_obj, event_info->opt_fd); if (!pnode) { @@ -513,11 +590,18 @@ MB_EVENT_HANDLER(mbs_on_error) return; } // Check if the node is not alive for timeout - int ret = mb_drv_check_node_state(drv_obj, (int *)&event_info->opt_fd, MB_TCP_EVENT_LOOP_TICK_MS); + int ret = mb_drv_check_node_state(drv_obj, (int *)&event_info->opt_fd, MB_EVENT_SEND_RCV_TOUT_MS); if ((ret != ERR_OK) && (ret != ERR_TIMEOUT)) { - ESP_LOGE(TAG, "Node: #%d is not alive, err= %d", (int)event_info->opt_fd, ret); + ESP_LOGE(TAG, "%p, " MB_NODE_FMT(", communication fail, err= %d"), + port_obj, pnode->index, pnode->sock_id, + pnode->addr_info.ip_addr_str, (int)ret); + mb_drv_lock(drv_obj); + // delete all queued transactions for the node to be closed. + (void)transaction_delete_by_node_id(port_obj->transaction, event_info->opt_fd); + mb_drv_unlock(drv_obj); mb_drv_close(drv_obj, event_info->opt_fd); } + mb_drv_check_suspend_shutdown(ctx); } MB_EVENT_HANDLER(mbs_on_close) @@ -525,6 +609,7 @@ MB_EVENT_HANDLER(mbs_on_close) mb_event_info_t *event_info = (mb_event_info_t *)data; ESP_LOGD(TAG, "%s %s, fd: %d", (char *)base, __func__, (int)event_info->opt_fd); port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); + mbs_tcp_port_t *port_obj = __containerof(drv_obj->parent, mbs_tcp_port_t, base); mb_node_info_t *pnode =NULL; // if close all sockets event is received if (event_info->opt_fd < 0) @@ -545,6 +630,9 @@ MB_EVENT_HANDLER(mbs_on_close) pnode = mb_drv_get_node(drv_obj, event_info->opt_fd); if (pnode && (MB_GET_NODE_STATE(pnode) >= MB_SOCK_STATE_OPENED)) { if ((pnode->sock_id < 0) && FD_ISSET(pnode->sock_id, &drv_obj->open_set)) { + mb_drv_lock(drv_obj); + (void)transaction_delete_by_node_id(port_obj->transaction, event_info->opt_fd); + mb_drv_unlock(drv_obj); mb_drv_close(ctx, event_info->opt_fd); } } @@ -555,14 +643,21 @@ MB_EVENT_HANDLER(mbs_on_close) MB_EVENT_HANDLER(mbs_on_timeout) { // Slave timeout triggered - + //mb_event_info_t *event_info = (mb_event_info_t *)data; port_driver_t *drv_obj = MB_GET_DRV_PTR(ctx); + mbs_tcp_port_t *port_obj = __containerof(drv_obj->parent, mbs_tcp_port_t, base); static int curr_fd = 0; - ESP_LOGD(TAG, "%s %s: fd: %d, %d", (char *)base, __func__, (int)curr_fd, drv_obj->node_conn_count); + mb_node_info_t *pnode = mb_drv_get_node(drv_obj, curr_fd); + ESP_LOGD(TAG, "%s %s: fd: %d, count: %d", (char *)base, __func__, (int)curr_fd, drv_obj->node_conn_count); mb_drv_check_suspend_shutdown(ctx); - int ret = mb_drv_check_node_state(drv_obj, &curr_fd, MB_RECONNECT_TIME_MS); + int ret = mb_drv_check_node_state(drv_obj, &curr_fd, MB_TCP_KEEP_ALIVE_TOUT_MS); if ((ret != ERR_OK) && (ret != ERR_TIMEOUT)) { - ESP_LOGE(TAG, "Node: %d, connection lost, err= %d", curr_fd, ret); + ESP_LOGE(TAG, "%p, " MB_NODE_FMT(", connection lost, err=%d, drop connection."), + port_obj, pnode->index, pnode->sock_id, + pnode->addr_info.ip_addr_str, (int)ret); + mb_drv_lock(drv_obj); + (void)transaction_delete_by_node_id(port_obj->transaction, curr_fd); + mb_drv_unlock(drv_obj); mb_drv_close(drv_obj, curr_fd); } if ((curr_fd + 1) >= (drv_obj->node_conn_count)) { diff --git a/modbus/mb_ports/tcp/port_tcp_utils.c b/modbus/mb_ports/tcp/port_tcp_utils.c index c1f7ebc..9ffb628 100644 --- a/modbus/mb_ports/tcp/port_tcp_utils.c +++ b/modbus/mb_ports/tcp/port_tcp_utils.c @@ -82,6 +82,7 @@ bool port_close_connection(mb_node_info_t *info_ptr) if (shutdown(info_ptr->sock_id, SHUT_RDWR) == -1) { ESP_LOGV(TAG, "Shutdown failed sock %d, errno=%d", info_ptr->sock_id, (int)errno); } + close(info_ptr->sock_id); MB_SET_NODE_STATE(info_ptr, MB_SOCK_STATE_OPENED); info_ptr->sock_id = UNDEF_FD; @@ -198,11 +199,11 @@ int port_read_packet(mb_node_info_t *info_ptr) if (ret < 0) { info_ptr->recv_err = ret; return ret; - } + } if (ret != MB_TCP_UID) { - ESP_LOGD(TAG, "Socket (#%d)(%s), fail to read modbus header. ret=%d", - info_ptr->sock_id, info_ptr->addr_info.ip_addr_str, ret); + ESP_LOGD(TAG, "node #%d, Socket (#%d)(%s), fail to read modbus header, err=%d", + info_ptr->fd, info_ptr->sock_id, info_ptr->addr_info.ip_addr_str, ret); info_ptr->recv_err = ERR_VAL; return ERR_VAL; } @@ -270,17 +271,21 @@ err_t port_set_blocking(mb_node_info_t *info_ptr, bool is_blocking) return ERR_OK; } -int port_keep_alive(int sock) +int port_keep_alive_enable(int sock, int timeout_sec) { + if ((timeout_sec > 7200) || (timeout_sec < 1)) { + ESP_LOGD(TAG, "Sock %d, invalid keep alive timeout, err = (%d).", sock, timeout_sec); + return -1; + } int optval = 1; // Enable the Keepalive feature in the LWIP stack int ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)); if (ret != 0) { - ESP_LOGE(TAG, "Sock %d, set keep alive option fail, err= (%d).", sock, ret); + ESP_LOGE(TAG, "Sock %d, enable keep alive option fail, err= (%d).", sock, ret); return -1; } - // The interval between the last data packet sent and the first keepalive probe (seconds) - optval = 1; + // The interval between the last data packet sent and the first keepalive probe (seconds) + optval = timeout_sec; ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(optval)); if (ret != 0) { ESP_LOGD(TAG, "Sock %d, set keep idle time fail, err = (%d).", sock, ret); @@ -293,8 +298,8 @@ int port_keep_alive(int sock) ESP_LOGD(TAG, "Sock %d, set keep alive probes interval fail, err = (%d).", sock, ret); return -1; } - // Set count of probes before timing out - optval = CONFIG_FMB_TCP_CONNECTION_TOUT_SEC; + // Set count of probes before timing out. Try to minimize the keep alive probes to decrease latency. + optval = 1; ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(optval)); if (ret != 0) { ESP_LOGD(TAG, "Sock %d, set keep alive probes count fail., err = (%d).", sock, ret); @@ -425,7 +430,7 @@ err_t port_connect(void *ctx, mb_node_info_t *info_ptr) info_ptr->sock_id, str, (int)errno, strerror(errno)); // Set keep alive flag in socket options - (void)port_keep_alive(info_ptr->sock_id); + (void)port_keep_alive_enable(info_ptr->sock_id, CONFIG_FMB_TCP_KEEP_ALIVE_TOUT_SEC); err = port_check_alive(info_ptr, MB_TCP_CHECK_ALIVE_TOUT_MS); continue; } @@ -917,7 +922,7 @@ int port_accept_connection(int listen_sock_id, mb_uid_info_t *info_ptr) // Make sure ss_family is valid abort(); } - ESP_LOGI(TAG, "Socket (#%d), accept client connection from address[port]: %s[%d]", (int)sock_id, addr_str, info_ptr->port); + ESP_LOGI(TAG, "Socket (#%d), accept client connection from address[port]: %s[%u]", (int)sock_id, addr_str, info_ptr->port); paddr = strdup(addr_str); if (paddr) { info_ptr->fd = sock_id; diff --git a/modbus/mb_ports/tcp/port_tcp_utils.h b/modbus/mb_ports/tcp/port_tcp_utils.h index e042a37..ff3fbc0 100644 --- a/modbus/mb_ports/tcp/port_tcp_utils.h +++ b/modbus/mb_ports/tcp/port_tcp_utils.h @@ -92,7 +92,7 @@ int port_enqueue_packet(QueueHandle_t queue, uint8_t *buf, uint16_t len); int port_dequeue_packet(QueueHandle_t queue, frame_entry_t* frame_info); int port_read_packet(mb_node_info_t* info_ptr); err_t port_set_blocking(mb_node_info_t* info_ptr, bool is_blocking); -int port_keep_alive(int sock); +int port_keep_alive_enable(int sock, int timeout_sec); err_t port_check_alive(mb_node_info_t* info_ptr, uint32_t timeout_ms); err_t port_connect(void *ctx, mb_node_info_t* info_ptr); bool port_close_connection(mb_node_info_t* info_ptr); diff --git a/test_apps/test_common/mb_utest_lib/port_adapter.c b/test_apps/test_common/mb_utest_lib/port_adapter.c index e80441f..e87ddfd 100644 --- a/test_apps/test_common/mb_utest_lib/port_adapter.c +++ b/test_apps/test_common/mb_utest_lib/port_adapter.c @@ -616,7 +616,6 @@ bool mb_port_adapter_send_data(mb_port_base_t *inst, uint8_t addrets, uint8_t *f (void)mb_port_event_post(inst, EVENT(EV_FRAME_SENT)); ESP_LOGD(TAG, "%s, tx completed, flags = 0x%04x.", inst->descr.parent_name, (int)flags); ret = true; - } else {