为什么要自己搭中枢

厂商云的好处是开箱即用,坏处是把控制权、可用性和数据流向都交了出去:服务器一次维护就是全屋失联,一次账号策略调整就让自动化失效,厂商停止运营则设备直接变成砖。自建中枢要解决的就是这三件事——控制权、可用性和数据归属。

衡量一个智能家居中枢是否合格,最简单的标准是:把外网断掉,它还能不能完成你日常百分之八十的操作。

Home Assistant 是开源方案里生态最完整的一个:支持数千种设备与云服务集成、内置自动化引擎、支持本地控制、并提供标准化的 REST API 与其他程序互通。它不追求开箱即用的华丽,而是把可编排性和可控性放在第一位,这正是自建中枢的价值所在。

安装形态与目录结构

Home Assistant 常见的几种安装形态,适用场景差异明显,选错会在后续维护上付出很多时间。

  • 操作系统镜像:整机安装,包含受管操作系统与所有附加组件,升级最省心,适合长期运行的主力机。
  • 容器部署:跑在既有服务器或 NAS 的容器里,资源利用率高,适合已经有家庭服务器、并且愿意自行管理升级的人。
  • 核心程序直装:在通用 Linux 上直接运行 Python 程序,可控性最强,但要自己处理依赖与开机自启,不适合追求省事的人。

无论哪种形态,配置目录的结构都是一致的,理解它比记住菜单更重要。

配置目录里的关键文件
  • configuration.yaml主配置入口,声明集成、自定义传感器、通知渠道等基础设置。
  • automations.yaml自动化规则的默认存放位置,图形界面创建的自动化也落在这里,可以手工编辑。
  • secrets.yaml保存密码与令牌,主配置里用 !secret 引用,避免密钥散落在多处。
  • .storage/存放界面与集成运行期产生的数据,手工编辑容易损坏,建议只在排障时查看。

接入设备:集成、实体与命名

Home Assistant 里所有可被控制或读取的东西都是实体,实体由集成创建。接入设备时,尽量优先使用本地协议集成而不是云集成:本地集成不依赖外网、响应更快,也不会因为厂商账号策略变化而失效。只有在设备确实只提供云接口时,才退而求其次。

家庭中枢面板与四周设备图标用线条连接联动的扁平插画
中枢面板是整套系统的指挥中心:它从设备读取状态、执行自动化、对外提供接口,所有联动都汇聚在这里而不是散落在各个 App 中。

命名是这一阶段最容易被忽视、后果最持久的事。实体标识一旦被自动化引用,后期改名就要同步修改所有引用,容易出现“改了名字但自动化还在操作旧实体”的隐性故障。因此建议在接入阶段就定好命名规范:

  • 实体标识用“类型_位置_用途”的顺序,例如客厅的主灯命名为客厅主灯,玄关的门磁命名为玄关门磁。
  • 位置与设备名保持一致,避免同一个房间出现三种叫法。
  • 对暂时不用的实体设置隐藏或归类,而不是直接改名占位,减少以后重复劳动。

自动化:触发器、条件与动作

Home Assistant 的自动化由三部分组成:触发器决定“什么时候检查”,条件决定“这次要不要执行”,动作描述“执行什么”。三者分离的设计让规则可以写得很清晰,也更容易排查——一条自动化没生效,只需要沿着这三个环节依次确认。

# automations.yaml:玄关开门自动亮灯(仅环境暗时执行)
- id: hallway_light_on_door_open
  alias: 玄关-开门亮灯-夜间
  trigger:
    - platform: state
      entity_id: binary_sensor.xuanguan_door
      to: "on"
  condition:
    - condition: numeric_state
      entity_id: sensor.xuanguan_illuminance
      below: 60            # 环境照度低于 60 lx 才需要开灯
  action:
    - service: light.turn_on
      target:
        entity_id: light.xuanguan_main
      data:
        brightness_pct: 70
    - delay: "00:02:00"    # 保持两分钟后自动关闭
    - service: light.turn_off
      target:
        entity_id: light.xuanguan_main
  mode: restart            # 重复触发时重启计时,避免灯被提前关掉

这条规则里有三个细节值得单独说明。第一,条件是照度而不是时间,因为冬天傍晚六点已经很暗,而夏天七点还很亮,用光照判断比用时钟判断更贴近人的感受。第二,mode: restart 解决了“开门又开门”的场景——如果连续触发,上一轮的关闭计时会被取消,灯不会在你还站在门口时突然熄灭。第三,延迟关闭写在动作里而不是另开一条自动化,避免出现“开了灯但没关”的孤儿状态。

写自动化的通用原则可以总结成三条:让触发器只做触发、把所有判断逻辑放进条件、让动作序列可以被中断与重启。违背这三条,规则数量一多就会互相打架。

用 REST API 打通外部程序

Home Assistant 提供了完整的 REST API,任何能发起 HTTP 请求的程序都可以读写实体状态、调用服务。这让它成为家庭里各类脚本的天然枢纽:定时任务、自建看板、告警推送、甚至机器学习脚本都能挂上来。

import os
import requests

HA_URL = "http://192.168.1.30:8123"
TOKEN  = os.environ["HA_TOKEN"]     # 长期访问令牌,务必放进环境变量
HEADERS = {"Authorization": f"Bearer {TOKEN}",
           "Content-Type": "application/json"}

def call_service(domain, service, entity_id, **data):
    """调用实体服务,例如 light.turn_off"""
    resp = requests.post(
        f"{HA_URL}/api/services/{domain}/{service}",
        headers=HEADERS,
        json={"entity_id": entity_id, **data},
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json()

def get_state(entity_id):
    """读取实体当前状态与属性"""
    resp = requests.get(f"{HA_URL}/api/states/{entity_id}",
                        headers=HEADERS, timeout=5)
    resp.raise_for_status()
    return resp.json()

# 场景一:离家时关闭全屋灯,并把客厅空调设为 26 度
call_service("light", "turn_off", "all")
call_service("climate", "set_temperature",
             "climate.keting_air", temperature=26)

# 场景二:读取客厅温度用于外部告警脚本
state = get_state("sensor.keting_temperature")
print("客厅温度:", state["state"],
      state["attributes"].get("unit_of_measurement"))

使用 API 时有两条安全规则必须遵守:令牌只能存放在环境变量或密钥文件里,绝不写进代码提交到仓库;对外暴露接口时应当经由反向代理并加上访问控制,不要直接把服务端口映射到公网——一旦被扫描到,整屋的设备都会被陌生人控制。