PN RG-FN-57
A real dot-matrix font
The display doesn't use a font file. Each character is five bytes, one per column, and the app lights the dots one at a time. This page uses the same table. Type something.
portable ptt transceiver / model 01
A walkie-talkie for iPhone that thinks it's a piece of hardware.
Hold the key, talk, let go. The other phone hears you, then hears a roger beep. Phones find each other over Wi-Fi and Bluetooth. No server, no account, nothing in the middle.
Most ways to talk to someone on a phone start with an account, a contact list and a server somewhere. I wanted the opposite.
Two phones with the app open should just be a pair of handhelds. Same channel, you hear each other. Different channel, you don't. No inbox, no read receipts, no "seen 4m ago."
And if it's going to act like a radio, it should look like one. So Roger isn't styled like a chat app at all. It's styled like a little piece of gear: off-white chassis, orange accent, dot-matrix display, real knobs, one very large key. There's a comment in the code comparing the plates to "the way the OP-1's keyboard and encoder sections are separate milled pieces." That's the whole mood board, honestly.
Everything on screen is drawn, not imported. Here's the whole panel, pulled apart. It all works: turn the knobs, flip the switches, pick a channel, open the peers and setup sheets.
portable ptt transceiver / model 01
Radios find each other directly, the way two handhelds would.
Roger uses MultipeerConnectivity, Apple's Wi-Fi and Bluetooth mesh. It's encrypted, it doesn't need a router, and it doesn't need me to run anything. Two phones in the same room are just two phones in the same room.
Audio goes out unreliably on purpose. A late frame is worse than a missing one. Key-down, key-up and presence go reliably. And if someone walks out of range mid-sentence, a watchdog closes the squelch after a second of silence instead of leaving the channel hanging open.
It's half duplex like the real thing. Only one radio talks at a time, and if you press your key while someone else is talking, your key wins.
Simulated traffic, real header format. Opus frames sized like the ones I measured.
Every packet starts with the same eight bytes
kind 1 audio · 2 tx start · 3 tx end · 4 presence / flags bit 0 = opus
On cellular, carrier NAT rules out a direct path, so there's an optional relay: a Cloudflare Worker with one Durable Object per room code. It doesn't parse packets, doesn't know what a channel is, and stores nothing. Same dumb pipe as the mesh. Phones in the same room still talk directly and never spend cell data on each other. Leave the room code empty and the relay never turns on.
PN RG-FN-57
The display doesn't use a font file. Each character is five bytes, one per column, and the app lights the dots one at a time. This page uses the same table. Type something.
PN RG-SN-04
There isn't a single sound file in the app. Every beep, click and detent is synthesised from four numbers: generator, pitch, length and reverb. Change the scheme and every switch re-voices at once.
the roger beep: 1480 Hz for 55 ms, then 1100 Hz for 75 ms
PN RG-SC-04
“Philips screw. Purely decorative, entirely necessary.”
a real comment from the source
PN RG-PT-09
Rev A only moved 7 pt, and it barely felt like it was pressing at all. Now the face sits 11 pt proud of a darker base and the shadow collapses on the way down. In hands-free mode the key stays down while it's latched. A key that pops back up while you're still on air is lying to you.
PN RG-IC-01
A small Python script draws it: the knob and the key on the chassis. No image library. It supersamples by hand and writes the PNG itself with zlib.
PN RG-WF-36
Above the key: a 36 × 7 grid of punched holes the signal scrolls across, lit white from the centre. My first try drove it from state changes inside a TimelineView, which deadlocked SwiftUI's very first frame. Now a 20 Hz timer owns the history and stops itself when it goes quiet.
hold space to see it move
PN RG-CL-02
Same product, shipped in white and in black. The switch repaints this whole page.
#E7E5E0#1F1F1E#F3F2EF#2B2B29#1A1A18#EDEBE5#FF4A00#FF5A12#121211#0B0B0A#FF8A21#FF8A21
Of course it has a backronym. It lives bottom-left on the setup sheet, set in Orbitron like type moulded into the plastic instead of printed on it. It's pinned to the case, not the page, so the settings scroll underneath it.
model 01 / rev a · 16 khz / pcm / 8 ch
less data on the wire after switching PCM to Opus
bytes per 40 ms audio frame, on average (I measured 83 to 161)
pull requests merged in under thirteen hours
third-party packages. Opus ships inside iOS.
bfa32ca Roger, peer to peer The whole radio: Multipeer mesh, 8 channels, half duplex, PCM audio, the dot font, two finishes, a generated icon. 1dd18a1 Cellular, and Opus A relay on Cloudflare for when you're out of Wi-Fi range. Opus through the OS codec. The display shows LOCAL or CELL. 4741904 Orbitron, a 3D key, sound schemes New wordmark. The key got a base to travel onto. Dot waveform. Every control got its own voice. 4151fb7 System push to talk Apple's PushToTalk framework: the lock screen shows who's talking, and the relay wakes sleeping radios over APNs. e1c19cb Action Button + backronym Assign Tap to Talk to the side button and key the radio without opening the app. 332e436 Home Screen widget The radio's face in miniature, with a key that works. Liquid glass over the chassis. Jul 28 – 29, 2026 · all merged
in development
It runs on my phone. It isn't on the App Store yet.
Roger works between two phones today, over the mesh and over the relay. What's left is the stuff you can only test with real hardware in real pockets: locking the phone and making sure a sleeping radio still wakes up when someone keys up, and chasing an audio-level gremlin on actual devices.
No public link for now. When there's a TestFlight, it'll go right here.
| Interface | SwiftUI, Canvas, iOS 17+ |
|---|---|
| Audio | AVAudioEngine, 16 kHz mono, 40 ms frames |
| Codec | Opus at 24 kbit/s via AVAudioConverter, PCM16 fallback |
| Local link | MultipeerConnectivity, Wi-Fi + Bluetooth, encrypted |
| Cell link | Cloudflare Worker, one Durable Object per room |
| Background | PushToTalk framework + APNs |
| Extras | App Intents (Action Button), WidgetKit |
| Third-party packages | 0 |