An iroh powered smart fan

surprisetalk2 pts0 comments

An iroh powered smart fan - Iroh<br>Blog Index<br>If you live in Europe the northern hemisphere, you are probably suffering through a heat wave right now. Let's do something to bring back some chill, using iroh.

The previous ESP32 examples demonstrated echo protocols. But typically ESP32s are used for more than just echoing data; you use an ESP32 as a cheap means to read sensors and drive actuators.

So we are going to write a very simple end-to-end example using an ESP32 to measure temperature and control a fan. Unlike most IoT devices, there won't be any cloud component. Just a tiny website that you can use from anywhere in the world using any browser that supports WebAssembly.

As the base, we are going to use an ESP32-WROVER devkit with 4 MiB of PSRAM, so we have all of iroh's networking capabilities available, including a relay connection, and remote control it from anywhere in the world. You can also use a M5StickC-Plus2, but you will have to adapt the GPIO pins.

:first-child]:mt-0 [&>:last-child]:mb-0">If you have another devkit such as an ESP32-S3 with PSRAM, you should be able to run the examples with small config tweaks.<br>If all you have is an ESP32 without PSRAM, you can still use iroh. But you need to disable the relay connection and tweak QUIC buffers so we don't run out of memory. Use an appropriate example from iroh-esp32-examples as base.

Basic setup

As the first step, we are going to copy over an echo example from iroh-esp32-examples. We will use server-esp32-psram for the ESP32 binary.

For the client side we just use client, it runs on a desktop PC and is as vanilla as it gets.

This is going to be a smart fan example, so we just rename server-esp32-psram to server-smart-fan, and client to smart-fan-cli.

Note that we need different toolchains and want to keep the option to use a patch of iroh for the ESP32 variant, so the two directories are completely separate Rust projects. We do not use a workspace.

Initial state

First flash

Let's try it out once before we do modifications. cargo run on the server project will search for an ESP32 connected via USB and flash it. So we just connect our ESP32 with a USB-C cable.

The initial release build will take some time, since we are compiling not just iroh, but also the operating system to the xtensa architecture. Subsequent builds will be faster, since the compilation results are cached in the .embuild directory.

Flashing itself will never be really fast, because the data rate to the chip is very limited. We can make it go a bit faster by setting the ESPFLASH_BAUD environment variable. My chip supports 230400 baud, but YMMV. If it doesn't work just run without the environment variable set, then it will use safe defaults.

We need to tell the ESP32 how to connect to WLAN. In the example we just use another environment variable WIFI_CONFIG=SSID:PASSWORD. Set this to your local WLAN.

You can do a single export WIFI_CONFIG=SSID:PASSWORD so you don't have to pass it every single time.

ESPFLASH_BAUD=230400 WIFI_CONFIG=myap:mypass cargo run --release<br>❯ cargo run --release<br>Finished `release` profile [optimized] target(s) in 0.56s<br>Running `espflash flash --monitor target/xtensa-esp32-espidf/release/esp32-psram`<br>[2026-07-01T07:14:23Z INFO ] 🚀 A new version of espflash is available: v4.4.0<br>[2026-07-01T07:14:23Z INFO ] Serial port: '/dev/cu.usbserial-210'<br>[2026-07-01T07:14:23Z INFO ] Connecting...<br>[2026-07-01T07:14:30Z INFO ] Using flash stub<br>Chip type: esp32 (revision v3.1)<br>Crystal frequency: 40 MHz<br>Flash size: 4MB<br>Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None<br>MAC address: 00:70:07:19:c8:4c<br>App/part. size: 3,953,296/4,128,768 bytes, 95.75%<br>[00:00:00] [========================================] 17/17 0x1000 Skipped! (checksum matches) [00:00:00] [========================================] 1/1 0x8000 Skipped! (checksum matches) [00:04:11] [========================================] 2295/2295 0x10000 Verifying... OK! [2026-07-01T07:18:43Z INFO ] Flashing has completed!<br>CopyCopied!

As you can see from the flash output, we are pretty close to the limit of the flash size.

App/part. size: 3,953,296/4,128,768 bytes, 95.75%<br>CopyCopied!

You might think that every single added line of code will get you over the limit, but that is not the case. Additional pure Rust dependencies such as irpc add very little size.

Trying it out

What we should have now is a simple echo server running on the ESP32.

Endpoint Id

First of all, how do we assign the endpoint id? We want the ability to assign an endpoint id, but even if we don't do so we want the endpoint id to be stable after reboots. So the ESP32 should not generate a random one on each startup.

Instead we generate and store the secret key in non volatile memory on first startup and reuse it on subsequent startups. Non volatile memory is not overwritten by flashing, so we will get the same endpoint id for the same device unless we explicitly delete non volatile...

esp32 iroh flash psram smart example

Related Articles