A functional Reticulum mesh terminal — Heltec LoRa32 V4 display node, mechanical keyboard, 915 MHz antenna, acrylic base with LED underglow. This is not a prop. It talks to the network.
This is a desk terminal built around a Heltec LoRa32 V4 — an ESP32-S3 board with a built-in 0.96" OLED, a 915 MHz SX1262 LoRa radio, and a u.FL antenna connector. Flashed with RNode firmware, it becomes a fully functional Reticulum interface device. Mounted to the left of a mechanical keyboard on an acrylic base with LED underglow and an external whip antenna, it becomes something you can actually show people.
The Heltec is doing real work here. It's not a Raspberry Pi pretending to be a radio — the LoRa transceiver is on the same board as the display. When rnsd runs on your host machine and sees the Heltec over USB serial, it has a 915 MHz link into the Reticulum backbone. The display shows live network status: peer count, interface state, announce traffic. The antenna on the bracket picks up actual mesh nodes in range.
This is the build for people who have already set up Reticulum and want the infrastructure to feel like infrastructure.
Everything below is available from AliExpress (longer shipping) or Amazon (faster, $10–20 more total). The Heltec V4 ships from AliExpress reliably in 2–3 weeks. The Heltec official store on AliExpress is legitimate — use it over third-party listings.
| Component | What to get | Cost | Notes |
|---|---|---|---|
| LoRa display board | Heltec WiFi LoRa32 V4 | $18–25 | ESP32-S3 + SX1262 + 0.96" OLED. Buy from the Heltec official AliExpress store. |
| Antenna — whip | 915 MHz 3dBi whip, SMA-M | $4–7 | ~17cm rubber duck style. Any SMA-male 915 MHz whip. Avoid the tiny PCB stubs. |
| Antenna — pigtail | u.FL to SMA-F bulkhead, 10cm | $3–5 | Connects the board's u.FL port to the SMA bulkhead in the bracket. Keep it short. |
| SMA bulkhead | SMA female panel-mount | $2–4 | Mounts through a drilled hole in the 3D-printed bracket. The pigtail connects inside; the whip screws on outside. |
| Mechanical keyboard | Any TKL, USB | $25–60 | Budget Redragon (~$30) to Keychron K2 (~$60). USB HID — no driver needed. TKL proportions suit the base layout. |
| Acrylic sheet | 3mm clear, ~52 × 22cm | $8–14 | Clear for edge-lighting. Hardware store, Amazon, or Inventables. Score-and-snap with utility knife. |
| LED strip | WS2812B 60LED/m, 1m | $3–6 | Addressable RGB, self-adhesive. Controlled by Heltec GPIO. Cyan underglow from the board's 5V rail. |
| Display bracket | 3D printed, matte black PLA | $3–6 | STL at nodestar.net/builds/nomadnet-terminal. Holds Heltec at 15° tilt, has SMA bulkhead hole, M3 mounting points, cable channel. |
| Standoffs | M3 brass, 15mm, ×6 | $4–6 | Elevates base ~15mm off desk — the gap is what makes edge-lighting work. |
| Screws | M3 × 6mm pan head, ×12 | $2–3 | Keyboard to standoffs, bracket to standoffs. |
| Dupont wires | Female-to-female, 20cm, ×5 | $2–3 | LED strip to Heltec GPIO + power. Light soldering alternative: use pin header + crimp. |
| USB hub | Compact 4-port USB-A | $6–10 | Keyboard + Heltec on one cable run to host. Mount under base with 3M dual-lock. |
| Branding | Matte vinyl sticker or laser-etch | $3–20 | Applied to acrylic underside. LED strip edge-lights it from below. SVG assets at nodestar.net. |
| Total | ~$87–169 | AliExpress low / Amazon with good keyboard high end. | |
The base unifies everything into a single object. It's a piece of 3mm clear acrylic, elevated 15mm above the desk on brass standoffs, with an LED strip along the inside of the front edge. The clear acrylic transmits that light along its full perimeter — edge-lighting. A logo etched or stuck to the underside glows from below. The keyboard and display module mount on top via the same standoffs.
Score the acrylic five or six passes with a utility knife against a metal straight edge, then snap over a table edge. Sanded edges actually diffuse LED light better than polished cuts — run 400-grit along the front edge. Drill at low speed with a 3.5mm bit; tape the surface at each drill point first. Acrylic cracks from heat and vibration, not pressure — slow is the only technique.
Score and snap, or jigsaw with fine-tooth blade. Leave protective film on during cutting. Optional hex front edge: mark with a template, cut with jigsaw, sand smooth.
3.5mm bit, low RPM. 2 holes for display bracket, 4 corners for keyboard. Tape surface at each point. Thread M3 brass standoffs from below, hand-tight.
Peel bottom film. Apply vinyl from center outward, no bubbles. The LED strip will edge-light through the clear acrylic from below.
Peel adhesive, run strip along the interior of the front edge, LEDs facing outward. Route data + power leads to rear where the Heltec will sit.
Display bracket on left standoffs, keyboard on right standoffs. Peel top protective film last.
The Heltec WiFi LoRa32 V4 is doing three things simultaneously in this build: running the display firmware that draws the node map on its 0.96" OLED, driving the WS2812B LED strip via a GPIO pin, and acting as a Reticulum RNode interface — a LoRa transceiver that rnsd talks to over USB serial as if it were a radio modem.
The Heltec runs RNode firmware — the open-source firmware by unsigned.io that turns supported LoRa boards into Reticulum interface devices. When flashed, rnsd on your host machine detects the board over USB serial and uses it as a LoRa interface automatically. No custom serial protocol to implement, no bridge script needed for the radio link.
The RNode firmware also drives the OLED display — it shows interface state, frequency, and link activity natively. The display firmware in Section 07 runs as a secondary layer on top, adding the animated peer map to the OLED.
# Python 3.7+ required pip install rns # Verify rnodeconf --version
# Connect Heltec V4 via USB-C, then: rnodeconf --autoinstall # The tool will detect the board, download the correct firmware, # and flash it. As of RNode firmware v1.84+, a dedicated Heltec V4 # target is available — select it if listed. If your rnodeconf # version only shows "Heltec LoRa32 V3", that works too (same # ESP32-S3 + SX1262 base), but you may under-utilize the V4's # higher TX power (~28 dBm PA). Update rnodeconf first: # pip install --upgrade rns # Watch the output — it will confirm "Installation complete". # Verify the install: rnodeconf --info /dev/ttyUSB0 # Linux rnodeconf --info /dev/cu.usbserial-* # macOS rnodeconf --info COM3 # Windows (check Device Manager for port)
Python 3.7+, pip. Run pip install rns. This installs both the Reticulum stack and rnodeconf.
Hold BOOT while plugging in if the port isn't detected. Use a quality USB-C data cable — the V4 uses native ESP32-S3 USB (no CP2102 chip), which can be finicky with cheap charge-only cables. Linux users: add yourself to the dialout group (sudo usermod -aG dialout $USER) and log out/in. Windows users: check Device Manager → Ports for your COM number.
rnodeconf --autoinstall detects the board and flashes the correct firmware. Select "Heltec LoRa32 V4" if listed; otherwise "V3" works but may not fully utilize the V4's PA power. Takes 2–5 minutes. If flashing fails, try esptool.py erase_flash first, then re-run. The OLED will show the RNode splash on completion.
Run rnodeconf --freq 915000000 --bw 125000 --sf 7 --cr 5 /dev/ttyUSB0 to configure for 915 MHz, 125kHz BW, SF7. The V4's SX1262 PA supports up to ~22 dBm conducted power (higher with antenna gain) — set TX power according to your regional limit.
Add the interface to your ~/.reticulum/config (see Section 08) and run rnsd -v. It should report the RNode interface as UP and begin announcing. Confirm mesh participation with rnstatus — you should see the NomadNet Terminal interface listed with its frequency and bandwidth. If the interface shows DOWN, check the serial port path and cable.
--freq 868000000) with strict duty-cycle limits (typically 1% or 10% depending on sub-band). In Australia, 915–928 MHz. A 3dBi antenna on top of 22 dBm TX puts you at 25 dBm EIRP — well within US limits, but verify against your local rules. Setting txpower = 14 in the config is conservative and universally safe; raise it only if you've confirmed your regional ceiling.
The u.FL connector on the Heltec V4 is small and fragile — seat the pigtail connector firmly but don't rock it. The pigtail runs inside the 3D-printed bracket housing to the SMA bulkhead, which is pressed into a 6.5mm hole in the bracket wall and secured with its nut from the outside. The whip antenna screws onto the external SMA thread. Keep the pigtail coax as short as possible and avoid kinking it — coax below 10cm runs fine without additional strain relief.
After flashing RNode firmware, the OLED already shows basic interface state. The sketch below extends that — it runs on top of the RNode firmware logic and adds a live peer-count readout, signal strength bar, and animated mesh topology to the display. It also drives the WS2812B LED strip with the breathing cyan underglow.
https://resource.heltec.cn/download/package_heltec_esp32_index.json. Then Boards Manager → search "Heltec ESP32" → install. Select WiFi LoRa 32(V3) as the board target — V4 uses the same Arduino target as V3.
// NomadNet Terminal — Heltec LoRa32 V4 // Drives OLED node map + WS2812B underglow // nodestar.net/builds/nomadnet-terminal #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <FastLED.h> // ── OLED config (Heltec V4: 128×64, I2C on 17/18) ── #define OLED_SDA 17 #define OLED_SCL 18 #define OLED_RST 21 #define SCREEN_W 128 #define SCREEN_H 64 // ── LED strip config ── #define LED_PIN 45 // GPIO45 — safe, not used by LoRa or OLED #define NUM_LEDS 30 // adjust to your strip length // ── Serial from host (rnsd bridge) ── #define BAUD_HOST 115200 Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RST); CRGB leds[NUM_LEDS]; // Node map state #define NODE_COUNT 8 struct Node { float x, y, vx, vy; }; Node nodes[NODE_COUNT]; // Network state (updated from host via serial) int peerCount = 0; int rssi = 0; bool linkActive = false; char rxBuf[128]; int rxIdx = 0; void setup() { Serial.begin(BAUD_HOST); // OLED init Wire.begin(OLED_SDA, OLED_SCL); pinMode(OLED_RST, OUTPUT); digitalWrite(OLED_RST, LOW); delay(20); digitalWrite(OLED_RST, HIGH); delay(20); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.display(); // LED strip init FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(160); // Init floating nodes randomSeed(analogRead(0)); for (int i = 0; i < NODE_COUNT; i++) { nodes[i] = { (float)random(4, 86), (float)random(12, 52), (float)(random(-8, 8)) / 30.0f, (float)(random(-8, 8)) / 30.0f }; } } void readSerial() { while (Serial.available()) { char c = Serial.read(); if (c == '\n' || rxIdx > 126) { rxBuf[rxIdx] = '\0'; // Simple parse: "peers:4,rssi:-87,link:1" sscanf(rxBuf, "peers:%d,rssi:%d,link:%d", &peerCount, &rssi, (int*)&linkActive); rxIdx = 0; } else { rxBuf[rxIdx++] = c; } } } void drawNodeMap() { for (int i = 0; i < NODE_COUNT; i++) { for (int j = i+1; j < NODE_COUNT; j++) { float dx = nodes[i].x-nodes[j].x; float dy = nodes[i].y-nodes[j].y; if (sqrt(dx*dx+dy*dy) < 38) display.drawLine(nodes[i].x, nodes[i].y, nodes[j].x, nodes[j].y, SSD1306_WHITE); } } for (int i = 0; i < NODE_COUNT; i++) { display.fillCircle(nodes[i].x, nodes[i].y, 2, SSD1306_WHITE); nodes[i].x += nodes[i].vx; nodes[i].y += nodes[i].vy; if (nodes[i].x < 3 || nodes[i].x > 89) nodes[i].vx *= -1; if (nodes[i].y < 10 || nodes[i].y > 62) nodes[i].vy *= -1; } } void drawStatusPanel() { // Right panel (x=92..127) — status info display.drawFastVLine(91, 8, 56, SSD1306_WHITE); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(94, 10); display.print(peerCount); display.setCursor(94, 20); display.print("peers"); display.setCursor(94, 34); display.print(rssi); display.setCursor(94, 44); display.print("rssi"); // Link indicator dot if (linkActive) display.fillCircle(110, 58, 3, SSD1306_WHITE); else display.drawCircle(110, 58, 3, SSD1306_WHITE); } void updateLEDs() { // Exponential sine breathing — Node Star cyan float t = millis() / 2000.0 * PI; uint8_t b = (uint8_t)((exp(sin(t)) - 0.36) / 2.35 * 150); for (int i = 0; i < NUM_LEDS; i++) leds[i] = CRGB(0, (uint8_t)(212.0/255 * b), b); // #00D4FF FastLED.show(); } void loop() { readSerial(); display.clearDisplay(); // Header bar display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.print("NOMADNET 915MHz"); display.drawFastHLine(0, 8, 128, SSD1306_WHITE); drawNodeMap(); drawStatusPanel(); display.display(); updateLEDs(); delay(40); }
peers:N,rssi:-NN,link:1 strings from USB serial, sent by nomadterm.py on your host machine. Without the bridge running, it displays zero peers and draws the animated node map only. With it running, the right panel shows real data from your Reticulum node. Full bridge script at nodestar.net/builds/nomadnet-terminal.
With RNode firmware on the Heltec, Reticulum's configuration is straightforward. Add an RNodeInterface entry pointing to the board's serial port. When rnsd starts, it detects the RNode, confirms the firmware signature, and brings up the LoRa interface. From that point the terminal is a live mesh participant.
# Add this block to your existing Reticulum config # Find your port: ls /dev/tty* | grep -i usb (Linux) # ls /dev/cu.* (macOS) # Device Manager → Ports (Windows: COM3, COM4, etc.) [interfaces] [[NomadNet Terminal]] type = RNodeInterface enabled = yes port = /dev/ttyUSB0 # adjust to your port frequency = 915000000 # 915 MHz (use 868000000 for EU) bandwidth = 125000 # 125 kHz txpower = 14 # dBm — conservative default. V4 supports up to ~22 dBm; check regional limits + antenna gain. spreadingfactor = 7 codingrate = 5
#!/usr/bin/env python3 # Runs on host machine alongside rnsd # Queries rnstatus every 30s, forwards to Heltec over serial # pip install pyserial import subprocess, serial, time, re PORT = "/dev/ttyUSB0" # Heltec serial port INTERVAL = 30 # seconds between updates def get_status(): try: out = subprocess.check_output(["rnstatus"], stderr=subprocess.DEVNULL).decode() peers = len(re.findall(r"Destination", out)) rssi_m = re.search(r"RSSI.*?(-\d+)", out) rssi = int(rssi_m.group(1)) if rssi_m else 0 link = 1 if "NomadNet Terminal" in out else 0 return peers, rssi, link except: return 0, 0, 0 with serial.Serial(PORT, 115200, timeout=1) as ser: print(f"NomadNet bridge running on {PORT}") while True: peers, rssi, link = get_status() msg = f"peers:{peers},rssi:{rssi},link:{link}\n" ser.write(msg.encode()) print(f"→ {msg.strip()}") time.sleep(INTERVAL)
rnsd and nomadterm.py both access the Heltec's serial port — but at different times. rnsd holds the port open as an RNode interface. To feed the display separately, run nomadterm.py in a mode that queries rnsd's management socket rather than the serial port directly. The full bridge at nodestar.net/builds/nomadnet-terminal handles this correctly using Reticulum's Python API rather than subprocess calls.
The display bracket holds the Heltec at a 15° forward tilt, provides a mount point for the SMA bulkhead (6.5mm hole in the rear wall), includes a cable channel for the pigtail coax and the LED strip leads, and has M3 inserts for mounting to the acrylic standoffs. Print in matte black PLA at 0.2mm layer height — no supports needed with the bracket oriented face-down. The tilt angle keeps the OLED readable from a seated position without requiring you to lean forward.
PLA straight off the printer looks fine. For the carbon-fiber texture in the reference renders: Plasti-Dip rubberized coating in matte black, two coats. Hydro-dipping with carbon-fiber hydrographic film is the higher-effort version and looks exactly right. Kits run $20–30 on Amazon.
Route the keyboard USB and Heltec USB-C to a shared exit at the rear of the base through a compact 4-port hub mounted underneath with 3M dual-lock tape. The single cable exit to the host machine looks intentional rather than assembled. The LED strip leads should be routed inside the bracket housing — the 3D-printed STL has a channel for this.
Underside vinyl works immediately. For the etched-acrylic look: Ponoko or a local makerspace, laser-etch, $10–20. The etch catches the LED edge-light differently from vinyl — more diffuse, more permanent. Node Star SVG assets for both the NomadNet and Node Star logos are available at nodestar.net/assets.
"The mesh should feel like a place you inhabit, not a config file running in the background."
The natural next step is embedding a small SBC — a Bosgame mini PC, an old ThinkCentre mini, or a Pi 4 — either below the base or in a rear enclosure. At that point the Heltec is no longer slaved to a host machine over USB: it's a peripheral to a dedicated Reticulum node that lives inside the terminal itself. The keyboard types into that machine. The display queries it directly. The whole stack operates off one power cable and one USB run to a monitor.
The Nomad NetBrowser desktop application from Node Star runs natively on such a system, rendering NomadNet pages in a local browser window on a display that's part of the terminal enclosure. At that scale the build stops being a peripheral and starts being an actual mesh node with a UI.
Beyond that: the r/cyberdeck community has documented builds with integrated e-ink status displays, solar charging, NFC readers, and satellite fallback links. The acrylic base format scales. Everything in this guide is a starting point.
Questions, corrections, build reports: Please reach out through our contact page.