
EchoMuse, Part 2: Unlocking, Flashing, and Provisioning Echo Dots
EchoMuse, Part 2: Unlocking, Flashing, and Provisioning Echo Dots
Starting Point
Part 1 covered everything that needs to happen before the first flash: the Assist pipeline in Home Assistant, the EchoMuse controller on the NUC, and the dashboard via HTTPS behind Traefik. Now it’s time to work on the device itself.
I’ve done this twice. The first Dot got running after a few detours, the second one was done for this article - the images come from this second run. Where the two differ, I’ve noted it.

Before we start, a word on versions since it cost me the most time initially: The EchoMuse documentation is contradictory here. The rooting guide and emOS README still demand amonet v1.1.0 and FireOS 5, while the main README says both versions work. According to r/echomuse, the docs are outdated, and the provisioning wizard explicitly recognized and supported amonet v2.0.0 with FireOS 6 for me. In the XDA thread, anyway, only v2.0.0 is attached anymore.
v2.0.0 brings a real advantage: a USBDL emergency mode (hold Mute button while plugging in) to recover a bricked Dot without opening the case. But there’s also a downside to be aware of: Never go back from v2.0.0 to FireOS 5 or older amonet. This can permanently brick the Dot.
Implementation
Unlock with amonet-biscuit
The unlock runs on Ubuntu via Fastboot (Option 1 in the XDA thread), without opening the case. Preparation on the laptop:
sudo apt install adb fastboot python3 python3-serial dos2unix
sudo usermod -aG dialout $USER
sudo systemctl stop ModemManager
echo -1 | sudo tee /sys/module/usbcore/parameters/autosuspend
Three conditions apply: laptop on AC power, cable connected directly to the laptop (no hub), and a real data cable, not just a charging cable.
The process:
- Start
./fastbrick.shin the unpacked amonet folder. - On the Dot: power off, hold the Action button (•), plug the USB cable from the laptop in and keep holding until the LED turns green.
- Type
YES. After the 10-second grace period, don’t touch anything. - After about a minute: LED green again, reboot, white light means TWRP.




To verify, adb devices now shows the Dot as recovery:

Both Dots got unlocked successfully on the first try.
FireOS 6 into Both Slots
After unlocking, the Dot needs a bootable FireOS 6, and in both A/B slots. I used Fire OS 6574.1 (NS65741/8146), metadata on FTVDB, download directly from Amazon’s CDN. Before flashing, verify the checksum - it takes seconds and saves guesswork:
md5sum update.zip
# expected: 8ca06ee4ef2806c974d2944b7fadd543

The slot trap from the first run: With the first Dot, I followed the command sequence from the XDA thread - and wrote to slot A twice. Both installations reported:
Flashing A/B zip to inactive slot: A
The second run showed the root cause clearly: TWRP always flashes to the inactive slot and then activates it. The bcbtool switch command from the thread reverses this - and TWRP uses the same slot again next time. Switching isn’t necessary at all. A recovery reboot is enough for the second slot:
adb shell twrp wipe cache
adb shell twrp wipe data
adb push update.zip /sdcard/update.zip
adb shell bcbtool get_active # a
adb shell twrp install /sdcard/update.zip # → inactive slot: B
adb shell bcbtool get_active # b
adb reboot recovery
adb shell bcbtool get_active # b
adb shell twrp install /sdcard/update.zip # → inactive slot: A
With the second Dot, slot B was written first, then A:


Immediately after adb reboot recovery, adb devices is briefly empty - after a few seconds the Dot is back. My tip: After each installation, read the line inactive slot: ..., not just wait for “Done”. At the end, both A and B should appear once each. The boot-root.zip from the thread isn’t needed for EchoMuse, by the way.
Provisioning Wizard on emOS
The wizard in the dashboard handles the rest, starting from the Dot in TWRP. I chose emOS - for FireOS 6, it’s the only option.
Two things first on the laptop:
- Run
adb kill-server. Otherwise, the local ADB service holds the USB connection, and the USB selection in the wizard stays empty. - Use Chrome or Edge. Firefox lacks WebUSB and gives a misleading “USB is blocked … not a secure address” message.


The wizard works through nine steps:
| Step | What Happens |
|---|---|
| 1 Connect Device | Dot “Echo” in USB selection, recognizes amonet v2 and FireOS 6 |
| 2 Connect to TWRP | Connection is reused |
| 3 Escrow Boot Image | Checks both slots, backs up slot A (16 MB) as a file to the laptop |
| 4 Install EchoMuse | Firmware from GitHub to /data, including TLS credentials |
| 5 Wake Word Assets | ONNX runtime and models (~15 MB) |
| 6 Build emOS | Controller builds emOS from its own kernel |
| 7 Flash and Verify | emOS in slot A, read back and verified |
| 8 Reboot and Watch | emOS v0.10 boots, monitored via serial console |
| 9 Configure WiFi | WiFi scan, connection, registration with controller |

Step 3 deserves special attention. The escrow file is the way back: restoring takes about ten seconds, and the second slot keeps an untouched FireOS 6 as well. Comparing is interesting: the escrow files from both Dots are byte-identical, the MD5 is the same. With the same FireOS build, the boot image is apparently the same. I still save them per Dot (escrow-dot1.img, escrow-dot2.img) and store them in two places immediately.

The wizard doesn’t care about the active slot either: with the second Dot it was on B after the FireOS flash. The wizard flashes emOS to slot A anyway and then switches to A itself.
In step 8, I tripped over my own impatience with the second Dot: “Failed to open serial port”. The Dot only appears as “emOS” in the port selection once the boot ring starts to fill. So wait until the ring is running, then connect.


Integration into Home Assistant
After the wizard, the Dot appears in the dashboard and needs to be approved once.

In Home Assistant, the Dot is added via the built-in ESPHome integration. It’s discovered automatically, or manually with the controller IP and the Dot’s port (for the first Dot 192.168.1.10, port 16001). Then assign the “Local Assistant” pipeline. “Hey Jarvis” runs as the wake word with a threshold of 0.5.
Here are the setup steps:





Console Password
emOS provides a root shell on the USB port - without a password. With the first Dot, I added this afterward fleet-wide via the dashboard: gear → Config → Advanced → USB console. The controller hashes the password and pushes it to all connected Dots, including future ones. The password itself lives in Vaultwarden and isn’t used anywhere else.
With the second Dot, I saw how this works in detail: wizard step 4 deletes any existing console password, and the controller sets a new one after approval. But the console shell exists already during the wizard in step 8 - before the password is pushed. It remains unprotected until it’s exited or the Dot reboots. With both Dots, the password prompt came only after an exit.
My tip: After setup, run exit in the console or reboot the Dot once. Only then is the shell truly protected.
Testing from the laptop with the Dot via USB:
picocom /dev/ttyACM0
picocom is the right tool here because it opens the connection raw and without echo. According to emOS docs, the console seems to work with echo but executes nothing. The displayed baud rate doesn’t matter for the USB console.

There’s no lockout risk: wrong entries just slow things down, there’s intentionally no hard lockout, and the password file can always be deleted via TWRP. Only the shell is protected; boot messages remain readable.
Result
Both Dots run as voice satellites in Home Assistant, with emOS v0.10 and firmware v2.17.0.
The first Dot has accumulated some everyday experience. The Activity tab shows 33 runs with a success rate of 67% and a median response time of 3.8 seconds from wake word to complete answer - with speech recognition and synthesis via the cloud. There were no near-misses or audio dropouts.

Noticeable are five “NO SPEECH” entries, four of them at night between three and half past six. The wake word triggered, nothing was spoken, and after about 5.4 seconds the pipeline ended itself. These are genuine false positives. The threshold is currently at 0.5 - there’s still tuning needed here.
To wrap up, I secured the connection between Dots and controller. Since step 4 of the wizard, the Dots have had TLS credentials, and both show “wss (TLS)” in the dashboard. This lets the controller reject unencrypted connections:
sed -i 's/^REQUIRE_DEVICE_TLS=.*/REQUIRE_DEVICE_TLS=1/' .env
docker compose -f docker-compose.deploy.yml up -d --force-recreate
Both Dots continue to connect afterward, unencrypted connections are rejected.
Conclusion
Amazon is completely out of the picture: no account, no Alexa, no Amazon software on the Dots. The wake word runs locally on the controller on the NUC, device control happens locally in Home Assistant’s conversation agent.
However, the setup is not yet completely cloud-independent. Speech recognition and synthesis run via Home Assistant Cloud - everything spoken after “Hey Jarvis” goes to Nabu Casa for processing. The nighttime false positives add a privacy dimension. The cloud pipeline was deliberately chosen for setup because it’s a known quantity. Switching to a completely local pipeline with Whisper and Piper is its own topic for later.
The second Dot went much more smoothly than the first - almost everything I did differently is in the box below.
Pitfalls
| Pitfall | Symptom | Solution |
|---|---|---|
| Contradictory docs on amonet | v1.1.0 vs v2.0.0, FireOS 5 vs 6 | Ask the community (r/echomuse, Discord), the wizard checks itself |
| Slot trap during FireOS flash | twice “inactive slot: A” | don’t switch with bcbtool, just adb reboot recovery between installations |
| Dot gone after recovery reboot | adb devices briefly empty |
wait a few seconds |
| Firefox | “USB is blocked … not a secure address” | use Chrome or Edge |
| ADB service still running | USB selection in wizard empty | run adb kill-server |
| Wizard step 8 too early | “Failed to open serial port” | wait until the boot ring fills and “emOS” appears in the port selection |
| Console in use | “Device or resource busy” on /dev/ttyACM0 |
close dashboard tab, sudo fuser -v /dev/ttyACM0 shows who holds it |
| No password prompt | shell still from wizard | exit or reboot Dot |