开关柜局放监测软件:Modbus RTU 数据落地与 PRPD 图谱可视化 简介该资源是一套面向电力系统运维与电气工程学习者的开关柜局部放电监测软件基于Modbus_RTU协议连接TEV、超声波与UHF传感器实时读取遥测数据和图谱数据并通过图形界面完成可视化展示可用于开关柜局部放电状态监测与故障预警。资源包共11个文件包含5个Python源码、3个Markdown说明文档、2个文本文件及1份docx文档压缩包约64KB源码覆盖多传感器读取、UHF监测及PyQt5向PySide6迁移等模块文档则提供环境依赖与使用说明。目前已有145人学习下载。读者可据此掌握Modbus_RTU通信、多传感器数据采集与图形界面开发思路理解局部放电事件识别、分级报警与趋势分析逻辑并参考迁移说明完成界面框架适配适合作为电力监测类课程设计或工程原型的实践参考。1. 开关柜局放监测软件从 TEV 到 UHF 的 Modbus 数据落地变电站里开关柜的绝缘劣化往往先表现为局部放电等闻到焦糊味或者保护动作基本已经晚了。这套软件解决的就是把 TEV、超声波、UHF 三类传感器的数据通过 Modbus RTU 拉回来实时显示遥测值和图谱让运维人员在缺陷早期就能看到趋势。它适合做电力设备状态监测的二次开发人员、想给现有开关柜加装在线监测的集成商以及需要一套可改可调的局放上位机做算法验证的工程师。软件本身不绑定特定厂家硬件只要传感器支持标准 Modbus RTU 寄存器映射就能接进来。图谱数据这块是重点PRPD、PRPS 这类相位分布图不是简单存个数组涉及相位同步和周期对齐后面会拆开讲。2. Modbus RTU 通信层串口参数、寄存器映射与轮询策略2.1 为什么选 Modbus RTU 而不是 Modbus TCP开关柜现场电磁环境复杂TEV 传感器和超声波传感器通常装在柜内或柜面走 RS485 总线到集中器再由集中器转以太网。如果传感器直接带网口Modbus TCP 当然更省事但实际项目里大量存量传感器只有 RS485 接口Modbus RTU 是唯一选择。RTU 模式用二进制传输比 ASCII 模式效率高CRC 校验对现场干扰的容忍度也够用。这套软件在通信层做了两件事一是把串口参数做成可配置二是把寄存器地址映射抽成配置文件换传感器不用改代码。常见做法是用 Python 的 pymodbus 库搭通信层配合 serial 库管理串口。下面是一个最小可用的 RTU 读取封装我一般会把它单独放一个模块里方便后面换成多线程或异步。import serial from pymodbus.client import ModbusSerialClient from pymodbus.exceptions import ModbusIOException class PDModbusReader: def __init__(self, port, baudrate9600, parityN, stopbits1, bytesize8, timeout1): # 串口参数按传感器手册填9600-8-N-1 是局放传感器最常见的默认值 self.client ModbusSerialClient( portport, baudratebaudrate, parityparity, stopbitsstopbits, bytesizebytesize, timeouttimeout ) self.connected False def connect(self): self.connected self.client.connect() return self.connected def read_telemetry(self, slave_id, start_addr, count): # 遥测数据一般是连续的保持寄存器一次读回来再按偏移解析 try: rr self.client.read_holding_registers( addressstart_addr, countcount, slaveslave_id ) if rr.isError(): return None return rr.registers except ModbusIOException: return None def close(self): self.client.close()逻辑说明ModbusSerialClient把串口打开和 Modbus 帧封装在一起read_holding_registers发的是功能码 0x03。参数slave是传感器地址现场如果一条总线上挂多个传感器地址不能冲突。count是一次读的寄存器数量注意不要超过传感器允许的最大连续读取长度有些低端传感器超过 16 个寄存器就返回异常。超时设 1 秒是保守值现场如果总线长、波特率低可以放到 2 秒但轮询周期会拉长。2.2 寄存器映射表怎么定不同厂家的传感器寄存器定义差别很大但结构上逃不出三类遥测值放电幅值、次数、相位、状态字传感器自检、通信状态、图谱数据PRPD 数组或波形采样点。我一般先跟厂家要一份寄存器表然后做成 YAML 或 JSON 映射文件软件启动时加载。下面是一个示例映射覆盖 TEV 和超声波两种传感器。sensors: - type: TEV slave_id: 1 telemetry: - name: discharge_amplitude addr: 0x0000 scale: 0.1 unit: dBmV - name: discharge_count addr: 0x0001 scale: 1 unit: 次/秒 status: - name: self_test addr: 0x0010 bit: 0 prpd: start_addr: 0x0100 length: 128 phase_bins: 64 - type: Ultrasonic slave_id: 2 telemetry: - name: amplitude addr: 0x0000 scale: 0.01 unit: dB - name: frequency addr: 0x0001 scale: 1 unit: kHz参数说明scale是原始寄存器值到工程值的换算系数比如寄存器读到 452scale 0.1实际就是 45.2 dBmV。bit用于状态字按位解析。prpd段的length是图谱数组占用的寄存器数量phase_bins是相位分箱数常见 64 或 128。这个映射文件的好处是换传感器只改 YAML不动 Python 代码。2.3 轮询策略与多传感器调度一条 RS485 总线上挂多个传感器时不能同时发请求必须串行轮询。轮询周期取决于传感器响应时间和寄存器数量。我一般把遥测和状态放在快轮询里200ms 到 500ms 一次图谱数据量大放在慢轮询里1s 到 2s 一次。下面是一个简单的轮询调度片段。import time from collections import deque class PollScheduler: def __init__(self, reader, sensor_configs): self.reader reader self.sensors sensor_configs self.fast_interval 0.3 # 遥测轮询间隔秒 self.slow_interval 1.5 # 图谱轮询间隔秒 self.last_slow 0 def poll_once(self): results {} for s in self.sensors: sid s[slave_id] tele self.reader.read_telemetry(sid, 0x0000, 2) if tele: results[sid] {telemetry: tele} now time.time() if now - self.last_slow self.slow_interval: for s in self.sensors: if prpd in s: sid s[slave_id] prpd self.reader.read_telemetry( sid, s[prpd][start_addr], s[prpd][length] ) if prpd: results.setdefault(sid, {})[prpd] prpd self.last_slow now return results逻辑说明快轮询只读前两个寄存器保证实时性慢轮询读图谱数组避免总线被长帧占满。results用字典按 slave_id 聚合后面交给界面层。注意如果某个传感器掉线read_telemetry返回 None调度器不会阻塞但要在界面上标红提示。3. 图谱数据解析与可视化PRPD 相位分布怎么画3.1 PRPD 数据的结构局部放电的 PRPD 图谱本质是一个二维直方图横轴是相位 0 到 360 度纵轴是放电幅值或放电次数。传感器传回来的通常是一段连续寄存器每个寄存器代表一个相位分箱的计数值或幅值。比如 64 个相位分箱每个分箱用 1 个寄存器存放电次数那 64 个寄存器就是一张 PRPD 图。有些传感器还会传 PRPS 数据那是按工频周期排列的相位序列数据量更大一般用于实验室分析现场监测用 PRPD 就够了。解析的时候要注意字节序。Modbus 寄存器是 16 位大端但有些传感器把两个寄存器拼成一个 32 位浮点数这时候要按 IEEE 754 解析。下面是一个解析函数同时处理 16 位整数和 32 位浮点两种格式。import struct def parse_registers(registers, fmtH): fmtH 表示每个寄存器独立解析为无符号16位整数 fmtf 表示每两个寄存器拼成一个32位浮点数 if fmt H: return list(registers) elif fmt f: values [] for i in range(0, len(registers) - 1, 2): # Modbus 大端两个寄存器高在前 raw struct.pack(HH, registers[i], registers[i1]) values.append(struct.unpack(f, raw)[0]) return values else: raise ValueError(unsupported format)参数说明registers是从 Modbus 读回来的整数列表。fmtH适合放电次数这类整数fmtf适合幅值、相位这类浮点。注意有些传感器用小端浮点那就把f改成f这个必须拿实际数据试试错了图谱会完全乱掉。3.2 用 Matplotlib 嵌入 PyQt 做实时刷新这套软件如果做成桌面端常见方案是 PyQt5 加 Matplotlib 的 FigureCanvas。图谱刷新不能每来一帧就重绘整个画布那样 CPU 扛不住。我一般用 blit 或者只更新数据源的方式。下面是一个简化的 PRPD 绘制类。import numpy as np from matplotlib.figure import Figure from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas class PRPDCanvas(FigureCanvas): def __init__(self, parentNone): self.fig Figure(figsize(5, 4), dpi100) self.ax self.fig.add_subplot(111) super().__init__(self.fig) self.setParent(parent) self.phase_bins 64 self.phase_axis np.linspace(0, 360, self.phase_bins, endpointFalse) self.bar None def update_prpd(self, counts): # counts 长度必须等于 phase_bins if len(counts) ! self.phase_bins: return if self.bar is None: self.bar self.ax.bar( self.phase_axis, counts, width360/self.phase_bins, color#2c7fb8, edgecolornone ) self.ax.set_xlim(0, 360) self.ax.set_xlabel(相位 (°)) self.ax.set_ylabel(放电次数) else: for rect, h in zip(self.bar, counts): rect.set_height(h) self.draw_idle()逻辑说明第一次调用时创建柱状图对象后续只更新每个柱子的高度避免重复创建。draw_idle比draw温和不会阻塞界面事件循环。相位轴用linspace生成 0 到 360 的等分点endpointFalse保证最后一个分箱不重叠。如果传感器传的是幅值而不是次数把 y 轴标签改成 dBmV 即可。3.3 图谱数据的存储与回放实时监测之外图谱数据要落盘不然出了故障没法回溯。我一般用 SQLite 存遥测和状态图谱数据量大的话用 HDF5 或单独的文件目录。下面是一个 SQLite 建表和插入的示例。import sqlite3 import time def init_db(pathpd_monitor.db): conn sqlite3.connect(path) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS telemetry ( ts REAL, slave_id INTEGER, amplitude REAL, count INTEGER )) c.execute(CREATE TABLE IF NOT EXISTS prpd ( ts REAL, slave_id INTEGER, phase_bins INTEGER, data BLOB )) conn.commit() return conn def save_prpd(conn, slave_id, counts): import array blob array.array(H, counts).tobytes() conn.execute( INSERT INTO prpd (ts, slave_id, phase_bins, data) VALUES (?, ?, ?, ?), (time.time(), slave_id, len(counts), blob) ) conn.commit()参数说明array.array(H, counts)把整数列表转成二进制比 JSON 省空间。回放的时候用array.frombytes还原。注意 SQLite 的 BLOB 字段不要存太大的数组单条超过 1MB 会影响查询性能PRPD 一般 64 到 128 个点完全没问题。4. 避坑与排查现场调试最容易翻车的五个点4.1 串口能打开但读不到数据现象client.connect()返回 True但read_holding_registers一直超时或返回异常。原因通常是 A/B 线接反或者波特率、校验位跟传感器不一致。解决先用串口调试助手手动发一帧读命令确认传感器有回复。如果手动能通代码不通检查 pymodbus 的slave参数是不是写成了unit不同版本参数名不一样。4.2 遥测值跳变或明显偏大现象放电幅值在 0 和满量程之间乱跳。原因多半是寄存器解析格式错了比如传感器传的是有符号 16 位代码按无符号解析负值就变成 65535 附近的大数。解决查手册确认数据类型有符号用struct.unpack(h, ...)浮点确认大小端。另外检查scale系数有些传感器出厂默认 0.1但实际是 0.01。4.3 图谱相位对不齐现象PRPD 图上放电集中在固定相位但跟工频相位对不上或者每次刷新图形左右漂移。原因是没有做相位同步。TEV 和超声波传感器内部通常有工频参考但 Modbus 读回来的相位是相对值需要软件根据工频周期做对齐。常见做法是读一个工频同步寄存器或者用系统时间戳做插值。如果传感器不支持同步图谱只能看趋势不能做相位诊断。4.4 多传感器轮询时丢包现象总线上挂 3 个以上传感器轮询几轮后某个传感器数据不再更新。原因是轮询间隔太短传感器还没处理完上一帧就收到下一帧。解决拉长快轮询间隔到 500ms 以上或者在每次请求之间加 50ms 延时。RS485 半双工收发切换也需要时间有些转换器切换慢延时不够就丢帧。4.5 界面卡死现象图谱刷新时界面无响应。原因是 Modbus 读取和绘图都在主线程。解决把轮询放到 QThread 或 Python 的 threading 里通过信号槽把数据传给界面。注意 Matplotlib 的绘图必须在主线程子线程只负责读数据。如果数据量大可以用队列缓冲界面定时从队列取最新一帧。5. 进阶技巧用配置文件驱动多类型传感器混接这套软件真正省事的地方在于TEV、超声波、UHF 三种传感器可以混在同一条总线上只要它们的波特率和数据格式一致。我一般把传感器配置写成 JSON启动时遍历生成对应的读取任务和界面面板。下面是一个混接配置的片段。{ serial: {port: COM3, baudrate: 9600, parity: N}, sensors: [ {type: TEV, slave_id: 1, telemetry_addrs: [0, 1], prpd_addr: 256, prpd_len: 64}, {type: Ultrasonic, slave_id: 2, telemetry_addrs: [0, 1], prpd_addr: 256, prpd_len: 64}, {type: UHF, slave_id: 3, telemetry_addrs: [0, 1, 2], prpd_addr: 512, prpd_len: 128} ] }参数说明telemetry_addrs是遥测寄存器起始地址列表不同传感器长度不同。prpd_addr和prpd_len决定图谱读取范围。UHF 传感器数据量大prpd_len给到 128。加载配置后界面按传感器数量动态生成选项卡每个选项卡里放遥测数值和 PRPD 图。验证方法先只接一个传感器确认遥测和图谱都正常再逐个增加。每加一个观察轮询周期是否还在可接受范围。如果周期超过 2 秒就要考虑把图谱轮询拆到独立线程或者降低图谱刷新频率。我习惯在软件里加一个通信质量统计记录每个传感器的超时次数和平均响应时间调试的时候一眼就能看出哪条链路有问题。从那以后我每次拿到新传感器都强制先用手动发帧的方式确认寄存器映射再写进配置文件绝不直接猜。希望帮到你。本文还有配套的精品资源点击获取