cd /news/developer-tools/pinevoice-golang-wyoming-protocol · home topics developer-tools article
[ARTICLE · art-60494] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

PineVoice – Golang Wyoming Protocol

A developer released gowyoming, a pure-Go implementation of the Wyoming voice-assistant protocol, enabling Go programs to act as clients, servers, or satellites for Home Assistant's voice pipeline without Python dependencies. The library supports audio streaming for ASR and TTS, and includes example programs for PineVoice hardware integration.

read6 min views1 publishedJul 15, 2026
PineVoice – Golang Wyoming Protocol
Image: source

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.)

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.

  • 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.

Build everything and run the tests:

make build
make test        # go test -race ./...

wyoming-info

connects to any Wyoming server and prints what it offers:

go run ./cmd/wyoming-info --uri tcp://127.0.0.1:10300

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

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

go run ./examples/loopback/satellite --uri tcp://127.0.0.1:10700 &
go run ./examples/loopback/server   --uri tcp://127.0.0.1:10700

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

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

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.

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

listensand 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 usesatellite-record

for PineVoice.record-server

remains useful for testing a client/mic service that streams to an ASR endpoint.

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

.

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]

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)
}
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

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 Real Wyoming peers include an optionalversion

header.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 ownSource

/Sink

.

MIT — see LICENSE.

── more in #developer-tools 4 stories · sorted by recency
── more on @gowyoming 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/pinevoice-golang-wyo…] indexed:0 read:6min 2026-07-15 ·