

先说清楚一个基本事实:楼宇自控(BA)系统里,温湿度数据从来不是"采集到就行"这么简单。它要同时服务于两个完全不同的消费方——楼控 DDC/PLC 需要周期性地拿数据做逻辑联动(空调启停、新风阀开度),动环平台需要实时接收越限告警做应急处置(短信通知、声光报警、联动排风)。这两个需求对通信模式的要求截然不同:前者是拉模式(轮询),后者是推模式(事件驱动)。
如果只用单一链路,要么告警延迟高(轮询周期 30s,告警发现就晚了 30s),要么占用大量轮询资源(为了快就把周期缩到 5s,交换机和传感器都吃不消)。双链路设计的核心思路就是:SNMP 负责"我知道你要什么,我定时给你",UDP 负责"我有事,我主动告诉你"。

┌─────────────────────────────────────────────────────────────────────┐
│ 楼宇自控中心 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ BA 系统(DDC) │ │ 动环监控平台 │ │
│ │ Niagara / │ │ Zabbix / │ │
│ │ Honeywell / │◄──── SNMP 轮询 ────│ 定制平台 │ │
│ │ Siemens │ (161端口) │ │ │
│ │ │──── UDP 接收 ────►│ UDP Server │ │
│ │ 采集周期 30s │ (8888端口) │ (8888端口) │ │
│ └──────────────────┘ └──────────────────┘ │
│ │ │ │
└───────────┼──────────────────────────────────────┼───────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ 传感器接入层(独立监测 VLAN) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 以太网温湿度传感器(双栈固件) │ │
│ │ ┌─────────────────┐ ┌──────────────────────────────┐ │ │
│ │ │ SNMP Agent │ │ UDP 告警推送模块 │ │ │
│ │ │ (161端口) │ │ (目标IP:Port, 间隔可配) │ │ │
│ │ │ v1/v2c/v3 │ │ 阈值触发/心跳/状态变化 │ │ │
│ │ └─────────────────┘ └──────────────────────────────┘ │ │
│ │ │ │ │ │
│ │ └──────────┬───────────────┘ │ │
│ │ ▼ │ │
│ │ ┌─────────────────┐ │ │
│ │ │ 共享数据区 │ ← 温湿度采样值、状态字、配置 │ │
│ │ │ (互斥锁保护) │ │ │
│ │ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 供电:PoE 802.3af/at,每端口预算 ×1.5 冗余 │
│ 布线:Cat6 S/FTP 屏蔽,独立桥架,与动力电缆隔离 ≥30cm │
│ 网络:接入交换机端口安全 MAC限1 + Storm Control + BPDU Guard │
└───────────────────────────────────────────────────────────────────────┘维度 | SNMP | Modbus TCP |
|---|---|---|
BA 系统原生支持 | ✅ 几乎所有 BA 平台(Niagara、Honeywell、Siemens)都内置 SNMP 驱动 | ❌ 需要额外网关或自定义驱动 |
多设备批量管理 | ✅ 通过 SNMP Walk 自动发现 | ❌ 需要逐个配置 IP 和寄存器 |
标准 MIB 定义 | ✅ 有标准 MIB-II + 自定义私有 MIB | ❌ 寄存器映射每家不同 |
轮询开销 | 低(单次请求多 OID) | 中(每次读少量寄存器) |
实时性 | 取决于轮询周期 | 取决于轮询周期 |
适合场景 | 周期性数据采集、设备状态监控 | 高频采集、复杂寄存器操作 |
结论:BA 系统做常规数据采集和状态监控,SNMP 是最省事的方案。不需要写驱动、不需要配寄存器映射,BA 工程师用 MIB 文件导入就能用。
-- 企业私有 MIB 树
-- 1.3.6.1.4.1.<Enterprise-OID>.1.x
temperatureValue 1.3.6.1.4.1.xxx.1.1 INTEGER ×0.1 ℃
temperatureSetpoint 1.3.6.1.4.1.xxx.1.2 INTEGER ×0.1 ℃
temperatureAlarmHigh 1.3.6.1.4.1.xxx.1.3 INTEGER ×0.1 ℃
temperatureAlarmLow 1.3.6.1.4.1.xxx.1.4 INTEGER ×0.1 ℃
humidityValue 1.3.6.1.4.1.xxx.1.5 INTEGER ×0.1 %RH
humiditySetpoint 1.3.6.1.4.1.xxx.1.6 INTEGER ×0.1 %RH
humidityAlarmHigh 1.3.6.1.4.1.xxx.1.7 INTEGER ×0.1 %RH
humidityAlarmLow 1.3.6.1.4.1.xxx.1.8 INTEGER ×0.1 %RH
deviceStatus 1.3.6.1.4.1.xxx.1.9 INTEGER 1=正常 2=告警 3=离线
powerStatus 1.3.6.1.4.1.xxx.1.10 INTEGER 1=PoE 2=DC 3=断电
uptime 1.3.6.1.4.1.xxx.1.11 Timeticks
firmwareVersion 1.3.6.1.4.1.xxx.1.12 OCTET STRINGNiagara AX / N4 配置示例:
1. 导入私有 MIB 文件(.mib 或编译为 .module)
2. 创建 SNMP 设备,填写:
- IP 地址:传感器 IP
- 端口:161
- 社区名(v1/v2c):public(建议改为自定义)
- 版本:v2c(或 v3 加密)
- 超时:2s
- 重试:2 次
3. 添加数据点:
- temperatureValue → 模拟输入点(AI),缩放 ×0.1
- humidityValue → 模拟输入点(AI),缩放 ×0.1
- deviceStatus → 二进制输入点(BI)
4. 轮询周期:30s(常规)/ 10s(关键机房)
Honeywell EBI / Siemens Desigo 配置类似,均支持 SNMP 驱动除了 BA 系统轮询,传感器还可以配置 SNMP Trap,在越限事件发生时主动向网管平台发送 Trap 报文:
Trap 配置:
- Trap 目标 IP:动环平台 IP
- Trap 端口:162
- 社区名:trap-community
- 版本:v2c(或 v3)
- 触发条件:温度 > 设定值 / 温度 < 设定值 / 离线 / 上电
Trap OID 示例:
1.3.6.1.4.1.xxx.2.1 temperatureAlarm (INTEGER: 1=High 2=Low 3=Cleared)
1.3.6.1.4.1.xxx.2.2 humidityAlarm (INTEGER: 1=High 2=Low 3=Cleared)
1.3.6.1.4.1.xxx.2.3 deviceRestart (Timeticks: 重启时间)SNMP 轮询的周期决定了告警发现延迟的下限。如果 BA 系统 30s 轮询一次,那么一个温度越限事件最多要 30s 才能被发现。对于以下场景,这个延迟不可接受:

UDP 推送的优势:
维度 | UDP 推送 | SNMP 轮询 |
|---|---|---|
延迟 | 毫秒级(事件触发即发) | 秒级(取决于轮询周期) |
网络开销 | 极低(仅事件发生时发送) | 持续占用(周期性请求+响应) |
服务器负载 | 极低(无状态接收) | 中(需维护轮询任务) |
可靠性 | 不可靠(丢包不重传) | 可靠(TCP 或 UDP 重试) |
适合场景 | 实时告警、事件通知 | 周期性数据采集 |
设计原则:UDP 只负责"通知有事件发生",不负责传输完整的历史数据。收到 UDP 告警后,动环平台再通过 SNMP 或 Modbus TCP 去读取详细信息。
┌──────────────────────────────────────────────────────────────┐
│ UDP 告警报文(32 字节) │
├────────┬────────┬────────┬────────┬────────┬────────┬────────┤
│ 帧头 │ 设备ID │ 时间戳 │ 序列号 │ 温度 │ 湿度 │ 状态字 │
│ 2B │ 4B │ 4B │ 2B │ 2B │ 2B │ 2B │
│ AA 55 │ 000064 │ 66789A │ 000A │ 0938 │ 022A │ 0001 │
├────────┼────────┼────────┼────────┼────────┼────────┼────────┤
│ 电源电压 │ 保留 │ CRC16 │
│ 2B │ 4B │ 2B │
│ 12D6 │ 0000 │ 3F8E │
└────────┴────────┴────────┘
状态字位定义:
bit0: 高温告警(1=触发)
bit1: 低温告警
bit2: 高湿告警
bit3: 低湿告警
bit4: 传感器离线
bit5: PoE 断电
bit6: 通信异常
bit7: 保留触发条件(满足任一即推送):
1. 温度 ≥ 高温阈值(如 28℃)
2. 温度 ≤ 低温阈值(如 18℃)
3. 湿度 ≥ 高湿阈值(如 65%RH)
4. 湿度 ≤ 低湿阈值(如 35%RH)
5. 传感器状态变化(上电、离线恢复)
6. 心跳(每 60s 一次,用于检测链路存活)
防抖机制:
→ 连续 3 次采样均越限才触发(避免瞬时干扰)
→ 告警恢复需连续 3 次采样回到正常范围
→ 同一告警事件最小间隔 30s(防止风暴)
目标配置:
→ 目标 IP:动环平台 IP(支持多目标,最多 3 个)
→ 目标端口:8888(可配置)
→ 发送次数:3 次(间隔 1s,提高到达率)
→ 超时:不等待响应(UDP 无连接)┌─────────────────────┐
│ 共享数据区 │
│ temp = 25.6℃ │
│ hum = 52.3%RH │
│ status = 0x00 │
└──────────┬──────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ SNMP Agent │ │ UDP 推送模块 │ │ Modbus TCP │
│ 响应轮询 │ │ 事件触发 │ │ (可选) │
│ 161端口 │ │ 8888端口 │ │ 502端口 │
└─────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ └──────────────┘
│ BA 系统 │ │ 动环平台 │
│ 30s 轮询 │ │ 实时接收 │
│ 拿趋势数据 │ │ 拿告警事件 │
└─────────────┘ └──────────────┘共享数据区访问规则:
→ 采样任务(最高优先级):每 2s 更新一次共享数据区
→ SNMP 响应:读共享数据区,加互斥锁,读完后释放
→ UDP 推送:读共享数据区 + 判断阈值,不阻塞其他任务
→ Modbus TCP(如启用):读共享数据区,加互斥锁
任务优先级(FreeRTOS):
tcpip_thread > SNMP > UDP推送 > Modbus TCP(如并存)
注意:如果 SNMP 和 Modbus TCP 同时启用,共享数据区的互斥锁必须保证
读取的原子性,避免 SNMP 读到一半的数据。LwIP 参数调优(SNMP + UDP 双栈):
MEMP_NUM_TCP_PCB: 5 → 8-12(SNMP 用 UDP,但 Modbus TCP 可能并存)
MEMP_NUM_UDP_PCB: 4 → 8(SNMP + UDP 推送各占)
MEM_SIZE: 16KB → 32-48KB
MEMP_NUM_PBUF: 16 → 32
UDP 发送缓冲区: 每 socket 2KB
CPU 占用估算(以 120MHz Cortex-M4 为例):
SNMP 响应: ~2ms/次(30s 周期,几乎可忽略)
UDP 推送: ~1ms/次(事件触发,频率低)
采样: ~5ms/次(2s 周期)
总计: <1% CPU 占用import socket
import struct
import time
from datetime import datetime
class SensorUDPReceiver:
def __init__(self, host="0.0.0.0", port=8888):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.bind((host, port))
self.sock.settimeout(1.0)
print(f"UDP Server listening on {host}:{port}")
def parse_packet(self, data, addr):
if len(data) < 24:
return None
# 帧头校验
header = struct.unpack_from("<H", data, 0)[0]
if header != 0x55AA:
return None
fields = struct.unpack_from("<IHHhhH", data, 0)
# fields: header, device_id, timestamp, seq, temp_raw, hum_raw, status
_, device_id, ts, seq, temp_raw, hum_raw, status = fields
return {
"device_id": device_id,
"timestamp": ts,
"sequence": seq,
"temperature": temp_raw * 0.1,
"humidity": hum_raw * 0.1,
"status": status,
"source_ip": addr[0],
"recv_time": datetime.now().isoformat()
}
def run(self):
while True:
try:
data, addr = self.sock.recvfrom(1024)
pkt = self.parse_packet(data, addr)
if pkt:
self.handle_alarm(pkt)
except socket.timeout:
pass
def handle_alarm(self, pkt):
status = pkt["status"]
alarms = []
if status & 0x01:
alarms.append(f"高温告警: {pkt['temperature']}℃")
if status & 0x02:
alarms.append(f"低温告警: {pkt['temperature']}℃")
if status & 0x04:
alarms.append(f"高湿告警: {pkt['humidity']}%RH")
if status & 0x08:
alarms.append(f"低湿告警: {pkt['humidity']}%RH")
if status & 0x10:
alarms.append("传感器离线")
if alarms:
print(f"[{pkt['recv_time']}] 设备 {pkt['device_id']} "
f"({pkt['source_ip']}): {'; '.join(alarms)}")
# 这里可以触发短信、邮件、声光报警等
if __name__ == "__main__":
receiver = SensorUDPReceiver(port=8888)
receiver.run()from pysnmp.hlapi import *
def snmp_get(ip, oid, community="public", port=161):
iterator = getCmd(
SnmpEngine(),
CommunityData(community),
UdpTransportTarget((ip, port), timeout=2, retries=2),
ContextData(),
ObjectType(ObjectIdentity(oid))
)
errorIndication, errorStatus, errorIndex, varBinds = next(iterator)
if errorIndication:
return None
for varBind in varBinds:
return varBind[1].prettyPrint()
# 读取温度
temp_raw = snmp_get("192.168.100.50", "1.3.6.1.4.1.xxx.1.1.0")
temp = int(temp_raw) * 0.1 if temp_raw else None
# 读取湿度
hum_raw = snmp_get("192.168.100.50", "1.3.6.1.4.1.xxx.1.5.0")
hum = int(hum_raw) * 0.1 if hum_raw else None
print(f"温度: {temp}℃, 湿度: {hum}%RH")Niagara 中的联动逻辑(温湿度 → 空调控制):
条件1:温度 > 26℃ 且持续 5min
→ 动作:启动备用空调 / 增大新风阀开度 / 发送告警
条件2:温度 < 20℃ 且持续 5min
→ 动作:关闭新风阀 / 启动加热 / 发送告警
条件3:湿度 > 65%RH 且持续 10min
→ 动作:启动除湿机 / 增大排风量 / 发送告警
条件4:湿度 < 35%RH 且持续 10min
→ 动作:启动加湿器 / 发送告警
条件5:deviceStatus != 1(设备异常)
→ 动作:发送维护工单 / 声光报警
实现方式:
→ 在 Niagara 中创建 Control Program(AX/JavaScript 或 PX 图形编程)
→ 输入点:SNMP 数据点(temperatureValue, humidityValue, deviceStatus)
→ 输出点:BACnet 或 Modbus 输出到 DDC
→ 定时器:使用 Timer 对象实现"持续 N 分钟"逻辑
→ 带回差:避免临界值频繁切换(如 26℃ 启动,25.5℃ 停止)┌────────────────────────────────────────────────────┐
│ 温度控制状态机(带回差) │
│ │
│ 当前状态:空调关闭 │
│ ├── 温度 ≥ 26.0℃ → 启动空调(进入"制冷中") │
│ └── 温度 < 26.0℃ → 保持关闭 │
│ │
│ 当前状态:空调开启(制冷中) │
│ ├── 温度 ≤ 25.0℃ → 关闭空调(进入"关闭") │
│ └── 温度 > 25.0℃ → 保持开启 │
│ │
│ 回差 = 1.0℃(26.0 - 25.0) │
│ 目的:避免温度在阈值附近波动时空调频繁启停 │
│ │
│ 湿度同理:启动 65%,停止 63%(回差 2%RH) │
└────────────────────────────────────────────────────┘VLAN 规划:
VLAN 10: 办公网
VLAN 20: BA 系统(DDC、Niagara 服务器)
VLAN 30: 动环平台
VLAN 100: 传感器接入(独立监测 VLAN)
路由策略:
VLAN 20 → VLAN 100: 允许 SNMP (161), Modbus TCP (502)
VLAN 30 → VLAN 100: 允许 UDP (8888), SNMP Trap (162)
VLAN 100 → 其他: 默认拒绝(传感器不主动访问其他网络)
交换机配置要点:
→ 传感器端口:Access VLAN 100
→ 端口安全:MAC 地址限制 1 个
→ BPDU Guard: 启用(防止环路)
→ Storm Control: 广播/组播抑制 10%
→ PoE 优先级: Critical(确保传感器不断电)v1/v2c 安全建议:
→ 修改默认 community(public/private → 自定义复杂字符串)
→ 使用 SNMP View 限制可读 OID 范围
→ 使用 ACL 限制可访问的源 IP
v3 安全(推荐用于新建项目):
→ 认证:SHA-256
→ 加密:AES-256
→ 用户:创建独立监控用户,只读权限
→ 引擎 ID:配置唯一引擎 ID步骤1:确认 SNMP 已启用
→ 登录传感器 Web 界面,确认 SNMP Agent 状态为"启用"
→ 确认端口 161,社区名/用户配置正确
→ 常见坑:SNMP 默认关闭,藏在"高级设置"第 3 页
步骤2:命令行测试
snmpwalk -v2c -c public 192.168.100.50 1.3.6.1.4.1.xxx.1
→ 能看到温度和湿度数据 = SNMP 正常
步骤3:找对 OID
→ 用 snmpwalk 列出所有 OID,确认私有 MIB 的 OID 路径
→ 对比 MIB 文件中的定义
步骤4:验证数据
→ 对比 Web 界面显示值和 SNMP 读取值
→ 确认缩放因子正确(×0.1 还是 ×0.01)
→ 常见坑:温度显示 224 而非 22.4,缩放因子不对步骤1:确认 UDP 推送已启用
→ Web 界面确认 UDP 推送目标 IP、端口、阈值配置正确
步骤2:服务器端抓包
tcpdump -i eth0 udp port 8888
→ 能看到 UDP 包 = 传感器在推送
步骤3:验证报文内容
Wireshark 过滤:udp.port == 8888
→ 展开 UDP 载荷,对照报文格式逐字节解析
步骤4:检查防火墙
→ 服务器防火墙放行 UDP 8888 端口
→ 交换机 ACL 允许传感器到服务器的 UDP 流量现象:SNMP 读到的温度和 UDP 推送的温度不一致
→ 可能原因1:SNMP 和 UDP 读取的不是同一时刻的采样值
(共享数据区更新间隔 vs 推送时机)
→ 可能原因2:缩放因子配置不同
→ 可能原因3:SNMP 缓存了旧值(Agent 内部缓存未刷新)
排查方法:
→ 同时抓 SNMP 响应和 UDP 报文,对比时间戳
→ 确认两者读取的寄存器/OID 是同一个物理量
→ 检查传感器固件是否有已知 bug项目 | 标准 | 测试方法 |
|---|---|---|
SNMP 可读性 | 100% OID 可读,响应 <200ms | snmpwalk 全量读取 |
SNMP 精度 | 与 Web 界面偏差 ≤0.5℃/≤3%RH | 同时读取对比 |
UDP 推送延迟 | 越限事件 <5s 到达服务器 | 人为制造越限,记录时间差 |
UDP 到达率 | 丢包率 <0.1%(局域网内) | 连续发送 10000 包统计 |
双链路一致性 | SNMP 和 UDP 读同一物理量偏差 <0.5℃ | 同时采集对比 |
联动响应 | BA 系统从越限到执行动作 <60s | 模拟越限,记录完整链路时间 |
在线率 | ≥99.9%(7×24h) | 持续监控 |
并发能力 | 同时响应 8 个 SNMP Get + UDP 推送 | 压力测试 |
楼宇自控动环联动的双链路设计,本质是用 SNMP 喂 BA 系统的"胃",用 UDP 敲动环平台的"门"。 SNMP 解决"BA 工程师不想写驱动"的问题,UDP 解决"告警不能等 30s"的问题。 两条链路共享同一份数据,但走完全不同的通信路径——一个拉一个推,一个管趋势一个管事件。 设计好了,BA 系统和动环平台各取所需;设计不好,两条链路互相干扰,反而把简单问题搞复杂。 关键就三点:共享数据区加锁、UDP 推送防抖、SNMP 和 UDP 的 OID/报文格式文档化,让 BA 工程师和动环工程师都能"对表"。
关键词:楼宇自控、动环联动、SNMP、UDP、双链路、以太网温湿度传感器、BA系统、Niagara、告警推送、MIB设计、回差控制、VLAN隔离、PoE供电、FreeRTOS、LwIP、Modbus TCP
标签:#楼宇自控 #动环联动 #SNMP #UDP #双链路 #以太网传感器 #温湿度 #BA系统 #Niagara #告警推送 #BACnet #MIB #回差控制 #VLAN #PoE #FreeRTOS #LwIP #ModbusTCP #动环监控 #智能建筑
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。