diff --git a/release/src/makefile b/release/src/makefile index b981499..5cf63e6 100644 --- a/release/src/makefile +++ b/release/src/makefile @@ -1,4 +1,4 @@ -SUBDIRS := ./public +SUBDIRS := ./public ./protocol define make_subdir for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done endef diff --git a/release/src/protocol/libmodbus/makefile b/release/src/protocol/libmodbus/makefile new file mode 100644 index 0000000..1d89eda --- /dev/null +++ b/release/src/protocol/libmodbus/makefile @@ -0,0 +1,31 @@ +include ./../../../linux.mk +L := $(notdir $(realpath $(CURDIR)/..)) +M := libmodbus +O := $(LIB_REL)/$(M).a +S := $(SRC_ROOT_DIR)/$(L)/libmodbus/src +I := -I$(SRC_ROOT_DIR)/$(L)/libmodbus/inc -I$(SRC_ROOT_DIR)/public/liblog/inc +B := $(CURDIR)/$(M)/obj +SRCS := $(wildcard $(S)/*.c) +OBJS := $(patsubst $(S)/%.c, $(B)/%.o, $(SRCS)) +F := $(C_FLAGS) $(I) + +.PHONY: all +all: + @mkdir -p $(ROOT_DIR)/release/inc + @$(MAKE) $(O) + +$(O): $(OBJS) + @mkdir -p $(dir $@) + $(AR) rcs $@ $^ + @echo "[$(M)] built" + +$(B)/%.o: $(S)/%.c + @mkdir -p $(dir $@) + $(CC) $(F) -c $< -o $@ + +.PHONY: clean +clean: + rm -rf $(B) $(O) + +.PHONY: rebuild +rebuild: clean all diff --git a/release/src/protocol/makefile b/release/src/protocol/makefile index 75098b1..c78c1ec 100644 --- a/release/src/protocol/makefile +++ b/release/src/protocol/makefile @@ -1,7 +1,8 @@ -SUBDIRS := +SUBDIRS := ./libmodbus define make_subdir - @for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done + for d in $(SUBDIRS); do [ -f "$$d/makefile" ] && (cd "$$d" && make -f makefile $1) || true; done endef -.PHONY: all; all:; $(call make_subdir,all) -.PHONY: clean; clean:; $(call make_subdir,clean) -.PHONY: rebuild; rebuild: clean all +.PHONY: all clean rebuild +all:; @$(call make_subdir,all) +clean:; @$(call make_subdir,clean) +rebuild: clean all diff --git a/src/protocol/libmodbus/inc/config.h b/src/protocol/libmodbus/inc/config.h new file mode 100644 index 0000000..6b81ba9 --- /dev/null +++ b/src/protocol/libmodbus/inc/config.h @@ -0,0 +1,53 @@ +/* + * 平台适配配置文件 + * + * 替代 autotools 生成的 config.h,提供跨平台编译所需的宏定义。 + * 默认配置针对 Linux 平台,其他平台请根据实际情况修改以下宏定义。 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_CONFIG_H +#define MODBUS_CONFIG_H + +/* ================================================================ + * 版本信息(已硬编码,见 modbus-version.h) + * 此处定义供源文件中 extern 声明使用 + * ================================================================ */ +#define LIBMODBUS_VERSION_MAJOR 3 +#define LIBMODBUS_VERSION_MINOR 1 +#define LIBMODBUS_VERSION_MICRO 12 + +/* ================================================================ + * 平台功能检测开关 + * + * 以下宏定义控制条件编译行为。默认启用 Linux 常见功能。 + * 如果你的平台不支持某项功能,请注释掉对应的 #define。 + * ================================================================ */ + +/* 是否提供 termios2 结构体(Linux 4.x+ 支持,允许任意波特率) */ +#define HAVE_STRUCT_TERMIOS2 1 + +/* 是否声明了 TIOCSRS485 ioctl(Linux RS485 模式切换) */ +#define HAVE_DECL_TIOCSRS485 1 + +/* 是否声明了 TIOCM_RTS ioctl(Linux RTS 引脚控制) */ +#define HAVE_DECL_TIOCM_RTS 1 + +/* 是否提供 strlcpy 函数(OpenBSD/macOS 原生支持,Linux 需自行实现) */ +/* libmodbus 在 modbus.c 中提供了备用实现,因此这里通常不定义 */ +/* #define HAVE_STRLCPY 1 */ + +/* 是否提供 头文件 */ +#define HAVE_NETINET_IN_H 1 + +/* 是否提供 头文件 */ +#define HAVE_NETINET_IP_H 1 + +/* 是否提供 gai_strerror 函数(用于 getaddrinfo 错误信息) */ +#define HAVE_GAI_STRERROR 1 + +/* 是否提供 accept4 系统调用(Linux 2.6.28+) */ +#define HAVE_ACCEPT4 1 + +#endif /* MODBUS_CONFIG_H */ diff --git a/src/protocol/libmodbus/inc/modbus-private.h b/src/protocol/libmodbus/inc/modbus-private.h new file mode 100644 index 0000000..7a74515 --- /dev/null +++ b/src/protocol/libmodbus/inc/modbus-private.h @@ -0,0 +1,156 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_PRIVATE_H +#define MODBUS_PRIVATE_H + +// clang-format off +#include +#include +// clang-format on +#include +#include + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +/* + * 这并非真正的最小长度(真正的最小长度是 RTU 模式下的"报告从站 ID"消息,仅 4 字节), + * 但它是一个在 RTU 或 TCP 通信中读取多个值或写入单个值时非常方便使用的尺寸。 + * 取以下两者中的最大值: + * - HEADER_LENGTH_TCP (7) + 功能码 (1) + 地址 (2) + 数量 (2) + * - HEADER_LENGTH_RTU (1) + 功能码 (1) + 地址 (2) + 数量 (2) + CRC (2) + */ +#define _MIN_REQ_LENGTH 12 + +/* + * 报告从站 ID 的功能码子码。 + * 该值用于 modbus_report_slave_id() 函数内部,以区分标准的"报告从站 ID"请求 + * 与其他功能码。 + */ +#define _REPORT_SLAVE_ID 180 + +/* + * Modbus 异常响应的固定长度。 + * 异常响应帧 = 从站地址(1) + 功能码(最高位置1, 1字节) + 异常码(1) + CRC(2) + * 在 RTU 模式下最小为 5 字节,TCP 模式下加上 MBAP 头共 12 字节。 + */ +#define _MODBUS_EXCEPTION_RSP_LENGTH 5 + +/* 超时时间(单位:微秒),默认 0.5 秒 */ +#define _RESPONSE_TIMEOUT 500000 +/* + * 字节间超时时间(单位:微秒),默认 0.5 秒。 + * 在 RTU 模式下,如果两个字节之间的间隔超过此值,则认为一帧消息已结束。 + */ +#define _BYTE_TIMEOUT 500000 + +/* + * 后端类型枚举。 + * libmodbus 支持两种传输后端:RTU(串行链路)和 TCP(以太网)。 + * 该枚举用于标识当前 modbus_t 上下文使用的后端类型。 + */ +typedef enum { + _MODBUS_BACKEND_TYPE_RTU = 0, + _MODBUS_BACKEND_TYPE_TCP +} modbus_backend_type_t; + +/* + * Modbus 客户端/服务器通信模型示意: + * + * ---------- 请求(Request) 指示(Indication) ---------- + * | 客户端 | ---------------------->| 服务器 | + * ---------- 确认(Confirmation) 响应(Response) ---------- + * + * msg_type_t 枚举用于标识当前消息处于通信模型的哪个阶段, + * 主要用于内部函数 _modbus_receive_msg() 的消息类型参数。 + */ +typedef enum { + /* 服务器端的请求消息(即服务器收到来自客户端的请求) */ + MSG_INDICATION, + /* 客户端的请求消息(即客户端等待来自服务器的响应) */ + MSG_CONFIRMATION +} msg_type_t; + +/* + * sft_t 结构体将从站地址(slave)、功能码(function)和事务ID(t_id) + * 组合在一起,减少了函数调用的参数数量,从而优化执行速度(约提升 37%)。 + */ +typedef struct _sft { + int slave; + int function; + int t_id; +} sft_t; + +/* + * modbus_backend_t 是后端(RTU/TCP)的虚函数表结构体。 + * 每种传输后端(RTU、TCP)提供自己的一组函数指针实现, + * 通过此结构体实现多态,使得上层代码无需关心底层传输细节。 + */ +typedef struct _modbus_backend { + unsigned int backend_type; /* 后端类型标识 */ + unsigned int header_length; /* 协议头长度(RTU:1, TCP:7) */ + unsigned int checksum_length; /* 校验和长度(RTU:2, TCP:0) */ + unsigned int max_adu_length; /* 最大 ADU 长度 */ + int (*set_slave)(modbus_t *ctx, int slave); /* 设置从站地址 */ + int (*build_request_basis)( /* 构建请求帧基础部分 */ + modbus_t *ctx, int function, int addr, int nb, uint8_t *req); + int (*build_response_basis)(sft_t *sft, uint8_t *rsp); /* 构建响应帧基础部分 */ + int (*get_response_tid)(const uint8_t *req); /* 提取事务ID(TCP使用) */ + int (*send_msg_pre)(uint8_t *req, int req_length); /* 发送前的预处理 */ + ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length); /* 发送数据 */ + int (*receive)(modbus_t *ctx, uint8_t *req); /* 接收数据(旧接口) */ + ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length); /* 接收数据(新接口) */ + int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length); /* 完整性校验 */ + int (*pre_check_confirmation)(modbus_t *ctx, /* 收到响应后的初步校验 */ + const uint8_t *req, + const uint8_t *rsp, + int rsp_length); + int (*connect)(modbus_t *ctx); /* 建立连接 */ + unsigned int (*is_connected)(modbus_t *ctx); /* 检查连接状态 */ + void (*close)(modbus_t *ctx); /* 关闭连接 */ + int (*flush)(modbus_t *ctx); /* 清空缓冲区 */ + int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length); /* 多路复用等待 */ + void (*free)(modbus_t *ctx); /* 释放后端私有数据 */ +} modbus_backend_t; + +/* + * struct _modbus — libmodbus 的核心上下文结构体。 + * 每个 Modbus 连接(无论是 RTU 还是 TCP)都由一个 modbus_t 实例表示, + * 该结构体包含了连接的所有配置参数和运行时状态。 + */ +struct _modbus { + /* 从站地址 */ + int slave; + /* 套接字或文件描述符 */ + int s; + int debug; /* 调试标志 */ + int error_recovery; /* 错误恢复模式 */ + int quirks; /* 兼容性标志位(用于处理非标准设备的行为差异) */ + struct timeval response_timeout; /* 响应超时时间 */ + struct timeval byte_timeout; /* 字节间超时时间 */ + struct timeval indication_timeout; /* 指示超时时间(服务器端等待请求) */ + const modbus_backend_t *backend; /* 后端虚函数表指针 */ + void *backend_data; /* 后端私有数据(如 modbus_rtu_t 或 modbus_tcp_t) */ +}; + +void _modbus_init_common(modbus_t *ctx); +void _error_print(modbus_t *ctx, const char *context); +int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type); + +/* + * 如果目标平台未提供 strlcpy 函数,则声明本地的 strlcpy 实现。 + * strlcpy 是 BSD 风格的字符串拷贝函数,保证目标缓冲区以 '\0' 结尾, + * 且不会发生缓冲区溢出。 + */ +#ifndef HAVE_STRLCPY +size_t strlcpy(char *dest, const char *src, size_t dest_size); +#endif + +MODBUS_END_DECLS + +#endif /* MODBUS_PRIVATE_H */ diff --git a/src/protocol/libmodbus/inc/modbus-rtu-private.h b/src/protocol/libmodbus/inc/modbus-rtu-private.h new file mode 100644 index 0000000..80b9680 --- /dev/null +++ b/src/protocol/libmodbus/inc/modbus-rtu-private.h @@ -0,0 +1,91 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_RTU_PRIVATE_H +#define MODBUS_RTU_PRIVATE_H + +#include + +/* + * 防止同时包含 时, + * "struct termios" 的重复定义冲突。 + * 策略:先用宏 termios 屏蔽版本标识,包含 termbits.h 后再取消屏蔽, + * 这样 termios.h 会在后续正常包含。 + */ +#define termios +#include +#undef termios +#include + +/* + * Modbus RTU 协议的帧格式定义: + * + * RTU ADU 帧结构: + * ┌─────────┬──────┬──────────┬─────┐ + * │ 从站地址 │ PDU │ CRC │ │ + * │ (1字节) │(变长)│ (2字节) │ ... │ + * └─────────┴──────┴──────────┴─────┘ + */ + +/* RTU 帧头长度:只有从站地址 1 字节 */ +#define _MODBUS_RTU_HEADER_LENGTH 1 +/* RTU 预设请求帧长度(如报告从站ID等广播命令) */ +#define _MODBUS_RTU_PRESET_REQ_LENGTH 6 +/* RTU 预设响应帧长度 */ +#define _MODBUS_RTU_PRESET_RSP_LENGTH 2 + +/* RTU CRC 校验和长度:2 字节(CRC-16) */ +#define _MODBUS_RTU_CHECKSUM_LENGTH 2 + + +/* + * modbus_rtu_t — Modbus RTU 后端的私有数据结构体。 + * 保存 RTU 串口连接所需的所有配置参数和运行时状态。 + * 该结构体通过 modbus_t.backend_data 指针引用。 + */ +typedef struct _modbus_rtu { + /* + * 串口设备路径。 + * Linux 下形如 "/dev/ttyS0"、"/dev/ttyUSB0"; + * macOS 下形如 "/dev/tty.USA19*"。 + */ + char *device; + /* 波特率:9600, 19200, 57600, 115200 等 */ + int baud; + /* 数据位(通常为 8) */ + uint8_t data_bit; + /* 停止位(通常为 1 或 2) */ + uint8_t stop_bit; + /* 校验位:'N'(无校验), 'O'(奇校验), 'E'(偶校验) */ + char parity; + /* 保存原始 termios2 设置,用于关闭时恢复 */ + struct termios2 old_tios; +#if HAVE_DECL_TIOCSRS485 + /* + * RS-485 串口模式标志。 + * TIOCSRS485 是 Linux 内核提供的 ioctl,用于配置 RS-485 收发控制。 + */ + int serial_mode; +#endif +#if HAVE_DECL_TIOCM_RTS + /* + * RTS(请求发送)引脚控制相关字段。 + * 在 RS-485 半双工通信中,需要通过 RTS 信号控制收发切换。 + */ + int rts; /* RTS 控制模式 */ + int rts_delay; /* RTS 发送延迟(微秒) */ + int onebyte_time; /* 单字节传输时间(用于 RTS 时序计算) */ + void (*set_rts)(modbus_t *ctx, int on); /* 设置 RTS 信号电平的回调函数 */ +#endif + /* + * 需要忽略的确认(Confirmation)消息数量。 + * 当一条链路上存在多个从站时,广播命令会导致所有从站都产生响应, + * 需要忽略除目标从站之外的响应消息。 + */ + int confirmation_to_ignore; +} modbus_rtu_t; + +#endif /* MODBUS_RTU_PRIVATE_H */ diff --git a/src/protocol/libmodbus/inc/modbus-rtu.h b/src/protocol/libmodbus/inc/modbus-rtu.h new file mode 100644 index 0000000..d6b8064 --- /dev/null +++ b/src/protocol/libmodbus/inc/modbus-rtu.h @@ -0,0 +1,43 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_RTU_H +#define MODBUS_RTU_H + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + +/* 参考 Modbus_Application_Protocol_V1_1b.pdf 第四章第一节第5页 + * RS232 / RS485 应用数据单元(ADU)= 253 字节数据 + 从站地址(1 字节)+ CRC 校验(2 字节)= 共 256 字节 + */ +#define MODBUS_RTU_MAX_ADU_LENGTH 256 + +MODBUS_API modbus_t * +modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit); + +#define MODBUS_RTU_RS232 0 +#define MODBUS_RTU_RS485 1 + +MODBUS_API int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode); +MODBUS_API int modbus_rtu_get_serial_mode(modbus_t *ctx); + +#define MODBUS_RTU_RTS_NONE 0 +#define MODBUS_RTU_RTS_UP 1 +#define MODBUS_RTU_RTS_DOWN 2 + +MODBUS_API int modbus_rtu_set_rts(modbus_t *ctx, int mode); +MODBUS_API int modbus_rtu_get_rts(modbus_t *ctx); + +MODBUS_API int modbus_rtu_set_custom_rts(modbus_t *ctx, + void (*set_rts)(modbus_t *ctx, int on)); + +MODBUS_API int modbus_rtu_set_rts_delay(modbus_t *ctx, int us); +MODBUS_API int modbus_rtu_get_rts_delay(modbus_t *ctx); + +MODBUS_END_DECLS + +#endif /* MODBUS_RTU_H */ diff --git a/src/protocol/libmodbus/inc/modbus-tcp-private.h b/src/protocol/libmodbus/inc/modbus-tcp-private.h new file mode 100644 index 0000000..86b5d2b --- /dev/null +++ b/src/protocol/libmodbus/inc/modbus-tcp-private.h @@ -0,0 +1,78 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_TCP_PRIVATE_H +#define MODBUS_TCP_PRIVATE_H + +/* + * Modbus TCP 协议的帧格式定义: + * + * TCP ADU 帧结构(基于 MBAP 头 + PDU): + * ┌──────────────────────────┬───────────────┬──────────┐ + * │ MBAP 头部(7字节) │ PDU(变长) │ ... │ + * └──────────────────────────┴───────────────┴──────────┘ + * + * MBAP 头部分解: + * ┌──────────────┬──────────────┬──────────────┬──────────┐ + * │ 事务标识符 │ 协议标识符 │ 长度字段 │ 单元标识符 │ + * │ (2字节) │ (2字节,=0) │ (2字节) │ (1字节) │ + * └──────────────┴──────────────┴──────────────┴──────────┘ + */ + +/* TCP MBAP 头长度:事务ID(2) + 协议ID(2) + 长度(2) + 单元ID(1) = 7 字节 */ +#define _MODBUS_TCP_HEADER_LENGTH 7 +/* TCP 预设请求帧的总长度:MBAP头(7) + PDU(5) = 12 字节 */ +#define _MODBUS_TCP_PRESET_REQ_LENGTH 12 +/* TCP 预设响应帧的总长度:MBAP头(7) + PDU(1) = 8 字节 */ +#define _MODBUS_TCP_PRESET_RSP_LENGTH 8 + +/* + * TCP 模式下没有独立的校验和字段。 + * TCP 协议本身通过底层 TCP/IP 栈的校验和机制保证数据完整性, + * 因此 Modbus TCP 的 checksum 长度为 0。 + */ +#define _MODBUS_TCP_CHECKSUM_LENGTH 0 + +/* + * modbus_tcp_t — Modbus TCP 后端的私有数据结构体。 + * 在两个结构体中,事务标识符(t_id)都必须放在第一个字段位置, + * 这样无论使用哪个 TCP 后端变体,都可以通过相同的内存偏移量快速访问事务ID。 + */ +typedef struct _modbus_tcp { + /* + * 事务标识符。 + * 摘录自《MODBUS Messaging on TCP/IP Implementation Guide V1.0b》 + * (第 23/46 页): + * 事务标识符用于将未来的响应与对应的请求关联起来。 + * 在每个 TCP 连接上,此标识符是唯一的。 + * 客户端生成一个事务ID,服务器在响应中原样返回, + * 从而允许客户端匹配请求和响应。 + */ + uint16_t t_id; + /* TCP 端口号(标准 Modbus TCP 端口为 502) */ + int port; + /* IP 地址字符串(如 "192.168.1.100"),最大 15 字符 + '\0' 结尾 */ + char ip[16]; +} modbus_tcp_t; + +/* + * modbus_tcp_pi_t — Modbus TCP PI(协议独立)后端的私有数据结构体。 + * "PI" 表示 Protocol Independent(协议独立),即使用 getaddrinfo() + * 解析的主机名和服务名,而非仅限 IPv4 地址。 + * 这使得该结构体同时支持 IPv4 和 IPv6 地址。 + */ +typedef struct _modbus_tcp_pi { + /* 事务标识符(放在首位,便于快速访问) */ + uint16_t t_id; + /* TCP 端口号 */ + int port; + /* 主机节点名(如 "modbus-server.local" 或 IPv4/IPv6 地址字符串) */ + char *node; + /* 服务名(如 "502" 或 "/etc/services 中定义的服务名") */ + char *service; +} modbus_tcp_pi_t; + +#endif /* MODBUS_TCP_PRIVATE_H */ diff --git a/src/protocol/libmodbus/inc/modbus-tcp.h b/src/protocol/libmodbus/inc/modbus-tcp.h new file mode 100644 index 0000000..6375db5 --- /dev/null +++ b/src/protocol/libmodbus/inc/modbus-tcp.h @@ -0,0 +1,33 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_TCP_H +#define MODBUS_TCP_H + +#include "modbus.h" + +MODBUS_BEGIN_DECLS + + +#define MODBUS_TCP_DEFAULT_PORT 502 +#define MODBUS_TCP_SLAVE 0xFF + +/* 参考 Modbus_Application_Protocol_V1_1b.pdf 第四章第一节第5页 + * TCP MODBUS 应用数据单元(ADU)= 253 字节数据 + MBAP 报文头(7 字节)= 共 260 字节 + */ +#define MODBUS_TCP_MAX_ADU_LENGTH 260 + +MODBUS_API modbus_t *modbus_new_tcp(const char *ip_address, int port); +MODBUS_API int modbus_tcp_listen(modbus_t *ctx, int nb_connection); +MODBUS_API int modbus_tcp_accept(modbus_t *ctx, int *s); + +MODBUS_API modbus_t *modbus_new_tcp_pi(const char *node, const char *service); +MODBUS_API int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection); +MODBUS_API int modbus_tcp_pi_accept(modbus_t *ctx, int *s); + +MODBUS_END_DECLS + +#endif /* MODBUS_TCP_H */ diff --git a/src/protocol/libmodbus/inc/modbus-version.h b/src/protocol/libmodbus/inc/modbus-version.h new file mode 100644 index 0000000..8bd375f --- /dev/null +++ b/src/protocol/libmodbus/inc/modbus-version.h @@ -0,0 +1,44 @@ +/* + * libmodbus 版本头文件 + * 从 modbus-version.h.in 模板生成,版本号硬编码为 3.1.12 + * + * Copyright © Stéphane Raimbault + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_VERSION_H +#define MODBUS_VERSION_H + +/* 主版本号(例如 v1.2.3 中的 1) */ +#define LIBMODBUS_VERSION_MAJOR 3 + +/* 次版本号(例如 v1.2.3 中的 2) */ +#define LIBMODBUS_VERSION_MINOR 1 + +/* 修订版本号(例如 v1.2.3 中的 3) */ +#define LIBMODBUS_VERSION_MICRO 12 + +/* 完整版本号字符串,形式如 "3.1.12" */ +#define LIBMODBUS_VERSION 3.1.12 + +/* 完整版本号字符串(适用于字符串拼接场景) */ +#define LIBMODBUS_VERSION_STRING "3.1.12" + +/* 数值编码的版本号,例如 v1.2.3 编码为 0x010203 */ +#define LIBMODBUS_VERSION_HEX \ + ((LIBMODBUS_VERSION_MAJOR << 16) | (LIBMODBUS_VERSION_MINOR << 8) | \ + (LIBMODBUS_VERSION_MICRO << 0)) + +/* 版本检查宏:当库版本大于等于指定版本时返回真 + * @param major 主版本号 + * @param minor 次版本号 + * @param micro 修订版本号 + * @return 若当前版本 >= 指定版本则为真 + */ +#define LIBMODBUS_VERSION_CHECK(major, minor, micro) \ + (LIBMODBUS_VERSION_MAJOR > (major) || \ + (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR > (minor)) || \ + (LIBMODBUS_VERSION_MAJOR == (major) && LIBMODBUS_VERSION_MINOR == (minor) && \ + LIBMODBUS_VERSION_MICRO >= (micro))) + +#endif /* MODBUS_VERSION_H */ diff --git a/src/protocol/libmodbus/inc/modbus.h b/src/protocol/libmodbus/inc/modbus.h new file mode 100644 index 0000000..0f675e8 --- /dev/null +++ b/src/protocol/libmodbus/inc/modbus.h @@ -0,0 +1,318 @@ +/* + * 版权所有 © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef MODBUS_H +#define MODBUS_H + +// clang-format off +/* 添加此宏用于定义了 Unix 风格的系统 */ +#if (defined(__unix__) || defined(unix)) && !defined(USG) +# include +#endif + +#include + +#include "modbus-version.h" + +#define MODBUS_API + +#ifdef __cplusplus +# define MODBUS_BEGIN_DECLS extern "C" { +# define MODBUS_END_DECLS } +#else +# define MODBUS_BEGIN_DECLS +# define MODBUS_END_DECLS +#endif +// clang-format on + +MODBUS_BEGIN_DECLS + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef OFF +#define OFF 0 +#endif + +#ifndef ON +#define ON 1 +#endif + +/* Modbus 功能码 */ +#define MODBUS_FC_READ_COILS 0x01 +#define MODBUS_FC_READ_DISCRETE_INPUTS 0x02 +#define MODBUS_FC_READ_HOLDING_REGISTERS 0x03 +#define MODBUS_FC_READ_INPUT_REGISTERS 0x04 +#define MODBUS_FC_WRITE_SINGLE_COIL 0x05 +#define MODBUS_FC_WRITE_SINGLE_REGISTER 0x06 +#define MODBUS_FC_READ_EXCEPTION_STATUS 0x07 +#define MODBUS_FC_WRITE_MULTIPLE_COILS 0x0F +#define MODBUS_FC_WRITE_MULTIPLE_REGISTERS 0x10 +#define MODBUS_FC_REPORT_SLAVE_ID 0x11 +#define MODBUS_FC_MASK_WRITE_REGISTER 0x16 +#define MODBUS_FC_WRITE_AND_READ_REGISTERS 0x17 + +#define MODBUS_BROADCAST_ADDRESS 0 + +/* 参考 Modbus_Application_Protocol_V1_1b.pdf(第 6 章第 1 节第 12 页) + * 要读取的 Coil 数量(2 字节):1 到 2000(0x7D0) + * (第 6 章第 11 节第 29 页) + * 要写入的 Coil 数量(2 字节):1 到 1968(0x7B0) + */ +#define MODBUS_MAX_READ_BITS 2000 +#define MODBUS_MAX_WRITE_BITS 1968 + +/* 参考 Modbus_Application_Protocol_V1_1b.pdf(第 6 章第 3 节第 15 页) + * 要读取的 Register 数量(2 字节):1 到 125(0x7D) + * (第 6 章第 12 节第 31 页) + * 要写入的 Register 数量(2 字节):1 到 123(0x7B) + * (第 6 章第 17 节第 38 页) + * 读写寄存器中要写入的 Register 数量(2 字节):1 到 121(0x79) + */ +#define MODBUS_MAX_READ_REGISTERS 125 +#define MODBUS_MAX_WRITE_REGISTERS 123 +#define MODBUS_MAX_WR_WRITE_REGISTERS 121 +#define MODBUS_MAX_WR_READ_REGISTERS 125 + +/* Modbus PDU 的大小受限于首个 Modbus 实现(串行线路网络)的尺寸约束 + * (最大 RS485 ADU = 256 字节)。因此,串行线路通信的 Modbus PDU = 256 - 服务器 + * 地址(1 字节)- CRC(2 字节)= 253 字节。 + */ +#define MODBUS_MAX_PDU_LENGTH 253 + +/* 由此可得: + * - RTU Modbus ADU = 253 字节 + 服务器地址(1 字节)+ CRC(2 字节)= 256 字节。 + * - TCP Modbus ADU = 253 字节 + MBAP(7 字节)= 260 字节。 + * 因此两种后端中的最大值为 260 字节。此大小可用于分配字节数组来存储响应, + * 并且兼容两种后端。 + */ +#define MODBUS_MAX_ADU_LENGTH 260 + +/* 用一个任意数作为基准,避免与 errno 冲突 */ +#define MODBUS_ENOBASE 112345678 + +/* 协议异常码 */ +enum { + MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 0x01, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE, + MODBUS_EXCEPTION_ACKNOWLEDGE, + MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY, + MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE, + MODBUS_EXCEPTION_MEMORY_PARITY, + MODBUS_EXCEPTION_NOT_DEFINED, + MODBUS_EXCEPTION_GATEWAY_PATH, + MODBUS_EXCEPTION_GATEWAY_TARGET, + MODBUS_EXCEPTION_MAX +}; + +#define EMBXILFUN (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_FUNCTION) +#define EMBXILADD (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS) +#define EMBXILVAL (MODBUS_ENOBASE + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE) +#define EMBXSFAIL (MODBUS_ENOBASE + MODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE) +#define EMBXACK (MODBUS_ENOBASE + MODBUS_EXCEPTION_ACKNOWLEDGE) +#define EMBXSBUSY (MODBUS_ENOBASE + MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY) +#define EMBXNACK (MODBUS_ENOBASE + MODBUS_EXCEPTION_NEGATIVE_ACKNOWLEDGE) +#define EMBXMEMPAR (MODBUS_ENOBASE + MODBUS_EXCEPTION_MEMORY_PARITY) +#define EMBXGPATH (MODBUS_ENOBASE + MODBUS_EXCEPTION_GATEWAY_PATH) +#define EMBXGTAR (MODBUS_ENOBASE + MODBUS_EXCEPTION_GATEWAY_TARGET) + +/* libmodbus 本地错误码 */ +#define EMBBADCRC (EMBXGTAR + 1) +#define EMBBADDATA (EMBXGTAR + 2) +#define EMBBADEXC (EMBXGTAR + 3) +#define EMBUNKEXC (EMBXGTAR + 4) +#define EMBMDATA (EMBXGTAR + 5) +#define EMBBADSLAVE (EMBXGTAR + 6) + +extern const unsigned int libmodbus_version_major; +extern const unsigned int libmodbus_version_minor; +extern const unsigned int libmodbus_version_micro; + +typedef struct _modbus modbus_t; + +/*! tab_xxx 数组中的内存布局采用处理器字节序。 + 接收 Modbus 数据时,数据会转换为处理器字节序,参见 read_registers() 函数。 +*/ +typedef struct _modbus_mapping_t { + int nb_bits; + int start_bits; + int nb_input_bits; + int start_input_bits; + int nb_input_registers; + int start_input_registers; + int nb_registers; + int start_registers; + uint8_t *tab_bits; + uint8_t *tab_input_bits; + uint16_t *tab_input_registers; + uint16_t *tab_registers; +} modbus_mapping_t; + +typedef enum { + MODBUS_ERROR_RECOVERY_NONE = 0, + MODBUS_ERROR_RECOVERY_LINK = (1 << 1), + MODBUS_ERROR_RECOVERY_PROTOCOL = (1 << 2) +} modbus_error_recovery_mode; + +typedef enum { + MODBUS_QUIRK_NONE = 0, + MODBUS_QUIRK_MAX_SLAVE = (1 << 1), + MODBUS_QUIRK_REPLY_TO_BROADCAST = (1 << 2), + MODBUS_QUIRK_ALL = 0xFF +} modbus_quirks; + +MODBUS_API int modbus_set_slave(modbus_t *ctx, int slave); +MODBUS_API int modbus_get_slave(modbus_t *ctx); +MODBUS_API int modbus_set_error_recovery(modbus_t *ctx, + modbus_error_recovery_mode error_recovery); +MODBUS_API int modbus_set_socket(modbus_t *ctx, int s); +MODBUS_API int modbus_get_socket(modbus_t *ctx); + +MODBUS_API int +modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int +modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); + +MODBUS_API int +modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); + +MODBUS_API int +modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec); +MODBUS_API int +modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec); + +MODBUS_API int modbus_get_header_length(modbus_t *ctx); + +MODBUS_API int modbus_connect(modbus_t *ctx); +MODBUS_API void modbus_close(modbus_t *ctx); + +MODBUS_API void modbus_free(modbus_t *ctx); + +MODBUS_API int modbus_flush(modbus_t *ctx); +MODBUS_API int modbus_set_debug(modbus_t *ctx, int flag); + +MODBUS_API const char *modbus_strerror(int errnum); + +MODBUS_API int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); +MODBUS_API int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest); +MODBUS_API int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); +MODBUS_API int +modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest); +MODBUS_API int modbus_write_bit(modbus_t *ctx, int coil_addr, int status); +MODBUS_API int modbus_write_register(modbus_t *ctx, int reg_addr, const uint16_t value); +MODBUS_API int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *data); +MODBUS_API int +modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data); +MODBUS_API int +modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask); +MODBUS_API int modbus_write_and_read_registers(modbus_t *ctx, + int write_addr, + int write_nb, + const uint16_t *src, + int read_addr, + int read_nb, + uint16_t *dest); +MODBUS_API int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest); + +MODBUS_API modbus_mapping_t * +modbus_mapping_new_start_address(unsigned int start_bits, + unsigned int nb_bits, + unsigned int start_input_bits, + unsigned int nb_input_bits, + unsigned int start_registers, + unsigned int nb_registers, + unsigned int start_input_registers, + unsigned int nb_input_registers); + +MODBUS_API modbus_mapping_t *modbus_mapping_new(int nb_bits, + int nb_input_bits, + int nb_registers, + int nb_input_registers); +MODBUS_API void modbus_mapping_free(modbus_mapping_t *mb_mapping); + +MODBUS_API int +modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length); + +MODBUS_API int modbus_send_raw_request_tid(modbus_t *ctx, + const uint8_t *raw_req, + int raw_req_length, + int tid); + +MODBUS_API int modbus_receive(modbus_t *ctx, uint8_t *req); + +MODBUS_API int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp); + +MODBUS_API int modbus_reply(modbus_t *ctx, + const uint8_t *req, + int req_length, + modbus_mapping_t *mb_mapping); +MODBUS_API int +modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code); +MODBUS_API int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask); +MODBUS_API int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask); + +/** + * 工具函数(UTILS FUNCTIONS) + **/ + +#define MODBUS_GET_HIGH_BYTE(data) (((data) >> 8) & 0xFF) +#define MODBUS_GET_LOW_BYTE(data) ((data) & 0xFF) +#define MODBUS_GET_INT64_FROM_INT16(tab_int16, index) \ + (((int64_t) tab_int16[(index)] << 48) | ((int64_t) tab_int16[(index) + 1] << 32) | \ + ((int64_t) tab_int16[(index) + 2] << 16) | (int64_t) tab_int16[(index) + 3]) +#define MODBUS_GET_INT32_FROM_INT16(tab_int16, index) \ + (((int32_t) tab_int16[(index)] << 16) | (int32_t) tab_int16[(index) + 1]) +#define MODBUS_GET_INT16_FROM_INT8(tab_int8, index) \ + (((int16_t) tab_int8[(index)] << 8) | (int16_t) tab_int8[(index) + 1]) +#define MODBUS_SET_INT16_TO_INT8(tab_int8, index, value) \ + do { \ + ((int8_t *) (tab_int8))[(index)] = (int8_t) ((value) >> 8); \ + ((int8_t *) (tab_int8))[(index) + 1] = (int8_t) (value); \ + } while (0) +#define MODBUS_SET_INT32_TO_INT16(tab_int16, index, value) \ + do { \ + ((int16_t *) (tab_int16))[(index)] = (int16_t) ((value) >> 16); \ + ((int16_t *) (tab_int16))[(index) + 1] = (int16_t) (value); \ + } while (0) +#define MODBUS_SET_INT64_TO_INT16(tab_int16, index, value) \ + do { \ + ((int16_t *) (tab_int16))[(index)] = (int16_t) ((value) >> 48); \ + ((int16_t *) (tab_int16))[(index) + 1] = (int16_t) ((value) >> 32); \ + ((int16_t *) (tab_int16))[(index) + 2] = (int16_t) ((value) >> 16); \ + ((int16_t *) (tab_int16))[(index) + 3] = (int16_t) (value); \ + } while (0) + +MODBUS_API void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value); +MODBUS_API void modbus_set_bits_from_bytes(uint8_t *dest, + int idx, + unsigned int nb_bits, + const uint8_t *tab_byte); +MODBUS_API uint8_t modbus_get_byte_from_bits(const uint8_t *src, + int idx, + unsigned int nb_bits); +MODBUS_API float modbus_get_float(const uint16_t *src); +MODBUS_API float modbus_get_float_abcd(const uint16_t *src); +MODBUS_API float modbus_get_float_dcba(const uint16_t *src); +MODBUS_API float modbus_get_float_badc(const uint16_t *src); +MODBUS_API float modbus_get_float_cdab(const uint16_t *src); + +MODBUS_API void modbus_set_float(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_abcd(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_dcba(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_badc(float f, uint16_t *dest); +MODBUS_API void modbus_set_float_cdab(float f, uint16_t *dest); + +MODBUS_END_DECLS + +#endif /* MODBUS_H */ diff --git a/src/protocol/libmodbus/inc/myModbus.h b/src/protocol/libmodbus/inc/myModbus.h new file mode 100644 index 0000000..6e19764 --- /dev/null +++ b/src/protocol/libmodbus/inc/myModbus.h @@ -0,0 +1,29 @@ +/** + * @file myModbus.h + * @brief Modbus 协议栈统一入口 + * @details 开源库 libmodbus v3.1.12(LGPL-2.1-or-later)。 + * 本项目不修改 libmodbus 源码,本文件仅作为聚合入口, + * 调用者只需 #include "myModbus.h" 即可获取全部 Modbus 接口。 + * + * Copyright © Stéphane Raimbault + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#ifndef _MY_MODBUS_H_ +#define _MY_MODBUS_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "modbus.h" /* 主 API:功能码、错误码、数据结构、读写接口 */ +#include "modbus-rtu.h" /* RTU 后端:创建串口上下文、RTS 控制 */ +#include "modbus-tcp.h" /* TCP 后端:创建 TCP 上下文、listen/accept */ +#include "modbus-version.h" /* 版本号宏 */ + +#ifdef __cplusplus +} +#endif + +#endif /* _MY_MODBUS_H_ */ diff --git a/src/protocol/libmodbus/src/modbus-data.c b/src/protocol/libmodbus/src/modbus-data.c new file mode 100644 index 0000000..b7c4e58 --- /dev/null +++ b/src/protocol/libmodbus/src/modbus-data.c @@ -0,0 +1,231 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +// clang-format off +#include + +#include +#include + +#include + +#include "config.h" + +#include "modbus.h" + +// clang-format on + +/* 从单个字节值中设置多个位(字节值的所有8位都会被设置) */ +void modbus_set_bits_from_byte(uint8_t *dest, int idx, const uint8_t value) +{ + int i; + + for (i = 0; i < 8; i++) { + dest[idx + i] = (value & (1 << i)) ? 1 : 0; + } +} + +/* 从字节表中设置多个位(仅设置 idx 到 idx + nb_bits 之间的位) */ +void modbus_set_bits_from_bytes(uint8_t *dest, + int idx, + unsigned int nb_bits, + const uint8_t *tab_byte) +{ + unsigned int i; + int shift = 0; + + for (i = idx; i < idx + nb_bits; i++) { + dest[i] = tab_byte[(i - idx) / 8] & (1 << shift) ? 1 : 0; + /* gcc 不喜欢: shift = (++shift) % 8; */ + shift++; + shift %= 8; + } +} + +/* 从多个位中获取字节值。 + 要获得完整的一个字节,请将 nb_bits 设置为 8。 */ +uint8_t modbus_get_byte_from_bits(const uint8_t *src, int idx, unsigned int nb_bits) +{ + unsigned int i; + uint8_t value = 0; + + if (nb_bits > 8) { + /* 如果设置了 NDEBUG,则断言会被忽略 */ + assert(nb_bits < 8); + nb_bits = 8; + } + + for (i = 0; i < nb_bits; i++) { + value |= (src[idx + i] << i); + } + + return value; +} + +/* 从4个字节(Modbus格式)中获取浮点数,不做任何转换(ABCD字节序) */ +float modbus_get_float_abcd(const uint16_t *src) +{ + float f; + uint32_t i; + uint8_t a, b, c, d; + + // 注意: src 包含按处理器字节序排列的16位数字, + // 因此我们使用移位操作,不直接访问内存 + a = (src[0] >> 8) & 0xFF; // 第一个字的高字节 + b = (src[0] >> 0) & 0xFF; // 第一个字的低字节 + c = (src[1] >> 8) & 0xFF; // 第二个字的高字节 + d = (src[1] >> 0) & 0xFF; // 第二个字的低字节 + + // 我们总是通过移位操作按 abcd 顺序组装32位整数 + i = (a << 24) | (b << 16) | (c << 8) | (d << 0); + memcpy(&f, &i, 4); + + return f; +} + +/* 从4个字节(Modbus格式)中获取浮点数,使用反向格式(DCBA字节序) */ +float modbus_get_float_dcba(const uint16_t *src) +{ + float f; + uint32_t i; + uint8_t a, b, c, d; + + // 从 src 读取时的字节顺序定义为: dcba + d = (src[0] >> 8) & 0xFF; + c = (src[0] >> 0) & 0xFF; + b = (src[1] >> 8) & 0xFF; + a = (src[1] >> 0) & 0xFF; + + // 我们总是通过移位操作按 abcd 顺序组装32位整数 + i = (a << 24) | (b << 16) | (c << 8) | (d << 0); + memcpy(&f, &i, 4); + + return f; +} + +/* 从4个字节(Modbus格式)中获取浮点数,交换字节顺序(BADC) */ +float modbus_get_float_badc(const uint16_t *src) +{ + float f; + uint32_t i; + uint8_t a, b, c, d; + + // 从 src 读取时的字节顺序定义为: badc + b = (src[0] >> 8) & 0xFF; + a = (src[0] >> 0) & 0xFF; + d = (src[1] >> 8) & 0xFF; + c = (src[1] >> 0) & 0xFF; + + // 我们总是通过移位操作按 abcd 顺序组装32位整数 + i = (a << 24) | (b << 16) | (c << 8) | (d << 0); + memcpy(&f, &i, 4); + + return f; +} + +/* 从4个字节(Modbus格式)中获取浮点数,交换字顺序(CDAB) */ +float modbus_get_float_cdab(const uint16_t *src) +{ + float f; + uint32_t i; + uint8_t a, b, c, d; + + // 从 src 读取时的字节顺序定义为: cdab + c = (src[0] >> 8) & 0xFF; + d = (src[0] >> 0) & 0xFF; + a = (src[1] >> 8) & 0xFF; + b = (src[1] >> 0) & 0xFF; + + // 我们总是通过移位操作按 abcd 顺序组装32位整数 + i = (a << 24) | (b << 16) | (c << 8) | (d << 0); + memcpy(&f, &i, 4); + + return f; +} + +/* 【已弃用】 - 从4个字节中以某种 Modbus 格式获取浮点数 */ +float modbus_get_float(const uint16_t *src) +{ + return modbus_get_float_cdab(src); +} + +/* 将浮点数设置为4字节 Modbus 格式,不做任何转换(ABCD字节序) */ +void modbus_set_float_abcd(float f, uint16_t *dest) +{ + // 直接类型转换会因为类型双关指针别名警告而无法工作 + // uint32_t i = *(uint32_t*)(&f); + float * fptr = &f; + uint32_t * iptr = (uint32_t *)fptr; + uint32_t i = *iptr; + uint8_t a, b, c, d; + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; + + dest[0] = (a << 8) | b; + dest[1] = (c << 8) | d; +} + +/* 将浮点数设置为4字节 Modbus 格式,进行字节和字交换转换(DCBA字节序) */ +void modbus_set_float_dcba(float f, uint16_t *dest) +{ + float * fptr = &f; + uint32_t * iptr = (uint32_t *)fptr; + uint32_t i = *iptr; + uint8_t a, b, c, d; + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; + + dest[0] = (d << 8) | c; + dest[1] = (b << 8) | a; +} + +/* 将浮点数设置为4字节 Modbus 格式,仅交换字节顺序(BADC) */ +void modbus_set_float_badc(float f, uint16_t *dest) +{ + float * fptr = &f; + uint32_t * iptr = (uint32_t *)fptr; + uint32_t i = *iptr; + uint8_t a, b, c, d; + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; + + dest[0] = (b << 8) | a; + dest[1] = (d << 8) | c; +} + +/* 将浮点数设置为4字节 Modbus 格式,仅交换字顺序(CDAB) */ +void modbus_set_float_cdab(float f, uint16_t *dest) +{ + float * fptr = &f; + uint32_t * iptr = (uint32_t *)fptr; + uint32_t i = *iptr; + uint8_t a, b, c, d; + + a = (i >> 24) & 0xFF; + b = (i >> 16) & 0xFF; + c = (i >> 8) & 0xFF; + d = (i >> 0) & 0xFF; + + dest[0] = (c << 8) | d; + dest[1] = (a << 8) | b; +} + +/* 【已弃用】 - 将浮点数设置为4字节,采用某种 Modbus 格式! */ +void modbus_set_float(float f, uint16_t *dest) +{ + modbus_set_float_cdab(f, dest); +} diff --git a/src/protocol/libmodbus/src/modbus-rtu.c b/src/protocol/libmodbus/src/modbus-rtu.c new file mode 100644 index 0000000..d42e697 --- /dev/null +++ b/src/protocol/libmodbus/src/modbus-rtu.c @@ -0,0 +1,885 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include +#include +#include +#include +#include +#include +#include +#include "modbus-private.h" +#include "myLog.h" +#include + +#include "modbus-rtu-private.h" +#include "modbus-rtu.h" + +#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS +#include +#endif + +#if HAVE_DECL_TIOCSRS485 +#include +#endif + +/* CRC高位字节查找表 */ +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}; + +/* CRC低位字节查找表 */ +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}; + +/* 定义远程设备的从站ID:在主站模式下用于指定要与之通信的远程设备, + * 在从站模式下用于设置内部从站ID */ +static int _modbus_set_slave(modbus_t *ctx, int slave) +{ + int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247; + + /* 广播地址为 0(MODBUS_BROADCAST_ADDRESS) */ + if (slave >= 0 && slave <= max_slave) { + ctx->slave = slave; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +/* 构建 RTU 请求报文头 */ +static int _modbus_rtu_build_request_basis( + modbus_t *ctx, int function, int addr, int nb, uint8_t *req) +{ + assert(ctx->slave != -1); + req[0] = ctx->slave; + req[1] = function; + req[2] = addr >> 8; + req[3] = addr & 0x00ff; + req[4] = nb >> 8; + req[5] = nb & 0x00ff; + + return _MODBUS_RTU_PRESET_REQ_LENGTH; +} + +/* 构建 RTU 响应报文头 */ +static int _modbus_rtu_build_response_basis(sft_t *sft, uint8_t *rsp) +{ + /* 在这种情况下,从站地址肯定是有效的, + * 因为 _modbus_rtu_listen 中已经做了检查 */ + rsp[0] = sft->slave; + rsp[1] = sft->function; + + return _MODBUS_RTU_PRESET_RSP_LENGTH; +} + +static uint16_t crc16(uint8_t *buffer, uint16_t buffer_length) +{ + uint8_t crc_hi = 0xFF; /* 初始化 CRC 高位字节 */ + uint8_t crc_lo = 0xFF; /* 初始化 CRC 低位字节 */ + unsigned int i; /* 用于索引 CRC 查找表 */ + + /* 遍历消息缓冲区 */ + while (buffer_length--) { + i = crc_lo ^ *buffer++; /* 计算 CRC */ + crc_lo = crc_hi ^ table_crc_hi[i]; + crc_hi = table_crc_lo[i]; + } + + return (crc_hi << 8 | crc_lo); +} + +static int _modbus_rtu_get_response_tid(const uint8_t *req) +{ + /* RTU 协议中没有事务ID(TID) */ + return 0; +} + +static int _modbus_rtu_send_msg_pre(uint8_t *req, int req_length) +{ + uint16_t crc = crc16(req, req_length); + + /* 根据 MODBUS 规范(第14页),CRC 的低位字节在 RTU 消息中先发送 */ + req[req_length++] = crc & 0x00FF; + req[req_length++] = crc >> 8; + + return req_length; +} + +#if HAVE_DECL_TIOCM_RTS +static void _modbus_rtu_ioctl_rts(modbus_t *ctx, int on) +{ + int fd = ctx->s; + int flags; + + ioctl(fd, TIOCMGET, &flags); + if (on) { + flags |= TIOCM_RTS; + } else { + flags &= ~TIOCM_RTS; + } + ioctl(fd, TIOCMSET, &flags); +} +#endif + +static ssize_t _modbus_rtu_send(modbus_t *ctx, const uint8_t *req, int req_length) +{ +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + if (ctx_rtu->rts != MODBUS_RTU_RTS_NONE) { + ssize_t size; + + if (ctx->debug) { + LOG_I("Sending request using RTS signal"); + } + + ctx_rtu->set_rts(ctx, ctx_rtu->rts == MODBUS_RTU_RTS_UP); + usleep(ctx_rtu->rts_delay); + + size = write(ctx->s, req, req_length); + + usleep(ctx_rtu->onebyte_time * req_length + ctx_rtu->rts_delay); + ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP); + + return size; + } else { +#endif + return write(ctx->s, req, req_length); +#if HAVE_DECL_TIOCM_RTS + } +#endif +} + +static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req) +{ + int rc; + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + if (ctx_rtu->confirmation_to_ignore) { + (void) _modbus_receive_msg(ctx, req, MSG_CONFIRMATION); + /* 忽略错误并重置标志 */ + ctx_rtu->confirmation_to_ignore = FALSE; + rc = 0; + if (ctx->debug) { + LOG_I("Confirmation to ignore\n"); + } + } else { + rc = _modbus_receive_msg(ctx, req, MSG_INDICATION); + if (rc == 0) { + /* 下一条期望的消息是需要忽略的确认消息 */ + ctx_rtu->confirmation_to_ignore = TRUE; + } + } + return rc; +} + +static ssize_t _modbus_rtu_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) +{ + return read(ctx->s, rsp, rsp_length); +} + +static int _modbus_rtu_flush(modbus_t *); + +static int _modbus_rtu_pre_check_confirmation(modbus_t *ctx, + const uint8_t *req, + const uint8_t *rsp, + int rsp_length) +{ + /* 检查响应的从站是否是我们请求的从站(广播请求除外) */ + if (req[0] != rsp[0] && req[0] != MODBUS_BROADCAST_ADDRESS) { + if (ctx->debug) { + LOG_E( + "The responding slave %d isn't the requested slave %d", + rsp[0], + req[0]); + } + errno = EMBBADSLAVE; + return -1; + } else { + return 0; + } +} + +/* check_crc16 函数在消息被忽略时应返回 0, + 如果 CRC 有效则返回消息长度。否则返回 -1 并设置 errno 为 EMBBADCRC。 */ +static int _modbus_rtu_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + uint16_t crc_calculated; + uint16_t crc_received; + int slave = msg[0]; + + crc_calculated = crc16(msg, msg_length - 2); + crc_received = (msg[msg_length - 1] << 8) | msg[msg_length - 2]; + + /* 检查消息的 CRC */ + if (crc_calculated != crc_received) { + if (ctx->debug) { + LOG_E( + "ERROR CRC received 0x%0X != CRC calculated 0x%0X", + crc_received, + crc_calculated); + } + + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _modbus_rtu_flush(ctx); + } + errno = EMBBADCRC; + return -1; + } + + /* 在 RTU 模式下按 Modbus 单元标识符(从站地址)进行过滤 */ + if (slave != ctx->slave && slave != MODBUS_BROADCAST_ADDRESS) { + if (ctx->debug) { + LOG_I("Request for slave %d ignored (not %d)\n", slave, ctx->slave); + } + /* 后续对 check_confirmation 的调用会处理这个错误 */ + return 0; + } + + return msg_length; +} + +/* 配置串口以进行 RTU 通信 */ +/* POSIX 系统 */ +static int _modbus_rtu_connect(modbus_t *ctx) +{ + struct termios2 tios; + int flags; + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + if (ctx->debug) { + LOG_I("Opening %s at %d bauds (%c, %d, %d)\n", + ctx_rtu->device, + ctx_rtu->baud, + ctx_rtu->parity, + ctx_rtu->data_bit, + ctx_rtu->stop_bit); + } + + /* O_NOCTTY 标志告诉 UNIX 此程序不希望成为该端口的"控制终端"。 + 如果不指定此标志,任何输入(如键盘中止信号等)都会影响你的进程。 + + 在规范输入模式或通过 open 或 fcntl 设置了 NONBLOCK 选项时, + 超时会被忽略 */ + flags = O_RDWR | O_NOCTTY | O_NONBLOCK; + flags |= O_CLOEXEC; + + ctx->s = open(ctx_rtu->device, flags); + if (ctx->s < 0) { + if (ctx->debug) { + LOG_E( + "ERROR Can't open the device %s (%s)", + ctx_rtu->device, + strerror(errno)); + } + return -1; + } + + /* 保存原始设置 */ + ioctl(ctx->s, TCGETS2, &ctx_rtu->old_tios); + + memset(&tios, 0, sizeof(tios)); + + /* C_ISPEED 输入波特率(新接口) + C_OSPEED 输出波特率(新接口) + */ + + /* 设置波特率 */ + + tios.c_cflag |= BOTHER; /* 允许自定义波特率 */ + tios.c_ispeed = ctx_rtu->baud; /* 设置输入波特率 */ + tios.c_ospeed = ctx_rtu->baud; /* 设置输出波特率 */ + + /* C_CFLAG 控制选项 + CLOCAL 本地线路 - 不改变端口的"所有者" + CREAD 启用接收器 + */ + tios.c_cflag |= (CREAD | CLOCAL); + /* CSIZE, HUPCL, CRTSCTS(硬件流控制) */ + + /* 设置数据位(5、6、7、8位) + CSIZE 数据位的位掩码 + */ + tios.c_cflag &= ~CSIZE; + switch (ctx_rtu->data_bit) { + case 5: + tios.c_cflag |= CS5; + break; + case 6: + tios.c_cflag |= CS6; + break; + case 7: + tios.c_cflag |= CS7; + break; + case 8: + default: + tios.c_cflag |= CS8; + break; + } + + /* 停止位(1 或 2) */ + if (ctx_rtu->stop_bit == 1) + tios.c_cflag &= ~CSTOPB; + else /* 2 */ + tios.c_cflag |= CSTOPB; + + /* PARENB 启用奇偶校验位 + PARODD 使用奇校验而非偶校验 */ + if (ctx_rtu->parity == 'N') { + /* 无校验 */ + tios.c_cflag &= ~PARENB; + } else if (ctx_rtu->parity == 'E') { + /* 偶校验 */ + tios.c_cflag |= PARENB; + tios.c_cflag &= ~PARODD; + } else { + /* 奇校验 */ + tios.c_cflag |= PARENB; + tios.c_cflag |= PARODD; + } + + /* 如需更多信息,请阅读 termios 的手册页。 */ + + /* 此字段在 POSIX 系统上不使用 + tios.c_line = 0; + */ + + /* C_LFLAG 行选项 + + ISIG 启用 SIGINTR、SIGSUSP、SIGDSUSP 和 SIGQUIT 信号 + ICANON 启用规范输入(否则为原始输入) + XCASE 映射大写\小写(已过时) + ECHO 启用输入字符的回显 + ECHOE 将擦除字符回显为 BS-SP-BS + ECHOK 在终止字符后回显 NL + ECHONL 回显 NL + NOFLSH 禁用中断或退出字符后的输入缓冲区刷新 + IEXTEN 启用扩展功能 + ECHOCTL 将控制字符回显为 ^char,删除为 ~? + ECHOPRT 将擦除字符回显为被擦除的字符 + ECHOKE 行终止时 BS-SP-BS 整行 + FLUSHO 输出正在刷新 + PENDIN 在下一次读取或输入字符时重新输入挂起的输入 + TOSTOP 为后台输出发送 SIGTTOU + + 规范输入是面向行的。输入字符被放入缓冲区, + 用户可以交互式编辑,直到收到 CR(回车)或 LF(换行)字符。 + + 原始输入是未经处理的。输入字符按接收到的原样传递。 + 使用原始输入时,通常需要取消 ICANON、ECHO、ECHOE 和 ISIG 选项 + */ + + /* 原始输入 */ + tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + /* C_IFLAG 输入选项 + + 常量 描述 + INPCK 启用奇偶校验检查 + IGNPAR 忽略奇偶校验错误 + PARMRK 标记奇偶校验错误 + ISTRIP 剥离奇偶校验位 + IXON 启用软件流控制(输出方向) + IXOFF 启用软件流控制(输入方向) + IXANY 允许任意字符重新启动流 + IGNBRK 忽略中断条件 + BRKINT 检测到中断条件时发送 SIGINT + INLCR 将 NL 映射为 CR + IGNCR 忽略 CR + ICRNL 将 CR 映射为 NL + IUCLC 将大写映射为小写 + IMAXBEL 输入行过长时回显 BEL + */ + if (ctx_rtu->parity == 'N') { + /* 无校验 */ + tios.c_iflag &= ~INPCK; + } else { + tios.c_iflag |= INPCK; + } + + /* 禁用软件流控制 */ + tios.c_iflag &= ~(IXON | IXOFF | IXANY); + + /* C_OFLAG 输出选项 + OPOST 后处理输出(未设置 = 原始输出) + ONLCR 将 NL 映射为 CR-NL + + ONCLR 和其他选项需要启用 OPOST + */ + + /* 原始输出 */ + tios.c_oflag &= ~OPOST; + + /* C_CC 控制字符 + VMIN 读取的最小字符数 + VTIME 等待数据的时间(十分之一秒) + + UNIX 串行接口驱动程序提供指定字符和包超时的能力。 + c_cc 数组中的两个元素用于超时:VMIN 和 VTIME。 + 在规范输入模式或通过 open 或 fcntl 设置了 NONBLOCK 选项时,超时会被忽略。 + + VMIN 指定要读取的最小字符数。如果设置为 0, + 则 VTIME 值指定等待每个字符读取的时间。 + 注意,这并不意味着对 N 字节的读取调用会等待 N 个字符到来。 + 相反,超时仅应用于第一个字符,读取调用将返回 + 立即可用的字符数(最多达到你请求的数量)。 + + 如果 VMIN 非零,VTIME 指定等待第一个字符读取的时间。 + 如果在给定时间内读到了一个字符,任何读取操作将阻塞(等待) + 直到所有 VMIN 个字符都被读取。也就是说,一旦第一个字符被读取, + 串行接口驱动程序期望接收一个完整的字符包(总共 VMIN 字节)。 + 如果在允许的时间内没有读到字符,则读取调用返回 0。 + 这种方法允许你告诉串行驱动程序你需要正好 N 个字节, + 任何读取调用将返回 0 或 N 个字节。然而,超时仅应用于 + 第一个字符的读取,因此如果由于某种原因驱动程序在 N 字节包中 + 漏掉了一个字符,读取调用可能会永远阻塞等待额外的输入字符。 + + VTIME 指定等待输入字符的时间,以十分之一秒为单位。 + 如果 VTIME 设置为 0(默认值),读取将无限期阻塞(等待), + 除非在端口上通过 open 或 fcntl 设置了 NONBLOCK 选项。 + */ + /* 因为我们使用 open 并设置了 NONBLOCK 选项,所以这里不使用 */ + tios.c_cc[VMIN] = 0; + tios.c_cc[VTIME] = 0; + + if (ioctl(ctx->s, TCSETS2, &tios) < 0) { + close(ctx->s); + ctx->s = -1; + return -1; + } + if (ctx->debug) { + ioctl(ctx->s, TCGETS2, &tios); + if (tios.c_ispeed != (unsigned int)ctx_rtu->baud) { + LOG_E( + "WARNING Failed to set baud rate %d (%d used)", + ctx_rtu->baud, + tios.c_ispeed); + } + } + + return 0; +} + +// FIXME 在重写 Windows RTU 后端之前的临时解决方案 +static unsigned int _modbus_rtu_is_connected(modbus_t *ctx) +{ + return ctx->s >= 0; +} + +int modbus_rtu_set_serial_mode(modbus_t *ctx, int mode) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCSRS485 + modbus_rtu_t *ctx_rtu = ctx->backend_data; + struct serial_rs485 rs485conf; + + if (mode == MODBUS_RTU_RS485) { + // 获取当前 RS485 配置 + if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) { + return -1; + } + // 设置 RS485 模式 + rs485conf.flags |= SER_RS485_ENABLED; + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { + return -1; + } + + ctx_rtu->serial_mode = MODBUS_RTU_RS485; + return 0; + } else if (mode == MODBUS_RTU_RS232) { + /* 仅在需要时关闭 RS485 模式 */ + if (ctx_rtu->serial_mode == MODBUS_RTU_RS485) { + /* 避免调用 ioctl,因为在某些 RS232 端口上可能失败 */ + if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) { + return -1; + } + rs485conf.flags &= ~SER_RS485_ENABLED; + if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) { + return -1; + } + } + ctx_rtu->serial_mode = MODBUS_RTU_RS232; + return 0; + } +#else + if (ctx->debug) { + LOG_E("This function isn't supported on your platform"); + } + errno = ENOTSUP; + return -1; +#endif + } + + /* 错误的后端类型或指定了无效的模式 */ + errno = EINVAL; + return -1; +} + +int modbus_rtu_get_serial_mode(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCSRS485 + modbus_rtu_t *ctx_rtu = ctx->backend_data; + return ctx_rtu->serial_mode; +#else + if (ctx->debug) { + LOG_E("This function isn't supported on your platform"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_get_rts(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + return ctx_rtu->rts; +#else + if (ctx->debug) { + LOG_E("This function isn't supported on your platform"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_set_rts(modbus_t *ctx, int mode) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + if (mode == MODBUS_RTU_RTS_NONE || mode == MODBUS_RTU_RTS_UP || + mode == MODBUS_RTU_RTS_DOWN) { + ctx_rtu->rts = mode; + + /* 设置 RTS 位,以避免占用 RS485 总线 */ + ctx_rtu->set_rts(ctx, ctx_rtu->rts != MODBUS_RTU_RTS_UP); + + return 0; + } else { + errno = EINVAL; + return -1; + } +#else + if (ctx->debug) { + LOG_E("This function isn't supported on your platform"); + } + errno = ENOTSUP; + return -1; +#endif + } + /* 错误的后端类型或指定了无效的模式 */ + errno = EINVAL; + return -1; +} + +int modbus_rtu_set_custom_rts(modbus_t *ctx, void (*set_rts)(modbus_t *ctx, int on)) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu = ctx->backend_data; + ctx_rtu->set_rts = set_rts; + return 0; +#else + if (ctx->debug) { + LOG_E("This function isn't supported on your platform"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_get_rts_delay(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu; + ctx_rtu = (modbus_rtu_t *) ctx->backend_data; + return ctx_rtu->rts_delay; +#else + if (ctx->debug) { + LOG_E("This function isn't supported on your platform"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +int modbus_rtu_set_rts_delay(modbus_t *ctx, int us) +{ + if (ctx == NULL || us < 0) { + errno = EINVAL; + return -1; + } + + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU) { +#if HAVE_DECL_TIOCM_RTS + modbus_rtu_t *ctx_rtu; + ctx_rtu = (modbus_rtu_t *) ctx->backend_data; + ctx_rtu->rts_delay = us; + return 0; +#else + if (ctx->debug) { + LOG_E("This function isn't supported on your platform"); + } + errno = ENOTSUP; + return -1; +#endif + } else { + errno = EINVAL; + return -1; + } +} + +static void _modbus_rtu_close(modbus_t *ctx) +{ + /* 在 RTU 模式下恢复线路设置并关闭文件描述符 */ + modbus_rtu_t *ctx_rtu = ctx->backend_data; + + if (ctx->s >= 0) { + ioctl(ctx->s, TCSETS2, &ctx_rtu->old_tios); + close(ctx->s); + ctx->s = -1; + } +} + +static int _modbus_rtu_flush(modbus_t *ctx) +{ + return tcflush(ctx->s, TCIOFLUSH); +} + +static int +_modbus_rtu_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read) +{ + int s_rc; + while ((s_rc = select(ctx->s + 1, rset, NULL, NULL, tv)) == -1) { + if (errno == EINTR) { + if (ctx->debug) { + LOG_E("A non blocked signal was caught"); + } + /* 发生错误后需要重新设置 */ + FD_ZERO(rset); + if (ctx->s < 0 || ctx->s >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } + FD_SET(ctx->s, rset); + } else { + return -1; + } + } + + if (s_rc == 0) { + /* 超时 */ + errno = ETIMEDOUT; + return -1; + } + + return s_rc; +} + +static void _modbus_rtu_free(modbus_t *ctx) +{ + if (ctx->backend_data) { + free(((modbus_rtu_t *) ctx->backend_data)->device); + free(ctx->backend_data); + } + + free(ctx); +} + +// clang-format off +const modbus_backend_t _modbus_rtu_backend = { + _MODBUS_BACKEND_TYPE_RTU, + _MODBUS_RTU_HEADER_LENGTH, + _MODBUS_RTU_CHECKSUM_LENGTH, + MODBUS_RTU_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_rtu_build_request_basis, + _modbus_rtu_build_response_basis, + _modbus_rtu_get_response_tid, + _modbus_rtu_send_msg_pre, + _modbus_rtu_send, + _modbus_rtu_receive, + _modbus_rtu_recv, + _modbus_rtu_check_integrity, + _modbus_rtu_pre_check_confirmation, + _modbus_rtu_connect, + _modbus_rtu_is_connected, + _modbus_rtu_close, + _modbus_rtu_flush, + _modbus_rtu_select, + _modbus_rtu_free +}; + +// clang-format on + +modbus_t * +modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit) +{ + modbus_t *ctx; + modbus_rtu_t *ctx_rtu; + + /* 检查设备参数 */ + if (device == NULL || *device == 0) { + LOG_E("The device string is empty"); + errno = EINVAL; + return NULL; + } + + /* 检查波特率参数 */ + if (baud == 0) { + LOG_E("The baud rate value must not be zero"); + errno = EINVAL; + return NULL; + } + + ctx = (modbus_t *) malloc(sizeof(modbus_t)); + if (ctx == NULL) { + return NULL; + } + + _modbus_init_common(ctx); + ctx->backend = &_modbus_rtu_backend; + ctx->backend_data = (modbus_rtu_t *) malloc(sizeof(modbus_rtu_t)); + if (ctx->backend_data == NULL) { + modbus_free(ctx); + errno = ENOMEM; + return NULL; + } + ctx_rtu = (modbus_rtu_t *) ctx->backend_data; + + /* 设备名称和字符串结束符 \0 */ + ctx_rtu->device = (char *) malloc((strlen(device) + 1) * sizeof(char)); + if (ctx_rtu->device == NULL) { + modbus_free(ctx); + errno = ENOMEM; + return NULL; + } + + strcpy(ctx_rtu->device, device); + + ctx_rtu->baud = baud; + if (parity == 'N' || parity == 'E' || parity == 'O') { + ctx_rtu->parity = parity; + } else { + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + ctx_rtu->data_bit = data_bit; + ctx_rtu->stop_bit = stop_bit; + +#if HAVE_DECL_TIOCSRS485 + /* 默认设置为 RS232 模式 */ + ctx_rtu->serial_mode = MODBUS_RTU_RS232; +#endif + +#if HAVE_DECL_TIOCM_RTS + /* 默认情况下 RTS 功能已设置 */ + ctx_rtu->rts = MODBUS_RTU_RTS_NONE; + + /* 计算发送一个字节的估计时间(微秒) */ + ctx_rtu->onebyte_time = + 1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud; + + /* 默认使用内部函数来设置 RTS */ + ctx_rtu->set_rts = _modbus_rtu_ioctl_rts; + + /* 切换 RTS 引脚时发送前后的延迟时间 */ + ctx_rtu->rts_delay = ctx_rtu->onebyte_time; +#endif + + ctx_rtu->confirmation_to_ignore = FALSE; + + return ctx; +} diff --git a/src/protocol/libmodbus/src/modbus-tcp.c b/src/protocol/libmodbus/src/modbus-tcp.c new file mode 100644 index 0000000..622b9f9 --- /dev/null +++ b/src/protocol/libmodbus/src/modbus-tcp.c @@ -0,0 +1,888 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +// clang-format off +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +# include +# include + +#include +#include +# include +# include +# include + +#if !defined(MSG_NOSIGNAL) +#define MSG_NOSIGNAL 0 +#endif +// clang-format on + +#include "modbus-private.h" + +#include "myLog.h" +#include "modbus-tcp-private.h" +#include "modbus-tcp.h" + +static int _modbus_set_slave(modbus_t *ctx, int slave) +{ + int max_slave = (ctx->quirks & MODBUS_QUIRK_MAX_SLAVE) ? 255 : 247; + + /* 广播地址为 0(MODBUS_BROADCAST_ADDRESS) */ + if (slave >= 0 && slave <= max_slave) { + ctx->slave = slave; + } else if (slave == MODBUS_TCP_SLAVE) { + /* 特殊值 MODBUS_TCP_SLAVE(0xFF)可在 TCP 模式下用于恢复默认值 */ + ctx->slave = slave; + } else { + errno = EINVAL; + return -1; + } + + return 0; +} + +/* 构建 TCP 请求报文头(MBAP报文头) */ +static int _modbus_tcp_build_request_basis( + modbus_t *ctx, int function, int addr, int nb, uint8_t *req) +{ + modbus_tcp_t *ctx_tcp = ctx->backend_data; + + /* 递增事务ID(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; + + /* Modbus 协议标识符 */ + req[2] = 0; + req[3] = 0; + + /* 长度将在后面的 set_req_length_tcp 中通过偏移量 4 和 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 _MODBUS_TCP_PRESET_REQ_LENGTH; +} + +/* 构建 TCP 响应报文头(MBAP报文头) */ +static int _modbus_tcp_build_response_basis(sft_t *sft, uint8_t *rsp) +{ + /* 摘自《MODBUS Messaging on TCP/IP Implementation Guide V1.0b》(第23/46页): + 事务标识符用于将未来的响应与请求关联起来。 */ + rsp[0] = sft->t_id >> 8; + rsp[1] = sft->t_id & 0x00ff; + + /* Modbus 协议标识符 */ + rsp[2] = 0; + rsp[3] = 0; + + /* 长度将在后面的 send_msg 中设置(偏移量 4 和 5) */ + + /* 从站ID 从请求指示中复制 */ + rsp[6] = sft->slave; + rsp[7] = sft->function; + + return _MODBUS_TCP_PRESET_RSP_LENGTH; +} + +static int _modbus_tcp_get_response_tid(const uint8_t *req) +{ + return (req[0] << 8) + req[1]; +} + +static int _modbus_tcp_send_msg_pre(uint8_t *req, int req_length) +{ + /* 从消息长度中减去报文头长度 */ + int mbap_length = req_length - 6; + + req[4] = mbap_length >> 8; + req[5] = mbap_length & 0x00FF; + + return req_length; +} + +static ssize_t _modbus_tcp_send(modbus_t *ctx, const uint8_t *req, int req_length) +{ + /* MSG_NOSIGNAL + 请求在面向流的套接字上发生错误时不发送 SIGPIPE 信号, + 当另一端断开连接时仍然返回 EPIPE 错误。 */ + return send(ctx->s, (const char *) req, req_length, MSG_NOSIGNAL); +} + +static int _modbus_tcp_receive(modbus_t *ctx, uint8_t *req) +{ + return _modbus_receive_msg(ctx, req, MSG_INDICATION); +} + +static ssize_t _modbus_tcp_recv(modbus_t *ctx, uint8_t *rsp, int rsp_length) +{ + return recv(ctx->s, (char *) rsp, rsp_length, 0); +} + +static int _modbus_tcp_check_integrity(modbus_t *ctx, uint8_t *msg, const int msg_length) +{ + return msg_length; +} + +static int _modbus_tcp_pre_check_confirmation(modbus_t *ctx, + const uint8_t *req, + const uint8_t *rsp, + int rsp_length) +{ + unsigned int protocol_id; + /* 检查事务ID(Transaction ID) */ + if (req[0] != rsp[0] || req[1] != rsp[1]) { + if (ctx->debug) { + LOG_E( + "Invalid transaction ID received 0x%X (not 0x%X)", + (rsp[0] << 8) + rsp[1], + (req[0] << 8) + req[1]); + } + errno = EMBBADDATA; + return -1; + } + + /* 检查协议ID */ + protocol_id = (rsp[2] << 8) + rsp[3]; + if (protocol_id != 0x0) { + if (ctx->debug) { + LOG_E("Invalid protocol ID received 0x%X (not 0x0)", protocol_id); + } + errno = EMBBADDATA; + return -1; + } + + return 0; +} + +static int _modbus_tcp_set_ipv4_options(int s) +{ + int rc; + int option; + + /* 设置 TCP 无延迟标志(Nagle算法禁用) */ + /* SOL_TCP = IPPROTO_TCP */ + option = 1; + rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const void *)&option, sizeof(int)); + if (rc == -1) { + return -1; + } + + /** + * Cygwin 定义了 IPTOS_LOWDELAY 但无法处理该标志, + * 因此需要绕过这个问题。 + **/ + /* 设置 IP 低延迟选项 */ + option = IPTOS_LOWDELAY; + rc = setsockopt(s, IPPROTO_IP, IP_TOS, (const void *)&option, sizeof(int)); + if (rc == -1) { + return -1; + } + + return 0; +} + +static int _connect(int sockfd, + const struct sockaddr *addr, + socklen_t addrlen, + const struct timeval *ro_tv) +{ + int rc = connect(sockfd, addr, addrlen); + + if (rc == -1 && errno == EINPROGRESS) { + fd_set wset; + int optval; + socklen_t optlen = sizeof(optval); + struct timeval tv = *ro_tv; + + /* 等待可写状态 */ + FD_ZERO(&wset); + if (sockfd >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } + FD_SET(sockfd, &wset); + rc = select(sockfd + 1, NULL, &wset, NULL, &tv); + if (rc < 0) { + /* 失败 */ + return -1; + } + + if (rc == 0) { + /* 超时 */ + errno = ETIMEDOUT; + return -1; + } + + /* 如果 SO_ERROR 和 optval 都设置为 0,表示连接已建立 */ + rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *) &optval, &optlen); + if (rc == 0 && optval == 0) { + return 0; + } else { + errno = ECONNREFUSED; + return -1; + } + } + return rc; +} + +/* 建立与 Modbus 服务器的 Modbus TCP 连接 */ +static int _modbus_tcp_connect(modbus_t *ctx) +{ + int rc; + /* 专用于 Internet 套接字地址的 sockaddr 版本(相同大小) */ + struct sockaddr_in addr; + modbus_tcp_t *ctx_tcp = ctx->backend_data; + int flags = SOCK_STREAM; + + flags |= SOCK_CLOEXEC; + + flags |= SOCK_NONBLOCK; + + ctx->s = socket(PF_INET, flags, 0); + if (ctx->s < 0) { + return -1; + } + + if (ctx->s >= FD_SETSIZE) { + if (ctx->debug) + { + LOG_E("ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)", ctx->s, FD_SETSIZE); + } + close(ctx->s); + ctx->s = -1; + errno = EINVAL; + return -1; + } + + rc = _modbus_tcp_set_ipv4_options(ctx->s); + if (rc == -1) { + close(ctx->s); + ctx->s = -1; + return -1; + } + + if (ctx->debug) { + LOG_I("Connecting to %s:%d\n", ctx_tcp->ip, ctx_tcp->port); + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(ctx_tcp->port); + rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr)); + if (rc <= 0) { + if (ctx->debug) { + LOG_E("Invalid IP address: %s", ctx_tcp->ip); + } + close(ctx->s); + ctx->s = -1; + return -1; + } + + rc = + _connect(ctx->s, (struct sockaddr *) &addr, sizeof(addr), &ctx->response_timeout); + if (rc == -1) { + close(ctx->s); + ctx->s = -1; + return -1; + } + + return 0; +} + +/* 建立与 Modbus 服务器的 Modbus TCP PI(协议无关)连接 */ +static int _modbus_tcp_pi_connect(modbus_t *ctx) +{ + int rc; + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + struct addrinfo ai_hints; + modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data; + + memset(&ai_hints, 0, sizeof(ai_hints)); +#ifdef AI_ADDRCONFIG + ai_hints.ai_flags |= AI_ADDRCONFIG; +#endif + ai_hints.ai_family = AF_UNSPEC; + ai_hints.ai_socktype = SOCK_STREAM; + ai_hints.ai_addr = NULL; + ai_hints.ai_canonname = NULL; + ai_hints.ai_next = NULL; + + ai_list = NULL; + rc = getaddrinfo(ctx_tcp_pi->node, ctx_tcp_pi->service, &ai_hints, &ai_list); + if (rc != 0) { + if (ctx->debug) { + LOG_E("Error returned by getaddrinfo: %s", gai_strerror(rc)); + } + freeaddrinfo(ai_list); + errno = ECONNREFUSED; + return -1; + } + + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) { + int flags = ai_ptr->ai_socktype; + int s; + + flags |= SOCK_CLOEXEC; + + flags |= SOCK_NONBLOCK; + + s = socket(ai_ptr->ai_family, flags, ai_ptr->ai_protocol); + if (s < 0) + continue; + + if (s >= FD_SETSIZE) { + if (ctx->debug) + { + LOG_E("ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)", s, FD_SETSIZE); + } + close(s); + continue; + } + + if (ai_ptr->ai_family == AF_INET) + _modbus_tcp_set_ipv4_options(s); + + if (ctx->debug) { + LOG_I("Connecting to [%s]:%s\n", ctx_tcp_pi->node, ctx_tcp_pi->service); + } + + rc = _connect(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen, &ctx->response_timeout); + if (rc == -1) { + close(s); + continue; + } + + ctx->s = s; + break; + } + + freeaddrinfo(ai_list); + + if (ctx->s < 0) { + return -1; + } + + return 0; +} + +static unsigned int _modbus_tcp_is_connected(modbus_t *ctx) +{ + return ctx->s >= 0; +} + +/* 在 TCP 模式下关闭网络连接和套接字 */ +static void _modbus_tcp_close(modbus_t *ctx) +{ + if (ctx->s >= 0) { + shutdown(ctx->s, SHUT_RDWR); + close(ctx->s); + ctx->s = -1; + } +} + +static int _modbus_tcp_flush(modbus_t *ctx) +{ + int rc; + // 使用无符号16位整数来降低溢出风险。flush 函数不期望处理大量数据(> 2GB)。 + uint16_t rc_sum = 0; + + do { + /* 从套接字中取出垃圾数据 */ + char devnull[MODBUS_TCP_MAX_ADU_LENGTH]; + rc = recv(ctx->s, devnull, MODBUS_TCP_MAX_ADU_LENGTH, MSG_DONTWAIT); + if (rc > 0) { + // 在累加之前检查是否溢出 + if (rc_sum <= UINT16_MAX - rc) { + rc_sum += rc; + } else { + // 处理溢出 + ctx->error_recovery = MODBUS_ERROR_RECOVERY_PROTOCOL; + errno = EOVERFLOW; + return -1; + } + } + } while (rc == MODBUS_TCP_MAX_ADU_LENGTH); + + /* 类型转换是安全的:uint16_t 总是能放入 int,溢出已在上面检查过 */ + return (int) rc_sum; +} + +/* 在 TCP 模式下监听来自一个或多个 Modbus 主站的请求 */ +int modbus_tcp_listen(modbus_t *ctx, int nb_connection) +{ + int new_s; + int enable; + int flags; + struct sockaddr_in addr; + modbus_tcp_t *ctx_tcp; + int rc; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx_tcp = ctx->backend_data; + + flags = SOCK_STREAM; + + flags |= SOCK_CLOEXEC; + + new_s = socket(PF_INET, flags, IPPROTO_TCP); + if (new_s == -1) { + return -1; + } + + enable = 1; + if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, (const void *)&enable, sizeof(enable)) == -1) { + close(new_s); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + /* 如果 Modbus 端口号小于 1024,则需要 setuid root 权限 */ + addr.sin_port = htons(ctx_tcp->port); + if (ctx_tcp->ip[0] == '0') { + /* 监听所有地址 */ + addr.sin_addr.s_addr = htonl(INADDR_ANY); + } else { + /* 仅监听指定的 IP 地址 */ + rc = inet_pton(addr.sin_family, ctx_tcp->ip, &(addr.sin_addr)); + if (rc <= 0) { + if (ctx->debug) { + LOG_E("Invalid IP address: %s", ctx_tcp->ip); + } + close(new_s); + return -1; + } + } + + if (bind(new_s, (struct sockaddr *) &addr, sizeof(addr)) == -1) { + close(new_s); + return -1; + } + + if (listen(new_s, nb_connection) == -1) { + close(new_s); + return -1; + } + + return new_s; +} + +int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection) +{ + int rc; + struct addrinfo *ai_list; + struct addrinfo *ai_ptr; + struct addrinfo ai_hints; + const char *node; + const char *service; + int new_s; + modbus_tcp_pi_t *ctx_tcp_pi; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx_tcp_pi = ctx->backend_data; + + if (ctx_tcp_pi->node[0] == 0) { + node = NULL; /* == 任意地址 */ + } else { + node = ctx_tcp_pi->node; + } + + if (ctx_tcp_pi->service[0] == 0) { + service = "502"; + } else { + service = ctx_tcp_pi->service; + } + + memset(&ai_hints, 0, sizeof(ai_hints)); + /* 如果 node 不为 NULL,则 AI_PASSIVE 标志会被忽略 */ + ai_hints.ai_flags |= AI_PASSIVE; +#ifdef AI_ADDRCONFIG + ai_hints.ai_flags |= AI_ADDRCONFIG; +#endif + ai_hints.ai_family = AF_UNSPEC; + ai_hints.ai_socktype = SOCK_STREAM; + ai_hints.ai_addr = NULL; + ai_hints.ai_canonname = NULL; + ai_hints.ai_next = NULL; + + ai_list = NULL; + rc = getaddrinfo(node, service, &ai_hints, &ai_list); + if (rc != 0) { + if (ctx->debug) { + LOG_E("Error returned by getaddrinfo: %s", gai_strerror(rc)); + } + if (ai_list != NULL) { + freeaddrinfo(ai_list); + } + errno = ECONNREFUSED; + return -1; + } + + new_s = -1; + for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) { + int flags = ai_ptr->ai_socktype; + int s; + + flags |= SOCK_CLOEXEC; + + s = socket(ai_ptr->ai_family, flags, ai_ptr->ai_protocol); + if (s < 0) { + if (ctx->debug) { + perror("socket"); + } + continue; + } else { + int enable = 1; + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const void *)&enable, sizeof(enable)); + if (rc != 0) { + close(s); + if (ctx->debug) { + perror("setsockopt"); + } + continue; + } + } + + rc = bind(s, ai_ptr->ai_addr, ai_ptr->ai_addrlen); + if (rc != 0) { + close(s); + if (ctx->debug) { + perror("bind"); + } + continue; + } + + rc = listen(s, nb_connection); + if (rc != 0) { + close(s); + if (ctx->debug) { + perror("listen"); + } + continue; + } + + new_s = s; + break; + } + freeaddrinfo(ai_list); + + if (new_s < 0) { + return -1; + } + + return new_s; +} + +int modbus_tcp_accept(modbus_t *ctx, int *s) +{ + struct sockaddr_in addr; + socklen_t addrlen; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + addrlen = sizeof(addr); + /* 继承套接字标志并使用 accept4 调用 */ + ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC); + + if (ctx->s < 0) { + return -1; + } + + if (ctx->s >= FD_SETSIZE) { + if (ctx->debug) + { + LOG_E("ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)", ctx->s, FD_SETSIZE); + } + close(ctx->s); + ctx->s = -1; + errno = EINVAL; + return -1; + } + + if (ctx->debug) { + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &(addr.sin_addr), buf, INET_ADDRSTRLEN) == NULL) { + LOG_E("Client connection accepted from unparsable IP."); + } else { + LOG_I("Client connection accepted from %s.\n", buf); + } + } + + return ctx->s; +} + +int modbus_tcp_pi_accept(modbus_t *ctx, int *s) +{ + struct sockaddr_in6 addr; + socklen_t addrlen; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + addrlen = sizeof(addr); + /* 继承套接字标志并使用 accept4 调用 */ + ctx->s = accept4(*s, (struct sockaddr *) &addr, &addrlen, SOCK_CLOEXEC); + + if (ctx->s < 0) { + return -1; + } + + if (ctx->s >= FD_SETSIZE) { + if (ctx->debug) + { + LOG_E("ERROR Socket descriptor %d exceeds FD_SETSIZE (%d)", ctx->s, FD_SETSIZE); + } + close(ctx->s); + ctx->s = -1; + errno = EINVAL; + return -1; + } + + if (ctx->debug) { + char buf[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, &(addr.sin6_addr), buf, INET6_ADDRSTRLEN) == NULL) { + LOG_E("Client connection accepted from unparsable IP."); + } else { + LOG_I("Client connection accepted from %s.\n", buf); + } + } + + return ctx->s; +} + +static int +_modbus_tcp_select(modbus_t *ctx, fd_set *rset, struct timeval *tv, int length_to_read) +{ + int s_rc; + while ((s_rc = select(ctx->s + 1, rset, NULL, NULL, tv)) == -1) { + if (errno == EINTR) { + if (ctx->debug) { + LOG_E("A non blocked signal was caught"); + } + /* 发生错误后需要重新设置 */ + FD_ZERO(rset); + if (ctx->s < 0 || ctx->s >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } + FD_SET(ctx->s, rset); + } else { + return -1; + } + } + + if (s_rc == 0) { + errno = ETIMEDOUT; + return -1; + } + + return s_rc; +} + +static void _modbus_tcp_free(modbus_t *ctx) +{ + if (ctx->backend_data) { + free(ctx->backend_data); + } + free(ctx); +} + +static void _modbus_tcp_pi_free(modbus_t *ctx) +{ + if (ctx->backend_data) { + modbus_tcp_pi_t *ctx_tcp_pi = ctx->backend_data; + free(ctx_tcp_pi->node); + free(ctx_tcp_pi->service); + free(ctx->backend_data); + } + + free(ctx); +} + +// clang-format off +const modbus_backend_t _modbus_tcp_backend = { + _MODBUS_BACKEND_TYPE_TCP, + _MODBUS_TCP_HEADER_LENGTH, + _MODBUS_TCP_CHECKSUM_LENGTH, + MODBUS_TCP_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_tcp_build_request_basis, + _modbus_tcp_build_response_basis, + _modbus_tcp_get_response_tid, + _modbus_tcp_send_msg_pre, + _modbus_tcp_send, + _modbus_tcp_receive, + _modbus_tcp_recv, + _modbus_tcp_check_integrity, + _modbus_tcp_pre_check_confirmation, + _modbus_tcp_connect, + _modbus_tcp_is_connected, + _modbus_tcp_close, + _modbus_tcp_flush, + _modbus_tcp_select, + _modbus_tcp_free +}; + +const modbus_backend_t _modbus_tcp_pi_backend = { + _MODBUS_BACKEND_TYPE_TCP, + _MODBUS_TCP_HEADER_LENGTH, + _MODBUS_TCP_CHECKSUM_LENGTH, + MODBUS_TCP_MAX_ADU_LENGTH, + _modbus_set_slave, + _modbus_tcp_build_request_basis, + _modbus_tcp_build_response_basis, + _modbus_tcp_get_response_tid, + _modbus_tcp_send_msg_pre, + _modbus_tcp_send, + _modbus_tcp_receive, + _modbus_tcp_recv, + _modbus_tcp_check_integrity, + _modbus_tcp_pre_check_confirmation, + _modbus_tcp_pi_connect, + _modbus_tcp_is_connected, + _modbus_tcp_close, + _modbus_tcp_flush, + _modbus_tcp_select, + _modbus_tcp_pi_free +}; + +// clang-format on + +modbus_t *modbus_new_tcp(const char *ip, int port) +{ + modbus_t *ctx; + modbus_tcp_t *ctx_tcp; + size_t dest_size; + size_t ret_size; + + ctx = (modbus_t *) malloc(sizeof(modbus_t)); + if (ctx == NULL) { + return NULL; + } + + _modbus_init_common(ctx); + ctx->backend = &_modbus_tcp_backend; + + ctx->backend_data = (modbus_tcp_t *) malloc(sizeof(modbus_tcp_t)); + if (ctx->backend_data == NULL) { + modbus_free(ctx); + errno = ENOMEM; + return NULL; + } + ctx_tcp = (modbus_tcp_t *) ctx->backend_data; + + if (ip != NULL) { + dest_size = sizeof(char) * 16; + ret_size = strlcpy(ctx_tcp->ip, ip, dest_size); + if (ret_size == 0) { + LOG_E("The IP string is empty"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + + if (ret_size >= dest_size) { + LOG_E("The IP string has been truncated"); + modbus_free(ctx); + errno = EINVAL; + return NULL; + } + } else { + ctx_tcp->ip[0] = '0'; + } + ctx_tcp->port = port; + ctx_tcp->t_id = 0; + + return ctx; +} + +modbus_t *modbus_new_tcp_pi(const char *node, const char *service) +{ + modbus_t *ctx; + modbus_tcp_pi_t *ctx_tcp_pi; + + ctx = (modbus_t *) malloc(sizeof(modbus_t)); + if (ctx == NULL) { + return NULL; + } + _modbus_init_common(ctx); + + /* 之后可以更改此值以访问远程串行 Modbus 设备 */ + ctx->slave = MODBUS_TCP_SLAVE; + + ctx->backend = &_modbus_tcp_pi_backend; + + ctx->backend_data = (modbus_tcp_pi_t *) malloc(sizeof(modbus_tcp_pi_t)); + if (ctx->backend_data == NULL) { + modbus_free(ctx); + errno = ENOMEM; + return NULL; + } + ctx_tcp_pi = (modbus_tcp_pi_t *) ctx->backend_data; + ctx_tcp_pi->node = NULL; + ctx_tcp_pi->service = NULL; + + if (node != NULL) { + ctx_tcp_pi->node = strdup(node); + } else { + /* node 参数可以为空,表示任意主机 */ + ctx_tcp_pi->node = strdup(""); + } + + if (ctx_tcp_pi->node == NULL) { + modbus_free(ctx); + errno = ENOMEM; + return NULL; + } + + if (service != NULL && service[0] != '\0') { + ctx_tcp_pi->service = strdup(service); + } else { + /* 默认的 Modbus 端口号 */ + ctx_tcp_pi->service = strdup("502"); + } + + if (ctx_tcp_pi->service == NULL) { + modbus_free(ctx); + errno = ENOMEM; + return NULL; + } + + ctx_tcp_pi->t_id = 0; + + return ctx; +} diff --git a/src/protocol/libmodbus/src/modbus.c b/src/protocol/libmodbus/src/modbus.c new file mode 100644 index 0000000..f62f896 --- /dev/null +++ b/src/protocol/libmodbus/src/modbus.c @@ -0,0 +1,2075 @@ +/* + * Copyright © Stéphane Raimbault + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * 本库实现了 Modbus 协议。 + * http://libmodbus.org/ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +#include "myLog.h" +#include "modbus-private.h" +#include "modbus.h" + +/* 内部使用 */ +#define MSG_LENGTH_UNDEFINED -1 + +/* 导出的版本号 */ +const unsigned int libmodbus_version_major = LIBMODBUS_VERSION_MAJOR; +const unsigned int libmodbus_version_minor = LIBMODBUS_VERSION_MINOR; +const unsigned int libmodbus_version_micro = LIBMODBUS_VERSION_MICRO; + +/* RTU 和 TCP 的最大 ADU 长度之间的最大值(取 TCP 的值) */ +#define MAX_MESSAGE_LENGTH 260 + +/* 解析查询分为3个步骤 */ +typedef enum { + _STEP_FUNCTION, + _STEP_META, + _STEP_DATA +} _step_t; + +const char *modbus_strerror(int errnum) +{ + switch (errnum) { + case EMBXILFUN: + return "Illegal function"; + case EMBXILADD: + return "Illegal data address"; + case EMBXILVAL: + return "Illegal data value"; + case EMBXSFAIL: + return "Slave device or server failure"; + case EMBXACK: + return "Acknowledge"; + case EMBXSBUSY: + return "Slave device or server is busy"; + case EMBXNACK: + return "Negative acknowledge"; + case EMBXMEMPAR: + return "Memory parity error"; + case EMBXGPATH: + return "Gateway path unavailable"; + case EMBXGTAR: + return "Target device failed to respond"; + case EMBBADCRC: + return "Invalid CRC"; + case EMBBADDATA: + return "Invalid data"; + case EMBBADEXC: + return "Invalid exception code"; + case EMBMDATA: + return "Too many data"; + case EMBBADSLAVE: + return "Response not from requested slave"; + default: + return strerror(errnum); + } +} + +void _error_print(modbus_t *ctx, const char *context) +{ + if (ctx->debug) { + if (context != NULL) { + LOG_E("%s: %s", modbus_strerror(errno), context); + } else { + LOG_E("%s", modbus_strerror(errno)); + } + } +} + +static void _sleep_response_timeout(modbus_t *ctx) +{ + /* 响应超时始终为正数 */ + /* 使用 nanosleep 实现的 usleep 源码 */ + struct timespec request, remaining; + request.tv_sec = ctx->response_timeout.tv_sec; + request.tv_nsec = ((long int) ctx->response_timeout.tv_usec) * 1000; + while (nanosleep(&request, &remaining) == -1 && errno == EINTR) { + request = remaining; + } +} + +int modbus_flush(modbus_t *ctx) +{ + int rc; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + rc = ctx->backend->flush(ctx); + if (rc != -1 && ctx->debug) { + /* 并非所有后端都能返回已刷新的字节数 */ + LOG_I("Bytes flushed (%d)\n", rc); + } + return rc; +} + +/* 计算预期响应的长度(包括校验和) */ +static unsigned int compute_response_length_from_request(modbus_t *ctx, uint8_t *req) +{ + int length; + const int offset = ctx->backend->header_length; + + switch (req[offset]) { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: { + /* 报文头 + 值的数量(来自 write_bits 的代码) */ + int nb = (req[offset + 3] << 8) | req[offset + 4]; + length = 2 + (nb / 8) + ((nb % 8) ? 1 : 0); + } break; + case MODBUS_FC_WRITE_AND_READ_REGISTERS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + /* 报文头 + 2 * 值的数量 */ + length = 2 + 2 * (req[offset + 3] << 8 | req[offset + 4]); + break; + case MODBUS_FC_READ_EXCEPTION_STATUS: + length = 3; + break; + case MODBUS_FC_REPORT_SLAVE_ID: + /* 响应是设备特定的(报文头中提供了长度信息) */ + return MSG_LENGTH_UNDEFINED; + case MODBUS_FC_MASK_WRITE_REGISTER: + length = 7; + break; + default: + length = 5; + } + + return offset + length + ctx->backend->checksum_length; +} + +/* 发送请求/响应 */ +static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length) +{ + int rc; + int i; + + msg_length = ctx->backend->send_msg_pre(msg, msg_length); + + if (ctx->debug) { + LOG_I("Sending %d bytes:", msg_length); + for (i = 0; i < msg_length; i++) + printf("[%.2X]", msg[i]); + printf("\n"); + } + + /* 在恢复模式下,写命令会反复发出直到成功!默认禁用。 */ + do { + rc = ctx->backend->send(ctx, msg, msg_length); + if (rc == -1) { + _error_print(ctx, NULL); + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) { + int saved_errno = errno; + + if ((errno == EBADF || errno == ECONNRESET || errno == EPIPE)) { + modbus_close(ctx); + _sleep_response_timeout(ctx); + modbus_connect(ctx); + } else { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + errno = saved_errno; + } + } + } while ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && rc == -1); + + if (rc > 0 && rc != msg_length) { + errno = EMBBADDATA; + return -1; + } + + return rc; +} + +int modbus_send_raw_request_tid(modbus_t *ctx, + const uint8_t *raw_req, + int raw_req_length, + int tid) +{ + sft_t sft; + uint8_t req[MAX_MESSAGE_LENGTH]; + int req_length; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (raw_req_length < 2 || raw_req_length > (MODBUS_MAX_PDU_LENGTH + 1)) { + /* 原始请求必须至少包含功能码和从站地址,且不得超过 + 最大 PDU 长度加上从站地址的长度。 */ + errno = EINVAL; + return -1; + } + + sft.slave = raw_req[0]; + sft.function = raw_req[1]; + /* t_id 保持为零 */ + sft.t_id = tid; + /* 此响应函数仅设置报文头,因此在这里使用很方便 */ + req_length = ctx->backend->build_response_basis(&sft, req); + + if (raw_req_length > 2) { + /* 复制功能码之后的数据 */ + memcpy(req + req_length, raw_req + 2, raw_req_length - 2); + req_length += raw_req_length - 2; + } + + return send_msg(ctx, req, req_length); +} + +int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length) +{ + return modbus_send_raw_request_tid(ctx, raw_req, raw_req_length, 0); +} + +/* + * ---------- 请求 指示 ---------- + * | 客户端 | ---------------------->| 服务器 | + * ---------- 确认 响应 ---------- + */ + +/* 在接收到功能码后计算需要读取的长度 */ +static uint8_t compute_meta_length_after_function(int function, msg_type_t msg_type) +{ + int length; + + if (msg_type == MSG_INDICATION) { + if (function <= MODBUS_FC_WRITE_SINGLE_REGISTER) { + length = 4; + } else if (function == MODBUS_FC_WRITE_MULTIPLE_COILS || + function == MODBUS_FC_WRITE_MULTIPLE_REGISTERS) { + length = 5; + } else if (function == MODBUS_FC_MASK_WRITE_REGISTER) { + length = 6; + } else if (function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { + length = 9; + } else { + /* MODBUS_FC_READ_EXCEPTION_STATUS, MODBUS_FC_REPORT_SLAVE_ID */ + length = 0; + } + } else { + /* MSG_CONFIRMATION */ + switch (function) { + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + length = 4; + break; + case MODBUS_FC_MASK_WRITE_REGISTER: + length = 6; + break; + default: + length = 1; + } + } + + return length; +} + +/* 在元信息(地址、数量等)之后计算需要读取的数据长度 */ +static int +compute_data_length_after_meta(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) +{ + int function = msg[ctx->backend->header_length]; + int length; + + if (msg_type == MSG_INDICATION) { + switch (function) { + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + length = msg[ctx->backend->header_length + 5]; + break; + case MODBUS_FC_WRITE_AND_READ_REGISTERS: + length = msg[ctx->backend->header_length + 9]; + break; + default: + length = 0; + } + } else { + /* MSG_CONFIRMATION */ + if (function <= MODBUS_FC_READ_INPUT_REGISTERS || + function == MODBUS_FC_REPORT_SLAVE_ID || + function == MODBUS_FC_WRITE_AND_READ_REGISTERS) { + length = msg[ctx->backend->header_length + 1]; + } else { + length = 0; + } + } + + length += ctx->backend->checksum_length; + + return length; +} + +/* 等待来自 Modbus 服务器的响应或来自 Modbus 客户端的请求。 + 如果没有回复,此函数会阻塞(3次超时)。 + + 如果成功,函数应返回接收到的字符数, + 并将接收到的消息存储在 uint8_t 数组中。否则返回 -1, + 并设置 errno 为以下定义的值之一: + - ECONNRESET + - EMBBADDATA + - ETIMEDOUT + - read() 或 recv() 错误码 +*/ + +int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) +{ + int rc; + fd_set rset; + struct timeval tv; + struct timeval *p_tv; + unsigned int length_to_read; + int msg_length = 0; + _step_t step; + + if (ctx->debug) { + if (msg_type == MSG_INDICATION) { + LOG_I("Waiting for an indication...\n"); + } else { + LOG_I("Waiting for a confirmation...\n"); + } + } + + if (!ctx->backend->is_connected(ctx)) { + if (ctx->debug) { + LOG_E("ERROR The connection is not established."); + } + return -1; + } + + /* 将文件描述符添加到集合中 */ + FD_ZERO(&rset); + if (ctx->s < 0 || ctx->s >= FD_SETSIZE) { + if (ctx->debug) { + LOG_E("ERROR Invalid socket descriptor %d", ctx->s); + } + errno = EINVAL; + return -1; + } + FD_SET(ctx->s, &rset); + + /* 我们需要逐步分析消息。在第一步,我们希望 + * 到达功能码的位置,因为所有包都包含此信息。 */ + step = _STEP_FUNCTION; + length_to_read = ctx->backend->header_length + 1; + + if (msg_type == MSG_INDICATION) { + /* 等待消息,我们不知道消息何时会到达 */ + if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) { + /* 默认情况下,指示超时未设置 */ + p_tv = NULL; + } else { + /* 等待指示(服务器接收到的请求的名称,参见时序图) */ + tv.tv_sec = ctx->indication_timeout.tv_sec; + tv.tv_usec = ctx->indication_timeout.tv_usec; + p_tv = &tv; + } + } else { + tv.tv_sec = ctx->response_timeout.tv_sec; + tv.tv_usec = ctx->response_timeout.tv_usec; + p_tv = &tv; + } + + while (length_to_read != 0) { + rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read); + if (rc == -1) { + _error_print(ctx, "select"); + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) { + int saved_errno = errno; + + if (errno == ETIMEDOUT) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } else if (errno == EBADF) { + modbus_close(ctx); + modbus_connect(ctx); + } + errno = saved_errno; + } + return -1; + } + + rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read); + if (rc == 0) { + errno = ECONNRESET; + rc = -1; + } + + if (rc == -1) { + _error_print(ctx, "read"); + if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) && + (errno == ECONNRESET || errno == ECONNREFUSED || errno == EBADF)) { + int saved_errno = errno; + modbus_close(ctx); + modbus_connect(ctx); + /* 可能被之前的调用清除 */ + errno = saved_errno; + } + return -1; + } + + /* 显示接收到的每个字符的十六进制代码 */ + if (ctx->debug) { + LOG_I("Received %d bytes:", rc); + int i; + for (i = 0; i < rc; i++) + printf("<%.2X>", msg[msg_length + i]); + } + + /* 累加已接收的字节数 */ + msg_length += rc; + /* 计算剩余字节数 */ + length_to_read -= rc; + + if (length_to_read == 0) { + switch (step) { + case _STEP_FUNCTION: + /* 功能码位置 */ + length_to_read = compute_meta_length_after_function( + msg[ctx->backend->header_length], msg_type); + if (length_to_read != 0) { + step = _STEP_META; + break; + } /* 否则直接切换到下一步 */ + case _STEP_META: + length_to_read = compute_data_length_after_meta(ctx, msg, msg_type); + if ((msg_length + length_to_read) > ctx->backend->max_adu_length) { + errno = EMBBADDATA; + _error_print(ctx, "too many data"); + return -1; + } + step = _STEP_DATA; + break; + default: + break; + } + } + + if (length_to_read > 0 && + (ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) { + /* 如果缓冲区中没有字符,两个连续字节之间允许的超时间隔 + 由 byte_timeout 定义 */ + tv.tv_sec = ctx->byte_timeout.tv_sec; + tv.tv_usec = ctx->byte_timeout.tv_usec; + p_tv = &tv; + } + /* 否则超时不会重新设置,必须在响应超时(仅对确认消息)到期之前 + 读取完整的响应 */ + } + + if (ctx->debug) + printf("\n"); + + return ctx->backend->check_integrity(ctx, msg, msg_length); +} + +/* 接收来自 Modbus 主站的请求 */ +int modbus_receive(modbus_t *ctx, uint8_t *req) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->receive(ctx, req); +} + +/* 接收确认消息。 + + 函数应将读取到的响应存储在 rsp 中,并返回值的数量(位或字)。 + 否则返回 -1 并设置 errno。 + + 此函数不检查确认消息是否与初始请求的预期响应一致。 +*/ +int modbus_receive_confirmation(modbus_t *ctx, uint8_t *rsp) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); +} + +static int check_confirmation(modbus_t *ctx, uint8_t *req, uint8_t *rsp, int rsp_length) +{ + int rc; + int rsp_length_computed; + const unsigned 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 == -1) { + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + return -1; + } + } + + rsp_length_computed = compute_response_length_from_request(ctx, req); + + /* 异常码 */ + if (function >= 0x80) { + if (rsp_length == (int) (offset + 2 + ctx->backend->checksum_length) && + req[offset] == (rsp[offset] - 0x80)) { + /* 收到有效的异常码 */ + + int exception_code = rsp[offset + 1]; + if (exception_code < MODBUS_EXCEPTION_MAX) { + errno = MODBUS_ENOBASE + exception_code; + } else { + errno = EMBBADEXC; + } + _error_print(ctx, NULL); + return -1; + } else { + errno = EMBBADEXC; + _error_print(ctx, NULL); + return -1; + } + } + + /* 检查长度 */ + if ((rsp_length == rsp_length_computed || + rsp_length_computed == MSG_LENGTH_UNDEFINED) && + function < 0x80) { + int req_nb_value; + int rsp_nb_value; + int resp_addr_ok = TRUE; + int resp_data_ok = TRUE; + + /* 检查功能码 */ + if (function != req[offset]) { + if (ctx->debug) + { + LOG_E("Received function not corresponding to the request (0x%X != 0x%X)", function, req[offset]); + } + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + errno = EMBBADDATA; + return -1; + } + + /* 检查值的数量是否与请求一致 */ + switch (function) { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: + /* 读取功能,一个字节中包含8个值 + * (请求中的值数量与响应中的字节计数) */ + 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 MODBUS_FC_WRITE_AND_READ_REGISTERS: + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: + /* 读取功能,1个值 = 2个字节 */ + req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; + rsp_nb_value = (rsp[offset + 1] / 2); + break; + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + /* 请求和响应中的地址必须相等 */ + if ((req[offset + 1] != rsp[offset + 1]) || + (req[offset + 2] != rsp[offset + 2])) { + resp_addr_ok = FALSE; + } + /* N个写功能 */ + req_nb_value = (req[offset + 3] << 8) + req[offset + 4]; + rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4]; + break; + case MODBUS_FC_REPORT_SLAVE_ID: + /* 报告从站ID(接收到的字节数) */ + req_nb_value = rsp_nb_value = rsp[offset + 1]; + break; + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + /* 请求和响应中的地址必须相等 */ + if ((req[offset + 1] != rsp[offset + 1]) || + (req[offset + 2] != rsp[offset + 2])) { + resp_addr_ok = FALSE; + } + /* 请求和响应中的数据必须相等 */ + if ((req[offset + 3] != rsp[offset + 3]) || + (req[offset + 4] != rsp[offset + 4])) { + resp_data_ok = FALSE; + } + /* 1个写功能及其他 */ + req_nb_value = rsp_nb_value = 1; + break; + default: + /* 1个写功能及其他 */ + req_nb_value = rsp_nb_value = 1; + break; + } + + if ((req_nb_value == rsp_nb_value) && (resp_addr_ok == TRUE) && + (resp_data_ok == TRUE)) { + rc = rsp_nb_value; + } else { + if (ctx->debug) { + LOG_E( + "Received data not corresponding to the request (%d != %d)", + rsp_nb_value, + req_nb_value); + } + + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + + errno = EMBBADDATA; + rc = -1; + } + } else { + if (ctx->debug) + { + LOG_E("Message length not corresponding to the computed length (%d != %d)", rsp_length, rsp_length_computed); + } + if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + errno = EMBBADDATA; + rc = -1; + } + + return rc; +} + +static int +response_io_status(uint8_t *tab_io_status, int address, int nb, uint8_t *rsp, int offset) +{ + int shift = 0; + /* 替代 byte(在 Win32 中不允许) */ + int one_byte = 0; + int i; + + for (i = address; i < address + nb; i++) { + one_byte |= tab_io_status[i] << shift; + if (shift == 7) { + /* 字节已满 */ + rsp[offset++] = one_byte; + one_byte = shift = 0; + } else { + shift++; + } + } + + if (shift != 0) + rsp[offset++] = one_byte; + + return offset; +} + +/* 构建异常响应 */ +static int response_exception(modbus_t *ctx, + sft_t *sft, + int exception_code, + uint8_t *rsp, + unsigned int to_flush, + const char *template, + ...) +{ + int rsp_length; + + /* 打印调试消息 */ + if (ctx->debug) { + va_list ap; + char buf[256]; + + va_start(ap, template); + vsnprintf(buf, sizeof(buf), template, ap); + va_end(ap); + LOG_E("%s", buf); + } + + /* 如果需要则刷新 */ + if (to_flush) { + _sleep_response_timeout(ctx); + modbus_flush(ctx); + } + + /* 构建异常响应 */ + sft->function = sft->function + 0x80; + rsp_length = ctx->backend->build_response_basis(sft, rsp); + rsp[rsp_length++] = exception_code; + + return rsp_length; +} + +/* 发送对接收到的请求的响应。 + 分析请求并构造响应。 + + 如果发生错误,此函数会相应地构造错误响应。 +*/ +int modbus_reply(modbus_t *ctx, + const uint8_t *req, + int req_length, + modbus_mapping_t *mb_mapping) +{ + unsigned int offset; + int slave; + int function; + uint16_t address; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + int rsp_length = 0; + sft_t sft; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + offset = ctx->backend->header_length; + slave = req[offset - 1]; + function = req[offset]; + + /* 某些功能码(例如 FC_READ_EXCEPTION_STATUS、FC_REPORT_SLAVE_ID) + 在请求 PDU 中不携带地址,因此仅在可用时读取。读取它们是安全的, + 因为它们的长度在 MODBUS_MAX_ADU_LENGTH 范围内,这只是为了保持一致性。 + */ + if (req_length >= (int) (offset + 3)) + address = (req[offset + 1] << 8) + req[offset + 2]; + else + address = 0; + + sft.slave = slave; + sft.function = function; + sft.t_id = ctx->backend->get_response_tid(req); + + /* 在值数量非法错误时刷新数据。 */ + switch (function) { + case MODBUS_FC_READ_COILS: + case MODBUS_FC_READ_DISCRETE_INPUTS: { + unsigned int is_input = (function == MODBUS_FC_READ_DISCRETE_INPUTS); + int start_bits = is_input ? mb_mapping->start_input_bits : mb_mapping->start_bits; + int nb_bits = is_input ? mb_mapping->nb_input_bits : mb_mapping->nb_bits; + uint8_t *tab_bits = is_input ? mb_mapping->tab_input_bits : mb_mapping->tab_bits; + const char *const name = is_input ? "read_input_bits" : "read_bits"; + int nb = (req[offset + 3] << 8) + req[offset + 4]; + /* 映射可以偏移以减少内存消耗,它并不总是从地址零开始。 */ + int mapping_address = address - start_bits; + + if (nb < 1 || MODBUS_MAX_READ_BITS < nb) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal nb of values %d in %s (max %d)\n", + nb, + name, + MODBUS_MAX_READ_BITS); + } else if (mapping_address < 0 || (mapping_address + nb) > nb_bits) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in %s\n", + mapping_address < 0 ? address : address + nb, + name); + } else { + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = (nb / 8) + ((nb % 8) ? 1 : 0); + rsp_length = + response_io_status(tab_bits, mapping_address, nb, rsp, rsp_length); + } + } break; + case MODBUS_FC_READ_HOLDING_REGISTERS: + case MODBUS_FC_READ_INPUT_REGISTERS: { + unsigned int is_input = (function == MODBUS_FC_READ_INPUT_REGISTERS); + int start_registers = + is_input ? mb_mapping->start_input_registers : mb_mapping->start_registers; + int nb_registers = + is_input ? mb_mapping->nb_input_registers : mb_mapping->nb_registers; + uint16_t *tab_registers = + is_input ? mb_mapping->tab_input_registers : mb_mapping->tab_registers; + const char *const name = is_input ? "read_input_registers" : "read_registers"; + int nb = (req[offset + 3] << 8) + req[offset + 4]; + /* 映射可以偏移以减少内存消耗,它并不总是从地址零开始。 */ + int mapping_address = address - start_registers; + + if (nb < 1 || MODBUS_MAX_READ_REGISTERS < nb) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal nb of values %d in %s (max %d)\n", + nb, + name, + MODBUS_MAX_READ_REGISTERS); + } else if (mapping_address < 0 || (mapping_address + nb) > nb_registers) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in %s\n", + mapping_address < 0 ? address : address + nb, + name); + } else { + int i; + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = nb << 1; + for (i = mapping_address; i < mapping_address + nb; i++) { + rsp[rsp_length++] = tab_registers[i] >> 8; + rsp[rsp_length++] = tab_registers[i] & 0xFF; + } + } + } break; + case MODBUS_FC_WRITE_SINGLE_COIL: { + int mapping_address = address - mb_mapping->start_bits; + + if (mapping_address < 0 || mapping_address >= mb_mapping->nb_bits) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write bit\n", + address); + break; + } + + /* 此检查仅在此处进行,以确保使用 memcpy 是安全的。 */ + rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req); + if (rsp_length != req_length) { + /* modbus_reply 的错误使用 */ + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Invalid request length in modbus_reply to write bit (%d)\n", + req_length); + break; + } + + /* 不复制 CRC(如果有的话),它将在后面计算(即使与请求中的相同) */ + rsp_length -= ctx->backend->checksum_length; + + int data = (req[offset + 3] << 8) + req[offset + 4]; + if (data == 0xFF00 || data == 0x0) { + /* 将更改应用到映射 */ + mb_mapping->tab_bits[mapping_address] = data ? ON : OFF; + /* 准备响应 */ + memcpy(rsp, req, rsp_length); + } else { + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Illegal data value 0x%0X in write_bit request at address %0X\n", + data, + address); + } + } break; + case MODBUS_FC_WRITE_SINGLE_REGISTER: { + int mapping_address = address - mb_mapping->start_registers; + + if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) { + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_register\n", + address); + break; + } + + rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req); + if (rsp_length != req_length) { + /* modbus_reply 的错误使用 */ + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Invalid request length in modbus_reply to write register (%d)\n", + req_length); + break; + } + int data = (req[offset + 3] << 8) + req[offset + 4]; + + mb_mapping->tab_registers[mapping_address] = data; + + rsp_length -= ctx->backend->checksum_length; + memcpy(rsp, req, rsp_length); + } break; + case MODBUS_FC_WRITE_MULTIPLE_COILS: { + int nb = (req[offset + 3] << 8) + req[offset + 4]; + int nb_bits = req[offset + 5]; + int mapping_address = address - mb_mapping->start_bits; + + if (nb < 1 || MODBUS_MAX_WRITE_BITS < nb || nb_bits * 8 < nb) { + /* 由于地址无效(例如 nb 为0但请求中包含要写入的值), + * 指示可能在读取时被截断,因此需要刷新。 */ + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal number of values %d in write_bits (max %d)\n", + nb, + MODBUS_MAX_WRITE_BITS); + } else if (mapping_address < 0 || (mapping_address + nb) > mb_mapping->nb_bits) { + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_bits\n", + mapping_address < 0 ? address : address + nb); + } else { + /* 6 = 字节计数 */ + modbus_set_bits_from_bytes( + mb_mapping->tab_bits, mapping_address, nb, &req[offset + 6]); + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + /* 4 用于复制位地址(2字节)和位的数量 */ + memcpy(rsp + rsp_length, req + rsp_length, 4); + rsp_length += 4; + } + } break; + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: { + int nb = (req[offset + 3] << 8) + req[offset + 4]; + int nb_bytes = req[offset + 5]; + int mapping_address = address - mb_mapping->start_registers; + + if (nb < 1 || MODBUS_MAX_WRITE_REGISTERS < nb || nb_bytes != nb * 2) { + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal number of values %d in write_registers (max %d)\n", + nb, + MODBUS_MAX_WRITE_REGISTERS); + } else if (mapping_address < 0 || + (mapping_address + nb) > mb_mapping->nb_registers) { + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_registers\n", + mapping_address < 0 ? address : address + nb); + } else { + int i, j; + for (i = mapping_address, j = 6; i < mapping_address + nb; i++, j += 2) { + /* 6 和 7 = 第一个值 */ + mb_mapping->tab_registers[i] = + (req[offset + j] << 8) + req[offset + j + 1]; + } + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + /* 4 用于复制地址(2字节)和寄存器数量 */ + memcpy(rsp + rsp_length, req + rsp_length, 4); + rsp_length += 4; + } + } break; + case MODBUS_FC_REPORT_SLAVE_ID: { + int str_len; + int byte_count_pos; + + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + /* 暂时跳过字节计数 */ + byte_count_pos = rsp_length++; + rsp[rsp_length++] = _REPORT_SLAVE_ID; + /* 运行指示状态设置为 ON */ + rsp[rsp_length++] = 0xFF; + /* LMB + LIBMODBUS_VERSION_STRING 的长度 */ + str_len = 3 + strlen(LIBMODBUS_VERSION_STRING); + memcpy(rsp + rsp_length, "LMB" LIBMODBUS_VERSION_STRING, str_len); + rsp_length += str_len; + rsp[byte_count_pos] = rsp_length - byte_count_pos - 1; + } break; + case MODBUS_FC_READ_EXCEPTION_STATUS: + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_FUNCTION, + rsp, + TRUE, + "Unsupported function: READ EXCEPTION STATUS (0x07)\n"); + break; + case MODBUS_FC_MASK_WRITE_REGISTER: { + int mapping_address = address - mb_mapping->start_registers; + + if (mapping_address < 0 || mapping_address >= mb_mapping->nb_registers) { + rsp_length = + response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data address 0x%0X in write_register\n", + address); + } else { + uint16_t data = mb_mapping->tab_registers[mapping_address]; + uint16_t and = (req[offset + 3] << 8) + req[offset + 4]; + uint16_t or = (req[offset + 5] << 8) + req[offset + 6]; + + data = (data & and) | (or & (~and)); + mb_mapping->tab_registers[mapping_address] = data; + + rsp_length = compute_response_length_from_request(ctx, (uint8_t *) req); + if (rsp_length != req_length) { + /* modbus_reply 的错误使用 */ + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + FALSE, + "Invalid request length in modbus_reply " + "to mask write register (%d)\n", + req_length); + break; + } + + rsp_length -= ctx->backend->checksum_length; + memcpy(rsp, req, rsp_length); + } + } break; + case 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]; + int mapping_address = address - mb_mapping->start_registers; + int mapping_address_write = address_write - mb_mapping->start_registers; + + if (nb_write < 1 || MODBUS_MAX_WR_WRITE_REGISTERS < nb_write || nb < 1 || + MODBUS_MAX_WR_READ_REGISTERS < nb || nb_write_bytes != nb_write * 2) { + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, + rsp, + TRUE, + "Illegal nb of values (W%d, R%d) in write_and_read_registers (max W%d, " + "R%d)\n", + nb_write, + nb, + MODBUS_MAX_WR_WRITE_REGISTERS, + MODBUS_MAX_WR_READ_REGISTERS); + } else if (mapping_address < 0 || + (mapping_address + nb) > mb_mapping->nb_registers || + mapping_address_write < 0 || + (mapping_address_write + nb_write) > mb_mapping->nb_registers) { + rsp_length = response_exception( + ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, + rsp, + FALSE, + "Illegal data read address 0x%0X or write address 0x%0X " + "write_and_read_registers\n", + mapping_address < 0 ? address : address + nb, + mapping_address_write < 0 ? address_write : address_write + nb_write); + } else { + int i, j; + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + rsp[rsp_length++] = nb << 1; + + /* 先写入。 + 10 和 11 是第一个要写入的值的偏移量 */ + for (i = mapping_address_write, j = 10; i < mapping_address_write + nb_write; + i++, j += 2) { + mb_mapping->tab_registers[i] = + (req[offset + j] << 8) + req[offset + j + 1]; + } + + /* 然后读取响应数据 */ + for (i = mapping_address; i < mapping_address + nb; i++) { + rsp[rsp_length++] = mb_mapping->tab_registers[i] >> 8; + rsp[rsp_length++] = mb_mapping->tab_registers[i] & 0xFF; + } + } + } break; + + default: + rsp_length = response_exception(ctx, + &sft, + MODBUS_EXCEPTION_ILLEGAL_FUNCTION, + rsp, + TRUE, + "Unknown Modbus function code: 0x%0X\n", + function); + break; + } + + /* 在 RTU 模式下,当请求是广播时抑制任何响应, + * 除非启用了 quirk 特性。 */ + if (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU && + slave == MODBUS_BROADCAST_ADDRESS && + !(ctx->quirks & MODBUS_QUIRK_REPLY_TO_BROADCAST)) { + return 0; + } + return send_msg(ctx, rsp, rsp_length); +} + +int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code) +{ + unsigned int offset; + int slave; + int function; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + int rsp_length; + sft_t sft; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + offset = ctx->backend->header_length; + slave = req[offset - 1]; + function = req[offset]; + + sft.slave = slave; + sft.function = function + 0x80; + sft.t_id = ctx->backend->get_response_tid(req); + rsp_length = ctx->backend->build_response_basis(&sft, rsp); + + /* 正整数异常码 */ + if (exception_code < MODBUS_EXCEPTION_MAX) { + rsp[rsp_length++] = exception_code; + return send_msg(ctx, rsp, rsp_length); + } else { + errno = EINVAL; + return -1; + } +} + +/* 读取 I/O 状态 */ +static int read_io_status(modbus_t *ctx, int function, int addr, int nb, uint8_t *dest) +{ + int rc; + int req_length; + + uint8_t req[_MIN_REQ_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req); + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int temp, bit; + int pos = 0; + unsigned int offset; + unsigned int offset_end; + unsigned int i; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length + 2; + offset_end = offset + rc; + for (i = offset; i < offset_end; i++) { + /* 将寄存器高字节移位到 temp */ + temp = rsp[i]; + + for (bit = 0x01; (bit & 0xff) && (pos < nb);) { + dest[pos++] = (temp & bit) ? TRUE : FALSE; + bit = bit << 1; + } + } + } + + return rc; +} + +/* 读取位的布尔状态,并将数组元素设置为 TRUE 或 FALSE(单个位)。 */ +int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) +{ + int rc; + + if (ctx == NULL || dest == NULL) { + errno = EINVAL; + return -1; + } + + if (nb < 1 || nb > MODBUS_MAX_READ_BITS) { + if (ctx->debug) { + LOG_E( + "ERROR Too many bits requested (%d > %d)", + nb, + MODBUS_MAX_READ_BITS); + } + errno = EMBXILVAL; + return -1; + } + + rc = read_io_status(ctx, MODBUS_FC_READ_COILS, addr, nb, dest); + + if (rc == -1) + return -1; + else + return nb; +} + +/* 与 modbus_read_bits 相同,但读取远程设备的输入表 */ +int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest) +{ + int rc; + + if (ctx == NULL || dest == NULL) { + errno = EINVAL; + return -1; + } + + if (nb < 1 || nb > MODBUS_MAX_READ_BITS) { + if (ctx->debug) { + LOG_E( + "ERROR Too many discrete inputs requested (%d > %d)", + nb, + MODBUS_MAX_READ_BITS); + } + errno = EMBXILVAL; + return -1; + } + + rc = read_io_status(ctx, MODBUS_FC_READ_DISCRETE_INPUTS, addr, nb, dest); + + if (rc == -1) + return -1; + else + return nb; +} + +/* 从远程设备读取数据并将数据放入数组中 */ +static int read_registers(modbus_t *ctx, int function, int addr, int nb, uint16_t *dest) +{ + int rc; + int req_length; + uint8_t req[_MIN_REQ_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + if (nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + LOG_E( + "ERROR Too many registers requested (%d > %d)", + nb, + MODBUS_MAX_READ_REGISTERS); + } + errno = EMBXILVAL; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, function, addr, nb, req); + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + unsigned int offset; + int i; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length; + + for (i = 0; i < rc; i++) { + /* 将寄存器高字节移位到 temp,与低字节进行 OR 运算 */ + dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)]; + } + } + + return rc; +} + +/* 读取远程设备的保持寄存器并将数据放入数组 */ +int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest) +{ + int status; + + if (ctx == NULL || dest == NULL) { + errno = EINVAL; + return -1; + } + + if (nb < 1 || nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + LOG_E( + "ERROR Too many registers requested (%d > %d)", + nb, + MODBUS_MAX_READ_REGISTERS); + } + errno = EMBXILVAL; + return -1; + } + + status = read_registers(ctx, MODBUS_FC_READ_HOLDING_REGISTERS, addr, nb, dest); + return status; +} + +/* 读取远程设备的输入寄存器并将数据放入数组 */ +int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest) +{ + int status; + + if (ctx == NULL || dest == NULL) { + errno = EINVAL; + return -1; + } + + if (nb < 1 || nb > MODBUS_MAX_READ_REGISTERS) { + if (ctx->debug) { + LOG_E( + "ERROR Too many input registers requested (%d > %d)", + nb, + MODBUS_MAX_READ_REGISTERS); + } + errno = EMBXILVAL; + return -1; + } + + status = read_registers(ctx, MODBUS_FC_READ_INPUT_REGISTERS, addr, nb, dest); + + return status; +} + +/* 向远程设备的指定寄存器写入一个值。 + 由 write_bit 和 write_register 使用 */ +static int write_single(modbus_t *ctx, int function, int addr, const uint16_t value) +{ + int rc; + int req_length; + uint8_t req[_MIN_REQ_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + req_length = ctx->backend->build_request_basis(ctx, function, addr, (int) value, req); + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + /* 由 write_bit 和 write_register 使用 */ + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + +/* 打开或关闭远程设备的单个位 */ +int modbus_write_bit(modbus_t *ctx, int addr, int status) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return write_single(ctx, MODBUS_FC_WRITE_SINGLE_COIL, addr, status ? 0xFF00 : 0); +} + +/* 向远程设备的一个寄存器写入一个值 */ +int modbus_write_register(modbus_t *ctx, int addr, const uint16_t value) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return write_single(ctx, MODBUS_FC_WRITE_SINGLE_REGISTER, addr, value); +} + +/* 将数组中的位写入远程设备 */ +int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src) +{ + int rc; + int i; + int byte_count; + int req_length; + int bit_check = 0; + int pos = 0; + uint8_t req[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL || src == NULL) { + errno = EINVAL; + return -1; + } + + if (nb < 1 || nb > MODBUS_MAX_WRITE_BITS) { + if (ctx->debug) { + LOG_E( + "ERROR Writing too many bits (%d > %d)", + nb, + MODBUS_MAX_WRITE_BITS); + } + errno = EMBXILVAL; + return -1; + } + + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_WRITE_MULTIPLE_COILS, addr, nb, req); + byte_count = (nb / 8) + ((nb % 8) ? 1 : 0); + req[req_length++] = byte_count; + + for (i = 0; i < byte_count; i++) { + int bit; + + bit = 0x01; + req[req_length] = 0; + + while ((bit & 0xFF) && (bit_check++ < nb)) { + if (src[pos++]) + req[req_length] |= bit; + else + req[req_length] &= ~bit; + + bit = bit << 1; + } + req_length++; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + +/* 将数组中的值写入远程设备的寄存器 */ +int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src) +{ + int rc; + int i; + int req_length; + int byte_count; + uint8_t req[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL || src == NULL) { + errno = EINVAL; + return -1; + } + + if (nb < 1 || nb > MODBUS_MAX_WRITE_REGISTERS) { + if (ctx->debug) { + LOG_E( + "ERROR Trying to write to too many registers (%d > %d)", + nb, + MODBUS_MAX_WRITE_REGISTERS); + } + errno = EMBXILVAL; + return -1; + } + + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_WRITE_MULTIPLE_REGISTERS, addr, nb, req); + byte_count = nb * 2; + req[req_length++] = byte_count; + + for (i = 0; i < nb; i++) { + req[req_length++] = src[i] >> 8; + req[req_length++] = src[i] & 0x00FF; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + +int modbus_mask_write_register(modbus_t *ctx, + int addr, + uint16_t and_mask, + uint16_t or_mask) +{ + int rc; + int req_length; + /* 请求长度不能超过 _MIN_REQ_LENGTH - 2 加上4个字节来存储掩码。 + * 丑陋的减法是用来移除未使用的 'nb' 值(2字节)。 */ + uint8_t req[_MIN_REQ_LENGTH + 2]; + + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_MASK_WRITE_REGISTER, addr, 0, req); + + /* 权宜之计,计数未使用 */ + req_length -= 2; + + req[req_length++] = and_mask >> 8; + req[req_length++] = and_mask & 0x00ff; + req[req_length++] = or_mask >> 8; + req[req_length++] = or_mask & 0x00ff; + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + /* 由 write_bit 和 write_register 使用 */ + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + } + + return rc; +} + +/* 从 src 数组向远程设备写入多个寄存器, + 并从远程设备读取多个寄存器到 dest 数组。 */ +int modbus_write_and_read_registers(modbus_t *ctx, + int write_addr, + int write_nb, + const uint16_t *src, + int read_addr, + int read_nb, + uint16_t *dest) + +{ + int rc; + int req_length; + int i; + int byte_count; + uint8_t req[MAX_MESSAGE_LENGTH]; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + if (write_nb > MODBUS_MAX_WR_WRITE_REGISTERS) { + if (ctx->debug) { + LOG_E( + "ERROR Too many registers to write (%d > %d)", + write_nb, + MODBUS_MAX_WR_WRITE_REGISTERS); + } + errno = EMBXILVAL; + return -1; + } + + if (read_nb > MODBUS_MAX_WR_READ_REGISTERS) { + if (ctx->debug) { + LOG_E( + "ERROR Too many registers requested (%d > %d)", + read_nb, + MODBUS_MAX_WR_READ_REGISTERS); + } + errno = EMBXILVAL; + return -1; + } + req_length = ctx->backend->build_request_basis( + ctx, MODBUS_FC_WRITE_AND_READ_REGISTERS, read_addr, read_nb, req); + + req[req_length++] = write_addr >> 8; + req[req_length++] = write_addr & 0x00ff; + req[req_length++] = write_nb >> 8; + req[req_length++] = write_nb & 0x00ff; + byte_count = write_nb * 2; + req[req_length++] = byte_count; + + for (i = 0; i < write_nb; i++) { + req[req_length++] = src[i] >> 8; + req[req_length++] = src[i] & 0x00FF; + } + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + unsigned int offset; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length; + for (i = 0; i < rc; i++) { + /* 将寄存器高字节移位到 temp,与低字节进行 OR 运算 */ + dest[i] = (rsp[offset + 2 + (i << 1)] << 8) | rsp[offset + 3 + (i << 1)]; + } + } + + return rc; +} + +/* 发送请求以获取设备的从站ID(仅在串行通信中可用)。 */ +int modbus_report_slave_id(modbus_t *ctx, int max_dest, uint8_t *dest) +{ + int rc; + int req_length; + uint8_t req[_MIN_REQ_LENGTH]; + + if (ctx == NULL || max_dest <= 0) { + errno = EINVAL; + return -1; + } + + req_length = + ctx->backend->build_request_basis(ctx, MODBUS_FC_REPORT_SLAVE_ID, 0, 0, req); + + /* 权宜之计,地址和计数未使用 */ + req_length -= 4; + + rc = send_msg(ctx, req, req_length); + if (rc > 0) { + int i; + unsigned int offset; + uint8_t rsp[MAX_MESSAGE_LENGTH]; + + rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); + if (rc == -1) + return -1; + + rc = check_confirmation(ctx, req, rsp, rc); + if (rc == -1) + return -1; + + offset = ctx->backend->header_length + 2; + + /* 字节计数、从站ID、运行指示状态和附加数据。 + 将复制截断到 max_dest 以内。 */ + for (i = 0; i < rc && i < max_dest; i++) { + dest[i] = rsp[offset + i]; + } + } + + return rc; +} + +void _modbus_init_common(modbus_t *ctx) +{ + /* 从站和套接字初始化为 -1 */ + ctx->slave = -1; + ctx->s = -1; + + ctx->debug = FALSE; + ctx->error_recovery = MODBUS_ERROR_RECOVERY_NONE; + ctx->quirks = MODBUS_QUIRK_NONE; + + ctx->response_timeout.tv_sec = 0; + ctx->response_timeout.tv_usec = _RESPONSE_TIMEOUT; + + ctx->byte_timeout.tv_sec = 0; + ctx->byte_timeout.tv_usec = _BYTE_TIMEOUT; + + ctx->indication_timeout.tv_sec = 0; + ctx->indication_timeout.tv_usec = 0; +} + +/* 定义从站编号 */ +int modbus_set_slave(modbus_t *ctx, int slave) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->set_slave(ctx, slave); +} + +int modbus_get_slave(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->slave; +} + +int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode error_recovery) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + /* modbus_error_recovery_mode 的类型是无符号枚举 */ + ctx->error_recovery = (uint8_t) error_recovery; + return 0; +} + +// FIXME 在 Windows RTU 下无法工作 +int modbus_set_socket(modbus_t *ctx, int s) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx->s = s; + return 0; +} + +int modbus_get_socket(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->s; +} + +/* 获取等待响应所用的超时间隔 */ +int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + *to_sec = ctx->response_timeout.tv_sec; + *to_usec = ctx->response_timeout.tv_usec; + return 0; +} + +int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec) +{ + if (ctx == NULL || (to_sec == 0 && to_usec == 0) || to_usec > 999999) { + errno = EINVAL; + return -1; + } + + ctx->response_timeout.tv_sec = to_sec; + ctx->response_timeout.tv_usec = to_usec; + return 0; +} + +/* 获取消息的两个连续字节之间的超时间隔 */ +int modbus_get_byte_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + *to_sec = ctx->byte_timeout.tv_sec; + *to_usec = ctx->byte_timeout.tv_usec; + return 0; +} + +int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec) +{ + /* 当两个值都为零时,字节超时可以被禁用 */ + if (ctx == NULL || to_usec > 999999) { + errno = EINVAL; + return -1; + } + + ctx->byte_timeout.tv_sec = to_sec; + ctx->byte_timeout.tv_usec = to_usec; + return 0; +} + +/* 获取服务器等待客户端指示所用的超时间隔 */ +int modbus_get_indication_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + *to_sec = ctx->indication_timeout.tv_sec; + *to_usec = ctx->indication_timeout.tv_usec; + return 0; +} + +int modbus_set_indication_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec) +{ + /* 当两个值都为零时,指示超时可以被禁用 */ + if (ctx == NULL || to_usec > 999999) { + errno = EINVAL; + return -1; + } + + ctx->indication_timeout.tv_sec = to_sec; + ctx->indication_timeout.tv_usec = to_usec; + return 0; +} + +int modbus_get_header_length(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->header_length; +} + +int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + /* 启用掩码中对应索引位置为真的 quirk 特性 */ + ctx->quirks |= quirks_mask; + return 0; +} + +int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + /* 禁用掩码中对应索引位置为真的 quirk 特性 */ + ctx->quirks &= ~quirks_mask; + return 0; +} + +int modbus_connect(modbus_t *ctx) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + return ctx->backend->connect(ctx); +} + +void modbus_close(modbus_t *ctx) +{ + if (ctx == NULL) + return; + + ctx->backend->close(ctx); +} + +void modbus_free(modbus_t *ctx) +{ + if (ctx == NULL) + return; + + ctx->backend->free(ctx); +} + +int modbus_set_debug(modbus_t *ctx, int flag) +{ + if (ctx == NULL) { + errno = EINVAL; + return -1; + } + + ctx->debug = flag; + return 0; +} + +/* 分配4个数组来存储位、输入位、寄存器和输入寄存器。 + 指针存储在 modbus_mapping 结构中。 + + modbus_mapping_new_start_address() 函数在成功时返回新分配的结构体。 + 否则返回 NULL 并设置 errno 为 ENOMEM。 */ +modbus_mapping_t *modbus_mapping_new_start_address(unsigned int start_bits, + unsigned int nb_bits, + unsigned int start_input_bits, + unsigned int nb_input_bits, + unsigned int start_registers, + unsigned int nb_registers, + unsigned int start_input_registers, + unsigned int nb_input_registers) +{ + modbus_mapping_t *mb_mapping; + + mb_mapping = (modbus_mapping_t *) malloc(sizeof(modbus_mapping_t)); + if (mb_mapping == NULL) { + return NULL; + } + + /* 0X - 线圈 */ + mb_mapping->nb_bits = nb_bits; + mb_mapping->start_bits = start_bits; + if (nb_bits == 0) { + mb_mapping->tab_bits = NULL; + } else { + /* 负数会引发 POSIX 错误 */ + mb_mapping->tab_bits = (uint8_t *) malloc(nb_bits * sizeof(uint8_t)); + if (mb_mapping->tab_bits == NULL) { + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_bits, 0, nb_bits * sizeof(uint8_t)); + } + + /* 1X - 离散输入 */ + mb_mapping->nb_input_bits = nb_input_bits; + mb_mapping->start_input_bits = start_input_bits; + if (nb_input_bits == 0) { + mb_mapping->tab_input_bits = NULL; + } else { + mb_mapping->tab_input_bits = (uint8_t *) malloc(nb_input_bits * sizeof(uint8_t)); + if (mb_mapping->tab_input_bits == NULL) { + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_input_bits, 0, nb_input_bits * sizeof(uint8_t)); + } + + /* 4X - 保持寄存器 */ + mb_mapping->nb_registers = nb_registers; + mb_mapping->start_registers = start_registers; + if (nb_registers == 0) { + mb_mapping->tab_registers = NULL; + } else { + mb_mapping->tab_registers = (uint16_t *) malloc(nb_registers * sizeof(uint16_t)); + if (mb_mapping->tab_registers == NULL) { + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_registers, 0, nb_registers * sizeof(uint16_t)); + } + + /* 3X - 输入寄存器 */ + mb_mapping->nb_input_registers = nb_input_registers; + mb_mapping->start_input_registers = start_input_registers; + if (nb_input_registers == 0) { + mb_mapping->tab_input_registers = NULL; + } else { + mb_mapping->tab_input_registers = + (uint16_t *) malloc(nb_input_registers * sizeof(uint16_t)); + if (mb_mapping->tab_input_registers == NULL) { + free(mb_mapping->tab_registers); + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); + return NULL; + } + memset(mb_mapping->tab_input_registers, 0, nb_input_registers * sizeof(uint16_t)); + } + + return mb_mapping; +} + +modbus_mapping_t *modbus_mapping_new(int nb_bits, + int nb_input_bits, + int nb_registers, + int nb_input_registers) +{ + return modbus_mapping_new_start_address( + 0, nb_bits, 0, nb_input_bits, 0, nb_registers, 0, nb_input_registers); +} + +/* 释放4个数组 */ +void modbus_mapping_free(modbus_mapping_t *mb_mapping) +{ + if (mb_mapping == NULL) { + return; + } + + free(mb_mapping->tab_input_registers); + free(mb_mapping->tab_registers); + free(mb_mapping->tab_input_bits); + free(mb_mapping->tab_bits); + free(mb_mapping); +} + +#ifndef HAVE_STRLCPY +/* + * strlcpy 函数最初由 Todd C. Miller 开发, + * 用于简化安全代码的编写。 + * 详情请参见 ftp://ftp.openbsd.org/pub/OpenBSD/src/lib/libc/string/strlcpy.3 + * + * 感谢 Ulrich Drepper... 才怪! + * + * 将 src 复制到大小为 dest_size 的目标字符串 dest。最多复制 dest_size-1 个字符。 + * 始终以 NUL 结尾(除非 dest_size == 0)。 + * 返回 strlen(src);如果 retval >= dest_size,则发生了截断。 + */ +size_t strlcpy(char *dest, const char *src, size_t dest_size) +{ + register char *d = dest; + register const char *s = src; + register size_t n = dest_size; + + /* 复制尽可能多的字节 */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* dest 中没有足够的空间,添加 NUL 并遍历 src 的剩余部分 */ + if (n == 0) { + if (dest_size != 0) + *d = '\0'; /* 以 NUL 结尾 dest */ + while (*s++) + ; + } + + return (s - src - 1); /* 计数不包括 NUL */ +} +#endif