emergingrobotics/gowyoming: Contribute to emergingrobotics/gowyoming development by creating an account on GitHub.

github.com · 2026-07-15 · by Long Nguyen ·github
Contribute to emergingrobotics/gowyoming development by creating an account on GitHub.

emergingrobotics/gowyoming

Contribute to emergingrobotics/gowyoming development by creating an account on GitHub.

0 stars · 0 forks · Go

README

gowyoming A pure-Go, cgo-free implementation of the Wyoming voice-assistant protocol: the wire framing, a typed event model, and both the client and server roles — plus a satellite composition for edge devices and example programs you can run today. Disclaimer: This works for me — that's the entire guarantee. Built with AI in the loop, so check your own biases before you love it or hate it on principle. Use at your own risk, fork freely, and don't @ me when it explodes. (But do drop me a note if it helps — pay it forward.) What problem this solves Wyoming is the small peer-to-peer TCP protocol the Open Home Foundation (Home Assistant) uses to wire together voice-pipeline services: wake word, speech-to-text (ASR), text-to-speech (TTS), intent handling, and the microphone/speaker "satellite" edge role. It is newline-delimited JSON headers plus raw PCM audio on one socket — simple by design. The only maintained reference library is Python. gowyoming gives Go programs a first-class way to speak Wyoming on both sides of every conversation, without reverse-engineering the wire format:

Client — connect to a Wyoming service, run the describe/info handshake, stream audio for ASR, receive audio from TTS. Server — expose a Go ASR/TTS/wake engine as a Wyoming service. Satellite — a mic + speaker edge endpoint (e.g. a small speaker like PineVoice) that a pipeline connects to and drives.

Prerequisites

Go 1.22 or newer (developed on 1.26). No cgo, no external runtime dependencies. The external-command audio backend optionally shells out to arecord/aplay (or any PCM-piping tool); not required for the file backend or the examples.

Quick start Build everything and run the tests: make build make test # go test -race ./... Describe a live endpoint wyoming-info connects to any Wyoming server and prints what it offers: go run ./cmd/wyoming-info --uri tcp://127.0.0.1:10300 Capture what a device sends (record-server) Point a real device (e.g. a PineVoice satellite) at this server; every utterance it streams is written to disk as a playable WAV, with a one-line summary so wrong gain or silence is obvious: go run ./examples/record-server --uri tcp://0.0.0.0:10300 --out ./out

utt-20260714-150405.wav 1.28s 16000Hz/16bit/1ch peak=8123 rms=1580

Loopback testing framework Two programs that together exercise the whole protocol path — handshake, run-pipeline, audio both directions — with no Python and no hardware: make loopback # runs the in-process integration test

or as two real processes:

go run ./examples/loopback/satellite --uri tcp://127.0.0.1:10700 & go run ./examples/loopback/server --uri tcp://127.0.0.1:10700 Testing with a PineVoice A PineVoice runs its factory firmware as a Wyoming satellite server on port 10700 — it listens, and a pipeline (Home Assistant, or a gowyoming driver) connects to it by IP. It does not connect out to an ASR endpoint, so you talk to it as a client. (This is why record-server, which listens, is not the tool for a PineVoice — see the note below.) 1. Find the device IP. Look in your router's client list, or rely on mDNS. The port is 10700. 2. Describe it — the simplest end-to-end check that gowyoming talks to the real hardware: go run ./cmd/wyoming-info --uri tcp://<pinevoice-ip>:10700

prints the satellite name and its advertised mic/snd formats

3. Capture its microphone to WAV with the driver-recorder. It connects, sends run-pipeline, and writes each utterance the device streams to a timestamped WAV in --out: go run ./examples/satellite-record --uri tcp://<pinevoice-ip>:10700 --out ./out

trigger the device (wake word or button); after it streams audio you'll see:

utt-20260714-150405.wav 1.36s 16000Hz/16bit/1ch 34560 bytes

Then play a captured file back to confirm the device sent the audio you expect: aplay ./out/utt-*.wav # or open it in any audio player Each utterance's summary prints peak and rms (out of a 32767 full scale), so you can see how hot the signal actually is. When exactly the device streams depends on its firmware — many satellites stream only after local wake-word detection or a button press, so trigger it after the recorder connects. Stop the recorder with Ctrl-C; any in-progress utterance is still flushed to disk. If the recording is too quiet gowyoming records the device's PCM byte-for-byte, so a quiet file means the device is sending a low-level signal. In order of preference:

Raise gain at the source. If the PineVoice exposes a microphone gain or volume-multiplier in its settings, increase that first — more input gain lifts the signal without amplifying the noise floor. Normalize on capture. Scale each utterance's peak up to near full scale: go run ./examples/satellite-record --uri tcp://<ip>:10700 --out ./out --normalize

Apply a fixed digital gain when you want a specific multiplier: go run ./examples/satellite-record --uri tcp://<ip>:10700 --out ./out --gain 4

--gain/--normalize operate on 16-bit PCM and clip at full scale. They amplify noise along with speech, so they are a convenience for inspection, not a substitute for good input gain. The default (--gain 1.0, no normalize) captures the device's audio unchanged.

Note — record-server is the inverse topology. record-server listens and advertises ASR, for a client that connects out to an ASR endpoint and pushes audio. A PineVoice does not do that (it is itself a server), so use satellite-record for PineVoice. record-server remains useful for testing a client/mic service that streams to an ASR endpoint.

Configuration The example binaries take flags, not config files:

Binary Flag Default Meaning

wyoming-info --uri tcp://127.0.0.1:10300 endpoint to describe

satellite-record --uri / --out / --gain / --normalize tcp://127.0.0.1:10700 / ./out / 1.0 / false satellite to connect to (e.g. a PineVoice) / WAV output dir / digital gain / peak-normalize

record-server --uri / --out tcp://0.0.0.0:10300 / ./out listen address / WAV output dir

loopback/satellite --uri / --audio tcp://0.0.0.0:10700 / fixture listen address / WAV to stream as mic

loopback/server --uri / --audio tcp://127.0.0.1:10700 / fixture satellite to drive / fixture to validate against

URIs are tcp://host:port or unix:///path/to/socket. Connection topology In the Home Assistant model the roles are inverted from intuition: the satellite runs the server and Home Assistant connects in and drives it.

flowchart LR subgraph Edge["Satellite host (edge)"] MIC[mic] --> SAT[Satellite<br/>listens :10700] SAT --> SND[speaker] end HA[Home Assistant<br/>pipeline driver] -->|connects in<br/>run-pipeline| SAT HA -->|client| ASR[ASR server] HA -->|client| TTS[TTS server]

Loading

Using the library Client — describe and transcribe: c, _ := client.Dial(ctx, "tcp://127.0.0.1:10300") defer c.Close() info, _ := c.Describe(ctx) transcript, _ := c.Transcribe(ctx, nextChunk) // nextChunk yields wyoming.AudioChunk Server — expose a Go ASR engine: type myASR struct{} func (myASR) Transcribe(ctx context.Context, audio <-chan wyoming.AudioChunk) (wyoming.Transcript, error) { // consume audio, return text } srv := server.New(server.NewASR(info, myASR{})) srv.ListenAndServe(ctx, "tcp://0.0.0.0:10300") Raw events (the hybrid model) — the canonical *wyoming.Event is always available; Decode gives a typed value when you want to switch: switch e := must(wyoming.Decode(ev)).(type) { case wyoming.Transcript: use(e.Text) case wyoming.AudioChunk: play(e.Audio) } Project structure wyoming/ core: framing (Event/Reader/Writer), typed events, Decode, Info transport/ tcp:// and unix:// dial/listen (the only net-using package) client/ client role: Dial, Describe, Synthesize, Transcribe server/ server role: Handler, Server, NewASR/NewTTS domain helpers satellite/ edge composition: mic Source + speaker Sink + pipeline glue audio/ Source/Sink with WAV-file and external-command backends cmd/ wyoming-info CLI examples/ loopback framework, satellite-record (PineVoice capture), record-server, testdata

Design rationale lives in VISION.md; the decision log and API design are in docs/superpowers/specs. Known limitations (v1)

No local wake word. satellite.Config.Wake is refused explicitly; wake is handled server-side. Local wake is a planned increment. No TLS/auth. The protocol defines none; rely on network trust. No version header. Real Wyoming peers include an optional version key in the event header; gowyoming omits it on write and ignores it on read. This does not affect interop (the field is optional for readers) but is noted for completeness. No cgo audio backend. Real device I/O goes through the external-command backend (arecord/aplay) or your own Source/Sink.

License MIT — see LICENSE.

source

github.com · 107 links

Comments (0)

No comments yet