Adicionar primeiro
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_tcp_slave.c
|
||||
// Implementation of the Modbus controller TCP slave
|
||||
|
||||
#include <sys/time.h> // for calculation of time stamp in milliseconds
|
||||
#include "esp_log.h" // for log_write
|
||||
#include "mb.h" // for mb types definition
|
||||
#include "mbutils.h" // for mbutils functions definition for stack callback
|
||||
#include "port.h" // for port callback functions and defines
|
||||
#include "sdkconfig.h" // for KConfig values
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
#include "esp_modbus_slave.h" // for public slave interface types
|
||||
#include "mbc_slave.h" // for private slave interface types
|
||||
#include "mbc_tcp_slave.h" // for tcp slave mb controller defines
|
||||
#include "port_tcp_slave.h" // for tcp slave port defines
|
||||
|
||||
#if MB_TCP_ENABLED
|
||||
|
||||
// Shared pointer to interface structure
|
||||
static mb_slave_interface_t* mbs_interface_ptr = NULL;
|
||||
static const char *TAG = "MB_CONTROLLER_SLAVE";
|
||||
|
||||
// Modbus task function
|
||||
static void modbus_tcp_slave_task(void *pvParameters)
|
||||
{
|
||||
MB_SLAVE_ASSERT(mbs_interface_ptr != NULL);
|
||||
mb_slave_options_t* mbs_opts = &mbs_interface_ptr->opts;
|
||||
|
||||
// Main Modbus stack processing cycle
|
||||
for (;;) {
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->mbs_event_group,
|
||||
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
||||
pdFALSE, // do not clear bits
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
// Check if stack started then poll for data
|
||||
if (status & MB_EVENT_STACK_STARTED) {
|
||||
(void)eMBPoll(); // allow stack to process data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup Modbus controller parameters
|
||||
static esp_err_t mbc_tcp_slave_setup(void* comm_info)
|
||||
{
|
||||
MB_SLAVE_ASSERT(mbs_interface_ptr != NULL);
|
||||
mb_slave_options_t* mbs_opts = &mbs_interface_ptr->opts;
|
||||
MB_SLAVE_CHECK((comm_info != NULL), ESP_ERR_INVALID_ARG,
|
||||
"mb wrong communication settings.");
|
||||
mb_communication_info_t* comm_settings = (mb_communication_info_t*)comm_info;
|
||||
MB_SLAVE_CHECK((comm_settings->ip_mode == MB_MODE_TCP),
|
||||
ESP_ERR_INVALID_ARG, "mb incorrect mode = (%u).", (unsigned)comm_settings->ip_mode);
|
||||
MB_SLAVE_CHECK(((comm_settings->ip_addr_type == MB_IPV4) || (comm_settings->ip_addr_type == MB_IPV6)),
|
||||
ESP_ERR_INVALID_ARG, "mb incorrect addr type = (%u).", (unsigned)comm_settings->ip_addr_type);
|
||||
MB_SLAVE_CHECK((comm_settings->ip_netif_ptr != NULL),
|
||||
ESP_ERR_INVALID_ARG, "mb incorrect iface address.");
|
||||
// Set communication options of the controller
|
||||
mbs_opts->mbs_comm = *comm_settings;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Start Modbus controller start function
|
||||
static esp_err_t mbc_tcp_slave_start(void)
|
||||
{
|
||||
MB_SLAVE_ASSERT(mbs_interface_ptr != NULL);
|
||||
mb_slave_options_t* mbs_opts = &mbs_interface_ptr->opts;
|
||||
eMBErrorCode status = MB_EIO;
|
||||
|
||||
// Initialize Modbus stack using mbcontroller parameters
|
||||
status = eMBTCPInit((UCHAR)mbs_opts->mbs_comm.slave_uid, (USHORT)mbs_opts->mbs_comm.ip_port);
|
||||
MB_SLAVE_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE,
|
||||
"mb stack initialization failure, eMBInit() returns (0x%x).", (int)status);
|
||||
|
||||
eMBPortProto proto = (mbs_opts->mbs_comm.ip_mode == MB_MODE_TCP) ? MB_PROTO_TCP : MB_PROTO_UDP;
|
||||
eMBPortIpVer ip_ver = (mbs_opts->mbs_comm.ip_addr_type == MB_IPV4) ? MB_PORT_IPV4 : MB_PORT_IPV6;
|
||||
vMBTCPPortSlaveSetNetOpt(mbs_opts->mbs_comm.ip_netif_ptr, ip_ver, proto, (char*)mbs_opts->mbs_comm.ip_addr);
|
||||
|
||||
status = eMBEnable();
|
||||
MB_SLAVE_CHECK((status == MB_ENOERR), ESP_ERR_INVALID_STATE,
|
||||
"mb TCP stack start failure, eMBEnable() returned (0x%x).", (int)status);
|
||||
// Set the mbcontroller start flag
|
||||
EventBits_t flag = xEventGroupSetBits(mbs_opts->mbs_event_group,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_SLAVE_CHECK((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, "mb stack start event set error.");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Modbus controller destroy function
|
||||
static esp_err_t mbc_tcp_slave_destroy(void)
|
||||
{
|
||||
MB_SLAVE_ASSERT(mbs_interface_ptr != NULL);
|
||||
mb_slave_options_t* mbs_opts = &mbs_interface_ptr->opts;
|
||||
eMBErrorCode mb_error = MB_ENOERR;
|
||||
// Stop polling by clearing correspondent bit in the event group
|
||||
EventBits_t flag = xEventGroupClearBits(mbs_opts->mbs_event_group,
|
||||
(EventBits_t)MB_EVENT_STACK_STARTED);
|
||||
MB_SLAVE_CHECK((flag & MB_EVENT_STACK_STARTED),
|
||||
ESP_ERR_INVALID_STATE, "mb stack stop event failure.");
|
||||
// Disable and then destroy the Modbus stack
|
||||
mb_error = eMBDisable();
|
||||
MB_SLAVE_CHECK((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, "mb stack disable failure.");
|
||||
(void)vTaskDelete(mbs_opts->mbs_task_handle);
|
||||
(void)vQueueDelete(mbs_opts->mbs_notification_queue_handle);
|
||||
(void)vEventGroupDelete(mbs_opts->mbs_event_group);
|
||||
(void)vMBTCPPortClose();
|
||||
mbs_interface_ptr = NULL;
|
||||
vMBPortSetMode((UCHAR)MB_PORT_INACTIVE);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Blocking function to get event on parameter group change for application task
|
||||
static mb_event_group_t mbc_tcp_slave_check_event(mb_event_group_t group)
|
||||
{
|
||||
MB_SLAVE_ASSERT(mbs_interface_ptr != NULL);
|
||||
mb_slave_options_t* mbs_opts = &mbs_interface_ptr->opts;
|
||||
MB_SLAVE_ASSERT(mbs_opts->mbs_event_group != NULL);
|
||||
BaseType_t status = xEventGroupWaitBits(mbs_opts->mbs_event_group, (BaseType_t)group,
|
||||
pdTRUE , pdFALSE, portMAX_DELAY);
|
||||
return (mb_event_group_t)status;
|
||||
}
|
||||
|
||||
// Function to get notification about parameter change from application task
|
||||
static esp_err_t mbc_tcp_slave_get_param_info(mb_param_info_t* reg_info, uint32_t timeout)
|
||||
{
|
||||
MB_SLAVE_ASSERT(mbs_interface_ptr != NULL);
|
||||
mb_slave_options_t* mbs_opts = &mbs_interface_ptr->opts;
|
||||
esp_err_t err = ESP_ERR_TIMEOUT;
|
||||
MB_SLAVE_CHECK((mbs_opts->mbs_notification_queue_handle != NULL),
|
||||
ESP_ERR_INVALID_ARG, "mb queue handle is invalid.");
|
||||
MB_SLAVE_CHECK((reg_info != NULL), ESP_ERR_INVALID_ARG, "mb register information is invalid.");
|
||||
BaseType_t status = xQueueReceive(mbs_opts->mbs_notification_queue_handle,
|
||||
reg_info, pdMS_TO_TICKS(timeout));
|
||||
if (status == pdTRUE) {
|
||||
err = ESP_OK;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* ----------------------- Callback functions for Modbus stack ---------------------------------*/
|
||||
// These are executed by modbus stack to read appropriate type of registers.
|
||||
|
||||
// This is required to suppress warning when register start address is zero
|
||||
#pragma GCC diagnostic ignored "-Wtype-limits"
|
||||
|
||||
// Initialization of Modbus controller
|
||||
esp_err_t mbc_tcp_slave_create(void** handler)
|
||||
{
|
||||
// Allocate space for options
|
||||
if (mbs_interface_ptr == NULL) {
|
||||
mbs_interface_ptr = malloc(sizeof(mb_slave_interface_t));
|
||||
}
|
||||
MB_SLAVE_ASSERT(mbs_interface_ptr != NULL);
|
||||
mb_slave_options_t* mbs_opts = &mbs_interface_ptr->opts;
|
||||
mbs_opts->port_type = MB_PORT_TCP_SLAVE; // set interface port type
|
||||
|
||||
vMBPortSetMode((UCHAR)MB_PORT_TCP_SLAVE);
|
||||
|
||||
// Set default values of communication options
|
||||
mbs_opts->mbs_comm.ip_port = MB_TCP_DEFAULT_PORT;
|
||||
|
||||
// Initialization of active context of the Modbus controller
|
||||
BaseType_t status = 0;
|
||||
|
||||
// Parameter change notification queue
|
||||
mbs_opts->mbs_event_group = xEventGroupCreate();
|
||||
MB_SLAVE_CHECK((mbs_opts->mbs_event_group != NULL),
|
||||
ESP_ERR_NO_MEM, "mb event group error.");
|
||||
// Parameter change notification queue
|
||||
mbs_opts->mbs_notification_queue_handle = xQueueCreate(
|
||||
MB_CONTROLLER_NOTIFY_QUEUE_SIZE,
|
||||
sizeof(mb_param_info_t));
|
||||
MB_SLAVE_CHECK((mbs_opts->mbs_notification_queue_handle != NULL),
|
||||
ESP_ERR_NO_MEM, "mb notify queue creation error.");
|
||||
// Create Modbus controller task
|
||||
status = xTaskCreatePinnedToCore((void*)&modbus_tcp_slave_task,
|
||||
"mbs_port_tcp_task",
|
||||
MB_CONTROLLER_STACK_SIZE,
|
||||
NULL,
|
||||
MB_CONTROLLER_PRIORITY,
|
||||
&mbs_opts->mbs_task_handle,
|
||||
MB_PORT_TASK_AFFINITY);
|
||||
if (status != pdPASS) {
|
||||
vTaskDelete(mbs_opts->mbs_task_handle);
|
||||
MB_SLAVE_CHECK((status == pdPASS), ESP_ERR_NO_MEM,
|
||||
"mb controller task creation error, xTaskCreate() returns (%u).", (unsigned)status);
|
||||
}
|
||||
|
||||
// The task is created but handle is incorrect
|
||||
MB_SLAVE_ASSERT(mbs_opts->mbs_task_handle != NULL);
|
||||
|
||||
// Initialization of interface pointers
|
||||
mbs_interface_ptr->init = mbc_tcp_slave_create;
|
||||
mbs_interface_ptr->destroy = mbc_tcp_slave_destroy;
|
||||
mbs_interface_ptr->setup = mbc_tcp_slave_setup;
|
||||
mbs_interface_ptr->start = mbc_tcp_slave_start;
|
||||
mbs_interface_ptr->check_event = mbc_tcp_slave_check_event;
|
||||
mbs_interface_ptr->get_param_info = mbc_tcp_slave_get_param_info;
|
||||
mbs_interface_ptr->set_descriptor = NULL; // Use common descriptor setter
|
||||
|
||||
// Initialize stack callback function pointers
|
||||
mbs_interface_ptr->slave_reg_cb_discrete = NULL; // implemented in common layer
|
||||
mbs_interface_ptr->slave_reg_cb_input = NULL;
|
||||
mbs_interface_ptr->slave_reg_cb_holding = NULL;
|
||||
mbs_interface_ptr->slave_reg_cb_coils = NULL;
|
||||
|
||||
*handler = (void*)mbs_interface_ptr;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#endif //#if MB_TCP_ENABLED
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// mbc_tcp_slave.h Modbus controller TCP slave implementation header file
|
||||
|
||||
#ifndef _MODBUS_TCP_CONTROLLER_SLAVE
|
||||
#define _MODBUS_TCP_CONTROLLER_SLAVE
|
||||
|
||||
#include <stdint.h> // for standard int types definition
|
||||
#include <stddef.h> // for NULL and std defines
|
||||
#include "esp_modbus_common.h" // for common defines
|
||||
|
||||
/* ----------------------- Defines ------------------------------------------*/
|
||||
|
||||
#define MB_CONTROLLER_NOTIFY_QUEUE_SIZE (CONFIG_FMB_CONTROLLER_NOTIFY_QUEUE_SIZE) // Number of messages in parameter notification queue
|
||||
#define MB_CONTROLLER_NOTIFY_TIMEOUT (pdMS_TO_TICKS(CONFIG_FMB_CONTROLLER_NOTIFY_TIMEOUT)) // notification timeout
|
||||
|
||||
/**
|
||||
* @brief Initialize Modbus controller and stack for TCP slave
|
||||
*
|
||||
* @param[out] handler handler(pointer) to slave data structure
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Parameter error
|
||||
*/
|
||||
esp_err_t mbc_tcp_slave_create(void** handler);
|
||||
|
||||
#endif // _MODBUS_TCP_CONTROLLER_SLAVE
|
||||
@@ -0,0 +1,758 @@
|
||||
/*
|
||||
* 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 <wolti@sil.at>
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_timer.h"
|
||||
#include "sys/time.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
/* ----------------------- lwIP includes ------------------------------------*/
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sockets.h"
|
||||
#include "lwip/netdb.h"
|
||||
#include "net/if.h"
|
||||
|
||||
/* ----------------------- Modbus includes ----------------------------------*/
|
||||
#include "mb.h"
|
||||
#include "mbport.h"
|
||||
#include "port.h"
|
||||
#include "mbframe.h"
|
||||
#include "port_tcp_slave.h"
|
||||
#include "esp_modbus_common.h" // for common types for network options
|
||||
|
||||
#if MB_TCP_ENABLED
|
||||
|
||||
/* ----------------------- Defines -----------------------------------------*/
|
||||
#define MB_TCP_DISCONNECT_TIMEOUT ( CONFIG_FMB_TCP_CONNECTION_TOUT_SEC * 1000000 ) // disconnect timeout in uS
|
||||
#define MB_TCP_RESP_TIMEOUT_MS ( MB_MASTER_TIMEOUT_MS_RESPOND - 1 ) // slave response time limit
|
||||
#define MB_TCP_NET_LISTEN_BACKLOG ( SOMAXCONN )
|
||||
|
||||
/* ----------------------- Prototypes ---------------------------------------*/
|
||||
void vMBPortEventClose( void );
|
||||
|
||||
/* ----------------------- Static variables ---------------------------------*/
|
||||
static const char *TAG = "MB_TCP_SLAVE_PORT";
|
||||
static int xListenSock = -1;
|
||||
static SemaphoreHandle_t xShutdownSema = NULL;
|
||||
static MbSlavePortConfig_t xConfig = { 0 };
|
||||
|
||||
/* ----------------------- Static functions ---------------------------------*/
|
||||
// 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 vxMBTCPPortMStoTimeVal(USHORT usTimeoutMs, struct timeval *pxTimeout)
|
||||
{
|
||||
pxTimeout->tv_sec = usTimeoutMs / 1000;
|
||||
pxTimeout->tv_usec = (usTimeoutMs - (pxTimeout->tv_sec * 1000)) * 1000;
|
||||
}
|
||||
|
||||
static QueueHandle_t xMBTCPPortRespQueueCreate(void)
|
||||
{
|
||||
QueueHandle_t xRespQueueHandle = xQueueCreate(2, sizeof(void*));
|
||||
MB_PORT_CHECK((xRespQueueHandle != NULL), NULL, "TCP respond queue creation failure.");
|
||||
return xRespQueueHandle;
|
||||
}
|
||||
|
||||
static void vMBTCPPortRespQueueDelete(QueueHandle_t xRespQueueHandle)
|
||||
{
|
||||
vQueueDelete(xRespQueueHandle);
|
||||
}
|
||||
|
||||
static void* vxMBTCPPortRespQueueRecv(QueueHandle_t xRespQueueHandle)
|
||||
{
|
||||
void* pvResp = NULL;
|
||||
MB_PORT_CHECK(xRespQueueHandle != NULL, NULL, "Response queue is not initialized.");
|
||||
BaseType_t xStatus = xQueueReceive(xRespQueueHandle,
|
||||
(void*)&pvResp,
|
||||
pdMS_TO_TICKS(MB_TCP_RESP_TIMEOUT_MS));
|
||||
if (xStatus != pdTRUE) {
|
||||
ESP_LOGD(TAG, "Could not get respond confirmation.");
|
||||
}
|
||||
return pvResp;
|
||||
}
|
||||
|
||||
static BOOL vxMBTCPPortRespQueueSend(QueueHandle_t xRespQueueHandle, void* pvResp)
|
||||
{
|
||||
MB_PORT_CHECK(xRespQueueHandle != NULL, FALSE, "Response queue is not initialized.");
|
||||
BaseType_t xStatus = xQueueSend(xConfig.xRespQueueHandle,
|
||||
(const void*)&pvResp,
|
||||
pdMS_TO_TICKS(MB_TCP_RESP_TIMEOUT_MS));
|
||||
MB_PORT_CHECK((xStatus == pdTRUE), FALSE, "FAIL to send to response queue.");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void vMBTCPPortServerTask(void *pvParameters);
|
||||
|
||||
/* ----------------------- Begin implementation -----------------------------*/
|
||||
BOOL
|
||||
xMBTCPPortInit( USHORT usTCPPort )
|
||||
{
|
||||
BOOL bOkay = FALSE;
|
||||
|
||||
xConfig.pxMbClientInfo = calloc(MB_TCP_PORT_MAX_CONN + 1, sizeof(MbClientInfo_t*));
|
||||
if (!xConfig.pxMbClientInfo) {
|
||||
ESP_LOGE(TAG, "TCP client info allocation failure.");
|
||||
return FALSE;
|
||||
}
|
||||
for(int idx = 0; idx < MB_TCP_PORT_MAX_CONN; xConfig.pxMbClientInfo[idx] = NULL, idx++);
|
||||
|
||||
xConfig.xRespQueueHandle = xMBTCPPortRespQueueCreate();
|
||||
if (!xConfig.xRespQueueHandle) {
|
||||
ESP_LOGE(TAG, "Response queue allocation failure.");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
xConfig.usPort = usTCPPort;
|
||||
xConfig.eMbProto = MB_PROTO_TCP;
|
||||
xConfig.usClientCount = 0;
|
||||
xConfig.pvNetIface = NULL;
|
||||
xConfig.xIpVer = MB_PORT_IPV4;
|
||||
xConfig.pcBindAddr = NULL;
|
||||
|
||||
// Create task for packet processing
|
||||
BaseType_t xErr = xTaskCreatePinnedToCore(vMBTCPPortServerTask,
|
||||
"tcp_slave_task",
|
||||
MB_TCP_STACK_SIZE,
|
||||
NULL,
|
||||
MB_TCP_TASK_PRIO,
|
||||
&xConfig.xMbTcpTaskHandle,
|
||||
MB_PORT_TASK_AFFINITY);
|
||||
if (xErr != pdTRUE)
|
||||
{
|
||||
ESP_LOGE(TAG, "Server task creation failure.");
|
||||
vTaskDelete(xConfig.xMbTcpTaskHandle);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Protocol stack initialized.");
|
||||
bOkay = TRUE;
|
||||
}
|
||||
return bOkay;
|
||||
}
|
||||
|
||||
void vMBTCPPortSlaveSetNetOpt(void* pvNetIf, eMBPortIpVer xIpVersion, eMBPortProto xProto, CHAR* pcBindAddrStr)
|
||||
{
|
||||
// Set network options
|
||||
xConfig.pvNetIface = pvNetIf;
|
||||
xConfig.eMbProto = xProto;
|
||||
xConfig.xIpVer = xIpVersion;
|
||||
xConfig.pcBindAddr = pcBindAddrStr;
|
||||
}
|
||||
|
||||
static int xMBTCPPortAcceptConnection(int xListenSockId, char** pcIPAddr)
|
||||
{
|
||||
MB_PORT_CHECK(pcIPAddr, -1, "Wrong IP address pointer.");
|
||||
MB_PORT_CHECK((xListenSockId > 0), -1, "Incorrect listen socket ID.");
|
||||
|
||||
// Address structure large enough for both IPv4 or IPv6 address
|
||||
struct sockaddr_storage xSrcAddr;
|
||||
CHAR cAddrStr[128];
|
||||
int xSockId = -1;
|
||||
CHAR* pcStr = NULL;
|
||||
socklen_t xSize = sizeof(struct sockaddr_storage);
|
||||
|
||||
// Accept new socket connection if not active
|
||||
xSockId = accept(xListenSockId, (struct sockaddr *)&xSrcAddr, &xSize);
|
||||
if (xSockId < 0) {
|
||||
ESP_LOGE(TAG, "Unable to accept connection: errno=%u", (unsigned)errno);
|
||||
close(xSockId);
|
||||
} else {
|
||||
// Get the sender's ip address as string
|
||||
if (xSrcAddr.ss_family == PF_INET) {
|
||||
inet_ntoa_r(((struct sockaddr_in *)&xSrcAddr)->sin_addr.s_addr, cAddrStr, sizeof(cAddrStr) - 1);
|
||||
}
|
||||
#if CONFIG_LWIP_IPV6
|
||||
else if (xSrcAddr.ss_family == PF_INET6) {
|
||||
inet6_ntoa_r(((struct sockaddr_in6 *)&xSrcAddr)->sin6_addr, cAddrStr, sizeof(cAddrStr) - 1);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
// Make sure ss_family is valid
|
||||
abort();
|
||||
}
|
||||
ESP_LOGI(TAG, "Socket (#%d), accept client connection from address: %s", (int)xSockId, cAddrStr);
|
||||
pcStr = calloc(1, strlen(cAddrStr) + 1);
|
||||
if (pcStr && pcIPAddr) {
|
||||
memcpy(pcStr, cAddrStr, strlen(cAddrStr));
|
||||
pcStr[strlen(cAddrStr)] = '\0';
|
||||
*pcIPAddr = pcStr; // Set IP address of connected client
|
||||
}
|
||||
}
|
||||
return xSockId;
|
||||
}
|
||||
|
||||
static BOOL xMBTCPPortCloseConnection(MbClientInfo_t* pxInfo)
|
||||
{
|
||||
MB_PORT_CHECK(pxInfo, FALSE, "Client info is NULL.");
|
||||
|
||||
if (pxInfo->xSockId == -1) {
|
||||
ESP_LOGE(TAG, "Wrong socket info or disconnected socket: %d.", (int)pxInfo->xSockId);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Empty tcp buffer before shutdown
|
||||
(void)recv(pxInfo->xSockId, &pxInfo->pucTCPBuf[0], MB_PDU_SIZE_MAX, MSG_DONTWAIT);
|
||||
|
||||
if (shutdown(pxInfo->xSockId, SHUT_RDWR) == -1)
|
||||
{
|
||||
ESP_LOGE(TAG, "Socket (#%d), shutdown failed: errno %u", (int)pxInfo->xSockId, (unsigned)errno);
|
||||
}
|
||||
close(pxInfo->xSockId);
|
||||
pxInfo->xSockId = -1;
|
||||
if (xConfig.usClientCount) {
|
||||
xConfig.usClientCount--; // decrement counter of client connections
|
||||
} else {
|
||||
xConfig.pxCurClientInfo = NULL;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void vMBTCPPortFreeClientInfo(MbClientInfo_t *pxClientInfo)
|
||||
{
|
||||
if (pxClientInfo) {
|
||||
if (pxClientInfo->pucTCPBuf) {
|
||||
free((void *)pxClientInfo->pucTCPBuf);
|
||||
}
|
||||
if (pxClientInfo->pcIpAddr) {
|
||||
free((void *)pxClientInfo->pcIpAddr);
|
||||
}
|
||||
free((void *)pxClientInfo);
|
||||
}
|
||||
}
|
||||
|
||||
static void vMBTCPPortShutdown(void)
|
||||
{
|
||||
xSemaphoreGive(xShutdownSema);
|
||||
vTaskDelete(NULL);
|
||||
xConfig.xMbTcpTaskHandle = NULL;
|
||||
|
||||
for (int i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
|
||||
MbClientInfo_t *pxClientInfo = xConfig.pxMbClientInfo[i];
|
||||
if ((pxClientInfo != NULL) && (pxClientInfo->xSockId > 0)) {
|
||||
xMBTCPPortCloseConnection(pxClientInfo);
|
||||
vMBTCPPortFreeClientInfo(pxClientInfo);
|
||||
xConfig.pxMbClientInfo[i] = NULL;
|
||||
}
|
||||
}
|
||||
free(xConfig.pxMbClientInfo);
|
||||
}
|
||||
|
||||
static int xMBTCPPortRxPoll(MbClientInfo_t *pxClientInfo, ULONG xTimeoutMs)
|
||||
{
|
||||
int xRet = ERR_CLSD;
|
||||
struct timeval xTimeVal;
|
||||
fd_set xReadSet;
|
||||
int64_t xStartTimeStamp = 0;
|
||||
|
||||
// Receive data from connected client
|
||||
if (pxClientInfo && pxClientInfo->xSockId > -1) {
|
||||
// Set receive timeout
|
||||
vxMBTCPPortMStoTimeVal(xTimeoutMs, &xTimeVal);
|
||||
xStartTimeStamp = xMBTCPGetTimeStamp();
|
||||
while (1)
|
||||
{
|
||||
FD_ZERO(&xReadSet);
|
||||
FD_SET(pxClientInfo->xSockId, &xReadSet);
|
||||
xRet = select(pxClientInfo->xSockId + 1, &xReadSet, NULL, NULL, &xTimeVal);
|
||||
if (xRet == -1)
|
||||
{
|
||||
// If select an error occurred
|
||||
xRet = ERR_CLSD;
|
||||
TCP_PORT_CHECK_SHDN(xShutdownSema, vMBTCPPortShutdown);
|
||||
break;
|
||||
} else if (xRet == 0) {
|
||||
// timeout occurred
|
||||
if ((xStartTimeStamp + xTimeoutMs * 1000) > xMBTCPGetTimeStamp()) {
|
||||
ESP_LOGD(TAG, "Socket (#%d) Read timeout.", (int)pxClientInfo->xSockId);
|
||||
xRet = ERR_TIMEOUT;
|
||||
TCP_PORT_CHECK_SHDN(xShutdownSema, vMBTCPPortShutdown);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (FD_ISSET(pxClientInfo->xSockId, &xReadSet)) {
|
||||
// If new buffer received then read Modbus packet into buffer
|
||||
MB_PORT_CHECK((pxClientInfo->usTCPBufPos + pxClientInfo->usTCPFrameBytesLeft < MB_TCP_BUF_SIZE),
|
||||
ERR_BUF, "Socket (#%d), incorrect request buffer size = %u, ignore.",
|
||||
(int)pxClientInfo->xSockId,
|
||||
(unsigned)(pxClientInfo->usTCPBufPos + pxClientInfo->usTCPFrameBytesLeft));
|
||||
int xLength = recv(pxClientInfo->xSockId, &pxClientInfo->pucTCPBuf[pxClientInfo->usTCPBufPos],
|
||||
pxClientInfo->usTCPFrameBytesLeft, MSG_DONTWAIT);
|
||||
if (xLength < 0) {
|
||||
// If an error occurred during receiving
|
||||
ESP_LOGE(TAG, "Receive failed: length=%u, errno=%u", (unsigned)xLength, (unsigned)errno);
|
||||
xRet = (err_t)xLength;
|
||||
break;
|
||||
} else if (xLength == 0) {
|
||||
// Socket connection closed
|
||||
ESP_LOGD(TAG, "Socket (#%d)(%s), connection closed.",
|
||||
(int)pxClientInfo->xSockId, pxClientInfo->pcIpAddr);
|
||||
xRet = ERR_CLSD;
|
||||
break;
|
||||
} else {
|
||||
// New data received
|
||||
pxClientInfo->usTCPBufPos += xLength;
|
||||
pxClientInfo->usTCPFrameBytesLeft -= xLength;
|
||||
if (pxClientInfo->usTCPBufPos >= MB_TCP_FUNC) {
|
||||
// Length is a byte count of Modbus PDU (function code + data) and the
|
||||
// unit identifier.
|
||||
xLength = (int)MB_TCP_GET_FIELD(pxClientInfo->pucTCPBuf, MB_TCP_LEN);
|
||||
// Is the frame already complete.
|
||||
if (pxClientInfo->usTCPBufPos < (MB_TCP_UID + xLength)) {
|
||||
// The incomplete frame is received
|
||||
pxClientInfo->usTCPFrameBytesLeft = xLength + MB_TCP_UID - pxClientInfo->usTCPBufPos;
|
||||
} else if (pxClientInfo->usTCPBufPos == (MB_TCP_UID + xLength)) {
|
||||
#if MB_TCP_DEBUG
|
||||
prvvMBTCPLogFrame(TAG, (UCHAR*)&pxClientInfo->pucTCPBuf[0], pxClientInfo->usTCPBufPos);
|
||||
#endif
|
||||
// Copy TID field from incoming packet
|
||||
pxClientInfo->usTidCnt = MB_TCP_GET_FIELD(pxClientInfo->pucTCPBuf, MB_TCP_TID);
|
||||
xRet = pxClientInfo->usTCPBufPos;
|
||||
break;
|
||||
} else if ((pxClientInfo->usTCPBufPos + xLength) >= MB_TCP_BUF_SIZE) {
|
||||
ESP_LOGE(TAG, "Incorrect buffer received (%u) bytes.", (unsigned)xLength);
|
||||
// This should not happen. We can't deal with such a client and
|
||||
// drop the connection for security reasons.
|
||||
xRet = ERR_BUF;
|
||||
break;
|
||||
}
|
||||
} // if ( pxClientInfo->usTCPBufPos >= MB_TCP_FUNC )
|
||||
} // if data received
|
||||
} // if (FD_ISSET(pxClientInfo->xSockId, &xReadSet))
|
||||
} // while (1)
|
||||
}
|
||||
return (xRet);
|
||||
}
|
||||
|
||||
// Create a listening socket on pcBindIp: Port
|
||||
static int
|
||||
vMBTCPPortBindAddr(const CHAR* pcBindIp)
|
||||
{
|
||||
int xPar, xRet;
|
||||
int xListenSockFd = -1;
|
||||
struct addrinfo xHint;
|
||||
struct addrinfo* pxAddrList;
|
||||
struct addrinfo* pxCurAddr;
|
||||
CHAR* pcStr = NULL;
|
||||
|
||||
memset( &xHint, 0, sizeof( xHint ) );
|
||||
|
||||
// Bind to IPv6 and/or IPv4, but only in the desired protocol
|
||||
// Todo: Find a reason why AF_UNSPEC does not work for IPv6
|
||||
xHint.ai_family = (xConfig.xIpVer == MB_PORT_IPV4) ? AF_INET : AF_INET6;
|
||||
xHint.ai_socktype = (xConfig.eMbProto == MB_PROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
|
||||
// The LWIP has an issue when connection to IPv6 socket
|
||||
xHint.ai_protocol = (xConfig.eMbProto == MB_PROTO_UDP) ? IPPROTO_UDP : IPPROTO_TCP;
|
||||
xHint.ai_flags = AI_NUMERICSERV;
|
||||
|
||||
if (pcBindIp == NULL) {
|
||||
xHint.ai_flags |= AI_PASSIVE;
|
||||
} else {
|
||||
xHint.ai_flags |= AI_CANONNAME;
|
||||
}
|
||||
|
||||
if (asprintf(&pcStr, "%u", xConfig.usPort) == -1) {
|
||||
abort();
|
||||
}
|
||||
|
||||
xRet = getaddrinfo(pcBindIp, pcStr, &xHint, &pxAddrList);
|
||||
free(pcStr);
|
||||
|
||||
if (xRet != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Try the sockaddr until a binding succeeds
|
||||
for (pxCurAddr = pxAddrList; pxCurAddr != NULL; pxCurAddr = pxCurAddr->ai_next)
|
||||
{
|
||||
xListenSockFd = (int)socket(pxCurAddr->ai_family, pxCurAddr->ai_socktype,
|
||||
pxCurAddr->ai_protocol);
|
||||
if (xListenSockFd < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
xPar = 1;
|
||||
// Allow multi client connections
|
||||
if (setsockopt(xListenSockFd, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const char*)&xPar, sizeof(xPar)) != 0)
|
||||
{
|
||||
close(xListenSockFd);
|
||||
xListenSockFd = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bind(xListenSockFd, (struct sockaddr *)pxCurAddr->ai_addr,
|
||||
(socklen_t)pxCurAddr->ai_addrlen) != 0 )
|
||||
{
|
||||
close(xListenSockFd);
|
||||
xListenSockFd = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Listen only makes sense for TCP
|
||||
if (xConfig.eMbProto == MB_PROTO_TCP)
|
||||
{
|
||||
if (listen(xListenSockFd, MB_TCP_NET_LISTEN_BACKLOG) != 0)
|
||||
{
|
||||
ESP_LOGE(TAG, "Error occurred during listen: errno=%u", (unsigned)errno);
|
||||
close(xListenSockFd);
|
||||
xListenSockFd = -1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Bind was successful
|
||||
pcStr = (pxCurAddr->ai_canonname == NULL) ? (CHAR*)"ANY_IP" : pxCurAddr->ai_canonname;
|
||||
ESP_LOGI(TAG, "Socket (#%d), listener %s on port: %u, errno=%u",
|
||||
(int)xListenSockFd, pcStr, (unsigned)xConfig.usPort, (unsigned)errno);
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(pxAddrList);
|
||||
return(xListenSockFd);
|
||||
}
|
||||
|
||||
static void vMBTCPPortServerTask(void *pvParameters)
|
||||
{
|
||||
int xErr = 0;
|
||||
fd_set xReadSet;
|
||||
int i;
|
||||
CHAR* pcClientIp = NULL;
|
||||
struct timeval xTimeVal;
|
||||
|
||||
// Main connection cycle
|
||||
while (1) {
|
||||
// Create listen socket
|
||||
xListenSock = vMBTCPPortBindAddr(xConfig.pcBindAddr);
|
||||
if (xListenSock < 0) {
|
||||
TCP_PORT_CHECK_SHDN(xShutdownSema, vMBTCPPortShutdown);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Connections handling cycle
|
||||
while (1) {
|
||||
// clear the socket set
|
||||
FD_ZERO(&xReadSet);
|
||||
// add master socket to set
|
||||
FD_SET(xListenSock, &xReadSet);
|
||||
int xMaxSd = xListenSock;
|
||||
xConfig.usClientCount = 0;
|
||||
|
||||
vxMBTCPPortMStoTimeVal(1, &xTimeVal);
|
||||
// Initialize read set and file descriptor according to
|
||||
// all registered connected clients
|
||||
for (i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
|
||||
TCP_PORT_CHECK_SHDN(xShutdownSema, vMBTCPPortShutdown);
|
||||
if ((xConfig.pxMbClientInfo[i] != NULL) && (xConfig.pxMbClientInfo[i]->xSockId > 0)) {
|
||||
// calculate max file descriptor for select
|
||||
xMaxSd = (xConfig.pxMbClientInfo[i]->xSockId > xMaxSd) ?
|
||||
xConfig.pxMbClientInfo[i]->xSockId : xMaxSd;
|
||||
FD_SET(xConfig.pxMbClientInfo[i]->xSockId, &xReadSet);
|
||||
xConfig.usClientCount++;
|
||||
}
|
||||
}
|
||||
|
||||
vxMBTCPPortMStoTimeVal(MB_TCP_RESP_TIMEOUT_MS, &xTimeVal);
|
||||
|
||||
// Wait for an activity on one of the sockets during timeout
|
||||
xErr = select(xMaxSd + 1, &xReadSet, NULL, NULL, &xTimeVal);
|
||||
if ((xErr < 0) && (errno != EINTR)) {
|
||||
// error occurred during wait for read
|
||||
ESP_LOGE(TAG, "select() errno = %u.", (unsigned)errno);
|
||||
TCP_PORT_CHECK_SHDN(xShutdownSema, vMBTCPPortShutdown);
|
||||
continue;
|
||||
} else if (xErr == 0) {
|
||||
// If timeout happened, something is wrong
|
||||
ESP_LOGD(TAG, "select() timeout, errno = %u.", (unsigned)errno);
|
||||
TCP_PORT_CHECK_SHDN(xShutdownSema, vMBTCPPortShutdown);
|
||||
}
|
||||
|
||||
// If something happened on the master socket, then its an incoming connection.
|
||||
if (FD_ISSET(xListenSock, &xReadSet) && xConfig.usClientCount < MB_TCP_PORT_MAX_CONN) {
|
||||
MbClientInfo_t* pxClientInfo = NULL;
|
||||
// find first empty place to insert connection info
|
||||
for (i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
|
||||
pxClientInfo = xConfig.pxMbClientInfo[i];
|
||||
if (pxClientInfo == NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if request for new connection but no space left
|
||||
if (pxClientInfo != NULL) {
|
||||
if (xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] == NULL) {
|
||||
ESP_LOGE(TAG, "Fail to accept connection %u, only %u connections supported.", (unsigned)(i + 1), (unsigned)MB_TCP_PORT_MAX_CONN);
|
||||
}
|
||||
xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] = pxClientInfo; // set last connection info
|
||||
} else {
|
||||
// allocate memory for new client info
|
||||
pxClientInfo = calloc(1, sizeof(MbClientInfo_t));
|
||||
if (!pxClientInfo) {
|
||||
ESP_LOGE(TAG, "Client info allocation fail.");
|
||||
vMBTCPPortFreeClientInfo(pxClientInfo);
|
||||
pxClientInfo = NULL;
|
||||
} else {
|
||||
// Accept new client connection
|
||||
pxClientInfo->xSockId = xMBTCPPortAcceptConnection(xListenSock, &pcClientIp);
|
||||
if (pxClientInfo->xSockId < 0) {
|
||||
ESP_LOGE(TAG, "Fail to accept connection for client %u.", (unsigned)(xConfig.usClientCount - 1));
|
||||
// Accept connection fail, then free client info and continue polling.
|
||||
vMBTCPPortFreeClientInfo(pxClientInfo);
|
||||
pxClientInfo = NULL;
|
||||
continue;
|
||||
}
|
||||
pxClientInfo->pucTCPBuf = calloc(MB_TCP_BUF_SIZE, sizeof(UCHAR));
|
||||
if (!pxClientInfo->pucTCPBuf) {
|
||||
ESP_LOGE(TAG, "Fail to allocate buffer for client %u.", (unsigned)(xConfig.usClientCount - 1));
|
||||
vMBTCPPortFreeClientInfo(pxClientInfo);
|
||||
pxClientInfo = NULL;
|
||||
continue;
|
||||
}
|
||||
// Fill the connection info structure
|
||||
xConfig.pxMbClientInfo[i] = pxClientInfo;
|
||||
pxClientInfo->xIndex = i;
|
||||
xConfig.usClientCount++;
|
||||
pxClientInfo->pcIpAddr = pcClientIp;
|
||||
pxClientInfo->xRecvTimeStamp = xMBTCPGetTimeStamp();
|
||||
xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] = NULL;
|
||||
pxClientInfo->usTCPFrameBytesLeft = MB_TCP_FUNC;
|
||||
pxClientInfo->usTCPBufPos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle data request from client
|
||||
if (xErr > 0) {
|
||||
// Handling client connection requests
|
||||
for (i = 0; i < MB_TCP_PORT_MAX_CONN; i++) {
|
||||
MbClientInfo_t* pxClientInfo = xConfig.pxMbClientInfo[i];
|
||||
if ((pxClientInfo != NULL) && (pxClientInfo->xSockId > 0)) {
|
||||
if (FD_ISSET(pxClientInfo->xSockId, &xReadSet)) {
|
||||
// Other sockets are ready to be read
|
||||
xErr = xMBTCPPortRxPoll(pxClientInfo, MB_TCP_READ_TIMEOUT_MS);
|
||||
// If an invalid data received from socket or connection fail
|
||||
// or if timeout then drop connection and restart
|
||||
if (xErr < 0) {
|
||||
uint64_t xTimeStamp = xMBTCPGetTimeStamp();
|
||||
// If data update is timed out
|
||||
switch(xErr)
|
||||
{
|
||||
case ERR_TIMEOUT:
|
||||
ESP_LOGE(TAG, "Socket (#%d)(%s), data receive timeout, time[us]: %" PRIu64 ", close active connection.",
|
||||
(int)pxClientInfo->xSockId, pxClientInfo->pcIpAddr,
|
||||
(uint64_t)(xTimeStamp - pxClientInfo->xRecvTimeStamp));
|
||||
break;
|
||||
case ERR_CLSD:
|
||||
ESP_LOGE(TAG, "Socket (#%d)(%s), connection closed by peer.",
|
||||
(int)pxClientInfo->xSockId, pxClientInfo->pcIpAddr);
|
||||
break;
|
||||
case ERR_BUF:
|
||||
default:
|
||||
ESP_LOGE(TAG, "Socket (#%d)(%s), read data error: 0x%x",
|
||||
(int)pxClientInfo->xSockId, pxClientInfo->pcIpAddr, (int)xErr);
|
||||
break;
|
||||
}
|
||||
|
||||
TCP_PORT_CHECK_SHDN(xShutdownSema, vMBTCPPortShutdown);
|
||||
|
||||
// Close client connection
|
||||
xMBTCPPortCloseConnection(pxClientInfo);
|
||||
|
||||
// This client does not respond, then unregister it
|
||||
vMBTCPPortFreeClientInfo(pxClientInfo);
|
||||
xConfig.pxMbClientInfo[i] = NULL;
|
||||
xConfig.pxMbClientInfo[MB_TCP_PORT_MAX_CONN] = NULL;
|
||||
// If no any active connections, break
|
||||
if (!xConfig.usClientCount) {
|
||||
xConfig.pxCurClientInfo = NULL;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pxClientInfo->xRecvTimeStamp = xMBTCPGetTimeStamp();
|
||||
|
||||
// set current client info to active client from which we received request
|
||||
xConfig.pxCurClientInfo = pxClientInfo;
|
||||
|
||||
// Complete frame received, inform state machine to process frame
|
||||
xMBPortEventPost(EV_FRAME_RECEIVED);
|
||||
|
||||
ESP_LOGD(TAG, "Socket (#%d)(%s), get packet TID=0x%X, %d bytes.",
|
||||
(int)pxClientInfo->xSockId, pxClientInfo->pcIpAddr,
|
||||
(int)pxClientInfo->usTidCnt, (int)xErr);
|
||||
|
||||
// Wait while response is not processed by stack by timeout
|
||||
UCHAR* pucSentBuffer = vxMBTCPPortRespQueueRecv(xConfig.xRespQueueHandle);
|
||||
if (pucSentBuffer == NULL) {
|
||||
ESP_LOGD(TAG, "Response is ignored, time exceeds configured %d [ms].",
|
||||
(unsigned)MB_TCP_RESP_TIMEOUT_MS);
|
||||
} else {
|
||||
USHORT usSentTid = MB_TCP_GET_FIELD(pucSentBuffer, MB_TCP_TID);
|
||||
if (usSentTid != pxClientInfo->usTidCnt) {
|
||||
ESP_LOGE(TAG, "Sent TID(%x) != Recv TID(%x), ignore packet.",
|
||||
(int)usSentTid, (int)pxClientInfo->usTidCnt);
|
||||
}
|
||||
}
|
||||
|
||||
// Get time stamp of last data update
|
||||
pxClientInfo->xSendTimeStamp = xMBTCPGetTimeStamp();
|
||||
ESP_LOGD(TAG, "Client %d, Socket(#%d), processing time = %" PRIu64 "(us).",
|
||||
(int)pxClientInfo->xIndex, (int)pxClientInfo->xSockId,
|
||||
(uint64_t)(pxClientInfo->xSendTimeStamp - pxClientInfo->xRecvTimeStamp));
|
||||
}
|
||||
} else {
|
||||
if (pxClientInfo) {
|
||||
// client is not ready to be read
|
||||
int64_t xTime = xMBTCPGetTimeStamp() - pxClientInfo->xRecvTimeStamp;
|
||||
if (xTime > MB_TCP_DISCONNECT_TIMEOUT) {
|
||||
ESP_LOGE(TAG, "Client %d, Socket(#%d) do not answer for %" PRIu64 " (us). Drop connection...",
|
||||
(int)pxClientInfo->xIndex, (int)pxClientInfo->xSockId, (uint64_t)xTime);
|
||||
xMBTCPPortCloseConnection(pxClientInfo);
|
||||
|
||||
// This client does not respond, then delete registered data
|
||||
vMBTCPPortFreeClientInfo(pxClientInfo);
|
||||
xConfig.pxMbClientInfo[i] = NULL;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Client %d is disconnected.", (int)i);
|
||||
}
|
||||
}
|
||||
} // if ((pxClientInfo != NULL)
|
||||
} // Handling client connection requests
|
||||
}
|
||||
} // while(1) // Handle connection cycle
|
||||
} // Main connection cycle
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void
|
||||
vMBTCPPortClose( )
|
||||
{
|
||||
// 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 (xShutdownSema == NULL || // if no semaphore (alloc issues) or couldn't acquire it, just delete the task
|
||||
xSemaphoreTake(xShutdownSema, 2 * pdMS_TO_TICKS(CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND)) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "Task couldn't exit gracefully within timeout -> abruptly deleting the task");
|
||||
vTaskDelete(xConfig.xMbTcpTaskHandle);
|
||||
}
|
||||
|
||||
close(xListenSock);
|
||||
xListenSock = -1;
|
||||
|
||||
vMBTCPPortRespQueueDelete(xConfig.xRespQueueHandle);
|
||||
|
||||
if (xShutdownSema) {
|
||||
vSemaphoreDelete(xShutdownSema);
|
||||
xShutdownSema = NULL;
|
||||
}
|
||||
vMBPortEventClose();
|
||||
}
|
||||
|
||||
void vMBTCPPortEnable( void )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
vMBTCPPortDisable( void )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BOOL
|
||||
xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
|
||||
{
|
||||
BOOL xRet = FALSE;
|
||||
if (xConfig.pxCurClientInfo) {
|
||||
*ppucMBTCPFrame = &xConfig.pxCurClientInfo->pucTCPBuf[0];
|
||||
*usTCPLength = xConfig.pxCurClientInfo->usTCPBufPos;
|
||||
|
||||
// Reset the buffer.
|
||||
xConfig.pxCurClientInfo->usTCPBufPos = 0;
|
||||
xConfig.pxCurClientInfo->usTCPFrameBytesLeft = MB_TCP_FUNC;
|
||||
xRet = TRUE;
|
||||
}
|
||||
return xRet;
|
||||
}
|
||||
|
||||
BOOL
|
||||
xMBTCPPortSendResponse( UCHAR * pucMBTCPFrame, USHORT usTCPLength )
|
||||
{
|
||||
BOOL bFrameSent = FALSE;
|
||||
fd_set xWriteSet;
|
||||
fd_set xErrorSet;
|
||||
int xErr = -1;
|
||||
struct timeval xTimeVal;
|
||||
|
||||
if (xConfig.pxCurClientInfo) {
|
||||
FD_ZERO(&xWriteSet);
|
||||
FD_ZERO(&xErrorSet);
|
||||
FD_SET(xConfig.pxCurClientInfo->xSockId, &xWriteSet);
|
||||
FD_SET(xConfig.pxCurClientInfo->xSockId, &xErrorSet);
|
||||
vxMBTCPPortMStoTimeVal(MB_TCP_SEND_TIMEOUT_MS, &xTimeVal);
|
||||
// Check if socket writable
|
||||
xErr = select(xConfig.pxCurClientInfo->xSockId + 1, NULL, &xWriteSet, &xErrorSet, &xTimeVal);
|
||||
if ((xErr == -1) || FD_ISSET(xConfig.pxCurClientInfo->xSockId, &xErrorSet)) {
|
||||
ESP_LOGE(TAG, "Socket(#%d) , send select() error = %u.",
|
||||
(int)xConfig.pxCurClientInfo->xSockId, (unsigned)errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Apply TID field from request to the frame before send response
|
||||
pucMBTCPFrame[MB_TCP_TID] = (UCHAR)(xConfig.pxCurClientInfo->usTidCnt >> 8U);
|
||||
pucMBTCPFrame[MB_TCP_TID + 1] = (UCHAR)(xConfig.pxCurClientInfo->usTidCnt & 0xFF);
|
||||
|
||||
// Write message into socket and disable Nagle's algorithm
|
||||
xErr = send(xConfig.pxCurClientInfo->xSockId, pucMBTCPFrame, usTCPLength, TCP_NODELAY);
|
||||
if (xErr < 0) {
|
||||
ESP_LOGE(TAG, "Socket(#%d), fail to send data, errno = %u",
|
||||
(int)xConfig.pxCurClientInfo->xSockId, (unsigned)errno);
|
||||
xConfig.pxCurClientInfo->xError = xErr;
|
||||
} else {
|
||||
bFrameSent = TRUE;
|
||||
vxMBTCPPortRespQueueSend(xConfig.xRespQueueHandle, (void*)pucMBTCPFrame);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Port is not active. Release lock.");
|
||||
vxMBTCPPortRespQueueSend(xConfig.xRespQueueHandle, (void*)pucMBTCPFrame);
|
||||
}
|
||||
return bFrameSent;
|
||||
}
|
||||
|
||||
#endif //#if MB_TCP_ENABLED
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 <wolti@sil.at>
|
||||
* 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/opt.h"
|
||||
#include "lwip/sys.h"
|
||||
#include "port.h"
|
||||
#include "esp_modbus_common.h" // for common types for network options
|
||||
|
||||
/* ----------------------- 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; /*!< Modbus info index */
|
||||
int xSockId; /*!< Socket id */
|
||||
int xError; /*!< TCP/UDP sock error */
|
||||
const char* pcIpAddr; /*!< TCP/UDP IP address (string) */
|
||||
UCHAR* pucTCPBuf; /*!< buffer pointer */
|
||||
USHORT usTCPBufPos; /*!< buffer active position */
|
||||
USHORT usTCPFrameBytesLeft; /*!< buffer left bytes to receive transaction */
|
||||
int64_t xSendTimeStamp; /*!< send request timestamp */
|
||||
int64_t xRecvTimeStamp; /*!< receive response timestamp */
|
||||
USHORT usTidCnt; /*!< last TID counter from packet */
|
||||
} MbClientInfo_t;
|
||||
|
||||
typedef struct {
|
||||
TaskHandle_t xMbTcpTaskHandle; /*!< Server task handle */
|
||||
QueueHandle_t xRespQueueHandle; /*!< Response queue handle */
|
||||
MbClientInfo_t* pxCurClientInfo; /*!< Current client info */
|
||||
MbClientInfo_t** pxMbClientInfo; /*!< Pointers to information about connected clients */
|
||||
USHORT usPort; /*!< TCP/UDP port number */
|
||||
CHAR* pcBindAddr; /*!< IP address to bind */
|
||||
eMBPortProto eMbProto; /*!< Protocol type used by port */
|
||||
USHORT usClientCount; /*!< Client connection count */
|
||||
void* pvNetIface; /*!< Network netif interface pointer for port */
|
||||
eMBPortIpVer xIpVer; /*!< IP protocol version */
|
||||
} MbSlavePortConfig_t;
|
||||
|
||||
/* ----------------------- Function prototypes ------------------------------*/
|
||||
|
||||
/**
|
||||
* Function to setup communication options for TCP/UDP Modbus port
|
||||
*
|
||||
* @param pvNetIf netif interface pointer
|
||||
* @param xIpVersion IP version
|
||||
* @param xProto protocol type option
|
||||
* @param pcBindAddr IP bind address
|
||||
*
|
||||
* @return error code
|
||||
*/
|
||||
void vMBTCPPortSlaveSetNetOpt(void* pvNetIf, eMBPortIpVer xIpVersion, eMBPortProto xProto, CHAR* pcBindAddr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
PR_END_EXTERN_C
|
||||
#endif
|
||||
#endif
|
||||
Reference in New Issue
Block a user