2. Modbus TCP 电表数据采集
任务目标
编写一个Python程序,通过网络连接支持Modbus TCP协议的电表,周期性地读取电压(U)、电流(I)、有功功率(P)等关键电气参数,并在控制台以格式化表格输出。本案例聚焦于Modbus TCP通信链路建立与标准功能码读取,不涉及具体电表品牌私有寄存器映射。
环境依赖
需要Python 3.7+环境,并安装开源Modbus库pymodbus(版本2.5.3或更新):
pip install pymodbus
关键完整代码
from pymodbus.client import ModbusTcpClient
import time
import struct
# 电表Modbus寄存器地址(以常见电表为例,可按实际设备修改)
REG_ADDRS = {
'U': 0x0000, # 电压寄存器起始地址(float32,2个寄存器)
'I': 0x0002, # 电流寄存器起始地址
'P': 0x0004, # 有功功率寄存器起始地址
}
def read_float32(client, addr, unit=1):
"""读取2个连续寄存器并解析为32位浮点数(大端序)"""
result = client.read_holding_registers(address=addr, count=2, slave=unit)
if result.isError():
raise RuntimeError(f"读取寄存器0x{addr:04X}失败: {result}")
# Modbus寄存器为大端序,需按大端字节序组合为float
raw = struct.pack('>HH', result.registers[0], result.registers[1])
return struct.unpack('>f', raw)[0]
def main():
# 电表IP与端口(按实际设备修改)
HOST = '192.168.1.100'
PORT = 502
UNIT_ID = 1 # Modbus从站地址
INTERVAL = 2.0 # 采集间隔(秒)
client = ModbusTcpClient(HOST, port=PORT, timeout=3)
if not client.connect():
print(f"无法连接到 {HOST}:{PORT}")
return
print(f"已连接电表 {HOST}:{PORT},从站地址 {UNIT_ID}")
print("开始采集数据(Ctrl+C退出)...\n")
print(f"{'时间':<20} {'电压(V)':<12} {'电流(A)':<12} {'功率(W)':<12}")
try:
while True:
try:
u = read_float32(client, REG_ADDRS['U'], UNIT_ID)
i = read_float32(client, REG_ADDRS['I'], UNIT_ID)
p = read_float32(client, REG_ADDRS['P'], UNIT_ID)
ts = time.strftime('%Y-%m-%d %H:%M:%S')
print(f"{ts:<20} {u:<12.2f} {i:<12.3f} {p:<12.2f}")
except Exception as e:
print(f"[{time.strftime('%H:%M:%S')}] 读取异常: {e}")
# 尝试重连
if not client.connected:
print("连接断开,尝试重连...")
client.connect()
time.sleep(INTERVAL)
except KeyboardInterrupt:
print("\n采集停止")
finally:
client.close()
if __name__ == '__main__':
main()
运行与验证
运行方式:
python modbus_meter.py
验证方法:
- 将代码中
HOST、PORT、UNIT_ID修改为实际电表参数; - 确认电表已启用Modbus TCP服务,且本机网络可访问电表IP;
- 运行程序后,若连接成功,控制台每2秒输出一行包含电压、电流、功率的数据;
- 判定通过条件:能连续稳定输出非零且数值合理的电气参数(如电压在200~240V范围内),且无持续报错。
常见问题
- 连接失败:检查IP、端口(默认502)是否正确,电表是否允许Modbus TCP访问,以及防火墙是否放行。
- 读取异常(从站无响应):确认
UNIT_ID与电表配置的从站地址一致;部分电表使用从站地址0或255,需按设备手册调整。 - 数值明显错误:寄存器地址或数据格式不匹配。本代码按大端序float32解析,若电表使用int32或小端序,需修改
read_float32中的字节序(将>改为<)或改用read_float32的word_order参数。