OR OPEN RADIO · CORE2
SHEET 1/1 · PRE-RELEASE PL
Polish radio stations, anywhere on Earth/M5Stack Core2/ playing on my desk

Polish radio that
just plays.

My family and friends abroad like having Polish radio on in the background. Until now that meant paid apps or pricey dedicated receivers, so I built a cube that plays nine Polish radio stations over Wi-Fi and Bluetooth. The system image is free — you can build your own.

ASSEMBLY DRAWING OR-C2 LAB
54 mm 54 mm 16 mm USB-C 5 V ESP32 2 × 240 MHz 16 MB FLASH · 8 MB PSRAM SPEAKER 1 W WI-FI 2.4 GHz + BT CLASSIC OR-C2 · SCREENS: 1:1 EMULATOR
last capture: output BTstarvation 0watchdog 0queue full
A1
9
radio stations
A2
~€50
all the hardware, no soldering
A3
320×240
touch IPS 2.0″
A4
0
accounts · cloud · telemetry
A5
2.75B
AI tokens in production
A6
100+
engineering documents
01 / scope

What this cube is, and what it deliberately isn't

Open Radio started from one premise: buy ready-made all-in-one hardware and write the entire system for it. We froze the feature set on purpose.

Is

  • A system for off-the-shelf hardware. M5Stack builds the hardware, we build the rest: firmware, GUI, recovery, station catalog. No soldering iron, no 3D printer.
  • A trade-off between cost and performance. ESP32 versus streaming in 2026. All the engineering in this project flows from that mismatch.
  • Portable and dead simple to use. Three taps along the bottom of the screen: previous station, grid, next. A device you understand in ten seconds — and it plays up to 2 h unplugged on its built-in battery.
  • As hands-off as possible. Network, stream, speaker, configuration: anything that can fail retries itself, no reboot and no phone required.
  • Deliberately narrow in scope. Every kilobyte of DRAM is accounted for, and Wi-Fi and Bluetooth fight over one radio. More features simply don't fit in this silicon.

Isn't

  • High-end audio. 128 kbps MP3 over SBC to a Bluetooth speaker or the built-in 1 W driver. For the kitchen, the workshop, the balcony — not for a listening room.
  • A platform for everything. Nine stations and a closed feature set. No Spotify, no podcasts, no alarm clock, no phone control.
  • A configuration monster. Settings fit on three screens. No hidden files, no expert mode.
  • A cloud product. The only outbound traffic is station streams and optional NTP. The radio doesn't know what you're listening to, and has no one to tell.

Nine of Poland's biggest broadcasters — from Antyradio's rock to the club mixes on RMF MAXX Club. The cube plays their official, public MP3 streams straight from the broadcaster: no retransmission, no recording, no middlemen. As far as the station can tell, it's just one more tick on the listener counter, only in a nicer box. The links go to the official sites.

02 / hardware

One box, off the shelf

MCUESP32-D0WDQ6-V3 · 2 Xtensa LX6 cores @ 240 MHz
Memory520 KB SRAM + 8 MB PSRAM · 16 MB flash
DisplayIPS 2.0″ · 320 × 240 · capacitive touch
AudioI2S NS4168 amplifier + 1 W speaker
RadioWi-Fi 2.4 GHz b/g/n + Bluetooth Classic · A2DP source (SBC)
PowerUSB-C · built-in 380 mAh battery — up to 2 h unplugged
Enclosure54 × 54 × 16 mm · stock, factory-made
Why M5Stack Core2

Because it's the only path to €50 without soldering. Screen, touch, speaker, battery, enclosure and both radios are all included — you pull it out of the box and flash it.

You pay for it in one currency: Wi-Fi and Bluetooth share a single 2.4 GHz radio and have to time-share it. Most of the entries in the log below trace straight back to that one purchasing decision.

The Core2 is one piece of a bigger M5Stack ecosystem: display-equipped Core cubes, smaller Atom and StickC units, and dozens of stackable modules — all built on ESP32, all with public documentation. We didn't add anything to this project: one cube, straight out of the box.

03 / audio path

From the CDN to the speaker cone

SIGNAL FLOW OR-C2/A 44.1 kHz
STATIONCDN · MP3 128 kbps WI-FI / TCPneeds 16 KB/s MP3 DECODER8192 frames / pass PCM QUEUE≈ 6 s of headroom · 44.1 kHz BT A2DP · SBCreconnect 8–20 s BT SPEAKERyours, remembered SPEAKER 1 Wbuilt-in fallback BT failure → sound keeps playing locally
MP3 128 kbps needs 16 KB/sWi-Fi under active BT delivers 12–14 KB/sthat gap = the project's main antagonist → LOG-01
04 / build log

Nerd stuff: what actually happened

Selected entries from the engineering log. Every number comes from telemetry dumped off the device during multi-minute test runs.

LOG-01PHYSICS

One radio, two jobs

Wi-Fi and Bluetooth share one 2.4 GHz radio on the ESP32 and time-slice the same antenna. When A2DP is transmitting, the TCP window (~5.7 KB — an Arduino LWIP compile-time constant) multiplied by an RTT stretched to 350–500 ms gives you 12–14 KB/s. A 128 kbps MP3 stream needs 16. The deficit isn't a bug, it's arithmetic.

LOG-02GOTCHA

WiFi.setSleep(false)

Classic forum advice for speeding up Wi-Fi on the ESP32. With Bluetooth active, the controller hits abort() and the cube boot-loops every 26 seconds. Modem-sleep stays; the coex layer requires it.

LOG-03PHYSICS

Burst-equilibrium

The CDN hands over the first few seconds of a stream in a fast burst, then settles into exactly 1× realtime. Any buffer threshold set above the equilibrium point will never be reached — the radio waits forever to "finish buffering." Every reserve threshold had to come down below it.

LOG-04DISCOVERY

Decoder budget ≠ bandwidth

Tripling the decoder's work per loop pass didn't add a single audio frame (the deficit lives in the network), but it stretched the pass to 1.6 s and killed touch response. Under a network deficit, the decoder budget only governs UI responsiveness. We set it back to 8192 frames per pass.

LOG-05BUG

The rotation that didn't rotate

The stream's death paths never cleared the active endpoint, so "candidate rotation" had been reopening the same dead URL over and over. Symptom: 81 restarts in 3 minutes. The fix came down to clearing the endpoint in three places.

LOG-06RAM

TLS versus Bluetooth

A TLS handshake needs ~40 KB of free DRAM. With Bluetooth playing, only 25–30 KB is left, so switching to a station that needs HTTPS discovery mid-playback is physically impossible. The fix: pinned plain-HTTP fallbacks per station, plus a cache of the last source that worked.

LOG-07BT

The pairing window

After a power cycle, the speaker doesn't politely wait for a scan — it calls the cube itself. And the cube was rejecting strangers. A standoff. Fix: tapping "scan" opens a window where any matching incoming speaker gets adopted and remembered.

LOG-08RF

Too close to the router

Two meters from the access point, the audio drops out in waves; one room over, it plays flawlessly. The router's Wi-Fi transmitter is drowning out the cube's Bluetooth receiver. Sometimes the best firmware fix is moving the device three meters.

05 / hands-off

What happens when something breaks

The design rule: plug in + power = it plays. The radio has to survive every failure on its own, no phone and no human required.

Wi-Fi dropsretries known networks with bounded backoff; never joins an unknown or open network
server drops the stream2 s without a frame = instant reopen and candidate rotation (one server killed the TCP connection 65× in 12 minutes — it kept playing anyway)
BT speaker diesaudio falls back to the built-in speaker, reconnect retries in the background every 8–20 s
dead URL in the cataloglast-known-good: each station's last working source is remembered in flash
main loop hangswatchdog 30 s: diagnostic reset and an automatic return to playing instead of a brick
corrupted configsafe mode with local repair; a broken state never overwrites the last one that worked
0 accounts0 cloud0 telemetry0 recording0 auto-OTAonly traffic: station streams + optional NTP
06 / production

Built in a triangle: two AIs and a human

The system is built entirely by a pair of AI agents working on the same machine, cross-auditing each other's work. The human is left with the decisions and the ears.

Lead engineer

GPT-5.6 Sol · Codex

2.5 billion
tokens

Architecture, firmware, a pixel-parity GUI simulator (the browser draws the exact same RGB565 pixels as the device), bilingual documentation.

Stabilization and audit

Claude Fable 5 · Claude Code

250M
tokens

Crisis takeovers: audio path physics, rotation and TLS hotfixes, QC gates, cross-auditing the other engine's reports.

Project owner

Tomasz Fiedoruk

1 pair of ears
+ decisions and veto

Sets the product boundaries, listens to the results live, and stops any flash that hasn't passed the test protocol. The only part of the system that can hear.

Telemetry hands down the PASS/FAIL verdict. Every firmware candidate runs a multi-minute logged test: starvation Δ0 · watchdog Δ0 · injected silence 0.0 s / 300 s · queues full. Only then does the human listen.

The repo holds over 100 numbered engineering documents, each in Polish and English. Yes, for a radio.

07 / FAQ

Questions people actually asked

Some of them, anyway.

FAQ-01

Why these exact nine stations?

My wife likes seven of them. The other two added themselves by having a stable stream. Engineering can be simple. ;)

FAQ-02

Why does it play better one room away from the router?

Because the router shouts Wi-Fi straight into the cube's Bluetooth ear. We measured it. Three meters of distance beat many a firmware fix, see LOG-08.

FAQ-03

But phone apps exist.

They do, some of them paid. Except a phone rings, notifies, and leaves the kitchen with you. The cube stays and plays.

FAQ-04

What did it really cost?

About €50 in hardware. Plus 2.75 billion AI tokens that we are not converting to euros, because this was supposed to be a hobby.

08 / status and distribution

Firmware image first, source second

We flipped the usual release order on purpose: instead of dropping raw source first, we're shipping a ready-to-flash image first — one esptool command and the cube plays. The code goes up on GitHub once it's cleaned up, has survived long-run testing, and the licensing gates are closed.

Plays on the desk for days on end — lab build, 9/9 stations with logos, touch, favorites
10-minute continuous test passed — 0.0 s of injected silence, with 1 real source restart along the way
Two physical BT speakers confirmed — pairing, auto-reconnect after a restart, adopting incoming connections
Long-run qualification in progress — 60 min → 2 h → 8 h continuous-play thresholds before release
GitHub repo: coming soon — the system image ships first for download, source once it's polished

Declared licenses: GPL-3.0-or-later (code) · CC BY-SA 4.0 (documentation) · CC0 (station catalog) · no retransmission or recording of broadcasts · station logos only with broadcaster consent