Python上位机开发实战:5分钟搞定Modbus通信(附完整代码)
最近在做一个设备数据采集的小项目,客户那边有几台老旧的PLC,需要把里面的温度、压力数据读到电脑上做个简单的监控界面。一开始我琢磨着是不是得用组态软件,但预算有限,而且客户后期还想加一些自定义的数据分析功能。这时候,Python的优势就体现出来了。我花了大概一个下午的时间,用Python写了个简单的上位机程序,不仅成功读到了数据,还做了一个带实时曲线图的界面。整个过程比想象中顺畅得多,尤其是利用现成的库来处理Modbus协议,几乎没在通信协议本身花什么时间。
所谓上位机,在工业控制场景里,通常指的就是我们面前这台电脑上运行的软件,它负责跟现场的设备“对话”,把数据拿上来,展示给人看,或者把人的指令发下去,控制设备动作。Python来做这件事,最大的好处就是“快”。你不用去啃那些复杂的工业软件套件,也不用纠结于C++的指针和内存管理,几行清晰的代码就能建立起通信链路,特别适合快速验证想法、搭建原型,或者应对那些需求灵活多变的中小型项目。
如果你有Python基础,想把手伸向工业自动化或物联网数据采集领域,那么用Python开发上位机将是一个非常高效的切入点。这篇文章,我就以最经典的Modbus TCP通信为例,带你从零开始,一步步搭建一个能实际运行的上位机数据采集程序。我们会涵盖从环境搭建、协议理解、代码编写到界面构建的完整流程,并提供可直接运行的代码。你会发现,那些听起来很“工业”的东西,用Python来实现,也可以如此简单直接。
1. 环境准备与库的选择
工欲善其事,必先利其器。用Python开发上位机,第一步就是搭建一个顺手的环境。我个人强烈推荐使用 Anaconda 来管理Python环境,它能很好地处理不同项目间第三方库的版本冲突问题。当然,如果你习惯使用纯Python安装,配合 venv 创建虚拟环境也是完全可行的。
对于这个Modbus TCP上位机项目,我们主要需要两个核心库:
pymodbus: 这是Python生态中功能最全面、最活跃的Modbus协议库。它同时支持Modbus TCP和RTU(串口),并且完美实现了客户端(主站)和服务器(从站)的功能。我们将用它来发起数据请求。PySide6: 用于构建图形用户界面(GUI)。选择PySide6而不是Tkinter,是因为它提供的控件更现代、功能更强大,布局管理也更灵活,能轻松做出接近专业工业软件风格的界面。它是Qt官方提供的Python绑定,完全免费商用。
你可以通过以下命令一次性安装所有必需的库:
# 使用pip安装
pip install pymodbus PySide6
# 如果你使用conda,可以先创建并激活一个环境
conda create -n scada python=3.10
conda activate scada
conda install pymodbus pyside6 -c conda-forge
安装完成后,可以通过一个简单的导入测试来验证:
import pymodbus.client as ModbusClient
from PySide6.QtWidgets import QApplication
print(“环境检查通过!”)
除了软件库,你还需要一个**Modbus TCP服务器(从站)**用于测试。有以下几种选择:
- 硬件设备: 真实的PLC、传感器网关等。
- 软件模拟器: 这是学习和开发阶段最方便的工具。推荐
Modbus Poll的从站模拟功能,或者开源的qModMaster。 - Python模拟: 我们甚至可以用
pymodbus库自己写一个简单的模拟服务器,这在后续调试时会非常有用。
为了让大家能无障碍地跟着操作,我将在后续章节中穿插一个用 pymodbus 创建简易测试服务器的代码块,这样你在一台电脑上就能完成客户端和服务的全部测试。
2. 理解Modbus TCP:五分钟协议精讲
在写代码之前,花五分钟理解一下Modbus TCP的基本原理,能让你在调试时胸有成竹,而不是盲目地试错。Modbus本质上是一种主从式(客户端/服务器) 的请求-响应协议。
核心通信模型:
- 客户端(上位机/主站): 主动发起请求,例如“请把1号保持寄存器地址40001的值发给我”。
- 服务器(下位机/从站): 被动响应请求,执行读/写操作,并回复结果或错误码。
在Modbus TCP中,这个对话过程被封装在了标准的TCP/IP数据包中。一个Modbus TCP报文主要包含两部分:
- MBAP头(Modbus Application Protocol Header): 7个字节,包含事务标识符、协议标识符(Modbus固定为0)、长度字段和单元标识符(从站地址)。
- PDU(Protocol Data Unit): 即Modbus协议本身的数据单元,包含功能码和请求/响应数据。
对我们开发者而言,pymodbus 库已经完美地处理了这些底层报文的组帧和解析。我们需要关心的,主要是以下几组核心概念:
| 概念 | 说明 | 对应功能码(常见) | 在代码中的体现 |
|---|---|---|---|
| 线圈(Coils) | 1位(比特)读写,通常表示开关量状态,如电机启停、报警位。 | 读:0x01;写:0x05, 0x0F | read_coils, write_coil |
| 离散输入(Discrete Inputs) | 1位只读,通常表示只读的开关量输入,如限位开关状态。 | 读:0x02 | read_discrete_inputs |
| 保持寄存器(Holding Registers) | 16位读写,最常用的数据区,存储温度、压力等模拟量数据或可修改参数。 | 读:0x03;写:0x06, 0x10 | read_holding_registers, write_register |
| 输入寄存器(Input Registers) | 16位只读,通常存储只读的模拟量输入,如传感器实时值。 | 读:0x04 | read_input_registers |
注意: 很多设备厂商的文档中,会使用“4xxxx”、“3xxxx”这样的地址编号来指代寄存器。例如“地址40001”通常对应保持寄存器的地址0(在代码中起始地址填0)。务必仔细查阅你的设备手册,确认地址映射关系。
理解了这些,我们就可以用 pymodbus 来发起一次最简单的数据读取了。假设我们要从地址为 192.168.1.100,端口为 502 的从站设备上,读取其保持寄存器起始地址为0的连续10个寄存器值(即设备手册里的40001到40010)。
from pymodbus.client import ModbusTcpClient
# 1. 创建客户端并连接到服务器
client = ModbusTcpClient(host=‘192.168.1.100’, port=502)
connection = client.connect(

&spm=1001.2101.3001.5002&articleId=149953419&d=1&t=3&u=d5aa333fe5e74d2fbfde4181e8cf4023)
6529

被折叠的 条评论
为什么被折叠?



