Loading files…
ESP-IDF + PlatformIO firmware for a 6-tube multiplexed nixie clock, replacing
the original ESPHome config (nixieclock.yaml, kept for reference). WiFi is used
for time sync and remote control; the clock keeps running (and keeps time, via
its RTC) with no network.
New here? See GUIDE.md — a bilingual (English / Čeština) user guide with a For everyone section (setup, buttons, web page) and a Power users section (REST, MQTT/Home Assistant, building the firmware).
ESP32 (WROOM-32) on the UCB32 controller board driving the Unidisp display board:
Full pin map: components/common/include/pins.h.
Printable enclosure (case, bezel, panels, TPU feet) + print/assembly notes:
3D_Files/.
Requires PlatformIO (CLI or the VS Code ext).
pio run # build
pio run -t upload # build + flash over USB
pio device monitor # serial console @ 115200
PlatformIO downloads the ESP-IDF 5.5 toolchain and the managed components
(espressif/onewire_bus, espressif/ds18b20) automatically on first build.
After changing a component's idf_component.yml or REQUIRES, do a clean
configure (rm -rf .pio/build/esp32dev) so the component manager re-runs.
The board has no auto-reset: hold IO0, tap EN, release IO0 to enter download mode before
pio run -t upload.
The flash is a dual-OTA layout (two ~1.875 MB app slots + otadata), so after the
first serial flash you can update firmware over WiFi and never cable the clock
again:
pio run to build a new .pio/build/esp32dev/firmware.bin.firmware.bin → Upload.ota_0/ota_1), which flips after each successful update.A failed or interrupted upload leaves the current firmware bootable — the new slot is only made active once the whole image is written and verified. Settings and WiFi/MQTT credentials live in NVS and are untouched by updates.
Credentials live in NVS (set via the SoftAP portal or the web UI).
components/common/include/secrets.h (gitignored) is a first-boot fallback
only: once a WiFi SSID is stored in NVS the device trusts NVS exclusively (an
empty MQTT host means no MQTT — the client goes idle), and a factory reset
forces SoftAP setup on the next boot regardless of secrets.h.
To seed defaults at compile time, copy the template:
cp components/common/include/secrets.example.h components/common/include/secrets.h
# edit WIFI_SSID / WIFI_PASS / MQTT_*
Or skip secrets.h entirely and provision over WiFi (see below).
To set credentials without editing code (e.g. when lending the clock):
NixieClock-XXXXXX WiFi using that PIN as the WPA2
password — so only someone looking at the clock can connect.WiFi/MQTT can also be changed live from the web UI's Network section (MQTT applies immediately; WiFi on next reboot).
| Button | Short press | Long hold |
|---|---|---|
| 1 | Brightness up | — |
| 2 | Brightness down | — |
| 3 | Show temperature now | — |
| 4 | Anti-poison scrub now | — |
| 5 (provision) | Show IP address on tubes | ~3 s: provisioning · ~10 s: factory reset |
| Snooze | Snooze ringing alarm | Dismiss alarm (until tomorrow) |
Bonus combo: hold brightness + and − together (~2 s) toggles the alarm on/off.
All three share one settings store, so changes from any surface are reflected everywhere instantly:
http://<clock-ip>/ : brightness, night schedule, anti-poison
(interval, length, speed, 5 animations + mix, pause-for-interludes),
alarm (incl. day-of-week + melody + snooze), temperature slots/decimals, and
network config. Tap button 5 to see the IP on the tubes.nixie-<mac>). Topics under nixieclock/<node>/…, HA discovery under
homeassistant/….GET /api/state, POST /api/set?k=<key> (value in body) or
the GET /api/set?k=…&v=… alias, POST|GET /api/action?a=<action>,
GET|POST /api/settime?epoch=…&tz=… (set the clock from a client),
GET /api/net, POST /api/netset, POST /api/ota (firmware upload),
GET /api/log, GET /api/blescan, GET /api/shellytime.shelly_sync action).shelly_time_daily), which yields to NTP: it only
acts when SNTP hasn't synced for 24 h.GET /api/shellytime lists each sender with its offset from
the clock.DDMM : DoW (day, month, then the ISO weekday after the colon). Night
dimming in sun mode also takes a twilight offset (stay bright longer into
dusk/dawn).firmware.bin from the web UI (dual-OTA
slots); no serial needed after the first flash.GET /api/log, since the assembled
clock has no serial access.platformio.ini build config (env: esp32dev), custom partition table
partitions.csv dual-OTA: 2x ~1.875 MB app slots + otadata + NVS + SPIFFS (4 MB flash)
sdkconfig.defaults ESP-IDF defaults
src/main.c app entry: orchestration + display draw loop
components/
common/ shared pin map + secrets (header-only)
nixie/ GPTimer multiplex display driver + PWM brightness
i2cbus/ shared I²C master + bus scanner
rtcdev/ RTC driver (PCF8563 / DS1307 / DS3231 auto-detect)
netclock/ WiFi STA + SNTP
netcfg/ WiFi/MQTT credential store (NVS + secrets fallback)
provision/ SoftAP captive-portal WiFi provisioning
settings/ persisted settings blob (NVS) + change versioning
control/ shared key=value apply + state-JSON core
mqttctrl/ MQTT client + Home Assistant autodiscovery
webui/ self-hosted web UI + REST/JSON API
temps/ temperature slots + DS18B20 (RMT 1-Wire)
blescan/ NimBLE passive scan → Shelly BLU H&T (BTHome v2)
shellytime/ Shelly BLE time beacon → optional clock sync
bh1750/ optional ambient-light sensor (I²C) for lux night mode
weblog/ esp_log → RAM ring buffer, read over /api/log
buttons/ multiplexed-button debounce + classification
alarm/ alarm state machine
buzzer/ LEDC tone + RTTTL player
antipoison/ anti-cathode-poisoning routine
Architecture note: control is the single place that maps a setting key=value
and renders the state JSON; MQTT, the web UI, and REST all go through it, so
adding a setting touches one switch and one JSON builder, not three.
This project uses reciprocal ("share-alike") open licenses — you may use, copy, and modify it, including commercially, but you must publish your modifications to whichever part you change:
LICENSE at the repo root).PCB_Sources/ucb vibecheck_final/) —
CERN-OHL-S v2 (CERN Open
Hardware Licence, Strongly Reciprocal).Third-party content keeps its own license: the Unidisp display board
(PCB_Sources/Unidisp-main/) is licensed by its author under CERN-OHL-W (Weakly
Reciprocal) — see its bundled LICENSE. The original ESPHome reference
(nixieclock.yaml) is retained only for reference.
This started as a university project. The Unidisp digitron display board — the HV supply, multiplexing, and tube driving — is the work of @Unipuls80, along with his original Unidisp and an MH106 ASIC-based controller (ucb106).
The UCB32 controller board and this ESP32 firmware are my "modern" take on driving the same display — by Michal Basler (@Majklzbastlirny).
No comments yet. Be the first to ask about this board.