# Devices These are the boards the ESP32 Device SDK runs on. Each board is an `sdkconfig` overlay: a short file of settings loaded on top of [`../sdkconfig.defaults`](../sdkconfig.defaults) that picks the chip, flash, memory, button, and how the device shows its status. The ESP32-C5 DevKitC-1 is the default and needs no overlay. Every board pairs with the Muse app, joins your Wi-Fi, and holds an encrypted session to Muse. The rest depends on the hardware. ## Supported devices | Board | Chip | Display | Flash / PSRAM | Reference | Buy | |---|---|---|---|---|---| | **ESP32-C5 DevKitC-1** | ESP32-C5 | None (RGB status light) | 8 MB / 8 MB | [Espressif docs](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32c5/esp32-c5-devkitc-1/index.html) | [DigiKey](https://www.digikey.com/en/products/result?keywords=ESP32-C5-DevKitC-1) | | **ideaspark ESP32 with 1.9" display** | ESP32 | 1.9" 170×320 LCD | 16 MB / none | — | [Amazon](https://www.amazon.com/s?k=ideaspark+ESP32+1.9+inch+ST7789) | | **Seeed SenseCAP Indicator** | ESP32-S3 | 4" 480×480 LCD | 8 MB / 8 MB | [Seeed wiki](https://wiki.seeedstudio.com/SenseCAP_Indicator_Get_Started/) | [Seeed Studio](https://www.seeedstudio.com/SenseCAP-Indicator-D1-p-5643.html) | | **Seeed reTerminal E1001** | ESP32-S3 | 7.5" 800×480 black and white e-paper | 32 MB / 8 MB | [Seeed wiki](https://wiki.seeedstudio.com/getting_started_with_reterminal_e1001/) | [Seeed Studio](https://www.seeedstudio.com/reTerminal-E1001-p-6534.html) | | **Home Assistant Voice Preview Edition** | ESP32-S3 | None (12-LED ring) | 16 MB / 8 MB | [ESPHome repo](https://github.com/esphome/home-assistant-voice-pe) | [Home Assistant](https://www.home-assistant.io/voice-pe/) | | **Waveshare ESP32-S3-Touch-AMOLED-1.75C** | ESP32-S3 | 1.75" 466×466 round AMOLED, touch | 32 MB / 8 MB | [Waveshare wiki](https://www.waveshare.com/wiki/ESP32-S3-Touch-AMOLED-1.75C), [GitHub](https://github.com/waveshareteam/ESP32-S3-Touch-AMOLED-1.75C) | [Waveshare](https://www.waveshare.com/esp32-s3-touch-amoled-1.75c.htm) | | **Waveshare ESP32-S3-Touch-AMOLED-1.75** | ESP32-S3 | 1.75" 466×466 round AMOLED, touch | 16 MB / 8 MB | [Waveshare wiki](https://www.waveshare.com/wiki/ESP32-S3-Touch-AMOLED-1.75), [GitHub](https://github.com/waveshareteam/ESP32-S3-Touch-AMOLED-1.75) | [Waveshare](https://www.waveshare.com/esp32-s3-touch-amoled-1.75.htm) | | **AIPI Lite** | ESP32-S3 | 128×128 LCD | 16 MB / 8 MB | [xiaozhi-esp32 board](https://github.com/78/xiaozhi-esp32/tree/main/main/boards/xorigin/aipi-lite) | [AliExpress](https://www.aliexpress.com/w/wholesale-aipi-lite.html) | | **Waveshare ESP32-C6-Touch-AMOLED-1.8** | ESP32-C6 | 1.8" 368×448 AMOLED, touch | 16 MB / none | [Waveshare wiki](https://www.waveshare.com/wiki/ESP32-C6-Touch-AMOLED-1.8) | [Waveshare](https://www.waveshare.com/esp32-c6-touch-amoled-1.8.htm) | | **Seeed SenseCAP Watcher** | ESP32-S3 | 1.45" 412×412 round LCD, touch | 32 MB / 8 MB | [Seeed wiki](https://wiki.seeedstudio.com/watcher/), [GitHub](https://github.com/Seeed-Studio/SenseCAP-Watcher-Firmware) | [Seeed Studio](https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html) | | **M5Stack StickS3** | ESP32-S3 | 1.14" 135×240 LCD | 8 MB / 8 MB | [M5Stack docs](https://docs.m5stack.com/en/core/StickS3), [M5Unified](https://github.com/m5stack/M5Unified) | [M5Stack](https://shop.m5stack.com/products/m5sticks3-esp32s3-mini-iot-dev-kit) | | **M5Stack StickC Plus2** | ESP32 | 1.14" 135×240 LCD | 8 MB / 2 MB | [M5Stack docs](https://docs.m5stack.com/en/core/M5StickC%20PLUS2), [M5Unified](https://github.com/m5stack/M5Unified) | [M5Stack](https://shop.m5stack.com/products/m5stickc-plus2-esp32-mini-iot-development-kit) (end of life) | ## Features | | DevKitC-1 | ideaspark | SenseCAP Indicator | reTerminal E1001 | HA Voice PE | Waveshare S3 1.75C | Waveshare S3 1.75 | AIPI Lite | Waveshare C6 1.8 | Watcher | StickS3 | StickC Plus2 | |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | Home-network tunnel | ✅ | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | | Shows status on | Light | Screen | Screen | E-paper | Light ring | Avatar | Avatar | Avatar | Avatar | Avatar | Avatar | Avatar | | Images from Muse | — | ✅ | ✅ | Black and white | — | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | | UI and settings | — | — | — | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Push-to-talk | — | — | — | — | ✅ | ✅ | ✅ | ✅ | Text replies | ✅ | ✅ | ✅ | | Speaker and mic | — | — | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Buzzer and mic | | Air sensors | — | — | D1S, D1Pro | — | — | — | — | — | — | — | — | — | | Touch | — | — | — | — | — | ✅ | ✅ | — | ✅ | ✅ | — | — | | Battery status | — | — | — | — | — | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | Voltage only | | Over-the-air updates | Off | Off | Off | Off | Off | On | On | On | On | On | On | On | | Buttons | BOOT | BOOT | Top | Green | Centre (talk), dial | PWR (talk), BOOT | BOOT (talk), PWR | Two | BOOT (talk), PWR | Wheel (press to talk, turn to sleep) | Front (talk), side (menu), PWR | Front (talk), side (menu), PWR | Boards without PSRAM (the ideaspark and the Waveshare C6) don't have room for the home-network tunnel. Muse can still reach and control them once the control session is up. The Waveshare C6 also can't hold its own voice session, so push-to-talk sends your voice note over its control session to the Muse it's paired with, and the reply scrolls past as text instead of being spoken. It can't show images either: the UI holds a whole image in PSRAM, where the ideaspark draws one straight to its screen. The SenseCAP Indicator's sensors hang off its RP2040, which passes the readings to the ESP32-S3. The D1S and D1Pro have CO2 and tVOC sensors built in, and temperature and humidity come from the Grove AHT20 in the box (plug it in). Muse reads them all at once with `sensors.read`. This needs Seeed's stock RP2040 firmware. The reTerminal E1001's e-paper shows only black and white, 1 bit per pixel, and keeps its picture without power. It shows a still status screen (the agent's name, the character and a line of status text) that changes only when the status does, since each refresh takes a second or two. Images from Muse are dithered to black and white on the device and refresh once they are all in, with a brief black-and-white flash. `display.draw_url` tells Muse the bit depth. The SenseCAP Watcher keeps its factory data (the identity SenseCraft uses) in an `nvsfactory` partition at `0x9000`, where Muse puts its partition table and NVS. It's unique to each Watcher, so back it up before you flash Muse the first time. To return to Seeed's firmware, flash it and then write the backup back: ```sh tools/muse/paced_esptool.py --chip esp32s3 -p PORT read-flash 0x9000 0x32000 nvsfactory.bin tools/muse/paced_esptool.py --chip esp32s3 -p PORT write-flash 0x9000 nvsfactory.bin ``` The Watcher's CH342 USB bridge corrupts reads at 921600 baud and above, so these use esptool's default of 115200. It also drops bytes when a whole packet arrives at once, so plain esptool can't upload its stub or write flash (`0107: Checksum error`, `0105: The format of the received message is invalid`). `tools/muse/paced_esptool.py` takes esptool's arguments and sends 64 bytes at a time at the line rate; `tools/muse/board.sh flash watcher` uses it. The M5Stack StickS3 has 8 MB of flash, so it uses its own partition table with two smaller app slots. The front button is push-to-talk and the side button steps through the menu, as on the AIPI. Powering off from Muse turns off the screen, speaker and codec and puts the ESP32-S3 in deep sleep; either button wakes it. Double-click the power button for a full power-off, and click it to turn back on. Muse turns off the power chip's green LED, which would otherwise stay lit. The IMU, IR and Grove port aren't used yet. [M5Unified](https://github.com/m5stack/M5Unified) is M5Stack's reference driver for the power chip and peripherals. M5Stack ships the StickS3 with UiFlow2, which hands the ESP32-S3's USB to its own driver and switches off the chip's USB serial port, so esptool can't find it. The power chip drives the boot pin (GPIO0), so there's no BOOT button either. To flash Muse the first time, put UiFlow2 in USB mode, open its REPL (for example with `mpremote repl`) and paste: ```python import machine; m = machine.mem32 m[0x600C001C] = m[0x600C001C] | (1 << 10) # USB serial clock on m[0x600C0024] = m[0x600C0024] & ~(1 << 10) # and out of reset m[0x60038018] = 0x4200 # its pads on m[0x60008120] = (m[0x60008120] | (1 << 20)) & ~(1 << 19) # give it the USB pins ``` The REPL stops answering. Unplug the USB cable and plug it back in (the battery keeps the stick running), and it shows up as a USB JTAG/serial port. Anything that resets the stick now boots UiFlow2 again, so pass `--after no-reset` to esptool until Muse is on. Back up the flash, then flash Muse: ```sh python -m esptool --chip esp32s3 -p PORT --after no-reset read-flash 0 0x800000 sticks3.bin tools/muse/board.sh flash sticks3 PORT ``` Muse keeps the USB serial port on, so later flashes need none of this. To go back to UiFlow2, write the backup: ```sh python -m esptool --chip esp32s3 -p PORT write-flash 0 sticks3.bin ``` The M5Stack StickC Plus2 is the only board with the full UI on a classic ESP32. It has 8 MB of flash and 2 MB of PSRAM, so it uses the 8 MB partition table too. The front button is push-to-talk and the side button steps through the menu. The power button wakes the screen, and holding it for 2 s powers off. There's no power chip: the power button switches the stick on and the ESP32 keeps it on (GPIO4). Powering off lets go of that, which cuts the power on battery. On USB the stick stays powered, so the ESP32 also goes into deep sleep, and the front or power button wakes it. Muse speaks through a small passive buzzer, so replies are quiet. The battery shows its voltage, but the stick can't tell Muse whether it's on USB or charging. The IMU, IR, RTC and Grove port aren't used yet. [M5Unified](https://github.com/m5stack/M5Unified) and [M5GFX](https://github.com/m5stack/M5GFX) are M5Stack's reference drivers for the pins and peripherals. The Plus2's console is a CH9102 USB-UART bridge, which drops out above 230400 baud, so `tools/muse/board.sh flash plus2` uses 230400. It comes with M5Stack's factory firmware. Back up the flash before you flash Muse for the first time: ```sh python -m esptool --chip esp32 -p PORT -b 230400 read-flash 0 0x800000 plus2.bin ``` To go back, write the backup with `write-flash 0 plus2.bin`, using the same chip, port and baud. ## Build From the `esp32` directory, load `sdkconfig.defaults` first and then the board's overlays, in order: | Board | Target | Overlays after `sdkconfig.defaults` | Build | |---|---|---|---| | ESP32-C5 DevKitC-1 | `esp32c5` | none | `idf.py build` | | ideaspark ESP32 | `esp32` | [`devices/sdkconfig.ideaspark`](sdkconfig.ideaspark) | `tools/board.sh ideaspark build` | | Seeed SenseCAP Indicator | `esp32s3` | [`devices/sdkconfig.sensecap-indicator`](sdkconfig.sensecap-indicator) | `tools/board.sh sensecap-indicator build` | | Seeed reTerminal E1001 | `esp32s3` | [`devices/sdkconfig.reterminal-e1001`](sdkconfig.reterminal-e1001) | `tools/board.sh reterminal-e1001 build` | | Home Assistant Voice PE | `esp32s3` | [`devices/sdkconfig.home-assistant-voice`](sdkconfig.home-assistant-voice) | `tools/board.sh home-assistant-voice build` | | Waveshare S3 1.75C | `esp32s3` | [`devices/sdkconfig.muse`](sdkconfig.muse), [`devices/sdkconfig.muse-waveshare-s3-175c`](sdkconfig.muse-waveshare-s3-175c) | by hand | | Waveshare S3 1.75 | `esp32s3` | [`devices/sdkconfig.muse`](sdkconfig.muse), [`devices/sdkconfig.muse-waveshare-s3-175`](sdkconfig.muse-waveshare-s3-175) | by hand | | AIPI Lite | `esp32s3` | [`devices/sdkconfig.muse`](sdkconfig.muse), [`devices/sdkconfig.muse-aipi`](sdkconfig.muse-aipi) | by hand | | Waveshare C6 1.8 | `esp32c6` | [`devices/sdkconfig.muse`](sdkconfig.muse), [`devices/sdkconfig.muse-waveshare-c6-18`](sdkconfig.muse-waveshare-c6-18) | by hand | | SenseCAP Watcher | `esp32s3` | [`devices/sdkconfig.muse`](sdkconfig.muse), [`devices/sdkconfig.muse-sensecap-watcher`](sdkconfig.muse-sensecap-watcher) | by hand | | M5Stack StickS3 | `esp32s3` | [`devices/sdkconfig.muse`](sdkconfig.muse), [`devices/sdkconfig.muse-m5stack-sticks3`](sdkconfig.muse-m5stack-sticks3) | by hand | | M5Stack StickC Plus2 | `esp32` | [`devices/sdkconfig.muse`](sdkconfig.muse), [`devices/sdkconfig.muse-m5stack-stickc-plus2`](sdkconfig.muse-m5stack-stickc-plus2) | by hand | `tools/board.sh BOARD [build|flash|monitor|flash-monitor] [PORT]` builds each board in its own `build-` directory. For the boards with the full UI, run `idf.py` with the target and overlays from the table: ```sh idf.py -B build-muse-aipi -DIDF_TARGET=esp32s3 \ -DSDKCONFIG=build-muse-aipi/sdkconfig \ -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;devices/sdkconfig.muse;devices/sdkconfig.muse-aipi" build ``` To flash, add `-p PORT flash` with the same arguments. Boards with the full UI need 16 MB of flash or more, except the StickS3 and StickC Plus2, whose overlays switch to the 8 MB layout in [`partitions_muse_8mb.csv`](../partitions_muse_8mb.csv). [`AGENTS.md`](../AGENTS.md) covers flashing, monitoring, and what to do when a build picks up stale settings. ## Add a board 1. Copy the closest overlay here as `sdkconfig.`. 2. Set at least `CONFIG_IDF_TARGET`, `CONFIG_HOMEHUB_BUTTON_GPIO`, the status backend (`CONFIG_HOMEHUB_LED_BACKEND_*`), and the flash size and mode. 3. Without PSRAM, also turn off `CONFIG_SPIRAM` and `CONFIG_HOMEHUB_TUNNEL` and keep mbedtls in internal memory, as [`sdkconfig.ideaspark`](sdkconfig.ideaspark) does. 4. Build with `SDKCONFIG_DEFAULTS="sdkconfig.defaults;devices/sdkconfig."` and add your board to the tables above. A board with a new kind of display, or one that runs the UI, needs code as well. [`AGENTS.md`](AGENTS.md) walks through every kind of board, from the overlay to testing on hardware. Got it working on something new? Share it in the [Muse Gadgets Discord](https://discord.gg/3bhjCkZdd6). ### Watcher camera Camera support is disabled by default. In the Watcher build's `menuconfig`, under **Muse**, enable **SenseCAP Watcher camera capture and live preview** (`CONFIG_MUSE_WATCHER_CAMERA=y`) and rebuild. It requires PSRAM. Disabled builds omit the camera worker, shutter UI, double-click gesture, and `camera.capture` command. Double-click the wheel to open a live camera view. Aim the Watcher, then tap **TAP TO TAKE PHOTO** or double-click the wheel again. The captured frame stays on screen. Tap the image to return to the avatar. The `camera.capture` command returns a JPEG in `payload.data_base64`, with `payload.format` set to `jpeg-base64`. During live preview it returns the latest frame. Otherwise it requests a fresh frame. Capture runs asynchronously and reports initialization, timeout, or busy errors. The camera uses the existing Himax firmware. It's powered only while a capture or the live view runs (a capture takes under a second, start-up included), and nothing polls it in between. Photo attachments to voice messages are not included.