Fork of BeerTapRoundDisplay that separates the scale (HX711 + weight sensors) from the round GC9A01 display. The two boards communicate over Bluetooth Low Energy instead of being wired together on a single ESP32. ─────────────────────────┐ BLE (GATT) ┌──────────────────────────┐ │ scale-esp32c6 │ ────────────────────► │ display-esp32 │ │ ESP32-C6 + HX711 │ notify weight (g) │ ESP32-C3 + round touch │ │ under the keg │ ◄──────────────────── │ Wifi + WebServer + OTA │ │ │ tare command │ computes volume │ └─────────────────────────┘ └──────────────────────────┘
display-esp32 now targets an all-in-one ESP32-C3 + 1.28" round GC9A01 touch display module (sold on AliExpress under the PCB reference "JCZN ESP32-2424S012"), instead of a bare ESP32 wired to a separate GC9A01 breakout. Long-pressing the screen sends a tare command, same as the "Tare" button in the web UI.
- The scale (HX711 + load cells) stays as close as possible to the keg, under its foot, far from the display, which is mounted remotely on the cabinet/fridge front panel.
- The ESP32-C6 is a natural fit for this node: small, cheap, native BLE, no need for Wifi/display/large flash on the scale side.
- The display keeps the existing Wifi/OTA/web server (beer config, IBU, ABV, SG...) and shows a status dot (green/orange/red) indicating how fresh the BLE link is.
scale-esp32c6/ Scale firmware (HX711 -> BLE GATT server)
display-esp32/ Display firmware (BLE GATT client -> display + Wifi/webserver)
These are two independent PlatformIO projects, flashed separately (two platformio.ini files).
The scale (scale-esp32c6) is the BLE server (peripheral); the display (display-esp32) is the BLE client (central). There's no pairing/bonding (no sensitive data is exchanged) — the connection is made by filtering on the service UUID.
| Element | UUID | Type | Content |
|---|---|---|---|
| Scale service | a1f4e100-3b7a-4f56-9d1b-2f6a9c5e10a0 |
- | - |
| Weight characteristic | a1f4e101-3b7a-4f56-9d1b-2f6a9c5e11a0 |
READ + NOTIFY | float32 weight in grams |
| Command characteristic | a1f4e102-3b7a-4f56-9d1b-2f6a9c5e12a0 |
WRITE | uint8: 0x01 = tare |
These values are defined in scale-esp32c6/include/config.h and display-esp32/include/config.h (must stay identical on both sides).
Behavior:
- The scale measures weight every
WEIGHT_UPDATE_PERIOD_MS(2s by default) and notifies the value if a client is connected. - The display scans, connects to the first device advertising the service above, and automatically resubscribes on disconnection (
BLE_RECONNECT_PERIOD_MS). - Volume (weight / configured SG density on the display) is still computed on the display side, as before — the scale only reports raw weight.
- A "Tare" tap in the display's web UI (
POST /tare) sends the command to the scale, which performsscale.tare(). GET /scale_statusreturns{"connected":bool,"stale":bool}for external monitoring.- On the display, a dot at the top of the dial shows status: green = fresh data, orange = connected but stale data (>
BLE_WEIGHT_STALE_MS= 15s), red = scale not connected.
| HX711 | ESP32-C6 |
|---|---|
| DOUT | GPIO2 |
| SCK | GPIO3 |
| VCC | 3.3V (or 5V depending on HX711 module) |
| GND | GND |
GPIO2/3 were chosen because GPIO4, 5, 8, 9, 15 are boot strapping pins on the ESP32-C6 and must be avoided. Adjust SCALE_CALIBRATION_FACTOR in scale-esp32c6/include/config.h for your own load cell (same calibration procedure as the original: weigh a known weight and adjust the factor).
scale_setup()/scale_tare()/getWeight() use HX711::wait_ready_timeout() (1s) instead of the library's plain tare()/get_units(), which otherwise block forever if the HX711 never pulls DOUT low (not wired yet, loose connection...). Without this guard, a disconnected load cell hangs setup() before BLE ever starts advertising - with it, the scale still comes up and reports 0.0.
This project uses the pioarduino platform fork (the official PlatformIO espressif32 registry entry dropped Arduino framework support for ESP32-C6). Two Windows-specific gotchas:
- Build from a native shell (cmd/PowerShell), not Git Bash/MSYS - pioarduino's
idf_tools.py/esptool install step explicitly refuses to run under MSYS ("MSys/Mingw is not supported"). - Set the console to UTF-8 first (
chcp 65001incmd/PowerShell, plus$env:PYTHONIOENCODING="utf-8") - esptool's progress bar otherwise crashes PlatformIO's output relay under the defaultcp1252codepage.
Runs on an ESP32-C3 all-in-one module with a built-in 1.28" round GC9A01 display and CST816 capacitive touch, everything on one small board/PCB (no separate breakout or wiring). Pins (see display-esp32/include/config.h):
| Function | Pin |
|---|---|
| TFT MOSI | GPIO7 |
| TFT SCLK | GPIO6 |
| TFT CS | GPIO10 |
| TFT DC | GPIO2 |
| TFT RST | not connected (module is reset over SPI) |
| TFT Backlight | GPIO3 |
| Touch SDA | GPIO4 |
| Touch SCL | GPIO5 |
| Touch RST | GPIO1 |
| Touch INT | GPIO0 |
The touch controller (CST816, I2C address 0x15) is handled by display-esp32/src/touch.cpp using the fbiego/CST816S library: a long-press anywhere on the screen sends a tare command to the scale over BLE (a plain tap is deliberately ignored, so brushing the screen doesn't accidentally zero it).
The HX711 is no longer wired on this board (moved to scale-esp32c6).
display-esp32/data/ (served over Wifi at the device's http://<ip>/ or
http://BeerTapDisplay.local/) is a from-scratch dark/OLED-themed control panel,
implemented from a Claude Design mockup
("BeeTap Control", project e4750120-6cf9-463d-8540-04c588b6e35e) against the
device's real REST API - plain HTML/CSS/JS, no framework, no CDN dependency (system
fonts only, so it works with zero internet access, same as the rest of the device).
Replaces the previous jQuery + w3.css UI, which alone was ~330KB of the ~376KB
data/ folder (mostly the bundled jQuery copy) - the new UI is a fraction of that.
| Home | Settings |
|---|---|
![]() |
![]() |
- Home tab: live weight + circular level gauge (colour-coded green→red, same spirit as the physical display's own ring meter), Tare button, "full keg" capacity estimate (used only to compute the gauge %), and the beer profile form (name, SG, IBU, EBC, ABV)
- Settings tab: Wifi status + Reset Wifi, firmware/filesystem OTA upload (via
ElegantOTA's real
/ota/start+/ota/uploadAPI, with a progress bar), and a Restart button - New backend endpoints added to
setup.cppto support it:GET /beer_profile- JSON{name, sg, ibu, ebc, abv, capacity}, single read to populate the form (existing per-fieldPOSTendpoints are unchanged, used for saving)POST /beer_capacity- same pattern as the existing per-field beer endpoints; backed by a newgetBeerCapacity()/setBeerCapacity()pair inconfig.cpp(Preferences, default19.0) - the capacity estimate persists on the device like every other beer field, rather than only in the browserPOST /restart-ESP.restart()after a short delay so the response reaches the client firstPOST /reset_wifi- clears saved Wifi credentials (WiFiManager::resetSettings(), wrapped asresetWifiSettings()inwifi_setup.cppto keepWiFiManager.hout ofsetup.cpp- see the comment onconnectWifi()about theHTTP_GET/HTTP_POSTenum collision with ESPAsyncWebServer) then reboots into theTapDisplay_APportal- The old
/param.xml+/wparamxmlroutes (a stale, unsynced static XML file) were removed, superseded by/beer_profile
Each folder is a standalone PlatformIO project:
cd scale-esp32c6
pio run -t upload # ESP32-C6 over USB
cd ../display-esp32
pio run -e esp32-c3-usb -t upload # ESP32-C3 firmware, first flash over USB
pio run -e esp32-c3-usb -t uploadfs # ESP32-C3 filesystem image (data/ folder - web UI, beer BMPs)
pio run -e esp32-c3-ota -t upload # subsequent firmware flashes over Wifi/OTA (set upload_port in platformio.ini to the display's IP/hostname)
display-esp32 keeps OTA (ArduinoOTA + ElegantOTA) as in the original project; scale-esp32c6 is flashed over USB (no OTA, no Wifi on this board).
The filesystem image (uploadfs) is required, not optional - without it SPIFFS fails to mount, initialize() returns before ever calling display_init(), and the screen stays blank.
Both boards talk USB through the chip's built-in USB-Serial/JTAG controller (no external CP210x/CH340 bridge), which has no reliable auto-reset circuit:
- Entering bootloader mode (before every
upload/uploadfs): hold BOOT, tap RESET while still holding BOOT, then release BOOT. - Returning to normal run mode (after every flash): a plain RESET tap is not reliable either - fully unplug/replug the USB cable to guarantee a clean boot into the app.
- Opening a serial monitor can itself reset the board back into bootloader (default DTR/RTS assertion on connect is misread as a reset request). If the monitor shows nothing at all right after a flash, reopen it with
pio device monitor -p COMx -b 115200 --rts 0 --dtr 0before assuming the firmware is broken.
Hardware-verified on real boards (2026-07): display-esp32 boots, connects to Wifi (falls back to the TapDisplay_AP WiFiManager captive portal if no credentials are saved), shows the beer dial, and long-press-to-tare over touch. scale-esp32c6 advertises over BLE, the display connects (status dot green), and the tare command round-trips correctly (Tare requested by display logged on the scale on each long-press) - tested without load cells physically wired.
- The
Case/andPCB/files from the original repo (3D enclosure, Eagle PCB) were designed for the combined display+HX711 board: they'll need to be redesigned/split into two enclosures (not addressed in this version, out of software scope). - No BLE pairing (LE Secure Connections): fine for home use where only weight is transmitted, but worth keeping in mind if the protocol evolves.


