Linux daemon for MeshCore companion devices. Bridges a LoRa radio to UI clients via the MeshCore companion frame protocol, running as a systemd-managed service.
Similar in spirit to meshtasticd but for the MeshCore ecosystem — companion-only, UI-agnostic, with support for both direct SPI and USB serial radios.
- Companion-only — no repeater or room server functionality
- UI-agnostic — connects to any MeshCore-compatible UI over Unix socket or TCP
- Meshtadpole support — USB SX1262 radio via pymc_usb firmware over serial
- Direct SPI support — SX1262 modules connected via SPI bus
- systemd integration — proper daemon lifecycle, PID file, structured logging
- Auto-reconnect — exponential backoff on radio failure
# Install
pip install meshcored
# Generate default config
meshcored --write-defaults /etc/meshcored/config.yaml
# Edit config for your hardware
sudoedit /etc/meshcored/config.yaml
# Run manually (for testing)
meshcored -c /etc/meshcored/config.yaml
# Or install as a systemd service
sudo cp systemd/meshcored.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now meshcoredThe default config template covers all supported options. Generate it with:
meshcored --write-defaults /etc/meshcored/config.yamlFor SX1262 modules connected directly to the SPI bus (e.g., Raspberry Pi):
radio:
type: "sx1262"
spi_bus: 0
cs_pin: 5
reset_pin: 15
busy_pin: 14
irq_pin: 25
txen_pin: null
frequency: 915000000
bandwidth: 250000
spreading_factor: 10
coding_rate: 5
tx_power: 22For the Meshtadpole USB stick running pymc_usb firmware:
radio:
type: "meshtadpole"
serial_port: "/dev/ttyACM0"
baudrate: 921600
frequency: 915000000
bandwidth: 250000
spreading_factor: 10
coding_rate: 5
tx_power: 22The Meshtadpole exposes the SX1262 radio over USB-CDC serial. It must be running pymc_usb firmware (not Meshtastic firmware).
Choose between Unix domain socket (default) or TCP:
listener:
transport: "unix"
path: "/var/run/meshcored.sock"Or TCP:
listener:
transport: "tcp"
host: "127.0.0.1"
port: 4401companion:
node_name: "meshcored"
adv_type: 1 # 1=chat, 2=repeater, 3=room, 4=sensor
max_contacts: 1000
max_channels: 40
offline_queue_size: 512reconnect:
enabled: true
initial_delay_ms: 1000
max_delay_ms: 60000
backoff_factor: 2.0- Flash pymc_usb firmware onto your Meshtadpole
- Connect the USB stick to your Linux machine
- Identify the serial device (usually
/dev/ttyACM0or/dev/ttyUSB0) - Set
radio.type: "meshtadpole"andradio.serial_portin config - Start the daemon
The included systemd/meshcored.service provides:
- Kernel module loading (
spidev,spi_bcm2835,gpiomem) - Device permissions for SPI, GPIO, I2C, and USB serial
- Systemd hardening (protected system, private tmp, capability bounding)
- Auto-restart on failure
Install it:
sudo cp systemd/meshcored.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now meshcoredmeshcored [-h] [-c CONFIG] [-v] [--write-defaults [PATH]]
options:
-c, --config CONFIG Path to config file
-v, --version Show version
--write-defaults Write default config template
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ UI Client │────▶│ DaemonFrame │────▶│ Companion │
│ (any MeshCore│ │ Server │ │ Radio │
│ compatible)│◀────│ (unix/tcp) │◀────│ (companion) │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌────────────▼────────────┐
│ Radio │
│ ┌──────────────────┐ │
│ │ SX1262Radio (SPI)│ │
│ │ USBLoRaRadio (USB)│ │
│ └──────────────────┘ │
└─────────────────────────┘
DaemonFrameServersubclassesCompanionFrameServerto add Unix socket supportDaemonBridgemanages the radio lifecycle, companion setup, and reconnect loop- The daemon runs in a single asyncio event loop with signal handling
git clone https://github.com/aseiger/meshcored.git
cd meshcored
pip install -e ".[dev,hardware]"
pytest tests/ -vMIT