Kegboard is the kegerator controller firmware from the Kegbot project. It reads flow meters, temperature sensors, and auth tokens, drives valves and relays, and reports pours to a Kegbot Server.
This is Kegboard v4, a ground-up rewrite for ESP32 built on ESPHome.
Looking for the Arduino version? The v2/v3 firmware lives on the
arduinobranch. It is feature-frozen and does not speak the same protocol as this one.
📖 Documentation — overview,
operating modes, installation, wiring, configuration, and the protocol specs.
Sources in docs/.
The old Kegboard was a dumb sensor pipe: it streamed raw tick counts over USB
serial, and a host daemon (kegbot-pycore) assembled those ticks into pours and
posted them to the server. That meant a cable, a always-on host, and a lost pour
whenever the host was down.
Kegboard v4 is a networked appliance:
- Pours are assembled on the device. The board detects the start and end
of a pour, applies its own calibration, and reports a finished pour with an
authoritative
volume_ml. - One simple protocol. Everything is JSON batches to a single endpoint
(
POST /kegboard-event), specified in docs/kegboard-event-protocol.md with normative JSON Schemas. Any server can implement it. - Outages are survivable. Events queue and deliver late with correct timestamps — even on a board whose clock never synced — and retries can never create a duplicate drink.
- No API keys to carry around. An unprovisioned board shows up on the server dashboard by name; click allow and it provisions itself.
- No
kegbot-pycore, no USB cable. WiFi and HTTP. - It's ESPHome. Adding a display, a pressure sensor, a second thermometer, or a different RFID reader is YAML you write, not a firmware release we ship.
A minimal two-tap board reporting to a server:
external_components:
- source: github://Kegbot/kegboard@main
kegboard:
id: kb
kegboard_meter:
- id: flow0
pin: GPIO4
meter_number: 0
total:
name: Tap 1 Ticks
pouring:
name: Tap 1 Pouring
kegboard_reporter:
reporting_url: !secret kegboard_reporting_url
meters: [flow0]See examples/ for complete configs, and boards/ for pin maps.
| Option | Default | Notes |
|---|---|---|
serial_number |
kegboard-<mac> |
Device identity in the protocol. Set explicitly to adopt a replaced board's identity. |
| Option | Default | Notes |
|---|---|---|
pin |
required | Meter input. Pulled up internally; counts falling edges. |
meter_number |
0 |
The protocol's meter number: (device, meter_number) identifies a tap server-side. Must be unique per meter (validated at build). The YAML id is a config-internal reference and is never reported. |
ml_per_tick |
0.185 |
SwissFlow SF800 and clones (~5.4 ticks/mL). The device's calibration is authoritative: reported volume comes from this. |
debounce |
1200us |
Matches the legacy firmware's filter. |
idle_timeout |
10s |
Silence after which a pour is considered finished. |
min_pour_ticks |
3 |
Anything shorter is treated as a drip and discarded. |
max_pour_duration |
5min |
Safety cutoff for a stuck meter; 0s disables. |
report_interval |
250ms |
Throttle for sensor updates during a pour. |
series_resolution |
100ms |
Bucket width for the diagnostic tick series; 0s disables. |
Optional entities: total, volume, flow_rate, pouring.
Triggers: on_pour_start, on_pour_end (with ticks, volume_ml, duration_ms).
Actions: kegboard_meter.reset_total, .end_pour, .set_calibration.
Speaks the Kegboard Event Protocol.
| Option | Default | Notes |
|---|---|---|
reporting_url |
required | Full URL, path included, e.g. https://kegbot.example.com/api/kegboard-event. No credential is configured — the device provisions its own bearer token by pairing via the server dashboard, and it persists in flash. |
meters |
[] |
Meters whose pours are reported. |
relays |
[] |
The device's numbered relays: relay_number: plus relay:. Reported in the status inventory, and the targets of server grants. |
thermo_sensors |
[] |
sensor:/name: pairs; any ESPHome sensor works. |
heartbeat_interval |
60s |
Status event cadence; also bounds worst-case command latency. |
pour_update_interval |
1s |
Live pour_update cadence; 0s disables. |
retry_interval |
30s |
Base for exponential backoff, capped at 5 min. |
Optional diagnostic entities: queue_depth, dropped. A non-zero dropped
means events were lost and is worth alerting on.
Applies authenticated pouring:
server-decided grants driving valve relays and tagging pours for server-side
attribution. Requires a kegboard_reporter. (Serverless installs can gate
valves with plain ESPHome automations on the reader triggers instead.)
| Option | Default | Notes |
|---|---|---|
offline_policy |
deny |
Token presented while the server is unreachable: deny (signal refusal), or guest (stay silent; pours proceed as guest pours). Neither opens valves. |
max_grant_duration |
5min |
Device-side clamp on server-issued grants: the final bound on valve-open time. |
Actions: kegboard_auth.token_attached / .token_detached (device,
token), .revoke. Condition: .is_authorized. Triggers: on_authorized
(auth_device, token), on_denied (reason), on_revoked. Optional
entities: authorized.
iButton presence on a 1-Wire bus — ESPHome's one_wire enumerates devices but
has no arrive/leave events. Triggers on_token_attached and
on_token_detached with the ROM code as hex.
max_missed_searches (default 4) is how many consecutive misses before a
detach is reported. A held iButton makes intermittent contact, so reporting on
the first miss would make it flap several times a second.
packages/relays.yaml and packages/buzzer.yaml are plain YAML over stock
components. The relay watchdog is worth keeping from the AVR firmware: a relay
left on is usually a valve held open, so each one switches itself off
relay_watchdog_timeout (default 10s) after turning on. On grant-driven
relays, set it longer than max_grant_duration (the grant clamp is their
bound) or the watchdog closes the valve mid-grant.
components/ ESPHome external components (this repo is the component source)
kegboard/ Hub component + the framework-agnostic core (see CORE.md)
kegboard_meter/ Flow meter and pour detection
kegboard_reporter/ Event protocol client (batching, pairing, commands)
kegboard_auth/ Per-meter authorization
kegboard_onewire/ iButton presence
packages/ Composable YAML users include
boards/ Pin maps per target board
docs/ Manual + protocol specifications
schemas/ Normative JSON Schemas for the protocol
examples/ Worked configurations
tests/core/ Host unit tests -- plain g++, no hardware, no toolchain
script/ CI helpers
Host unit tests for the core logic need nothing but a C++ compiler:
$ make -C tests/core
$ make -C tests/core STRICT=1 # warnings as errors, as CI runs itBuild output is scoped by OS and architecture (build/Darwin-arm64/, etc.), so
a checkout shared between a host and a container or VM won't hand one
platform's binaries to the other.
Formatting and lint mirror ESPHome's own conventions, since these components compile into ESPHome's tree:
$ pip install pre-commit && pre-commit install
$ pre-commit run --all-filesThe manual is a Sphinx project in docs/, published at
docs.kegbot.org/projects/kegboard.
Its toolchain is managed by uv, synced
automatically on first use:
$ make -C docs html # output in docs/_build/html/
$ make -C docs livehtml # live-rebuild server while editingtools/kegboard-sim.py is a TUI kegboard for developing receivers without
hardware. It speaks the full protocol — pairing, batching, age_ms,
commands, dedup — and validates every outgoing batch against the schemas, so
it cannot teach a server the wrong protocol.
$ uv run tools/kegboard-sim.py http://localhost:8000/kegboard-eventSingle keys drive it: pour a beer (with live pour_updates), toggle
temperature logging, present preset tokens (including an unknown fob and a
presence-style iButton), kill the heartbeat so your server's liveness check
can notice, go offline to build a backlog that delivers late with correct
ages, replay the last batch verbatim to exercise dedup, send an
unknown event type, and reboot to reset boot_id.
The reporter logs one line per delivery at DEBUG (status, event count,
bytes). To see the actual protocol traffic — every request and response body,
including pour_updates while beer is flowing — raise its log level to
VERY_VERBOSE:
logger:
logs:
kegboard_reporter: VERY_VERBOSEThen attach with esphome logs <config>.yaml. Two notes:
VERY_VERBOSElines are compiled out at default log levels, so production builds pay nothing for this machinery.- The logger truncates lines to its buffer (default 512 bytes); a full batch
body will clip. Add
logger: { tx_buffer_size: 2048 }if that bites.
ESP32 GPIOs are not 5 V tolerant. Most beer flow meters are open-collector hall-effect sensors, which are safe on a 3.3 V pull-up because they only ever pull the line to ground. Meters with a push-pull 5 V output will damage the ESP32 and need a level shifter or divider. Check your meter before wiring it.
Kegboard was created in 2004 and has received several major updates over the years:
- v1 (2004) — PIC16-based, written in the JAL programming language.
- v2 (~2009) — rewritten for Arduino, introducing the KBSP serial protocol.
- v3 (2014) — the Kegboard Pro Mini, a fully-assembled Arduino-based board.
- v4 (2026) — this ground-up ESP32 rewrite on ESPHome.
Kegboard v4 is offered under the MIT license, matching the rest of the
Kegbot project; see LICENSE.txt.
Two notes on what that does and doesn't cover:
- Built firmware images are GPLv3. ESPHome's C++ runtime is GPLv3, and a compiled Kegboard image links against it. MIT is GPL-compatible, so this is fine and is how ESPHome external components normally work — but if you distribute binaries, you are distributing GPLv3 binaries. The source in this repository remains MIT and is reusable as such.
- The
arduinobranch is still GPLv2-or-later. The legacy AVR firmware was written under that license and had outside contributors; nothing here relicenses it.
Copyright 2003-2026 The Kegbot Project Contributors info@kegbot.org