feat(libmodbus): 导入 libmodbus v3.1.12 开源库 (LGPL-2.1)
- 从原工程 src/protocol/libmodbus 完整复制 8 个头文件 + 4 个源文件 - 原工程已做日志适配: fprintf → LOG_I/LOG_E (依赖 liblog) - 新增 release/src/protocol/libmodbus/makefile - release/src/protocol/makefile: SUBDIRS 添加 libmodbus - release/src/makefile: SUBDIRS 添加 ./protocol - copy_headers.sh: 新增 copy_proto_module() 支持 protocol/ 目录
This commit is contained in:
parent
c99f4cb8cd
commit
16f5067adb
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
/* 是否提供 <netinet/in.h> 头文件 */
|
||||
#define HAVE_NETINET_IN_H 1
|
||||
|
||||
/* 是否提供 <netinet/ip.h> 头文件 */
|
||||
#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 */
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef MODBUS_PRIVATE_H
|
||||
#define MODBUS_PRIVATE_H
|
||||
|
||||
// clang-format off
|
||||
#include <stdint.h>
|
||||
#include <sys/time.h>
|
||||
// clang-format on
|
||||
#include <config.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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 */
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#ifndef MODBUS_RTU_PRIVATE_H
|
||||
#define MODBUS_RTU_PRIVATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* 防止同时包含 <asm/termbits.h> 和 <termios.h> 时,
|
||||
* "struct termios" 的重复定义冲突。
|
||||
* 策略:先用宏 termios 屏蔽版本标识,包含 termbits.h 后再取消屏蔽,
|
||||
* 这样 termios.h 会在后续正常包含。
|
||||
*/
|
||||
#define termios
|
||||
#include <asm/termbits.h>
|
||||
#undef termios
|
||||
#include <termios.h>
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* 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 */
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* 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 */
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* 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 */
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* libmodbus 版本头文件
|
||||
* 从 modbus-version.h.in 模板生成,版本号硬编码为 3.1.12
|
||||
*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
* 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 */
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* 版权所有 © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* 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 <sys/param.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#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 */
|
||||
|
|
@ -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 <stephane.raimbault@gmail.com>
|
||||
* 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_ */
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
// clang-format off
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
|
@ -0,0 +1,885 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "modbus-private.h"
|
||||
#include "myLog.h"
|
||||
#include <assert.h>
|
||||
|
||||
#include "modbus-rtu-private.h"
|
||||
#include "modbus-rtu.h"
|
||||
|
||||
#if HAVE_DECL_TIOCSRS485 || HAVE_DECL_TIOCM_RTS
|
||||
#include <sys/ioctl.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_DECL_TIOCSRS485
|
||||
#include <linux/serial.h>
|
||||
#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;
|
||||
}
|
||||
|
|
@ -0,0 +1,888 @@
|
|||
/*
|
||||
* Copyright © Stéphane Raimbault <stephane.raimbault@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
*/
|
||||
|
||||
// clang-format off
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
# include <sys/socket.h>
|
||||
# include <sys/ioctl.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
# include <netinet/tcp.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <netdb.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue