Show HN: Smart Mouth Billy Bass – another fishy LLM assistant Developer ryandrake08 published a Show HN project called Smart Mouth Billy Bass, a local-only LLM assistant built into a Big Mouth Billy Bass toy, with code, schematic, and PCB layout on GitHub. The project was inspired by a prior Show HN post called BillAI Bass and aims for zero internet dependencies, LAN or on-device functionality only, original toy battery power, and no visible physical modifications to the chassis. The build involved a TFLite wake-word detector, ESP32 development, I²S devices, a MEMS microphone, KiCad, ideal diode controllers and MOSFETs, and SMT reflow soldering. Since it matters to some people: This project was executed with some AI assistance. If you don't like this, feel free to stop reading. This writeup was human-written with little LLM involvement initial outline and formatting . The image above is pure slop. Sorry, I'm not an artist. | Software design and code | Hardware design and PCB layout | README / Documentation | |---|---|---| This project was inspired by a Show HN post called BillAI Bass https://news.ycombinator.com/item?id=48896599 where someone took a Big Mouth Billy Bass toy and shoved a Raspberry Pi in it to turn it into an AI assistant. I thought to myself, wow, this is cool, but can I improve on it? Thus began a months-long project to see if I could. I also learned a few things along the way, including GPU setup in an LXC container, LLM model applications, small-model training for a TFLite wake-word detector, ESP32 development, I²S devices, acoustic transmission to a MEMS microphone, KiCad, ideal diode controllers and MOSFETs, SMT reflow soldering, and more If you want to skip to the end result, here's a video demo-video , further down this page. If you want to skip to the code, schematic, and PCB layout: GitHub https://github.com/ryandrake08/billy The first step was to see what else was out there. Is there other prior art that can be built upon? Did someone else do the exact thing I was trying to build? Are people rage quitting these projects half way through? Besides the above Show HN project, there are quite a few other success stories to build upon: Someone used an ESP32 and custom chassis to integrate the toy with Home Assistant https://ciangallagher.me/2025/10/04/Turning-A-Singing-Fish-Into-A-SMart-AI-Assistant/ , closest to what I had in mind on the hardware side, but it leans on OpenAI's API for the actual conversation. I kind of want to not lean on any cloud AI providers for this thing. There is a Raspberry Pi / Arduino project that bundles everything together as a local AI assistant https://hackaday.io/project/201939-big-mouth-billy-bass-offline-ai . And some other Arduino/Assistant hacks: an early write-up on hacking the toy https://automateeverythingsite.wordpress.com/2016/11/20/hacking-big-mouth-billy-bass-part-13/ , a piece on animating the mouth from any audio source https://www.hackster.io/donald/animate-a-billy-bass-mouth-with-any-audio-source-c2b029 , a Bluetooth-audio variant https://maker.pro/arduino/projects/how-to-animate-billy-bass-with-bluetooth-audio-source , and an Alexa-hack forum thread https://forum.arduino.cc/t/help-wanted-for-big-mouth-billy-bass-alexa-hack/1361748 . All cool projects but not really what I had in mind. I started thinking about requirements: I want zero dependencies on the Internet. Local LAN or on-device functionality only. I hate it when you're supposed to talk to a computer, yet it introduces huge, unnatural latency into the conversation by requiring a round-trip to the Internet. No way. Local latency only. I want it to run off the original toy's batteries and/or power supply, and have low/no idle/standby power usage. I don't want to be changing the batteries every day, and I should not need to carry around an awkward bench DC supply to use it. I want minimal preferably no physical mods to the chassis. It should be totally stealth and look exactly like the original toy. No hint that it's different until it starts talking to you. I want it to be a simple drop-in replacement to the existing electronics. Most of the other projects involved stuffing huge Raspberry Pis or dev boards and a rat's nest of wiring into the existing toy. I wanted to see if I could do something simpler and cleaner. Finally, I didn't really want a useful assistant, just a goofy toy I could play around with and amuse guests with. A silly or witty voice was necessary. The biggest decision we'll tackle this later was whether to make it self-contained or whether it can rely on external compute. I don't want the latency and dependencies of Internet services like Google or Amazon, but I wouldn't be able to do everything in the fish chassis itself. I figured a local machine on the LAN wouldn't be cheating, as long as I could connect to it using the original toy form factor no ethernet wires sticking out , and I could keep latency under control. Bonus points if I could retain the toy's exact circuit board size and mounting footprint. Apparently, not all Big Mouth Billy Bass toys are the same. The hardware, electronics, and chassis have all undergone revisions since the original 1999 release. Some models have 3 motors one for each of mouth, tail, head , some have two one for the mouth, one doing double duty for tail/head . Some models have a barrel connector for regulated DC, and others just run off batteries. Some models take AA batteries and others take C batteries. Some sing Christmas songs, others sing normal songs. There appear to also be major chassis changes. I decided that this project would be tailored to the actual toy I had on hand. It would not be made generic enough to power all the different Billy Bass variants released over the years. Detecting and supporting multiple revisions of the toy might be an interesting future project for someone. Toy in hand, I decided to tear it down and see what I had. The chassis of my toy consisted of six small screws and four soft "feet". Once the screws were removed, the front and back halves separated easily. The front chassis contains the actual fish figure, and I learned that I have the three-motor variant, not the two-motor one this has circuit board BOM and software implications addressed later on . The front chassis also houses a pushbutton and what the box calls a "motion sensor." Closer inspection shows it's actually just a photocell LDR : it doesn't detect motion or heat at all, only a change in light level, like a shadow passing over it. The rear chassis contains the battery holder. I have the four-C-battery variant with a separate barrel connector for regulated 6V power. When a plug is inserted, the barrel connector physically disconnects the batteries also impacts the circuit design later . The rear chassis also houses a speaker for audio output, and a 2-position ON-ON switch — physically a DPDT package, but wired as a plain SPST toggle that just enables/disables the photocell trigger. The button is wired independently and works either way. There's no actual OFF position, but that's because the toy's animatronics chip is normally unpowered and only wakes up via a trigger circuit. The rear chassis also comes with a retractable stand, if you want to rest the toy on a desk. All wires from the motors, switches, power supplies and so on were connected to a small 70mm x 50mm circuit board attached to the inside of the front chassis with two screws, which implemented all of the toy's outputs. Although there is plenty of room inside the chassis to stuff electronics, I wanted to see if I could do the entire project with just that 70mm x 50mm circuit board constraint. My physical constraints ruled out having a lot of compute inside the toy chassis. So, I settled on a "thin client" architecture: the fish toy electronics would just run the motors, sensors, buttons, and audio, and the heavy lifting compute would happen on a more powerful backend machine on my LAN. I needed an in-toy board that had GPIOs, could drive motors and output/process audio, and had WiFi to wirelessly connect to my backend. On the backend, I'd run the Speech-To-Text, LLM I/O, and Text-To-Speech, where I had heaps of compute/GPU to spare. For the in-toy CPU, I settled on the tried and true ESP32-S3 chip. This should boot within seconds, give me about 8MB of RAM, 8MB of flash, plenty of GPIOs and, importantly, built-in 2.4 GHz WiFi. I could have used a Raspberry Pi, but the form factor is kind of bulky and it contains much more computing power than I need for the in-toy board, but not quite enough to do the backend's STT+LLM+TTS job. Along with the ESP32, we'll need a few other peripherals: three DC motor drives, an audio amplifier to drive the speaker I²S or similar , a microphone to listen to my voice I²S or similar , a 6V-to-3.3V power step-down to power the ESP32 and peripherals, and some kind of serial and/or JTAG connector for flashing and debugging. We'll go into the BOM and hardware choices later. One more decision: the firmware itself could also have been built on ESPHome, which plugs an ESP32-S3 straight into Home Assistant's ecosystem. Several of the prior-art projects above do exactly this. I have zero interest in running a Home Assistant stack myself, so I wrote custom C firmware instead, but it's a legitimate path if you're already living in HA. For the backend, I planned to simply use my existing dual Xeon E5-2697 v2 @ 2.70GHz "homelab" server already running Proxmox with a number of VMs and containers doing various things. I'd add a dedicated LXC container to run Billy's backend processes: I picked whisper.cpp for Speech To Text, llama.cpp running a local LLM, and Kokoro for Text To Speech; all three fit comfortably on an NVIDIA RTX 4000 Ada Generation GPU with 20GB VRAM. The other latency trick is architectural: the backend doesn't wait for the LLM's complete reply before doing anything. As soon as the LLM streams back a complete sentence, that sentence gets sent to Kokoro for synthesis and queued for playback, while the LLM keeps generating the rest of the response in the background. So the fish starts talking after the first sentence is ready. This gets first-audio latency down to a few seconds. My main concern about requiring a separate backend was latency. Whisper.cpp and llama.cpp were pretty clear choices for serving Speech To Text and the LLM from the backend, but I had a few choices for Text To Speech. I tried a couple out, including Chatterbox and Qwen3-TTS, but quickly settled on Kokoro TTS with its 82 million parameter model, due to its extremely high speed and low latency using the GPU , accuracy and lack of hallucination, and "OK" voices. The speed gap comes down to architecture: Qwen3-TTS is autoregressive, generating audio one speech token at a time, each conditioned on everything generated before it, which is inherently sequential, so it doesn't get meaningfully faster just by throwing more GPU at it. Chatterbox's token-generation stage is effectively the same story, even though its downstream vocoder uses flow matching. Kokoro is non-autoregressive: it produces a whole sentence's waveform in a single parallel forward pass, which is what gets it down to ~0.2 seconds instead of the multi-second waits I saw from the other two. The built-in voices are Kokoro's weakest attribute, at least for my application. They were very plain and corporate-sounding. It would have been nice to have more "silly" options. I picked one of the canned voices am onyx that sounded at least moderately silly. I suppose I could play with the voice using a DSP or something, but instead, I chose to build the fish's personality by way of a system prompt for the LLM. This provided infinite flexibility to improve/tweak personality, and it's actually kind of funny to hear a dry corporate-sounding voice try to tell fish jokes if it has the right attitude. Power management, sleep, and activation was the next major concern/decision. On one hand, I wanted the device to last a long time on battery power. The ESP32 can be put in a deep sleep mode, but that powers down the I²S peripheral entirely. I also wanted to be able to "activate" the toy via a typical assistant-like wake word like "Hey Billy". There's no low-power listening state: either the CPU is fully powered or it is in deep sleep. The only thing that stays alive during deep sleep is the chip's ULP ultra-low-power coprocessor. It can only read RTC-GPIO and RTC-ADC pins. It cannot read the digital I²S bus my mic talks over, and even if it could hear the audio, it doesn't have anywhere near enough compute to run a keyword-spotting neural net. So "half-listening" just isn't a state that exists on this chip. As a compromise, I planned to use the switch on the rear chassis to switch the unit between "button activation" which can interrupt deep sleep, and can idle for months on the toy's C-cell batteries and "wakeword activation," which needs the CPU and mic pipeline running continuously and drains the same batteries in about two days. By now, I had the overall system designed to my liking. My plan was to work on the backend first, which would primarily be container setup and off-the-shelf software installation, and work backwards from there toward the user. After the backend was done, I'd implement a basic CLI test client that recorded audio from a PC, sent it to the backend, and received response audio to play back on the PC, ensuring all backend components were working and latency was fine. After that, I'd breadboard the electronics and write the ESP32-S3 firmware, matching the "reference implementation" proven by the CLI. The breadboard would validate all peripherals on the device, including mic, amp, all motors, the button, the switch, and the photocell sensor. Finally, once all the on-fish and off-fish functionality was working via the breadboard, I'd design and produce a custom PCB and test it on the toy. First, I set up an LXC container on my host to allow GPU passthrough the GPU was already installed in the homelab, but didn't have much to do yet and got all the NVIDIA/CUDA drivers installed and working. I kept all three AI services in this one container rather than splitting them up. More containers would each need their own GPU passthrough. Then I installed whisper.cpp, llama.cpp, and Kokoro onto that container, and downloaded Qwen3-8B-Q4 K M. I independently verified each service was listening and responding properly, and the AI models fit in VRAM. My homelab has heaps of system RAM, so I was pretty much unconstrained there, but the single GPU only has 20GB VRAM, which could be tight. Ultimately, llama.cpp+Qwen3-8B took up around 5.4GB, whisper.cpp 3.7GB, and Kokoro 1.2GB, in total using only half of my VRAM. Very nice, lots of space With the off-the-shelf software installed and working, I needed to start thinking a little bit about the boundary between the in-fish software and the backend. Would the fish call each backend service directly, or would it operate through a higher-level application layer also running on the host? Pros and cons for each scenario. It would have been nice to have a single application endpoint for the fish to interact with, letting it handle the entire STT+LLM+TTS round trip. On the other hand, I'd be concerned about latency and copying audio all over the place, and the ability to play back audio sentence-by-sentence as the LLM generates it. This sentence-by-sentence pipelining means TTS synthesis and LLM decode briefly share GPU compute. The other design, fish firmware calling each backend independently, would be simple, but I'd have to bake more application logic than I'd like into the embedded firmware, like the system prompt, conversation history, and so on. I ended up with a design where the fish firmware calls whisper.cpp and Kokoro directly, but calls into the LLM via a Python "shim" application running on the host. The shim is built on FastAPI/HTTPX and sits in front of llama.cpp , holding on to the system prompt and per-session data, handling all model-specific quirks such as Qwen3's /no think and stripping stray