mirror of
https://github.com/espressif/esp-modbus.git
synced 2026-08-03 20:24:09 +02:00
666 lines
30 KiB
C
666 lines
30 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2016-2023 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
// mbc_serial_master.c
|
|
// Serial master implementation of the Modbus controller
|
|
|
|
#include <sys/time.h> // for calculation of time stamp in milliseconds
|
|
#include "esp_log.h" // for log_write
|
|
#include <string.h> // for memcpy
|
|
#include "freertos/FreeRTOS.h" // for task creation
|
|
#include "freertos/task.h" // for task api access
|
|
#include "freertos/event_groups.h" // for event groups
|
|
|
|
#include "sdkconfig.h" // for KConfig values
|
|
#include "esp_modbus_common.h" // for common types
|
|
#include "esp_modbus_master.h" // for public master types
|
|
#include "mbc_master.h" // for private master types
|
|
#include "mbc_serial_master.h" // for serial master create function and types
|
|
|
|
#include "mb_common.h" // for mb types definition
|
|
#include "mb_config.h"
|
|
#include "mb_proto.h"
|
|
|
|
#if (CONFIG_FMB_COMM_MODE_ASCII_EN || CONFIG_FMB_COMM_MODE_RTU_EN)
|
|
|
|
/*-----------------------Master mode use these variables----------------------*/
|
|
|
|
static const char *TAG = "mbc_serial.master";
|
|
|
|
// Modbus event processing task
|
|
static void mbc_ser_master_task(void *param)
|
|
{
|
|
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(param);
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(param);
|
|
|
|
// Main Modbus stack processing cycle
|
|
for (;;) {
|
|
// Wait for poll events
|
|
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
|
|
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
|
pdFALSE,
|
|
pdFALSE,
|
|
portMAX_DELAY);
|
|
// Check if stack started then poll for data
|
|
if (status & MB_EVENT_STACK_STARTED) {
|
|
(void)mbm_iface->mb_base->poll(mbm_iface->mb_base);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Modbus controller stack start function
|
|
static esp_err_t mbc_serial_master_start(void *ctx)
|
|
{
|
|
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
|
mb_err_enum_t status = MB_EIO;
|
|
mbm_iface->mb_base->descr.parent = ctx;
|
|
MB_RETURN_ON_FALSE((mbm_opts->mbm_param_descriptor_size >= 1),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb descriptor table size is incorrect.");
|
|
status = mbm_iface->mb_base->enable(mbm_iface->mb_base);
|
|
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
|
"mb stack start fail, returned (0x%x).", (int)status);
|
|
// Set the mbcontroller start flag
|
|
EventBits_t flag = xEventGroupSetBits(mbm_opts->event_group_handle,
|
|
(EventBits_t)MB_EVENT_STACK_STARTED);
|
|
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
|
ESP_ERR_INVALID_STATE, TAG, "mb stack start event set error.");
|
|
mbm_iface->is_active = true;
|
|
return ESP_OK;
|
|
}
|
|
|
|
// Modbus controller stack stop function
|
|
static esp_err_t mbc_serial_master_stop(void *ctx)
|
|
{
|
|
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
|
mb_err_enum_t status = MB_EIO;
|
|
mbm_iface->mb_base->descr.parent = ctx;
|
|
|
|
// Set the mbcontroller start flag
|
|
EventBits_t flag = xEventGroupClearBits(mbm_opts->event_group_handle,
|
|
(EventBits_t)MB_EVENT_STACK_STARTED);
|
|
status = mbm_iface->mb_base->disable(mbm_iface->mb_base);
|
|
MB_RETURN_ON_FALSE((status == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
|
"mb stack disable fail, returned (0x%x).", (int)status);
|
|
MB_RETURN_ON_FALSE((flag & MB_EVENT_STACK_STARTED),
|
|
ESP_ERR_INVALID_STATE, TAG, "mb stack stop event set error.");
|
|
mbm_iface->is_active = false;
|
|
return ESP_OK;
|
|
}
|
|
|
|
// Modbus controller destroy function
|
|
static esp_err_t mbc_serial_master_delete(void *ctx)
|
|
{
|
|
mbm_controller_iface_t *mbm_iface = MB_MASTER_GET_IFACE(ctx);
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
|
mb_err_enum_t mb_error = MB_ENOERR;
|
|
|
|
// Check the stack started bit
|
|
BaseType_t status = xEventGroupWaitBits(mbm_opts->event_group_handle,
|
|
(BaseType_t)(MB_EVENT_STACK_STARTED),
|
|
pdFALSE,
|
|
pdFALSE,
|
|
pdMS_TO_TICKS(MB_MASTER_TIMEOUT_MS_RESPOND));
|
|
if (mbm_iface->is_active || (status & MB_EVENT_STACK_STARTED)) {
|
|
ESP_LOGV(TAG, "mb stack is active, try to disable.");
|
|
MB_RETURN_ON_FALSE((mbc_serial_master_stop(ctx) == ESP_OK),
|
|
ESP_ERR_INVALID_STATE, TAG, "mb stack stop failure.");
|
|
}
|
|
|
|
mbm_iface->is_active = false;
|
|
vTaskDelete(mbm_opts->task_handle);
|
|
mbm_opts->task_handle = NULL;
|
|
vEventGroupDelete(mbm_opts->event_group_handle);
|
|
mbm_opts->event_group_handle = NULL;
|
|
vSemaphoreDelete(mbm_opts->mbm_sema);
|
|
mbm_opts->mbm_sema = NULL;
|
|
// delete mb_base instance and all its allocations
|
|
mb_error = mbm_iface->mb_base->delete (mbm_iface->mb_base);
|
|
MB_RETURN_ON_FALSE((mb_error == MB_ENOERR), ESP_ERR_INVALID_STATE, TAG,
|
|
"mb stack delete failure, returned (0x%x).", (int)mb_error);
|
|
mbm_iface->mb_base = NULL;
|
|
free(mbm_iface); // free the memory allocated
|
|
return ESP_OK;
|
|
}
|
|
|
|
// Set Modbus parameter description table
|
|
static esp_err_t mbc_serial_master_set_descriptor(void *ctx, const mb_parameter_descriptor_t *descriptor, const uint16_t num_elements)
|
|
{
|
|
MB_RETURN_ON_FALSE((descriptor), ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor.");
|
|
MB_RETURN_ON_FALSE((num_elements >= 1), ESP_ERR_INVALID_ARG, TAG, "mb table size is incorrect.");
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
|
const mb_parameter_descriptor_t *reg_ptr = descriptor;
|
|
// Go through all items in the table to check all Modbus registers
|
|
for (uint16_t counter = 0; counter < (num_elements); counter++, reg_ptr++) {
|
|
// Below is the code to check consistency of the table format and required fields.
|
|
MB_RETURN_ON_FALSE((reg_ptr->cid == counter),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb descriptor cid field is incorrect.");
|
|
MB_RETURN_ON_FALSE((reg_ptr->param_key),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb descriptor param key is incorrect.");
|
|
MB_RETURN_ON_FALSE((reg_ptr->mb_size > 0),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb descriptor param size is incorrect.");
|
|
}
|
|
mbm_opts->param_descriptor_table = descriptor;
|
|
mbm_opts->mbm_param_descriptor_size = num_elements;
|
|
return ESP_OK;
|
|
}
|
|
|
|
// Send custom Modbus request defined as mb_param_request_t structure
|
|
static esp_err_t mbc_serial_master_send_request(void *ctx, mb_param_request_t *request, void *data_ptr)
|
|
{
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
|
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
|
|
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb request structure.");
|
|
MB_RETURN_ON_FALSE((data_ptr), ESP_ERR_INVALID_ARG, TAG, "mb incorrect data pointer.");
|
|
|
|
mb_err_enum_t mb_error = MB_EBUSY;
|
|
|
|
if (xSemaphoreTake(mbm_opts->mbm_sema, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS)) == pdTRUE) {
|
|
uint8_t mb_slave_addr = request->slave_addr;
|
|
uint8_t mb_command = request->command;
|
|
uint16_t mb_offset = request->reg_start;
|
|
uint16_t mb_size = request->reg_size;
|
|
|
|
// Set the buffer for callback function processing of received data
|
|
mbm_opts->reg_buffer_ptr = (uint8_t *)data_ptr;
|
|
mbm_opts->reg_buffer_size = mb_size;
|
|
|
|
// Calls appropriate request function to send request and waits response
|
|
switch (mb_command) {
|
|
#if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
|
|
case MB_FUNC_OTHER_REPORT_SLAVEID:
|
|
mb_error = mbm_rq_report_slave_id(mbm_controller_iface->mb_base, mb_slave_addr, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_READ_COILS_ENABLED
|
|
case MB_FUNC_READ_COILS:
|
|
mb_error = mbm_rq_read_coils(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
mb_size, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_WRITE_COIL_ENABLED
|
|
case MB_FUNC_WRITE_SINGLE_COIL:
|
|
mb_error = mbm_rq_write_coil(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
*(uint16_t *)data_ptr, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED
|
|
case MB_FUNC_WRITE_MULTIPLE_COILS:
|
|
mb_error = mbm_rq_write_multi_coils(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
mb_size, (uint8_t *)data_ptr, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_READ_DISCRETE_INPUTS_ENABLED
|
|
case MB_FUNC_READ_DISCRETE_INPUTS:
|
|
mb_error = mbm_rq_read_discrete_inputs(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
mb_size, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_READ_HOLDING_ENABLED
|
|
case MB_FUNC_READ_HOLDING_REGISTER:
|
|
mb_error = mbm_rq_read_holding_reg(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
mb_size, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_WRITE_HOLDING_ENABLED
|
|
case MB_FUNC_WRITE_REGISTER:
|
|
mb_error = mbm_rq_write_holding_reg(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
*(uint16_t *)data_ptr, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED
|
|
case MB_FUNC_WRITE_MULTIPLE_REGISTERS:
|
|
mb_error = mbm_rq_write_multi_holding_reg(mbm_controller_iface->mb_base, mb_slave_addr,
|
|
mb_offset, mb_size, (uint16_t *)data_ptr, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_READWRITE_HOLDING_ENABLED
|
|
case MB_FUNC_READWRITE_MULTIPLE_REGISTERS:
|
|
mb_error = mbm_rq_rw_multi_holding_reg(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
mb_size, (uint16_t *)data_ptr,
|
|
mb_offset, mb_size, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
|
|
#if MB_FUNC_READ_INPUT_ENABLED
|
|
case MB_FUNC_READ_INPUT_REGISTER:
|
|
mb_error = mbm_rq_read_inp_reg(mbm_controller_iface->mb_base, mb_slave_addr, mb_offset,
|
|
mb_size, pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
break;
|
|
#endif
|
|
default:
|
|
mb_fn_handler_fp handler = NULL;
|
|
// check registered function handler
|
|
mb_error = mbm_get_handler(mbm_controller_iface->mb_base, mb_command, &handler);
|
|
if (mb_error == MB_ENOERR) {
|
|
// send the request for custom command
|
|
mb_error = mbm_rq_custom(mbm_controller_iface->mb_base, mb_slave_addr, mb_command,
|
|
data_ptr, (uint16_t)(mb_size << 1),
|
|
pdMS_TO_TICKS(MB_MAX_RESP_DELAY_MS));
|
|
ESP_LOGD(TAG, "%s: Send custom request (%u)", __FUNCTION__, mb_command);
|
|
} else {
|
|
ESP_LOGE(TAG, "%s: Incorrect or unsupported function in request (%u), error = (0x%x)",
|
|
__FUNCTION__, mb_command, (int)mb_error);
|
|
mb_error = MB_ENOREG;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
ESP_LOGD(TAG, "%s:MBC semaphore take fail.", __func__);
|
|
}
|
|
(void)xSemaphoreGive(mbm_opts->mbm_sema);
|
|
|
|
// Propagate the Modbus errors to higher level
|
|
return MB_ERR_TO_ESP_ERR(mb_error);
|
|
}
|
|
|
|
static esp_err_t mbc_serial_master_get_cid_info(void *ctx, uint16_t cid, const mb_parameter_descriptor_t **param_buffer)
|
|
{
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
|
|
|
MB_RETURN_ON_FALSE((param_buffer),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb incorrect data buffer pointer.");
|
|
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb incorrect descriptor table or not set.");
|
|
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size),
|
|
ESP_ERR_NOT_FOUND, TAG, "mb incorrect cid of characteristic.");
|
|
|
|
// It is assumed that characteristics cid increased in the table
|
|
const mb_parameter_descriptor_t *reg_info = &mbm_opts->param_descriptor_table[cid];
|
|
|
|
MB_RETURN_ON_FALSE((reg_info->param_key),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb incorrect characteristic key.");
|
|
*param_buffer = reg_info;
|
|
return ESP_OK;
|
|
}
|
|
|
|
// Helper to search parameter by name in the parameter description table
|
|
// and fills Modbus request fields accordingly
|
|
static esp_err_t mbc_serial_master_set_request(void *ctx, uint16_t cid, mb_param_mode_t mode,
|
|
mb_param_request_t *request,
|
|
mb_parameter_descriptor_t *reg_data)
|
|
{
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(ctx);
|
|
esp_err_t error = ESP_ERR_NOT_FOUND;
|
|
MB_RETURN_ON_FALSE((request), ESP_ERR_INVALID_ARG, TAG, "mb incorrect request parameter.");
|
|
MB_RETURN_ON_FALSE((mode <= MB_PARAM_WRITE), ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode.");
|
|
MB_RETURN_ON_FALSE((cid < mbm_opts->mbm_param_descriptor_size), ESP_ERR_INVALID_ARG, TAG, "mb incorrect cid parameter.");
|
|
MB_RETURN_ON_FALSE((mbm_opts->param_descriptor_table), ESP_ERR_INVALID_ARG, TAG, "mb data dictionary is incorrect.");
|
|
const mb_parameter_descriptor_t *reg_ptr = mbm_opts->param_descriptor_table;
|
|
reg_ptr += cid;
|
|
if (reg_ptr->cid == cid) {
|
|
request->slave_addr = reg_ptr->mb_slave_addr;
|
|
request->reg_start = reg_ptr->mb_reg_start;
|
|
request->reg_size = reg_ptr->mb_size;
|
|
request->command = mbc_master_get_command(reg_ptr, mode);
|
|
MB_RETURN_ON_FALSE((request->command > 0), ESP_ERR_INVALID_ARG, TAG, "mb incorrect command or parameter type.");
|
|
if (reg_data) {
|
|
*reg_data = *reg_ptr; // Set the cid registered parameter data
|
|
}
|
|
error = ESP_OK;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
// Get parameter data for corresponding characteristic
|
|
static esp_err_t mbc_serial_master_get_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
|
{
|
|
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
|
|
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
|
mbm_controller_iface_t *mbm_controller_iface = MB_MASTER_GET_IFACE(ctx);
|
|
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
|
mb_param_request_t request ;
|
|
mb_parameter_descriptor_t reg_info = { 0 };
|
|
uint8_t *data_ptr = NULL;
|
|
|
|
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, ®_info);
|
|
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
|
|
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
|
// alloc buffer to store parameter data
|
|
data_ptr = calloc(1, (reg_info.mb_size << 1));
|
|
if (!data_ptr) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
error = mbc_serial_master_send_request(ctx, &request, data_ptr);
|
|
if (error == ESP_OK) {
|
|
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
|
|
if (value) {
|
|
error = mbc_master_set_param_data((void *)value, (void *)data_ptr,
|
|
reg_info.param_type, reg_info.param_size);
|
|
if (error != ESP_OK) {
|
|
ESP_LOGE(TAG, "%p fail to set parameter data.", mbm_controller_iface);
|
|
error = ESP_ERR_INVALID_STATE;
|
|
} else {
|
|
ESP_LOGD(TAG, "%s: %p Good response for get cid(%u) = %s",
|
|
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
}
|
|
}
|
|
} else {
|
|
ESP_LOGD(TAG, "%s: %p Bad response to get cid(%u) = %s",
|
|
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
}
|
|
free(data_ptr);
|
|
// Set the type of parameter found in the table
|
|
*type = reg_info.param_type;
|
|
} else {
|
|
ESP_LOGE(TAG, "%s: %p The cid(%u) not found in the data dictionary.",
|
|
__FUNCTION__, mbm_controller_iface, (unsigned)reg_info.cid);
|
|
error = ESP_ERR_INVALID_ARG;
|
|
}
|
|
return error;
|
|
}
|
|
// Get parameter data for corresponding characteristic
|
|
static esp_err_t mbc_serial_master_get_parameter_with(void *ctx, uint16_t cid, uint8_t uid,
|
|
uint8_t *value_ptr, uint8_t *type)
|
|
{
|
|
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
|
|
MB_RETURN_ON_FALSE((value_ptr), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
|
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
|
mb_param_request_t request;
|
|
mb_parameter_descriptor_t reg_info = {0};
|
|
uint8_t *data_ptr = NULL;
|
|
|
|
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_READ, &request, ®_info);
|
|
if ((error == ESP_OK) && (cid == reg_info.cid)) {
|
|
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER) {
|
|
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
|
|
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
|
|
}
|
|
request.slave_addr = uid; // override the UID
|
|
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
|
// alloc buffer to store parameter data
|
|
data_ptr = calloc(1, (reg_info.mb_size << 1));
|
|
if (!data_ptr) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
// Send request to read characteristic data
|
|
error = mbc_serial_master_send_request(ctx, &request, data_ptr);
|
|
if (error == ESP_OK) {
|
|
// If data pointer is NULL then we don't need to set value (it is still in the cache of cid)
|
|
if (value_ptr) {
|
|
error = mbc_master_set_param_data((void *)value_ptr, (void *)data_ptr,
|
|
reg_info.param_type, reg_info.param_size);
|
|
if (error != ESP_OK) {
|
|
ESP_LOGE(TAG, "fail to set parameter data.");
|
|
error = ESP_ERR_INVALID_STATE;
|
|
} else {
|
|
ESP_LOGD(TAG, "%s: Good response for get cid(%u) = %s",
|
|
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
}
|
|
}
|
|
} else {
|
|
ESP_LOGD(TAG, "%s: Bad response to get cid(%u) = %s",
|
|
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
}
|
|
free(data_ptr);
|
|
// Set the type of parameter found in the table
|
|
*type = reg_info.param_type;
|
|
} else {
|
|
ESP_LOGE(TAG, "%s: The cid(%u) not found in the data dictionary.",
|
|
__FUNCTION__, (unsigned)reg_info.cid);
|
|
error = ESP_ERR_INVALID_ARG;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
// Set parameter value for characteristic selected by cid
|
|
static esp_err_t mbc_serial_master_set_parameter(void *ctx, uint16_t cid, uint8_t *value, uint8_t *type)
|
|
{
|
|
MB_RETURN_ON_FALSE((value), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
|
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
|
|
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
|
mb_param_request_t request ;
|
|
mb_parameter_descriptor_t reg_info = { 0 };
|
|
uint8_t *data_ptr = NULL;
|
|
|
|
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, ®_info);
|
|
if ((error == ESP_OK) && (cid == reg_info.cid) && (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER)) {
|
|
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
|
data_ptr = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
|
|
if (!data_ptr) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
// Transfer value of characteristic into parameter buffer
|
|
error = mbc_master_set_param_data((void *)data_ptr, (void *)value,
|
|
reg_info.param_type, reg_info.param_size);
|
|
if (error != ESP_OK) {
|
|
ESP_LOGE(TAG, "fail to set parameter data.");
|
|
free(data_ptr);
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
// Send request to write characteristic data
|
|
error = mbc_serial_master_send_request(ctx, &request, data_ptr);
|
|
if (error == ESP_OK) {
|
|
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
|
|
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
} else {
|
|
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
|
|
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
}
|
|
free(data_ptr);
|
|
// Set the type of parameter found in the table
|
|
*type = reg_info.param_type;
|
|
} else {
|
|
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
|
|
__FUNCTION__, (unsigned)reg_info.cid);
|
|
error = ESP_ERR_INVALID_ARG;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
// Set parameter value for characteristic selected by name and cid
|
|
static esp_err_t mbc_serial_master_set_parameter_with(void *ctx, uint16_t cid, uint8_t uid,
|
|
uint8_t *value_ptr, uint8_t *type)
|
|
{
|
|
MB_RETURN_ON_FALSE((value_ptr), ESP_ERR_INVALID_ARG, TAG, "value pointer is incorrect.");
|
|
MB_RETURN_ON_FALSE((type), ESP_ERR_INVALID_ARG, TAG, "type pointer is incorrect.");
|
|
esp_err_t error = ESP_ERR_INVALID_RESPONSE;
|
|
mb_param_request_t request;
|
|
mb_parameter_descriptor_t reg_info = {0};
|
|
uint8_t *data_ptr = NULL;
|
|
error = mbc_serial_master_set_request(ctx, cid, MB_PARAM_WRITE, &request, ®_info);
|
|
if ((error == ESP_OK) && (cid == reg_info.cid)) {
|
|
if (request.slave_addr != MB_SLAVE_ADDR_PLACEHOLDER) {
|
|
ESP_LOGD(TAG, "%s: override uid %d = %d for cid(%u)",
|
|
__FUNCTION__, (int)request.slave_addr, (int)uid, (unsigned)reg_info.cid);
|
|
}
|
|
request.slave_addr = uid; // override the UID
|
|
MB_MASTER_ASSERT(xPortGetFreeHeapSize() > (reg_info.mb_size << 1));
|
|
data_ptr = calloc(1, (reg_info.mb_size << 1)); // alloc parameter buffer
|
|
if (!data_ptr) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
// Transfer value of characteristic into parameter buffer
|
|
error = mbc_master_set_param_data((void *)data_ptr, (void *)value_ptr,
|
|
reg_info.param_type, reg_info.param_size);
|
|
if (error != ESP_OK) {
|
|
ESP_LOGE(TAG, "fail to set parameter data.");
|
|
free(data_ptr);
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
// Send request to write characteristic data
|
|
error = mbc_serial_master_send_request(ctx, &request, value_ptr);
|
|
if (error == ESP_OK) {
|
|
ESP_LOGD(TAG, "%s: Good response for set cid(%u) = %s",
|
|
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
} else {
|
|
ESP_LOGD(TAG, "%s: Bad response to set cid(%u) = %s",
|
|
__FUNCTION__, (unsigned)reg_info.cid, (char *)esp_err_to_name(error));
|
|
}
|
|
free(data_ptr);
|
|
// Set the type of parameter found in the table
|
|
*type = reg_info.param_type;
|
|
} else {
|
|
ESP_LOGE(TAG, "%s: The requested cid(%u) not found in the data dictionary.",
|
|
__FUNCTION__, (unsigned)reg_info.cid);
|
|
error = ESP_ERR_INVALID_ARG;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static void mbc_serial_master_iface_free(void *ctx)
|
|
{
|
|
mbm_controller_iface_t *mbm_iface = (mbm_controller_iface_t *)(ctx);
|
|
if (mbm_iface) {
|
|
if (mbm_iface->opts.task_handle) {
|
|
vTaskDelete(mbm_iface->opts.task_handle);
|
|
mbm_iface->opts.task_handle = NULL;
|
|
}
|
|
if (mbm_iface->opts.event_group_handle) {
|
|
vEventGroupDelete(mbm_iface->opts.event_group_handle);
|
|
mbm_iface->opts.event_group_handle = NULL;
|
|
}
|
|
free(mbm_iface); // free the memory allocated for interface
|
|
}
|
|
}
|
|
|
|
static esp_err_t mbc_serial_master_controller_create(void **ctx)
|
|
{
|
|
MB_RETURN_ON_FALSE((ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack init interface fail.");
|
|
mbm_controller_iface_t *mbm_controller_iface = NULL;
|
|
|
|
esp_err_t ret = ESP_ERR_INVALID_STATE;
|
|
BaseType_t status = 0;
|
|
|
|
// Allocate space for controller
|
|
mbm_controller_iface = malloc(sizeof(mbm_controller_iface_t));
|
|
MB_GOTO_ON_FALSE((mbm_controller_iface), ESP_ERR_INVALID_STATE, error,
|
|
TAG, "mb stack memory allocation fail.");
|
|
|
|
// Initialize interface properties
|
|
mb_master_options_t *mbm_opts = &mbm_controller_iface->opts;
|
|
mbm_opts->task_handle = NULL;
|
|
|
|
// Initialization of active context of the modbus controller
|
|
mbm_opts->event_group_handle = xEventGroupCreate();
|
|
MB_GOTO_ON_FALSE((mbm_opts->event_group_handle), ESP_ERR_INVALID_STATE, error, TAG, "mb event group error.");
|
|
mbm_opts->mbm_sema = xSemaphoreCreateBinary();
|
|
MB_GOTO_ON_FALSE((mbm_opts->mbm_sema != NULL), ESP_ERR_NO_MEM, error, TAG, "%s: mbm resource create error.", __func__);
|
|
(void)xSemaphoreGive(mbm_opts->mbm_sema);
|
|
|
|
// Create modbus controller task
|
|
status = xTaskCreatePinnedToCore((void *)&mbc_ser_master_task,
|
|
"mbc_ser_master",
|
|
MB_CONTROLLER_STACK_SIZE,
|
|
mbm_controller_iface,
|
|
MB_CONTROLLER_PRIORITY,
|
|
&mbm_opts->task_handle,
|
|
MB_PORT_TASK_AFFINITY);
|
|
MB_GOTO_ON_FALSE((status == pdPASS), ESP_ERR_INVALID_STATE, error, TAG,
|
|
"mb controller task creation error");
|
|
MB_MASTER_ASSERT(mbm_opts->task_handle); // The task is created but handle is incorrect
|
|
|
|
// Initialize public interface methods of the interface
|
|
mbm_controller_iface->create = mbc_serial_master_create;
|
|
mbm_controller_iface->delete = mbc_serial_master_delete;
|
|
mbm_controller_iface->start = mbc_serial_master_start;
|
|
mbm_controller_iface->stop = mbc_serial_master_stop;
|
|
mbm_controller_iface->get_cid_info = mbc_serial_master_get_cid_info;
|
|
mbm_controller_iface->get_parameter = mbc_serial_master_get_parameter;
|
|
mbm_controller_iface->get_parameter_with = mbc_serial_master_get_parameter_with;
|
|
mbm_controller_iface->send_request = mbc_serial_master_send_request;
|
|
mbm_controller_iface->set_descriptor = mbc_serial_master_set_descriptor;
|
|
mbm_controller_iface->set_parameter = mbc_serial_master_set_parameter;
|
|
mbm_controller_iface->set_parameter_with = mbc_serial_master_set_parameter_with;
|
|
mbm_controller_iface->mb_base = NULL;
|
|
*ctx = mbm_controller_iface;
|
|
return ESP_OK;
|
|
|
|
error:
|
|
mbc_serial_master_iface_free((void *)mbm_controller_iface);
|
|
return ret;
|
|
}
|
|
|
|
// Initialization of resources for Modbus serial master controller
|
|
esp_err_t mbc_serial_master_create(mb_communication_info_t *config, void **ctx)
|
|
{
|
|
mbm_controller_iface_t *mbm_controller_iface = NULL;
|
|
MB_RETURN_ON_FALSE((ctx && config), ESP_ERR_INVALID_STATE, TAG,
|
|
"mb stack init interface fail.");
|
|
MB_RETURN_ON_FALSE((!*ctx), ESP_ERR_INVALID_STATE, TAG, "mb stack is not destroyed?");
|
|
|
|
mb_serial_opts_t *pcomm_info = &config->ser_opts;
|
|
|
|
// Check communication options
|
|
MB_RETURN_ON_FALSE(((pcomm_info->mode == MB_RTU) || (pcomm_info->mode == MB_ASCII)),
|
|
ESP_ERR_INVALID_ARG, TAG, "mb incorrect mode = (%u).", (unsigned)pcomm_info->mode);
|
|
MB_RETURN_ON_FALSE((pcomm_info->port <= UART_NUM_MAX), ESP_ERR_INVALID_ARG, TAG,
|
|
"mb wrong port to set = (%u).", (unsigned)pcomm_info->port);
|
|
MB_RETURN_ON_FALSE((pcomm_info->parity <= UART_PARITY_ODD), ESP_ERR_INVALID_ARG, TAG,
|
|
"mb wrong parity option = (%u).", (unsigned)pcomm_info->parity);
|
|
|
|
esp_err_t ret = mbc_serial_master_controller_create((void *)&mbm_controller_iface);
|
|
MB_GOTO_ON_FALSE((ret == ESP_OK), ESP_ERR_INVALID_STATE, error, TAG, "mbc create returns (0x%x).", (int)ret);
|
|
|
|
mb_master_options_t *mbm_opts = MB_MASTER_GET_OPTS(mbm_controller_iface);
|
|
mbm_opts->comm_opts = *config;
|
|
mbm_opts->port_type = MB_PORT_SERIAL_MASTER;
|
|
|
|
// Keep the response time setting
|
|
if (!pcomm_info->response_tout_ms) {
|
|
mbm_opts->comm_opts.ser_opts.response_tout_ms = CONFIG_FMB_MASTER_TIMEOUT_MS_RESPOND;
|
|
}
|
|
|
|
// Initialize Modbus stack using mbcontroller parameters
|
|
mb_err_enum_t err = MB_EILLSTATE;
|
|
void *inst = (void *)mbm_controller_iface;
|
|
|
|
if (pcomm_info->mode == MB_RTU) {
|
|
#if ( CONFIG_FMB_COMM_MODE_RTU_EN )
|
|
err = mbm_rtu_create(pcomm_info, &inst);
|
|
#else
|
|
ESP_LOGE(TAG, "RTU mode is not enabled in the configuration.");
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
goto error;
|
|
#endif // CONFIG_FMB_COMM_MODE_RTU_EN
|
|
} else if (pcomm_info->mode == MB_ASCII) {
|
|
#if ( CONFIG_FMB_COMM_MODE_ASCII_EN )
|
|
err = mbm_ascii_create(pcomm_info, &inst);
|
|
#else
|
|
ESP_LOGE(TAG, "ASCII mode is not enabled in the configuration.");
|
|
ret = ESP_ERR_NOT_SUPPORTED;
|
|
goto error;
|
|
#endif // CONFIG_FMB_COMM_MODE_ASCII_EN
|
|
}
|
|
MB_GOTO_ON_FALSE((err == MB_ENOERR), ESP_ERR_INVALID_STATE, error, TAG,
|
|
"mb object create returns (0x%x).", (int)err);
|
|
mbm_controller_iface->mb_base = (mb_base_t *)inst;
|
|
|
|
const mb_rw_callbacks_t rw_cbs = {
|
|
.reg_input_cb = mbc_reg_input_master_cb,
|
|
.reg_holding_cb = mbc_reg_holding_master_cb,
|
|
.reg_coils_cb = mbc_reg_coils_master_cb,
|
|
.reg_discrete_cb = mbc_reg_discrete_master_cb
|
|
};
|
|
|
|
mbm_controller_iface->mb_base->rw_cbs = rw_cbs;
|
|
mbm_controller_iface->is_active = false;
|
|
*ctx = mbm_controller_iface;
|
|
return ESP_OK;
|
|
|
|
error:
|
|
if (mbm_controller_iface) {
|
|
if (mbm_controller_iface->mb_base) {
|
|
mbm_controller_iface->mb_base->delete (mbm_controller_iface->mb_base);
|
|
mbm_controller_iface->mb_base = NULL;
|
|
}
|
|
mbc_serial_master_iface_free((void *)mbm_controller_iface);
|
|
*ctx = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#endif
|