浪人
DE|EN
EchoMuse, Part 2: Unlocking, Flashing, and Provisioning Echo Dots
tech

EchoMuse, Part 2: Unlocking, Flashing, and Provisioning Echo Dots

Back to Blog
7 min read

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.

Two 2nd-generation Echo Dots, front and back

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:

  1. Start ./fastbrick.sh in the unpacked amonet folder.
  2. 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.
  3. Type YES. After the 10-second grace period, don’t touch anything.
  4. After about a minute: LED green again, reboot, white light means TWRP.

Echo Dot with green LED in Fastboot mode

fastbrick.sh recognizes the Dot as BISCUIT

fastbrick.sh reports successful exploit

Echo Dot with white LED in TWRP

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

adb devices with Dot in recovery status

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

FTVDB entry with build and MD5 next to md5sum output

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:

First installation to slot B

Second installation to slot 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.

Firefox error message USB is blocked

Wizard start page with emOS selection

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

Wizard Step 1: amonet v2 and FireOS 6 recognized

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.

Wizard Step 3: Escrow of the boot image

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.

emOS boot ring with blue arc and cyan head

Wizard Step 9: WiFi connected, provisioning complete

Integration into Home Assistant

After the wizard, the Dot appears in the dashboard and needs to be approved once.

Dashboard with both Dots and the Approve step

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:

Home Assistant: Adding the Dot, Step 1

Home Assistant: Adding the Dot, Step 2

Home Assistant: Adding the Dot, Step 3

Home Assistant: Adding the Dot, Step 4

Home Assistant: Adding the Dot, Step 5

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.

picocom with password prompt and emOS banner

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.

Activity tab of the first Dot

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