hughobrien/breezyd
GitHub: hughobrien/breezyd
一个用 Go 编写的 Vents Twinfresh 系列通风机局域网控制工具,提供 CLI、守护进程、Web 仪表盘、HomeKit 网桥和 Prometheus 指标,无需云账号即可完整管理设备。
Stars: 0 | Forks: 0
# breezyd
[](https://www.gnu.org/licenses/gpl-3.0)
[](https://pkg.go.dev/github.com/hughobrien/breezyd)
[](https://github.com/hughobrien/breezyd/releases)
一个用于在局域网内控制 [Vents
Twinfresh](https://ventilation-system.com/) Breezy 无管道热回收
通风机的 Go 库、守护进程和 CLI。它直接使用设备原生的 UDP/4000
协议进行通信 —— 无需云账号、MQTT broker、厂商应用或 Home
Assistant 集成。仅限 LAN 使用。
CLI 可以独立工作 —— `breezy ` 会向
配置的设备打开 UDP 连接,完成操作后退出 —— 这也是全新安装后的默认行为。
当您需要轮询、缓存、JSON
HTTP API、Prometheus `/metrics`、内嵌的 Web 仪表盘、通往 Apple Home 的 HomeKit
网桥,或者跨多个进程对同一设备的写入操作进行序列化时,可以添加可选的守护进程 (`breezyd`)。

]` 块:
```
[devices.playroom]
id = "BREEZY00000000A0"
password = "testpwd"
ip = "192.168.1.148"
[devices.bedroom]
id = "BREEZY00000000A1"
password = "testpwd"
ip = "192.168.1.152"
[devices.office]
id = "BREEZY00000000A2"
password = "testpwd"
ip = "192.168.1.160"
```
```
mkdir -p ~/.config/breezy
$EDITOR ~/.config/breezy/config.toml
chmod 0600 ~/.config/breezy/config.toml
```
0600 模式检查是强制执行的 —— 加载器否则将拒绝启动。
### 3. 验证
```
breezy ls # all configured devices, one line each
breezy playroom status # full snapshot — sensors, fans, service info
breezy bedroom speed manual:30 # set bedroom fan to 30 % manual
breezy office mode regeneration # switch office to heat-recovery mode
```
`breezy --help` 是最权威的参考;请参阅 [CLI 概览](#cli-overview)
获取完整的动词列表。
### 4. (可选)运行守护进程
如果您想要以下任何功能,请运行 `breezyd`:
- **轮询 + 缓存** —— 每个设备的状态按可配置的
节拍刷新,从内存中提供数据。CLI 运行更快,且不需要读取时
设备都必须可达。
- **JSON HTTP API** 位于 `http://127.0.0.1:9876/v1/devices/...`。
- **Prometheus `/metrics`** 用于 Grafana 仪表盘 / 警报。
- **内嵌的 Web 仪表盘** —— 即本 README 顶部附近的截图。
- **HomeKit 网桥** —— 参见 [HomeKit](#homekit) 章节。
- **并发安全** 当多个进程针对同一设备编写 `breezy` 脚本时。独立运行的 CLI 之间互不协调;守护进程
通过互斥锁序列化每个设备的 UDP 通信。
在配置中添加 `[daemon]` 块并启动守护进程:
```
[daemon]
listen = "127.0.0.1:9876"
poll_interval = "30s"
discovery = "on-start" # "on-start" | "off" | "periodic:"
```
```
breezyd # logs to stderr; stop with SIGINT/SIGTERM
```
当在配置中设置了 `[daemon].listen` 或
传递了 `--daemon URL` 时,CLI 会自动检测守护进程模式。使用 `--daemon http://...` 覆盖
可与远程守护进程通信,或者完全省略 `[daemon]` 以保持
独立运行模式。
如果 `breezyd` 启动时配置不存在,它会写入一个合理的
默认配置(其中 `[daemon]` 被注释掉,因此重新运行即可获得正常工作的
独立模式)并以“请编辑它”的消息退出。
如果您希望守护进程在 systemd 下开机自启,请参阅
[Linux + systemd](#linux--systemd) —— 该章节中的单元文件可以
原样工作,无论您是通过 `nix profile install` 还是从发布存档中获取二进制文件。
## Linux + systemd
适用于没有 Nix 的 Ubuntu / Debian / Arch / Fedora 等系统。
### 1. 下载二进制文件
为 Linux (amd64/arm64)、macOS (amd64/arm64) 和 Windows
(amd64) 预构建的二进制文件发布在 [GitHub Releases
页面](https://github.com/hughobrien/breezyd/releases)。下载适合您平台的
压缩包,并将 `breezyd` 和 `breezy` 解压到 `$PATH` 上的某个位置:
```
# Linux amd64 示例
curl -sSL -o breezyd.tar.gz \
https://github.com/hughobrien/breezyd/releases/latest/download/breezyd_Linux_x86_64.tar.gz
tar -xzf breezyd.tar.gz breezyd breezy
sudo install -m 0755 breezyd breezy /usr/local/bin/
breezyd --version
```
### 2. 查找您的设备 ID
```
breezy discover
# 192.168.1.148 id=BREEZY00000000A0 type=17 (Breezy 160)
# 192.168.1.152 id=BREEZY00000000A1 type=17 (Breezy 160)
# 192.168.1.160 id=BREEZY00000000A2 type=17 (Breezy 160)
```
如果广播结果为空,请将每个 IP 作为位置参数传递,并且如果您的设备使用非默认密码,请添加 `-p PASSWORD`:
```
breezy discover -p huffpuff 192.168.1.148 192.168.1.152 192.168.1.160
```
### 3. 独立使用 CLI
如果您只想要 CLI(不需要守护进程或服务),这就足够了:
以 0600 模式编写包含已发现 ID 的 `~/.config/breezy/config.toml`,然后运行 `breezy ls`。格式同
[随处运行的 Nix 独立配置](#2-write-the-config)。仅当您希望守护进程
在后台进行轮询时才跳到第 4 步。
### 4. (可选)将 breezyd 作为系统服务运行
创建一个系统用户,将守护进程的配置放在 `/etc/breezyd/` 下,
并添加一个精简的 `/etc/breezy/config.toml` 以便用户获得自动检测功能:
```
# 创建守护进程的用户和配置目录。
sudo useradd --system --no-create-home --shell /usr/sbin/nologin breezyd
sudo install -d -m 0750 -o breezyd -g breezyd /etc/breezyd
# 写入守护进程的配置(包含密码)。权限模式为 0600。
sudo tee /etc/breezyd/breezyd.toml <<'EOF' >/dev/null
[daemon]
listen = "127.0.0.1:9876"
poll_interval = "30s"
discovery = "on-start"
password = "huffpuff"
[devices.bedroom]
id = "BREEZY00000000A0"
ip = "192.168.1.148"
[devices.office]
id = "BREEZY00000000A1"
ip = "192.168.1.152"
[devices.playroom]
id = "BREEZY00000000A2"
ip = "192.168.1.160"
EOF
sudo chown breezyd:breezyd /etc/breezyd/breezyd.toml
sudo chmod 0600 /etc/breezyd/breezyd.toml
# 写入 CLI 的系统回退配置(不含密码)。权限模式设为 0644,以便主机上的任何
# 用户都能读取;当不存在
# ~/.config/breezy/config.toml 时,CLI 会尝试读取此文件。
sudo install -d -m 0755 /etc/breezy
sudo tee /etc/breezy/config.toml <<'EOF' >/dev/null
[daemon]
listen = "127.0.0.1:9876"
EOF
```
将以下内容保存为 `/etc/systemd/system/breezyd.service`(其
强化配置与 NixOS 模块的设置相一致):
```
[Unit]
Description=Vents Twinfresh Breezy / Elite 160 Pro daemon
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/usr/local/bin/breezyd --config /etc/breezyd/breezyd.toml
User=breezyd
Group=breezyd
Restart=on-failure
RestartSec=5s
# 安全加固 — breezyd 仅需要出站 UDP 和 HTTP 监听器。
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
ProtectProc=invisible
# Go 的 net.Interfaces() 在 Linux 上需要 AF_NETLINK;若没有它,
# HomeKit 网桥虽然运行,但不会在任何接口上进行广播(对 mDNS 无响应)。
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
[Install]
WantedBy=multi-user.target
```
然后启用并启动:
```
sudo systemctl daemon-reload
sudo systemctl enable --now breezyd
journalctl -u breezyd -f # tail the log to confirm it's polling
breezy ls # any user can talk to the daemon now
```
如果您在配置中开启了 `[homekit]`,还需:
- 将 `StateDirectory=breezyd` 添加到 `[Service]` 中,以便 systemd 为 HAP 服务器的配对状态创建 `/var/lib/breezyd/`。
- 在 `[homekit]` 中固定一个具体的 `port = N`(默认 `0` 是临时的,无法通过防火墙控制),然后打开主机防火墙:
`ufw allow N/tcp`(或 `firewall-cmd --add-port=N/tcp`)。
- 打开 **用于 mDNS 的 UDP/5353** 以便 iPhone 能够发现网桥:
`ufw allow 5353/udp`。如果不这样做,即使配对功能正常,
网桥也不会出现在添加配件列表中。
## Web UI
守护进程在其 HTTP 监听器的根路径上提供服务端渲染的仪表盘(通过 SSE 使用 templ + datastar):
```
http://127.0.0.1:9876/
```
三列卡片(每个配置的设备一张)显示实时传感器
读数(湿度 / eCO₂ / VOC,每个都带有警报阈值;点击
数值会打开一个内联编辑器来调整阈值)、风扇 RPM 及
指令百分比、服务信息(滤网、电机寿命、RTC
电池、故障)、固件版本,以及电源、气流
模式、风扇速度(预设 1-3 或手动百分比滑块)、加热器和
夜间/涡轮增压 特殊模式定时器的控制项。当
固件设置超阈值标志时,传感器值会以红色显示。页面每
5 秒自动刷新;如果上次轮询已超过 90 秒,卡片会变为灰度显示。暗黑
模式自动跟随 `prefers-color-scheme`;点击标题旁边的主题图标可进行手动覆盖。
默认的 `[daemon].listen` 为 `127.0.0.1:9876`,这意味着
仪表盘只能从运行 `breezyd` 的主机访问。要从同一 LAN 上的手机或笔记本电脑使用它,请在
`~/.config/breezy/config.toml` 中更改监听器:
```
[daemon]
listen = "0.0.0.0:9876"
```
(如果您不想绑定到所有接口,可以指定一个具体的 LAN IP。)更改配置后请重启 `breezyd`。
**安全隐患:** HTTP API 没有身份验证。绑定
到 LAN 可达的地址会将所有 `/v1/...` 端点暴露给同一网络中的任何人
—— 包括可以更改设备协议密码或 WiFi 凭据的原始 `POST /v1/devices//params/` 写入路径。缓解措施在于网络配置而非软件:将
设备(以及运行 `breezyd` 的主机)放在一个 IoT VLAN 中。请参阅
[安全](#security) 章节了解全貌。
### 在 nginx 之后(NixOS)
如果您已经在运行 NixOS 和 `services.nginx`,在 LAN 上暴露
仪表盘的更简洁方式是使用模块的可选 nginx
集成:保持 `[daemon].listen = "127.0.0.1:9876"`(这样守护进程
本身保持仅绑定到回环地址,原始 API 从 LAN 不可达),并让 nginx 作为面向网络的服务:
```
services.nginx.enable = true;
services.breezyd = {
enable = true;
nginx = {
enable = true;
virtualHost = "breezy.home.lan";
# basicAuthFile = "/run/secrets/breezy-htpasswd"; # sops-nix / agenix
};
};
# 请自行定义 vhost — TLS、ACME、监听端口等。此模块
# 仅添加了带有 proxy_pass + basicAuthFile 的 location."/"。
services.nginx.virtualHosts."breezy.home.lan" = {
forceSSL = true;
enableACME = true;
};
```
当仪表盘需要从运行 `breezyd` 的主机以外的
设备访问时,这是推荐的方案。结合
`basicAuthFile`,它为您提供了直接监听路径所缺乏的
传输层(如果您设置了 `forceSSL`,则为 TLS)和应用层(基本认证)的双重防护。守护进程的完整 `/v1/...` API 仍然位于
回环地址上,因此即使经过 nginx 认证后,受感染的 LAN 设备也无法访问原始的参数写入端点。
## HomeKit
守护进程包含一个可选的 HomeKit 网桥。启用后,每个
配置的 Breezy 都会在 Apple Home 应用中作为一个配件出现,
包含电源、风扇速度、仅送风 / 仅排风 / 加热器 / 夜间 /
涡轮增压开关、完整的传感器面板(湿度、eCO2、VOC、四个
温度)、一个带有 iOS 原生“更换滤网”指示器的
滤网维护服务,以及一个用于 RTC 纽扣电池的电池服务(低电量警告约在 40% 时触发)。
通过添加以下内容到 `~/.config/breezy/config.toml` 启用它:
```
[homekit]
enabled = true
```
重启 `breezyd`。启动日志中将包含类似如下的行:
```
homekit: bridge ready name="breezyd" pin="123-45-678" state_dir="..."
```
在 iPhone 上打开 Apple Home 应用 → 添加配件 → 手动
输入 PIN。所有配置的 Breezy 单元将一起出现;每个都有
其独立的磁贴。
**重置配对:** 删除状态目录(默认为 `~/.local/state/
breezyd/homekit`,在 NixOS 上为 `/var/lib/breezyd/homekit`)。
守护进程下次启动时会重新生成 PIN。
**可调参数**(均为可选):
- `bridge_name`:配对期间显示的名称。默认为 `"breezyd"`。
- `port`:HAP 服务器的 TCP 端口。默认为 0(由操作系统分配)。
- `state_dir`:配对密钥和 PIN 存放的位置。
在 NixOS 上,网桥只需一个开关 —— 参见上方 NixOS 章节中的 [HomeKit(可选)](#homekit-optional)。
HomeKit 网桥始终使用守护进程路径 —— 写入操作通过
`pkg/breezy/ops` 进行,具有与 HTTP 处理程序相同的每个设备互斥锁序列化和风扇稳定窗口。独立运行 CLI 的并发注意事项
与此无关;HomeKit 网桥从不打开自己的 UDP socket。
## Prometheus
守护进程以 Prometheus 展示格式暴露 `/metrics`。像抓取任何
其他目标一样抓取它:
```
# prometheus.yml
scrape_configs:
- job_name: breezy
static_configs:
- targets: ['localhost:9876']
```
每个指标都带有 `device=""` 和 `id="<16-char id>"` 标签。一些
有用的查询:
```
# 每个设备的室内温度
breezy_temperature_celsius{location="indoor"}
# 任何超过其警报阈值的传感器(湿度 / co2 / voc)
max by (device) (breezy_sensor_alert) > 0
# 逐间房的恢复效率
breezy_recovery_efficiency_pct
# 滤网剩余时间(以天为单位)
breezy_filter_remaining_seconds / 86400
# 过去 5 分钟内是否有设备变为不可达状态?
time() - breezy_last_poll_timestamp > 300
```
当轮询器连接到设备时 `breezy_up{device="..."}` 为 `1`,否则为 `0`;
相应的 `breezy_last_poll_timestamp` 是上次成功读取的 Unix 时间戳。
在 NixOS 上,自动抓取集成只需一个开关 —— 参见
NixOS 章节中的 [Prometheus(可选)](#prometheus-optional)。
## CLI 概览
`breezy --help` 是最权威的参考。其结构为“主语在谓语之前”,
因此按设备划分的命令读起来很自然:
| 命令 | 功能 |
| ------------------------------------ | -------------------------------------------- |
| `breezy ls` | 每个已配置设备的单行表格摘要 |
| `breezy discover [-p PWD] [ip...]` | LAN 广播(或向每个 IP 单播);`-p` 覆盖通配符发现密码 |
| `breezy param` | 列出已知参数(id、类型、单位、容量;使用 `name` 搭配 `get`/`set`) |
| `breezy playroom status` | 完整的结构化快照 |
| `breezy bedroom on` / `off` | 电源 |
| `breezy bedroom speed manual:30` | 将风扇设置为 30% 手动模式 |
| `breezy bedroom speed 2` | 切换到预设 2 |
| `breezy office mode regeneration` | 气流模式(ventilation / regeneration / supply / extract) |
| `breezy office heater on` | 切换辅助加热器 |
| `breezy bedroom timer night` | 启动夜间模式定时器(或 `turbo`/`off`) |
| `breezy playroom faults` | 列出活动的故障代码 |
| `breezy playroom firmware` | 固件版本 + 构建日期 |
| `breezy playroom efficiency` | 回收效率百分比 % |
| `breezy playroom rtc` | 显示设备时钟 |
| `breezy playroom rtc set 2026-05-03T22:00:00-07:00` | 设置设备时钟 |
| `breezy playroom reset-filter` | 清除滤网定时器 |
| `breezy playroom reset-faults` | 清除活动的故障标志 |
| `breezy playroom get humidity` | 按名称或十六进制读取原始参数 |
| `breezy playroom set 0x25 1e` | 写入原始参数(十六进制) |
CLI 的退出码为:`0` 成功,`1` 后端错误(守护进程模式下为 HTTP 封装消息,独立模式下为纯文本错误信息),`2` 本地用法错误。
### 独立运行 vs 守护进程模式
CLI 默认为独立模式(直接通过 UDP 连接每个设备)。典型流程
在针对 Nix 用户的 [随处运行的 Nix](#nix-anywhere) 和针对其他用户的
[Linux + systemd](#linux--systemd) 中有详细说明;两者最终都以
在有无守护进程运行的情况下都能工作的 `breezy ls` 告终。
**并发注意事项:** 守护进程通过互斥锁序列化每个设备的 UDP 通信。独立运行的 CLI 进程之间互不协调 —— 两个在同一时刻针对同一设备调用的 `breezy` 可能会产生静默的校验和损坏。如果您针对同一设备并行编写脚本调用,请运行守护进程并在守护进程模式下使用 CLI。
## 配置参考
`~/.config/breezy/config.toml` —— 模式 `0600`(当文件包含密码时由加载器强制要求)。守护进程和 CLI 都会读取此文件。CLI
`[daemon].listen` 来决定是通过 HTTP 与守护进程通信还是直接通过 UDP
与每个设备通信,并在独立模式下读取每个 `[devices.]` 作为单播目标。
当主目录中不存在配置文件时,CLI 还会尝试将 `/etc/breezy/config.toml` 作为备选。
系统备选文件通常仅包含 `[daemon].listen = "..."`(没有密码),加载器允许
其权限为 0644,以便主机上的每个用户都可以读取。当 `services.breezyd.enable = true` 时,NixOS 模块会自动写入此文件。
带默认值的完整结构:
```
# 可选。如果没有此配置块,CLI 将以 standalone 模式运行(无 HTTP)。
[daemon]
listen = "127.0.0.1:9876" # http listener; required when [daemon] present
poll_interval = "30s" # default 30s
discovery = "on-start" # "on-start" | "off" | "periodic:"
password = "" # optional fleet-wide protocol password; used for
# the daemon's discovery probes and inherited by
# any [devices.] block that omits its own
# 可选。默认关闭。参见 HomeKit 部分。
[homekit]
enabled = false
# bridge_name = "breezyd"
# port = 0 # 0 = ephemeral
# state_dir = "~/.local/state/breezyd/homekit"
# 每个 Breezy 单元对应一个 [devices.] 配置块。Name = 用作
# CLI 的 的标签:"breezy playroom status"。
[devices.playroom]
id = "BREEZY00000000A0" # 16-char device ID; from `breezy discover`
password = "testpwd" # protocol password; falls back to [daemon].password if absent
ip = "192.168.1.148" # required in standalone; optional in daemon mode
```
如果您希望将配置保存在其他地方(例如 sops-nix / agenix),请使用 `--config /path/to/file` 为守护进程指定路径。只要文件包含密码,0600 模式检查仍然适用。CLI 的配置路径顺序固定为先 `~/.config/breezy/config.toml` 然后是 `/etc/breezy/config.toml`。
## 安全
Breezy 固件会以明文形式通过 UDP/4000 向同一广播域中
知道 16 字符设备 ID 的任何客户端分发其自身的协议密码(参数 `0x7D`)、WiFi SSID (`0x95`) 和 WiFi 密码 (`0x96`)。发现过程本身是未经身份验证的 —— LAN 上的任何人都可以枚举每个 Breezy 单元并读取这些参数。
缓解措施在于网络配置而非软件:将这些单元放在无法访问家庭 LAN 其余部分的 IoT VLAN 中,并且只允许运行 `breezyd` 的主机访问该 VLAN。本项目没有在线路协议之上增加加密 —— 这不会改变威胁模型,因为设备固件本身就是以明文响应的。
位于 `GET /` 的 Web 仪表盘与 JSON API 使用相同的监听器。
如果您将 `[daemon].listen` 从默认的回环地址更改为 LAN 地址以便可以从手机访问仪表盘,您也将
把其余的 API —— 包括原始参数写入 —— 暴露给该网络上的任何人。同样的 VLAN 分段建议同样适用:将
运行 `breezyd` 的主机与设备一起放在 IoT VLAN 中,并从一台被临时授予该 VLAN 访问权限的工作站访问仪表盘,而不是将 `breezyd` 绑定到您受信任的 LAN。
## 已知局限
这些是刻意的省略,而不是 bug。每一项都是设计选择;详见规范了解完整的理由。
- **无 WiFi 重新配置。** 从 CLI 更改 WiFi SSID/密码在技术上可行,但在操作上存在风险(一次错误的写入就会导致设备失联)。请使用厂商应用进行此操作。
- **无 MQTT 网桥。** HTTP API 和 Prometheus 指标面板已经覆盖了操作人员迄今为止的所有用例。状态缓存的架构设计使得未来可以在不重写核心的情况下添加网桥。
- **无 Home Assistant 集成。** 理由同上。任何想要 HA 集成的人都可以基于 `/v1/devices/` 构建 REST 传感器或抓取 `/metrics`。
## 开发
想要参与开发 breezyd 本身?请从这里开始。
### 从源代码构建
需要 Go 1.22+(在 1.26 上开发)以及 `templ` CLI。二进制文件本身没有其他系统依赖;竞争检测器配方(`just test-race`)需要一个可用的 C 工具链。
`nix develop` 提供了包括 `templ` 在内的所有先决条件。在 Nix 之外的环境:
```
go install github.com/a-h/templ/cmd/templ@v0.3.x
```
```
just generate # run templ codegen (needed once after checkout or .templ edits)
just build # generate + produces ./breezyd and ./breezy
just check # vet + fast tests + templ-drift (pre-commit gate)
just test-race # full race-detector run (the CI command)
```
`just test-race` 已经设置了 `CGO_ENABLED=1 CC=clang`,因此该配方在默认 `gcc` 缺少 TSan 运行时的开发主机上也能开箱即用。
### 项目结构
```
breezyd/
├── pkg/breezy/ # protocol library (importable)
│ ├── frame.go # FDFD/02 packet codec
│ ├── client.go # UDP transport, retries, timeouts
│ ├── params.go # parameter registry (id, type, R/W, units)
│ ├── values.go # typed value codecs
│ ├── discover.go # LAN broadcast
│ └── fakedevice/ # in-process protocol-speaking fake for tests
├── cmd/breezyd/ # the daemon (HTTP + Prometheus + poller)
│ └── ui/ # templ templates, datastar+dashboard vendor JS, style.css, view.go
├── cmd/breezy/ # the CLI (standalone UDP by default; daemon mode opt-in)
├── cmd/fakedevice/ # build-tagged fakedevice binary with admin HTTP (for Playwright)
├── internal/config/ # TOML config loader, shared by both
├── tools/ # Phase 0 Python probes (one-off, kept for reference)
└── docs/superpowers/specs/ # design doc, parameter map, vendor PDF manual
```
### 测试
```
just test # unit tests (uses fakedevice)
just test-race # same, with -race (the CI command)
just lint # go vet + gofmt-drift check
just check # lint + fast tests (pre-commit gate)
just check-all # check + test-race + Playwright UI suite
```
UI 测试是位于 `tests/ui/` 下的端到端 Playwright 规范,它们会启动一个真实的
`breezyd` 进程,并将其指向 `cmd/fakedevice`(一个带有构建标签的 UDP 模拟设备,具有 HTTP 管理控制平面):
```
just test-ui-install # one-time: pnpm install + chromium download
just test-ui # 82 tests (66 active + 16 fixme), ~20 s
just screenshot # re-render tests/ui/screenshots/*.png
```
使用原始的 `go` 命令运行单个 Go 包或测试:
```
go test ./pkg/breezy/...
go test ./cmd/breezyd -run TestPoller_FanSettle
```
针对真实硬件的实时集成测试同时受 `integration` 构建标签和 `BREEZY_INTEGRATION=1` 环境变量的门控,加上用于标识目标设备的三个环境变量。`just test-integration` 配方封装了所有这些内容:
```
just test-integration 192.168.1.148 BREEZY00000000A0
```
这些测试会对设备进行写入操作 —— 每个测试都会注册一个 `t.Cleanup` 以
恢复先前的值,因此重新运行后设备仍会保持其原始状态。
### 深入文档的指南
- `docs/superpowers/specs/2026-05-03-twinfresh-cli-design.md` —— 完整的 v1 设计文档:协议决策、守护进程架构、错误语义、状态行格式等。
- `docs/superpowers/specs/2026-05-04-basic-ui-design.md` —— Web 仪表盘的设计文档,绑定地址的权衡,以及可选的 NixOS-nginx 反向代理集成。
- `docs/superpowers/specs/2026-05-04-discover-investigation.md` —— 导致 `breezy discover` 失败的两个原因(一个代码缺陷,已修复;以及 QEMU-NAT 环境限制,已记录),并提供了具体的下一步操作。
- `docs/superpowers/specs/2026-05-03-param-map.md` —— 设备暴露的每个参数 ID,包含类型、单位、观察到的值以及阶段 0 特性描述中的注释。
- `docs/superpowers/specs/breezy-manual-vendor.pdf` —— 厂商协议手册,是线路协议的权威参考。已在本地缓存以供离线阅读;官方副本由 Vents 发布在 。
- `docs/superpowers/specs/breezy-datasheet-vendor.pdf` —— 硬件数据手册。官方副本位于 。
## 致谢
如果没有 **Ventilation Systems Ltd. (Vents)** 发布的协议文档,这个项目是不可能实现的。位于 的 Breezy / Breezy Eco 连接指导手册记录了本库实现的完整线路协议、数据包结构、功能码和参数表。阅读该手册确认了(并在某些地方纠正了)在此项目阶段 0 捕获的经验性逆向工程结果。感谢 Vents 公开发布这些文档。
在 `docs/superpowers/specs/` 下提供的手册和数据手册副本仅供方便使用,其版权归 © Vents 所有。请参阅上面的官方 URL 以获取最新版本。
## 许可证
Copyright (C) 2026 Hugh O'Brien
本程序是自由软件:您可以根据自由软件基金会发布的 GNU 通用公共许可证的条款进行重新分发和/或修改,许可证版本为第 3 版或(由您选择的)任何更新版本(`SPDX-License-Identifier: GPL-3.0-or-later`)。
本程序的发布是希望它能有用,但不提供任何保证;甚至没有对适销性或特定用途适用性的默示保证。有关 GNU 通用公共许可证 v3 的全文,请参见 [LICENSE](LICENSE) 文件。
本项目未受 Ventilation Systems Ltd. 赞助或认可。
"Vents" 和 "Twinfresh" 是其各自所有者的商标。
标签:Apple HomeKit, CLI, EVTX分析, Go语言, HRV, IoT, Python安全, SSE, UDP通信, Web仪表盘, WiFi技术, 守护进程, 实时推送, 局域网协议, 局域网控制, 嵌入式Web, 开源硬件控制, 插件系统, 日志审计, 智能家居, 智能家电控制, 本地控制, 桥接, 热回收新风系统, 监控指标, 程序破解, 自定义请求头, 通风设备