| /* |
| * 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_qty == 0 || reg_qty > regs_limit) { |
| LOG_ERR("Wrong register quantity, %u (limit is %u)", |
| reg_qty, regs_limit); |
| 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)); |
| |
| 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; |
| } |
| |
| } 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 (num_bytes % sizeof(uint32_t)) { |
| mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC); |
| return true; |
| } |
| } |
| |
| /* 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); |
| } |
| |
| /* Increment current register address */ |
| reg_addr++; |
| reg_qty--; |
| } 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); |
| } |
| |
| /* Increment current register address */ |
| reg_addr += 2; |
| reg_qty -= 2; |
| } |
| |
| if (err != 0) { |
| LOG_INF("Holding register address not supported"); |
| mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR); |
| return true; |
| } |
| |
| } |
| |
| 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_qty == 0 || reg_qty > regs_limit) { |
| LOG_ERR("Wrong register quantity, %u (limit is %u)", |
| reg_qty, regs_limit); |
| 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)); |
| |
| 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; |
| } |
| |
| } 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 (num_bytes % sizeof(uint32_t)) { |
| mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC); |
| return true; |
| } |
| } |
| |
| /* 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); |
| } |
| |
| /* Increment current register number */ |
| reg_addr++; |
| reg_qty--; |
| } 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); |
| } |
| |
| /* Increment current register address */ |
| reg_addr += 2; |
| reg_qty -= 2; |
| } |
| |
| if (err != 0) { |
| LOG_INF("Input register address not supported"); |
| mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_ADDR); |
| return true; |
| } |
| } |
| |
| 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; |
| |
| 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_qty == 0 || reg_qty > regs_limit) { |
| LOG_ERR("Number of registers limit exceeded"); |
| mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_DATA_VAL); |
| return true; |
| } |
| |
| 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; |
| } |
| } 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 (num_bytes % sizeof(uint32_t)) { |
| mbs_exception_rsp(ctx, MODBUS_EXC_ILLEGAL_FC); |
| return true; |
| } |
| } |
| |
| /* 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) != sizeof(uint16_t)) { |
| 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;) { |
| 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); |
| reg_cntr++; |
| } else { |
| uint32_t reg_val = sys_get_be32(prx_data); |
| float fp; |
| |
| /* Write to floating point register */ |
| memcpy(&fp, ®_val, sizeof(uint32_t)); |
| prx_data += sizeof(uint32_t); |
| err = ctx->mbs_user_cb->holding_reg_wr_fp(addr, fp); |
| reg_cntr += 2; |
| } |
| |
| 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; |
| } |