4、Modbus RTU实战:使用Python的 minimalmodbus 库读取温湿度传感器数据
好,咱们进入真正的实战环节了。
前面讲了那么多Modbus协议的理论,说白了,最终目的就是让咱们的工业网关能跟现场设备对上话。今天这一章,我就带你手把手用Python把温湿度数据读回来。
我个人习惯,学任何协议栈,先跑通一个最小系统,再谈优化。所以咱们今天的目标很纯粹:用一根串口线,连上温湿度传感器,用Python把温度和湿度数值打印到屏幕上。
4.1 准备工作:硬件与软件环境
先看看你手头需要什么。别急着买贵的,我刚开始做项目时,用几十块钱的USB转485模块一样能调通。
| 硬件/软件 | 说明 | 备注 |
|---|---|---|
| 温湿度传感器 | 支持Modbus RTU协议,比如建大仁科、RS485型 | 默认从站地址一般为1 |
| USB转RS485模块 | FTDI或CH340芯片的都可以 | 记得装驱动 |
| 双绞线 | 连接传感器A/B线到转换器 | 线不要太长,1米内调试没问题 |
| Python环境 | Python 3.6以上 | 建议用虚拟环境 |
| minimalmodbus库 | 纯Python实现,无需额外依赖 | pip install minimalmodbus |
4.2 安装minimalmodbus库
这个库非常轻量,说白了就是封装了Modbus RTU的底层串口通信。你不需要关心CRC校验怎么算,它都帮你搞定了。
# 打开终端或命令提示符
pip install minimalmodbus
# 验证安装
python -c "import minimalmodbus; print(minimalmodbus.__version__)"
嗯,这里要注意:如果你用的是Linux系统,记得把当前用户加到dialout组,不然没权限访问串口。
sudo usermod -a -G dialout $USER
# 然后注销重新登录
4.3 连接硬件与确认串口
把USB转485模块插到电脑上,传感器A线接模块的A,B线接B。千万别接反了,我曾经有一次在项目现场,就是因为A/B线接反,折腾了半小时才发现。
然后确认串口号:
- Windows: 设备管理器 -> 端口(COM和LPT) -> 看是COM几
- Linux:
ls /dev/ttyUSB*或ls /dev/ttyACM* - macOS:
ls /dev/cu.usbserial*
4.4 第一个读取程序:读取温湿度
好,核心代码来了。我习惯先写一个最简单的版本,能跑通再说。
import minimalmodbus
import time
# 初始化串口
# 参数1:串口设备路径(Windows下是'COM3')
# 参数2:从站地址(传感器默认一般是1)
instrument = minimalmodbus.Instrument('/dev/ttyUSB0', 1)
# 设置串口参数
instrument.serial.baudrate = 9600 # 波特率
instrument.serial.bytesize = 8 # 数据位
instrument.serial.parity = 'N' # 无校验
instrument.serial.stopbits = 1 # 停止位
instrument.serial.timeout = 0.5 # 超时时间,单位秒
# 设置Modbus模式
instrument.mode = minimalmodbus.MODE_RTU
print("开始读取温湿度传感器数据...")
print("按 Ctrl+C 停止\n")
try:
while True:
# 读取温度:功能码03,起始地址0,读取1个寄存器
# 注意:有些传感器温度地址是0,有些是1,看手册
temperature = instrument.read_register(0, 1) # 1表示1位小数
# 读取湿度:功能码03,起始地址1,读取1个寄存器
humidity = instrument.read_register(1, 1) # 1表示1位小数
print(f"温度: {temperature:.1f} °C, 湿度: {humidity:.1f} %")
time.sleep(2) # 每2秒读一次
except KeyboardInterrupt:
print("\n停止读取")
except Exception as e:
print(f"读取失败: {e}")
finally:
instrument.serial.close()
跑一下试试。如果一切正常,你应该能看到类似这样的输出:
开始读取温湿度传感器数据...
按 Ctrl+C 停止
温度: 25.3 °C, 湿度: 60.1 %
温度: 25.4 °C, 湿度: 60.0 %
温度: 25.3 °C, 湿度: 60.2 %
4.5 避坑指南:我踩过的三个坑
说实话,这个程序看起来简单,但实际项目中我遇到过不少问题。给你列几个最常见的:
- 坑一:寄存器地址搞错
我曾经遇到过一款传感器,手册上写温度地址是0x0000,但实际读出来是0。后来发现它用的是协议地址而不是物理地址。说白了,有些厂家从0开始编号,有些从1开始。我的建议是:先用Modbus Poll工具扫一遍地址范围。
- 坑二:小数位数不对
read_register的第二个参数decimals,如果你设成0,读出来就是整数。但传感器实际返回的是放大10倍或100倍的值。比如温度25.3,它可能返回253。这时候decimals要设成1。
- 坑三:串口被占用
程序跑第二次时报错"Port is already open"。这是因为上一次程序异常退出,没来得及关闭串口。解决办法:要么重启电脑,要么在代码里加try/finally确保关闭。
4.6 进阶:批量读取多个寄存器
如果你需要同时读取温度和湿度,用read_registers批量读效率更高。一次Modbus请求就能拿到两个寄存器的值。
# 批量读取:从地址0开始,连续读2个寄存器
values = instrument.read_registers(0, 2)
temperature = values[0] / 10.0 # 假设1位小数
humidity = values[1] / 10.0
print(f"温度: {temperature:.1f} °C, 湿度: {humidity:.1f} %")
为什么推荐批量读?你想想看,工业现场往往有几十个传感器,如果每个都单独发一次请求,总线压力会很大。批量读能减少通信次数,说白了就是省带宽、省时间。
4.7 完整代码示例:带异常处理的生产级版本
下面这个版本是我在实际项目中用的模板,加了重试机制和日志记录。你可以直接拿去用。
import minimalmodbus
import time
import logging
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class ModbusSensor:
"""Modbus温湿度传感器读取类"""
def __init__(self, port, slave_address, baudrate=9600):
self.instrument = minimalmodbus.Instrument(port, slave_address)
self.instrument.serial.baudrate = baudrate
self.instrument.serial.bytesize = 8
self.instrument.serial.parity = 'N'
self.instrument.serial.stopbits = 1
self.instrument.serial.timeout = 0.5
self.instrument.mode = minimalmodbus.MODE_RTU
def read_temperature(self):
"""读取温度,带重试机制"""
for attempt in range(3):
try:
return self.instrument.read_register(0, 1)
except Exception as e:
logger.warning(f"读取温度失败,第{attempt+1}次重试: {e}")
time.sleep(0.1)
raise Exception("温度读取失败,已重试3次")
def read_humidity(self):
"""读取湿度"""
for attempt in range(3):
try:
return self.instrument.read_register(1, 1)
except Exception as e:
logger.warning(f"读取湿度失败,第{attempt+1}次重试: {e}")
time.sleep(0.1)
raise Exception("湿度读取失败,已重试3次")
def close(self):
self.instrument.serial.close()
# 使用示例
if __name__ == "__main__":
sensor = ModbusSensor('/dev/ttyUSB0', 1)
try:
while True:
temp = sensor.read_temperature()
hum = sensor.read_humidity()
logger.info(f"温度: {temp:.1f}°C, 湿度: {hum:.1f}%")
time.sleep(5)
except KeyboardInterrupt:
logger.info("用户停止")
finally:
sensor.close()
logger.info("串口已关闭")
4.8 本章小结
今天咱们干了三件事:
- 装好了minimalmodbus库,连好了硬件
- 写了一个最简单的读取程序,跑通了
- 加上了异常处理和批量读取,让代码更健壮
说实话,Modbus RTU的实战就这么简单。你只要记住:地址、功能码、数据格式这三个要素,剩下的就是调参。
下一章,咱们会深入Modbus TCP,看看以太网环境下的工业通信怎么玩。到时候你会发现,核心逻辑其实一模一样,只是传输层换成了网络。