
电厂配电室和常规机房/车间柜内监测有一个本质区别:它不是单一场景,而是一组强约束条件的叠加。

先说清楚这个背景,后面所有设计决策才有依据:
约束维度 | 具体条件 | 对监测方案的影响 |
|---|---|---|
电磁环境 | 发电机出口断路器分合闸、励磁系统、厂用电切换 | 传感器和通信链路必须耐受强瞬态干扰 |
网络架构 | 生产控制大区与管理信息大区物理隔离(等保+电力监控系统安全防护规定) | 监测数据不能直接上云,必须落在本地 |
可靠性要求 | 连续运行,计划检修窗口外不允许中断 | 服务器和采集链路需冗余设计 |
数据完整性 | 环境数据可能作为事故追溯依据 | 不能丢数据,时间戳必须可信 |
设备规模 | 大型电厂配电室通常有20–200个监测点 | 需要分层架构,不能简单点对点 |
运维权限 | 生产区网络变更需工作票审批 | 方案要一次做对,减少后期改动 |
本文给出一套从传感器到本地服务器的完整对接方案,覆盖网络拓扑、通信协议、服务端架构、数据持久化和运维管理。
┌─────────────────────────────────────────────────────────────────────┐
│ 电厂配电室(生产控制大区/安全区Ⅱ) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ 6kV配电室 │ │ 400V配电室│ │ 励磁小室 │ │ 电缆夹层 │ │
│ │ │ │ │ │ │ │ │ │
│ │ [T1] │ │ [T2] │ │ [T3] │ │ [T4]...[Tn] │ │
│ │ 温湿度 │ │ 温湿度 │ │ 温湿度 │ │ 温湿度 │ │
│ │ 变送器 │ │ 变送器 │ │ 变送器 │ │ 变送器 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬────────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┴──────────────────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │ 工业交换机 │ (环网/星型, 管理VLAN隔离) │
│ │ 带光口 │ │
│ └─────┬─────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │ 正向隔离 │ (生产区→管理区,单向) │
│ │ 装置 │ 如需要跨区传输 │
│ └─────┬─────┘ │
└──────────────────────────┼───────────────────────────────────────────┘
│
┌──────────────────────────┼───────────────────────────────────────────┐
│ 管理信息大区/安全区Ⅲ │ │
│ │ │
│ ┌───────────────────────▼───────────────────────────────────────┐ │
│ │ 数据采集服务(Daemon) │ │
│ │ ├── TCP连接池管理(长连接/短连接可配) │ │
│ │ ├── 协议解析(Modbus TCP / 私有TCP / SNMP) │ │
│ │ ├── 数据校验与归一化 │ │
│ │ └── 缓存与批量写入 │ │
│ └───────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────▼───────────────────────────────────────┐ │
│ │ 时序数据库(TDengine / InfluxDB) │ │
│ │ ├── 原始数据表(带时间戳、设备ID、区域标签) │ │
│ │ ├── 降采样表(1min/5min/1h 聚合) │ │
│ │ └── 保留策略(原始90天,降采样永久) │ │
│ └───────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────▼───────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ ├── 实时监控大屏(Web) │ │
│ │ ├── 告警引擎(越限/变化率/通信中断) │ │
│ │ ├── 历史趋势分析 │ │
│ │ └── 数据导出(Excel/PDF,事故追溯用) │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 冗余设计 │ │
│ │ ├── 双机热备(主备服务器,心跳检测) │ │
│ │ ├── 数据库主从复制 │ │
│ │ └── 采集服务断线续传(本地SQLite缓存) │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
电厂配电室测点多、分布广,IP规划要有层次:
网段:10.40.0.0/16(管理VLAN 40,配电室专用)
10.40.10.0/24 — 6kV配电室A列
10.40.10.1 — 交换机管理IP
10.40.10.11 — 变送器 T-6kVA-01(A列1柜)
10.40.10.12 — 变送器 T-6kVA-02
...
10.40.11.0/24 — 6kV配电室B列
10.40.20.0/24 — 400V配电室
10.40.30.0/24 — 励磁小室
10.40.40.0/24 — 电缆夹层
10.40.254.1 — 采集服务器(主)
10.40.254.2 — 采集服务器(备)每台变送器配置:
参数 | 值 | 说明 |
|---|---|---|
IP地址 | 静态,按规划表 | DHCP在电厂环境不可靠 |
子网掩码 | 255.255.255.0 | /24 |
网关 | 对应区域交换机IP | 跨子网通信用 |
TCP端口 | 默认502(Modbus TCP)或自定义 | 与服务器采集程序一致 |
通信模式 | Server(被动等待连接)或 Client(主动上报) | 推荐Server模式,服务器主动轮询 |
上报周期 | 5–10s | 平衡实时性和网络负载 |
数据格式 | IEEE754浮点 / 整型放大10倍 | 与服务器解析程序匹配 |
每台设备外壳贴标签,内容与IP规划表一致:
设备ID:T-6kVA-01
区域:6kV配电室-A列
安装位置:#3开关柜母线室
IP:10.40.10.11
MAC:00:08:DC:01:0A:0B
安装日期:2025-03-15
校准有效期:2026-03-15方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
Modbus TCP | 标准协议,通用性强,调试工具多 | 无压缩,报文冗余;功能码有限 | 大多数场景首选 |
私有TCP协议 | 紧凑高效,可定制字段 | 需自行开发解析程序 | 自研传感器/大规模部署 |
SNMP | 运维侧标准,轮询简单 | 实时性一般,不适合高频采集 | 已有SNMP基础设施时 |
推荐:电厂项目首选 Modbus TCP,理由是——电厂DCS/辅控系统普遍支持Modbus TCP,后期如需接入厂级SIS(厂级监控信息系统),协议对接零成本。
设备地址:每个变送器独立Slave ID(1–247)
功能码:0x04(读输入寄存器)
寄存器地址表:
40001 (0x0000) — 温度值,INT16,放大10倍,单位℃
40002 (0x0001) — 湿度值,INT16,放大10倍,单位%RH
40003 (0x0002) — 传感器状态,UINT16,位定义见下
40004 (0x0003) — 设备运行时间,UINT32,单位小时(占2个寄存器)
40006 (0x0005) — 采样次数,UINT32(占2个寄存器)
40008 (0x0007) — 通信错误计数,UINT16
40009 (0x0008) — 软件版本号,UINT16(高字节主版本,低字节次版本)
状态寄存器位定义:
Bit 0 — 传感器正常(1=正常,0=故障)
Bit 1 — 温度越上限
Bit 2 — 温度越下限
Bit 3 — 湿度越上限
Bit 4 — 湿度越下限
Bit 5 — 校准标志(1=需要校准)
Bit 6 — 通信看门狗(1=最近一次通信正常)
Bit 7–15 — 保留package main
import (
"encoding/binary"
"fmt"
"net"
"sync"
"time"
"github.com/go-modbus/modbus"
"github.com/influxdata/influxdb-client-go/v2"
)
// 设备配置
type DeviceConfig struct {
ID string
IP string
Port int
SlaveID byte
Zone string
Location string
Enabled bool
}
// 采集结果
type Sample struct {
DeviceID string
Zone string
Location string
Temp float64
Humi float64
Status uint16
Ts time.Time
Err error
}
var (
devices []DeviceConfig
influxCli influxdb2.Client
samples chan Sample
)
func initDevices() {
// 从配置文件或数据库加载设备列表
devices = []DeviceConfig{
{"T-6kVA-01", "10.40.10.11", 502, 1, "6kV配电室", "A列#3开关柜", true},
{"T-6kVA-02", "10.40.10.12", 502, 2, "6kV配电室", "A列#5开关柜", true},
// ... 更多设备
}
}
func pollDevice(dev DeviceConfig, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
var client modbus.Client
var handler *modbus.TCPClientHandler
for range ticker.C {
if !dev.Enabled {
continue
}
// 建立连接(短连接模式:每次轮询新建连接)
handler = modbus.NewTCPClientHandler(fmt.Sprintf("%s:%d", dev.IP, dev.Port))
handler.SlaveId = dev.SlaveID
handler.Timeout = 3 * time.Second
if err := handler.Connect(); err != nil {
samples <- Sample{
DeviceID: dev.ID, Zone: dev.Zone, Location: dev.Location,
Ts: time.Now(), Err: fmt.Errorf("connect: %w", err),
}
handler.Close()
continue
}
client = modbus.NewClient(handler)
// 读取4个寄存器(温度、湿度、状态、保留)
results, err := client.ReadInputRegisters(0, 4)
if err != nil {
samples <- Sample{
DeviceID: dev.ID, Zone: dev.Zone, Location: dev.Location,
Ts: time.Now(), Err: fmt.Errorf("read: %w", err),
}
handler.Close()
continue
}
// 解析
temp := float64(int16(binary.BigEndian.Uint16(results[0:2]))) / 10.0
humi := float64(int16(binary.BigEndian.Uint16(results[2:4]))) / 10.0
status := binary.BigEndian.Uint16(results[4:6])
samples <- Sample{
DeviceID: dev.ID, Zone: dev.Zone, Location: dev.Location,
Temp: temp, Humi: humi, Status: status,
Ts: time.Now(), Err: nil,
}
handler.Close()
}
}
func writer() {
writeAPI := influxCli.WriteAPI("org", "env_monitor")
for s := range samples {
if s.Err != nil {
// 通信异常,写入错误日志/告警
fmt.Printf("[%s] %v\n", s.DeviceID, s.Err)
// 触发告警:连续3次失败 → 上报告警
continue
}
p := influxdb2.NewPoint(
"env_sample",
map[string]string{
"device_id": s.DeviceID,
"zone": s.Zone,
"location": s.Location,
},
map[string]interface{}{
"temp_c": s.Temp,
"humi_pct": s.Humi,
"status": int(s.Status),
},
s.Ts,
)
writeAPI.WritePoint(p)
}
}
func main() {
initDevices()
influxCli = influxdb2.NewClient("http://localhost:8086", "token")
defer influxCli.Close()
samples = make(chan Sample, 1000)
// 启动采集协程
for _, dev := range devices {
go pollDevice(dev, 10*time.Second)
}
// 启动写入协程
go writer()
select {} // 阻塞运行
}当设备数量超过100台时,短连接(每次轮询新建TCP连接)的开销变得显著。改为长连接池模式:
type ConnectionPool struct {
mu sync.Mutex
conns map[string]net.Conn // key: deviceID
cfg map[string]DeviceConfig
}
func (p *ConnectionPool) Get(devID string) (net.Conn, error) {
p.mu.Lock()
defer p.mu.Unlock()
if conn, ok := p.conns[devID]; ok {
// 检查连接是否还活着
if conn != nil {
conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
one := make([]byte, 1)
_, err := conn.Read(one)
if err == nil {
conn.SetReadDeadline(time.Time{})
return conn, nil
}
// 连接已断,关闭旧连接
conn.Close()
}
}
// 新建连接
dev := p.cfg[devID]
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", dev.IP, dev.Port), 3*time.Second)
if err != nil {
return nil, err
}
p.conns[devID] = conn
return conn, nil
}数据库 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
TDengine | 专为时序优化,压缩比高,集群成熟 | 相对较新,社区资源少 | ★★★★★(首选) |
InfluxDB | 生态成熟,查询灵活 | 集群版收费,单机有写入瓶颈 | ★★★★ |
TimescaleDB | 基于PostgreSQL,SQL友好 | 时序优化不如专用TSDB | ★★★★ |
MySQL + 分区表 | 运维简单 | 写入性能和压缩比都差 | ★★ |
推荐TDengine,理由:电厂数据量大(200点×6次/分钟×24小时=28.8万条/天),TDengine的压缩比可达1:10,单机能扛住。
采集服务在写入数据库失败时,先缓存到本地SQLite:
// 写入失败时存入本地缓存
func cacheSample(s Sample) {
db, _ := sql.Open("sqlite3", "/var/cache/envd/offline.db")
stmt, _ := db.Prepare(
"INSERT INTO offline_cache(device_id, ts, temp, humi, status) VALUES(?,?,?,?,?)")
stmt.Exec(s.DeviceID, s.Ts.Unix(), s.Temp, s.Humi, s.Status)
}
// 数据库恢复后,批量回灌
func flushCache() {
// 读取SQLite,批量写入TDengine,成功后删除缓存记录
}主服务器(10.40.254.1):
├── 采集服务(活跃)
├── 数据库(主)
└── 心跳检测(每1s ping备机)
备服务器(10.40.254.2):
├── 采集服务(待命,不主动采集)
├── 数据库(从,实时同步)
└── 心跳检测(每1s ping主机)
切换条件:
- 主机心跳丢失 > 5s
- 主机数据库无新数据 > 60s
- 手动切换(运维命令)
切换动作:
- 备机采集服务启动
- 备机数据库提升为主
- 通知交换机/网关更新路由(VRRP)rules:
- name: high_temp
condition: temp_c > 40
duration: 60s # 持续60秒才触发,避免瞬时干扰
severity: warning
message: "{{device_id}} 温度超限: {{temp_c}}℃"
- name: high_humi
condition: humi_pct > 80
duration: 120s
severity: warning
message: "{{device_id}} 湿度超限: {{humi_pct}}%RH"
- name: rapid_temp_change
condition: abs(rate(temp_c[1m])) > 2
duration: 30s
severity: critical
message: "{{device_id}} 温度骤变: {{rate}}℃/min"
- name: comm_lost
condition: time_since_last_sample > 120s
severity: critical
message: "{{device_id}} 通信中断超过120秒"
- name: sensor_fault
condition: status & 0x0001 == 0
severity: warning
message: "{{device_id}} 传感器故障"渠道 | 触发条件 | 实现方式 |
|---|---|---|
声光报警器 | 任何Critical级别 | 继电器输出到现场声光 |
短信网关 | Critical | HTTP API调用电厂短信平台 |
运维平台工单 | Warning持续>30min | 调用运维系统API |
大屏弹窗 | 任何告警 | WebSocket推送到前端 |
电厂网络按等保2.0和《电力监控系统安全防护规定》要求分区:
安全区Ⅰ(控制区):DCS、保护系统 — 绝对不能接环境监测
安全区Ⅱ(非控制区):辅控系统、SIS接口 — 监测数据可接入
安全区Ⅲ(管理区):MIS、办公网 — 监控展示、告警通知环境监测系统部署在安全区Ⅱ,采集服务器通过正向隔离装置(如南瑞SysKeeper)单向同步数据到安全区Ⅲ的展示服务器。
关键点:
层面 | 措施 |
|---|---|
网络 | VLAN隔离,ACL限定只允许采集服务器访问变送器TCP端口 |
主机 | 采集服务器关闭不必要的服务,只开502(采集)和SSH(运维) |
应用 | 采集程序以非root用户运行,最小权限 |
数据 | 数据库启用认证,通信加密(TLS,如变送器支持) |
审计 | 所有配置变更、告警确认操作记录日志 |
□ IP规划表确认,无冲突
□ 交换机端口VLAN配置正确
□ 传感器安装位置合理(避开热源、气流短路)
□ 网线敷设符合规范(与强电线缆间距>30cm,交叉时垂直)
□ RJ45接头打线牢固,线序T568B
□ 屏蔽网线屏蔽层单端接地(交换机侧)
□ 设备上电,Web界面可访问
□ 配置静态IP、网关、TCP参数
□ 标签张贴,与规划表一致□ 采集服务器能ping通所有变送器
□ Modbus TCP读取成功,数据准确
□ 与手持仪比对,温度误差≤±0.5℃,湿度≤±3%RH
□ 模拟通信中断(拔网线),告警正确触发
□ 模拟越限,告警正确触发
□ 数据库写入正常,Grafana可查询
□ 连续运行72小时,数据完整率≥99.9%□ 网络拓扑图
□ IP-区域-测点映射表
□ 寄存器地址表
□ 采集服务配置说明
□ 数据库备份恢复流程
□ 告警规则说明
□ 运维手册(常见问题处理)
□ 备品备件清单电厂配电室环境监控的对接方案,核心在于在强约束条件下找到可靠、可维护的平衡点:
关键词:电厂配电室,环境监控,TCP/IP,温湿度变送器,本地服务器,Modbus TCP,数据采集,时序数据库,冗余设计,安全隔离
标签:#电厂 #配电室 #环境监控 #ModbusTCP #温湿度变送器 #TCP #数据采集 #时序数据库 #冗余 #电力安全
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。