1045 lines
25 KiB
C
1045 lines
25 KiB
C
/*
|
|
* Copyright (c) 2020 PHYTEC Messtechnik GmbH
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/*
|
|
* This file is based on mbs_core.c from uC/Modbus Stack.
|
|
*
|
|
* uC/Modbus
|
|
* The Embedded Modbus Stack
|
|
*
|
|
* Copyright 2003-2020 Silicon Laboratories Inc. www.silabs.com
|
|
*
|
|
* SPDX-License-Identifier: APACHE-2.0
|
|
*
|
|
* This software is subject to an open source license and is distributed by
|
|
* Silicon Laboratories Inc. pursuant to the terms of the Apache License,
|
|
* Version 2.0 available at www.apache.org/licenses/LICENSE-2.0.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <modbus_internal.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(modbus_s, CONFIG_MODBUS_LOG_LEVEL);
|
|
|
|
/*
|
|
* This functions are used to reset and update server's
|
|
* statistics and communications counters.
|
|
*/
|
|
#ifdef CONFIG_MODBUS_FC08_DIAGNOSTIC
|
|
void modbus_reset_stats(struct modbus_context *ctx)
|
|
{
|
|
/* Initialize all MODBUS event counters. */
|
|
ctx->mbs_msg_ctr = 0;
|
|
ctx->mbs_crc_err_ctr = 0;
|
|
ctx->mbs_except_ctr = 0;
|
|
ctx->mbs_server_msg_ctr = 0;
|
|
ctx->mbs_noresp_ctr = 0;
|
|
}
|
|
|
|
static void update_msg_ctr(struct modbus_context *ctx)
|
|
{
|
|
ctx->mbs_msg_ctr++;
|
|
}
|
|
|
|
static void update_crcerr_ctr(struct modbus_context *ctx)
|
|
{
|
|
ctx->mbs_crc_err_ctr++;
|
|
}
|
|
|
|
static void update_excep_ctr(struct modbus_context *ctx)
|
|
{
|
|
ctx->mbs_except_ctr++;
|
|
}
|
|
|
|
static void update_server_msg_ctr(struct modbus_context *ctx)
|
|
{
|
|
ctx->mbs_server_msg_ctr++;
|
|
}
|
|
|
|
static void update_noresp_ctr(struct modbus_context *ctx)
|
|
{
|
|
ctx->mbs_noresp_ctr++;
|
|
}
|
|
#else
|
|
#define modbus_reset_stats(...)
|
|
#define update_msg_ctr(...)
|
|
#define update_crcerr_ctr(...)
|
|
#define update_excep_ctr(...)
|
|
#define update_server_msg_ctr(...)
|
|
#define update_noresp_ctr(...)
|
|
#endif /* CONFIG_MODBUS_FC08_DIAGNOSTIC */
|
|
|
|
/*
|
|
* This function sets the indicated error response code into the response frame.
|
|
* Then the routine is called to calculate the error check value.
|
|
*/
|
|
static void mbs_exception_rsp(struct modbus_context *ctx, uint8_t excep_code)
|
|
{
|
|
const uint8_t excep_bit = BIT(7);
|
|
|
|
LOG_INF("FC 0x%02x Error 0x%02x", ctx->rx_adu.fc, excep_code);
|
|
|
|
update_excep_ctr(ctx);
|
|
|
|
ctx->tx_adu.fc |= excep_bit;
|
|
ctx->tx_adu.data[0] = excep_code;
|
|
ctx->tx_adu.length = 1;
|
|
}
|
|
|
|
/*
|
|
* FC 01 (0x01) Read Coils
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Coils 2 Bytes
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Byte count 1 Bytes
|
|
* Coil status N * 1 Byte
|
|
*/
|
|
static bool mbs_fc01_coil_read(struct modbus_context *ctx)
|
|
{
|
|
const uint16_t coils_limit = 2000;
|
|
const uint8_t request_len = 4;
|
|
uint8_t *presp;
|
|
bool coil_state;
|
|
int err;
|
|
uint16_t coil_addr;
|
|
uint16_t coil_qty;
|
|
uint16_t num_bytes;
|
|
uint8_t bit_mask;
|
|
uint16_t coil_cntr;
|
|
|
|
if (ctx->rx_adu.length != request_len) {
|
|
LOG_ERR("Wrong request length");
|
|
return false;
|
|
}
|
|
|
|
if (ctx->mbs_user_cb->coil_rd == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
coil_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
coil_qty = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
|
|
/* Make sure we don't exceed the allowed limit per request */
|
|
if (coil_qty == 0 || coil_qty > coils_limit) {
|
|
LOG_ERR("Number of coils limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* Calculate byte count for response. */
|
|
num_bytes = ((coil_qty - 1) / 8) + 1;
|
|
/* Number of data bytes + byte count. */
|
|
ctx->tx_adu.length = num_bytes + 1;
|
|
/* Set number of data bytes in response message. */
|
|
ctx->tx_adu.data[0] = (uint8_t)num_bytes;
|
|
|
|
/* Clear bytes in response */
|
|
presp = &ctx->tx_adu.data[1];
|
|
memset(presp, 0, num_bytes);
|
|
|
|
/* Reset the pointer to the start of the response payload */
|
|
presp = &ctx->tx_adu.data[1];
|
|
/* Start with bit 0 in response byte data mask. */
|
|
bit_mask = BIT(0);
|
|
/* Initialize loop counter. */
|
|
coil_cntr = 0;
|
|
|
|
/* Loop through each coil requested. */
|
|
while (coil_cntr < coil_qty) {
|
|
|
|
err = ctx->mbs_user_cb->coil_rd(coil_addr, &coil_state);
|
|
if (err != 0) {
|
|
LOG_INF("Coil address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
|
|
if (coil_state) {
|
|
*presp |= bit_mask;
|
|
}
|
|
|
|
coil_addr++;
|
|
/* Increment coil counter. */
|
|
coil_cntr++;
|
|
/* Determine if 8 data bits have been filled. */
|
|
if ((coil_cntr % 8) == 0) {
|
|
/* Reset the data mask. */
|
|
bit_mask = BIT(0);
|
|
/* Increment frame data index. */
|
|
presp++;
|
|
} else {
|
|
/*
|
|
* Still in same data byte, so shift the data mask
|
|
* to the next higher bit position.
|
|
*/
|
|
bit_mask <<= 1;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* FC 02 (0x02) Read Discrete Inputs
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Inputs 2 Bytes
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Byte count 1 Bytes
|
|
* Input status N * 1 Byte
|
|
*/
|
|
static bool mbs_fc02_di_read(struct modbus_context *ctx)
|
|
{
|
|
const uint16_t di_limit = 2000;
|
|
const uint8_t request_len = 4;
|
|
uint8_t *presp;
|
|
bool di_state;
|
|
int err;
|
|
uint16_t di_addr;
|
|
uint16_t di_qty;
|
|
uint16_t num_bytes;
|
|
uint8_t bit_mask;
|
|
uint16_t di_cntr;
|
|
|
|
if (ctx->rx_adu.length != request_len) {
|
|
LOG_ERR("Wrong request length");
|
|
return false;
|
|
}
|
|
|
|
if (ctx->mbs_user_cb->discrete_input_rd == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
di_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
di_qty = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
|
|
/* Make sure we don't exceed the allowed limit per request */
|
|
if (di_qty == 0 || di_qty > di_limit) {
|
|
LOG_ERR("Number of inputs limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* Get number of bytes needed for response. */
|
|
num_bytes = ((di_qty - 1) / 8) + 1;
|
|
/* Number of data bytes + byte count. */
|
|
ctx->tx_adu.length = num_bytes + 1;
|
|
/* Set number of data bytes in response message. */
|
|
ctx->tx_adu.data[0] = (uint8_t)num_bytes;
|
|
|
|
/* Clear bytes in response */
|
|
presp = &ctx->tx_adu.data[1];
|
|
for (di_cntr = 0; di_cntr < num_bytes; di_cntr++) {
|
|
*presp++ = 0x00;
|
|
}
|
|
|
|
/* Reset the pointer to the start of the response payload */
|
|
presp = &ctx->tx_adu.data[1];
|
|
/* Start with bit 0 in response byte data mask. */
|
|
bit_mask = BIT(0);
|
|
/* Initialize loop counter. */
|
|
di_cntr = 0;
|
|
|
|
/* Loop through each DI requested. */
|
|
while (di_cntr < di_qty) {
|
|
|
|
err = ctx->mbs_user_cb->discrete_input_rd(di_addr, &di_state);
|
|
if (err != 0) {
|
|
LOG_INF("Discrete input address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
|
|
if (di_state) {
|
|
*presp |= bit_mask;
|
|
}
|
|
|
|
di_addr++;
|
|
/* Increment DI counter. */
|
|
di_cntr++;
|
|
/* Determine if 8 data bits have been filled. */
|
|
if ((di_cntr % 8) == 0) {
|
|
/* Reset the data mask. */
|
|
bit_mask = BIT(0);
|
|
/* Increment data frame index. */
|
|
presp++;
|
|
} else {
|
|
/*
|
|
* Still in same data byte, so shift the data mask
|
|
* to the next higher bit position.
|
|
*/
|
|
bit_mask <<= 1;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 03 (0x03) Read Holding Registers
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Registers 2 Bytes
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Byte count 1 Bytes
|
|
* Register Value N * 2 Byte
|
|
*/
|
|
static bool mbs_fc03_hreg_read(struct modbus_context *ctx)
|
|
{
|
|
const uint16_t regs_limit = 125;
|
|
const uint8_t request_len = 4;
|
|
uint8_t *presp;
|
|
uint16_t err;
|
|
uint16_t reg_addr;
|
|
uint16_t reg_qty;
|
|
uint16_t num_bytes;
|
|
|
|
if (ctx->rx_adu.length != request_len) {
|
|
LOG_ERR("Wrong request length");
|
|
return false;
|
|
}
|
|
|
|
reg_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
reg_qty = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
|
|
if ((reg_addr < MODBUS_FP_EXTENSIONS_ADDR) ||
|
|
!IS_ENABLED(CONFIG_MODBUS_FP_EXTENSIONS)) {
|
|
/* Read integer register */
|
|
if (ctx->mbs_user_cb->holding_reg_rd == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
if (reg_qty == 0 || reg_qty > regs_limit) {
|
|
LOG_ERR("Number of registers limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* Get number of bytes needed for response. */
|
|
num_bytes = (uint8_t)(reg_qty * sizeof(uint16_t));
|
|
} else {
|
|
/* Read floating-point register */
|
|
if (ctx->mbs_user_cb->holding_reg_rd_fp == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
if (reg_qty == 0 || reg_qty > (regs_limit / 2)) {
|
|
LOG_ERR("Number of registers limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* Get number of bytes needed for response. */
|
|
num_bytes = (uint8_t)(reg_qty * sizeof(float));
|
|
}
|
|
|
|
/* Number of data bytes + byte count. */
|
|
ctx->tx_adu.length = num_bytes + 1;
|
|
/* Set number of data bytes in response message. */
|
|
ctx->tx_adu.data[0] = (uint8_t)num_bytes;
|
|
|
|
/* Reset the pointer to the start of the response payload */
|
|
presp = &ctx->tx_adu.data[1];
|
|
/* Loop through each register requested. */
|
|
while (reg_qty > 0) {
|
|
if (reg_addr < MODBUS_FP_EXTENSIONS_ADDR) {
|
|
uint16_t reg;
|
|
|
|
/* Read integer register */
|
|
err = ctx->mbs_user_cb->holding_reg_rd(reg_addr, ®);
|
|
if (err == 0) {
|
|
sys_put_be16(reg, presp);
|
|
presp += sizeof(uint16_t);
|
|
}
|
|
|
|
} else if (IS_ENABLED(CONFIG_MODBUS_FP_EXTENSIONS)) {
|
|
float fp;
|
|
uint32_t reg;
|
|
|
|
/* Read floating-point register */
|
|
err = ctx->mbs_user_cb->holding_reg_rd_fp(reg_addr, &fp);
|
|
if (err == 0) {
|
|
memcpy(®, &fp, sizeof(reg));
|
|
sys_put_be32(reg, presp);
|
|
presp += sizeof(uint32_t);
|
|
}
|
|
}
|
|
|
|
if (err != 0) {
|
|
LOG_INF("Holding register address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
|
|
/* Increment current register address */
|
|
reg_addr++;
|
|
reg_qty--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 04 (0x04) Read Input Registers
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Registers 2 Bytes
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Byte count 1 Bytes
|
|
* Register Value N * 2 Byte
|
|
*/
|
|
static bool mbs_fc04_inreg_read(struct modbus_context *ctx)
|
|
{
|
|
const uint16_t regs_limit = 125;
|
|
const uint8_t request_len = 4;
|
|
uint8_t *presp;
|
|
int err;
|
|
uint16_t reg_addr;
|
|
uint16_t reg_qty;
|
|
uint16_t num_bytes;
|
|
|
|
if (ctx->rx_adu.length != request_len) {
|
|
LOG_ERR("Wrong request length");
|
|
return false;
|
|
}
|
|
|
|
reg_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
reg_qty = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
|
|
if ((reg_addr < MODBUS_FP_EXTENSIONS_ADDR) ||
|
|
!IS_ENABLED(CONFIG_MODBUS_FP_EXTENSIONS)) {
|
|
/* Read integer register */
|
|
if (ctx->mbs_user_cb->input_reg_rd == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
if (reg_qty == 0 || reg_qty > regs_limit) {
|
|
LOG_ERR("Number of registers limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* Get number of bytes needed for response. */
|
|
num_bytes = (uint8_t)(reg_qty * sizeof(uint16_t));
|
|
} else {
|
|
/* Read floating-point register */
|
|
if (ctx->mbs_user_cb->input_reg_rd_fp == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
if (reg_qty == 0 || reg_qty > (regs_limit / 2)) {
|
|
LOG_ERR("Number of registers limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* Get number of bytes needed for response. */
|
|
num_bytes = (uint8_t)(reg_qty * sizeof(float));
|
|
}
|
|
|
|
/* Number of data bytes + byte count. */
|
|
ctx->tx_adu.length = num_bytes + 1;
|
|
/* Set number of data bytes in response message. */
|
|
ctx->tx_adu.data[0] = (uint8_t)num_bytes;
|
|
|
|
/* Reset the pointer to the start of the response payload */
|
|
presp = &ctx->tx_adu.data[1];
|
|
/* Loop through each register requested. */
|
|
while (reg_qty > 0) {
|
|
if (reg_addr < MODBUS_FP_EXTENSIONS_ADDR) {
|
|
uint16_t reg;
|
|
|
|
/* Read integer register */
|
|
err = ctx->mbs_user_cb->input_reg_rd(reg_addr, ®);
|
|
if (err == 0) {
|
|
sys_put_be16(reg, presp);
|
|
presp += sizeof(uint16_t);
|
|
}
|
|
|
|
} else if (IS_ENABLED(CONFIG_MODBUS_FP_EXTENSIONS)) {
|
|
float fp;
|
|
uint32_t reg;
|
|
|
|
/* Read floating-point register */
|
|
err = ctx->mbs_user_cb->input_reg_rd_fp(reg_addr, &fp);
|
|
if (err == 0) {
|
|
memcpy(®, &fp, sizeof(reg));
|
|
sys_put_be32(reg, presp);
|
|
presp += sizeof(uint32_t);
|
|
}
|
|
}
|
|
|
|
if (err != 0) {
|
|
LOG_INF("Input register address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
|
|
/* Increment current register number */
|
|
reg_addr++;
|
|
reg_qty--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* FC 05 (0x05) Write Single Coil
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Output Address 2 Bytes
|
|
* Output Value 2 Bytes
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Output Address 2 Bytes
|
|
* Output Value 2 Bytes
|
|
*/
|
|
static bool mbs_fc05_coil_write(struct modbus_context *ctx)
|
|
{
|
|
const uint8_t request_len = 4;
|
|
const uint8_t response_len = 4;
|
|
int err;
|
|
uint16_t coil_addr;
|
|
uint16_t coil_val;
|
|
bool coil_state;
|
|
|
|
if (ctx->rx_adu.length != request_len) {
|
|
LOG_ERR("Wrong request length %u", ctx->rx_adu.length);
|
|
return false;
|
|
}
|
|
|
|
if (ctx->mbs_user_cb->coil_wr == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
/* Get the desired coil address and coil value */
|
|
coil_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
coil_val = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
|
|
/* See if coil needs to be OFF? */
|
|
if (coil_val == MODBUS_COIL_OFF_CODE) {
|
|
coil_state = false;
|
|
} else {
|
|
coil_state = true;
|
|
}
|
|
|
|
err = ctx->mbs_user_cb->coil_wr(coil_addr, coil_state);
|
|
|
|
if (err != 0) {
|
|
LOG_INF("Coil address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
|
|
/* Assemble response payload */
|
|
ctx->tx_adu.length = response_len;
|
|
sys_put_be16(coil_addr, &ctx->tx_adu.data[0]);
|
|
sys_put_be16(coil_val, &ctx->tx_adu.data[2]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 06 (0x06) Write Single Register
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Register Address 2 Bytes
|
|
* Register Value 2 Bytes
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Register Address 2 Bytes
|
|
* Register Value 2 Bytes
|
|
*/
|
|
static bool mbs_fc06_hreg_write(struct modbus_context *ctx)
|
|
{
|
|
const uint8_t request_len = 4;
|
|
const uint8_t response_len = 4;
|
|
int err;
|
|
uint16_t reg_addr;
|
|
uint16_t reg_val;
|
|
|
|
if (ctx->rx_adu.length != request_len) {
|
|
LOG_ERR("Wrong request length %u", ctx->rx_adu.length);
|
|
return false;
|
|
}
|
|
|
|
if (ctx->mbs_user_cb->holding_reg_wr == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
reg_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
reg_val = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
|
|
err = ctx->mbs_user_cb->holding_reg_wr(reg_addr, reg_val);
|
|
|
|
if (err != 0) {
|
|
LOG_INF("Register address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
|
|
/* Assemble response payload */
|
|
ctx->tx_adu.length = response_len;
|
|
sys_put_be16(reg_addr, &ctx->tx_adu.data[0]);
|
|
sys_put_be16(reg_val, &ctx->tx_adu.data[2]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 08 (0x08) Diagnostics
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Sub-function code 2 Bytes
|
|
* Data N * 2 Byte
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Sub-function code 2 Bytes
|
|
* Data N * 2 Byte
|
|
*/
|
|
#ifdef CONFIG_MODBUS_FC08_DIAGNOSTIC
|
|
static bool mbs_fc08_diagnostics(struct modbus_context *ctx)
|
|
{
|
|
const uint8_t request_len = 4;
|
|
const uint8_t response_len = 4;
|
|
uint16_t sfunc;
|
|
uint16_t data;
|
|
|
|
if (ctx->rx_adu.length != request_len) {
|
|
LOG_ERR("Wrong request length %u", ctx->rx_adu.length);
|
|
return false;
|
|
}
|
|
|
|
sfunc = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
data = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
|
|
switch (sfunc) {
|
|
case MODBUS_FC08_SUBF_QUERY:
|
|
/* Sub-function 0x00 return Query Data */
|
|
break;
|
|
|
|
case MODBUS_FC08_SUBF_CLR_CTR:
|
|
/* Sub-function 0x0A clear Counters and Diagnostic */
|
|
modbus_reset_stats(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC08_SUBF_BUS_MSG_CTR:
|
|
/* Sub-function 0x0B return Bus Message Count */
|
|
data = ctx->mbs_msg_ctr;
|
|
break;
|
|
|
|
case MODBUS_FC08_SUBF_BUS_CRC_CTR:
|
|
/* Sub-function 0x0C return Bus Communication Error Count */
|
|
data = ctx->mbs_crc_err_ctr;
|
|
break;
|
|
|
|
case MODBUS_FC08_SUBF_BUS_EXCEPT_CTR:
|
|
/* Sub-function 0x0D return Bus Exception Error Count */
|
|
data = ctx->mbs_except_ctr;
|
|
break;
|
|
|
|
case MODBUS_FC08_SUBF_SERVER_MSG_CTR:
|
|
/* Sub-function 0x0E return Server Message Count */
|
|
data = ctx->mbs_server_msg_ctr;
|
|
break;
|
|
|
|
case MODBUS_FC08_SUBF_SERVER_NO_RESP_CTR:
|
|
/* Sub-function 0x0F return Server No Response Count */
|
|
data = ctx->mbs_noresp_ctr;
|
|
break;
|
|
|
|
default:
|
|
LOG_INF("Sub-function not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
/* Assemble response payload */
|
|
ctx->tx_adu.length = response_len;
|
|
sys_put_be16(sfunc, &ctx->tx_adu.data[0]);
|
|
sys_put_be16(data, &ctx->tx_adu.data[2]);
|
|
|
|
return true;
|
|
}
|
|
#else
|
|
static bool mbs_fc08_diagnostics(struct modbus_context *ctx)
|
|
{
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* FC 15 (0x0F) Write Multiple Coils
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Outputs 2 Bytes
|
|
* Byte Count 1 Byte
|
|
* Outputs Value N * 1 Byte
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Outputs 2 Bytes
|
|
*/
|
|
static bool mbs_fc15_coils_write(struct modbus_context *ctx)
|
|
{
|
|
const uint16_t coils_limit = 2000;
|
|
const uint8_t request_len = 6;
|
|
const uint8_t response_len = 4;
|
|
uint8_t temp = 0;
|
|
int err;
|
|
uint16_t coil_addr;
|
|
uint16_t coil_qty;
|
|
uint16_t num_bytes;
|
|
uint16_t coil_cntr;
|
|
uint8_t data_ix;
|
|
bool coil_state;
|
|
|
|
if (ctx->rx_adu.length < request_len) {
|
|
LOG_ERR("Wrong request length %u", ctx->rx_adu.length);
|
|
return false;
|
|
}
|
|
|
|
if (ctx->mbs_user_cb->coil_wr == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
coil_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
coil_qty = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
/* Get the byte count for the data. */
|
|
num_bytes = ctx->rx_adu.data[4];
|
|
|
|
if (coil_qty == 0 || coil_qty > coils_limit) {
|
|
LOG_ERR("Number of coils limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* Be sure byte count is valid for quantity of coils. */
|
|
if (((((coil_qty - 1) / 8) + 1) != num_bytes) ||
|
|
(ctx->rx_adu.length != (num_bytes + 5))) {
|
|
LOG_ERR("Mismatch in the number of coils");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
coil_cntr = 0;
|
|
/* The 1st coil data byte is 6th element in payload */
|
|
data_ix = 5;
|
|
/* Loop through each coil to be forced. */
|
|
while (coil_cntr < coil_qty) {
|
|
/* Move to the next data byte after every eight bits. */
|
|
if ((coil_cntr % 8) == 0) {
|
|
temp = ctx->rx_adu.data[data_ix++];
|
|
}
|
|
|
|
if (temp & BIT(0)) {
|
|
coil_state = true;
|
|
} else {
|
|
coil_state = false;
|
|
}
|
|
|
|
err = ctx->mbs_user_cb->coil_wr(coil_addr + coil_cntr,
|
|
coil_state);
|
|
|
|
if (err != 0) {
|
|
LOG_INF("Coil address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
|
|
/* Shift the data one bit position * to the right. */
|
|
temp >>= 1;
|
|
/* Increment the COIL counter. */
|
|
coil_cntr++;
|
|
}
|
|
|
|
/* Assemble response payload */
|
|
ctx->tx_adu.length = response_len;
|
|
sys_put_be16(coil_addr, &ctx->tx_adu.data[0]);
|
|
sys_put_be16(coil_qty, &ctx->tx_adu.data[2]);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* FC16 (0x10) Write Multiple registers
|
|
*
|
|
* Request Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Registers 2 Bytes
|
|
* Byte Count 1 Byte
|
|
* Registers Value N * 1 Byte
|
|
*
|
|
* Response Payload:
|
|
* Function code 1 Byte
|
|
* Starting Address 2 Bytes
|
|
* Quantity of Registers 2 Bytes
|
|
*
|
|
* If the address of the request exceeds or is equal to MODBUS_FP_EXTENSIONS_ADDR,
|
|
* then the function would write to multiple 'floating-point' according to
|
|
* the 'Daniels Flow Meter' extensions. This means that each register
|
|
* requested is considered as a 32-bit IEEE-754 floating-point format.
|
|
*/
|
|
static bool mbs_fc16_hregs_write(struct modbus_context *ctx)
|
|
{
|
|
const uint16_t regs_limit = 125;
|
|
const uint8_t request_len = 6;
|
|
const uint8_t response_len = 4;
|
|
uint8_t *prx_data;
|
|
int err;
|
|
uint16_t reg_addr;
|
|
uint16_t reg_qty;
|
|
uint16_t num_bytes;
|
|
uint8_t reg_size;
|
|
|
|
if (ctx->rx_adu.length < request_len) {
|
|
LOG_ERR("Wrong request length %u", ctx->rx_adu.length);
|
|
return false;
|
|
}
|
|
|
|
reg_addr = sys_get_be16(&ctx->rx_adu.data[0]);
|
|
reg_qty = sys_get_be16(&ctx->rx_adu.data[2]);
|
|
/* Get the byte count for the data. */
|
|
num_bytes = ctx->rx_adu.data[4];
|
|
|
|
if ((reg_addr < MODBUS_FP_EXTENSIONS_ADDR) ||
|
|
!IS_ENABLED(CONFIG_MODBUS_FP_EXTENSIONS)) {
|
|
/* Write integer register */
|
|
if (ctx->mbs_user_cb->holding_reg_wr == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
if (reg_qty == 0 || reg_qty > regs_limit) {
|
|
LOG_ERR("Number of registers limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
reg_size = sizeof(uint16_t);
|
|
} else {
|
|
/* Write floating-point register */
|
|
if (ctx->mbs_user_cb->holding_reg_wr_fp == NULL) {
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
return true;
|
|
}
|
|
|
|
if (reg_qty == 0 || reg_qty > (regs_limit / 2)) {
|
|
LOG_ERR("Number of registers limit exceeded");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
reg_size = sizeof(float);
|
|
}
|
|
|
|
/* Compare number of bytes and payload length */
|
|
if ((ctx->rx_adu.length - 5) != num_bytes) {
|
|
LOG_ERR("Mismatch in the number of bytes");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
if ((num_bytes / reg_qty) != (uint16_t)reg_size) {
|
|
LOG_ERR("Mismatch in the number of registers");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL);
|
|
return true;
|
|
}
|
|
|
|
/* The 1st registers data byte is 6th element in payload */
|
|
prx_data = &ctx->rx_adu.data[5];
|
|
|
|
for (uint16_t reg_cntr = 0; reg_cntr < reg_qty; reg_cntr++) {
|
|
uint16_t addr = reg_addr + reg_cntr;
|
|
|
|
if ((reg_addr < MODBUS_FP_EXTENSIONS_ADDR) ||
|
|
!IS_ENABLED(CONFIG_MODBUS_FP_EXTENSIONS)) {
|
|
uint16_t reg_val = sys_get_be16(prx_data);
|
|
|
|
prx_data += sizeof(uint16_t);
|
|
err = ctx->mbs_user_cb->holding_reg_wr(addr, reg_val);
|
|
} else {
|
|
uint32_t reg_val = sys_get_be32(prx_data);
|
|
float fp;
|
|
|
|
/* Write to floating point register */
|
|
memcpy(&fp, ®_val, sizeof(float));
|
|
prx_data += sizeof(uint32_t);
|
|
err = ctx->mbs_user_cb->holding_reg_wr_fp(addr, fp);
|
|
}
|
|
|
|
if (err != 0) {
|
|
LOG_INF("Register address not supported");
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Assemble response payload */
|
|
ctx->tx_adu.length = response_len;
|
|
sys_put_be16(reg_addr, &ctx->tx_adu.data[0]);
|
|
sys_put_be16(reg_qty, &ctx->tx_adu.data[2]);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool mbs_try_user_fc(struct modbus_context *ctx, uint8_t fc)
|
|
{
|
|
struct modbus_custom_fc *p;
|
|
|
|
LOG_DBG("Searching for custom Modbus handlers for code %u", fc);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&ctx->user_defined_cbs, p, node) {
|
|
if (p->fc == fc) {
|
|
int iface = modbus_iface_get_by_ctx(ctx);
|
|
bool rval;
|
|
|
|
LOG_DBG("Found custom handler");
|
|
|
|
p->excep_code = MODBUS_EXC_NONE;
|
|
rval = p->cb(iface, &ctx->rx_adu, &ctx->tx_adu, &p->excep_code,
|
|
p->user_data);
|
|
|
|
if (p->excep_code != MODBUS_EXC_NONE) {
|
|
LOG_INF("Custom handler failed with code %d", p->excep_code);
|
|
mbs_exception_rsp(ctx, p->excep_code);
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
}
|
|
|
|
LOG_ERR("Function code 0x%02x not implemented", fc);
|
|
mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool modbus_server_handler(struct modbus_context *ctx)
|
|
{
|
|
bool send_reply = false;
|
|
uint8_t addr = ctx->rx_adu.unit_id;
|
|
uint8_t fc = ctx->rx_adu.fc;
|
|
|
|
LOG_DBG("Server RX handler %p", ctx);
|
|
update_msg_ctr(ctx);
|
|
|
|
if (ctx->rx_adu_err != 0) {
|
|
update_noresp_ctr(ctx);
|
|
if (ctx->rx_adu_err == -EIO) {
|
|
update_crcerr_ctr(ctx);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (addr != 0 && addr != ctx->unit_id) {
|
|
LOG_DBG("Unit ID doesn't match %u != %u", addr, ctx->unit_id);
|
|
update_noresp_ctr(ctx);
|
|
return false;
|
|
}
|
|
|
|
/* Prepare response header */
|
|
ctx->tx_adu.trans_id = ctx->rx_adu.trans_id;
|
|
ctx->tx_adu.proto_id = ctx->rx_adu.proto_id;
|
|
ctx->tx_adu.unit_id = addr;
|
|
ctx->tx_adu.fc = fc;
|
|
|
|
update_server_msg_ctr(ctx);
|
|
|
|
switch (fc) {
|
|
case MODBUS_FC01_COIL_RD:
|
|
send_reply = mbs_fc01_coil_read(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC02_DI_RD:
|
|
send_reply = mbs_fc02_di_read(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC03_HOLDING_REG_RD:
|
|
send_reply = mbs_fc03_hreg_read(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC04_IN_REG_RD:
|
|
send_reply = mbs_fc04_inreg_read(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC05_COIL_WR:
|
|
send_reply = mbs_fc05_coil_write(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC06_HOLDING_REG_WR:
|
|
send_reply = mbs_fc06_hreg_write(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC08_DIAGNOSTICS:
|
|
send_reply = mbs_fc08_diagnostics(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC15_COILS_WR:
|
|
send_reply = mbs_fc15_coils_write(ctx);
|
|
break;
|
|
|
|
case MODBUS_FC16_HOLDING_REGS_WR:
|
|
send_reply = mbs_fc16_hregs_write(ctx);
|
|
break;
|
|
|
|
default:
|
|
send_reply = mbs_try_user_fc(ctx, fc);
|
|
}
|
|
|
|
if (addr == 0) {
|
|
/* Broadcast address, do not reply */
|
|
send_reply = false;
|
|
}
|
|
|
|
if (send_reply == false) {
|
|
update_noresp_ctr(ctx);
|
|
}
|
|
|
|
return send_reply;
|
|
}
|