Jordan Fielding
Published © MIT

Nomad 2 as a Laptop-Free Field HQ

I turned Nomad 2 into a throw-in-the-ruck box: mesh texts, GPS breadcrumbs, quick RF checks—no laptop, 45-min setup.

IntermediateWork in progress1 hour87
Nomad 2 as a Laptop-Free Field HQ

Things used in this project

Hardware components

Compact Pi
×1
5" screen
×1
mini keyboard
×1
Recon adds internal RTL-SDR
×1

Software apps and online services

pip3 install meshtastic
Flashed with Raspberry Pi Imager
sudo apt install rtl-sdr — rtl_test, rtl_tcp, etc.

Story

Read more

Schematics

Nomad 2 — High-Level Block Diagram (LoRa + GPS + optional SDR)

Simple reference diagram for this project. Shows the Nomad 2 core (Pi 4/5) with built-in LoRa and GPS, optional RTL-SDR on the Recon model, optional Wi-Fi/Ethernet/LTE backhaul, and power. This is a block diagram (not a wiring schematic).

Code

setup_meshtastic.sh

SH
Installs Meshtastic CLI, gpsd, and tmux on Raspberry Pi OS. Run with bash
#!/usr/bin/env bash
set -euo pipefail

sudo apt update
sudo apt install -y python3-pip gpsd gpsd-clients tmux

# Meshtastic CLI
pip3 install --upgrade meshtastic

echo "Done. Next:"
echo " - Enable SSH (raspi-config) if not already."
echo " - Configure gpsd and add the services below."

meshlog.service

INI
Starts on boot and logs Meshtastic packets to ~/mesh.log. Enable with sudo systemctl enable --now meshlog
# /etc/systemd/system/meshlog.service
[Unit]
Description=Meshtastic packet logger
After=network-online.target

[Service]
Type=simple
# Use /usr/bin/env so it finds the CLI whether pip put it in /usr/local/bin or ~/.local/bin
ExecStart=/usr/bin/env meshtastic --subscribe
WorkingDirectory=/home/pi
StandardOutput=append:/home/pi/mesh.log
StandardError=append:/home/pi/mesh.log
Restart=always
User=pi

[Install]
WantedBy=multi-user.target

breadcrumbs.py

Python
Polls gpsd and appends ~/breadcrumbs.csv with time/lat/lon. Start with python3 ~/breadcrumbs.py.
#!/usr/bin/env python3
import time, json, subprocess, csv, os, shlex

OUT = os.path.expanduser('~/breadcrumbs.csv')
HDR = ['ts','lat','lon','alt','speed','track']

# Ensure header exists
exists = os.path.exists(OUT)
with open(OUT, 'a', newline='') as f:
    w = csv.writer(f)
    if not exists:
        w.writerow(HDR)

while True:
    try:
        # one JSON NMEA/TPV object from gpsd
        line = subprocess.check_output(shlex.split('gpspipe -w -n 1')).decode(errors='ignore').strip()
        if not line:
            time.sleep(2); continue
        j = json.loads(line)
        if j.get('class') == 'TPV' and 'lat' in j and 'lon' in j:
            row = [
                time.time(),
                j.get('lat'), j.get('lon'),
                j.get('alt',''),
                j.get('speed',''),
                j.get('track','')
            ]
            with open(OUT, 'a', newline='') as f:
                csv.writer(f).writerow(row)
    except Exception:
        time.sleep(2)
    time.sleep(5)

hb.sh

SH
Sends a tiny heartbeat message every time you run it. Add to crontab for periodic pings.
#!/usr/bin/env bash
meshtastic --sendtext "HB $(date +%H:%M)"

Credits

Jordan Fielding
3 projects • 0 followers
Ham Enthusiast, father of two, and freelance tech writer. I tinker with mesh networking, LoRa, solar power, and low-power design.

Comments