libModbus的安装和测试 libModbus 安装PS D:\soft\vcpkg\installed\x86-windows\binvcpkginstalllibmodbus:x64-windows PS D:\soft\vcpkg\installed\x86-windows\binvcpkglist|Select-String modbus libmodbus:x86-windows3.1.10 libmodbus is afreesoftware library to send/repython 测试保持寄存器 registers[1] 每秒加1importctypesfromctypesimport*importtime# # 加载 libmodbus 动态链接库# # 使用 ctypes 加载 Windows 平台下的 libmodbus DLL 文件# 该库提供了 Modbus TCP 协议的底层通信功能libctypes.CDLL(rD:\soft\vcpkg\installed\x86-windows\bin\modbus.dll)# # 声明 libmodbus 中需要用到的 C 函数签名# 必须指定参数类型(argtypes)和返回值类型(restype)# 否则 ctypes 会使用默认的 int 类型可能导致指针截断等问题# # modbus_new_tcp(ip, port) - modbus_t*# 创建一个新的 Modbus TCP 上下文返回上下文指针lib.modbus_new_tcp.argtypes[c_char_p,c_int]# 参数IP地址(字节串), 端口号lib.modbus_new_tcp.restypec_void_p# 返回值不透明指针(上下文句柄)# modbus_tcp_listen(ctx, nb_connection) - int# 创建监听套接字等待客户端连接lib.modbus_tcp_listen.argtypes[c_void_p,c_int]# 参数上下文指针, 最大连接数lib.modbus_tcp_listen.restypec_int# 返回值服务器套接字描述符失败返回 -1# modbus_tcp_accept(ctx, socket*) - int# 接受一个客户端连接请求lib.modbus_tcp_accept.argtypes[c_void_p,POINTER(c_int)]# 参数上下文指针, 指向服务器套接字的指针lib.modbus_tcp_accept.restypec_int# 返回值客户端套接字描述符失败返回 -1# modbus_mapping_new(nb_bits, nb_input_bits, nb_registers, nb_input_registers) - modbus_mapping_t*# 创建寄存器映射表用于存储 Modbus 数据lib.modbus_mapping_new.argtypes[c_int,c_int,c_int,c_int]# 参数线圈数, 离散输入数, 保持寄存器数, 输入寄存器数lib.modbus_mapping_new.restypec_void_p# 返回值映射表指针# modbus_receive(ctx, req_buffer) - int# 接收来自客户端的 Modbus 请求报文lib.modbus_receive.argtypes[c_void_p,POINTER(c_uint8)]# 参数上下文指针, 接收缓冲区(字节数组)lib.modbus_receive.restypec_int# 返回值接收到的字节数失败或断开返回 0# modbus_reply(ctx, req_buffer, req_length, mapping) - int# 根据收到的请求自动查找映射表并生成响应报文回复给客户端lib.modbus_reply.argtypes[c_void_p,POINTER(c_uint8),c_int,c_void_p]# 参数上下文, 请求缓冲区, 请求长度, 映射表lib.modbus_reply.restypec_int# 返回值发送的字节数失败返回 -1# # 创建 Modbus TCP 服务端# # 创建 Modbus TCP 上下文监听所有网卡(0.0.0.0)的 1502 端口# 1502 是 Modbus TCP 的默认端口号ctxlib.modbus_new_tcp(b0.0.0.0,1502)# 检查上下文是否创建成功返回 NULL(0) 表示失败ifnotctx:print(modbus_new_tcp failed)raiseSystemExit# 退出程序# 开始监听参数 1 表示最多同时接受 1 个连接server_socketlib.modbus_tcp_listen(ctx,1)print(listen:,server_socket)# 将服务器套接字包装为 ctypes 的 c_int 类型# 因为 modbus_tcp_accept 需要 POINTER(c_int) 类型的参数sockc_int(server_socket)# 阻塞等待客户端连接连接成功后返回客户端套接字描述符retlib.modbus_tcp_accept(ctx,byref(sock))print(accept:,ret)# 返回值小于 0 表示接受连接失败ifret0:print(accept failed)raiseSystemExit# # 创建寄存器映射表 (Holding Register 地址 0~32共 33 个)# # 参数说明# 第1个参数 0 - 线圈(Coil)数量这里不需要# 第2个参数 0 - 离散输入(Input Bit)数量这里不需要# 第3个参数 33 - 保持寄存器(Holding Register)数量分配 33 个(地址 0~32)# 第4个参数 0 - 输入寄存器(Input Register)数量这里不需要mappinglib.modbus_mapping_new(0,0,33,0)# 检查映射表是否创建成功ifnotmapping:print(mapping failed)raiseSystemExit# 打印映射表的内存地址调试用print(mapping:,hex(mapping))# # 获取 holding registers 数组指针用于直接读写寄存器值# # libmodbus 内部定义了 modbus_mapping_t 结构体其中包含了# 各种寄存器的数量、起始地址以及数据数组指针。# 为了在 Python 中直接操作寄存器数据需要用 ctypes.Structure# 定义一个与 C 语言完全一致的结构体然后将 mapping 指针强转为该结构体指针。## modbus_mapping_t 结构体字段与 libmodbus 源码保持一致# 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; - 保持寄存器数据数组指针我们需要的classModbusMapping(Structure):与 libmodbus 中 modbus_mapping_t 结构体对应的 Python 定义_fields_[(nb_bits,c_int),# 线圈数量(start_bits,c_int),# 线圈起始地址(nb_input_bits,c_int),# 离散输入数量(start_input_bits,c_int),# 离散输入起始地址(nb_input_registers,c_int),# 输入寄存器数量(start_input_registers,c_int),# 输入寄存器起始地址(nb_registers,c_int),# 保持寄存器数量(start_registers,c_int),# 保持寄存器起始地址(tab_bits,POINTER(c_uint8)),# 线圈数据数组(tab_input_bits,POINTER(c_uint8)),# 离散输入数据数组(tab_input_registers,POINTER(c_uint16)),# 输入寄存器数据数组(tab_registers,POINTER(c_uint16)),# 保持寄存器数据数组核心]# 将 modbus_mapping_new 返回的 void* 强转为 ModbusMapping 结构体指针mapping_ptrcast(mapping,POINTER(ModbusMapping))# 取出保持寄存器数组的指针之后可以通过 registers[地址] 直接读写寄存器值registersmapping_ptr.contents.tab_registers# # 主循环每秒自增寄存器 处理 Modbus 请求# # 创建接收缓冲区260 字节足够容纳一个 Modbus TCP 报文query(c_uint8*260)()print(Modbus TCP server running...)# 记录上一次自增的时间戳用于控制每秒执行一次last_increment_timetime.time()whileTrue:# 每秒对地址 1 的保持寄存器自增 1 current_timetime.time()ifcurrent_time-last_increment_time1.0:registers[1]1# 地址 1 的寄存器值 1print(fregister[1] {registers[1]})# 打印当前值调试用last_increment_timecurrent_time# 更新上次自增时间# 接收并处理 Modbus 请求 # modbus_receive 会阻塞等待客户端发来的请求rclib.modbus_receive(ctx,query)ifrc0:# rc 0 表示成功接收到请求rc 为接收到的字节数print(recv len:,rc)# 将接收到的原始字节以十六进制格式打印出来方便调试print(bytes(query[:rc]).hex( ))# 根据请求自动查表并回复响应# modbus_reply 会读取 mapping 中对应地址的寄存器值# 组装成 Modbus 响应报文发送给客户端retlib.modbus_reply(ctx,query,rc,mapping)print(reply:,ret)else:# rc 0 表示客户端断开连接或发生错误print(client disconnected)breakc 测试#includeiostream#includecstring#includecstdio#includepthread.h#includeunistd.h#includemodbus.h// // 线程函数每秒对寄存器地址1自增1// 参数 param 传入的是 uint16_t* 类型的寄存器数组指针// void*incrementThread(void*param){uint16_t*registers(uint16_t*)param;while(true){sleep(1);// 休眠1秒POSIX标准函数单位秒registers[1]1;// 地址1的寄存器值 1std::coutregister[1] registers[1]std::endl;}returnNULL;}intmain(){// // 创建 Modbus TCP 服务端上下文// 监听所有网卡(0.0.0.0)的 1502 端口// modbus_t*ctxmodbus_new_tcp(0.0.0.0,1502);if(!ctx){std::cerrmodbus_new_tcp failedstd::endl;return1;}// // 开始监听参数 1 表示最多同时接受 1 个连接// intserver_socketmodbus_tcp_listen(ctx,1);std::coutlisten: server_socketstd::endl;// // 阻塞等待客户端连接// intretmodbus_tcp_accept(ctx,server_socket);std::coutaccept: retstd::endl;if(ret0){std::cerraccept failedstd::endl;modbus_free(ctx);return1;}// // 创建寄存器映射表// 参数线圈数, 离散输入数, 保持寄存器数, 输入寄存器数// 这里只分配 33 个保持寄存器地址 0~32// modbus_mapping_t*mappingmodbus_mapping_new(0,0,33,0);if(!mapping){std::cerrmapping failedstd::endl;modbus_free(ctx);return1;}std::coutmapping: mappingstd::endl;// // 获取保持寄存器数组指针// mapping-tab_registers 就是 uint16_t* 类型// 可以直接通过下标读写寄存器值// uint16_t*registersmapping-tab_registers;// // 创建 pthread 线程每秒对寄存器自增// pthread_create 参数// 1. 线程句柄指针// 2. 线程属性NULL 表示默认// 3. 线程函数// 4. 传给线程函数的参数// pthread_ttid;intthread_retpthread_create(tid,NULL,incrementThread,registers);if(thread_ret!0){std::cerrpthread_create failed, ret thread_retstd::endl;modbus_mapping_free(mapping);modbus_free(ctx);return1;}// 分离线程线程结束后自动回收资源无需 pthread_joinpthread_detach(tid);// // 主循环接收并处理 Modbus 请求// uint8_tquery[260];// 接收缓冲区260 字节足够容纳一个 Modbus TCP 报文std::coutModbus TCP server running...std::endl;while(true){// modbus_receive 会阻塞等待客户端发来的请求intrcmodbus_receive(ctx,query);if(rc0){// rc 0 表示成功接收到请求rc 为接收到的字节数std::coutrecv len: rcstd::endl;// 将接收到的原始字节以十六进制格式打印出来方便调试for(inti0;irc;i){printf(%02x ,query[i]);}std::coutstd::endl;// 根据请求自动查表并回复响应retmodbus_reply(ctx,query,rc,mapping);std::coutreply: retstd::endl;}else{// rc 0 表示客户端断开连接或发生错误std::coutclient disconnectedstd::endl;break;}}// // 清理资源// modbus_mapping_free(mapping);modbus_close(ctx);modbus_free(ctx);return0;}CMakeLists.txtcmake_minimum_required(VERSION3.26)project(untitled29)set(CMAKE_CXX_STANDARD11)add_executable(untitled29 main.cpp)# 手动指定 libmodbus 的头文件目录编译时查找 #include modbus/modbus.htarget_include_directories(untitled29 PRIVATED:/soft/vcpkg/installed/x86-windows/include)# 手动指定 libmodbus 的库文件目录链接时查找 modbus.libtarget_link_directories(untitled29 PRIVATED:/soft/vcpkg/installed/x86-windows/lib)# 链接 modbus 库自动找到 modbus.libtarget_link_libraries(untitled29 PRIVATE modbus pthread)测试