

楼宇自控系统的核心价值不在于"看得见",而在于"控得住"。以太网温湿度传感器采集到数据后,真正的考验是:如何把数据变成控制指令,并且确保控制过程安全、稳定、不抖动。空调启停联动是最常见的场景——温度高了开空调,低了关空调,听起来简单,但实际工程中翻车的案例比比皆是。
先说一个真实案例:
某甲级写字楼 BA 项目,Niagara 平台对接 120 台以太网温湿度传感器,控制 8 台新风机组 + 24 台风机盘管。 初始联动逻辑:温度 > 26℃ → 开空调;温度 < 24℃ → 关空调。 上线后问题:

┌─────────────────────────────────────────────────────────────────────┐
│ BA 平台(控制中心) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 联动逻辑引擎 │ │
│ │ ┌────────────┐ ┌────────────┐ ┌──────────────────────────┐ │ │
│ │ │ 数据采集 │ │ 联动决策 │ │ 控制输出 │ │ │
│ │ │ Modbus TCP │→│ 防抖+回差 │→│ BACnet/MQTT/Relay │ │ │
│ │ │ SNMP │ │ PID 计算 │ │ 写空调 DDC 控制器 │ │ │
│ │ │ UDP 推送 │ │ 投票机制 │ │ 或直控继电器模块 │ │ │
│ │ └────────────┘ └────────────┘ └──────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 策略管理 │ │ 告警引擎 │ │ 历史趋势 │ │
│ │ 时间表+节假日 │ │ 越限通知 │ │ 能耗分析 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└───────────────────────┬─────────────────────────────────────────────┘
│ (监测 VLAN / 控制 VLAN)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 现场设备层 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ 以太网温湿度 │ │ DDC 控制器 │ │ 空调设备 │ │
│ │ 传感器(区域) │───▶│ (AHU/FCU) │───▶│ 新风机组 │ │
│ │ 每区 3-5 台 │ │ 内置 I/O │ │ 风机盘管 │ │
│ │ 冗余部署 │ │ PID 回路 │ │ VRF 多联机 │ │
│ └─────────────────┘ └─────────────────┘ └────────────────┘ │
│ │
│ 通信协议:Modbus TCP(传感器→BA)、BACnet IP(BA→DDC) │
│ 控制方式:BA 写 DDC 的 AO/BO 点,DDC 执行 PID 输出到执行器 │
│ 安全链路:独立控制 VLAN,与办公网隔离 │
└─────────────────────────────────────────────────────────────────────┘模式 | 控制位置 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
BA 中心联动 | BA 平台计算,下发指令 | 逻辑集中管理、策略灵活 | 依赖网络通信、有延迟 | 大型楼宇、多系统协同 |
DDC 本地联动 | DDC 直接读传感器,本地控制 | 响应快、不依赖网络 | 策略固化、难以全局优化 | 独立机房、小型系统 |
混合联动(推荐) | DDC 本地 PID + BA 全局优化 | 兼顾响应速度和全局协调 | 架构复杂 | 中大型楼宇 |
推荐架构:混合联动
正常模式:DDC 本地 PID 控制(传感器直连 DDC,毫秒级响应)
优化模式:BA 平台根据全局策略调整 DDC 设定值(Setpoint Reset)
应急模式:BA 平台直接强制 DDC 输出(Override)核心问题:温度在阈值附近波动时,空调会频繁启停
解决方案:
1. 防抖:连续 N 次采样越限才触发动作(过滤瞬时波动)
2. 回差:开启阈值和关闭阈值之间留一个"死区"(防止在阈值处反复横跳)
参数示例:
夏季制冷:
开启条件:连续 3 次采样(每次间隔 30s)温度 > 26.0℃
关闭条件:连续 3 次采样温度 < 24.5℃
回差:1.5℃
冬季制热:
开启条件:连续 3 次采样温度 < 20.0℃
关闭条件:连续 3 次采样温度 > 21.5℃
回差:1.5℃
湿度控制:
除湿:连续 3 次湿度 > 65%RH → 开新风/除湿机
加湿:连续 3 次湿度 < 40%RH → 开加湿器问题:单台传感器故障或安装位置不当,会导致误判
方案:每个控制区域部署 3-5 台传感器,取中位数
区域 A(3 台传感器):
传感器 1: 25.2℃
传感器 2: 27.8℃(故障/阳光直射)
传感器 3: 25.5℃
中位数 = 25.5℃(排除异常值)
决策逻辑:
- 使用中位数作为控制依据
- 如果某台传感器与其他传感器偏差 > 2℃,标记为异常并告警
- 异常传感器不参与投票,其余传感器继续工作
- 超过半数传感器离线,切换到安全模式(按时间表运行)压缩机启动电流是额定电流的 5-7 倍,频繁启停会烧毁压缩机
保护规则:
1. 最小停机时间:空调关机后,至少等待 5 分钟才能重新启动
2. 最小运行时间:空调启动后,至少运行 3 分钟才能关机
3. 启动间隔:同一台空调两次启动之间至少间隔 10 分钟
实现方式:
DDC 控制器内置防频繁启停定时器
BA 平台下发指令时检查时间戳,未到时间则缓存指令工作日(周一至周五):
07:00-09:00 预冷/预热(上班前将温度调到舒适区)
09:00-18:00 正常控制(26℃制冷 / 20℃制热)
18:00-19:00 延时关闭(逐步减少空调运行台数)
19:00-07:00 节能模式(28℃制冷 / 18℃制热,或全关)
周末/节假日:
全天节能模式 或 按预定时间表
特殊场景:
会议室:有人占用时正常控制,无人时节能模式
机房:24h 维持 22±2℃(不随时间表变化)// Niagara 4 - JavaScript 联动逻辑
// 文件:ControlLogic/ACControl.js
// 配置参数
var CONFIG = {
COOL_SETPOINT: 26.0, // 制冷开启温度
COOL_DEADBAND: 1.5, // 制冷回差
HEAT_SETPOINT: 20.0, // 制热开启温度
HEAT_DEADBAND: 1.5, // 制热回差
HUMIDITY_HIGH: 65.0, // 高湿阈值
HUMIDITY_LOW: 40.0, // 低湿阈值
DEBOUNCE_COUNT: 3, // 防抖次数
SAMPLE_INTERVAL: 30, // 采样间隔(秒)
MIN_OFF_TIME: 300, // 最小停机时间(秒)
MIN_RUN_TIME: 180, // 最小运行时间(秒)
SENSOR_TIMEOUT: 90 // 传感器超时(秒)
};
// 状态变量
var state = {
coolDebounce: 0,
heatDebounce: 0,
humidityHighDebounce: 0,
humidityLowDebounce: 0,
acRunning: false,
acStartTime: 0,
acStopTime: 0,
lastValidSensors: []
};
/**
* 获取区域有效传感器数据(取中位数)
*/
function getZoneMedian(zoneSensors) {
var validSensors = zoneSensors.filter(function(s) {
return s.isValid && (Date.now() - s.lastUpdate) < CONFIG.SENSOR_TIMEOUT * 1000;
});
if (validSensors.length === 0) {
return null; // 无有效数据,进入安全模式
}
// 按温度排序
validSensors.sort(function(a, b) { return a.temp - b.temp; });
var median = validSensors[Math.floor(validSensors.length / 2)];
// 检测异常传感器
validSensors.forEach(function(s) {
if (Math.abs(s.temp - median.temp) > 2.0) {
log.warn("Sensor " + s.name + " abnormal: " + s.temp +
" (median=" + median.temp + ")");
s.status = "ABNORMAL";
}
});
state.lastValidSensors = validSensors;
return median;
}
/**
* 检查冷启动保护
*/
function canStartAC() {
if (!state.acRunning) {
// 停机状态,检查最小停机时间
var offTime = Date.now() - state.acStopTime;
return offTime >= CONFIG.MIN_OFF_TIME * 1000;
} else {
// 运行状态,检查最小运行时间
var runTime = Date.now() - state.acStartTime;
return runTime >= CONFIG.MIN_RUN_TIME * 1000;
}
}
/**
* 主联动逻辑
*/
function evaluateControl(zoneSensors) {
var median = getZoneMedian(zoneSensors);
if (!median) {
log.error("No valid sensors, entering safe mode");
return "SAFE_MODE"; // 安全模式:按时间表运行
}
var temp = median.temp;
var hum = median.humidity;
var now = Date.now();
// 制冷逻辑
if (temp > CONFIG.COOL_SETPOINT) {
state.coolDebounce++;
state.heatDebounce = 0; // 重置制热防抖
} else if (temp < CONFIG.COOL_SETPOINT - CONFIG.COOL_DEADBAND) {
state.coolDebounce = 0;
}
// 制热逻辑
if (temp < CONFIG.HEAT_SETPOINT) {
state.heatDebounce++;
state.coolDebounce = 0; // 重置制冷防抖
} else if (temp > CONFIG.HEAT_SETPOINT + CONFIG.HEAT_DEADBAND) {
state.heatDebounce = 0;
}
// 湿度逻辑
if (hum > CONFIG.HUMIDITY_HIGH) {
state.humidityHighDebounce++;
} else {
state.humidityHighDebounce = 0;
}
if (hum < CONFIG.HUMIDITY_LOW) {
state.humidityLowDebounce++;
} else {
state.humidityLowDebounce = 0;
}
// 决策
var action = "MAINTAIN"; // 维持现状
if (state.coolDebounce >= CONFIG.DEBOUNCE_COUNT) {
if (!state.acRunning && canStartAC()) {
action = "COOL_ON";
state.acRunning = true;
state.acStartTime = now;
log.info("Cooling ON: temp=" + temp);
} else if (state.acRunning) {
action = "COOL_CONTINUE";
}
} else if (state.heatDebounce >= CONFIG.DEBOUNCE_COUNT) {
if (!state.acRunning && canStartAC()) {
action = "HEAT_ON";
state.acRunning = true;
state.acStartTime = now;
log.info("Heating ON: temp=" + temp);
} else if (state.acRunning) {
action = "HEAT_CONTINUE";
}
} else if (state.coolDebounce === 0 && state.heatDebounce === 0) {
// 温度在舒适区内
if (state.acRunning && canStartAC()) {
action = "AC_OFF";
state.acRunning = false;
state.acStopTime = now;
log.info("AC OFF: temp=" + temp);
}
}
// 湿度控制(独立于温度)
if (state.humidityHighDebounce >= CONFIG.DEBOUNCE_COUNT) {
action += "_DEHUMIDIFY_ON";
log.info("Dehumidify ON: hum=" + hum);
}
if (state.humidityLowDebounce >= CONFIG.DEBOUNCE_COUNT) {
action += "_HUMIDIFY_ON";
log.info("Humidify ON: hum=" + hum);
}
return action;
}
/**
* 时间表判断
*/
function isWorkingHours() {
var now = new Date();
var day = now.getDay(); // 0=周日, 1=周一, ..., 6=周六
var hour = now.getHours();
// 周末
if (day === 0 || day === 6) {
return false;
}
// 工作日 7:00-19:00
return hour >= 7 && hour < 19;
}
/**
* 主循环
*/
function mainLoop() {
var zoneSensors = readZoneSensors(); // 从 Modbus TCP 读取
if (isWorkingHours()) {
var action = evaluateControl(zoneSensors);
executeAction(action);
} else {
// 非工作时间,节能模式
executeEnergySaving();
}
}
// 每 30 秒执行一次
setInterval(mainLoop, 30000);import asyncio
import time
from typing import List, Dict, Optional, Tuple
from dataclasses import dataclass, field
from datetime import datetime, time as dtime
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)
@dataclass
class SensorReading:
"""单台传感器读数"""
ip: str
name: str
temperature: float
humidity: float
timestamp: float
valid: bool = True
status: str = "OK"
@dataclass
class ZoneConfig:
"""区域配置"""
zone_id: str
name: str
sensors: List[str] # 传感器 IP 列表
cool_setpoint: float = 26.0
cool_deadband: float = 1.5
heat_setpoint: float = 20.0
heat_deadband: float = 1.5
humidity_high: float = 65.0
humidity_low: float = 40.0
debounce_count: int = 3
sample_interval: int = 30
min_off_time: int = 300 # 5 分钟
min_run_time: int = 180 # 3 分钟
@dataclass
class ACState:
"""空调状态"""
running: bool = False
mode: str = "OFF" # OFF, COOL, HEAT
start_time: float = 0.0
stop_time: float = 0.0
cool_debounce: int = 0
heat_debounce: int = 0
hum_high_debounce: int = 0
hum_low_debounce: int = 0
last_action: str = "NONE"
class ZoneController:
"""区域控制器"""
def __init__(self, config: ZoneConfig, ac_controller):
self.config = config
self.ac = ac_controller
self.state = ACState()
self.sensor_history: Dict[str, List[SensorReading]] = {
ip: [] for ip in config.sensors
}
def add_reading(self, reading: SensorReading):
"""添加新的传感器读数"""
if reading.ip not in self.sensor_history:
return
history = self.sensor_history[reading.ip]
history.append(reading)
# 只保留最近 N 条(防抖次数 × 2)
max_len = self.config.debounce_count * 2
if len(history) > max_len:
self.sensor_history[reading.ip] = history[-max_len:]
def get_median_reading(self) -> Optional[SensorReading]:
"""获取有效传感器的中位数读数"""
valid_readings = []
for ip, history in self.sensor_history.items():
if not history:
continue
latest = history[-1]
# 检查数据有效性
age = time.time() - latest.timestamp
if not latest.valid or age > self.config.sample_interval * 3:
continue
valid_readings.append(latest)
if not valid_readings:
return None
# 按温度排序,取中位数
valid_readings.sort(key=lambda r: r.temperature)
median = valid_readings[len(valid_readings) // 2]
# 检测异常传感器
for r in valid_readings:
if abs(r.temperature - median.temperature) > 2.0:
logger.warning(f"[{self.config.zone_id}] Sensor {r.name} "
f"abnormal: {r.temperature}℃ "
f"(median={median.temperature}℃)")
return median
def can_switch(self) -> bool:
"""检查是否满足冷启动保护条件"""
now = time.time()
if self.state.running:
# 运行中,检查最小运行时间
run_time = now - self.state.start_time
return run_time >= self.config.min_run_time
else:
# 停机中,检查最小停机时间
off_time = now - self.state.stop_time
return off_time >= self.config.min_off_time
def evaluate(self) -> str:
"""评估联动逻辑,返回动作"""
median = self.get_median_reading()
if not median:
logger.error(f"[{self.config.zone_id}] No valid sensors, safe mode")
return "SAFE_MODE"
temp = median.temperature
hum = median.humidity
now = time.time()
# 制冷防抖
if temp > self.config.cool_setpoint:
self.state.cool_debounce += 1
self.state.heat_debounce = 0
elif temp < self.config.cool_setpoint - self.config.cool_deadband:
self.state.cool_debounce = 0
# 制热防抖
if temp < self.config.heat_setpoint:
self.state.heat_debounce += 1
self.state.cool_debounce = 0
elif temp > self.config.heat_setpoint + self.config.heat_deadband:
self.state.heat_debounce = 0
# 湿度防抖
if hum > self.config.humidity_high:
self.state.hum_high_debounce += 1
else:
self.state.hum_high_debounce = 0
if hum < self.config.humidity_low:
self.state.hum_low_debounce += 1
else:
self.state.hum_low_debounce = 0
# 决策
action = "MAINTAIN"
if self.state.cool_debounce >= self.config.debounce_count:
if not self.state.running and self.can_switch():
action = "COOL_ON"
self.state.running = True
self.state.mode = "COOL"
self.state.start_time = now
logger.info(f"[{self.config.zone_id}] COOL ON: {temp}℃")
elif self.state.running:
action = "COOL_CONTINUE"
elif self.state.heat_debounce >= self.config.debounce_count:
if not self.state.running and self.can_switch():
action = "HEAT_ON"
self.state.running = True
self.state.mode = "HEAT"
self.state.start_time = now
logger.info(f"[{self.config.zone_id}] HEAT ON: {temp}℃")
elif self.state.running:
action = "HEAT_CONTINUE"
else:
# 温度在舒适区内
if self.state.running and self.can_switch():
action = "AC_OFF"
self.state.running = False
self.state.mode = "OFF"
self.state.stop_time = now
logger.info(f"[{self.config.zone_id}] AC OFF: {temp}℃")
# 湿度控制
if self.state.hum_high_debounce >= self.config.debounce_count:
action += "_DEHUMIDIFY"
if self.state.hum_low_debounce >= self.config.debounce_count:
action += "_HUMIDIFY"
self.state.last_action = action
return action
class ACController:
"""空调控制器(对接 DDC 或继电器)"""
def __init__(self, protocol: str = "bacnet", host: str = "192.168.50.100"):
self.protocol = protocol
self.host = host
self.connected = False
async def connect(self):
"""连接 DDC"""
# 实现 BACnet/IP 或 Modbus TCP 连接
self.connected = True
logger.info(f"Connected to DDC at {self.host}")
async def send_command(self, zone_id: str, action: str):
"""发送控制指令"""
if not self.connected:
logger.error("DDC not connected")
return False
logger.info(f"[{zone_id}] Sending: {action}")
# 解析动作
if action == "COOL_ON":
await self._write_coil(zone_id, "cool", True)
await self._write_coil(zone_id, "fan", True)
elif action == "HEAT_ON":
await self._write_coil(zone_id, "heat", True)
await self._write_coil(zone_id, "fan", True)
elif action == "AC_OFF":
await self._write_coil(zone_id, "cool", False)
await self._write_coil(zone_id, "heat", False)
# 风机延时关闭
await asyncio.sleep(30)
await self._write_coil(zone_id, "fan", False)
elif action == "SAFE_MODE":
# 安全模式:按时间表运行
await self._safe_mode(zone_id)
# 湿度控制
if "_DEHUMIDIFY" in action:
await self._write_coil(zone_id, "dehumidify", True)
if "_HUMIDIFY" in action:
await self._write_coil(zone_id, "humidify", True)
return True
async def _write_coil(self, zone_id: str, point: str, value: bool):
"""写 DDC 线圈"""
# 实现具体的协议写入
pass
async def _safe_mode(self, zone_id: str):
"""安全模式"""
# 根据时间表决定
pass
class BuildingAutomationSystem:
"""楼宇自控系统主类"""
def __init__(self):
self.zones: Dict[str, ZoneController] = {}
self.ac_controller = ACController()
self.running = False
def add_zone(self, config: ZoneConfig):
"""添加控制区域"""
self.zones[config.zone_id] = ZoneController(
config, self.ac_controller
)
logger.info(f"Added zone: {config.name} ({config.zone_id})")
async def start(self):
"""启动系统"""
self.running = True
await self.ac_controller.connect()
# 启动数据采集和联动循环
asyncio.create_task(self._data_collection_loop())
asyncio.create_task(self._control_loop())
asyncio.create_task(self._schedule_loop())
async def _data_collection_loop(self):
"""数据采集循环"""
while self.running:
for zone in self.zones.values():
for ip in zone.config.sensors:
# 从传感器读取数据
reading = await self._read_sensor(ip)
if reading:
zone.add_reading(reading)
await asyncio.sleep(30)
async def _control_loop(self):
"""联动控制循环"""
while self.running:
for zone_id, zone in self.zones.items():
action = zone.evaluate()
if action != "MAINTAIN":
await self.ac_controller.send_command(zone_id, action)
await asyncio.sleep(10)
async def _schedule_loop(self):
"""时间表循环"""
while self.running:
# 检查是否需要切换工作/非工作模式
await asyncio.sleep(60)
async def _read_sensor(self, ip: str) -> Optional[SensorReading]:
"""读取传感器数据"""
# 实现 Modbus TCP 读取
pass
def stop(self):
self.running = False
# 配置示例
def setup_building():
bas = BuildingAutomationSystem()
# 办公区 A
bas.add_zone(ZoneConfig(
zone_id="OFFICE-A",
name="办公区A",
sensors=["192.168.100.50", "192.168.100.51", "192.168.100.52"],
cool_setpoint=26.0,
cool_deadband=1.5,
heat_setpoint=20.0,
heat_deadband=1.5,
humidity_high=65.0,
humidity_low=40.0,
debounce_count=3
))
# 会议室
bas.add_zone(ZoneConfig(
zone_id="MEETING-1",
name="一号会议室",
sensors=["192.168.100.60", "192.168.100.61"],
cool_setpoint=25.0, # 会议室人多,设定更低
cool_deadband=1.0,
heat_setpoint=21.0,
heat_deadband=1.0,
debounce_count=2 # 会议室响应更快
))
# 机房(24h 恒温)
bas.add_zone(ZoneConfig(
zone_id="SERVER-ROOM",
name="服务器机房",
sensors=["192.168.100.70", "192.168.100.71", "192.168.100.72"],
cool_setpoint=24.0, # 机房温度更低
cool_deadband=1.0,
heat_setpoint=22.0,
heat_deadband=0.5,
debounce_count=2
))
return bas
# 主程序
async def main():
bas = setup_building()
await bas.start()
if __name__ == "__main__":
asyncio.run(main())PID 控制公式:
u(t) = Kp × e(t) + Ki × ∫e(t)dt + Kd × de(t)/dt
其中:
e(t) = 设定值(SP) - 过程值(PV)
Kp = 比例增益
Ki = 积分时间
Kd = 微分时间
空调控制 PID 参数推荐(需现场微调):
新风机组(AHU):
Kp = 1.5 ~ 3.0
Ki = 0.05 ~ 0.15 (分钟)
Kd = 0.1 ~ 0.3
采样周期:30s
风机盘管(FCU):
Kp = 2.0 ~ 5.0
Ki = 0.1 ~ 0.3
Kd = 0
采样周期:15s
机房精密空调:
Kp = 1.0 ~ 2.0
Ki = 0.02 ~ 0.08
Kd = 0.2 ~ 0.5
采样周期:10s┌────────────────────────────────────────────────────────────┐
│ DDC PID 控制循环(每 30s 执行一次) │
├────────────────────────────────────────────────────────────┤
│ │
│ PV = 以太网温湿度传感器.温度(Modbus TCP 读取) │
│ SP = BA平台下发的设定值(或本地默认值 26℃) │
│ e = SP - PV │
│ │
│ IF |e| > 2℃ THEN │
│ P_term = Kp × e │
│ I_term = I_term + Ki × e × 30s │
│ D_term = Kd × (e - e_prev) / 30s │
│ u = P_term + I_term + D_term │
│ │
│ IF u > 80% THEN │
│ 启动压缩机 + 100% 新风阀 │
│ ELSE IF u > 30% THEN │
│ 启动压缩机 + 调节水阀开度 = u │
│ ELSE │
│ 关闭压缩机 + 最小新风 │
│ END IF │
│ │
│ e_prev = e │
│ ELSE │
│ // 在死区内,维持当前状态 │
│ I_term = I_term × 0.95 // 积分缓慢衰减 │
│ END IF │
│ │
│ 防积分饱和:I_term 限制在 ±50% │
│ 输出限幅:u 限制在 0~100% │
│ 安全限制:压缩机最小运行 3 分钟,最小停机 5 分钟 │
│ │
└────────────────────────────────────────────────────────────┘阶段 1:传感器校准与验证
□ 所有传感器读数与标准器对比,偏差 < 0.5℃
□ 多传感器一致性检查,同区域偏差 < 0.5℃
□ 传感器时间戳同步,偏差 < 1s
阶段 2:DDC 本地控制测试
□ 手动写 DDC 输出,确认执行器动作
□ PID 参数整定(Ziegler-Nichols 或现场试凑)
□ 防频繁启停测试(模拟温度在阈值附近波动)
阶段 3:BA 联动逻辑测试
□ 模拟传感器越限,验证防抖逻辑(连续 N 次才触发)
□ 验证回差控制(开启和关闭阈值不同)
□ 验证多传感器投票(屏蔽一台,系统仍正常工作)
□ 验证冷启动保护(强制关机后立即启动,应被拒绝)
阶段 4:时间表测试
□ 工作日/周末/节假日模式切换
□ 预冷/预热功能测试
□ 节能模式验证
阶段 5:故障注入测试
□ 传感器断线:系统切换到安全模式
□ 网络中断:DDC 本地维持最后状态
□ DDC 故障:BA 平台告警
□ 空调故障:联动备用设备(如有)
阶段 6:长期运行验证
□ 连续运行 7 天,记录启停次数、温度曲线
□ 验证能耗数据合理性
□ 验证告警通知准确性项目 | 标准 | 测试方法 |
|---|---|---|
温度控制精度 | ±1℃(设定值 ±1℃) | 连续 24h 记录,统计偏差 |
湿度控制精度 | ±5%RH | 连续 24h 记录 |
空调日均启停次数 | ≤8 次/天 | 统计 7 天平均值 |
联动响应时间 | <60s(从越限到输出指令) | 模拟越限,计时 |
防抖有效性 | 瞬时波动不触发动作 | 模拟温度在阈值附近抖动 |
冷启动保护 | 停机 5 分钟内不启动 | 强制关机后立即发启动指令 |
多传感器冗余 | 1 台故障不影响控制 | 拔掉 1 台传感器网线 |
网络中断恢复 | DDC 本地维持,网络恢复后同步 | 断开 BA 网络 30 分钟 |
告警准确率 | >99%(无误报、漏报) | 统计 30 天告警记录 |
问题 | 原因 | 解决方案 |
|---|---|---|
空调频繁启停 | 无回差或回差太小 | 增大回差到 1.5~2℃ |
温度控制超调 | PID 参数 Kp 过大 | 减小 Kp,增大 Kd |
区域温度不均 | 传感器位置不当 | 重新选址,避免阳光直射/风口 |
联动滞后 | 采样周期太长 | 缩短到 10-15s |
湿度控制无效 | 除湿/加湿设备容量不足 | 检查设备选型,增加容量 |
BA 平台卡顿 | 联动逻辑复杂 | 简化逻辑,DDC 本地执行 |
压缩机保护 | 频繁启停 | 增大最小停机时间到 10 分钟 |
夜间过冷 | 时间表未生效 | 检查时间表配置,确认节假日设置 |
楼宇自控环境联动的核心不是"温度到了就开空调",而是"怎么开才不把空调搞坏、怎么关才不浪费电、怎么判断才不被单次误读骗到"。 防抖解决"别被瞬时波动骗了"的问题,回差解决"别在阈值处反复横跳"的问题,多传感器投票解决"别被坏传感器带偏"的问题。 最终交付的是一套从数据采集到控制输出的全链路安全联动机制,让楼宇环境既舒适又节能,同时保护设备寿命。
关键词:楼宇自控、BA系统、环境联动、以太网温湿度传感器、空调启停控制、PID控制、防抖算法、回差控制、冷启动保护、多传感器投票、时间表、Niagara、BACnet、DDC控制器、ModbusTCP
标签:#楼宇自控 #BA系统 #环境联动 #以太网温湿度传感器 #空调启停控制 #PID控制 #防抖算法 #回差控制 #冷启动保护 #多传感器投票 #时间表 #Niagara #BACnet #DDC控制器 #ModbusTCP #智能建筑 #动环监控
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。