mission report · 2025

Billy Bot

basically alexa. except it's me talking to myself.

PLATFORM Raspberry Pi 5
LANGUAGE Python
STATUS Complete
LOCATION Brisbane, QLD
01

Mission Brief

Billy Bot is basically a mini Alexa — a local voice assistant that uses keyword extraction instead of AI. It runs on a Raspberry Pi with Python 3. There are two core output functions: play(), which plays a pre-recorded voice clip of me saying an answer (because I'm a narcissist), and say(), which falls back to text-to-speech if you'd rather not talk to yourself.

Billy waits for the wake word "billy" before listening for commands. Then it runs speech-to-text through Google (with Vosk as an offline backup) and converts what you said into a string. From there it scans for keywords. On the surface it sort of looks like AI — but it's actually just a really long list of if/else statements checking whether a word is in the sentence.

Because of the modular design, the keywords and functionality can be extended as far as you want. Write the function, add it to the if/else chain, done. Easy to expand — but it was a huge pain to build from scratch. Here's everything that went into it.

🎤
USB mic hears you
🔤
Google STT / Vosk
🔍
keyword scan
🔊
plays your voice
02

Hardware

total build cost · AUD $357.34 before delivery
Pi 5 — top-down flat lay, dark surface
Raspberry Pi 5 4GB
$179.55 AUD
The brain. Could get away with a Pi 4 2GB for this project alone but I'll use this for more.
view part →
Wonderboom — angled hero shot, speaker + Pi together
UE Wonderboom
$117.82 AUD
Only used this because I already had it and wanted more budget for the mic. Any speaker works.
view part →
Mini USB mic — close up, fill frame, side light
ChanGeek Mini USB Mic
$16.95 AUD
Great value. Actually really good. Far field mics are $100+ so this was the easy call.
view part →
Pi power supply — product shot, clean + simple
Official Pi 5 Power Supply
$21.07 AUD
Just get the right one. USB-C PD 27W. Don't cheap out on this.
view part →
microSD — close-up macro, card in hand or on surface
Samsung PRO Endurance 32GB
$21.95 AUD
I guessed 32GB was enough. Ended up using about 10GB including the OS. Fine call.
view part →
drag to scroll →
drag to scroll →
03

Setup

Full headless setup from a blank microSD card. You will never plug a monitor into this Pi — everything happens over SSH from your computer. If that sounds scary, it isn't. It's just a terminal window that talks to the Pi over WiFi.

before you start
You need: a computer (any OS), the microSD card with a card reader, your WiFi name and password, and all the hardware from Section 02 ready to go.
01
Flash the OS onto the microSD card

Download Raspberry Pi Imager from raspberrypi.com/software and install it on your computer. Open it and make these selections:

  • Choose Device → your Pi model
  • Choose OS → Raspberry Pi OS Lite (64-bit). Not the desktop version — you don't need a GUI and it'll just slow everything down
  • Choose Storage → your microSD card. Double check this. It will wipe whatever you pick

Before you click Write, click the cog icon (Edit Settings). This is critical — without it you can't connect headlessly:

  • Set hostname → e.g. billybot
  • Enable SSH → use password authentication
  • Set username and password → pick a username — this is [username] for the rest of this guide
  • Configure wireless LAN → your WiFi name and password, exactly as written including capitals. Country: AU
  • Set locale → your timezone

Click Save → Write. Wait until it fully finishes. Eject, plug into the Pi, connect the power.

if something goes wrong
Card reader not detected by ImagerTry a different USB port. If still nothing, the card reader might be faulty — try another one
Write fails halfwayDownload SD Card Formatter from sdcard.org, format the card, then try Imager again
Raspberry Pi 5 not listed in ImagerUpdate Raspberry Pi Imager to the latest version
Forgot to open the cog settings before writingYou'll need to reflash. Those settings can't be added after the fact
02
Find the Pi on your network

Give the Pi about 90 seconds to boot — it's doing first-time setup in the background. Log into your home router (usually 192.168.0.1 or 192.168.1.1 in a browser) and look at the connected devices list. Find billybot and write down its IP address — it'll look like 192.168.1.42.

If you can't find it in the router, try from your computer's terminal:

ping billybot.local
if something goes wrong
Pi not showing in router after 3 minutesThe WiFi credentials in Imager were wrong. Reflash the card with the correct details
ping billybot.local times out on WindowsWindows doesn't always support .local hostnames. Download Angry IP Scanner and scan your network range to find the IP directly
Pi appears then disappears from routerPower supply issue — make sure you're using the official Pi power supply. Underpowering causes instability on first boot
03
SSH into the Pi

Open a terminal on your computer. On Windows that's Command Prompt or PowerShell, on Mac/Linux it's Terminal.

ssh [username]@192.168.1.42

Replace the IP with yours. First time it'll ask if you want to continue connecting — type yes and hit Enter. Then enter your password. You won't see anything appear while you type — that's normal.

If you see [username]@billybot:~$ you're in. Everything from here runs in this window.

if something goes wrong
"Connection refused"Pi is still booting. Wait 30 seconds and try again. If it keeps refusing after 5 minutes, SSH wasn't enabled in Imager — reflash
"Permission denied" or "Authentication failed"Wrong username or wrong password. Both are case-sensitive. If you're completely locked out, reflash
"WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED"Run ssh-keygen -R 192.168.1.42 (your actual IP), then SSH again normally
Just times out with nothingThe IP address is wrong. Go back to Step 2 and find the correct one
04
Update the Pi
sudo apt update
sudo apt upgrade -y

The first fetches the list of available updates. The second installs them. The -y skips every confirmation prompt. Takes 5–10 minutes on first run — let it finish completely before moving on.

if something goes wrong
"E: Could not get lock /var/lib/dpkg/lock"Another apt process is running in the background — common on first boot. Wait 60 seconds and try again
"E: Unable to fetch some archives" or network errorsCheck your connection: ping google.com. If that fails, the Pi isn't on the internet — recheck your WiFi credentials
Seems frozen for more than 15 minutesIt's probably just slow on a weak connection. As long as the cursor isn't blinking at a prompt it's still working. Wait it out
"dpkg was interrupted" errorRun sudo dpkg --configure -a, then retry sudo apt upgrade -y
05
Install system dependencies
sudo apt install python3-pip espeak espeak-ng espeak-ng-data portaudio19-dev flac sox git -y
  • espeak + espeak-ng + espeak-ng-data — the voice engine pyttsx3 needs. You need all three. Installing only espeak causes pyttsx3 to appear to work but produce zero sound, with no useful error to tell you why
  • portaudio19-dev — lets Python communicate with the microphone
  • flac — required by speech_recognition. Without it, Google STT silently does nothing
  • sox — for creating the keep-alive silence file in Step 14
  • git — useful for moving files around
if something goes wrong
"E: Package not found" for any packageRun sudo apt update first to refresh the package list, then retry the install
Dependency errors or broken packagesRun sudo apt install -f to auto-fix, then retry
06
Install Python packages
pip install SpeechRecognition pygame pyttsx3 requests vosk word2number --break-system-packages

The --break-system-packages flag is required on current Raspberry Pi OS — newer versions block pip from installing to system Python by default. It sounds alarming. It's fine. Wait for it to finish — as long as it ends with Successfully installed and no red ERROR: lines, you're good.

if something goes wrong
"error: externally-managed-environment"You removed or forgot the --break-system-packages flag. Copy and paste the full command exactly as written
pygame install failssudo apt install python3-pygame -y
SSL certificate errorssudo apt install ca-certificates -y, then retry the pip install
vosk is taking foreverIt's a large package. Let it go — don't cancel it
07
Get your own GNews API key
important
Do not use the API key already in the code. That's mine. It has a request limit and using it will break the news feature for both of us. Get your own — it's free and takes two minutes.

Go to gnews.io → sign up for a free account → copy your API key. You'll paste it into the code in Step 13.

if you skip this step
Forgot to get a keyThe news feature won't work but the rest of the bot runs fine. Come back and add your key later by editing the file again in Step 13
08
Set up your speaker
Bluetooth speaker

Install Bluetooth audio packages:

sudo apt install pulseaudio pulseaudio-module-bluetooth bluez -y
sudo reboot

SSH back in after about 60 seconds. Start PulseAudio:

pulseaudio --start

Open the Bluetooth controller:

bluetoothctl

You'll see a [bluetooth]# prompt. Run these one at a time:

power on agent on default-agent scan on

Put your speaker into pairing mode now. Devices will appear with their MAC addresses (XX:XX:XX:XX:XX:XX). When you see your speaker's name:

pair XX:XX:XX:XX:XX:XX trust XX:XX:XX:XX:XX:XX connect XX:XX:XX:XX:XX:XX

If it says Connection successful, type exit. Now set it as the default audio output:

pactl list short sinks

Find the entry with bluez in the name. Then (colons in MAC become underscores):

pactl set-default-sink bluez_sink.XX_XX_XX_XX_XX_XX.a2dp_sink

Test it:

paplay /usr/share/sounds/alsa/Front_Left.wav

If you hear something, audio is working. Now create a reconnect script so Bluetooth re-pairs automatically on every boot:

nano /home/[username]/bt-connect.sh

Paste this in (replace MAC with yours):

#!/bin/bash sleep 20 bluetoothctl connect XX:XX:XX:XX:XX:XX

Save: Ctrl+XYEnter. Make it executable:

chmod +x /home/[username]/bt-connect.sh
if something goes wrong
Speaker not appearing during scan onNot in pairing mode, or too far from the Pi. Move it closer and try again
"Authentication Failed" when pairingRun remove XX:XX:XX:XX:XX:XX inside bluetoothctl to clear the attempt, then try pair again
Connected but paplay produces no soundDefault sink wasn't set. Redo the pactl set-default-sink step
bluez not appearing in pactl list short sinksRun pulseaudio --kill then pulseaudio --start, then reconnect the speaker in bluetoothctl
"PulseAudio: Connection refused"pulseaudio --kill then pulseaudio --start
Speaker keeps disconnecting on its ownYou skipped the trust command. Run bluetoothctl trust XX:XX:XX:XX:XX:XX
USB speaker or 3.5mm jack

Test it works straight away:

aplay /usr/share/sounds/alsa/Front_Left.wav

If sound comes out the wrong output, run raspi-config → System Options → Audio → select the right one.

You can delete the keep_alive() function from the code entirely — it only exists to stop Bluetooth speakers from auto-shutting off. A wired speaker doesn't need it.

if something goes wrong
No sound at allRun aplay -l to list output devices and confirm your speaker is detected. If it's not listed, try a different USB port
Sound comes out HDMI instead of your speakerraspi-config → System Options → Audio → manually select your output device
09
Set up and test the microphone

Plug in your USB microphone. Check the Pi can see it:

arecord -l

You should see something like:

card 1: Device [USB Audio Device], device 0: USB Audio [USB Audio]

Note the card number. Record a 3-second test (replace 1,0 with your card number if different):

arecord -D hw:1,0 -f cd -t wav -d 3 test.wav

Talk into the mic. Then play it back:

paplay test.wav

If you hear yourself, the mic works. Any USB mic that shows up in arecord -l will work — just use its card number.

if something goes wrong
arecord -l shows nothingMic isn't being detected. Unplug and replug it, then run arecord -l again. If still nothing, try a different USB port
"Device or resource busy"Another process is using the mic. Reboot the Pi and try again before running anything else
Recording works but playback is silentWrong card number. Reread arecord -l carefully — the card number is the digit after "card"
paplay plays through the wrong outputPulseAudio default sink reset. Redo the pactl set-default-sink step from Step 8
10
Create the folder structure
mkdir -p /home/[username]/billy/Voices

The -p flag creates both folders at once. Your project lives here:

/home/[username]/billy/
assistant.py
vosk-model-small-en-us-0.15/
Voices/
startup.wav, greeting1.wav, ...
if something goes wrong
"Permission denied"You're using the wrong username in the path. Run whoami to confirm your actual username
11
Download the Vosk model
cd /home/[username]/billy
wget https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip
unzip vosk-model-small-en-us-0.15.zip
rm vosk-model-small-en-us-0.15.zip

Check it worked — run ls and confirm vosk-model-small-en-us-0.15 is there as a folder. Don't rename it. The code looks for it by that exact name.

if something goes wrong
unzip: command not foundsudo apt install unzip -y, then run the unzip command again
Download times out or is very slowThe Vosk servers can be slow. Cancel with Ctrl+C and retry the wget command
Folder name after unzipping is slightly differentRename it: mv [wrong-name] vosk-model-small-en-us-0.15
12
Transfer your files to the Pi

Run these from your computer's terminal — open a new window for this, separate from the SSH session.

Transfer assistant.py:

scp /path/to/assistant.py [username]@192.168.1.42:/home/[username]/billy/

Transfer your Voices folder:

scp -r /path/to/Voices/ [username]@192.168.1.42:/home/[username]/billy/Voices/

Back in the SSH session, check everything arrived:

ls /home/[username]/billy/Voices/
if something goes wrong
"No such file or directory" on your computerYour local path is wrong. On Mac/Linux, drag the file into the terminal window to get its full path automatically
"Permission denied" on the Pi sideDestination path is wrong — double check your username and that the folder from Step 10 exists
scp not recognised on WindowsUse PowerShell, not Command Prompt — Windows 10 and 11 have scp built into PowerShell
Clips are nested inside a subfolder instead of directly in Voices/Move them out: mv /home/[username]/billy/Voices/Voices/* /home/[username]/billy/Voices/
13
Edit the code for your setup
nano /home/[username]/billy/assistant.py

Use Ctrl+W to search. There are no hardcoded file paths to fix — the code uses os.getcwd() so it works for anyone as long as you run it from the right directory. You only need to update these:

  • GNews key — find GNEWS_KEY = "YOUR_KEY_HERE" and replace the value with your own key from Step 7
  • Weather location — if you're not in Brisbane, find the Open-Meteo URL and replace the latitude and longitude values. Get your coordinates at latlong.net
  • Birthday countdowns — find the birthday() function and update the target dates to your own, or delete the whole function if you don't want it

Save: Ctrl+XYEnter. Three separate keystrokes.

if something goes wrong
Nano won't respond to Ctrl+XClick into the terminal window first so it has focus, then try again
Made a mistake and saved itRe-transfer the original file from your computer (Step 12) and start this step again
14
Create the keep-alive silence file
bluetooth speaker only
Skip this step if you're using a USB speaker or headphone jack. It won't cause problems if you leave the code as-is, but you don't need the file.
cd /home/[username]/billy/Voices
sox -n -r 44100 -c 1 silence.wav trim 0.0 3.0
ffmpeg -i silence.wav silence.mp3
rm silence.wav

This creates a 3-second silent .mp3 file. The code looks for silence.mp3 specifically — sox creates wav by default so the ffmpeg conversion step is required. If ffmpeg isn't installed: sudo apt install ffmpeg -y.

if something goes wrong
sox: command not foundYou missed it in Step 5. Run sudo apt install sox -y, then try the sox command again
"No such file or directory"You're in the wrong directory. Run the cd command first, then sox
15
Test run
cd /home/[username]/billy
python3 assistant.py

The cd is required — the code uses os.getcwd() to find the Voices folder and the Vosk model, so Python needs to be running from inside the billy directory. Running python3 /home/[username]/billy/assistant.py from anywhere else will fail to find both.

Vosk model loaded successfully. Billy is online. Say 'billy' to wake.

And hear your startup clip through the speaker. Say "billy hello" — if it responds, everything works. Hit Ctrl+C to stop.

if something goes wrong
FileNotFoundError for Voices or VoskYou didn't cd into the billy folder first. The code uses os.getcwd() — it only finds the files if Python is running from inside /home/[username]/billy/
No module named XA pip install from Step 6 failed. Run pip install [module-name] --break-system-packages for just that package
Script starts but no sound playsPulseAudio default sink reset. Redo the pactl set-default-sink step from Step 8 and try again
"Vosk model not found" in terminalRun ls /home/[username]/billy/ and confirm the folder is there and named exactly vosk-model-small-en-us-0.15
"Couldn't hear that" every single timeMic issue. Check arecord -l confirms the mic is still detected, then check speech_recognition is finding the right device
Lots of ALSA lib errors flooding the terminalHarmless warnings from pygame initialising audio. Ignore them — the bot still works
OSError: [Errno -9996] Invalid input devicePython can't find the microphone. Confirm the mic is plugged in and visible in arecord -l
Bot hears you but Google STT never respondsNo internet. Run ping google.com — if that fails, your WiFi dropped. Vosk will take over automatically but check your connection
16
Run on boot
crontab -e

If it asks which editor, pick 1 (nano). Scroll to the very bottom and add your lines.

Bluetooth speaker — add both lines
@reboot /home/[username]/bt-connect.sh @reboot sleep 30 && pulseaudio --start && sleep 10 && cd /home/[username]/billy && python3 assistant.py >> /home/[username]/billy/billy.log 2>&1 &
USB speaker or 3.5mm jack — add one line
@reboot sleep 15 && cd /home/[username]/billy && python3 assistant.py >> /home/[username]/billy/billy.log 2>&1 &

The sleep gives the Pi time to connect to WiFi before the script starts. The >> billy.log saves all output to a log file — if it breaks on boot, that's where you'll find out why.

Save: Ctrl+XYEnter. Then reboot:

sudo reboot

Wait 60 seconds. Without touching anything — say "billy hello." If it responds, you're done.

If it doesn't, SSH back in and check the log:

cat /home/[username]/billy/billy.log
if something goes wrong
Bot doesn't start and the log is emptyCrontab didn't save. Run crontab -e again and check your lines are actually there at the bottom
Log shows the same errors from Step 15Fix those errors first — same fixes apply. The bot won't run on boot if it can't run manually
Bluetooth not reconnecting on bootbt-connect.sh isn't executable. Run chmod +x /home/[username]/bt-connect.sh then reboot again
Log shows PulseAudio errors on bootThe sleep 30 isn't long enough. Change it to sleep 45 in crontab and reboot
Works on first boot but not on second rebootBluetooth trust wasn't set. Run bluetoothctl trust XX:XX:XX:XX:XX:XX then reboot
04

The Code

view full code →

This is a full breakdown of the code. It's all in one file, split into clear sections so you can paste it into a Python file and load it onto the Pi with the setup instructions. It looks simple from the outside but it's actually a carefully balanced system of audio playback, speech recognition, and controlled chaos. To view the full code, click here.

imports Libraries & Dependencies

Every module the project needs, and what it actually does:

speech_recognition
Converts microphone input into text via Google Speech-to-Text. The main input system — basically what lets Billy hear anything at all.
pip install SpeechRecognition
os
Handles file paths and checks if audio clips exist before trying to play them and pretending everything is fine.
built-in
random
Picks random responses for jokes, roasts, greetings — anything where I didn't want the bot sounding repetitive.
built-in
pygame
Plays all the recorded voice clips. The backbone of the play() function and basically what gives Billy its "personality".
pip install pygame
requests
Pulls data from external APIs like weather and news. Without this, Billy would just confidently guess everything like a very loud liar.
pip install requests
pyttsx3
Offline text-to-speech engine used by say() when there's no recorded clip, or when I don't want to hear my own voice again.
pip install pyttsx3
datetime
Used for anything time-related — time, date, uptime, birthdays, and general "what day is it" confusion.
built-in
vosk
Offline speech recognition backup for when Google decides to stop working. Keeps Billy usable without internet. Needs a model download — use vosk-model-small-en-us-0.15.
pip install vosk
json
Converts Vosk output into usable text so Python doesn't just stare at raw data and panic.
built-in
threading
Handles background tasks — timers, keep-alive loops, delayed audio — without freezing the whole program.
built-in
time
Used for delays, uptime tracking, and making sure audio doesn't overlap and turn into absolute chaos.
built-in
word2number
Converts spoken numbers like "twenty five" into actual integers so timers, maths, and guessing games work properly.
pip install word2number
import speech_recognition as sr
import os, random, pygame, requests
import pyttsx3, datetime, json, threading, time
from vosk import Model, KaldiRecognizer
from datetime import timedelta
from word2number import w2n
core play() and say()

Everything in Billy Bot ends up in one of two outputs. play() loads and plays a pre-recorded .mp3 clip from the Voices directory using pygame. say() is the text-to-speech fallback for dynamic data — temperature readings, timer durations, calculation results. Most responses are hybrid: a clip plays first for structure, then say() handles the variable part.

Both functions share an audio_lock — a reentrant lock (RLock) that prevents two things playing at once. It's reentrant specifically because if a clip is missing, play() calls say() internally. A regular Lock would deadlock at that point since the same thread can't acquire it twice. RLock allows it.

Clip filenames don't need the .mp3 extension — play() appends it automatically. VOICE_PATH uses os.getcwd() so the path is portable — no hardcoded username anywhere.

VOICE_PATH = os.path.join(os.getcwd(), "Voices")
audio_lock = threading.RLock()

def play(filename):
    with audio_lock:
        if not filename.endswith(".mp3"):
            filename += ".mp3"
        path = os.path.join(VOICE_PATH, filename)
        if os.path.exists(path):
            pygame.mixer.music.load(path)
            pygame.mixer.music.play()
            while pygame.mixer.music.get_busy():
                time.sleep(0.05)
        else:
            say("Missing audio file")

def say(text):
    with audio_lock:
        pygame.mixer.music.stop()
        speaker.say(text)
        speaker.runAndWait()
functional Weather, Time, News, Calculator

Billy has a set of functional systems that make it feel like an assistant instead of just a soundboard. Weather pulls live data from Open-Meteo (no API key needed — I value convenience over bureaucracy) and reads back temperature, conditions, and wind speed for Brisbane. Time and date come straight from the system clock. News uses the GNews API to fetch a real article on whatever topic you ask about. The calculator converts natural speech into evaluatable math expressions using word-to-operator replacement and eval(). All rule-based logic — but structured to feel responsive and "intelligent" without actually using AI.

🏆 best function The Timer — Duration Accumulation Algorithm

The one I'm most proud of. It understands any combination of hours, minutes, and seconds in natural language — "set a timer for 2 hours 15 minutes and 30 seconds", "remind me in 45 minutes", "timer for an hour and a half" — all of it works. It scans the word list, converts number-words to integers using word2number, then accumulates total seconds based on whatever unit word follows each number. threading.Timer fires the buzzer clip when time's up, without blocking the rest of the program.

def timer(speech):
    words = speech.lower().split()
    total_seconds = 0
    last_number = None
    for word in words:
        try:
            last_number = w2n.word_to_num(word)
        except ValueError:
            if last_number is not None:
                if word in ["second", "seconds", "sec", "secs"]:
                    total_seconds += last_number
                    last_number = None
                elif word in ["minute", "minutes", "min", "mins"]:
                    total_seconds += last_number * 60
                    last_number = None
                elif word in ["hour", "hours", "hr", "hrs"]:
                    total_seconds += last_number * 3600
                    last_number = None
    threading.Timer(total_seconds, play, args=["buzzer.wav"]).start()
games Game System & State

Billy supports interactive modes like number guessing and truth or dare — games that need multiple back-and-forth turns. Instead of nested loops (which would've locked the whole program), it uses a single global state variable: current_game. When a game starts, current_game gets set to "number_guess" or "truth_or_dare", and the main listening loop checks it before calling respond() — routing input to the right game handler instead. The system stays responsive and keeps running normally. When the game ends, current_game resets to None.

speech Dual Recognition — Google + Vosk Fallback

Billy prioritises Google Speech-to-Text because it's fast and accurate — but it needs internet and isn't always reliable. So there's a fallback: if Google throws an sr.RequestError, the system switches to Vosk, which runs entirely on the Pi with no internet required. If both fail, input is ignored and the loop continues.

Two things worth knowing about the main loop structure. First, the entire loop runs inside a single with sr.Microphone() block — opening and closing the USB mic device every iteration causes unnecessary overhead and can throw "device busy" errors on the Pi. One block, open for the whole session. Second, sr.WaitTimeoutError is caught separately from the speech errors — if the mic hears nothing for 5 seconds it raises this, and without catching it the whole program would crash silently.

05

What It Can Do

Full feature spec. Say "billy" then any of these.

Functional
  • Real-time Brisbane weather (temp, conditions, wind)
  • Current time and date
  • Natural language timer ("2 hours 15 minutes and 30 seconds")
  • News search by topic via GNews API
  • Spoken maths — add, subtract, multiply, divide
  • Birthday countdown in seconds (16th and 18th)
  • System uptime
Games
  • Truth or dare
  • Number guessing (1–100, hot/cold hints)
  • Coin flip
  • Magic 8 ball
  • Yes or no oracle
  • "Am I cooked" randomiser
Personality
  • Jokes (10 clips)
  • Roasts (5 clips)
  • Hype / motivation (5 clips)
  • Affirmations (10 clips)
  • Deaffirmations / insults (10 clips)
  • Fit checks (5 clips)
  • Greetings, goodbyes, good morning, good night
  • Emotional responses (sad, tired, hungry, bored)
  • ~50 personality responses total
System
  • Wake word: "billy"
  • Google STT primary, Vosk offline fallback
  • Keep-alive silent audio (Wonderboom auto-shutoff workaround)
  • Emergency stop protocol
  • Runs 24/7 on Raspberry Pi 5
06

What Went Wrong

The honest log.

ERR_001
PyAudio on Python 3.14
Placeholder — your words here.
ERR_002
pipwin broken
Placeholder — your words here.
ERR_003
playsound abandoned
Placeholder — your words here.
ERR_004
mp4 not supported by pygame
Placeholder — your words here.
ERR_005
espeak not installed
Placeholder — your words here.
ERR_006
FLAC missing from PATH
Placeholder — your words here.
ERR_007
Bluetooth audio chaos
Placeholder — your words here.
ERR_008
Vosk AcceptWaveform typo
Placeholder — your words here.
ERR_009
Capital S on Startup.mp4
Placeholder — your words here.
07

What I Learned

Placeholder — your words here.

Placeholder — your words here.

Placeholder — your words here.

Placeholder — your words here.