从一盏灯说起
装一盏智能灯并不难:买回来、扫码配网、装进 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 以节省功耗。
场景与自动化的组织方式
设备接完之后,真正决定体验的是自动化怎么组织。常见的第一版实现是几十条独立的规则堆在一起,运行半年后就没人敢改动,因为改一条不知道会影响哪一条。
- 分清场景、自动化与脚本:场景是“一组状态快照”(回家、离家、观影),自动化是“条件触发动作”(有人且光线暗则开灯),脚本是“可被调用的动作序列”(关全屋灯并延时断电)。
- 只保留一个权威条件源:判断“家里有没有人”应该只由一个实体表达,而不是每条自动化各自用手机定位、摄像头、传感器拼凑。
- 给自动化加防抖与冷却:门磁在关门时可能抖动多次,加一个 2 分钟冷却可以避免灯反复开关。
- 命名要表达意图:命名为“玄关-开门亮灯-夜间”远比“自动化 17”有用,半年后你会感谢自己。
- 关键动作要有兜底:涉及门锁、水阀、燃气的自动化必须设定超时与失败处理,而不是假设网络一定通。
选型与演进建议
技术选型上不必追求一步到位,更现实的做法是按阶段演进:先解决可控,再解决自动化,最后才谈优化体验。
- 第一阶段,把设备接入一个统一中枢,能本地控制、能在断网时仍然工作。
- 第二阶段,补齐传感器与状态,让自动化基于真实环境而不是定时器做判断。
- 第三阶段,处理可靠性:冗余网络、中枢备份、关键设备的失效预案。
- 第四阶段,才考虑跨生态联动与远程访问,此时前面三阶段的沉淀会直接决定上限。
最后提醒一句:无论架构怎么设计,都要给自己留一条“拔掉智能设备仍然能用”的退路。物理开关、机械门锁、手动阀门不是落后,而是整套系统在极端情况下最后的可用性保障。