首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >楼宇自控环境联动:以太网温湿度传感器数据驱动空调启停控制实战

楼宇自控环境联动:以太网温湿度传感器数据驱动空调启停控制实战

原创
作者头像
盛世宏博小可
发布于 2026-09-28 10:53:49
发布于 2026-09-28 10:53:49
1210
举报

楼宇自控环境联动:以太网温湿度传感器数据驱动空调启停控制实战

楼宇自控 #BA系统 #环境联动 #以太网温湿度传感器 #空调启停控制 #PID控制 #防抖算法 #冷启动保护 #ModbusTCP #Niagara #Honeywell #Siemens #动环联动

楼宇自控系统的核心价值不在于"看得见",而在于"控得住"。以太网温湿度传感器采集到数据后,真正的考验是:如何把数据变成控制指令,并且确保控制过程安全、稳定、不抖动。空调启停联动是最常见的场景——温度高了开空调,低了关空调,听起来简单,但实际工程中翻车的案例比比皆是。

先说一个真实案例:

某甲级写字楼 BA 项目,Niagara 平台对接 120 台以太网温湿度传感器,控制 8 台新风机组 + 24 台风机盘管。 初始联动逻辑:温度 > 26℃ → 开空调;温度 < 24℃ → 关空调。 上线后问题:

  • 空调频繁启停:夏季午后阳光直射区域温度在 25.5~26.5℃ 之间波动,空调 3 分钟内开关 4 次
  • 压缩机保护报警:某台新风机组一天内启停 47 次,压缩机过热保护跳闸
  • 区域温度不均:回风口传感器读 23℃,但靠窗工位实际 28℃(传感器安装位置不合理)
  • 联动滞后:传感器 30s 上报一次,空调从接收到指令到出风需要 2-3 分钟,等温度降下来已经超调 优化后方案:
  • 防抖:连续 3 次越限才触发(约 90s 确认)
  • 回差控制:开 26℃ / 关 24.5℃(1.5℃ 回差带)
  • 冷启动保护:关机后至少 5 分钟才能重新启动
  • 多传感器投票:每个区域 3-5 台传感器取中位数,防止单点误判
  • 前馈控制:结合室外温度预测,提前 15 分钟预冷 结果:空调日均启停次数从 47 次降到 6 次,压缩机再无保护报警,区域温差控制在 ±1℃ 内。

一、联动架构设计

1. 系统拓扑

代码语言:javascript
复制
┌─────────────────────────────────────────────────────────────────────┐
│                         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,与办公网隔离                               │
└─────────────────────────────────────────────────────────────────────┘

2. 三种联动模式对比

模式

控制位置

优点

缺点

适用场景

BA 中心联动

BA 平台计算,下发指令

逻辑集中管理、策略灵活

依赖网络通信、有延迟

大型楼宇、多系统协同

DDC 本地联动

DDC 直接读传感器,本地控制

响应快、不依赖网络

策略固化、难以全局优化

独立机房、小型系统

混合联动(推荐)

DDC 本地 PID + BA 全局优化

兼顾响应速度和全局协调

架构复杂

中大型楼宇

推荐架构:混合联动

代码语言:javascript
复制
正常模式:DDC 本地 PID 控制(传感器直连 DDC,毫秒级响应)
优化模式:BA 平台根据全局策略调整 DDC 设定值(Setpoint Reset)
应急模式:BA 平台直接强制 DDC 输出(Override)

二、联动逻辑设计

1. 基础防抖与回差控制

代码语言:javascript
复制
核心问题:温度在阈值附近波动时,空调会频繁启停

解决方案:
  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 → 开加湿器

2. 多传感器投票机制

代码语言:javascript
复制
问题:单台传感器故障或安装位置不当,会导致误判

方案:每个控制区域部署 3-5 台传感器,取中位数

  区域 A(3 台传感器):
    传感器 1: 25.2℃
    传感器 2: 27.8℃(故障/阳光直射)
    传感器 3: 25.5℃
    中位数 = 25.5℃(排除异常值)

  决策逻辑:
    - 使用中位数作为控制依据
    - 如果某台传感器与其他传感器偏差 > 2℃,标记为异常并告警
    - 异常传感器不参与投票,其余传感器继续工作
    - 超过半数传感器离线,切换到安全模式(按时间表运行)

3. 冷启动保护

代码语言:javascript
复制
压缩机启动电流是额定电流的 5-7 倍,频繁启停会烧毁压缩机

保护规则:
  1. 最小停机时间:空调关机后,至少等待 5 分钟才能重新启动
  2. 最小运行时间:空调启动后,至少运行 3 分钟才能关机
  3. 启动间隔:同一台空调两次启动之间至少间隔 10 分钟

实现方式:
  DDC 控制器内置防频繁启停定时器
  BA 平台下发指令时检查时间戳,未到时间则缓存指令

4. 时间表与节假日策略

代码语言:javascript
复制
工作日(周一至周五):
  07:00-09:00  预冷/预热(上班前将温度调到舒适区)
  09:00-18:00  正常控制(26℃制冷 / 20℃制热)
  18:00-19:00  延时关闭(逐步减少空调运行台数)
  19:00-07:00  节能模式(28℃制冷 / 18℃制热,或全关)

周末/节假日:
  全天节能模式 或 按预定时间表

特殊场景:
  会议室:有人占用时正常控制,无人时节能模式
  机房:24h 维持 22±2℃(不随时间表变化)

三、BA 平台联动实现

1. Niagara 联动逻辑(Baja/JavaScript)

代码语言:javascript
复制
// 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);

2. Python 联动引擎(独立前置机方案)

代码语言:javascript
复制
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())

四、DDC 本地 PID 控制

1. PID 参数整定

代码语言:javascript
复制
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

2. DDC 梯形图逻辑(伪代码)

代码语言:javascript
复制
┌────────────────────────────────────────────────────────────┐
│  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. 调试步骤

代码语言:javascript
复制
阶段 1:传感器校准与验证
  □ 所有传感器读数与标准器对比,偏差 < 0.5℃
  □ 多传感器一致性检查,同区域偏差 < 0.5℃
  □ 传感器时间戳同步,偏差 < 1s

阶段 2:DDC 本地控制测试
  □ 手动写 DDC 输出,确认执行器动作
  □ PID 参数整定(Ziegler-Nichols 或现场试凑)
  □ 防频繁启停测试(模拟温度在阈值附近波动)

阶段 3:BA 联动逻辑测试
  □ 模拟传感器越限,验证防抖逻辑(连续 N 次才触发)
  □ 验证回差控制(开启和关闭阈值不同)
  □ 验证多传感器投票(屏蔽一台,系统仍正常工作)
  □ 验证冷启动保护(强制关机后立即启动,应被拒绝)

阶段 4:时间表测试
  □ 工作日/周末/节假日模式切换
  □ 预冷/预热功能测试
  □ 节能模式验证

阶段 5:故障注入测试
  □ 传感器断线:系统切换到安全模式
  □ 网络中断:DDC 本地维持最后状态
  □ DDC 故障:BA 平台告警
  □ 空调故障:联动备用设备(如有)

阶段 6:长期运行验证
  □ 连续运行 7 天,记录启停次数、温度曲线
  □ 验证能耗数据合理性
  □ 验证告警通知准确性

2. 验收标准

项目

标准

测试方法

温度控制精度

±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 删除。

目录
  • 楼宇自控环境联动:以太网温湿度传感器数据驱动空调启停控制实战
  • 楼宇自控 #BA系统 #环境联动 #以太网温湿度传感器 #空调启停控制 #PID控制 #防抖算法 #冷启动保护 #ModbusTCP #Niagara #Honeywell #Siemens #动环联动
    • 一、联动架构设计
      • 1. 系统拓扑
      • 2. 三种联动模式对比
    • 二、联动逻辑设计
      • 1. 基础防抖与回差控制
      • 2. 多传感器投票机制
      • 3. 冷启动保护
      • 4. 时间表与节假日策略
    • 三、BA 平台联动实现
      • 1. Niagara 联动逻辑(Baja/JavaScript)
      • 2. Python 联动引擎(独立前置机方案)
    • 四、DDC 本地 PID 控制
      • 1. PID 参数整定
      • 2. DDC 梯形图逻辑(伪代码)
    • 五、联动调试与验收
      • 1. 调试步骤
      • 2. 验收标准
    • 六、常见问题与解决方案
    • 七、一句话总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档