A functional MeshCore 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 MeshCore companion firmware, it becomes a fully functional MeshCore node. 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. MeshCore firmware runs directly on the ESP32-S3 and drives the OLED natively: BLE pairing PIN, connection status, radio state. When you send a message from the MeshCore app on your phone over Bluetooth, the companion radio transmits it into the mesh, and repeaters in range route it toward the destination. The antenna on the bracket picks up actual MeshCore traffic in your area.
This is the build for people who are already on MeshCore and want their companion radio to feel like infrastructure instead of a development board zip-tied to a shelf.
flasher.meshcore.co.uk lists the Heltec V4 as a supported device. Select it from the device list and choose your role — BLE Companion for a personal desk node. The flasher handles the correct binary and flash offsets automatically. If you see a blank OLED after flashing, try the "merged" build option, which includes the bootloader and partition table in a single image.
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. Driven by a secondary MCU reading MeshCore serial output. Cyan underglow from a 5V USB supply. |
| Display bracket | 3D printed, matte black PLA | $3–6 | STL at nodestar.net/builds. 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 runs MeshCore firmware natively. The ESP32-S3 handles the mesh routing stack, and the firmware drives the built-in 0.96" OLED automatically — showing your BLE pairing PIN, connection status, radio state, and contact activity without any additional sketch. The SX1262 LoRa radio handles all RF. This board is the entire MeshCore companion node.
The WS2812B LED strip underneath the acrylic base is driven separately by a small secondary MCU (a $3 Seeed XIAO or Arduino Nano) that monitors MeshCore's serial debug output and pulses the LEDs on mesh activity. This keeps the LED logic off the MeshCore firmware entirely — no firmware modifications, no GPIO conflicts.
DOUT to LED #2 instead. The 300Ω series resistor at the data input also helps signal integrity but does not raise the voltage.
MeshCore firmware flashes to the Heltec V4 in about two minutes via the official web flasher. No Python environment, no platform-specific toolchain. Connect the board over USB-C, open the flasher in Chrome or Edge, and select your board and role. For a desk companion node, choose BLE Companion. The OLED will show the MeshCore boot screen and BLE pairing PIN on completion.
# Open in Chrome or Edge (requires WebSerial API support) https://flasher.meshcore.co.uk # Steps: # 1. Connect Heltec V4 via USB-C (hold BOOT if not detected) # 2. Select device: "Heltec V4" # 3. Select role: # - "BLE Companion" ← for a personal desk node (phone app over Bluetooth) # - "USB Companion" ← for host-connected serial companion (no BLE) # - "Repeater" ← infrastructure only, no user messaging # - "Room Server" ← BBS-style message store for the mesh # 4. Click Flash — downloads latest stable firmware and writes it # 5. Wait ~90 seconds. OLED shows the MeshCore boot screen + BLE PIN. # 6. Done. Open the MeshCore app and pair via Bluetooth.
# Python 3.8+ required pip install esptool # Download the correct .bin from flasher.meshcore.io for your board + role # For merged builds (includes bootloader + partition table): esptool.py --port /dev/ttyUSB0 write_flash 0x0 meshcore-heltec-v4-ble-companion-merged.bin # For non-merged builds (board already has valid bootloader): esptool.py --port /dev/ttyUSB0 write_flash 0x10000 meshcore-heltec-v4-ble-companion.bin
If the board is not detected, put it in bootloader mode: hold the BOOT button, plug in USB-C, then release BOOT after ~1 second. The ESP32-S3 has a built-in USB-CDC bootloader so no special USB driver is normally needed on Linux or modern Windows/macOS — but on older Windows you may need the Espressif USB JTAG/serial driver from the Espressif site. Linux users: add yourself to the dialout group (sudo usermod -aG dialout $USER) and log out/in. If WebSerial still can't see the device, try a different USB-C cable — many "charge-only" cables silently fail.
Use Chrome or Edge. Select Heltec V4 from the device list, then choose your firmware role. For this build, pick BLE Companion — it lets you connect via the MeshCore phone app over Bluetooth while keeping the OLED active.
Click Flash. The tool erases the board, downloads the latest stable release, and writes it. Takes 60–90 seconds. If the OLED stays blank after flashing, try the merged build option — it includes the bootloader and partition table in a single image.
The OLED shows the MeshCore logo, then switches to the companion info screen with the BLE pairing PIN. If it stays blank, hold RESET once and wait 5 seconds.
Open the MeshCore app on your phone (Android or iOS), connect via Bluetooth using the PIN shown on the OLED. Tap the settings cog → scroll to Radio Settings → Choose Preset → select your region (US Recommended for 915 MHz, EU Recommended for 868 MHz, AU Recommended for 915–928 MHz). The node applies the correct frequency plan immediately.
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 practical — under 10–15 cm; the thin RG178/1.13mm cable used in u.FL pigtails has high loss per meter at 915 MHz, and every extra centimeter is signal you don't get back. Add a small loop of strain relief at the u.FL end with a dot of hot glue or kapton tape — vibration on the bracket is what kills these connectors, not RF.
Serial1, and verify GND is shared between the strip, the XIAO, and the Heltec.
MeshCore firmware already drives the OLED natively — you don't touch it. What this sketch adds is the WS2812B underglow. It runs on a secondary microcontroller: a $3 Seeed XIAO ESP32C3, an Arduino Nano, or any small board with a spare GPIO. That secondary MCU mounts under the base, watches the Heltec's serial debug output over a second USB connection, and pulses the LED strip cyan on mesh activity. No firmware modification to the Heltec required, ever.
"RX" and "advert" is the simplest possible trigger and works fine today, but it's brittle: the exact wording of MeshCore's debug output is not a stable API, and a future firmware release can rename or restructure those lines without warning. If this sketch ever stops flashing the LEDs after a MeshCore update, that's almost certainly why — open a serial terminal on the host, watch the new log format, and update the strstr() patterns. meshcore_py on PyPI or meshcore.js) on the host machine and have it publish a clean signal — a GPIO pulse, a UDP packet, an MQTT message — to the secondary MCU on received messages. That uses the supported binary companion protocol instead of debug-text scraping and won't break across firmware versions. Slightly more setup, much more durable.
// MeshTerm — LED underglow controller // Runs on Seeed XIAO ESP32C3 (or Arduino Nano) // Reads MeshCore serial debug output, animates WS2812B strip // nodestar.net/builds/meshterm #include <FastLED.h> #define LED_PIN 3 // GPIO3 on XIAO ESP32C3; D6 on Nano #define NUM_LEDS 30 // adjust to your strip length #define BAUD 115200 CRGB leds[NUM_LEDS]; // Activity state bool rxFlash = false; // true for one frame on packet receipt uint32_t lastPacket = 0; uint32_t flashEnd = 0; char lineBuf[256]; int lineIdx = 0; void setup() { Serial.begin(BAUD); // USB serial (host monitoring, optional) Serial1.begin(BAUD); // Hardware serial → Heltec TX pin FastLED.addLeds<WS2812B, LED_PIN, GRB>(leds, NUM_LEDS); FastLED.setBrightness(150); } void checkMeshCoreSerial() { // MeshCore debug output contains "RX" on received packets while (Serial1.available()) { char c = Serial1.read(); if (c == '\n' || lineIdx >= 254) { lineBuf[lineIdx] = '\0'; // Trigger flash on incoming packet or node advert if (strstr(lineBuf, "RX") || strstr(lineBuf, "advert") || strstr(lineBuf, "msg") || strstr(lineBuf, "flood")) { rxFlash = true; flashEnd = millis() + 300; // flash for 300ms lastPacket = millis(); } lineIdx = 0; } else { lineBuf[lineIdx++] = c; } } } void updateLEDs() { uint32_t now = millis(); bool flashing = (now < flashEnd); if (flashing) { // White flash on packet receipt for (int i = 0; i < NUM_LEDS; i++) leds[i] = CRGB(200, 255, 255); } else { // Breathing cyan — exponential sine for natural feel float t = now / 2200.0f * PI; uint8_t b = (uint8_t)((exp(sin(t)) - 0.36f) / 2.35f * 140); // MeshCore blue tint when recently active, cyan base otherwise bool recentlyActive = (now - lastPacket < 10000); CRGB color = recentlyActive ? CRGB(0, (uint8_t)(b * 0.6f), b) // blue-cyan: active : CRGB(0, (uint8_t)(b * 0.83f), b); // pure cyan: idle for (int i = 0; i < NUM_LEDS; i++) leds[i] = color; } FastLED.show(); } void loop() { checkMeshCoreSerial(); updateLEDs(); delay(25); }
After flashing and setting your region preset, the companion node works immediately. Most configuration is done through the MeshCore app over Bluetooth. For desktop use, the web client at meshcore.nz connects over USB Serial and offers the same feature set in a browser tab.
# Install the MeshCore app (Android / iOS) or open meshcore.nz in Chrome # Steps: # 1. Open the app → tap Connect # 2. Select your Heltec V4 from the Bluetooth device list # 3. Enter the BLE pairing PIN shown on the Heltec OLED # (default is 123456 if no display; shown on-screen if OLED is active) # 4. App syncs contacts, channels, and messages # 5. Tap the settings cog (top-right) to open node settings # 6. Scroll to Radio Settings → Choose Preset → select your region
# Set your region (required before operating) Settings → Radio Settings → Choose Preset → "US Recommended" # (or EU Recommended, AU Recommended, etc.) # Set a node name (shows to other MeshCore users) Settings → Node Name → "LB Mesh Base" # Send a Flood Routed Advert to announce yourself to the mesh # Tap the signal/wave icon at the top of the app # If repeaters are in range, you'll see "Heard {x} repeat(s)" # Join a public channel Settings → Channels → Add Channel → enter channel name + shared secret # Connect to a Room Server (BBS-style message board) # Room servers appear in your contact list after they respond to your advert # Tap the room server → Messages to read and post
# For repeater or room server firmware, use the web flasher's console # or a serial terminal at 115200 baud after flashing # Set frequency (required for repeaters — no app connection available) freq 915.0 # Set node name name "LB-RPT-01" # Set position (lat, lon for the mesh map) lat 33.77 lon -118.19 # View current config status
In the MeshCore app: Settings → Radio Settings → Choose Preset → US Recommended (or your region). This is mandatory — the node won't operate on the correct frequency plan without it.
Settings → Node Name. Other mesh users will see this in their contact list after you send an advert.
Tap the signal icon in the app. This announces your node to the mesh. Unlike Meshtastic, MeshCore nodes only discover each other via adverts — patience is key. If repeaters are in range, you'll see confirmation.
Public channels broadcast to all subscribers. Room servers store message history — you can disconnect and retrieve missed messages later. Both appear in the app once configured.
Send a direct message to another contact, or post to a channel. If the message shows "Heard {x} repeat(s)" the mesh is routing your traffic through infrastructure.
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 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 leaning forward.
PLA straight off the printer looks fine. For a more finished look: Plasti-Dip rubberized coating in matte black, two coats. Hydro-dipping with carbon-fiber hydrographic film is the higher-effort option. Kits run $20–30 on Amazon.
Route the keyboard USB, Heltec USB-C, and XIAO USB-C to a shared exit at the rear through a compact 4-port hub mounted underneath with 3M dual-lock tape. One cable out to your machine. The LED strip leads route inside the bracket housing through the cable channel in the STL.
Underside vinyl works immediately. For the etched-acrylic look: Ponoko or a local makerspace, laser-etch, $10–20. The etch catches LED edge-light differently from vinyl — more diffuse, more permanent. MeshCore and Node Star SVG assets are available at nodestar.net/assets.
"The mesh should feel like a place you inhabit, not an app you check."
The natural next step is embedding a small SBC — a Pi Zero 2W, a Pi 4, or a mini PC — below the base or in a rear enclosure. Connect it to the Heltec over USB and run the MeshCore companion protocol library (meshcore_py or meshcore.js) as a service. At that point you have a local mesh node that the keyboard talks to directly, serves the web client on your LAN, logs all mesh traffic to a local database, and operates off a single power cable.
From there: the MeshCore companion protocol lets you build whatever you want on top. Auto-reply bots. Integration with Home Assistant or MQTT. A map display on a second monitor showing every node your terminal has heard. A script that pages you when a specific contact comes online. The companion protocol is documented and libraries exist for Python and JavaScript.
The r/cyberdeck community has documented builds with integrated e-ink status panels, solar charging, NFC readers, and satellite fallback via Garmin inReach. The acrylic base format scales to all of it. Everything in this guide is a starting point.
A few directions worth specifically calling out:
Solar + battery, untethered. The Heltec V4 has an onboard 1S Li-ion charge controller and JST-PH battery connector — drop in an 18650 holder or a 3.7V 2000 mAh pouch cell, add a small 5–6V solar panel through a TP4056 (or directly to the V4's solar input on revisions that expose it), and you have a node that runs indefinitely on a sunny windowsill. The acrylic deck stays plugged in at the desk; the radio module becomes optionally cable-free.
Dedicated repeater. If you want your desk build to also extend the mesh for others, add a second Heltec V4 flashed with Repeater firmware and mount it higher — roof, window, top of a bookshelf. MeshCore's architecture separates companion and repeater roles deliberately: companions don't relay, repeaters don't chat. One desk node for you, one infrastructure node for the mesh.
Alternative firmware: Meshtastic. If you're more interested in broadcast-style peer chat, automatic node discovery, and a mature ecosystem of integrations, take a look at Meshtastic. It targets the same SX1262-class hardware as MeshCore but optimizes for peer communication rather than structured routing. The hardware build in this guide doesn't change — you just flash a different image.
Display alternatives. The 0.96" OLED is fine but small. Builders who prefer a larger, low-power readout often swap in a 2.9" or 4.2" e-ink module driven from the host SBC over SPI, leaving the Heltec to do nothing but radio. E-ink updates more slowly but is readable in direct sunlight and uses near-zero idle power — a much better fit if you ever take the deck outside.
Keyboard alternatives. Any USB or USB-C mechanical keyboard with a footprint roughly 28–32 cm wide will fit the standoff pattern in the STL with minor edits. Cheap 60% boards from the usual AliExpress shops work, as do nicer hot-swap kits if you want to choose your own switches. For a more cyberdeck-coded look, consider an ortholinear board (Planck, Preonic) or a split board mounted symmetrically around the display module.
Questions, corrections, build reports: Please reach out through our contact page.