浪人
DE|EN
EchoMuse, Part 1: Echo Dot without Alexa - the Infrastructure
tech

EchoMuse, Part 1: Echo Dot without Alexa - the Infrastructure

Back to Blog
4 min read

EchoMuse, Part 1: Echo Dot without Alexa - the Infrastructure

The Starting Point

I have two Echo Dots from the 2nd generation lying around, manufactured in 2016, codename “biscuit”. The microphones, speakers, LED ring, and buttons work perfectly - I just don’t want Alexa and an Amazon account for them anymore. The goal: repurpose the Dots as voice satellites for Home Assistant.

That’s exactly what EchoMuse by wilbowes is for. The unlocking of the Dots is based on amonet-biscuit by R0rt1z2.

Here’s how the pieces fit together:

  • Echo Dot with emOS: its own lightweight Linux instead of Amazon’s Android. The Dot provides microphones, speakers, LED ring, and buttons.
  • EchoMuse Controller as a Docker container on my NUC: wake word detection with openWakeWord (“Hey Jarvis”), device management, and a dashboard.
  • Home Assistant on a Raspberry Pi 4: sees each Dot as a normal ESPHome voice satellite and handles speech recognition, understanding, and voice output via the Assist pipeline.
  • Traefik provides the dashboard via HTTPS, only in the LAN.

Important note upfront: nothing additional needs to be installed in Home Assistant. The built-in ESPHome integration is sufficient.

This article is split into two parts. This one covers everything that needs to be in place before touching the first Dot at all. Part 2 covers unlocking, flashing, and provisioning - with lessons learned from two attempts.

Implementation

Home Assistant: Checking the Assist Pipeline

The only real prerequisite on the HA side is a working Assist pipeline. I checked this in three steps.

View the pipeline (Settings → Voice assistants): My pipeline is called “Local Assistant”, language German, conversation agent Home Assistant. Speech recognition and speech output run via Home Assistant Cloud (Austrian German, voice Jonas). Despite the name, speech goes through Nabu Casa. For the initial setup, I deliberately left it this way because the cloud pipeline is a known quantity. What this means for cloud independence is covered at the end of Part 2.

Assist pipeline in Home Assistant

Expose entities: Assist showed “0 entities exposed”. Without exposed entities, Assist understands but can’t control anything. I started by exposing just one or two test lights.

Exposed entities in Assist

Test: first via text in the Assist dialog (“decke aus” turns off the light), then via voice through the microphone. The browser microphone only works over HTTPS or in the Companion app.

Assist dialog with successful text command

Learned along the way: short commands like “decke ein” are sometimes misinterpreted. Full sentences (“Schalte die Decke ein”) work more reliably.

EchoMuse Controller on the NUC

The controller runs as a ready-made image from the GitHub Container Registry. I placed it alongside my other stacks under ~/docker/echomuse, with the data/ folder in .gitignore.

curl -O https://raw.githubusercontent.com/wilbowes/EchoMuse/main/controller/docker-compose.deploy.yml
curl -o .env https://raw.githubusercontent.com/wilbowes/EchoMuse/main/controller/.env.example
sed -i 's/^SERVER_IP=.*/SERVER_IP=192.168.1.10/' .env
docker compose -f docker-compose.deploy.yml up -d

Three things are critical here:

  • network_mode: host is mandatory. The controller announces itself via mDNS and opens its own ESPHome port for each Dot. Neither works behind Docker port mapping.
  • Set SERVER_IP explicitly. My NUC has many interfaces (Docker networks, WireGuard). Without a fixed IP, the controller might announce the wrong one.
  • Know the ports: 8768 for the dashboard, 8767 and 8770 for Dot connections (plain and TLS), starting at 16001 ESPHome per Dot, starting at 17001 the Bluetooth proxy.

On first startup, the controller writes a setup token once to the log:

docker logs echomuse-controller

On my first check, it was missing because the container was still running database migrations. Wait a few seconds, read the log again, and there it is.

One warning in the log is harmless: zeroconf reports an error on the WireGuard interface (Required key not available). mDNS on the LAN interface still works.

Dashboard via Traefik, LAN Only

Why Traefik at all? The provisioning wizard in the dashboard speaks to the Dot via WebUSB, and Chrome only allows WebUSB in a secure context - that is, HTTPS or localhost. That’s why the dashboard went behind Traefik before the first Dot was even plugged in.

Since the container runs in the host network, Docker labels don’t work the usual way here. Instead, I created an entry in the existing file provider (dynamic.yml):

http:
  routers:
    echomuse:
      rule: "Host(`echomuse.home.example.com`)"
      entryPoints: [websecure]
      tls:
        certResolver: letsencrypt
      service: echomuse
      middlewares: [lan-only]
  services:
    echomuse:
      loadBalancer:
        servers:
          - url: "http://192.168.1.10:8768"
  middlewares:
    lan-only:
      ipAllowList:
        sourceRange: ["192.168.1.0/24", "10.0.0.0/24"]
  • Only the dashboard runs through Traefik. Dots and Home Assistant connect directly to the controller.
  • lan-only is mandatory: The dashboard contains a root shell to every Dot. It shouldn’t be on the internet.
  • The certificate comes via Let’s Encrypt DNS challenge at Hostinger, DNS via my existing wildcard entry *.home.
  • When editing a single mounted file, restart Traefik for safety: docker restart traefik.

I tested from the laptop. Requests from the NUC itself arrive at Traefik with a Docker IP and are correctly rejected by lan-only - a 403 there is not an error, but proof that the middleware works.

Dashboard via HTTPS

Result

The Assist pipeline switches via text and voice, the controller runs in host network mode on the NUC, and the dashboard is accessible via HTTPS from the LAN only - nowhere else. All prerequisites for the provisioning wizard are met.

Pitfalls

Pitfall Symptom Solution
No entities exposed Assist understands but controls nothing Expose entities in Assist, start small
Setup token missing from log Log ends mid-migrations wait a few seconds, read log again
Labels on host network container Traefik can’t reach the service reliably file provider with host IP
Test from NUC itself 403 from Traefik test from laptop, lan-only works correctly

Conclusion

The real effort in this part comes down to three details: exposed entities in Home Assistant, network_mode: host with fixed SERVER_IP, and HTTPS for the dashboard because of WebUSB. Set these up cleanly beforehand, and you can focus entirely on the Dot during flashing.

Part 2 is all about the device: unlock with amonet-biscuit, FireOS 6 into both slots, the wizard on emOS, and integration into Home Assistant.