ShackLink Knob
A ShackLink client you hold in one hand. A round 360×360 touch screen and a real tuning knob show the radio’s frequency, the SDR’s live spectrum and waterfall, the band and mode, and the server’s 48 shared memories. The station’s audio plays into a pair of headphones. It is firmware for the Waveshare ESP32-S3 1.8" Knob, and it talks to the ShackLink server over WiFi, with the same WebSocket and commands as the desktop, web and mobile clients.
The main screen on a live server: a HackRF watching 10 MHz of the FM broadcast band, the radio on 104.700 MHz WFM.
💡 What It Does
Tune With a Real Knob
Each detent moves the underlined digit. Tap a digit to tune by that place instead. Turn faster and the step grows ×2, ×4 or ×10.
Spectrum & Waterfall
The SDR’s live trace with faint peak hold, over a scrolling waterfall. Tap the waterfall to tune there, zoom with − / +, drag to pan, long-press for the full span.
Band & Mode Rings
Bands and modes sit round the rim of the screen; the knob walks the highlight. Bands use the desktop’s band stacking.
48 Shared Memories
The server’s memory bank as a clock face: twelve channels at the hours, four banks. One tap recalls. These are the same channels the desktop, web and mobile clients see.
Any SDR Slot
Follow the station, or pin one of the server’s SDR slots: you see its spectrum and hear its own demodulated audio, on this knob only.
Audio in Your Headphones
A 3.5 mm jack on the knob. The audio arrives as Opus, about 3 KB/s, and is decoded on the ESP32-S3.
Finds the Server Itself
It listens for the server’s UDP beacon on the LAN, so there is no address to type. After a drop it tries again every 4 s, and comes back on its own when the server does.
Light on the Network
It asks the server for only what it draws: one slot’s spectrum at 360 bins, Opus audio only while Sound is on, no map markers. That brings audio and spectrum from 146 KB/s down to about 11 KB/s.
15-Second Updates
New firmware goes over WiFi in about 15 s, not the five minutes espota takes. The upload is HMAC-signed, and the password never crosses the network.
Haptics
A light tick confirms a tap or a screen change. A firm click marks a band edge, the zoom reset and the memory sheet. Plain detents get nothing: the knob has its own mechanical ones.
HMAC Auth
If the server requires authentication, enter its shared secret in the setup portal. The knob signs in with the same challenge and response as every other client.
PC Emulator
The screens and the server link also build into a Windows emulator, so the UI can be worked on against a real server without flashing. The round UI captures on this page were taken in it.
The ShackLink Knob running against a live station.
On the desk
The Waveshare knob beside the SDR hardware, showing the same live station state, spectrum, waterfall, memories, bands and modes as the other ShackLink clients.
The shared memory bank on the physical knob.
A live HackRF spectrum and waterfall on the 360×360 display.
The band ring, with the FM broadcast band selected.
The mode ring, with WFM selected.
📡 How It Fits In
ShackLink Knob
ESP32-S3, round touch screen, knob, headphone jack
WebSocket 8084
beacon UDP 8081
ShackLink Server
Runs the station: rig control, SDR slots, audio, memories
Radio & SDRs
The rig, plus any HackRF, Hermes Lite 2 or other SDR the server drives
The knob is a client, like the desktop, web and mobile clients and the Home Assistant cards. Tuning, mode, band, memories and SDR slots are commands to the server, and the server tells every client what changed. Turn the knob and the desktop’s dial moves; recall a memory on the desktop and the knob follows.
🖱 The Main Screen
From the top of the circle to the bottom:
| Part | What it shows, and what a touch does |
|---|---|
| Status | The link to the server, always shown. A green dot and the server’s name when online. Amber while connecting or signing in, or when WiFi is weak (below −80 dBm). Red when offline, with no WiFi, or when sign-in failed. Tap it for Settings. |
| Frequency | Turn the knob to tune by the underlined place; tap a digit to tune by that place instead. Until the server confirms a new frequency, the dial shows the request in amber. With no confirmation within 4 s, it goes back to the radio’s frequency. |
| Mode · band · step | Tap the mode for the mode ring, the band for the band ring. |
| Spectrum | The live trace with faint peak hold. − and + at the rim zoom. The server does the zooming, so a zoomed view has full resolution, not stretched bins. |
| Waterfall | Tap to tune there. Drag sideways to pan when zoomed. Long-press for the full span. With no picture, it says why: no WiFi, the link state, or no spectrum on this slot. It clears when the link drops, so a frozen picture never passes for a quiet band. |
| Footer | The SDR slot (* when pinned), the zoom factor and the span. Tap it for the SDR slots. |
Full span: 10 MHz of the FM broadcast band from a HackRF.
Zoomed ×4 to 2.5 MHz. The server crops its own full-resolution FFT, so the station at 104.7 MHz stays sharp.
🧭 Getting Around
Swipe up or down on any screen to go round all five. From the main screen, up is Band & mode, and down is Memories, then Settings.
The screen ring. On any screen but Main, a sideways swipe goes straight back to Main.
Shortcuts from Main: swipe left on the dial for Memories, right for the SDR slots. Tap the status line for Settings, the mode or band for Band & mode, the footer for the SDR slots.
Band. The knob walks the highlight round the rim; the middle shows the band’s edges and where band stacking will take you.
Mode. The BAND / MODE tabs switch the ring. Tap the middle, or any label on the rim, to go.
Memories. Twelve channels at the hours, M1 at one o’clock; 1–4 below the middle switch banks.
SDR slots. Follow the station, or pin one of the server’s receivers to watch and hear it.
Settings. A rotary list: turn to choose, tap to open. Values are set with tap, turn, tap.
WiFi setup. The knob opens its own network for a phone to join. (The emulator calls itself -SIM; a knob shows four hex digits.)
Band & mode
Bands use band stacking, as on the desktop. Picking a band takes you back to the last frequency you used on it; the first time, to its calling frequency and default mode.
Memories
The knob shows the server’s 48-channel bank, the same one the desktop, web and mobile clients use. Turning the knob walks the highlight round the ring and on into the next bank. The middle shows the highlighted channel in full: its label, frequency and mode.
- Tap a filled channel to recall it. The radio tunes at once and the dial comes back.
- Tap an empty channel to highlight it, then tap it again (or the middle) to store the current frequency and mode there. The bank is shared by every client, so a stray touch on the rim must not write into it.
- Long-press any channel to overwrite it. The channel keeps its label: “FT8 20m” is your name for that channel, and storing a new frequency in it is not a rename.
- Clearing a channel is left to the other clients.
SDR slots
This screen sets which receiver the knob shows and hears. Follow the station shows the slot the station’s audio comes from and plays the station’s audio. Picking a slot pins it: you get its spectrum, and its own demodulated audio in the headphones. That change is for this connection only; the desktop’s audio, decoders and meters stay where they are. “heard here” is the server’s answer, not an echo of the tap. If the server drops a pinned slot, the knob goes back to following the station. If the slot stops streaming, the knob plays the station’s audio until the slot streams again.
Settings
| Item | What it does |
|---|---|
| WiFi setup | Opens the setup portal. While WiFi is up, tap the portal screen to cancel. |
| Server | Found automatically, or a fixed address. Tap to go back to finding it by beacon. |
| Sound | Off also asks the server to stop sending audio, which saves the bandwidth as well as the noise. |
| Volume, Brightness, Tuning step | Tap, turn, tap. |
| Fast-turn speed-up | The ×2 / ×4 / ×10 step when you turn quickly. Turn it off for a steady step. |
| Haptics | The taps and clicks from the vibration motor. |
| About | Firmware version and IP address. |
| Restart | Tap twice. |
Settings are kept in flash, written once the knob stops turning, not once per detent.
🔌 Connecting to the Server
Join WiFi from your phone
With no network stored, the knob opens a setup portal. On a phone, join ShackLinkKnob-XXXX and open 192.168.4.1. Choose your network, and fill in the server’s host, port and secret here if it needs them. Settings → WiFi setup opens the portal again later.
It finds the server
The knob listens for the server’s beacon on UDP 8081 and connects to the first server it hears, on WebSocket port 8084. To skip discovery, as when the server is on another subnet, set a fixed host in the portal or on the serial console.
The dot turns green
The status line shows the server’s name with a green dot, the dial shows the radio, and the spectrum starts, at about 25 frames a second on a good WiFi link.
Asking the server for less
By default the server sends every client what a desktop needs. For the knob that was 12 kHz audio it threw away, and every streaming slot’s spectrum at 1024 bins: about 146 KB/s. On connect the server also pushed a 273 KB list of broadcast stations, more than the ESP32’s WebSocket library can hold. So the knob opts out of what it does not draw:
| The knob asks for | How |
|---|---|
| No map markers | /ws?markers=0 in the URL. This must be in the URL: the server pushes markers before any command could decline them. |
| Audio only when played | audioDownlink off / on, following Settings → Sound |
| Opus, not PCM | audioCodec opus: about 3 KB/s instead of 24 |
| One slot’s spectrum, 360 bins | sdrStreamLimits for the followed slot, sent again whenever that slot changes |
The server measured the audio and spectrum traffic going from 146 KB/s to 11 KB/s. The frame rate is left at the source’s, because peak-hold decay and waterfall speed are tuned per frame.
ℹ Older servers
A server from before these opt-outs ignores all four and sends everything. The knob still copes: a message too large for it is read and dropped, and the link stays up. The serial console’s info shows what the server agreed to (“server sends: …”).
WiFi
By default an ESP32 joins the first access point it hears with the right network name. Where an extender, a mesh node or a second router serves the same name, that can be a far one. One knob sat a foot from its router on a distant access point at −88 dBm, getting one spectrum frame a second, with tuning confirmations seconds late. The knob therefore scans every channel and asks to join the strongest. On a near access point a frequency change makes the round trip in 40–65 ms, at 25 spectrum frames a second.
🔧 The Hardware
The Waveshare ESP32-S3 1.8" Knob, unmodified. Nothing to solder: plug in USB-C to flash it, and headphones to listen.
| Part | Details |
|---|---|
| Processor | ESP32-S3 with 16 MB flash and 8 MB PSRAM. The waterfall and screen buffers live in PSRAM. |
| Display | 1.8" round, 360×360, ST77916 over QSPI at 40 MHz |
| Touch | CST816 capacitive controller on I2C |
| Knob | A rotary knob with mechanical detents. It is not a quadrature encoder: each detent pulses one of two contacts, one for each direction, and one pulse is one step. |
| Haptics | DRV2605 driver and a vibration motor |
| Audio | PCM5100A DAC to a 3.5 mm jack: headphones or a powered speaker, no built-in speaker. The 12 kHz stream is played at 48 kHz, because with no master clock wired the DAC will not lock at 12 kHz. |
| Second chip | An ESP32-U4WDH behind a CH340, on the same USB-C port |
⚠ One USB-C port, two chips
Which chip the computer sees depends on which way up the USB-C plug goes. One way reaches the ESP32-S3, which runs this firmware and shows up as 303A:1001. The other way reaches the ESP32-U4WDH through a CH340. If esptool reports an ESP32-U4WDH, turn the plug over.
When the knob starts, it shows red, green, blue and white bars and an arrow at the top for two seconds. The panel cannot be read back, so this is how you check its colour order and rotation.
🛠 Building and Flashing
No Arduino IDE needed. Everything builds with a local arduino-cli, kept inside the project folder, from Git Bash on Windows:
arduino-cli 1.5.1
esp32:esp32 core 3.3.12
ArduinoJson 7.4.3, LovyanGFX 1.2.30, WiFiManager 2.0.17, esp32_opus 1.0.3
WebSockets 2.7.2 (patched copy in lib/: drops an oversized message, not the link)
./knob.sh ShackLinkKnob COM25 # over USB
./knob.sh ShackLinkKnob shacklink-knob.local # over WiFi
The script will not upload a failed build. WiFi updates take the password from the sketch’s secrets.h, which is not committed.
Updates over WiFi in 15 seconds
The knob takes new firmware as an HTTP POST on TCP 3232 (FastOta). A 1.4 MB image goes in 14–15 s, against 324 s through espota, the Arduino IDE’s method, which waits for a reply after every 1 KB. The client proves the password with HMAC-SHA-256 over a one-time nonce from the knob, and signs the image the same way, so the password never crosses the network. The knob writes the image as it arrives and makes it bootable only once the signature checks. A wrong password, a reused nonce, a file that is not firmware, or a dropped upload is refused, and the knob keeps running what it had.
🖥 Serial Console
Plug in USB (the S3 side of the plug) and open the port at any baud rate.
| Command | What it does |
|---|---|
info | WiFi (signal, access point, channel), link state, what the server agreed to send, audio (buffer, underruns, skips), the radio, servers heard, the screen showing, spectrum frame rate, memory. Also how long since the knob started, and why it started. |
scan | Every access point in range, marking the one joined |
host <addr|auto> [port] | A fixed server, or back to the beacon. Reconnects at once. |
secret <hex|none> | The server’s shared secret |
portal, portal stop | The WiFi setup portal |
enc <n> | As if the knob turned n detents (+ is clockwise) |
tap x y [ms], swipe x1 y1 x2 y2 [ms] | A touch or a swipe, as if a finger did it |
screen | The screen showing now |
buzz [effect] | Play a haptic effect |
reboot | Restart the knob |
💻 The PC Emulator
The knob’s screens and its server link build unchanged into a Windows program: a 360×360 round window that finds and talks to a ShackLink server exactly as the knob does (beacon, WebSocket, HMAC sign-in, the same commands). A UI change can be tried in seconds instead of a flash cycle. The mouse wheel is the knob, and the mouse is a finger.
It can also be scripted, which is how the screenshots here were taken against a live server:
ShackLinkKnobSim.exe --host 192.168.0.10 \
--at 3000 swipe:180,250,180,140 --at 4500 shot:band.png \
--at 5000 tap:222,96 --at 6000 shot:mode.png --quit-after 7000
It needs Visual Studio 2022 with the C++ desktop workload, and SDL2, which a setup script downloads. Without a radio, a stand-in server (FakeShackLinkLan, a small .NET program) feeds it a made-up ham band.
🔧 Troubleshooting
“Looking for a server…”
- The beacon is UDP broadcast on port 8081, and it does not cross routers. If the server is on another subnet or behind a VPN, set its address in the portal or with
host <addr>. - Check that the server is running, and that its firewall allows TCP 8084.
- A red dot that was briefly amber means sign-in failed: the secret is missing or wrong.
Slow spectrum, late tuning, amber dot
- An amber dot while online means the WiFi signal is below −80 dBm. Run
infoto see which access point the knob is on and its signal, andscanto see the others in range. - If a nearer access point is much stronger, restart the knob (Settings → Restart) so it chooses again.
No audio
- The knob has a headphone jack, not a speaker. Plug in headphones or a powered speaker.
- Check Settings → Sound is on and the volume is up.
- A pinned SDR slot plays that slot’s audio. Choose “Follow the station” to hear the station’s.
The knob restarts when a terminal opens the port
The ESP32-S3’s USB port resets the chip on the DTR / RTS lines that most terminals assert when they open or close a port. Turn DTR and RTS off in the terminal (in pyserial, set dtr and rts to False before open()). info shows why the knob last started: USB port, power-on, software restart, or a crash.
🚧 Not Done Yet
- Audio gaps. The server’s garbage-collection pauses (40–140 ms, about twice a minute) stop its HackRF capture, so that audio is lost, not merely late. The knob keeps a fixed 200 ms buffer rather than growing it to hide the gaps, since ham use wants low latency. The fix belongs on the server and is in progress.
- An S-meter on the rim.
- RIT / XIT.
- Spot markers on the waterfall.
📺 A sibling: ShackLink CYD
The same server link and spectrum code (ShackLinkCore) also drives a 320×240 “Cheap Yellow Display” (ESP32-2432S028R): a rectangular touch screen with on-screen tuning buttons in place of the knob.