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:
ypc 2026-07-08 15:15:22 +08:00
parent c99f4cb8cd
commit 16f5067adb
16 changed files with 4962 additions and 6 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ioctlLinux RS485 模式切换) */
#define HAVE_DECL_TIOCSRS485 1
/* 是否声明了 TIOCM_RTS ioctlLinux 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 */

View File

@ -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
* RTUTCP
* 使
*/
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); /* 提取事务IDTCP使用 */
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 */

View File

@ -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 */

View File

@ -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 */

View File

@ -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 */

View File

@ -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 */

View File

@ -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 */

View File

@ -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 20000x7D0
* 6 11 29
* Coil 2 1 19680x7B0
*/
#define MODBUS_MAX_READ_BITS 2000
#define MODBUS_MAX_WRITE_BITS 1968
/* 参考 Modbus_Application_Protocol_V1_1b.pdf第 6 章第 3 节第 15 页)
* Register 2 1 1250x7D
* 6 12 31
* Register 2 1 1230x7B
* 6 17 38
* Register 2 1 1210x79
*/
#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 - CRC2 = 253
*/
#define MODBUS_MAX_PDU_LENGTH 253
/* 由此可得:
* - RTU Modbus ADU = 253 + 1 + CRC2 = 256
* - TCP Modbus ADU = 253 + MBAP7 = 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 */

View File

@ -0,0 +1,29 @@
/**
* @file myModbus.h
* @brief Modbus
* @details libmodbus v3.1.12LGPL-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_ */

View File

@ -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);
}

View File

@ -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;
/* 广播地址为 0MODBUS_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 协议中没有事务IDTID */
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 SIGINTRSIGSUSPSIGDSUSP 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
使 ICANONECHOECHOE 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;
}

View File

@ -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;
/* 广播地址为 0MODBUS_BROADCAST_ADDRESS */
if (slave >= 0 && slave <= max_slave) {
ctx->slave = slave;
} else if (slave == MODBUS_TCP_SLAVE) {
/* 特殊值 MODBUS_TCP_SLAVE0xFF可在 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;
/* 递增事务IDTransaction 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;
/* 检查事务IDTransaction 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