From fc9562ccd6c86b8eeda6d0cdbff05a14d1fff131 Mon Sep 17 00:00:00 2001 From: pikastech Date: Sun, 18 Sep 2022 22:04:55 +0800 Subject: [PATCH] add modbus module add aglie_mmodbus add agile_modbus and LICENSE fix warning add serialize deserialize API --- package/modbus/LICENSE | 201 +++ package/modbus/_modbus.c | 183 ++ package/modbus/_modbus.pyi | 31 + package/modbus/agile_modbus.c | 1519 +++++++++++++++++ package/modbus/agile_modbus.h | 359 ++++ package/modbus/agile_modbus_rtu.c | 291 ++++ package/modbus/agile_modbus_rtu.h | 79 + package/modbus/agile_modbus_tcp.c | 226 +++ package/modbus/agile_modbus_tcp.h | 83 + package/modbus/modbus.py | 5 + port/linux/.vscode/settings.json | 6 +- port/linux/package/pikascript/_modbus.pyi | 31 + port/linux/package/pikascript/main.py | 2 + port/linux/package/pikascript/modbus.py | 5 + .../pikascript/pikascript-lib/modbus/LICENSE | 201 +++ .../pikascript-lib/modbus/_modbus.c | 183 ++ .../pikascript-lib/modbus/agile_modbus.c | 1519 +++++++++++++++++ .../pikascript-lib/modbus/agile_modbus.h | 359 ++++ .../pikascript-lib/modbus/agile_modbus_rtu.c | 291 ++++ .../pikascript-lib/modbus/agile_modbus_rtu.h | 79 + .../pikascript-lib/modbus/agile_modbus_tcp.c | 226 +++ .../pikascript-lib/modbus/agile_modbus_tcp.h | 83 + 22 files changed, 5961 insertions(+), 1 deletion(-) create mode 100644 package/modbus/LICENSE create mode 100644 package/modbus/_modbus.c create mode 100644 package/modbus/_modbus.pyi create mode 100644 package/modbus/agile_modbus.c create mode 100644 package/modbus/agile_modbus.h create mode 100644 package/modbus/agile_modbus_rtu.c create mode 100644 package/modbus/agile_modbus_rtu.h create mode 100644 package/modbus/agile_modbus_tcp.c create mode 100644 package/modbus/agile_modbus_tcp.h create mode 100644 package/modbus/modbus.py create mode 100644 port/linux/package/pikascript/_modbus.pyi create mode 100644 port/linux/package/pikascript/modbus.py create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/LICENSE create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/_modbus.c create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.c create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.h create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.c create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.h create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.c create mode 100644 port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.h diff --git a/package/modbus/LICENSE b/package/modbus/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/package/modbus/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/package/modbus/_modbus.c b/package/modbus/_modbus.c new file mode 100644 index 000000000..4694e03de --- /dev/null +++ b/package/modbus/_modbus.c @@ -0,0 +1,183 @@ +#include "_modbus__ModBus.h" +#include "_modbus__ModBusRTU.h" +#include "_modbus__ModBusTCP.h" +#include "agile_modbus.h" + +void _modbus__ModBusRTU___init__(PikaObj* self, + int sendBUffSize, + int readBuffSize) { + agile_modbus_rtu_t ctx_rtu = {0}; + agile_modbus_t* ctx = &ctx_rtu._ctx; + obj_setBytes(self, "sendBuff", NULL, sendBUffSize); + obj_setBytes(self, "readBuff", NULL, readBuffSize); + agile_modbus_rtu_init(&ctx_rtu, obj_getBytes(self, "sendBuff"), + sendBUffSize, obj_getBytes(self, "readBuff"), + readBuffSize); + obj_setStruct(self, "ctx_rtu", ctx_rtu); + obj_setPtr(self, "ctx", ctx); +} + +void _modbus__ModBus_setSlave(PikaObj* self, int slave) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + agile_modbus_set_slave(ctx, slave); +} + +void _modbus__ModBusTCP___init__(PikaObj* self, + int sendBuffSize, + int readBuffSize) { + agile_modbus_tcp_t ctx_tcp = {0}; + agile_modbus_t* ctx = &ctx_tcp._ctx; + obj_setBytes(self, "sendBuff", NULL, sendBuffSize); + obj_setBytes(self, "readBuff", NULL, readBuffSize); + agile_modbus_tcp_init(&ctx_tcp, obj_getBytes(self, "sendBuff"), + sendBuffSize, obj_getBytes(self, "readBuff"), + readBuffSize); + obj_setStruct(self, "ctx_tcp", ctx_tcp); + obj_setPtr(self, "ctx", ctx); +} + +int _modbus__ModBus_deserializeMaskWriteRegister(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_mask_write_register(ctx, msgLength); +} + +int _modbus__ModBus_deserializeReadBits(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_bits(ctx, msgLength, dest); +} + +int _modbus__ModBus_deserializeReadInputBits(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_input_bits(ctx, msgLength, dest); +} + +int _modbus__ModBus_deserializeReadInputRegisters(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_input_registers(ctx, msgLength, + (uint16_t*)dest); +} + +int _modbus__ModBus_deserializeReadRegisters(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_registers(ctx, msgLength, + (uint16_t*)dest); +} + +int _modbus__ModBus_deserializeReportSlaveId(PikaObj* self, + int msgLength, + int maxDest, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_report_slave_id(ctx, msgLength, maxDest, + dest); +} + +int _modbus__ModBus_deserializeWriteAndReadRegisters(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_and_read_registers(ctx, msgLength, + (uint16_t*)dest); +} + +int _modbus__ModBus_deserializeWriteBit(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_bit(ctx, msgLength); +} + +int _modbus__ModBus_deserializeWriteBits(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_bits(ctx, msgLength); +} + +int _modbus__ModBus_deserializeWriteRegister(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_register(ctx, msgLength); +} + +int _modbus__ModBus_deserializeWriteRegisters(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_registers(ctx, msgLength); +} + +int _modbus__ModBus_serializeMaskWriteRegister(PikaObj* self, + int addr, + int andMask, + int orMask) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_mask_write_register(ctx, addr, andMask, + orMask); +} + +int _modbus__ModBus_serializeReadBits(PikaObj* self, int addr, int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_bits(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReadInputBits(PikaObj* self, int addr, int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_input_bits(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReadInputRegisters(PikaObj* self, + int addr, + int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_input_registers(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReadRegisters(PikaObj* self, int addr, int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_registers(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReportSlaveId(PikaObj* self) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_report_slave_id(ctx); +} + +int _modbus__ModBus_serializeWriteAndReadRegisters(PikaObj* self, + int writeAddr, + int writeNb, + uint8_t* src, + int readAddr, + int readNb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_and_read_registers( + ctx, writeAddr, writeNb, (uint16_t*)src, readAddr, readNb); +} + +int _modbus__ModBus_serializeWriteBit(PikaObj* self, int addr, int status) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_bit(ctx, addr, status); +} + +int _modbus__ModBus_serializeWriteBits(PikaObj* self, + int addr, + int nb, + uint8_t* src) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_bits(ctx, addr, nb, src); +} + +int _modbus__ModBus_serializeWriteRegister(PikaObj* self, int addr, int value) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_register(ctx, addr, value); +} + +int _modbus__ModBus_serializeWriteRegisters(PikaObj* self, + int addr, + int nb, + uint8_t* src) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_registers(ctx, addr, nb, + (uint16_t*)src); +} diff --git a/package/modbus/_modbus.pyi b/package/modbus/_modbus.pyi new file mode 100644 index 000000000..5334fd719 --- /dev/null +++ b/package/modbus/_modbus.pyi @@ -0,0 +1,31 @@ +class _ModBus: + def setSlave(self, slave: int): ... + def serializeReadBits(self, addr: int, nb: int) -> int: ... + def deserializeReadBits(self, msgLength: int, dest: bytes) -> int: ... + def serializeReadInputBits(self, addr: int, nb: int) -> int: ... + def deserializeReadInputBits(self, msgLength: int, dest: bytes) -> int: ... + def serializeReadRegisters(self, addr: int, nb: int) -> int: ... + def deserializeReadRegisters(self, msgLength: int, dest: bytes) -> int: ... + def serializeReadInputRegisters(self, addr: int, nb: int) -> int: ... + def deserializeReadInputRegisters(self, msgLength: int, dest: bytes) -> int: ... + def serializeWriteBit(self, addr: int, status: int) -> int: ... + def deserializeWriteBit(self, msgLength: int) -> int: ... + def serializeWriteRegister(self, addr: int, value: int) -> int: ... + def deserializeWriteRegister(self, msgLength: int) -> int: ... + def serializeWriteBits(self, addr: int, nb: int, src: bytes) -> int: ... + def deserializeWriteBits(self, msgLength: int) -> int: ... + def serializeWriteRegisters(self, addr: int, nb: int, src: bytes) -> int: ... + def deserializeWriteRegisters(self, msgLength: int) -> int: ... + def serializeMaskWriteRegister(self, addr: int, andMask: int, orMask: int) -> int: ... + def deserializeMaskWriteRegister(self, msgLength: int) -> int: ... + def serializeWriteAndReadRegisters(self, writeAddr: int, writeNb: int, src: bytes, readAddr: int, readNb: int) -> int: ... + def deserializeWriteAndReadRegisters(self, msgLength: int, dest: bytes) -> int: ... + def serializeReportSlaveId(self) -> int: ... + def deserializeReportSlaveId(self, msgLength: int, maxDest: int, dest: bytes) -> int: ... + +class _ModBusRTU(_ModBus): + def __init__(self, sendBuffSize: int, readBuffSize: int): ... + + +class _ModBusTCP(_ModBus): + def __init__(self, sendBuffSize: int, readBuffSize: int): ... diff --git a/package/modbus/agile_modbus.c b/package/modbus/agile_modbus.c new file mode 100644 index 000000000..81ff40424 --- /dev/null +++ b/package/modbus/agile_modbus.c @@ -0,0 +1,1519 @@ +/** + * @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; +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ diff --git a/package/modbus/agile_modbus.h b/package/modbus/agile_modbus.h new file mode 100644 index 000000000..4be7429b5 --- /dev/null +++ b/package/modbus/agile_modbus.h @@ -0,0 +1,359 @@ +/** + * @file agile_modbus.h + * @brief Agile Modbus 软件包通用头文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2022-07-28 + * + * @attention + * + *

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

+ * + */ + +#ifndef __PKG_AGILE_MODBUS_H +#define __PKG_AGILE_MODBUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @addtogroup COMMON + * @{ + */ + +/** @defgroup COMMON_Exported_Constants Common Exported Constants + * @{ + */ + +/** @defgroup Modbus_Function_Codes Modbus Function Codes + * @{ + */ +#define AGILE_MODBUS_FC_READ_COILS 0x01 +#define AGILE_MODBUS_FC_READ_DISCRETE_INPUTS 0x02 +#define AGILE_MODBUS_FC_READ_HOLDING_REGISTERS 0x03 +#define AGILE_MODBUS_FC_READ_INPUT_REGISTERS 0x04 +#define AGILE_MODBUS_FC_WRITE_SINGLE_COIL 0x05 +#define AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 +#define AGILE_MODBUS_FC_READ_EXCEPTION_STATUS 0x07 +#define AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F +#define AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 +#define AGILE_MODBUS_FC_REPORT_SLAVE_ID 0x11 +#define AGILE_MODBUS_FC_MASK_WRITE_REGISTER 0x16 +#define AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17 +/** + * @} + */ + +/** @defgroup Modbus_Constants Modbus Constants + * @{ + */ +#define AGILE_MODBUS_VERSION_STRING "AMB_1.1.0" /**< Agile Modbus 版本号 */ + +#define AGILE_MODBUS_BROADCAST_ADDRESS 0 /**< Modbus 广播地址 */ + +/** @name Quantity limit of Coils + @verbatim + Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 1 page 12) + Quantity of Coils to read (2 bytes): 1 to 2000 (0x7D0) + (chapter 6 section 11 page 29) + Quantity of Coils to write (2 bytes): 1 to 1968 (0x7B0) + + @endverbatim + * @{ + */ +#define AGILE_MODBUS_MAX_READ_BITS 2000 +#define AGILE_MODBUS_MAX_WRITE_BITS 1968 +/** + * @} + */ + +/** @name Quantity limit of Registers + @verbatim + Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 3 page 15) + Quantity of Registers to read (2 bytes): 1 to 125 (0x7D) + (chapter 6 section 12 page 31) + Quantity of Registers to write (2 bytes) 1 to 123 (0x7B) + (chapter 6 section 17 page 38) + Quantity of Registers to write in R/W registers (2 bytes) 1 to 121 (0x79) + + @endverbatim + * @{ + */ +#define AGILE_MODBUS_MAX_READ_REGISTERS 125 +#define AGILE_MODBUS_MAX_WRITE_REGISTERS 123 +#define AGILE_MODBUS_MAX_WR_WRITE_REGISTERS 121 +#define AGILE_MODBUS_MAX_WR_READ_REGISTERS 125 +/** + * @} + */ + +/** + @verbatim + The size of the MODBUS PDU is limited by the size constraint inherited from + the first MODBUS implementation on Serial Line network (max. RS485 ADU = 256 + bytes). Therefore, MODBUS PDU for serial line communication = 256 - Server + address (1 byte) - CRC (2 bytes) = 253 bytes. + + @endverbatim + */ +#define AGILE_MODBUS_MAX_PDU_LENGTH 253 + +/** + @verbatim + Consequently: + - RTU MODBUS ADU = 253 bytes + Server address (1 byte) + CRC (2 bytes) = 256 + bytes. + - TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes. + so the maximum of both backend in 260 bytes. This size can used to allocate + an array of bytes to store responses and it will be compatible with the two + backends. + + @endverbatim + */ +#define AGILE_MODBUS_MAX_ADU_LENGTH 260 +/** + * @} + */ + +/** + * @} + */ + +/** @defgroup COMMON_Exported_Types Common Exported Types + * @{ + */ + +/** + * @brief Modbus 异常码 + */ +enum { + AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01, + AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + AGILE_MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE, + AGILE_MODBUS_EXCEPTION_ACKNOWLEDGE, + AGILE_MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY, + AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE, + AGILE_MODBUS_EXCEPTION_MEMORY_PARITY, + AGILE_MODBUS_EXCEPTION_NOT_DEFINED, + AGILE_MODBUS_EXCEPTION_GATEWAY_PATH, + AGILE_MODBUS_EXCEPTION_GATEWAY_TARGET, + AGILE_MODBUS_EXCEPTION_UNKNOW = 0xff +}; + +/** + * @brief Modbus 后端类型 + */ +typedef enum { + AGILE_MODBUS_BACKEND_TYPE_RTU = 0, /**< RTU */ + AGILE_MODBUS_BACKEND_TYPE_TCP /**< TCP */ +} agile_modbus_backend_type_t; + +/** + * @brief Modbus 收到消息类型 + * + @verbatim + ---------- Request Indication ---------- + | Client | ---------------------->| Server | + ---------- Confirmation Response ---------- + + @endverbatim + */ +typedef enum { + AGILE_MODBUS_MSG_INDICATION, /**< 主机端的请求消息 */ + AGILE_MODBUS_MSG_CONFIRMATION /**< 服务器端的请求消息 */ +} agile_modbus_msg_type_t; + +/** + * @brief 包含 modbus 头部参数结构体 + */ +typedef struct agile_modbus_sft { + int slave; /**< 从机地址 */ + int function; /**< 功能码 */ + int t_id; /**< 事务标识符 */ +} agile_modbus_sft_t; + +typedef struct agile_modbus agile_modbus_t; /**< Agile Modbus 结构体 */ + +/** + * @brief Agile Modbus 后端接口结构体 + */ +typedef struct agile_modbus_backend { + uint32_t backend_type; /**< 后端类型 */ + uint32_t header_length; /**< 头部长度,不包含功能码 */ + uint32_t checksum_length; /**< 校验数据长度 */ + uint32_t max_adu_length; /**< 后端 ADU 长度 */ + int (*set_slave)(agile_modbus_t *ctx, int slave); /**< 设置地址接口 */ + int (*build_request_basis)(agile_modbus_t *ctx, int function, int addr, + int nb, uint8_t *req); /**< 构建基础请求报文接口 */ + int (*build_response_basis)(agile_modbus_sft_t *sft, uint8_t *rsp); /**< 构建基础响应报文接口 */ + int (*prepare_response_tid)(const uint8_t *req, int *req_length); /**< 准备响应接口 */ + int (*send_msg_pre)(uint8_t *req, int req_length); /**< 预发送数据接口 */ + int (*check_integrity)(agile_modbus_t *ctx, uint8_t *msg, const int msg_length); /**< 检查接收数据完整性接口 */ + int (*pre_check_confirmation)(agile_modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length); /**< 预检查确认接口 */ +} agile_modbus_backend_t; + +/** + * @brief Agile Modbus 结构体 + */ +struct agile_modbus { + int slave; /**< 从机地址 */ + uint8_t *send_buf; /**< 发送缓冲区 */ + int send_bufsz; /**< 发送缓冲区大小 */ + uint8_t *read_buf; /**< 接收缓冲区 */ + int read_bufsz; /**< 接收缓冲区大小 */ + uint8_t (*compute_meta_length_after_function)(agile_modbus_t *ctx, int function, + agile_modbus_msg_type_t msg_type); /**< 自定义计算数据元长度接口 */ + int (*compute_data_length_after_meta)(agile_modbus_t *ctx, uint8_t *msg, + int msg_length, agile_modbus_msg_type_t msg_type); /**< 自定义计算数据长度接口 */ + const agile_modbus_backend_t *backend; /**< 后端接口 */ + void *backend_data; /**< 后端数据,指向 RTU 或 TCP 结构体 */ +}; + +/** + * @} + */ + +/** @addtogroup Modbus_Slave + * @{ + */ + +/** @defgroup Slave_Exported_Types Slave Exported Types + * @{ + */ + +/** + * @brief Agile Modbus 从机信息结构体 + */ +struct agile_modbus_slave_info { + agile_modbus_sft_t *sft; /**< sft 结构体指针 */ + int *rsp_length; /**< 响应数据长度指针 */ + int address; /**< 寄存器地址 */ + int nb; /**< 数目 */ + uint8_t *buf; /**< 不同功能码需要使用的数据域 */ + int send_index; /**< 发送缓冲区当前索引 */ +}; + +/** + * @brief 从机回调函数 + * @param ctx modbus 句柄 + * @param slave_info 从机信息体 + * @param data 私有数据 + * @return =0:正常; + * <0:异常 + * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): 未知异常,从机不会打包响应数据) + * (其他负数异常码: 从机会打包异常响应数据) + */ +typedef int (*agile_modbus_slave_callback_t)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const void *data); + +/** + * @} + */ + +/** + * @} + */ + +/** @addtogroup COMMON_Exported_Functions + * @{ + */ +void agile_modbus_common_init(agile_modbus_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); +int agile_modbus_set_slave(agile_modbus_t *ctx, int slave); +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)); +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)); +int agile_modbus_receive_judge(agile_modbus_t *ctx, int msg_length, agile_modbus_msg_type_t msg_type); +/** + * @} + */ + +/** @addtogroup Modbus_Master + * @{ + */ + +/** @addtogroup Master_Common_Operation_Functions + * @{ + */ +int agile_modbus_serialize_read_bits(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_bits(agile_modbus_t *ctx, int msg_length, uint8_t *dest); +int agile_modbus_serialize_read_input_bits(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_input_bits(agile_modbus_t *ctx, int msg_length, uint8_t *dest); +int agile_modbus_serialize_read_registers(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest); +int agile_modbus_serialize_read_input_registers(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_input_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest); +int agile_modbus_serialize_write_bit(agile_modbus_t *ctx, int addr, int status); +int agile_modbus_deserialize_write_bit(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_write_register(agile_modbus_t *ctx, int addr, const uint16_t value); +int agile_modbus_deserialize_write_register(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_write_bits(agile_modbus_t *ctx, int addr, int nb, const uint8_t *src); +int agile_modbus_deserialize_write_bits(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_write_registers(agile_modbus_t *ctx, int addr, int nb, const uint16_t *src); +int agile_modbus_deserialize_write_registers(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_mask_write_register(agile_modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); +int agile_modbus_deserialize_mask_write_register(agile_modbus_t *ctx, int msg_length); +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 agile_modbus_deserialize_write_and_read_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest); +int agile_modbus_serialize_report_slave_id(agile_modbus_t *ctx); +int agile_modbus_deserialize_report_slave_id(agile_modbus_t *ctx, int msg_length, int max_dest, uint8_t *dest); +/** + * @} + */ + +/** @addtogroup Master_Raw_Operation_Functions + * @{ + */ +int agile_modbus_serialize_raw_request(agile_modbus_t *ctx, const uint8_t *raw_req, int raw_req_length); +int agile_modbus_deserialize_raw_response(agile_modbus_t *ctx, int msg_length); +/** + * @} + */ + +/** + * @} + */ + +/** @addtogroup Modbus_Slave + * @{ + */ + +/** @addtogroup Slave_Operation_Functions + * @{ + */ +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); +void agile_modbus_slave_io_set(uint8_t *buf, int index, int status); +uint8_t agile_modbus_slave_io_get(uint8_t *buf, int index); +void agile_modbus_slave_register_set(uint8_t *buf, int index, uint16_t data); +uint16_t agile_modbus_slave_register_get(uint8_t *buf, int index); +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/* Include RTU and TCP module */ +#include "agile_modbus_rtu.h" +#include "agile_modbus_tcp.h" + +#ifdef __cplusplus +} +#endif + +#endif /* __PKG_AGILE_MODBUS_H */ diff --git a/package/modbus/agile_modbus_rtu.c b/package/modbus/agile_modbus_rtu.c new file mode 100644 index 000000000..95b24cf0a --- /dev/null +++ b/package/modbus/agile_modbus_rtu.c @@ -0,0 +1,291 @@ +/** + * @file agile_modbus_rtu.c + * @brief Agile Modbus 软件包 RTU 源文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#include "agile_modbus.h" +#include "agile_modbus_rtu.h" + +/** @defgroup RTU RTU + * @{ + */ + +/** @defgroup RTU_Private_Constants RTU Private Constants + * @{ + */ +/** Table of CRC values for high-order byte */ +static const uint8_t _table_crc_hi[] = + { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40}; + +/** Table of CRC values for low-order byte */ +static const uint8_t _table_crc_lo[] = + { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, + 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, + 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, + 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, + 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, + 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, + 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, + 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, + 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, + 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, + 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, + 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, + 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, + 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, + 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, + 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, + 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, + 0x43, 0x83, 0x41, 0x81, 0x80, 0x40}; +/** + * @} + */ + +/** @defgroup RTU_Private_Functions RTU Private Functions + * @{ + */ + +/** + * @brief RTU CRC16 计算 + * @param buffer 数据指针 + * @param buffer_length 数据长度 + * @return CRC16 值 + */ +static uint16_t agile_modbus_rtu_crc16(uint8_t *buffer, uint16_t buffer_length) +{ + uint8_t crc_hi = 0xFF; /* high CRC byte initialized */ + uint8_t crc_lo = 0xFF; /* low CRC byte initialized */ + unsigned int i; /* will index into CRC lookup */ + + /* pass through message buffer */ + while (buffer_length--) { + i = crc_hi ^ *buffer++; /* calculate the CRC */ + crc_hi = crc_lo ^ _table_crc_hi[i]; + crc_lo = _table_crc_lo[i]; + } + + return (crc_hi << 8 | crc_lo); +} + +/** + * @brief RTU 设置地址接口 + * @param ctx modbus 句柄 + * @param slave 从机地址 + * @return 0:成功 + */ +static int agile_modbus_rtu_set_slave(agile_modbus_t *ctx, int slave) +{ + ctx->slave = slave; + return 0; +} + +/** + * @brief RTU 构建基础请求报文接口(头部报文) + * @param ctx modbus 句柄 + * @param function 功能码 + * @param addr 寄存器地址 + * @param nb 寄存器数目 + * @param req 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_rtu_build_request_basis(agile_modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + req[0] = ctx->slave; + req[1] = function; + req[2] = addr >> 8; + req[3] = addr & 0x00ff; + req[4] = nb >> 8; + req[5] = nb & 0x00ff; + + return AGILE_MODBUS_RTU_PRESET_REQ_LENGTH; +} + +/** + * @brief RTU 构建基础响应报文接口(头部报文) + * @param sft modbus 头部参数结构体指针 + * @param rsp 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_rtu_build_response_basis(agile_modbus_sft_t *sft, uint8_t *rsp) +{ + rsp[0] = sft->slave; + rsp[1] = sft->function; + + return AGILE_MODBUS_RTU_PRESET_RSP_LENGTH; +} + +/** + * @brief RTU 准备响应接口 + * @note 该 API 会将 req_length 自动减去 AGILE_MODBUS_RTU_CHECKSUM_LENGTH 长度 + * @param req 请求数据指针 + * @param req_length 请求数据长度 + * @return 0 (RTU 没有事务标识符) + */ +static int agile_modbus_rtu_prepare_response_tid(const uint8_t *req, int *req_length) +{ + (*req_length) -= AGILE_MODBUS_RTU_CHECKSUM_LENGTH; + /* No TID */ + return 0; +} + +/** + * @brief RTU 预发送数据接口 + * @note 该 API 会计算 CRC16 并自动填入尾部 + * @param req 数据存放指针 + * @param req_length 已有数据长度 + * @return 数据长度 + */ +static int agile_modbus_rtu_send_msg_pre(uint8_t *req, int req_length) +{ + uint16_t crc = agile_modbus_rtu_crc16(req, req_length); + req[req_length++] = crc >> 8; + req[req_length++] = crc & 0x00FF; + + return req_length; +} + +/** + * @brief RTU 检查接收数据完整性接口(CRC16 对比) + * @param ctx modbus 句柄 + * @param msg 接收数据指针 + * @param msg_length 有效数据长度 + * @return >0:有效数据长度; 其他:异常 + */ +static int agile_modbus_rtu_check_integrity(agile_modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + uint16_t crc_calculated; + uint16_t crc_received; + + crc_calculated = agile_modbus_rtu_crc16(msg, msg_length - 2); + crc_received = (msg[msg_length - 2] << 8) | msg[msg_length - 1]; + + /* Check CRC of msg */ + if (crc_calculated == crc_received) + return msg_length; + + return -1; +} + +/** + * @brief RTU 预检查确认接口(请求响应地址对比) + * @note 如果请求地址是广播地址0,返回成功 + * @param ctx modbus 句柄 + * @param req 请求数据指针 + * @param rsp 响应数据指针 + * @param rsp_length 响应数据长度 + * @return 0:成功; 其他:异常 + */ +static int agile_modbus_rtu_pre_check_confirmation(agile_modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ + /* Check responding slave is the slave we requested (except for broacast + * request) */ + if (req[0] != rsp[0] && req[0] != AGILE_MODBUS_BROADCAST_ADDRESS) + return -1; + + return 0; +} + +/** + * @} + */ + +/** @addtogroup RTU_Private_Constants + * @{ + */ + +/** + * @brief RTU 后端接口 + */ +static const agile_modbus_backend_t agile_modbus_rtu_backend = + { + AGILE_MODBUS_BACKEND_TYPE_RTU, + AGILE_MODBUS_RTU_HEADER_LENGTH, + AGILE_MODBUS_RTU_CHECKSUM_LENGTH, + AGILE_MODBUS_RTU_MAX_ADU_LENGTH, + agile_modbus_rtu_set_slave, + agile_modbus_rtu_build_request_basis, + agile_modbus_rtu_build_response_basis, + agile_modbus_rtu_prepare_response_tid, + agile_modbus_rtu_send_msg_pre, + agile_modbus_rtu_check_integrity, + agile_modbus_rtu_pre_check_confirmation}; + +/** + * @} + */ + +/** @defgroup RTU_Exported_Functions RTU Exported Functions + * @{ + */ + +/** + * @brief RTU 初始化 + * @param ctx RTU 句柄 + * @param send_buf 发送缓冲区 + * @param send_bufsz 发送缓冲区大小 + * @param read_buf 接收缓冲区 + * @param read_bufsz 接收缓冲区大小 + * @return 0:成功 + */ +int agile_modbus_rtu_init(agile_modbus_rtu_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz) +{ + agile_modbus_common_init(&(ctx->_ctx), send_buf, send_bufsz, read_buf, read_bufsz); + ctx->_ctx.backend = &agile_modbus_rtu_backend; + ctx->_ctx.backend_data = ctx; + + return 0; +} + +/** + * @} + */ + +/** + * @} + */ diff --git a/package/modbus/agile_modbus_rtu.h b/package/modbus/agile_modbus_rtu.h new file mode 100644 index 000000000..062aa84a6 --- /dev/null +++ b/package/modbus/agile_modbus_rtu.h @@ -0,0 +1,79 @@ +/** + * @file agile_modbus_rtu.h + * @brief Agile Modbus 软件包 RTU 头文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#ifndef __PKG_AGILE_MODBUS_RTU_H +#define __PKG_AGILE_MODBUS_RTU_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @addtogroup RTU + * @{ + */ + +/** @defgroup RTU_Exported_Constants RTU Exported Constants + * @{ + */ +#define AGILE_MODBUS_RTU_HEADER_LENGTH 1 +#define AGILE_MODBUS_RTU_PRESET_REQ_LENGTH 6 +#define AGILE_MODBUS_RTU_PRESET_RSP_LENGTH 2 + +#define AGILE_MODBUS_RTU_CHECKSUM_LENGTH 2 + +/** + @verbatim + Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 + RS232 / RS485 ADU = 253 bytes + slave (1 byte) + CRC (2 bytes) = 256 bytes + + @endverbatim + */ +#define AGILE_MODBUS_RTU_MAX_ADU_LENGTH 256 +/** + * @} + */ + +/** @defgroup RTU_Exported_Types RTU Exported Types + * @{ + */ + +/** + * @brief RTU 结构体 + */ +typedef struct agile_modbus_rtu { + agile_modbus_t _ctx; /**< modbus 句柄 */ +} agile_modbus_rtu_t; + +/** + * @} + */ + +/** @addtogroup RTU_Exported_Functions + * @{ + */ +int agile_modbus_rtu_init(agile_modbus_rtu_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __PKG_AGILE_MODBUS_RTU_H */ diff --git a/package/modbus/agile_modbus_tcp.c b/package/modbus/agile_modbus_tcp.c new file mode 100644 index 000000000..478045994 --- /dev/null +++ b/package/modbus/agile_modbus_tcp.c @@ -0,0 +1,226 @@ +/** + * @file agile_modbus_tcp.c + * @brief Agile Modbus 软件包 TCP 源文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#include "agile_modbus.h" +#include "agile_modbus_tcp.h" + +/** @defgroup TCP TCP + * @{ + */ + +/** @defgroup TCP_Private_Functions TCP Private Functions + * @{ + */ + +/** + * @brief TCP 设置地址接口 + * @param ctx modbus 句柄 + * @param slave 从机地址 + * @return 0:成功 + */ +static int agile_modbus_tcp_set_slave(agile_modbus_t *ctx, int slave) +{ + ctx->slave = slave; + return 0; +} + +/** + * @brief TCP 构建基础请求报文接口(头部报文) + * @param ctx modbus 句柄 + * @param function 功能码 + * @param addr 寄存器地址 + * @param nb 寄存器数目 + * @param req 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_tcp_build_request_basis(agile_modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + agile_modbus_tcp_t *ctx_tcp = ctx->backend_data; + + /* Increase transaction ID */ + if (ctx_tcp->t_id < UINT16_MAX) + ctx_tcp->t_id++; + else + ctx_tcp->t_id = 0; + req[0] = ctx_tcp->t_id >> 8; + req[1] = ctx_tcp->t_id & 0x00ff; + + /* Protocol Modbus */ + req[2] = 0; + req[3] = 0; + + /* Length will be defined later by set_req_length_tcp at offsets 4 + and 5 */ + + req[6] = ctx->slave; + req[7] = function; + req[8] = addr >> 8; + req[9] = addr & 0x00ff; + req[10] = nb >> 8; + req[11] = nb & 0x00ff; + + return AGILE_MODBUS_TCP_PRESET_REQ_LENGTH; +} + +/** + * @brief TCP 构建基础响应报文接口(头部报文) + * @param sft modbus 头部参数结构体指针 + * @param rsp 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_tcp_build_response_basis(agile_modbus_sft_t *sft, uint8_t *rsp) +{ + /* Extract from MODBUS Messaging on TCP/IP Implementation + Guide V1.0b (page 23/46): + The transaction identifier is used to associate the future + response with the request. */ + rsp[0] = sft->t_id >> 8; + rsp[1] = sft->t_id & 0x00ff; + + /* Protocol Modbus */ + rsp[2] = 0; + rsp[3] = 0; + + /* Length will be set later by send_msg (4 and 5) */ + + /* The slave ID is copied from the indication */ + rsp[6] = sft->slave; + rsp[7] = sft->function; + + return AGILE_MODBUS_TCP_PRESET_RSP_LENGTH; +} + +/** + * @brief TCP 准备响应接口 + * @param req 请求数据指针 + * @param req_length 请求数据长度 + * @return 事务标识符 + */ +static int agile_modbus_tcp_prepare_response_tid(const uint8_t *req, int *req_length) +{ + return (req[0] << 8) + req[1]; +} + +/** + * @brief TCP 预发送数据接口(计算长度字段的值并存入) + * @param req 数据存放指针 + * @param req_length 已有数据长度 + * @return 数据长度 + */ +static int agile_modbus_tcp_send_msg_pre(uint8_t *req, int req_length) +{ + /* Substract the header length to the message length */ + int mbap_length = req_length - 6; + + req[4] = mbap_length >> 8; + req[5] = mbap_length & 0x00FF; + + return req_length; +} + +/** + * @brief TCP 检查接收数据完整性接口 + * @param ctx modbus 句柄 + * @param msg 接收数据指针 + * @param msg_length 有效数据长度 + * @return 有效数据长度 + */ +static int agile_modbus_tcp_check_integrity(agile_modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + return msg_length; +} + +/** + * @brief TCP 预检查确认接口(对比事务标识符和协议标识符) + * @param ctx modbus 句柄 + * @param req 请求数据指针 + * @param rsp 响应数据指针 + * @param rsp_length 响应数据长度 + * @return 0:成功; 其他:异常 + */ +static int agile_modbus_tcp_pre_check_confirmation(agile_modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ + /* Check transaction ID */ + if (req[0] != rsp[0] || req[1] != rsp[1]) + return -1; + + /* Check protocol ID */ + if (rsp[2] != 0x0 && rsp[3] != 0x0) + return -1; + + return 0; +} + +/** + * @} + */ + +/** @defgroup TCP_Private_Constants TCP Private Constants + * @{ + */ + +/** + * @brief TCP 后端接口 + */ +static const agile_modbus_backend_t agile_modbus_tcp_backend = + { + AGILE_MODBUS_BACKEND_TYPE_TCP, + AGILE_MODBUS_TCP_HEADER_LENGTH, + AGILE_MODBUS_TCP_CHECKSUM_LENGTH, + AGILE_MODBUS_TCP_MAX_ADU_LENGTH, + agile_modbus_tcp_set_slave, + agile_modbus_tcp_build_request_basis, + agile_modbus_tcp_build_response_basis, + agile_modbus_tcp_prepare_response_tid, + agile_modbus_tcp_send_msg_pre, + agile_modbus_tcp_check_integrity, + agile_modbus_tcp_pre_check_confirmation}; + +/** + * @} + */ + +/** @defgroup TCP_Exported_Functions TCP Exported Functions + * @{ + */ + +/** + * @brief TCP 初始化 + * @param ctx TCP 句柄 + * @param send_buf 发送缓冲区 + * @param send_bufsz 发送缓冲区大小 + * @param read_buf 接收缓冲区 + * @param read_bufsz 接收缓冲区大小 + * @return 0:成功 + */ +int agile_modbus_tcp_init(agile_modbus_tcp_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz) +{ + agile_modbus_common_init(&(ctx->_ctx), send_buf, send_bufsz, read_buf, read_bufsz); + ctx->_ctx.backend = &agile_modbus_tcp_backend; + ctx->_ctx.backend_data = ctx; + + ctx->t_id = 0; + + return 0; +} + +/** + * @} + */ + +/** + * @} + */ diff --git a/package/modbus/agile_modbus_tcp.h b/package/modbus/agile_modbus_tcp.h new file mode 100644 index 000000000..50475e073 --- /dev/null +++ b/package/modbus/agile_modbus_tcp.h @@ -0,0 +1,83 @@ +/** + * @file agile_modbus_tcp.h + * @brief Agile Modbus 软件包 TCP 头文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#ifndef __PKG_AGILE_MODBUS_TCP_H +#define __PKG_AGILE_MODBUS_TCP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @addtogroup TCP + * @{ + */ + +/** @defgroup TCP_Exported_Constants TCP Exported Constants + * @{ + */ +#define AGILE_MODBUS_TCP_HEADER_LENGTH 7 +#define AGILE_MODBUS_TCP_PRESET_REQ_LENGTH 12 +#define AGILE_MODBUS_TCP_PRESET_RSP_LENGTH 8 + +#define AGILE_MODBUS_TCP_CHECKSUM_LENGTH 0 + +/** + @verbatim + Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 + TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes + + @endverbatim + */ +#define AGILE_MODBUS_TCP_MAX_ADU_LENGTH 260 +/** + * @} + */ + +/** @defgroup TCP_Exported_Types TCP Exported Types + * @{ + */ + +/** + * @brief TCP 结构体 + */ +typedef struct agile_modbus_tcp { + agile_modbus_t _ctx; /**< modbus 句柄 */ + uint16_t t_id; /**< Extract from MODBUS Messaging on TCP/IP Implementation Guide V1.0b + (page 23/46): + The transaction identifier is used to associate the future response + with the request. This identifier is unique on each TCP connection. */ +} agile_modbus_tcp_t; + +/** + * @} + */ + +/** @addtogroup TCP_Exported_Functions + * @{ + */ +int agile_modbus_tcp_init(agile_modbus_tcp_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/package/modbus/modbus.py b/package/modbus/modbus.py new file mode 100644 index 000000000..7a58f56ab --- /dev/null +++ b/package/modbus/modbus.py @@ -0,0 +1,5 @@ +import _modbus + + +class ModBus(_modbus._Modbus): + ... diff --git a/port/linux/.vscode/settings.json b/port/linux/.vscode/settings.json index f9eaf5834..599788e7a 100644 --- a/port/linux/.vscode/settings.json +++ b/port/linux/.vscode/settings.json @@ -60,6 +60,10 @@ "streambuf": "c", "tuple": "c", "typeinfo": "c", - "variant": "c" + "variant": "c", + "_modbus__modbus.h": "c", + "agile_modbus.h": "c", + "_modbus__modbusrtu.h": "c", + "_modbus__modbustcp.h": "c" } } \ No newline at end of file diff --git a/port/linux/package/pikascript/_modbus.pyi b/port/linux/package/pikascript/_modbus.pyi new file mode 100644 index 000000000..5334fd719 --- /dev/null +++ b/port/linux/package/pikascript/_modbus.pyi @@ -0,0 +1,31 @@ +class _ModBus: + def setSlave(self, slave: int): ... + def serializeReadBits(self, addr: int, nb: int) -> int: ... + def deserializeReadBits(self, msgLength: int, dest: bytes) -> int: ... + def serializeReadInputBits(self, addr: int, nb: int) -> int: ... + def deserializeReadInputBits(self, msgLength: int, dest: bytes) -> int: ... + def serializeReadRegisters(self, addr: int, nb: int) -> int: ... + def deserializeReadRegisters(self, msgLength: int, dest: bytes) -> int: ... + def serializeReadInputRegisters(self, addr: int, nb: int) -> int: ... + def deserializeReadInputRegisters(self, msgLength: int, dest: bytes) -> int: ... + def serializeWriteBit(self, addr: int, status: int) -> int: ... + def deserializeWriteBit(self, msgLength: int) -> int: ... + def serializeWriteRegister(self, addr: int, value: int) -> int: ... + def deserializeWriteRegister(self, msgLength: int) -> int: ... + def serializeWriteBits(self, addr: int, nb: int, src: bytes) -> int: ... + def deserializeWriteBits(self, msgLength: int) -> int: ... + def serializeWriteRegisters(self, addr: int, nb: int, src: bytes) -> int: ... + def deserializeWriteRegisters(self, msgLength: int) -> int: ... + def serializeMaskWriteRegister(self, addr: int, andMask: int, orMask: int) -> int: ... + def deserializeMaskWriteRegister(self, msgLength: int) -> int: ... + def serializeWriteAndReadRegisters(self, writeAddr: int, writeNb: int, src: bytes, readAddr: int, readNb: int) -> int: ... + def deserializeWriteAndReadRegisters(self, msgLength: int, dest: bytes) -> int: ... + def serializeReportSlaveId(self) -> int: ... + def deserializeReportSlaveId(self, msgLength: int, maxDest: int, dest: bytes) -> int: ... + +class _ModBusRTU(_ModBus): + def __init__(self, sendBuffSize: int, readBuffSize: int): ... + + +class _ModBusTCP(_ModBus): + def __init__(self, sendBuffSize: int, readBuffSize: int): ... diff --git a/port/linux/package/pikascript/main.py b/port/linux/package/pikascript/main.py index e4bf98f5f..cf1fbe176 100644 --- a/port/linux/package/pikascript/main.py +++ b/port/linux/package/pikascript/main.py @@ -20,6 +20,8 @@ import socket import random import re import PikaNN +import modbus + mem = PikaStdLib.MemChecker() print('hello pikascript!') print('mem used max:') diff --git a/port/linux/package/pikascript/modbus.py b/port/linux/package/pikascript/modbus.py new file mode 100644 index 000000000..7a58f56ab --- /dev/null +++ b/port/linux/package/pikascript/modbus.py @@ -0,0 +1,5 @@ +import _modbus + + +class ModBus(_modbus._Modbus): + ... diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/LICENSE b/port/linux/package/pikascript/pikascript-lib/modbus/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/_modbus.c b/port/linux/package/pikascript/pikascript-lib/modbus/_modbus.c new file mode 100644 index 000000000..4694e03de --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/_modbus.c @@ -0,0 +1,183 @@ +#include "_modbus__ModBus.h" +#include "_modbus__ModBusRTU.h" +#include "_modbus__ModBusTCP.h" +#include "agile_modbus.h" + +void _modbus__ModBusRTU___init__(PikaObj* self, + int sendBUffSize, + int readBuffSize) { + agile_modbus_rtu_t ctx_rtu = {0}; + agile_modbus_t* ctx = &ctx_rtu._ctx; + obj_setBytes(self, "sendBuff", NULL, sendBUffSize); + obj_setBytes(self, "readBuff", NULL, readBuffSize); + agile_modbus_rtu_init(&ctx_rtu, obj_getBytes(self, "sendBuff"), + sendBUffSize, obj_getBytes(self, "readBuff"), + readBuffSize); + obj_setStruct(self, "ctx_rtu", ctx_rtu); + obj_setPtr(self, "ctx", ctx); +} + +void _modbus__ModBus_setSlave(PikaObj* self, int slave) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + agile_modbus_set_slave(ctx, slave); +} + +void _modbus__ModBusTCP___init__(PikaObj* self, + int sendBuffSize, + int readBuffSize) { + agile_modbus_tcp_t ctx_tcp = {0}; + agile_modbus_t* ctx = &ctx_tcp._ctx; + obj_setBytes(self, "sendBuff", NULL, sendBuffSize); + obj_setBytes(self, "readBuff", NULL, readBuffSize); + agile_modbus_tcp_init(&ctx_tcp, obj_getBytes(self, "sendBuff"), + sendBuffSize, obj_getBytes(self, "readBuff"), + readBuffSize); + obj_setStruct(self, "ctx_tcp", ctx_tcp); + obj_setPtr(self, "ctx", ctx); +} + +int _modbus__ModBus_deserializeMaskWriteRegister(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_mask_write_register(ctx, msgLength); +} + +int _modbus__ModBus_deserializeReadBits(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_bits(ctx, msgLength, dest); +} + +int _modbus__ModBus_deserializeReadInputBits(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_input_bits(ctx, msgLength, dest); +} + +int _modbus__ModBus_deserializeReadInputRegisters(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_input_registers(ctx, msgLength, + (uint16_t*)dest); +} + +int _modbus__ModBus_deserializeReadRegisters(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_read_registers(ctx, msgLength, + (uint16_t*)dest); +} + +int _modbus__ModBus_deserializeReportSlaveId(PikaObj* self, + int msgLength, + int maxDest, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_report_slave_id(ctx, msgLength, maxDest, + dest); +} + +int _modbus__ModBus_deserializeWriteAndReadRegisters(PikaObj* self, + int msgLength, + uint8_t* dest) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_and_read_registers(ctx, msgLength, + (uint16_t*)dest); +} + +int _modbus__ModBus_deserializeWriteBit(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_bit(ctx, msgLength); +} + +int _modbus__ModBus_deserializeWriteBits(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_bits(ctx, msgLength); +} + +int _modbus__ModBus_deserializeWriteRegister(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_register(ctx, msgLength); +} + +int _modbus__ModBus_deserializeWriteRegisters(PikaObj* self, int msgLength) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_deserialize_write_registers(ctx, msgLength); +} + +int _modbus__ModBus_serializeMaskWriteRegister(PikaObj* self, + int addr, + int andMask, + int orMask) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_mask_write_register(ctx, addr, andMask, + orMask); +} + +int _modbus__ModBus_serializeReadBits(PikaObj* self, int addr, int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_bits(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReadInputBits(PikaObj* self, int addr, int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_input_bits(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReadInputRegisters(PikaObj* self, + int addr, + int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_input_registers(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReadRegisters(PikaObj* self, int addr, int nb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_read_registers(ctx, addr, nb); +} + +int _modbus__ModBus_serializeReportSlaveId(PikaObj* self) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_report_slave_id(ctx); +} + +int _modbus__ModBus_serializeWriteAndReadRegisters(PikaObj* self, + int writeAddr, + int writeNb, + uint8_t* src, + int readAddr, + int readNb) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_and_read_registers( + ctx, writeAddr, writeNb, (uint16_t*)src, readAddr, readNb); +} + +int _modbus__ModBus_serializeWriteBit(PikaObj* self, int addr, int status) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_bit(ctx, addr, status); +} + +int _modbus__ModBus_serializeWriteBits(PikaObj* self, + int addr, + int nb, + uint8_t* src) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_bits(ctx, addr, nb, src); +} + +int _modbus__ModBus_serializeWriteRegister(PikaObj* self, int addr, int value) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_register(ctx, addr, value); +} + +int _modbus__ModBus_serializeWriteRegisters(PikaObj* self, + int addr, + int nb, + uint8_t* src) { + agile_modbus_t* ctx = obj_getPtr(self, "ctx"); + return agile_modbus_serialize_write_registers(ctx, addr, nb, + (uint16_t*)src); +} diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.c b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.c new file mode 100644 index 000000000..81ff40424 --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.c @@ -0,0 +1,1519 @@ +/** + * @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; +} + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.h b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.h new file mode 100644 index 000000000..4be7429b5 --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus.h @@ -0,0 +1,359 @@ +/** + * @file agile_modbus.h + * @brief Agile Modbus 软件包通用头文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2022-07-28 + * + * @attention + * + *

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

+ * + */ + +#ifndef __PKG_AGILE_MODBUS_H +#define __PKG_AGILE_MODBUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @addtogroup COMMON + * @{ + */ + +/** @defgroup COMMON_Exported_Constants Common Exported Constants + * @{ + */ + +/** @defgroup Modbus_Function_Codes Modbus Function Codes + * @{ + */ +#define AGILE_MODBUS_FC_READ_COILS 0x01 +#define AGILE_MODBUS_FC_READ_DISCRETE_INPUTS 0x02 +#define AGILE_MODBUS_FC_READ_HOLDING_REGISTERS 0x03 +#define AGILE_MODBUS_FC_READ_INPUT_REGISTERS 0x04 +#define AGILE_MODBUS_FC_WRITE_SINGLE_COIL 0x05 +#define AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 +#define AGILE_MODBUS_FC_READ_EXCEPTION_STATUS 0x07 +#define AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F +#define AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 +#define AGILE_MODBUS_FC_REPORT_SLAVE_ID 0x11 +#define AGILE_MODBUS_FC_MASK_WRITE_REGISTER 0x16 +#define AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17 +/** + * @} + */ + +/** @defgroup Modbus_Constants Modbus Constants + * @{ + */ +#define AGILE_MODBUS_VERSION_STRING "AMB_1.1.0" /**< Agile Modbus 版本号 */ + +#define AGILE_MODBUS_BROADCAST_ADDRESS 0 /**< Modbus 广播地址 */ + +/** @name Quantity limit of Coils + @verbatim + Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 1 page 12) + Quantity of Coils to read (2 bytes): 1 to 2000 (0x7D0) + (chapter 6 section 11 page 29) + Quantity of Coils to write (2 bytes): 1 to 1968 (0x7B0) + + @endverbatim + * @{ + */ +#define AGILE_MODBUS_MAX_READ_BITS 2000 +#define AGILE_MODBUS_MAX_WRITE_BITS 1968 +/** + * @} + */ + +/** @name Quantity limit of Registers + @verbatim + Modbus_Application_Protocol_V1_1b.pdf (chapter 6 section 3 page 15) + Quantity of Registers to read (2 bytes): 1 to 125 (0x7D) + (chapter 6 section 12 page 31) + Quantity of Registers to write (2 bytes) 1 to 123 (0x7B) + (chapter 6 section 17 page 38) + Quantity of Registers to write in R/W registers (2 bytes) 1 to 121 (0x79) + + @endverbatim + * @{ + */ +#define AGILE_MODBUS_MAX_READ_REGISTERS 125 +#define AGILE_MODBUS_MAX_WRITE_REGISTERS 123 +#define AGILE_MODBUS_MAX_WR_WRITE_REGISTERS 121 +#define AGILE_MODBUS_MAX_WR_READ_REGISTERS 125 +/** + * @} + */ + +/** + @verbatim + The size of the MODBUS PDU is limited by the size constraint inherited from + the first MODBUS implementation on Serial Line network (max. RS485 ADU = 256 + bytes). Therefore, MODBUS PDU for serial line communication = 256 - Server + address (1 byte) - CRC (2 bytes) = 253 bytes. + + @endverbatim + */ +#define AGILE_MODBUS_MAX_PDU_LENGTH 253 + +/** + @verbatim + Consequently: + - RTU MODBUS ADU = 253 bytes + Server address (1 byte) + CRC (2 bytes) = 256 + bytes. + - TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes. + so the maximum of both backend in 260 bytes. This size can used to allocate + an array of bytes to store responses and it will be compatible with the two + backends. + + @endverbatim + */ +#define AGILE_MODBUS_MAX_ADU_LENGTH 260 +/** + * @} + */ + +/** + * @} + */ + +/** @defgroup COMMON_Exported_Types Common Exported Types + * @{ + */ + +/** + * @brief Modbus 异常码 + */ +enum { + AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01, + AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + AGILE_MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + AGILE_MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE, + AGILE_MODBUS_EXCEPTION_ACKNOWLEDGE, + AGILE_MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY, + AGILE_MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE, + AGILE_MODBUS_EXCEPTION_MEMORY_PARITY, + AGILE_MODBUS_EXCEPTION_NOT_DEFINED, + AGILE_MODBUS_EXCEPTION_GATEWAY_PATH, + AGILE_MODBUS_EXCEPTION_GATEWAY_TARGET, + AGILE_MODBUS_EXCEPTION_UNKNOW = 0xff +}; + +/** + * @brief Modbus 后端类型 + */ +typedef enum { + AGILE_MODBUS_BACKEND_TYPE_RTU = 0, /**< RTU */ + AGILE_MODBUS_BACKEND_TYPE_TCP /**< TCP */ +} agile_modbus_backend_type_t; + +/** + * @brief Modbus 收到消息类型 + * + @verbatim + ---------- Request Indication ---------- + | Client | ---------------------->| Server | + ---------- Confirmation Response ---------- + + @endverbatim + */ +typedef enum { + AGILE_MODBUS_MSG_INDICATION, /**< 主机端的请求消息 */ + AGILE_MODBUS_MSG_CONFIRMATION /**< 服务器端的请求消息 */ +} agile_modbus_msg_type_t; + +/** + * @brief 包含 modbus 头部参数结构体 + */ +typedef struct agile_modbus_sft { + int slave; /**< 从机地址 */ + int function; /**< 功能码 */ + int t_id; /**< 事务标识符 */ +} agile_modbus_sft_t; + +typedef struct agile_modbus agile_modbus_t; /**< Agile Modbus 结构体 */ + +/** + * @brief Agile Modbus 后端接口结构体 + */ +typedef struct agile_modbus_backend { + uint32_t backend_type; /**< 后端类型 */ + uint32_t header_length; /**< 头部长度,不包含功能码 */ + uint32_t checksum_length; /**< 校验数据长度 */ + uint32_t max_adu_length; /**< 后端 ADU 长度 */ + int (*set_slave)(agile_modbus_t *ctx, int slave); /**< 设置地址接口 */ + int (*build_request_basis)(agile_modbus_t *ctx, int function, int addr, + int nb, uint8_t *req); /**< 构建基础请求报文接口 */ + int (*build_response_basis)(agile_modbus_sft_t *sft, uint8_t *rsp); /**< 构建基础响应报文接口 */ + int (*prepare_response_tid)(const uint8_t *req, int *req_length); /**< 准备响应接口 */ + int (*send_msg_pre)(uint8_t *req, int req_length); /**< 预发送数据接口 */ + int (*check_integrity)(agile_modbus_t *ctx, uint8_t *msg, const int msg_length); /**< 检查接收数据完整性接口 */ + int (*pre_check_confirmation)(agile_modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length); /**< 预检查确认接口 */ +} agile_modbus_backend_t; + +/** + * @brief Agile Modbus 结构体 + */ +struct agile_modbus { + int slave; /**< 从机地址 */ + uint8_t *send_buf; /**< 发送缓冲区 */ + int send_bufsz; /**< 发送缓冲区大小 */ + uint8_t *read_buf; /**< 接收缓冲区 */ + int read_bufsz; /**< 接收缓冲区大小 */ + uint8_t (*compute_meta_length_after_function)(agile_modbus_t *ctx, int function, + agile_modbus_msg_type_t msg_type); /**< 自定义计算数据元长度接口 */ + int (*compute_data_length_after_meta)(agile_modbus_t *ctx, uint8_t *msg, + int msg_length, agile_modbus_msg_type_t msg_type); /**< 自定义计算数据长度接口 */ + const agile_modbus_backend_t *backend; /**< 后端接口 */ + void *backend_data; /**< 后端数据,指向 RTU 或 TCP 结构体 */ +}; + +/** + * @} + */ + +/** @addtogroup Modbus_Slave + * @{ + */ + +/** @defgroup Slave_Exported_Types Slave Exported Types + * @{ + */ + +/** + * @brief Agile Modbus 从机信息结构体 + */ +struct agile_modbus_slave_info { + agile_modbus_sft_t *sft; /**< sft 结构体指针 */ + int *rsp_length; /**< 响应数据长度指针 */ + int address; /**< 寄存器地址 */ + int nb; /**< 数目 */ + uint8_t *buf; /**< 不同功能码需要使用的数据域 */ + int send_index; /**< 发送缓冲区当前索引 */ +}; + +/** + * @brief 从机回调函数 + * @param ctx modbus 句柄 + * @param slave_info 从机信息体 + * @param data 私有数据 + * @return =0:正常; + * <0:异常 + * (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): 未知异常,从机不会打包响应数据) + * (其他负数异常码: 从机会打包异常响应数据) + */ +typedef int (*agile_modbus_slave_callback_t)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info, const void *data); + +/** + * @} + */ + +/** + * @} + */ + +/** @addtogroup COMMON_Exported_Functions + * @{ + */ +void agile_modbus_common_init(agile_modbus_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); +int agile_modbus_set_slave(agile_modbus_t *ctx, int slave); +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)); +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)); +int agile_modbus_receive_judge(agile_modbus_t *ctx, int msg_length, agile_modbus_msg_type_t msg_type); +/** + * @} + */ + +/** @addtogroup Modbus_Master + * @{ + */ + +/** @addtogroup Master_Common_Operation_Functions + * @{ + */ +int agile_modbus_serialize_read_bits(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_bits(agile_modbus_t *ctx, int msg_length, uint8_t *dest); +int agile_modbus_serialize_read_input_bits(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_input_bits(agile_modbus_t *ctx, int msg_length, uint8_t *dest); +int agile_modbus_serialize_read_registers(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest); +int agile_modbus_serialize_read_input_registers(agile_modbus_t *ctx, int addr, int nb); +int agile_modbus_deserialize_read_input_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest); +int agile_modbus_serialize_write_bit(agile_modbus_t *ctx, int addr, int status); +int agile_modbus_deserialize_write_bit(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_write_register(agile_modbus_t *ctx, int addr, const uint16_t value); +int agile_modbus_deserialize_write_register(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_write_bits(agile_modbus_t *ctx, int addr, int nb, const uint8_t *src); +int agile_modbus_deserialize_write_bits(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_write_registers(agile_modbus_t *ctx, int addr, int nb, const uint16_t *src); +int agile_modbus_deserialize_write_registers(agile_modbus_t *ctx, int msg_length); +int agile_modbus_serialize_mask_write_register(agile_modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); +int agile_modbus_deserialize_mask_write_register(agile_modbus_t *ctx, int msg_length); +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 agile_modbus_deserialize_write_and_read_registers(agile_modbus_t *ctx, int msg_length, uint16_t *dest); +int agile_modbus_serialize_report_slave_id(agile_modbus_t *ctx); +int agile_modbus_deserialize_report_slave_id(agile_modbus_t *ctx, int msg_length, int max_dest, uint8_t *dest); +/** + * @} + */ + +/** @addtogroup Master_Raw_Operation_Functions + * @{ + */ +int agile_modbus_serialize_raw_request(agile_modbus_t *ctx, const uint8_t *raw_req, int raw_req_length); +int agile_modbus_deserialize_raw_response(agile_modbus_t *ctx, int msg_length); +/** + * @} + */ + +/** + * @} + */ + +/** @addtogroup Modbus_Slave + * @{ + */ + +/** @addtogroup Slave_Operation_Functions + * @{ + */ +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); +void agile_modbus_slave_io_set(uint8_t *buf, int index, int status); +uint8_t agile_modbus_slave_io_get(uint8_t *buf, int index); +void agile_modbus_slave_register_set(uint8_t *buf, int index, uint16_t data); +uint16_t agile_modbus_slave_register_get(uint8_t *buf, int index); +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +/* Include RTU and TCP module */ +#include "agile_modbus_rtu.h" +#include "agile_modbus_tcp.h" + +#ifdef __cplusplus +} +#endif + +#endif /* __PKG_AGILE_MODBUS_H */ diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.c b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.c new file mode 100644 index 000000000..95b24cf0a --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.c @@ -0,0 +1,291 @@ +/** + * @file agile_modbus_rtu.c + * @brief Agile Modbus 软件包 RTU 源文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#include "agile_modbus.h" +#include "agile_modbus_rtu.h" + +/** @defgroup RTU RTU + * @{ + */ + +/** @defgroup RTU_Private_Constants RTU Private Constants + * @{ + */ +/** Table of CRC values for high-order byte */ +static const uint8_t _table_crc_hi[] = + { + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, + 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, + 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, + 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, + 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40}; + +/** Table of CRC values for low-order byte */ +static const uint8_t _table_crc_lo[] = + { + 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, + 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, + 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, + 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, + 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, + 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, + 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, + 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, + 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, + 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, + 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, + 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, + 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, + 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, + 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, + 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, + 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, + 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, + 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, + 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, + 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, + 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, + 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, + 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, + 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, + 0x43, 0x83, 0x41, 0x81, 0x80, 0x40}; +/** + * @} + */ + +/** @defgroup RTU_Private_Functions RTU Private Functions + * @{ + */ + +/** + * @brief RTU CRC16 计算 + * @param buffer 数据指针 + * @param buffer_length 数据长度 + * @return CRC16 值 + */ +static uint16_t agile_modbus_rtu_crc16(uint8_t *buffer, uint16_t buffer_length) +{ + uint8_t crc_hi = 0xFF; /* high CRC byte initialized */ + uint8_t crc_lo = 0xFF; /* low CRC byte initialized */ + unsigned int i; /* will index into CRC lookup */ + + /* pass through message buffer */ + while (buffer_length--) { + i = crc_hi ^ *buffer++; /* calculate the CRC */ + crc_hi = crc_lo ^ _table_crc_hi[i]; + crc_lo = _table_crc_lo[i]; + } + + return (crc_hi << 8 | crc_lo); +} + +/** + * @brief RTU 设置地址接口 + * @param ctx modbus 句柄 + * @param slave 从机地址 + * @return 0:成功 + */ +static int agile_modbus_rtu_set_slave(agile_modbus_t *ctx, int slave) +{ + ctx->slave = slave; + return 0; +} + +/** + * @brief RTU 构建基础请求报文接口(头部报文) + * @param ctx modbus 句柄 + * @param function 功能码 + * @param addr 寄存器地址 + * @param nb 寄存器数目 + * @param req 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_rtu_build_request_basis(agile_modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + req[0] = ctx->slave; + req[1] = function; + req[2] = addr >> 8; + req[3] = addr & 0x00ff; + req[4] = nb >> 8; + req[5] = nb & 0x00ff; + + return AGILE_MODBUS_RTU_PRESET_REQ_LENGTH; +} + +/** + * @brief RTU 构建基础响应报文接口(头部报文) + * @param sft modbus 头部参数结构体指针 + * @param rsp 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_rtu_build_response_basis(agile_modbus_sft_t *sft, uint8_t *rsp) +{ + rsp[0] = sft->slave; + rsp[1] = sft->function; + + return AGILE_MODBUS_RTU_PRESET_RSP_LENGTH; +} + +/** + * @brief RTU 准备响应接口 + * @note 该 API 会将 req_length 自动减去 AGILE_MODBUS_RTU_CHECKSUM_LENGTH 长度 + * @param req 请求数据指针 + * @param req_length 请求数据长度 + * @return 0 (RTU 没有事务标识符) + */ +static int agile_modbus_rtu_prepare_response_tid(const uint8_t *req, int *req_length) +{ + (*req_length) -= AGILE_MODBUS_RTU_CHECKSUM_LENGTH; + /* No TID */ + return 0; +} + +/** + * @brief RTU 预发送数据接口 + * @note 该 API 会计算 CRC16 并自动填入尾部 + * @param req 数据存放指针 + * @param req_length 已有数据长度 + * @return 数据长度 + */ +static int agile_modbus_rtu_send_msg_pre(uint8_t *req, int req_length) +{ + uint16_t crc = agile_modbus_rtu_crc16(req, req_length); + req[req_length++] = crc >> 8; + req[req_length++] = crc & 0x00FF; + + return req_length; +} + +/** + * @brief RTU 检查接收数据完整性接口(CRC16 对比) + * @param ctx modbus 句柄 + * @param msg 接收数据指针 + * @param msg_length 有效数据长度 + * @return >0:有效数据长度; 其他:异常 + */ +static int agile_modbus_rtu_check_integrity(agile_modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + uint16_t crc_calculated; + uint16_t crc_received; + + crc_calculated = agile_modbus_rtu_crc16(msg, msg_length - 2); + crc_received = (msg[msg_length - 2] << 8) | msg[msg_length - 1]; + + /* Check CRC of msg */ + if (crc_calculated == crc_received) + return msg_length; + + return -1; +} + +/** + * @brief RTU 预检查确认接口(请求响应地址对比) + * @note 如果请求地址是广播地址0,返回成功 + * @param ctx modbus 句柄 + * @param req 请求数据指针 + * @param rsp 响应数据指针 + * @param rsp_length 响应数据长度 + * @return 0:成功; 其他:异常 + */ +static int agile_modbus_rtu_pre_check_confirmation(agile_modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ + /* Check responding slave is the slave we requested (except for broacast + * request) */ + if (req[0] != rsp[0] && req[0] != AGILE_MODBUS_BROADCAST_ADDRESS) + return -1; + + return 0; +} + +/** + * @} + */ + +/** @addtogroup RTU_Private_Constants + * @{ + */ + +/** + * @brief RTU 后端接口 + */ +static const agile_modbus_backend_t agile_modbus_rtu_backend = + { + AGILE_MODBUS_BACKEND_TYPE_RTU, + AGILE_MODBUS_RTU_HEADER_LENGTH, + AGILE_MODBUS_RTU_CHECKSUM_LENGTH, + AGILE_MODBUS_RTU_MAX_ADU_LENGTH, + agile_modbus_rtu_set_slave, + agile_modbus_rtu_build_request_basis, + agile_modbus_rtu_build_response_basis, + agile_modbus_rtu_prepare_response_tid, + agile_modbus_rtu_send_msg_pre, + agile_modbus_rtu_check_integrity, + agile_modbus_rtu_pre_check_confirmation}; + +/** + * @} + */ + +/** @defgroup RTU_Exported_Functions RTU Exported Functions + * @{ + */ + +/** + * @brief RTU 初始化 + * @param ctx RTU 句柄 + * @param send_buf 发送缓冲区 + * @param send_bufsz 发送缓冲区大小 + * @param read_buf 接收缓冲区 + * @param read_bufsz 接收缓冲区大小 + * @return 0:成功 + */ +int agile_modbus_rtu_init(agile_modbus_rtu_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz) +{ + agile_modbus_common_init(&(ctx->_ctx), send_buf, send_bufsz, read_buf, read_bufsz); + ctx->_ctx.backend = &agile_modbus_rtu_backend; + ctx->_ctx.backend_data = ctx; + + return 0; +} + +/** + * @} + */ + +/** + * @} + */ diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.h b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.h new file mode 100644 index 000000000..062aa84a6 --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_rtu.h @@ -0,0 +1,79 @@ +/** + * @file agile_modbus_rtu.h + * @brief Agile Modbus 软件包 RTU 头文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#ifndef __PKG_AGILE_MODBUS_RTU_H +#define __PKG_AGILE_MODBUS_RTU_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @addtogroup RTU + * @{ + */ + +/** @defgroup RTU_Exported_Constants RTU Exported Constants + * @{ + */ +#define AGILE_MODBUS_RTU_HEADER_LENGTH 1 +#define AGILE_MODBUS_RTU_PRESET_REQ_LENGTH 6 +#define AGILE_MODBUS_RTU_PRESET_RSP_LENGTH 2 + +#define AGILE_MODBUS_RTU_CHECKSUM_LENGTH 2 + +/** + @verbatim + Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 + RS232 / RS485 ADU = 253 bytes + slave (1 byte) + CRC (2 bytes) = 256 bytes + + @endverbatim + */ +#define AGILE_MODBUS_RTU_MAX_ADU_LENGTH 256 +/** + * @} + */ + +/** @defgroup RTU_Exported_Types RTU Exported Types + * @{ + */ + +/** + * @brief RTU 结构体 + */ +typedef struct agile_modbus_rtu { + agile_modbus_t _ctx; /**< modbus 句柄 */ +} agile_modbus_rtu_t; + +/** + * @} + */ + +/** @addtogroup RTU_Exported_Functions + * @{ + */ +int agile_modbus_rtu_init(agile_modbus_rtu_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __PKG_AGILE_MODBUS_RTU_H */ diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.c b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.c new file mode 100644 index 000000000..478045994 --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.c @@ -0,0 +1,226 @@ +/** + * @file agile_modbus_tcp.c + * @brief Agile Modbus 软件包 TCP 源文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#include "agile_modbus.h" +#include "agile_modbus_tcp.h" + +/** @defgroup TCP TCP + * @{ + */ + +/** @defgroup TCP_Private_Functions TCP Private Functions + * @{ + */ + +/** + * @brief TCP 设置地址接口 + * @param ctx modbus 句柄 + * @param slave 从机地址 + * @return 0:成功 + */ +static int agile_modbus_tcp_set_slave(agile_modbus_t *ctx, int slave) +{ + ctx->slave = slave; + return 0; +} + +/** + * @brief TCP 构建基础请求报文接口(头部报文) + * @param ctx modbus 句柄 + * @param function 功能码 + * @param addr 寄存器地址 + * @param nb 寄存器数目 + * @param req 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_tcp_build_request_basis(agile_modbus_t *ctx, int function, + int addr, int nb, + uint8_t *req) +{ + agile_modbus_tcp_t *ctx_tcp = ctx->backend_data; + + /* Increase transaction ID */ + if (ctx_tcp->t_id < UINT16_MAX) + ctx_tcp->t_id++; + else + ctx_tcp->t_id = 0; + req[0] = ctx_tcp->t_id >> 8; + req[1] = ctx_tcp->t_id & 0x00ff; + + /* Protocol Modbus */ + req[2] = 0; + req[3] = 0; + + /* Length will be defined later by set_req_length_tcp at offsets 4 + and 5 */ + + req[6] = ctx->slave; + req[7] = function; + req[8] = addr >> 8; + req[9] = addr & 0x00ff; + req[10] = nb >> 8; + req[11] = nb & 0x00ff; + + return AGILE_MODBUS_TCP_PRESET_REQ_LENGTH; +} + +/** + * @brief TCP 构建基础响应报文接口(头部报文) + * @param sft modbus 头部参数结构体指针 + * @param rsp 数据存放指针 + * @return 数据长度 + */ +static int agile_modbus_tcp_build_response_basis(agile_modbus_sft_t *sft, uint8_t *rsp) +{ + /* Extract from MODBUS Messaging on TCP/IP Implementation + Guide V1.0b (page 23/46): + The transaction identifier is used to associate the future + response with the request. */ + rsp[0] = sft->t_id >> 8; + rsp[1] = sft->t_id & 0x00ff; + + /* Protocol Modbus */ + rsp[2] = 0; + rsp[3] = 0; + + /* Length will be set later by send_msg (4 and 5) */ + + /* The slave ID is copied from the indication */ + rsp[6] = sft->slave; + rsp[7] = sft->function; + + return AGILE_MODBUS_TCP_PRESET_RSP_LENGTH; +} + +/** + * @brief TCP 准备响应接口 + * @param req 请求数据指针 + * @param req_length 请求数据长度 + * @return 事务标识符 + */ +static int agile_modbus_tcp_prepare_response_tid(const uint8_t *req, int *req_length) +{ + return (req[0] << 8) + req[1]; +} + +/** + * @brief TCP 预发送数据接口(计算长度字段的值并存入) + * @param req 数据存放指针 + * @param req_length 已有数据长度 + * @return 数据长度 + */ +static int agile_modbus_tcp_send_msg_pre(uint8_t *req, int req_length) +{ + /* Substract the header length to the message length */ + int mbap_length = req_length - 6; + + req[4] = mbap_length >> 8; + req[5] = mbap_length & 0x00FF; + + return req_length; +} + +/** + * @brief TCP 检查接收数据完整性接口 + * @param ctx modbus 句柄 + * @param msg 接收数据指针 + * @param msg_length 有效数据长度 + * @return 有效数据长度 + */ +static int agile_modbus_tcp_check_integrity(agile_modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + return msg_length; +} + +/** + * @brief TCP 预检查确认接口(对比事务标识符和协议标识符) + * @param ctx modbus 句柄 + * @param req 请求数据指针 + * @param rsp 响应数据指针 + * @param rsp_length 响应数据长度 + * @return 0:成功; 其他:异常 + */ +static int agile_modbus_tcp_pre_check_confirmation(agile_modbus_t *ctx, const uint8_t *req, + const uint8_t *rsp, int rsp_length) +{ + /* Check transaction ID */ + if (req[0] != rsp[0] || req[1] != rsp[1]) + return -1; + + /* Check protocol ID */ + if (rsp[2] != 0x0 && rsp[3] != 0x0) + return -1; + + return 0; +} + +/** + * @} + */ + +/** @defgroup TCP_Private_Constants TCP Private Constants + * @{ + */ + +/** + * @brief TCP 后端接口 + */ +static const agile_modbus_backend_t agile_modbus_tcp_backend = + { + AGILE_MODBUS_BACKEND_TYPE_TCP, + AGILE_MODBUS_TCP_HEADER_LENGTH, + AGILE_MODBUS_TCP_CHECKSUM_LENGTH, + AGILE_MODBUS_TCP_MAX_ADU_LENGTH, + agile_modbus_tcp_set_slave, + agile_modbus_tcp_build_request_basis, + agile_modbus_tcp_build_response_basis, + agile_modbus_tcp_prepare_response_tid, + agile_modbus_tcp_send_msg_pre, + agile_modbus_tcp_check_integrity, + agile_modbus_tcp_pre_check_confirmation}; + +/** + * @} + */ + +/** @defgroup TCP_Exported_Functions TCP Exported Functions + * @{ + */ + +/** + * @brief TCP 初始化 + * @param ctx TCP 句柄 + * @param send_buf 发送缓冲区 + * @param send_bufsz 发送缓冲区大小 + * @param read_buf 接收缓冲区 + * @param read_bufsz 接收缓冲区大小 + * @return 0:成功 + */ +int agile_modbus_tcp_init(agile_modbus_tcp_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz) +{ + agile_modbus_common_init(&(ctx->_ctx), send_buf, send_bufsz, read_buf, read_bufsz); + ctx->_ctx.backend = &agile_modbus_tcp_backend; + ctx->_ctx.backend_data = ctx; + + ctx->t_id = 0; + + return 0; +} + +/** + * @} + */ + +/** + * @} + */ diff --git a/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.h b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.h new file mode 100644 index 000000000..50475e073 --- /dev/null +++ b/port/linux/package/pikascript/pikascript-lib/modbus/agile_modbus_tcp.h @@ -0,0 +1,83 @@ +/** + * @file agile_modbus_tcp.h + * @brief Agile Modbus 软件包 TCP 头文件 + * @author 马龙伟 (2544047213@qq.com) + * @date 2021-12-02 + * + * @attention + * + *

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

+ * + */ + +#ifndef __PKG_AGILE_MODBUS_TCP_H +#define __PKG_AGILE_MODBUS_TCP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @addtogroup TCP + * @{ + */ + +/** @defgroup TCP_Exported_Constants TCP Exported Constants + * @{ + */ +#define AGILE_MODBUS_TCP_HEADER_LENGTH 7 +#define AGILE_MODBUS_TCP_PRESET_REQ_LENGTH 12 +#define AGILE_MODBUS_TCP_PRESET_RSP_LENGTH 8 + +#define AGILE_MODBUS_TCP_CHECKSUM_LENGTH 0 + +/** + @verbatim + Modbus_Application_Protocol_V1_1b.pdf Chapter 4 Section 1 Page 5 + TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes + + @endverbatim + */ +#define AGILE_MODBUS_TCP_MAX_ADU_LENGTH 260 +/** + * @} + */ + +/** @defgroup TCP_Exported_Types TCP Exported Types + * @{ + */ + +/** + * @brief TCP 结构体 + */ +typedef struct agile_modbus_tcp { + agile_modbus_t _ctx; /**< modbus 句柄 */ + uint16_t t_id; /**< Extract from MODBUS Messaging on TCP/IP Implementation Guide V1.0b + (page 23/46): + The transaction identifier is used to associate the future response + with the request. This identifier is unique on each TCP connection. */ +} agile_modbus_tcp_t; + +/** + * @} + */ + +/** @addtogroup TCP_Exported_Functions + * @{ + */ +int agile_modbus_tcp_init(agile_modbus_tcp_t *ctx, uint8_t *send_buf, int send_bufsz, uint8_t *read_buf, int read_bufsz); +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif