家里那台 Linux 机器,除了当 NAS、跑 Docker、挂下载之外,还能干点什么更有意思的事?我这几年的答案是:让它把整个家管起来。灯、窗帘、传感器、空调、门锁,甚至浇花的水泵,都可以通过一套跑在 Linux 上的自建自动化系统接管。这篇文章不吹平台,直接讲我在落地这套 Linux Home Automation 时踩过的坑、做过的决策,以及那些文档里不会明说的经验。
这篇文章适合两类人:一是已经在用 Linux,但觉得 Home Assistant 太重、或者不想被某个平台绑死的爱好者;二是对 Linux 命令和 systemd 有一点基础,想从零起一套“能跑、能坏、能恢复”的家庭自动化系统的人。你不一定需要买很多硬件,很多时候旧笔记本、ARM 开发板甚至一台虚拟机都能扛起这个任务。
1. 选型决策:为什么我最后没选现成平台,而是自己拼
家庭自动化这个领域,现成方案一大堆,Home Assistant、OpenHAB、Domoticz 都是成熟选择。我一开始也在 Home Assistant 里折腾了很久,它确实生态丰富,插件满天飞,但最后让我放弃它来做核心中枢的原因很现实:它把太多逻辑封装在界面和插件层里,真出了问题,排查链路太长,而且插件升级经常动到我不关心的部分,连带把稳定跑了几个月的环境搞挂。
自己拼方案,不是瞧不起现成平台,而是想要一个“每条链路都清楚”的系统。我的决策逻辑很简单:中枢只负责三件事——采集设备状态、按规则做判断、执行动作;其余统统交给 Linux 生态里的成熟组件。
1.1 中枢硬件选型:树莓派、旧笔记本还是 ARM 板
硬件选型有时候比选软件还重要,因为它是整个系统的地基。我用过的组合至少有四套,各有取舍:
- 树莓派 4B(2GB/4GB):功耗不到 5W,算力足够跑 MQTT Broker + 规则引擎 + 时序数据库。缺点是 SD 卡容易坏,一定要把系统和数据分离,日志写内存盘或者外置 SSD。
- 旧笔记本(i5 四代 + 8GB 内存):好处是内置电池,断电瞬间有缓冲;坏处是风扇噪音和功耗(30W 以上),长期开机电费不划算,适合前期调试。
- ARM 开发板(如 RK3568、Allwinner 系列):比树莓派便宜,GPIO 也没问题,但驱动成熟度参差,有些板子的官方内核不带设备树,GPIO 复用要自己改 dts,折腾成本不低。
- x86 迷你主机(N100/N5105 这类):我现在的主力。功耗 10-15W,双网口、多个 USB,跑 ARM 交叉编译、虚拟化、Docker 都没压力,扩展性比树莓派好太多。
我的建议是:如果只是想试试水,手里有旧笔记本就先拿它起步;如果要长时间无人值守,选一台无风扇 x86 迷你主机或者树莓派 4B + 外置 SSD,稳定性比硬件性能重要得多。
1.2 系统安装与裁剪:无桌面、固定 IP、SSH 密钥
装系统这一步看起来基础,但很多家庭自动化系统的不稳定,恰恰是桌面环境、自动挂载、网络管理器这些“看起来无关”的部分拖累的。
我的安装原则是:
- 只装 Server 版(不带图形界面),省下约 300MB 内存和大量 CPU 唤醒。
- 固定 IP,不用 DHCP 分配的业务地址,避免路由器重启后设备获取到不同 IP 导致全部客户端失联。
- 关闭 NetworkManager 中不需要的接口托管,保留
/etc/network/interfaces或 systemd-networkd 管理业务网卡。 - 开启 SSH 后立刻配置密钥登录,禁用密码登录,减少暴力扫描麻烦。
至于安装哪个发行版,Debian、Ubuntu Server、Arch 我都跑过。综合下来,Debian 系最省心,软件源稳定、升级周期长、不出幺蛾子。Arch 的滚动更新时不时让某个 Python 包编译失败,不适合没人盯着的地方。
1.3 “自建”的边界:哪些部分不该重复造轮子
自己拼系统不等于每个轮子都自己造。我给自己划了三条线:
- 消息通信:不自己写 socket 长连接,直接用 MQTT(Eclipse Mosquitto),设备端和规则引擎都通过它通信。
- 协议解析:不自己解析 Modbus RTU、Zigbee、BLE,而是用现成网关(如串口服务器、Zigbee2MQTT),把各种乱七八糟的协议统一成 MQTT 消息。
- 前端展示:不自己写图表库,直接上 Grafana + InfluxDB,几百行配置就能出一个还能看的面板。
这三条线省下的时间,足够你把核心的自动化规则打磨得比现成平台更贴合自己的习惯。
2. 设备接入层:把物理世界的“乱七八糟”统一成一条 MQTT 消息
设备接入是整个系统最琐碎、也最考验耐心的部分。家里常见的设备类型无非这几种:开关和继电器、温湿度传感器、人体红外/存在传感器、摄像头、空调/电视的红外遥控。它们各自的接入方式完全不同,如果不做一个统一的抽象层,规则引擎写起来会非常痛苦。
我的做法是:所有设备状态都变成 MQTT 主题上的 JSON 消息,格式统一为{"state": "on", "value": 23.5, "last_seen": 1690000000},规则引擎只关心主题和消息内容,不关心底层设备怎么来的。
2.1 四种常见接入方式的取舍
GPIO 直连:树莓派或 ARM 板的 GPIO 直接接继电器、按钮、LED。优点是实时性好、无网络依赖;缺点是接线距离有限,而且一旦板子挂了,这个点位的设备就全失联了。我一般只把“本地点位”——比如门口按钮、玄关灯——接到 GPIO。
串口/Modbus:很多工业级传感器(温湿度、光照、PM2.5)都是 RS485 接口,走 Modbus 协议。优点是抗干扰、传输距离远,适合埋在吊顶里;缺点是接线要 A/B 双线,还得分地址。用一个 USB-RS485 转接器插到 Linux 主机上,跑一个简单的 Modbus TCP/RTU 网关脚本,就能把这些传感器变成 MQTT 主题。
Wi-Fi 设备:现在大量智能插座、灯、空调伴侣都支持局域网 API 或 HTTP 接口。尽量不开云,直接轮询局域网 IP 的 80/8080 端口拿状态。这类接入最不稳定,因为厂商固件会更新协议,所以我在轮询层做了“最大失败次数熔断”,连续失败 10 次就自动停掉该设备的轮询并告警,避免一个设备拖垮整个采集进程。
红外/射频遥控:用小米或博联的红外 Hub,通过局域网接口发红外码控制空调、电视、风扇。这个方案的痛点是码库匹配不全,我的办法是“学习模式”——先用遥控器对着 Hub 录一遍每个按键的原始码,存成 JSON 文件,之后所有场景都复用这套码。
2.2 写一个轻量 Modbus 网关脚本的要点
如果你也有一堆 RS485 传感器,可以借鉴这个最小化思路。我没有用 node-red 那种可视化工装,因为现场调试时命令行更直白。核心脚本逻辑如下:
import paho.mqtt.client as mqtt from pymodbus.client import ModbusSerialClient client = ModbusSerialClient( port='/dev/ttyUSB0', baudrate=9600, timeout=1, parity='N', stopbits=1, bytesize=8 ) def read_sensor(slave, addr, count, topic): rr = client.read_holding_registers(addr, count, slave=slave) if rr.isError(): return None value = rr.registers[0] / 10.0 mqtt_client.publish(topic, f'{{"value": {value}, "unit": "celsius"}}') # 每秒轮询一次,注意异常隔离 while True: try: read_sensor(slave=1, addr=0, count=1, topic='sensor/livingroom/temp') read_sensor(slave=1, addr=1, count=1, topic='sensor/livingroom/humidity') except Exception as e: logger.exception("Modbus poll failed: %s", e) time.sleep(1)这里的坑:Modbus 从站设备初始化时间比你想的长,上电后立刻读会把错误码当状态值。我在网关启动后加了 3 秒等待,并且对错误码做了过滤——寄存器返回0xFFFF时直接丢弃,不往 MQTT 里发。
另外一个容易忽略的点:RS485 是半双工,轮询频率太高会导致总线冲突。经验值是一般传感器 1 秒间隔没问题,但如果一条总线上挂了超过 8 个设备,间隔最好放到 2 秒以上,否则会在主从切换时丢数据。
2.3 GPIO 按钮和继电器的去抖与互锁
GPIO 接入看似简单,实际全是细节。按钮按下瞬间的机械抖动,会让系统一次按下触发三五次事件。我的处理方式是:
- 硬件去抖:并联一个 104 电容(100nF),这能解决 80% 的抖动问题。
- 软件去抖:在代码里做 50ms 电平稳定判断,只有持续 50ms 为同一电平才认为有效。
import gpiod import time chip = gpiod.Chip('gpiochip0') line = chip.get_line(17) line.request(consumer='button', type=gpiod.LINE_REQ_DIR_IN) stable_state = line.get_value() last_change_time = time.time() while True: current = line.get_value() if current != stable_state and time.time() - last_change_time > 0.05: stable_state = current if stable_state == 1: mqtt_client.publish('event/button/entrance', '{"event": "single_click"}') elif current != stable_state: last_change_time = time.time() time.sleep(0.01)如果你要控制多个继电器,一定要加互锁逻辑——比如窗帘电机“开”和“关”两个继电器绝对不能同时吸合,否则电机短路烧驱动板。我在规则引擎里做了状态机约束:任何时刻,同一个设备的动作 token 只有“开”或“关”其中一个,再额外加一个 5 秒的强制互锁时间窗。
3. 自动化规则引擎:别一上来就写死一堆 if else
自动化规则写起来容易,维护起来难。我第一版就是几百个 if else 堆在一起,结果某天改了一个场景,把另一个场景的触发条件带崩了,花了一整个周末才查出来。之后我重构了三版,才定下现在这套结构:规则 = 触发器 + 条件 + 动作 + 恢复动作,用 JSON 描述,代码解释执行。
3.1 为什么选 JSON 规则而不是直接写代码
直接写代码的优势是灵活,坏处是——家里的非技术人员(比如家人)没法改,而且每次改规则都要重启服务。JSON 规则的好处是:
- 规则存在独立文件里,改了热加载,不需要重启。
- 可以把规则的执行日志打印出来,哪条触发了、哪条没触发一目了然。
- 后续想接可视化界面,JSON 直接可以映射到表单。
规则文件长这样:
{ "id": "livingroom_fan_on_hot", "name": "客厅温度高于28度自动开风扇", "triggers": [ {"type": "state", "topic": "sensor/livingroom/temp", "op": "gt", "value": 28.0}, {"type": "state", "topic": "sensor/livingroom/humidity", "op": "gt", "value": 70.0} ], "condition": { "op": "and", "items": [ {"type": "state", "topic": "device/fan/state", "op": "eq", "value": "off"}, {"type": "time", "range": ["08:00", "23:00"]} ] }, "actions": [ {"type": "publish", "topic": "command/fan", "payload": {"state": "on"}} ], "recover": [ {"type": "publish", "topic": "command/fan", "payload": {"state": "off"}} ] }triggers里的条件都是“变化时评估”——温度超过 28 度且湿度超过 70% 时触发;condition是触发时额外检查的约束——风扇当前是关的、时间在 8 点到 23 点之间。recover是温度回落到阈值以下(比如低于 27 度)时自动执行的动作,这样系统就不会反复开关风扇。
3.2 事件驱动 vs 定时轮询:两条腿走路
纯事件驱动的问题是:某些设备死了、断了,它不会发事件,系统就傻等了。纯定时轮询的问题是:响应慢,而且频繁轮询让设备受累。
我的方案是混合:
- 状态类事件(按钮按下、门窗传感器开合、人体传感器触发)走 MQTT 即时事件,毫秒级响应。
- 数值类状态(温度、湿度、电量)每 30 秒轮询一次,同时在规则引擎里维护一个“最近一次上报时间”字段,超过 5 分钟没上报的设备自动标记为离线,触发告警。
这个混合逻辑解决了一个实际问题:如果温度传感器卡在 27.9 度,它一直不触发 28 度的阈值事件,你要么永远不知道,要么等它自己报错。轮询让这个系统有“心跳感知”,事件驱动让响应足够快。
3.3 去抖、延时动作与失败补偿
这三个细节直接决定自动化系统在日常使用中的体验。
去抖:人体传感器刚检测到人,紧接着又丢失信号,这是常事。我的做法是:只有“持续 15 秒没检测到人”才执行关灯动作,而不是一没信号就关。15 秒这个值看起来短,但如果 PIR 传感器本身就是 30 秒一次的节拍,这个值就要设长一点,否则灯会闪。
延时动作:比如“开门后玄关灯亮 2 分钟再关”,这个 2 分钟应该由规则引擎调度器管理,而不是某个脚本里 sleep(120)。因为 sleep 会被进程重启打断,而调度器可以持久化任务,重启后还能补执行。
失败补偿:动作执行失败的场景远比想象的多——Wi-Fi 插座掉线、继电器驱动异常、红外码发出去没反应。我在规则引擎里加入了重试机制:动作失败后按 5 秒、30 秒、5 分钟的间隔重试 3 次,重试之前先发一个command/retry事件,让系统知道这不是重复触发。
4. 数据采集与沉淀:不只要实时,还要能回溯
自动化刚部署时,最关心的是“当下状态对不对”。但跑了一两个月后,你会发现真正有价值的是历史数据:这周比上周平均温度高了多少度?水泵每周浇水量有没有异常?某个人体传感器是不是越来越迟钝了?没有数据沉淀,这些问题永远只能靠猜。
4.1 时序数据库选型:我只试过这两个
- InfluxDB 1.8:我用了很久,优点是生态极好、Grafana 连接器成熟、写查询简单。但 1.8 之后的版本在授权模型上变化较大,我停在 1.8 LTS 版本,足够稳定。
- TimescaleDB(PostgreSQL 扩展):如果你是 PG 老手,推荐它。数据模型更正规,SQL 能直接用,而且可以和业务表做 join。缺点是安装略复杂,内存占用比 InfluxDB 高。
如果你只有一台树莓派,不推荐单独再跑一个数据库容器。SQLite + 定时导出 CSV其实已经够用,几十个传感器、每分钟一条数据,SQLite 完全扛得住,而且备份直接拷文件就行。
4.2 采样频率与数据生命周期
数据量别看单条很小,日积月累会吓人。一个传感器每分钟 1 条,一天就是 1440 条,50 个传感器一天 72000 条,跑一年就是 2600 万条。不控制保留策略,你的存储很快就被塞满。
我的策略是:
- 原始数据保留 30 天,精度 1 分钟。
- 30 天前的数据降采样到 5 分钟均值,再保留 6 个月。
- 超过 6 个月的只保留每日均值,归档到 SQLite 文件,直接压缩存到 NAS。
InfluxDB 的降采样我直接在 1.8 里用 Continuous Query 做,每天凌晨跑一次,生成汇总表之后再删原始数据。这条链路跑了一年半,存储占用一直控制在 2GB 以内。
4.3 面板的核心指标:我建议先看这几张图
Grafana 面板不必一开始就做得很花哨,先关注几个真正有用的:
| 面板 | 指标 | 用途 |
|---|---|---|
| 温湿度趋势 | 各房间逐小时均值 | 判断空调/暖气是否在正确时段运行 |
| 设备在线率 | 每设备 last_seen 的计数 | 一眼看出哪个设备离线了 |
| 动作耗时 | 从 MQTT 命令发出到设备状态翻转为 on 的间隔 | 排查哪条链路延迟高 |
| 自动化触发统计 | 每条规则每天的触发次数 | 找到写得太啰嗦或可能死循环的规则 |
面板是给别人看的,更重要的是给自己做监控。我特意加了一个“自动化异常”面板,把规则引擎的异常、重试日志、设备离线记录全部汇总到一块,每天早上扫一眼就能知道昨晚系统有没有抽风。
5. 告警通知:让系统在你不在家时替你盯盘
告警是家庭自动化和花架子系统的分水岭。一个系统如果什么都能自动做,但出问题时你一无所知,那它就是个花架子。我的告警体系分三层:设备层、规则层、系统层。
5.1 通知通道:邮件、Webhook、Push 哪个靠谱
- 邮件:最稳,但最容易忽略。我把它作为最后的兜底通道,系统级故障(比如磁盘满了、服务挂了)发邮件。
- Webhook(企业微信/钉钉/飞书机器人):响应快、手机端有推送,适合日常设备告警。缺点是如果机器人 webhook 地址被滥用会一直打扰你,所以我把告警内容做了分级,只有 WARN 和 CRITICAL 才发。
- Push(Pushover 或自建 Gotify):安卓 iOS 通用,适合“门窗忘关”“燃气传感器报警”这类必须立刻响应的场景。
我的建议是:不要把高优先级告警只挂在某一个通道上。我家的燃气报警,同时发 Pushover + Webhook + 邮件,虽然有点冗余,但燃气是底线,宁可多收几条重复消息,也不漏掉一条关键告警。
5.2 告警降噪:用“连续 N 次”代替“瞬时一次”
家庭环境误报实在太多了。人体传感器对着落地窗,下午太阳照进来它能误报十几次;门磁传感器电池快没电时,隔几分钟就抖一下。
我的降噪规则是:所有告警不做瞬时触发,而是连续 N 次(默认 3 次)状态异常才发告警。并且加了一个静默窗口:
- 夜间 23:00 - 08:00,除了安防相关告警(门磁、窗磁、燃气、烟雾),其他一律静默到早上汇总。
- 同一个告警主题,两次通知之间至少隔 30 分钟,防止一条消息刷屏。
- 可恢复的告警在恢复后发一条“已恢复”通知,避免看到一堆 RED 告警但不知道现在状态是否正常。
这套规则跑下来,一周告警数量从最初的三四十条降到了三五条,而真正重要的告警一次都没漏。
6. 外网访问与安全:你在外面,不等于整个家都裸奔
出门在外想看一眼家里的温度、开个通风格局,是家庭自动化最常用的需求。但这个需求也是最容易引入安全问题的——我见过太多直接把 1883 端口(MQTT)暴露到公网、登录密码还是admin/admin的例子,这不是智能家居,这是给全网开了个后门。
6.1 内网穿透方案的取舍
我的家用方案经历了三轮演变:
- 端口映射 + 密码:最不推荐,不做任何防护,扫描器很容易盯上。
- frp 内网穿透:用一台有公网 IP 的轻量服务器做中转,家里 Linux 主机主动连出去,公网侧不暴露任何家庭端口。这个方案很成熟,配合上 TLS 之后,穿透链路的加密是够用的。
- 组网工具:我最后换成组网工具方案——所有家庭成员设备统一装客户端,组成一个虚拟局域网。好处是:不暴露公网端口,不需要维护 frp 服务端,手机端随时随地像在家里局域网一样访问服务。家里 Linux 主机作为组网中心节点,路由转发都交给这套组网。
如果你不想折腾组网工具,frp 也已经够用了。关键一点:frp 服务端放在云上,frpc 装在 Linux 主机上,客户端通过 frp 的加密链路访问家庭内部服务,这条链路只对特定端口开放。
6.2 端口、TLS 与防火墙
不管用哪种穿透方式,安全配置有几条铁律:
- 永远不要直接暴露 MQTT 的 1883 端口,要暴露就暴露带 TLS 的 8883,并且配置客户端证书。
- 防火墙只放行必要的端口,其余全部 drop。我这里有专门的防火墙规则文件,允许的端口只有 SSH(改成非默认端口)、HTTPS、Grafana、MQTT Over TLS。
- 所有对内服务的登录页面都套一层反向代理(Nginx 或 Caddy),由反向代理统一终止 TLS,同时做访问控制。Caddy 可以自动申请证书,能省很多事。
6.3 客户端认证与日常审计
安全不是配完就完,日常审计更重要。我设置了三项例行检查:
- 每天凌晨扫描一次
/var/log/auth.log,统计 SSH 失败次数,异常频繁会触发告警。 - MQTT 开启用户名密码认证,并且每个设备单独一个账号,方便吊销某个设备的权限,而不是一个账号通吃。
- 每季度导出一次所有访问日志,重点看是否有陌生 IP 连上过。
有一次,审计日志发现某个智能插座固件更新后,开始往一个陌生 IP 定时发心跳包。我直接把那台设备的防火墙规则改成只允许访问 MQTT Broker 和服务端,把这个行为断掉了。没有日志审计,这种问题根本不会被发现。
7. 稳定性工程:让系统真正做到无人值守
跑得再好的规则引擎,如果 Linux 主机三天两头重启或者某个 Python 进程悄悄退出,整个系统就形同虚设。稳定性工程在家庭自动化里比在服务器上要求更高,因为没人会 7x24 小时盯着它。
7.1 systemd 服务管理:别再用 nohup 和 screen
我第一版用的nohup python3 main.py &,跑了两周后某个凌晨进程崩了,家里自动化全瘫,我还不知道。后来所有关键服务全部迁移到 systemd,配置里几个关键参数值得注意:
Restart=always:无论收到什么信号退出,都自动重启。RestartSec=5:重启前等 5 秒,避免快速循环重启把 CPU 打满。StartLimitIntervalSec和StartLimitBurst:限制 10 分钟内最多重启 5 次,如果超过说明有 bug,直接停止并告警,防止无限重启。
一个典型服务单元文件:
[Unit] Description=Home Automation Rule Engine After=network-online.target mosquitto.service Wants=network-online.target [Service] ExecStart=/usr/bin/python3 /opt/homelab/rule_engine.py WorkingDirectory=/opt/homelab Restart=always RestartSec=5 User=homelab Group=homelab # 防无限重启 StartLimitIntervalSec=600 StartLimitBurst=5 [Install] WantedBy=multi-user.target7.2 硬件看门狗:进程活着不等于系统活着
systemd 能管进程,但管不了内核死锁。我加了硬件看门狗:
# 启用内核 softdog 或者使用 iTCO_wdt # Debian/Ubuntu apt install watchdog # 配置 /etc/watchdog.conf,把 /dev/watchdog 指向系统看门狗设备 # 检测到系统无响应时自动重启整机当然,内核级看门狗只在系统完全卡死时兜底。更常用的是业务级看门狗——我写了一个监控脚本,每 5 分钟 ping 一次 MQTT Broker 和规则引擎的 health 端点,如果连续 3 次无响应,就通过 GPIO 控制继电器给异常主机断电重启。这套“二次看门狗”虽然有点暴力,但对于真正卡死的系统,比任何软件重启都可靠。
7.3 日志轮转与崩溃取证
日志一多,就面临“系统崩溃后想查日志但日志文件也被写坏了”的窘境。我统一用logrotate做日志轮转:
- 系统日志:每日轮转一次,保留 7 天。
- 应用日志:按大小轮转,超过 10MB 就转走,保留 5 份。
- MQTT 日志:本身就不大,保留 14 天。
每个应用服务启动时加上--log-file参数,把 Python 的print和logging都输出到指定文件,同时用systemd-cat把 stderr 传给 journald。崩溃后先用journalctl -u homelab-rule-engine -n 200看最后 200 行,通常能直接定位到异常位置。
7.4 网络依赖处理:网络掉了,规则不能一起掉
家庭自动化系统最尴尬的场景是:路由器重启、宽带断开,然后整个自动化规则一起挂掉。很多规则触发是基于 MQTT 的,但如果 MQTT Broker 所在的主机和网络一起断了,规则引擎连不上 Broker 就会反复抛异常,最终把自己搞崩。
我在这块吃了不少亏,现在总结了三条:
- 规则引擎连接 MQTT 的代码里必须有重连带退避逻辑(指数退避),不能每 1 秒重试一次,否则会把 Broker 日志刷爆。
- 规则引擎本地的判定逻辑与 MQTT 发布解耦:即使 MQTT 暂时连不上,规则引擎照常采集和处理,只是把动作暂存在内存队列里,等网络恢复再补发。
- 网关设备(比如 Zigbee2MQTT)和 MQTT Broker 必须跑在同一个 Linux 主机上,这样即便外部网络中断,本地自动化(灯、插座、人体传感器)依然可以正常工作,只有依赖外部网络的场景(比如自动开窗通风的天气接口)会暂停。
8. 离线优先:断网不断自动化
提到离线,很多人第一反应是“我家网络很稳”。但踩过几次坑之后我明白了:家里的自动化系统必须默认“随时可能断网”,并且把“断网时还能不能保持核心功能”作为设计目标。
8.1 本地规则与云端依赖分离
我的系统设计原则是:与云端的通信是可选的,本地的自动化是必须的。具体拆分如下:
- 本地规则:温湿度阈值、人体传感器联动、定时开关、安防告警,全部在本地 Linux 主机上完成,不依赖云端。
- 依赖云端的规则:天气预报联动(比如下雨自动关窗)、在线语音控制。这些规则在断网时会优雅降级——比如统一返回“网络不可用”,而不是反复尝试然后报错。
这个设计在一年里经历了四次宽带故障,每次断网期间,家里的灯、插座、风扇、安防告警全部正常,只有“用手机从外网访问”和“看天气联动”这两个功能暂停。
8.2 仲裁、缓存与最终一致性
当多个入口都能控制同一个设备时(比如物理开关、手机 App、自动化规则),“最后一毫秒谁说了算”就成了问题。我的原则是:让设备侧做最终仲裁,状态以设备上报为准,命令只是说服设备改变状态。
举个例子:灯的物理开关被按下后,设备上报state=off,但规则引擎里的灯泡状态可能还停留在on。如果我不做状态同步,下一次温度触发开灯时,规则引擎可能认为灯已经是开的,就不发命令了,但灯其实是灭的。
解决方法是:设备每次上报状态都更新本地状态缓存,所有规则判断都基于这个缓存,而不是基于“上次命令的内容”。这样即使存在冲突,系统也会在下一个事件周期内自动收敛,不会卡在某个错误状态上。
9. 备份、升级与回滚:给系统上最后一道保险
家庭自动化系统跑久了,配置文件、规则、设备映射表、数据库都会积累到让人不想重来一遍的程度。没有备份的系统,就像没有安全带的自动化——一旦出问题,所有积累归零,这种损失比“系统不好用”要痛得多。
9.1 配置文件的版本管理
从第一天起,我就把/opt/homelab下的所有配置文件和规则 JSON 纳入 Git 仓库管理,而且每天自动 commit 一次。具体做法:
# /opt/homelab 下的配置目录 cd /opt/homelab git init git add configs/ rules/ device_mappings/ git commit -m "daily backup $(date)" # 配合 cron 每天凌晨 3 点自动提交并推送远端仓库这个习惯让我可以在 30 秒内把整个系统恢复到前一天的状态。有一次我改规则文件时少写了一个逗号,导致 JSON 解析失败,规则引擎一直起不来。我直接回滚到上一个 commit,系统 20 秒内就恢复了。
9.2 数据库备份与容器镜像管理
- 数据库备份:InfluxDB 的备份用
influxd backup命令,每天凌晨执行,保留 7 份。SQLite 的备份是直接cp文件(安全做法是先.backup命令,防止写锁导致文件损坏)。 - 容器镜像管理:如果用了 Docker,建议固定所有容器镜像的 tag 到具体版本,而不是用
latest。否则某天重建容器时拉到一个不兼容的新版本,系统直接起不来。我的习惯是每周检查一次镜像更新,确认无兼容性问题后再手动升级。
9.3 升级后的回滚策略
升级系统组件(如 Mosquitto、Grafana、Python 包)之前,我会做三件事:
- 记录当前版本号:
dpkg -l | grep mosquitto或者pip freeze > requirements.txt。 - 备份整个配置目录到
/tmp/backup_before_upgrade_日期。 - 准备好在升级后 5 分钟内如果发现异常,用备份直接回滚。
有一次我把 Mosquitto 从 1.6 升到 2.0,结果新版默认不允许 root 用户连接,我的设备全连不上了。因为提前做了备份和版本记录,我花了 10 分钟就恢复到旧版本,而不是慌慌张张去查新文档。
最后再分享一点个人体会
这套 Linux Home Automation 系统从第一版到现在,已经跑了快两年。我最大的体会不是哪个工具更好用,而是——家庭自动化真正难的不是技术,而是“你愿不愿意把回家的体验,变成一套可以被观察、被调整、被维修的工程系统”。
如果你正准备起步,我建议先从最小的闭环开始:一个温湿度传感器 + 一个继电器 + 一个风扇,用文里的逻辑把这套链路完整跑通。不用急着追求接入多少设备,先让“传感器上报 -> 规则引擎判断 -> 执行器动作 -> 状态回读”这个循环稳定运行一个月,再逐步扩展场景。
毕竟,系统再炫酷,也没有“每天稳定运行、让你忘记它存在”更让人安心。