我在本地的服务器上跑 Home Assistant 已经两年多了,最初那几十条自动化用自带的 YAML 拖拖拽拽也完全够用,但是规则一旦超过三十条,整个人就开始难受:一个传感器状态变化可能同时触发四五条自动化,改 A 的时候总会莫名其妙牵连出 B 的问题,想排查“哪条规则把灯关了”都要在日志里翻半天。后来我把自动化引擎逐步迁移到 AppDaemon,用纯 Python 重写核心逻辑和状态机,才真正觉得自动化这条路走通了。
这篇文章从零开始,记录我如何在本地部署 AppDaemon、把它和 Home Assistant 接入,写出一批实用的自动化应用,最后再把整个 HA 系统安全地发布到外网访问。适合已经跑通 HA 基础功能、想进一步提升自动化能力和远程访问体验的朋友参考。
1. 为什么我弃了 YAML 自动化:从第 30 条规则开始失控
1.1 单一职责的自动化在变多之后会怎样
Home Assistant 自身的自动化并不差,它把触发条件、执行动作拆得明明白白,对入门非常友好。但它的表达上限停留在“条件 + 动作”这个模式上,而现实中的家庭自动化场景根本不是这个形态,更多是带时序、带分支、带状态记录的东西。
举个例子:我走廊的灯需要“有人在且照度低于某个值才开,并且 5 分钟后自动关,如果这期间人一直在动就不断往后延时”。用 YAML 写的话,trigger 里面要同时监听人体传感器和照度传感器,condition 要判断时间区间,动作里还要依靠 delay_on 或者再定义一条“重置倒计时”的自动化来配合。这套逻辑用 YAML 表达勉强能用,但可读性已经非常差了,之后想再加一个“晚上 11 点后降低亮度”的分支,整个自动化就得推翻重写。
当自动化数量超过 30 条之后,另一个问题开始放大:自动化之间相互影响。比如“离家模式”把所有灯关了,但“观影模式”可能同时在执行开灯动作,两个自动化抢同一个实体状态,最后结果取决于谁后执行。这类问题在 YAML 里极难排查,因为自动化是扁平结构,没有任何图层和优先级的概念。
1.2 AppDaemon 在自动化体系里的真实定位
AppDaemon 是一个独立运行的 Python 自动化引擎,它通过 WebSocket 长连接实时监听 Home Assistant 的事件和状态变化。你可以理解成:HA 负责设备接入和数据采集,AppDaemon 负责“思考”和决策,两者各管一摊。
AppDaemon 和 HA 自带自动化的本质区别在于,它给你的是一个完整的 Python 运行时。你可以定义类、写循环、用第三方库,甚至在自己的 App 里维护一份长期状态。原本 YAML 里绕来绕去的东西,在 Python 里就是几行代码的事。
比如刚才说的走廊灯逻辑,AppDaemon 里可以这样写:
python复制import appdaemon.plugins.hass.hassapi as hass
class MotionLight(hass.Hass):
def initialize(self):
self.sensor = self.args.get("sensor")
self.light = self.args.get("light")
self.delay = self.args.get("delay", 300)
self.timer = None
self.listen_state(self.on_motion, self.sensor, new="on")
def on_motion(self, entity, attribute, old, new, kwargs):
if self.timer:
self.cancel_timer(self.timer)
self.turn_on(self.light)
self.timer = self.run_in(self.turn_off_light, self.delay)
def turn_off_light(self, kwargs):
self.turn_off(self.light)
self.timer = None
每次检测到“有人”事件就把旧的定时器取消,再开灯并重新设定时器。这个重试逻辑在 YAML 里可以实现,但绝对没有这么直白。而且这里的 timer 是保留在 App 对象里的,你完全清楚当前这个流程走到了哪一步,不会出现改了一条规则还要全局搜索引用的情况。
1.3 什么情况下不建议上 AppDaemon
虽然我推荐 AppDaemon,但它不是银弹。如果你的自动化只有十来条,用 HA 自带的可视化编辑就够,硬要上 AppDaemon 只会增加维护成本。另外,对编程完全没有概念的朋友也不要强行引入,因为 AppDaemon 的应用文件本质上就是 Python 代码,涉及类、回调、参数注入这些概念,出问题的时候需要点编程功底来 debug。
还有一个容易被忽视的点:AppDaemon 是独立进程,相当于给整个智能家居系统多了一个故障节点。如果你家里的网络环境极不稳定,或者你希望整个系统越简单越好,那就要认真评估一下这些高级自动化是不是可以继续用 HA 自带的。就我个人经验来说,自动化规则超过 30 条、里面又带状态延时的,闭眼入 AppDaemon 不亏。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前必须想清楚的目录结构与容器编排
2.1 两种部署方式对比:Docker 与裸机 pip
AppDaemon 官方支持 pip 安装,也支持 Docker 镜像。我强烈建议用 Docker,原因有三点:一是环境隔离,不会因为系统里的 Python 版本或者某个依赖库冲突搞坏整个自动化系统;二是升级和回滚异常方便,镜像标签切回去就还原了;三是容器可以通过 restart: unless-stopped 实现开机自启和崩溃自愈,这条对长期放在家里运行的设备来说太重要了。
如果你用的是 Home Assistant OS 或者 Supervised 安装方式,可以直接通过 Add-on 商店装社区版 AppDaemon Add-on。但我本地的 HA 是 Docker 部署的,所以这篇顺着 Docker Compose 的路线写。
2.2 我采用的目录布局和各文件职责
我所有智能家居相关服务都放在一个根目录下统一管理,这样备份和迁移都很方便。目录结构如下:
text复制/srv/smarthome/
├── docker-compose.yml
├── ha_config/ # Home Assistant 配置目录
├── appdaemon_config/ # AppDaemon 配置目录
│ ├── apps/ # 存放所有 Python app 文件
│ ├── appdaemon.yaml # 主配置
│ ├── apps.yaml # app 实例配置
│ └── secrets.yaml # 存放 token 等敏感信息
└── logs/ # 容器日志统一输出目录
pros/cons 不说太多,重点提两个容易踩的坑。第一个是 secrets.yaml 里的敏感信息格式,AppDaemon 的主配置里使用 !secret ha_token 引用,而 secrets.yaml 自己不用再嵌套 secrets: 这一层,直接写 ha_token: eyJ... 就行。第二个是 apps 目录下最好不要再放非 Python 文件,AppDaemon 会扫描整个目录加载 app,多出来的无关文件在某些版本下会导致启动警告,虽然不影响运行,但看着难受。
2.3 docker-compose 配置逐行说明
先给出我完整的 docker-compose.yml:
yaml复制version: "3.8"
services:
homeassistant:
image: ghcr.io/home-assistant/home-assistant:stable
container_name: homeassistant
volumes:
- /srv/smarthome/ha_config:/config
- /etc/localtime:/etc/localtime:ro
ports:
- "8123:8123"
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
appdaemon:
image: ghcr.io/appdaemon/appdaemon:latest
container_name: appdaemon
depends_on:
- homeassistant
volumes:
- /srv/smarthome/appdaemon_config:/conf
- /srv/smarthome/logs/appdaemon:/logs
ports:
- "8000:8000"
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
几个关键点展开说一下:
depends_on 只能保证 Home Assistant 容器先启动,并不能保证 HA 的 WebSocket 服务已经就绪。不过 AppDaemon 的 HASS 插件本身有自动重连机制,启动早期连不上会持续重试,所以实际使用中不必担心这个先后问题。
/etc/localtime 的挂载是为了保证容器内时间和宿主机一致,虽然我同时设了 TZ 环境变量,但两个都配上是最稳的组合。
AppDaemon 容器默认会在 8000 端口起一个管理后台,可以看日志和 app 运行状态。这个端口刚才在 compose 里映射出来了,但如果你的服务器不打算给别人看 AppDaemon 管理页,建议不要把它映射到公网,只保留本机访问。
2.4 appdaemon.yaml 的坑:token 与日志配置
appdaemon.yaml 是 AppDaemon 容器的核心配置文件,我第一次配置时被日志配置折腾过一轮,所以这里直接给出一份可用的:
yaml复制secrets: /conf/secrets.yaml
app_dir: /conf/apps
time_zone: Asia/Shanghai
log:
logfile: /logs/appdaemon.log
accessfile: /logs/access.log
errorfile: /logs/error.log
log_level: INFO
plugins:
HASS:
type: hass
ha_url: http://homeassistant:8123
token: !secret ha_token
app_init_ack: true
注意 ha_url 里面用的是容器之间通信的服务名 homeassistant,因为在同一张 Docker 网络里,AppDaemon 容器可以直接通过服务名访问 HA,不需要绕到宿主机 IP。
token 必须是一个 Home Assistant 的长期访问令牌,我建议到 HA 页面右上角点用户头像,进入“安全”选项卡,滚动到底部创建一个“长期访问令牌”,只给 AppDaemon 单独生成一个,别拿自己日常用的 API 密码凑合。这个 token 的权限范围就是你的 HA 用户权限,所以建议专建一个受限用户,不要直接给管理员账号生成 token。
app_init_ack 是建议开启的,它要求 AppDaemon 在 app 的 initialize() 执行完成后才向插件确认启动成功,避免 app 里的异步操作还没准备完就开始消费事件。
3. 用 Python 写自动化:从第一个 App 到完整业务逻辑
3.1 最小可运行 App 的代码骨架
AppDaemon 里每一个自动化逻辑被称为一个 App,它并不是一个独立进程,而是一个继承自 hass.Hass 的类。当你把 Python 文件放到 apps 目录,并在 apps.yaml 里声明实例后,AppDaemon 会自动加载它并调用类的 initialize() 方法。
initialize() 是 App 的入口,你在里面注册各种回调和定时任务。最基本的骨架长这样:
python复制import appdaemon.plugins.hass.hassapi as hass
class HelloWorld(hass.Hass):
def initialize(self):
self.log("Hello from AppDaemon")
在 apps.yaml 里这样注册:
yaml复制hello_world:
module: hello_world
class: HelloWorld
这里 module 对应 Python 文件名,class 对应类名。AppDaemon 会在启动时加载 apps/hello_world.py,实例化 HelloWorld,然后调用 initialize()。
如果一切正常,你会在日志里看到 Hello from AppDaemon。这一步跑通,意味着整个 AppDaemon 和 HA 的连接基本没问题了。
3.2 常用回调:状态监听、定时任务、日出日落
AppDaemon 的开发体验中,最舒服的就是它的事件回调和任务调度库。我把日常用得最多的接口列一下。
状态监听是 listen_state,它有一个很实用的参数叫 new,只有当实体的新状态等于指定值时才触发回调。比如前面走廊灯的例子里,new="on" 保证只有从“无运动”变成“有运动”这一瞬间才触发,不会因为状态一直保持 on 而反复执行。
定时任务方面,run_in 是延迟指定秒数后执行一次,run_daily 是每天固定时间执行,run_at 是跑到某个精确时间点执行,run_hourly、run_every 也都有。对于需要跟随太阳光照的自动化,AppDaemon 提供了更人性化的封装:
python复制import appdaemon.plugins.hass.hassapi as hass
import datetime
class PorchLight(hass.Hass):
def initialize(self):
self.run_at_sunset(self.turn_on_porch_light)
self.run_at_sunrise(self.turn_off_porch_light)
def turn_on_porch_light(self, kwargs):
self.turn_on("light.porch")
def turn_off_porch_light(self, kwargs):
self.turn_off("light.porch")
日出日落的时间不是死的,HA 会根据你配置的地理位置自动计算。这种“自然节律”类自动化用 AppDaemon 写出来非常干净。
3.3 状态机与多步骤流程的写法
如果你家里有类似“晚上回家”这种需要顺序执行的场景,AppDaemon 的 Python 特性就派上大用场了。你可以用实例变量记录当前流程的进度,每一步触发后决定下一步做什么,而不是把十几条 YAML 自动化绞在一起。
我写过一个“夜间回家”流程,简单拆解逻辑就是:打开玄关灯并调暗,等待 30 秒后如果玄关的灯还是暗的但客厅有人移动,就恢复正常亮度;如果玄关 2 分钟没有检测到人,就关掉玄关灯。用 AppDaemon 实现,关键代码是:
python复制class ArriveHomeRoutine(hass.Hass):
def initialize(self):
self.listen_state(self.on_entry, "binary_sensor.front_door", new="on")
def on_entry(self, entity, attribute, old, new, kwargs):
self.turn_on("light.entry", brightness=80)
self.run_in(self.after_short_delay, 30)
def after_short_delay(self, kwargs):
if self.get_state("binary_sensor.living_motion") == "on":
self.turn_on("light.entry", brightness=255)
self.run_in(self.after_long_delay, 120)
else:
self.turn_off("light.entry")
def after_long_delay(self, kwargs):
self.turn_off("light.entry")
这里我用了 get_state 去主动查询当前状态,配合程序里的流程标记,整个逻辑非常接近你在纸上画的那个状态图。相比 YAML 里靠“条件 + 延迟 + 下次触发”硬凑的写法,这个可读性提升是降维打击。
3.4 多 App 之间的解耦与参数注入
AppDaemon 允许你在 apps.yaml 里给同一个类创建多个实例,每个实例可以传入不同的参数。这个设计我太喜欢了,同样的“人体感应灯”逻辑,可以给走廊、阳台、卫生间各配一个实例,参数不同而已:
yaml复制motion_light_corridor:
module: motion_light
class: MotionLight
sensor: binary_sensor.corridor_motion
light: light.corridor
delay: 180
motion_light_balcony:
module: motion_light
class: MotionLight
sensor: binary_sensor.balcony_motion
light: light.balcony
delay: 60
在 Python 文件里不需要改任何代码,通过 self.args.get("sensor") 就能拿到对应实例的参数。这意味着逻辑写一遍,参数化复用。新增一个场景时,只需要在 apps.yaml 里加几行,不用再复制粘贴一个 Python 文件。
3.5 调试技巧:日志里看回调的世界
写 AppDaemon 自动化,最有价值的排查手段就是日志。AppDaemon 的日志分级非常细,默认 INFO 级别会打印 app 加载、状态回调这些基本信息。如果你怀疑某条回调没有被触发,可以把 log_level 改成 DEBUG,然后就能看到 AppDaemon 和 HA 之间传递的原始事件。
我是这样用日志的:在自己的 app 代码里尽可能多地打点,比如“触发了”“开始执行”“执行完成”“延时取消”,然后串起来看整个流程。AppDaemon 的日志带时间戳、app 名称和日志级别,一眼就能定位到问题出在哪个环节。
另外一个调试利器是 AppDaemon 的 admin 界面,默认在 http://localhost:8000。在这里可以看到所有已加载的 app、它们的状态、以及最近执行的动作。我通常先看 admin 界面的 app 列表确认加载正常,再去日志里翻回调细节。
4. 外部访问落地:从端口映射到安全加固
4.1 先明确你的出口网络:公网 IP 还是内网环境
部署好 AppDaemon,自动化跑顺之后,下一步基本就是解决外网访问。我判断一个 HA 玩家是否“毕业”,就看他能不能在任何地方安全地打开家里的控制页面。
做外部访问之前,先搞清楚自己家里宽带的情况。如果你有一个公网 IP(可以从路由器 WAN 口状态里看,或者从外面的设备 ping 一下自己的域名),那恭喜你,可以用端口映射加反向代理,这是最标准、延迟最低的方案。如果你在运营商大规模部署 IPv4 转换的环境下,分不到公网 IPv4,那就需要用隧道方案把内网服务发布出去。
判断方法很简单:用手机流量访问家中路由器的公网 IP 地址,如果能打开你某个服务的端口,说明有公网 IP;如果完全不通,基本就是没有。另外也可以看 IPv6 是否可用,如果运营商给了公网 IPv6,HA 可以直接监听 IPv6 地址,效果和公网 IPv4 类似。
4.2 反向代理方案:Caddy 自动 HTTPS 实战
如果条件允许,我最推荐的反向代理是 Caddy。原因是它自动申请和续期 HTTPS 证书,配置只需要三行,几乎没有 Nginx 那些证书路径、权限、reload 的琐碎问题。用 Caddy 把 HA 的 8123 端口代理到一个域名上,配置如下:
text复制ha.example.com {
reverse_proxy 127.0.0.1:8123
}
前提是你这个域名已经解析到家里的公网 IP,并且 443 端口可以从外网访问。Caddy 启动后会自动通过 HTTP 和 HTTPS 两种方式验证域名所有权,申请 Let's Encrypt 证书,之后所有的外部流量都以 HTTPS 加密传输,不再需要手动处理证书相关的任何事。
如果你更习惯 Nginx,也可以在 Nginx 里手动配置证书和反代。我原来用的就是 Nginx,配置大体如下:
nginx复制server {
listen 443 ssl http2;
server_name ha.example.com;
ssl_certificate /etc/letsencrypt/live/ha.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ha.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8123;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
这里 proxy_set_header Upgrade 和 Connection "upgrade" 非常关键,因为 HA 前端和 AppDaemon 管理界面里有 WebSocket 连接,如果不放行 Upgrade 头,页面能打开但实时状态不会动,这个坑我踩过一次。
4.3 内网穿透场景的稳妥选择
没有公网 IP 的家庭网络,另一种常见做法是借助一台有公网 IP 的云服务器,通过 frp 这类隧道工具把本地 HA 端口映射到云服务器的某个端口上。
frp 分服务端和客户端两部分。服务端部署在云服务器上,客户端部署在家里的 NAS 或者任意一台能访问 HA 的机器上。我用的配置如下。
服务端 frps.toml:
toml复制bindPort = 7000
auth.token = "your_strong_token"
客户端 frpc.toml:
toml复制serverAddr = "your_cloud_server_ip"
serverPort = 7000
auth.token = "your_strong_token"
[[proxies]]
name = "homeassistant"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8123
remotePort = 8123
这样云服务器的 8123 端口就映射到了家里的 HA 8123 端口。然后云服务器上再套一层 Caddy 或 Nginx 做 HTTPS 反向代理,走域名访问。
另一个我这两年用得更多的方案是 Cloudflare Tunnel,也就是 cloudflared:在你家里跑一个轻量客户端,主动和 Cloudflare 的边缘节点建立加密隧道,不需要路由器开任何外来端口。配合 Cloudflare 的 DNS 和 HTTPS,外部访问的安全系数非常高。配置也比较简单,下载 cloudflared 后执行一次登录绑定域名,后面就只要启动服务保持连接即可,证书和续期都不用管。缺点是对国内访问速度不如本地机房直连,但胜在零配置、免费、稳定。
4.4 HA 侧的反代适配与安全配置
无论用哪种反代方案,HA 侧都需要告诉系统“现在有反向代理在帮我转发请求”,否则 HA 在处理请求时会把所有客户端都识别成反代服务器的 IP,导致 IP 限制、日志记录全部失效。
在 configuration.yaml 里加入:
yaml复制http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1
- 172.18.0.0/16
trusted_proxies 需要写成你反代服务所在网络的网段。如果 Caddy 跑在宿主机上,就填 127.0.0.1;如果你和我一样把 Caddy 也跑在 Docker 里,那需要确认容器所在网段。这个很关键,填错了外网访问会出现各种奇怪的“无法登录”和跳转问题。
如果你希望外部访问时地址栏显示域名而不是 IP,还要在 HA 里配置 external_url:
yaml复制homeassistant:
external_url: https://ha.example.com
这样 HA 在生成一些内部跳转链接时会自动使用你的安全域名,避免出现 HTTPS 页面里嵌入 HTTP 资源被浏览器拦截的问题。
4.5 安全加固清单
外部访问一旦打开,暴露在公网的 HA 实例就会被各种扫描器盯上。我不主张只依赖 HA 的登录页保护,那样太单薄。我整理了一份我实际执行的安全加固清单:
- HA 用户启用双因素认证,尤其是管理员账号。
- 为 AppDaemon 单独创建低权限用户并生成专用 token,不给管理员 token。
- 反向代理层开启 IP 白名单或访问控制,如果只自己用,可以考虑只允许本国家/地区 IP 段访问。
- 不要直接把 AppDaemon 的 8000 管理端口映射到公网,这个端口没有 HA 那么完善的认证机制。
- 定期备份 HA 配置和 AppDaemon 配置,至少做到每周快照一次。
- 域名解析记录不要用太弱的 TTL,尽量用 CNAME 或 A 记录配合 HTTPS 验证,避免被暴力解析到其他服务。
- 有条件的话在 HA 前面套一层 WAF 或者 CrowdSec / Fail2ban,对多次登录失败的 IP 进行封禁。
我自己的真实经历是,暴露公网的前几天,日志里就出现了大量来自境外 IP 的扫描和登录尝试。如果不是因为提前配置了双因素和 IP 白名单,后果真不好说。安全这个东西,永远不要等出了事再补。
5. 长期稳定运行:我看过的坑与维护经验
5.1 重启顺序与依赖问题
Docker Compose 里的 depends_on 只能保证启动顺序,不能保证 HA 已经可以被访问。AppDaemon 插件本身有较强的重连机制,即使 HA 还没就绪,AppDaemon 启动后也会一直尝试连接,所以通常情况下不会有什么大问题。
但如果你把 HA 容器升级了、然后马上重启整个 compose 项目,两个容器几乎是同时启动的,AppDaemon 可能会在重连好几十次后才连上。这时候去 admin 界面看 AppDaemon,可能显示连接失败,不要慌,等一两分钟再刷新就好。
我自己的解决方法是错开启动时间,在 compose 里给 appdaemon 加一个简单的 healthcheck,让 HA 的 8123 端口能响应后再启动 appdaemon。不过说实话,这个必要性不强,知道这是重连机制的正常表现就够了。
5.2 备份与版本升级
整个系统长期跑下来,配置会越改越多,备份这件事绝对不能省。我每周坐一次完整备份,把整个 /srv/smarthome 目录打包到 NAS:
bash复制tar -czf /backup/smarthome_$(date +%Y%m%d).tar.gz /srv/smarthome
这个 tar 包包含了 HA 配置、AppDaemon 配置以及所有 app 代码,恢复的时候解压回原目录再重新 docker compose up -d 即可。注意备份前最好先停一下 AppDaemon 容器,或者至少让它处于空闲状态,避免备份到写了一半的日志文件。
升级方面,我坚持一个原则:HA 和 AppDaemon 的镜像标签不要用 latest 直接用固定版本号。每次升级前先在仓库看 release notes,然后单独拉一个测试环境跑两天,没问题再应用到生产容器。毕竟智能家居这种东西,半夜家里灯突然不亮了可比线上业务挂掉的体验糟糕多了。
5.3 容器日志暴涨的处理
AppDaemon 的日志在 DEBUG 级别下非常详细,但你如果在 appdaemon.yaml 里把 log_level 一直设为 DEBUG 而没有开日志轮转,容器磁盘会很快被日志撑爆。我吃过这个亏,某天突然发现 NAS 报磁盘满,查了一圈才发现是 AppDaemon 的 access.log 长到了几个 GB。
现在我的策略是:日常运行保持 INFO,只在排查问题时临时切到 DEBUG 并在复位后改回。同时在 appdaemon.yaml 里设置了日志文件大小和保留份数:
yaml复制log:
logfile: /logs/appdaemon.log
accessfile: /logs/access.log
errorfile: /logs/error.log
log_level: INFO
log_size: 1048576
log_generations: 4
log_size 单位是字节,log_generations 表示保留多少个历史文件。这样即使日志量大,也不会无限膨胀。
5.4 AD 长时间运行后状态漂移
AppDaemon 长时间运行后,偶尔会遇到 app 内部持有的状态和 HA 真实状态不一致的情况。比如某个传感器实体因为断网被 HA 标记为不可用,AppDaemon 里的 get_state 返回值会变成 unavailable,如果你的自动化逻辑里只判断了 on 和 off,就可能漏掉这个分支,导致后续动作没有执行。
我的对策是在关键流程里主动判断“不可用”情况。比如人体感应灯的逻辑,我加了这样一段:
python复制def is_sensor_available(self, entity):
state = self.get_state(entity)
return state not in ("unavailable", "unknown")
还有一点,如果 HA 里删除了某个实体,但 AppDaemon 里还在监听它,listen_state 不会报错,只是一直不触发。排查这种问题时,去 admin 界面看 app 的订阅列表,确认监听的是不是当前存在的实体 ID。
5.5 一个提升幸福感的小技巧:给 App 起好名字
最后分享一个我实践后非常受益的习惯:在 apps.yaml 里给每个 App 实例起一个能直接看出业务含义的名字,并且在 Python 代码里用 self.log 统一输出关键节点的日志。这样当你家里某个自动化不按预期工作时,打开日志就是一条条“门口灯:检测到有人,开灯并延时 5 分钟”“门口灯:5 分钟倒计时结束,关灯”的记录,排查效率直接翻倍。
AppDaemon 的 self.log 默认会在日志前面加 App 名称和时间,所以我通常只输出业务信息,比如 self.log(f"Motion detected: {entity}")。当多个 App 同时跑的时候,这种命名规范比什么都好用。
