从一盏灯说起

装一盏智能灯并不难:买回来、扫码配网、装进 App,五分钟搞定。但当家里的设备从一盏变成三十盏——灯、开关、窗帘、空调、门锁、传感器、摄像头分散在四个品牌、三个 App 里——麻烦就开始了:想让“开门时玄关亮灯”这件事生效,却发现门锁在 A 生态、灯在 B 生态,两者互不认识。

智能家居的第一性问题不是“买什么设备”,而是“这些设备之间怎么说话、谁来发号施令”。这就是架构要回答的事。

架构的价值在处理异常时才真正显现:路由器重启后灯还能不能亮、断网后自动化还跑不跑、某个网关故障会不会让全屋失联。这些问题的答案在设计阶段就已经决定了,后期再想补救,代价通常是重新买一遍设备。

四层架构与各自的边界

把一套完整的智能家居系统拆开,结构上可以分成四层。分层最大的作用是明确职责边界——每一层只做自己该做的事,故障也就被限制在单层之内。

四层与各自的职责
  • 设备层灯、开关、传感器、电机。负责执行动作与感知环境,通常资源极度受限,功耗是首要约束。
  • 连接层WiFi、Thread、Zigbee、蓝牙 Mesh 等无线网络,加上网关或边界路由器,负责把设备接进家庭网络。
  • 中枢层自动化引擎与状态数据库。它保存“家里现在是什么状态”,并决定“什么时候做什么”。
  • 应用层手机 App、语音助手、墙面面板、大屏,负责呈现状态与接受人的指令。

很多人的困惑来自把中枢层和连接层混在了一起。实际上,网关解决的是“怎么连上”,中枢解决的是“连上之后怎么协同”。同一套灯可以先挂在 Zigbee 网关上,再通过中枢统一编排;也可以换成 Thread 接入,中枢的逻辑一行都不用改——只要抽象层次划对了。

三条数据通路:本地、云、混合

同一条“按下开关 → 灯亮”的指令,走的路径可能完全不同。三条通路的取舍,直接决定了系统的响应速度、断网可用性和隐私边界。

  • 纯云通路:App → 厂商云 → 网关 → 设备。优点是远程可用、无需内网穿透;缺点是一次操作要绕行半个互联网,延迟常在数百毫秒到数秒,而且断网即失控。
  • 纯本地通路:面板或中枢直接向设备下发指令,不经过互联网。延迟可以压到几十毫秒,断网照常工作,隐私数据不出家门;缺点是远程访问需要额外方案。
  • 混合通路:本地处理高频、实时、涉及安全的事情(开灯、门锁、漏水关阀),云端处理低频、需要外部数据的事情(天气联动、远程查看、固件升级)。这是目前最务实的组合。

判断一台设备值不值得买,有一个很实用的测试:把家里的宽带给它断掉,看它还能不能完成最基本的动作。门锁、灯、开关这类设备如果断网即瘫痪,无论参数多漂亮都应该谨慎。这也是近来 Matter 与本地优先方案受到欢迎的根本原因。

设备接入与状态建模

接入设备时,真正需要设计的不是配对流程,而是状态怎么表达。一盏可调光的灯至少有三个独立状态:开关、亮度、色温;一个窗帘除了位置,还有运动方向与是否卡住。把这些属性一条条定义清楚,自动化逻辑才可能写得简洁可靠。

工程上普遍采用“实体 + 属性”的建模方式:每个设备是一个实体,实体带若干属性,属性有类型、单位与可选值范围。这样一来,“把客厅灯调到 70%”就变成了对某个实体某个属性的赋值,而不是一串厂商私有的指令码。下面这段代码演示如何用一个极简的模型把设备状态统一起来。

class Entity:
    """统一的设备抽象:实体 + 属性,屏蔽底层协议差异"""

    def __init__(self, entity_id, name):
        self.entity_id = entity_id
        self.name = name
        self.attrs = {}
        self.available = True

    def set_attr(self, key, value):
        self.attrs[key] = value
        print(f"[{self.entity_id}] {key} = {value}")

    def describe(self):
        state = "在线" if self.available else "离线"
        return f"{self.name}({state}): " + ", ".join(
            f"{k}={v}" for k, v in self.attrs.items()
        )

hallway = Entity("light.hallway", "玄关灯")
hallway.set_attr("power", "on")
hallway.set_attr("brightness_pct", 70)
hallway.set_attr("color_temp_k", 4000)
print(hallway.describe())

door = Entity("binary_sensor.hallway_door", "玄关门磁")
door.set_attr("contact", "on")     # on 表示门被打开
print(door.describe())

这个模型简单到近乎朴素,但它带来的好处很关键:所有自动化逻辑都只面对统一属性,底层换成 Zigbee、Thread 还是 WiFi,上层都不需要改动。断线时把 available 置为假,自动化就能立刻知道“这个状态不可信”,从而跳过相关判断——这是很多智能家居系统最容易忽略、也最容易造成误动作的一环。

用 MQTT 搭一个可控的中枢

如果不想被单一生态绑定,MQTT 是搭建自建中枢最轻的一条路。它采用发布订阅模型,设备把状态发布到某个主题,中枢订阅这些主题;反过来,中枢把指令发布到控制主题,设备订阅执行。整套机制省电、可靠,而且在局域网内完全本地运行。

客厅与卧室设备通过线条连接到中央家庭网关的扁平插画
一个有秩序的智能家居:所有设备都指向同一个中枢,而不是各自连向各自的云。中枢的抽象层次决定了后续替换设备时的成本。
import json, time, random
import paho.mqtt.client as mqtt

BROKER, PORT = "192.168.1.30", 1883
TOPIC_STATE = "home/livingroom/light1/state"
TOPIC_SET   = "home/livingroom/light1/set"

client = mqtt.Client(client_id="light1-bridge")
client.connect(BROKER, PORT, keepalive=60)
client.loop_start()

def on_message(_c, _u, msg):
    # 收到中枢下发指令后,真实设备在这里执行动作
    print("收到指令:", msg.topic, msg.payload.decode())

client.subscribe(TOPIC_SET, qos=1)
client.on_message = on_message

try:
    while True:
        # retain=True 让新订阅者一上线就拿到当前状态,避免状态未知
        state = random.choice(["ON", "OFF"])
        client.publish(TOPIC_STATE, state, qos=1, retain=True)
        print("已上报状态:", state)
        time.sleep(5)
finally:
    client.loop_stop()
    client.disconnect()

这段代码里最值得留意的是 retain=True。没有它,中枢重启后要等到设备下一次上报才知道灯是开还是关,期间界面上显示的是未知状态;有了它,broker 会为新订阅者保留最后一个值,状态立即可用。同理,QoS 1 保证“至少送达一次”,对开关类指令是合理的选择,而对传感器读数则可以考虑 QoS 0 以节省功耗。