Resurrecting Google Glass Enterprise Edition 2 with a Local Voice Gateway (And Four Ghost Bugs Along the Way)
Turning a retired Android 8.1 smart-glasses headset into an ambient lab terminal backed by a dedicated Whisper + Coqui TTS VM, plus four infrastructure mysteries where every health check returned 200 OK while the system was broken.
Google Glass Enterprise Edition 2 is a great piece of hardware that history mostly forgot: a titanium frame weighing under fifty grams, a tiny prism display floating above your right eye, a bone-conduction speaker, a temple touchpad, and an 8-megapixel camera - all running standard Android 8.1 (API 27) with adb unlocked and no dead consumer companion app required.
In September, I picked up a pair with a simple goal: turn it into a hands-free, heads-up terminal for the lab and smart home, backed by local speech recognition, local LLM tool calling, and a custom text-to-speech voice.
Getting it working meant spinning up a fifth Hyper-V VM (VoiceGW) on REX, building a lightweight WebSocket relay for when I’m away from home, and chasing down four separate infrastructure ghosts where every health check reported 200 OK while the underlying system was quietly broken.
Part 1: The VoiceGW VM and the Attenborough Voice Loop
While the assistant VM (UbuntuClaw) already ran Whisper and Piper for Telegram voice notes, a heads-up display has a much tighter latency budget. Waiting eight to ten seconds for a two-stage RVC voice pass is fine in a chat app - it feels broken when you tap your glasses and stand there waiting for an answer.
So REX picked up its fifth Hyper-V guest: VoiceGW, an 8-vCPU, 8 GB RAM Debian VM built specifically for low-latency interactive speech:
- Speech-to-Text (
faster-whisper) - Keeps a quantised Whisper model warm in memory with VAD (Voice Activity Detection) pre-filtering, transcribing spoken commands in ~300-400ms. - Text-to-Speech (Coqui VITS) - Runs a single-stage VITS model trained on a custom David Attenborough voice profile, delivering first audio in ~1.4 seconds without needing a separate RVC conversion pass.
- Turn Coordinator - A small HTTP/WebSocket gateway that takes raw PCM audio from the glasses (or from Home Assistant’s Wyoming integration), transcribes it, sends the prompt to the AI gateway or local vLLM (
Swift-Qwen3.8-27B), pushes the HUD text card back immediately, and streams the audio response to the bone-conduction speaker.
The Android 8.1 Client on Glass EE2
Because Glass EE2 runs open Android 8.1, the client is a small native Kotlin app wired to the temple touchpad:
- Single tap - Wakes the mic, streams PCM audio until you stop talking, and renders the reply in high-contrast amber/white text sized for the 640×360 prism display while the voice reply plays.
- Double tap - Grabs a photo from the 8 MP camera and attaches it to the prompt for visual questions (“What model of transceiver is this?” or “Read the serial number on that label”).
- Swipe down - Clears the card and puts the prism back to dark standby so it stays out of your peripheral vision.
At home on Wi-Fi, Glass talks straight to VoiceGW on the LAN. When I’m out and tethered to my phone or travel router, it connects to a small Cloud Run WebSocket relay that checks the device token and forwards the turn over the WireGuard tunnel back to VoiceGW at home.
Having Sir David Attenborough calmly narrate the output of my solar inverter or explain why a switch port is dropping packets while I’m standing in front of the rack is easily in the top three reasons to own an RTX 5090.
Part 2: Four Ghost Bugs Where /health Returned 200 OK
Building the voice gateway and tidying up the lab between August and October turned up four separate bugs that all shared the exact same annoying trait: every health probe returned 200 OK, every status indicator was green, and real traffic failed anyway.
Here are all four, written down so future-me doesn’t have to diagnose them twice.
Ghost 1: The Jumbo-Frame Virtual Switch Blackhole
In September, I turned on MTU 9000 jumbo frames across the 10GbE storage path between REX, the Juniper core switch, and the Synology NAS (DS2419+). ping -l 8972 -f between host and NAS sailed through cleanly.
Two days later, I spun up a small Debian jump box (Jumphost) inside Synology Virtual Machine Manager (VMM) and attached its virtual NIC to the NAS’s 10GbE interface.
Right away, backups and big file copies from REX to the NAS hung completely - while SSH sessions, pings, and DNS lookups to the exact same NAS IP kept working with zero packet loss.
The cause: when Synology VMM attaches a guest tap interface to an Open vSwitch bridge (ovs_eth*), it silently resets the physical interface’s MTU back to 1500, even though the DSM control panel still proudly displays Jumbo Frame: MTU 9000.
- REX (
MTU 9000) and the Juniper switch port (MTU 9216) kept sending full 9000-byte TCP segments with theDF(Don’t Fragment) bit set. - Small packets (
<= 1500bytes) like SSH keystrokes, ICMP pings, and/healthprobes fit inside the NAS NIC’s silent 1500-byte ceiling and worked fine. - The instant a backup job or large API call sent a full-sized frame, the NAS interface dropped it on the floor at Layer 2.
Moving the VMM virtual switch off the 10GbE storage port onto a separate 1GbE trunk brought back true 9000-byte frames on the storage path straight away.
Ghost 2: The WAF That Hated Code Prompts
In late August, the public LiteLLM proxy in GCP passed every uptime check (GET /health/liveliness $\rightarrow$ 200 OK), yet every Claude Code session routed through the public endpoint failed on turn one with a 403 Forbidden - or worked for three minutes and then went dead for fifteen.
In Post 06’s August update, I’d already turned off Cloud Armor’s SQL-injection and XSS rules because coding prompts naturally contain SQL and HTML snippets. What bit me three weeks later were two other preconfigured OWASP Core Rule Set groups:
- Protocol enforcement rules (
900/901) - These inspect request body structure and headers. Large Anthropic/v1/messagespayloads carrying dozens of nested MCP tool schemas tripped the body parser on everyPOST, while emptyGET /health/livelinesschecks sailed right through. - Scanner-detection rules (
945) plus a 30 req/min rate limit - A fast multi-turn agent loop (where an agent fires ten quick tool calls and follow-up turns inside a minute) looked like an automated scanner flood to the WAF, which slapped my IP with a 15-minute ban.
Health checks only prove the container is breathing - they don’t prove your WAF will allow a 90-kilobyte JSON body full of bash scripts.
Ghost 3: The WinNAT Dynamic Port Bandit
On Windows 11, Hyper-V and WSL2 rely on the Windows NAT (WinNAT) driver. One morning after a reboot, a local media server refused to bind its port, complaining the port was already in use - even though netstat -ano showed nothing listening on it.
It turned out Windows had expanded its dynamic ephemeral TCP port reservation range across almost the entire high port space (1024-65535), letting Hyper-V/WinNAT randomly reserve blocks of ports at boot before user services started.
Worse, the standard fix suggested on every forum thread - running net stop winnat && net start winnat to free the reserved port - quietly destroys the WSLNat routing object that bridges traffic into WSL2. Inside WSL2, vLLM’s local curl localhost:8000/health still returned 200 OK, while from the rest of the LAN, port 8000 was completely unreachable until WSL2 was fully shut down and cold-booted. Resetting the dynamic port range back to the standard 49152-65535 window and reserving my service ports fixed it for good.
Ghost 4: The Four-DIMM DDR5 Reset Trap
Last of all was the longest-running mystery in the lab: REX’s random Kernel-Power 41 hard resets.
Back in July, I blamed ASUS AI Suite 3’s EPU power-saving driver and uninstalled it. REX behaved for three weeks - just long enough for me to write the August blog update claiming victory - and then hard-reset nine more times across August and September.
At first I suspected transient power spikes from the RTX 5090. Then, on 22 September, REX hard-reset while sitting almost completely idle with the GPU drawing 28 watts.
That cleared the GPU and pointed straight at the memory controller. REX runs 128 GB of DDR5 across all four DIMM slots (four Dual-Rank sticks) on an AM5 Ryzen 9 7950X3D. While two sticks of DDR5 will happily run at advertised EXPO speeds all day, driving four Dual-Rank DIMMs puts heavy electrical load on the integrated memory controller. On older AGESA firmware, marginal memory-training voltages show up as instant, unlogged CPU resets during light C-state voltage transitions.
Flashing the motherboard BIOS to the current AGESA release and stepping the 4-DIMM clock speed down to AMD’s conservative 5200 MT/s spec stopped the resets cold. A seven-day burn-in with the RTX 5090 uncapped at its full 600W limit finished without a single hiccup.
Sometimes the bug in your AI stack isn’t in the prompt, the proxy, or the kernel - sometimes it’s just physics on four sticks of RAM.