/** * @file agile_modbus.c * @brief Agile Modbus 软件包通用源文件 * @author 马龙伟 (2544047213@qq.com) * @date 2022-07-28 * @verbatim 使用: 用户需要实现硬件接口的 `发送数据` 、 `等待数据接收结束` 、 `清空接收缓存` 函数 - 主机: 1. `agile_modbus_rtu_init` / `agile_modbus_tcp_init` 初始化 `RTU/TCP` 环境 2. `agile_modbus_set_slave` 设置从机地址 3. `清空接收缓存` 4. `agile_modbus_serialize_xxx` 打包请求数据 5. `发送数据` 6. `等待数据接收结束` 7. `agile_modbus_deserialize_xxx` 解析响应数据 8. 用户处理得到的数据 - 从机: 1. 实现 `agile_modbus_slave_callback_t` 类型回调函数 2. `agile_modbus_rtu_init` / `agile_modbus_tcp_init` 初始化 `RTU/TCP` 环境 3. `agile_modbus_set_slave` 设置从机地址 4. `等待数据接收结束` 5. `agile_modbus_slave_handle` 处理请求数据 6. `清空接收缓存` (可选) 7. `发送数据` @endverbatim * * @attention * *

© Copyright (c) 2021 Ma Longwei. * All rights reserved.

* */ #include "agile_modbus.h" #include /** @defgroup COMMON Common * @{ */ /** @defgroup COMMON_Private_Constants Common Private Constants * @{ */ #define AGILE_MODBUS_MSG_LENGTH_UNDEFINED -1 /**< 对应功能码数据长度未定义 */ /** * @} */ /** @defgroup COMMON_Private_Functions Common Private Functions * @{ */ /** * @brief 计算功能码后要接收的数据元长度 @verbatim ---------- Request Indication ---------- | Client | ---------------------->| Server | ---------- Confirmation Response ---------- 以 03 功能码请求报文举例 ---------- ------ --------------- --------- | header | | 03 | | 00 00 00 01 | | CRC16 | ---------- ------ --------------- --------- ---------- | header | ---------- RTU: 设备地址 TCP: | 事务处理标识 协议标识 长度 单元标识符 | --------------- | 00 00 00 01 | --------------- 数据元: 与功能码相关的数据,如 03 功能码数据元中包含寄存器起始地址和寄存器长度 @endverbatim * @param ctx modbus 句柄 * @param function 功能码 * @param msg_type 消息类型 * @return 数据元长度 */ static uint8_t agile_modbus_compute_meta_length_after_function(agile_modbus_t *ctx, int function, agile_modbus_msg_type_t msg_type) { int length; if (msg_type == AGILE_MODBUS_MSG_INDICATION) { if (function <= AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER) { length = 4; } else if (function == AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS || function == AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS) { length = 5; } else if (function == AGILE_MODBUS_FC_MASK_WRITE_REGISTER) { length = 6; } else if (function == AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS) { length = 9; } else { /* MODBUS_FC_READ_EXCEPTION_STATUS, MODBUS_FC_REPORT_SLAVE_ID */ length = 0; if (ctx->compute_meta_length_after_function) length = ctx->compute_meta_length_after_function(ctx, function, msg_type); } } else { /* MSG_CONFIRMATION */ switch (function) { case AGILE_MODBUS_FC_READ_COILS: case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS: case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS: case AGILE_MODBUS_FC_READ_INPUT_REGISTERS: case AGILE_MODBUS_FC_REPORT_SLAVE_ID: case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS: length = 1; break; case AGILE_MODBUS_FC_WRITE_SINGLE_COIL: case AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER: case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS: case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS: length = 4; break; case AGILE_MODBUS_FC_MASK_WRITE_REGISTER: length = 6; break; default: length = 1; if (ctx->compute_meta_length_after_function) length = ctx->compute_meta_length_after_function(ctx, function, msg_type); } } return length; } /** * @brief 计算数据元之后要接收的数据长度 @verbatim ---------- Request Indication ---------- | Client | ---------------------->| Server | ---------- Confirmation Response ---------- 以 03 功能码响应报文举例 ---------- ------ ------ --------- --------- | header | | 03 | | 02 | | 00 00 | | CRC16 | ---------- ------ ------ --------- --------- ---------- | header | ---------- RTU: 设备地址 TCP: | 事务处理标识 协议标识 长度 单元标识符 | ------ | 02 | ------ 数据元: 两个字节数据 --------- | 00 00 | --------- 数据 @endverbatim * @param ctx modbus 句柄 * @param msg 消息指针 * @param msg_length 消息长度 * @param msg_type 消息类型 * @return 数据长度 */ static int agile_modbus_compute_data_length_after_meta(agile_modbus_t *ctx, uint8_t *msg, int msg_length, agile_modbus_msg_type_t msg_type) { int function = msg[ctx->backend->header_length]; int length; if (msg_type == AGILE_MODBUS_MSG_INDICATION) { switch (function) { case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS: case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS: length = msg[ctx->backend->header_length + 5]; break; case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS: length = msg[ctx->backend->header_length + 9]; break; default: length = 0; if (ctx->compute_data_length_after_meta) length = ctx->compute_data_length_after_meta(ctx, msg, msg_length, msg_type); } } else { /* MSG_CONFIRMATION */ if (function <= AGILE_MODBUS_FC_READ_INPUT_REGISTERS || function == AGILE_MODBUS_FC_REPORT_SLAVE_ID || function == AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS) { length = msg[ctx->backend->header_length + 1]; } else { length = 0; if (ctx->compute_data_length_after_meta) length = ctx->compute_data_length_after_meta(ctx, msg, msg_length, msg_type); } } length += ctx->backend->checksum_length; return length; } /** * @brief 检验接收数据正确性 * @param ctx modbus 句柄 * @param msg 消息指针 * @param msg_length 消息长度 * @param msg_type 消息类型 * @return >0:正确,modbus 数据帧长度; 其他:异常 */ static int agile_modbus_receive_msg_judge(agile_modbus_t *ctx, uint8_t *msg, int msg_length, agile_modbus_msg_type_t msg_type) { int remain_len = msg_length; remain_len -= (ctx->backend->header_length + 1); if (remain_len < 0) return -1; remain_len -= agile_modbus_compute_meta_length_after_function(ctx, msg[ctx->backend->header_length], msg_type); if (remain_len < 0) return -1; remain_len -= agile_modbus_compute_data_length_after_meta(ctx, msg, msg_length, msg_type); if (remain_len < 0) return -1; return ctx->backend->check_integrity(ctx, msg, msg_length - remain_len); } /** * @} */ /** @defgroup COMMON_Exported_Functions Common Exported Functions * @{ */ /** * @brief 初始化 modbus 句柄 * @param ctx modbus 句柄 * @param send_buf 发送缓冲区 * @param send_bufsz 发送缓冲区大小 * @param read_buf 接收缓冲区 * @param read_bufsz 接收缓冲区大小 */ void agile_modbus_common_init(agile_modbus_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz) { memset(ctx, 0, sizeof(agile_modbus_t)); ctx->slave = -1; ctx->send_buf = send_buf; ctx->send_bufsz = send_bufsz; ctx->read_buf = read_buf; ctx->read_bufsz = read_bufsz; } /** * @brief 设置地址 * @param ctx modbus 句柄 * @param slave 地址 * @return 0:成功 */ int agile_modbus_set_slave(agile_modbus_t *ctx, int slave) { return ctx->backend->set_slave(ctx, slave); } /** * @brief 设置 modbus 对象的计算功能码后要接收的数据元长度回调函数 * @param ctx modbus 句柄 * @param cb 计算功能码后要接收的数据元长度回调函数 * @see agile_modbus_compute_meta_length_after_function */ void agile_modbus_set_compute_meta_length_after_function_cb(agile_modbus_t *ctx, uint8_t (*cb)(agile_modbus_t *ctx, int function, agile_modbus_msg_type_t msg_type)) { ctx->compute_meta_length_after_function = cb; } /** * @brief 设置 modbus 对象的计算数据元之后要接收的数据长度回调函数 * @param ctx modbus 句柄 * @param cb 计算数据元之后要接收的数据长度回调函数 * @see agile_modbus_compute_data_length_after_meta */ void agile_modbus_set_compute_data_length_after_meta_cb(agile_modbus_t *ctx, int (*cb)(agile_modbus_t *ctx, uint8_t *msg, int msg_length, agile_modbus_msg_type_t msg_type)) { ctx->compute_data_length_after_meta = cb; } /** * @brief 校验接收数据正确性 * @note 该 API 返回的是 modbus 数据帧长度,比如 8 个字节的 modbus 数据帧 + 2 个字节的脏数据,返回 8 * @param ctx modbus 句柄 * @param msg_length 接收数据长度 * @param msg_type 消息类型 * @return >0:正确,modbus 数据帧长度; 其他:异常 */ int agile_modbus_receive_judge(agile_modbus_t *ctx, int msg_length, agile_modbus_msg_type_t msg_type) { if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, msg_type); return rc; } /** * @} */ /** @defgroup Modbus_Master Modbus Master * @{ */ /** @defgroup Master_Private_Functions Master Private Functions * @{ */ /** * @brief 计算预期响应数据长度 * @note 如果是特殊的功能码,返回 AGILE_MODBUS_MSG_LENGTH_UNDEFINED ,但这不代表异常。 * agile_modbus_check_confirmation 调用该 API 处理时认为 AGILE_MODBUS_MSG_LENGTH_UNDEFINED 返回值也是有效的。 * @param ctx modbus 句柄 * @param req 请求数据指针 * @return 预期响应数据长度 */ static int agile_modbus_compute_response_length_from_request(agile_modbus_t *ctx, uint8_t *req) { int length; const int offset = ctx->backend->header_length; switch (req[offset]) { case AGILE_MODBUS_FC_READ_COILS: case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS: { /* Header + nb values (code from write_bits) */ int nb = (req[offset + 3] << 8) | req[offset + 4]; length = 2 + (nb / 8) + ((nb % 8) ? 1 : 0); } break; case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS: case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS: case AGILE_MODBUS_FC_READ_INPUT_REGISTERS: /* Header + 2 * nb values */ length = 2 + 2 * (req[offset + 3] << 8 | req[offset + 4]); break; case AGILE_MODBUS_FC_WRITE_SINGLE_COIL: case AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER: case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS: case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS: length = 5; break; case AGILE_MODBUS_FC_MASK_WRITE_REGISTER: length = 7; break; default: /* The response is device specific (the header provides the length) */ return AGILE_MODBUS_MSG_LENGTH_UNDEFINED; } return offset + length + ctx->backend->checksum_length; } /** * @brief 检查确认从机响应的数据 * @param ctx modbus 句柄 * @param req 请求数据指针 * @param rsp 响应数据指针 * @param rsp_length 响应数据长度 * @return >=0:对应功能码响应对象的长度(如 03 功能码,值代表寄存器个数); * 其他:异常 (-1:报文错误;其他:可根据 `-128 - $返回值` 得到异常码) */ static int agile_modbus_check_confirmation(agile_modbus_t *ctx, uint8_t *req, uint8_t *rsp, int rsp_length) { int rc; int rsp_length_computed; const int offset = ctx->backend->header_length; const int function = rsp[offset]; if (ctx->backend->pre_check_confirmation) { rc = ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length); if (rc < 0) return -1; } rsp_length_computed = agile_modbus_compute_response_length_from_request(ctx, req); /* Exception code */ if (function >= 0x80) { if (rsp_length == (offset + 2 + (int)ctx->backend->checksum_length) && req[offset] == (rsp[offset] - 0x80)) return (-128 - rsp[offset + 1]); else return -1; } /* Check length */ if ((rsp_length == rsp_length_computed || rsp_length_computed == AGILE_MODBUS_MSG_LENGTH_UNDEFINED) && function < 0x80) { int req_nb_value; int rsp_nb_value; /* Check function code */ if (function != req[offset]) return -1; /* Check the number of values is corresponding to the request */ switch (function) { case AGILE_MODBUS_FC_READ_COILS: case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS: /* Read functions, 8 values in a byte (nb * of values in the request and byte count in * the response. */ req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; req_nb_value = (req_nb_value / 8) + ((req_nb_value % 8) ? 1 : 0); rsp_nb_value = rsp[offset + 1]; break; case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS: case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS: case AGILE_MODBUS_FC_READ_INPUT_REGISTERS: /* Read functions 1 value = 2 bytes */ req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; rsp_nb_value = (rsp[offset + 1] / 2); break; case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS: case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS: /* N Write functions */ req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4]; break; case AGILE_MODBUS_FC_REPORT_SLAVE_ID: /* Report slave ID (bytes received) */ req_nb_value = rsp_nb_value = rsp[offset + 1]; break; default: /* 1 Write functions & others */ req_nb_value = rsp_nb_value = 1; } if (req_nb_value == rsp_nb_value) rc = rsp_nb_value; else rc = -1; } else rc = -1; return rc; } /** * @} */ /** @defgroup Master_Common_Operation_Functions Master Common Operation Functions * @brief 常用 modbus 主机操作函数 @verbatim API 形式如下: - agile_modbus_serialize_xxx 打包请求数据 返回值: >0:请求数据长度 其他:异常 - agile_modbus_deserialize_xxx 解析响应数据 返回值: >=0:对应功能码响应对象的长度(如 03 功能码,值代表寄存器个数) 其他:异常 (-1:报文错误;其他:可根据 `-128 - $返回值` 得到异常码) @endverbatim * @{ */ int agile_modbus_serialize_read_bits(agile_modbus_t *ctx, int addr, int nb) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if (nb > AGILE_MODBUS_MAX_READ_BITS) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_READ_COILS, addr, nb, ctx->send_buf); req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_read_bits(agile_modbus_t *ctx, int msg_length, uint8_t *dest) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); if (rc < 0) return rc; int i, temp, bit; int pos = 0; int offset; int offset_end; int nb; offset = ctx->backend->header_length + 2; offset_end = offset + rc; nb = (ctx->send_buf[ctx->backend->header_length + 3] << 8) + ctx->send_buf[ctx->backend->header_length + 4]; for (i = offset; i < offset_end; i++) { /* Shift reg hi_byte to temp */ temp = ctx->read_buf[i]; for (bit = 0x01; (bit & 0xff) && (pos < nb);) { dest[pos++] = (temp & bit) ? 1 : 0; bit = bit << 1; } } return nb; } int agile_modbus_serialize_read_input_bits(agile_modbus_t *ctx, int addr, int nb) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if (nb > AGILE_MODBUS_MAX_READ_BITS) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_READ_DISCRETE_INPUTS, addr, nb, ctx->send_buf); req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_read_input_bits(agile_modbus_t *ctx, int msg_length, uint8_t *dest) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); if (rc < 0) return rc; int i, temp, bit; int pos = 0; int offset; int offset_end; int nb; offset = ctx->backend->header_length + 2; offset_end = offset + rc; nb = (ctx->send_buf[ctx->backend->header_length + 3] << 8) + ctx->send_buf[ctx->backend->header_length + 4]; for (i = offset; i < offset_end; i++) { /* Shift reg hi_byte to temp */ temp = ctx->read_buf[i]; for (bit = 0x01; (bit & 0xff) && (pos < nb);) { dest[pos++] = (temp & bit) ? 1 : 0; bit = bit << 1; } } return nb; } int agile_modbus_serialize_read_registers(agile_modbus_t *ctx, int addr, int nb) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if (nb > AGILE_MODBUS_MAX_READ_REGISTERS) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_READ_HOLDING_REGISTERS, addr, nb, ctx->send_buf); req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_read_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); if (rc < 0) return rc; int offset; int i; offset = ctx->backend->header_length; for (i = 0; i < rc; i++) { /* shift reg hi_byte to temp OR with lo_byte */ dest[i] = (ctx->read_buf[offset + 2 + (i << 1)] << 8) | ctx->read_buf[offset + 3 + (i << 1)]; } return rc; } int agile_modbus_serialize_read_input_registers(agile_modbus_t *ctx, int addr, int nb) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if (nb > AGILE_MODBUS_MAX_READ_REGISTERS) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_READ_INPUT_REGISTERS, addr, nb, ctx->send_buf); req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_read_input_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); if (rc < 0) return rc; int offset; int i; offset = ctx->backend->header_length; for (i = 0; i < rc; i++) { /* shift reg hi_byte to temp OR with lo_byte */ dest[i] = (ctx->read_buf[offset + 2 + (i << 1)] << 8) | ctx->read_buf[offset + 3 + (i << 1)]; } return rc; } int agile_modbus_serialize_write_bit(agile_modbus_t *ctx, int addr, int status) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_WRITE_SINGLE_COIL, addr, status ? 0xFF00 : 0, ctx->send_buf); req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_write_bit(agile_modbus_t *ctx, int msg_length) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); return rc; } int agile_modbus_serialize_write_register(agile_modbus_t *ctx, int addr, const uint16_t value) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER, addr, (int)value, ctx->send_buf); req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_write_register(agile_modbus_t *ctx, int msg_length) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); return rc; } int agile_modbus_serialize_write_bits(agile_modbus_t *ctx, int addr, int nb, const uint8_t *src) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if (nb > AGILE_MODBUS_MAX_WRITE_BITS) return -1; int i; int byte_count; int req_length; int bit_check = 0; int pos = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS, addr, nb, ctx->send_buf); byte_count = (nb / 8) + ((nb % 8) ? 1 : 0); min_req_length += (1 + byte_count); if (ctx->send_bufsz < min_req_length) return -1; ctx->send_buf[req_length++] = byte_count; for (i = 0; i < byte_count; i++) { int bit; bit = 0x01; ctx->send_buf[req_length] = 0; while ((bit & 0xFF) && (bit_check++ < nb)) { if (src[pos++]) ctx->send_buf[req_length] |= bit; else ctx->send_buf[req_length] &= ~bit; bit = bit << 1; } req_length++; } req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_write_bits(agile_modbus_t *ctx, int msg_length) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); return rc; } int agile_modbus_serialize_write_registers(agile_modbus_t *ctx, int addr, int nb, const uint16_t *src) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if (nb > AGILE_MODBUS_MAX_WRITE_REGISTERS) return -1; int i; int req_length; int byte_count; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS, addr, nb, ctx->send_buf); byte_count = nb * 2; min_req_length += (1 + byte_count); if (ctx->send_bufsz < min_req_length) return -1; ctx->send_buf[req_length++] = byte_count; for (i = 0; i < nb; i++) { ctx->send_buf[req_length++] = src[i] >> 8; ctx->send_buf[req_length++] = src[i] & 0x00FF; } req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_write_registers(agile_modbus_t *ctx, int msg_length) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); return rc; } int agile_modbus_serialize_mask_write_register(agile_modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length + 2; if (ctx->send_bufsz < min_req_length) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_MASK_WRITE_REGISTER, addr, 0, ctx->send_buf); /* HACKISH, count is not used */ req_length -= 2; ctx->send_buf[req_length++] = and_mask >> 8; ctx->send_buf[req_length++] = and_mask & 0x00ff; ctx->send_buf[req_length++] = or_mask >> 8; ctx->send_buf[req_length++] = or_mask & 0x00ff; req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_mask_write_register(agile_modbus_t *ctx, int msg_length) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); return rc; } int agile_modbus_serialize_write_and_read_registers(agile_modbus_t *ctx, int write_addr, int write_nb, const uint16_t *src, int read_addr, int read_nb) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if (write_nb > AGILE_MODBUS_MAX_WR_WRITE_REGISTERS) return -1; if (read_nb > AGILE_MODBUS_MAX_WR_READ_REGISTERS) return -1; int req_length; int i; int byte_count; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS, read_addr, read_nb, ctx->send_buf); byte_count = write_nb * 2; min_req_length += (5 + byte_count); if (ctx->send_bufsz < min_req_length) return -1; ctx->send_buf[req_length++] = write_addr >> 8; ctx->send_buf[req_length++] = write_addr & 0x00ff; ctx->send_buf[req_length++] = write_nb >> 8; ctx->send_buf[req_length++] = write_nb & 0x00ff; ctx->send_buf[req_length++] = byte_count; for (i = 0; i < write_nb; i++) { ctx->send_buf[req_length++] = src[i] >> 8; ctx->send_buf[req_length++] = src[i] & 0x00FF; } req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_write_and_read_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); if (rc < 0) return rc; int offset; int i; offset = ctx->backend->header_length; for (i = 0; i < rc; i++) { /* shift reg hi_byte to temp OR with lo_byte */ dest[i] = (ctx->read_buf[offset + 2 + (i << 1)] << 8) | ctx->read_buf[offset + 3 + (i << 1)]; } return rc; } int agile_modbus_serialize_report_slave_id(agile_modbus_t *ctx) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; int req_length = 0; req_length = ctx->backend->build_request_basis(ctx, AGILE_MODBUS_FC_REPORT_SLAVE_ID, 0, 0, ctx->send_buf); /* HACKISH, addr and count are not used */ req_length -= 4; req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } int agile_modbus_deserialize_report_slave_id(agile_modbus_t *ctx, int msg_length, int max_dest, uint8_t *dest) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; if (max_dest <= 0) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); if (rc < 0) return rc; int i; int offset; offset = ctx->backend->header_length + 2; /* Byte count, slave id, run indicator status and additional data. Truncate copy to max_dest. */ for (i = 0; i < rc && i < max_dest; i++) { dest[i] = ctx->read_buf[offset + i]; } return rc; } /** * @} */ /** @defgroup Master_Raw_Operation_Functions Master Raw Operation Functions * @{ */ /** * @brief 将原始数据打包成请求报文 * @param ctx modbus 句柄 * @param raw_req 原始报文(PDU + Slave address) * @param raw_req_length 原始报文长度 * @return >0:请求数据长度; 其他:异常 */ int agile_modbus_serialize_raw_request(agile_modbus_t *ctx, const uint8_t *raw_req, int raw_req_length) { if (raw_req_length < 2) { /* The raw request must contain function and slave at least and must not be longer than the maximum pdu length plus the slave address. */ return -1; } int min_req_length = ctx->backend->header_length + 1 + ctx->backend->checksum_length + raw_req_length - 2; if (ctx->send_bufsz < min_req_length) return -1; agile_modbus_sft_t sft; int req_length; sft.slave = raw_req[0]; sft.function = raw_req[1]; /* The t_id is left to zero */ sft.t_id = 0; /* This response function only set the header so it's convenient here */ req_length = ctx->backend->build_response_basis(&sft, ctx->send_buf); if (raw_req_length > 2) { /* Copy data after function code */ memcpy(ctx->send_buf + req_length, raw_req + 2, raw_req_length - 2); req_length += raw_req_length - 2; } req_length = ctx->backend->send_msg_pre(ctx->send_buf, req_length); return req_length; } /** * @brief 解析响应原始数据 * @param ctx modbus 句柄 * @param msg_length 接收数据长度 * @return >=0:对应功能码响应对象的长度(如 03 功能码,值代表寄存器个数); * 其他:异常 (-1:报文错误;其他:可根据 `-128 - $返回值` 得到异常码) */ int agile_modbus_deserialize_raw_response(agile_modbus_t *ctx, int msg_length) { int min_req_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_req_length) return -1; if ((msg_length <= 0) || (msg_length > ctx->read_bufsz)) return -1; int rc = agile_modbus_receive_msg_judge(ctx, ctx->read_buf, msg_length, AGILE_MODBUS_MSG_CONFIRMATION); if (rc < 0) return -1; rc = agile_modbus_check_confirmation(ctx, ctx->send_buf, ctx->read_buf, rc); return rc; } /** * @} */ /** * @} */ /** @defgroup Modbus_Slave Modbus Slave * @{ */ /** @defgroup Slave_Private_Functions Slave Private Functions * @{ */ /** * @brief 打包异常响应数据 * @param ctx modbus 句柄 * @param sft modbus 信息头 * @param exception_code 异常码 * @return 响应数据长度 */ static int agile_modbus_serialize_response_exception(agile_modbus_t *ctx, agile_modbus_sft_t *sft, int exception_code) { int rsp_length; /* Build exception response */ sft->function = sft->function + 0x80; rsp_length = ctx->backend->build_response_basis(sft, ctx->send_buf); ctx->send_buf[rsp_length++] = exception_code; return rsp_length; } /** * @} */ /** @defgroup Slave_Operation_Functions Slave Operation Functions * @{ */ /** * @brief 从机 IO 设置 * @param buf 存放 IO 数据区 * @param index IO 索引(第几个 IO) * @param status IO 状态 */ void agile_modbus_slave_io_set(uint8_t *buf, int index, int status) { int offset = index / 8; int shift = index % 8; if (status) buf[offset] |= (0x01 << shift); else buf[offset] &= ~(0x01 << shift); } /** * @brief 读取从机 IO 状态 * @param buf IO 数据区域 * @param index IO 索引(第几个 IO) * @return IO 状态(1/0) */ uint8_t agile_modbus_slave_io_get(uint8_t *buf, int index) { int offset = index / 8; int shift = index % 8; uint8_t status = (buf[offset] & (0x01 << shift)) ? 1 : 0; return status; } /** * @brief 从机寄存器设置 * @param buf 存放数据区 * @param index 寄存器索引(第几个寄存器) * @param data 寄存器数据 */ void agile_modbus_slave_register_set(uint8_t *buf, int index, uint16_t data) { buf[index * 2] = data >> 8; buf[index * 2 + 1] = data & 0xFF; } /** * @brief 读取从机寄存器数据 * @param buf 寄存器数据区域 * @param index 寄存器索引(第几个寄存器) * @return 寄存器数据 */ uint16_t agile_modbus_slave_register_get(uint8_t *buf, int index) { uint16_t data = (buf[index * 2] << 8) + buf[index * 2 + 1]; return data; } /** * @brief 从机数据处理 * @param ctx modbus 句柄 * @param msg_length 接收数据长度 * @param slave_strict 从机地址严格检查标志 * @arg 0: 不比对从机地址 * @arg 1: 比对从机地址 * @param slave_cb 从机回调函数 * @param slave_data 从机回调函数私有数据 * @param frame_length 存放 modbus 数据帧长度 * @return >=0:要响应的数据长度; 其他:异常 */ int agile_modbus_slave_handle(agile_modbus_t *ctx, int msg_length, uint8_t slave_strict, agile_modbus_slave_callback_t slave_cb, const void *slave_data, int *frame_length) { int min_rsp_length = ctx->backend->header_length + 5 + ctx->backend->checksum_length; if (ctx->send_bufsz < min_rsp_length) return -1; int req_length = agile_modbus_receive_judge(ctx, msg_length, AGILE_MODBUS_MSG_INDICATION); if (req_length < 0) return -1; if (frame_length) *frame_length = req_length; int offset; int slave; int function; uint16_t address; int rsp_length = 0; int exception_code = 0; agile_modbus_sft_t sft; uint8_t *req = ctx->read_buf; uint8_t *rsp = ctx->send_buf; memset(rsp, 0, ctx->send_bufsz); offset = ctx->backend->header_length; slave = req[offset - 1]; function = req[offset]; address = (req[offset + 1] << 8) + req[offset + 2]; sft.slave = slave; sft.function = function; sft.t_id = ctx->backend->prepare_response_tid(req, &req_length); struct agile_modbus_slave_info slave_info = {0}; slave_info.sft = &sft; slave_info.rsp_length = &rsp_length; slave_info.address = address; if (slave_strict) { if ((slave != ctx->slave) && (slave != AGILE_MODBUS_BROADCAST_ADDRESS)) return 0; } switch (function) { case AGILE_MODBUS_FC_READ_COILS: case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; if (nb < 1 || AGILE_MODBUS_MAX_READ_BITS < nb) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE; break; } int end_address = (int)address + nb - 1; if (end_address > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } rsp_length = ctx->backend->build_response_basis(&sft, rsp); slave_info.nb = (nb / 8) + ((nb % 8) ? 1 : 0); rsp[rsp_length++] = slave_info.nb; slave_info.send_index = rsp_length; rsp_length += slave_info.nb; slave_info.nb = nb; if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } } break; case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS: case AGILE_MODBUS_FC_READ_INPUT_REGISTERS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; if (nb < 1 || AGILE_MODBUS_MAX_READ_REGISTERS < nb) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE; break; } int end_address = (int)address + nb - 1; if (end_address > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } rsp_length = ctx->backend->build_response_basis(&sft, rsp); slave_info.nb = nb << 1; rsp[rsp_length++] = slave_info.nb; slave_info.send_index = rsp_length; rsp_length += slave_info.nb; slave_info.nb = nb; if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } } break; case AGILE_MODBUS_FC_WRITE_SINGLE_COIL: { //! warning: comparison is always false due to limited range of data type [-Wtype-limits] #if 0 if (address > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } #endif int data = (req[offset + 3] << 8) + req[offset + 4]; if (data == 0xFF00 || data == 0x0) data = data ? 1 : 0; else { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE; break; } slave_info.buf = (uint8_t *)&data; rsp_length = req_length; if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } memcpy(rsp, req, req_length); } break; case AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER: { //! warning: comparison is always false due to limited range of data type [-Wtype-limits] #if 0 if (address > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } #endif int data = (req[offset + 3] << 8) + req[offset + 4]; slave_info.buf = (uint8_t *)&data; rsp_length = req_length; if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } memcpy(rsp, req, req_length); } break; case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; int nb_bits = req[offset + 5]; if (nb < 1 || AGILE_MODBUS_MAX_WRITE_BITS < nb || nb_bits * 8 < nb) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE; break; } int end_address = (int)address + nb - 1; if (end_address > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } rsp_length = ctx->backend->build_response_basis(&sft, rsp); slave_info.nb = nb; slave_info.buf = &req[offset + 6]; if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length + 4)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } /* 4 to copy the bit address (2) and the quantity of bits */ memcpy(rsp + rsp_length, req + rsp_length, 4); rsp_length += 4; } break; case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; int nb_bytes = req[offset + 5]; if (nb < 1 || AGILE_MODBUS_MAX_WRITE_REGISTERS < nb || nb_bytes != nb * 2) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE; break; } int end_address = (int)address + nb - 1; if (end_address > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } rsp_length = ctx->backend->build_response_basis(&sft, rsp); slave_info.nb = nb; slave_info.buf = &req[offset + 6]; if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length + 4)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } /* 4 to copy the address (2) and the no. of registers */ memcpy(rsp + rsp_length, req + rsp_length, 4); rsp_length += 4; } break; case AGILE_MODBUS_FC_REPORT_SLAVE_ID: { int str_len; int byte_count_pos; slave_cb = NULL; rsp_length = ctx->backend->build_response_basis(&sft, rsp); /* Skip byte count for now */ byte_count_pos = rsp_length++; rsp[rsp_length++] = ctx->slave; /* Run indicator status to ON */ rsp[rsp_length++] = 0xFF; str_len = strlen(AGILE_MODBUS_VERSION_STRING); if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length + str_len)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } memcpy(rsp + rsp_length, AGILE_MODBUS_VERSION_STRING, str_len); rsp_length += str_len; rsp[byte_count_pos] = rsp_length - byte_count_pos - 1; } break; case AGILE_MODBUS_FC_READ_EXCEPTION_STATUS: exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION; break; case AGILE_MODBUS_FC_MASK_WRITE_REGISTER: { //! warning: comparison is always false due to limited range of data type [-Wtype-limits] #if 0 if (address > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } #endif slave_info.buf = &req[offset + 3]; rsp_length = req_length; if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } memcpy(rsp, req, req_length); } break; case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS: { int nb = (req[offset + 3] << 8) + req[offset + 4]; uint16_t address_write = (req[offset + 5] << 8) + req[offset + 6]; int nb_write = (req[offset + 7] << 8) + req[offset + 8]; int nb_write_bytes = req[offset + 9]; if (nb_write < 1 || AGILE_MODBUS_MAX_WR_WRITE_REGISTERS < nb_write || nb < 1 || AGILE_MODBUS_MAX_WR_READ_REGISTERS < nb || nb_write_bytes != nb_write * 2) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE; break; } int end_address = (int)address + nb - 1; int end_address_write = (int)address_write + nb_write - 1; if (end_address > 0xFFFF || end_address_write > 0xFFFF) { exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; break; } rsp_length = ctx->backend->build_response_basis(&sft, rsp); rsp[rsp_length++] = nb << 1; slave_info.buf = &req[offset + 3]; slave_info.send_index = rsp_length; rsp_length += (nb << 1); if (ctx->send_bufsz < (int)(rsp_length + ctx->backend->checksum_length)) { exception_code = AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE; break; } } break; default: { if (slave_cb == NULL) exception_code = AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION; else { rsp_length = ctx->backend->build_response_basis(&sft, rsp); slave_info.send_index = rsp_length; slave_info.buf = &req[offset + 1]; slave_info.nb = req_length - offset - 1; } } break; } if (exception_code) rsp_length = agile_modbus_serialize_response_exception(ctx, &sft, exception_code); else { if (slave_cb) { int ret = slave_cb(ctx, &slave_info, slave_data); if (ret < 0) { if (ret == -AGILE_MODBUS_EXCEPTION_UNKNOW) rsp_length = 0; else rsp_length = agile_modbus_serialize_response_exception(ctx, &sft, -ret); } } } if (rsp_length) { if ((ctx->backend->backend_type == AGILE_MODBUS_BACKEND_TYPE_RTU) && (slave == AGILE_MODBUS_BROADCAST_ADDRESS)) return 0; rsp_length = ctx->backend->send_msg_pre(rsp, rsp_length); } return rsp_length; } /** * @} */ /** * @} */ /** * @} */