一款由iroh驱动的智能风扇

Hacker News Top 工具

摘要

本文演示了如何使用ESP32微控制器和iroh网络库构建智能风扇,实现无需云服务的点对点控制。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/11 07:23

# 一款由 iroh 驱动的智能风扇 来源:https://www.iroh.computer/blog/an-iroh-powered-smart-fan 如果你生活在欧洲/北半球,现在很可能正忍受着热浪。让我们用 iroh 做点什么来降降温吧。 之前的 [ESP32 示例](https://github.com/n0-computer/iroh-esp32-examples) 演示了回声协议。但通常 ESP32 的用途远不止回声数据;你可以用 ESP32 作为廉价方案来读取传感器并驱动执行器。 因此,我们将编写一个非常简单的端到端示例,使用 ESP32 测量温度并控制风扇。与大多数物联网设备不同,这里不会有云组件。只有一个微型网站,你可以通过任何支持 WebAssembly 的浏览器从世界任何地方使用。 作为基础,我们将使用一个带有 4 MiB [PSRAM](https://en.wikipedia.org/wiki/Static_random-access_memory#Pseudo_static_RAM_(PSRAM)) 的 [ESP32-WROVER 开发套件](https://store.freenove.com/products/fnk0047),这样我们就可以使用 iroh 的所有网络功能,包括中继连接,并可从世界任何地方远程控制它。你也可以使用 M5StickC-Plus2,但需要调整 GPIO 引脚。 如果你有其他开发套件,例如 **带有** PSRAM 的 [ESP32-S3](https://store.freenove.com/products/fnk0084),通过小幅配置调整应该也能运行这些示例。 如果你只有 **不带** PSRAM 的 ESP32,仍然可以使用 iroh。但你需要禁用中继连接并调整 QUIC 缓冲区,以免内存不足。以 [iroh-esp32-examples](https://github.com/n0-computer/iroh-esp32-examples) 中的适当示例为基础。 第一步,我们将从 [iroh-esp32-examples](https://github.com/n0-computer/iroh-esp32-examples) 复制一个回声示例。对于 ESP32 二进制文件,我们将使用 `server-esp32-psram`。 对于客户端,我们直接使用 `client`,它在台式 PC 上运行,并且是最普通的版本。 这是一个智能风扇示例,所以我们只需将 `server-esp32-psram` 重命名为 `server-smart-fan`,将 `client` 重命名为 `smart-fan-cli`。 注意,我们需要不同的工具链,并且希望保留对 ESP32 变体使用 iroh 补丁的选项,因此这两个目录是完全独立的 Rust 项目。我们不使用工作区。 [初始状态](https://github.com/n0-computer/iroh-smart-fan/tree/d16fee122f21076872724b45f1773f9a7908d39a) 在进行修改之前,我们先尝试运行一次。在服务器项目上运行 `cargo run` 将搜索通过 USB 连接的 ESP32 并刷写它。因此,我们只需用 USB-C 线缆连接 ESP32。 首次 release 构建需要一些时间,因为我们不仅要编译 iroh,还要将操作系统编译到 xtensa 架构。后续构建会更快,因为编译结果会缓存在 `.embuild` 目录中。 刷写本身永远不会很快,因为芯片的数据速率非常有限。通过设置 `ESPFLASH_BAUD` 环境变量,我们可以让它更快一些。我的芯片支持 230400 波特率,但你的情况可能不同。如果不起作用,只需在不设置环境变量的情况下运行,它将使用安全默认值。 我们需要告诉 ESP32 如何连接到 WLAN。在示例中,我们只需使用另一个环境变量 `WIFI_CONFIG=SSID:PASSWORD`。将其设置为你当地的 WLAN。 你可以执行 `export WIFI_CONFIG=SSID:PASSWORD`,这样就不必每次都传递了。 从刷写输出可以看出,我们离闪存大小的极限已经非常接近。 你可能会认为每增加一行代码都会超出限制,但事实并非如此。额外的纯 Rust 依赖项(如 [irpc](https://docs.rs/irpc/latest/irpc/))增加的大小非常小。 我们现在应该有一个在 ESP32 上运行的简单回声服务器。 ### 端点 ID 首先,我们如何分配 [端点 ID](https://docs.iroh.computer/concepts/endpoints#endpoint-identifiers)?我们希望能够分配端点 ID,但即使我们不这样做,也希望端点 ID 在重启后保持稳定。因此,ESP32 **不应该** 在每次启动时生成一个随机 ID。 相反,我们将在首次启动时生成密钥并将其存储在非易失性存储器中,并在后续启动时重用。非易失性存储器不会被刷写覆盖,因此对于同一设备,我们将获得相同的端点 ID,除非我们显式删除非易失性存储器。 ### 启动 启动时,设备将尝试使用给定的凭据连接到 WiFi。如果连接失败,它将挂起。这发生在任何 iroh 端点设置之前。 对于实际产品,你可能需要两个备用的 WiFi 配置和一些恢复选项,但在这个示例中我们将跳过这一点。 一旦 ESP32 上的端点启动,我们会看到非常熟悉的输出: 设备已由路由器的 DHCP 分配了本地 IP 地址 `192.168.0.186:51831`。它打印了长票证和短票证,但目前仅使用包含 IP 地址的长票证进行本地可达。 接下来,它尝试使用 [QAD](https://www.iroh.computer/blog/qad) 确定其在世界上的位置。 假设你已连接到互联网,稍后它会找出最近的中继,并将其设置为家庭中继。 此时,使用仅包含端点 ID 的短票证,它可以从世界任何地方访问。 ### 连接 现在让我们使用客户端二进制文件尝试一下。 在本地,你可以使用长票证并绕过中继,但一旦端点发布了其家庭中继,它就应该可以全局访问。 客户端有一个禁用中继的选项。如果这样做,你将只能使用长票证。 它还具有 [mDNS](https://en.wikipedia.org/wiki/Multicast_DNS) 选项,但我们不会在此项目中使用 mDNS。 ### 关机 你可能会认为停止 `cargo run --release` 会停止二进制文件。但事实并非如此。它只是停止与设备的 **连接**。只要设备有电,端点就会愉快地继续运行。 你甚至可以断开它并插入单独的 USB-C 电源,它将以相同的端点 ID 再次启动。这就是关键所在。ESP32 是一个完全自包含的嵌入式计算机。它只需要电源。 如果你真的想关闭它,请拔掉电源或使用 `espflash erase-flash` 擦除闪存。 现在我们已经确认示例可以工作,我们可以开始让它真正做点什么了。 由于我们想要构建一个智能风扇,首先需要的是一个温度传感器。我们将使用 DHT22 温湿度传感器。 一个 DHT22 温湿度传感器。图片由 [L293D](https://commons.wikimedia.org/wiki/File:DHT_22_Sensor.jpg) 提供,[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/),来自维基共享资源。 连接传感器非常简单。它有三根线,两根用于 +3.3V **(不要使用 +5V!)** 和 GND,一根用于数据。 如果你使用带扩展板的 ESP32 开发套件和 [面包板](https://en.wikipedia.org/wiki/Breadboard),它将从 USB 端口为面包板导轨提供 +3.3V 和 +5V 电源。在没有外部电源的情况下,你只能拉取约 100 mA 电流,但这对于 DHT22 来说足够了,它在测量时仅消耗 1.5 mA,空闲时更少。 我们需要将中间线连接到 ESP32 的众多 GPIO 端口之一。我们将选择 GPIO 26,但几乎所有的 GPIO 都可以用于此。某些 GPIO 在启动时有特殊功能,但 GPIO26 没有。 为了测试传感器,我们将使用 tracing 打印传感器读数。 ### 运行它 故障排除: 确保正确连接了 +3.3V 和 GND。如果没有,你会注意到传感器变热,并且在冒烟之前只有几秒钟的反应时间。 DHT22 应该可以直接将信号线连接到 GPIO 26 工作。但如果你频繁遇到超时,可以尝试添加一个 3.3 kΩ 的上拉电阻,将 GPIO 连接到 +3.3V 导轨。**不要连接到 +5V 导轨!** 不要害怕出错。DHT22 和 ESP32 都很坚固,如果你快速纠正错误,它们对接线错误相当宽容! [添加传感器读数的提交](https://github.com/n0-computer/iroh-smart-fan/commit/aae5ced530dfc5e3e995719ad0d80d70640efc18) 此时,我们有一个支持回声协议的 iroh 端点,以及本地传感器读数。显然,我们也希望远程读取传感器。 为此,我们将使用 [irpc](https://docs.rs/irpc/latest/irpc/)。如果我们只想要读取单个传感器,这可能有点大材小用。但使用 irpc 将使将来扩展协议变得更容易。 ### 协议 crate 我们将在一个单独的 crate `smart-fan-proto` 中定义协议,因为它将被客户端和 ESP32 本身使用。 第一个 RPC 调用将只是读取当前的传感器值。目前这只是一个温度和湿度,但将来可能会有更多。因此,我们将使用一个传感器状态结构体。 以下是完整的协议定义: ### 服务器端 在服务器端,我们有一个结构体,它在一个互斥锁中携带当前的传感器状态: ### 客户端 将来我们会添加更多,但目前客户端只进行一次读取。 ### 我们还能保持简单吗? 也许我们仍然想要一种简单的方法来检查端点是否在线。当然,我们可以向 irpc 协议添加一个虚拟端点,但我们也可以继续支持回声协议。 在使用 [router](https://docs.rs/iroh/latest/iroh/protocol/struct.Router.html) 时,你可以组合任意多的协议! [添加单独协议 crate 的提交](https://github.com/n0-computer/iroh-smart-fan/commit/83ee4359775b5376afcf0a61fbccd17ab77a68b6) CLI 工具对于调试很好,但我们真正想要的是一个 GUI 来显示温度,并最终控制风扇。我们可以使用 [dioxus](https://dioxuslabs.com/) 编写一个在所有主要平台上工作的原生 GUI。但谁愿意为此安装一个应用程序呢?所以让我们做一个在浏览器中运行的 WASM GUI。 我不是 JavaScript 开发者,所以这个 WASM GUI 是 vibe coded 出来的。我只是简单检查了一下。 第一个版本只是一个远程温度计:粘贴你设备的票证,通过中继实时查看其温度和湿度。 要运行 GUI,进入 `smart-fan-wasm` 并运行 `npm run build; npm run serve`,然后在 `http://localhost:8080` 打开 GUI。但 **你不必这样做!** 这篇博客文章中的 GUI 是实时的,你可以直接粘贴票证并尝试。 [添加 WebAssembly GUI 的提交](https://github.com/n0-computer/iroh-smart-fan/commit/f31695de041aa6d3f0f47e1612efe892f4b73615#diff-1407a3c103152b8c2e47b76b36d350b24906cd2501aeafa4b17d803adc56a3dd) 此时,我们有了一个远程可访问的温度和湿度计。但我们想要一个智能风扇。所以让我们添加一个输出。我们将使用一个 5V 的台式电脑风扇,例如 [Noctua NF-A14-5V](https://www.noctua.at/en/products/nf-a14-5v-pwm)。它连接到 +5V 和 GND 导轨,并有一个单独的 PWM 控制线来开关或调节风扇。 如果你没有这样的风扇,你也可以只连接一个带 ~330 Ω 电阻的 LED,或者连接一个继电器来控制家用风扇。不过,用真正的风扇会更有趣! 执行器将使用简单的逻辑控制:如果温度高于某个值,则打开风扇。我们添加一点粘性,这样风扇就不会频繁开关。 然后设置实际的引脚: 到目前为止,一切顺利。 [添加输出开关的提交](https://github.com/n0-computer/iroh-smart-fan/commit/f671f6ca2cf18eb92afb81ebb3ed0556c233f48b) 此时主要组件已经就位,我停止了保持提交历史的整洁。 在开发过程中,你可以随意更改协议,并确保客户端和服务器都是最新的。 对于新的生产部署,我们只需更改 ALPN,以表明这是一个新协议。 但是,如果我们希望旧的远程温度计 GUI 仍然可以工作呢?在这种情况下,我们必须小心,**只添加** 新的方法到 RPC 协议,并将当前方法 **保留在枚举中的相同位置**,使用相同的结构。Irpc 使用 [postcard](https://docs.rs/postcard/latest/postcard/),与 json 或 protobuf 不同,postcard 完全不自描述。尝试读取与写入时不同的结构将失败或产生奇怪的结果。 我们仍然可以通过在协议枚举的 **末尾添加** 新的 RPC 方法来演进协议,而无需更改 ALPN。这样,我们可以保留 ALPN,旧版本的 GUI 将继续工作。 我们有更原则性的模式演进方法,`irpc-schema`,但这将是另一篇博客文章的主题。 所以这是我们的新兼容模式枚举: 只读 GUI 只使用 `GetStatus`:它显示温度、湿度和风扇是否运行,但不能更改任何内容,因此可以安全地公开共享。它与上面的温度计页面相同,只是与更新的协议通信: 我们现在希望能够设置风扇开始工作的阈值。但我们仍然希望智能风扇 GUI 能够托管在公共网站上。我们可以依赖端点 ID 的秘密性,但这并不是一个好主意。如果我们使用发现功能,数据会为端点 ID 发布在 dns.iroh.link 上。此外,我们可能希望保留共享风扇状态只读视图的能力。 因此,让我们扩展 RPC 协议,添加一个设置阈值的调用,但要添加一些身份验证。我们将使用一个简单的密钥,它被嵌入到代码中,然后仅在新 RPC 调用中使用。 ### 添加的 RPC 方法 ### GUI 添加 对于新的 GUI,我们只需添加一个滑块,显示当前阈值,并可用于更改。 在这里,它在浏览器中作为 WebAssembly 实时运行。粘贴你设备的票证进行连接,并可选择输入其 `FAN_API_SECRET` 以解锁阈值滑块: 到目前为止,我们有了一个面包板,上面有 ESP32、一个 LED 或风扇,以及一个 DHT22 传感器。在许多情况下,这类项目就到此为止了——你确认它工作正常,然后在桌上放几天,然后拆解它,因为你需要零件进行下一个有趣的项目,或者因为你想清理。 在这种情况下,我想将零件组装成一个可用的小装置。所以我设计了一个外壳来容纳风扇和面包板。 [外壳模型在 Bambu Studio 中加载](https://www.iroh.computer/blog/an-iroh-powered-smart-fan/case.stl) 外壳在 Bambu Studio 中。 [下载 STL](https://www.iroh.computer/blog/an-iroh-powered-smart-fan/case.stl) 我没有费心使用焊接的试验板或定制 PCB。这是最终结果: [组装好的智能风扇:一个 Noctua NF-A14 5V PWM 风扇在 3D 打印外壳中,立在木地板上,有一个传感器通风口](https://www.iroh.computer/blog/an-iroh-powered-smart-fan/fan_final.jpg) 成品风扇:Noctua NF-A14 在打印外壳中,为 DHT22 开了个口。 [ESP32 开发套件在面包板上,连接到 DHT22 传感器和外接天线,安装在 3D 打印外壳的底座中](https://www.iroh.computer/blog/an-iroh-powered-smart-fan/electronics.jpg) 电子元件——面包板上的 ESP32、连接的 DHT22 传感器和风扇——粘在底座里。 我使用了 ESP32-S3,因为我有 spare,并且更改了 GPIO 引脚以简化接线。使用 ESP32-S3,你只能接触到面包板的一侧。 我在外面放了一个 QR 码,可以打开只读的 Web UI,里面放了一个 QR 码,可以打开带有控制温度阈值密钥的 Web UI。 QR 码被模糊处理了,但如果你设法用某种花哨的反卷积算法恢复原始图像,你就可以控制我的风扇,直到我更改端点 ID。 [完整代码](https://github.com/n0-computer/iroh-smart-fan)

相似文章

破解我的云控吊扇的射频遥控

Hacker News Top

一位开发者对Dreo云控吊扇的射频遥控器进行逆向工程,以实现通过Home Assistant的本地控制,从而绕过云依赖,并记录了解码和重放命令的过程。

n0-computer/iroh

GitHub Trending (daily)

Iroh 是一个用于点对点网络的 Rust 库,它通过公钥简化端点连接,使用打洞技术和 QUIC 并配备后备中继服务器,还提供了诸如 blob、gossip 和 docs 等可组合协议。

SwitchBot 站立循环扇值得拥有

The Verge

对 SwitchBot 新款电池供电的站立循环扇的评测,重点介绍了其多功能性、静音运行和智能家居集成,但指出其 Matter 支持有限且对大房间的降温效果不足。