Skip to content

Boards: the Echo Dot 3rd gen (donut), and codec state as board data - #1033

Merged
wilbowes merged 1 commit into
mainfrom
feat/donut-profile
Oct 24, 2026
Merged

wilbowes merged 1 commit into
mainfrom
feat/donut-profile

Conversation

@wilbowes

Copy link
Copy Markdown
Owner

The Echo Dot 3rd gen (donut) is now a known board and runs as a speaker; its mics come next. Read off one unit (41C8, stock FireOS 6.5.7.5, NS6575). It is an MT8167 board with its own layout, so what was still biscuit's inside the drivers is now board data in pkg/board, not branches:

  • Codec state per board. Routes, mic input levels, the amp switch, the hardware volume (with its mute and 0dB values) and the jack table. The Dot 3's TAS2770 has no amp switch and sits at unity at 255, where biscuit's 127 would be -64dB. Its values come from the unit's own mixer_paths.xml.
  • Buttons as a list, a key means its code. The Dot 3 carries action and volume on one device and mute on another. Press state is now per key, and an evdev autorepeat no longer reads as a release: the Dot 3's keys autorepeat, and the old loop would have ended every hold, pairing included, at the first repeat.
  • Two fixes for every FireOS 6 Echo. Amazon's button service is acebuttond there and was never stopped (radar included). And the light sensor accepts IIO's decimal lux.

A board whose mics the firmware can't read runs as a speaker rather than exiting into the start script's rollback. That is the Dot 3 until its 4-channel capture is supported. It announces no mic, reports listening as degraded ("speaker only") in every mode on both halves, gets no voice turn from the button (an error cue instead; holds and tap events still reach HA), and gets no listen loop or wake word install. em_controller.py's size baseline rises by those 22 lines.

The controller shows the board as "Echo Dot Gen 3 (donut)" in Home Assistant.

Rebuilt to this design with reference to @shortgame11's Dot 3 port (GPIO 444 as an audio pin, the speaker route set at stream open), and @technotiger's note on #527 that the radio behind /dev/stpbt reads differently. Co-authored.

Tested

  • 41C8 on a bench controller: every part found by name; volume beeps, LED ring orientation, action tap (error cue, no turn), a 7s hold (one release, pairing request), mute, and jack routing both ways, all by hand; light sensor 62 lux against 62.404.
  • VVV (Dot 2, FireOS 5) on this binary in place of its service: wake word 0.892 into a session, volume steps, tap into a turn, a 9.25s hold forwarded to HA, mute.
  • Host: device tests and go vet, the ARM server build, the full controller suite (2239). Mutation checks on each new rule.
  • Not checked: sound out of the Dot 3's jack (the routing switches; nobody listened), and server board on the emOS Dot 2s, whose part names are the same as VVV's.

Next: the Dot 3's capture (4 mics, one per corner, with the playback loopback on its own PCM as the echo reference), then emOS if its bootloader allows it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BofN7aQbgNvVKNVXdGNR2f

Registers donut (A32DOYMUN6DTXA, read off 41C8, FireOS 6.5.7.5 NS6575) and
moves what was still biscuit's inside the drivers into pkg/board, so a board
that differs is data, not a branch:

- Routes and InputGains: codec.EnsureRoutes writes the board's. biscuit and
  radar share tlvRoutes/tlvInputGains (moved from the codec package with
  their measurements); donut's come from its own mixer_paths.xml.
- Speaker.AmpSwitch, Speaker.Volume (control, mute, unity) and Speaker.Jack
  (insert/remove writes): the speaker's Init, Close, jack routing and drift
  reconcile read them. The TAS2770 has no amp switch and unity at 255;
  biscuit's 127 there is -64dB. The jack reconcile reads back whatever the
  board's table writes.
- Keys is a list of input devices and a key means its code: biscuit splits
  action+mute from volume, donut action+volume from mute. Press state is per
  key and an evdev autorepeat (value 2) is ignored: donut's gpio-keys
  autorepeats, and the old loop read the first repeat as a release, which
  would end every hold (pairing is a 7s hold).
- Amazon's button service is acebuttond on FireOS 6, so stop both names;
  radar on FireOS 6 never had it stopped.
- Lux reads the IIO "<int>.<micro>" form (tsl2572 reads "3.636000"), held
  to that shape rather than whatever ParseFloat accepts.

donut's mics (TDM_Capture, 4ch S32_LE, loopback on its own DL1_AWB_Record)
are not readable by the capture path yet, so the board states no capture.
A device with no readable mic now runs as a speaker instead of exiting into
start_server.sh's rollback: no "mic" capability, listen_state degraded with
the reason in every mode (the controller's em_listen agrees from the missing
capability, so controller mode no longer reads as streaming), no voice turn
from the button (em_button.NO_MIC: error cue; holds and tap events still go
to HA), no listen loop or mic_start, and no wake word runtime install.
em_controller.py's size baseline rises by those 22 lines.

Checked on 41C8 against an isolated bench controller: every part found by
name, DL1 RUNNING at 48k/2048 with mixer, acebuttond and ledcontroller
stopped, pin 57 still in its audio function, lux 62 against 62.404, listen
reported "degraded — speaker only". Not yet heard, seen or pressed: the
speaker, LED orientation, buttons and the jack.

Reference: @shortgame11's Dot 3 port (GPIO 444 as an audio pin, the speaker
route decided at stream open) and @technotiger's note on #527 that the
radio behind /dev/stpbt reads differently; rebuilt to the board model here.

Co-authored-by: shortgame11 <35879721+shortgame11@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BofN7aQbgNvVKNVXdGNR2f
@wilbowes
wilbowes merged commit bb42aed into main Oct 24, 2026
13 checks passed
wilbowes added a commit that referenced this pull request Oct 26, 2026
…icrophones) (#1041)

- JOURNAL: the donut's bootloader (199-byte cmdline, ramdisk only in
  recovery) and the image that boots it; the cache superblock emOS's trail
  was destroying; the warm-reboot hang traced to the unclaimed ring; the
  donut hearing its own wake word; its stock sound read and swept; the open
  no-WiFi-after-emOS fault.
- CLAUDE.md: devcheck, so nine emOS off-target checks, in the quickref and
  the release and CI wording.
- device/CLAUDE.md: the capture as board data (MicArray, Loopback opened
  first, MicMutes, RingLED0), the donut's microphone and ring geometry, and
  what is still biscuit's inside the drivers.


Claude-Session: https://claude.ai/code/session_01XMzTzy1gEcUkKfAD2QDkL5

Co-authored-by: Wil Bowes <wil@presence.run>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant