‹ Jack's Brain

MeshCore Management with RemoteTerm and Seeed XIAO ESP32S3 & SX1262

Apr 27, 2026

MeshCore is a peer-to-peer encrypted mesh radio communications system. It runs on inexpensive hardware and operates in license-free sub-GHz bands worldwide. MeshCore radios (called “companions”) require an interface to send and receive messages. Most commonly, people use one of the available mobile apps, but this requires physical proximity to the radio for Bluetooth or Serial communication.

The existing clients were alright, but weren’t quite doing it for me. So, I built Remote Terminal for MeshCore (RemoteTerm), an open-source application that runs on a server with a connected MeshCore radio. RemoteTerm provides an interface to the local MeshCore mesh, allowing any device on the same local network to send and receive messages via the radio. It’s extremely mobile-friendly, and also enables additional abilities such as:

  • Caching all received packets, decrypting as you gain keys
  • Running multiple Python bots that can analyze messages and respond to DMs and channels
  • Monitoring unlimited contacts and channels (radio limits don’t apply – packets are decrypted server-side)
  • Accessing your radio remotely over your network or VPN
  • Forwarding packets to MQTT, SQS, Apprise, etc., including community observer aggregators like LetsMesh and MeshRank
  • Visualize the mesh as a map or node set, view repeater stats, and more!

I have a great love of all things compact and efficient, and the kind folks over at Seeed were keen to see MeshCore running on a generously donated board. So, this tutorial provides a fundamental setup for running RemoteTerm on a Raspberry Pi using the Seeed XIAO ESP32S3 & SX1262 Kit as a companion radio and Raspberry Pi Zero 2W for a compact, all-in-one MeshCore solution for power users!

Setup

Supplies

Note that there are many options for small-scale couplings, such as a USB Micro Male to USB-C 180 adapter and a USB-C male-male coupler to provide a tight, self-contained radio mount. Combine with a dongle board for an all-in-one radio/server combo ready to plug into a suitable power supply.

The author's combined right-angle adapter board

The author’s combined right-angle adapter board

Board Setup

  • Combine the Seeed XIAO ESP32S3 SX1262 boards using the coupler.
  • Attach the LoRA Antenna to the SX1262.
  • Plug into your computer or phone and flash with “Seeed Studio Xiao S3 WIO” (Serial Companion) per Seeed instructions.

Pi Setup

  • Download the Raspberry Pi Imager
  • Open the imager and select your Raspberry Pi model, and select “Raspberry Pi OS Lite (64 bit)” (or as appropriate for your particular board. “lite” distributions (without a desktop environment) are recommended for performance reasons.
  • Configure SSH and WiFi connection
  • Connect the radio to the Raspberry Pi’s USB port (not the POWER port), and connect power to the Pi
  • Use your home router’s admin interface to determine the address assigned to the Raspberry Pi. Consider allocating a DHCP reservation to prevent IP address changes.

Pi Configuration

  • Open an SSH session to the Raspberry Pi (e.g., ssh <username>@<ip address>)
  • Validate that the radio appears. find /dev/serial/by-id -maxdepth 1 -type l -print | sort should show a listing like /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_XX:XX:XX:XX:XX:XX-if00
  • Update and upgrade
    sudo apt update                        # update package repositories
    sudo apt upgrade -y                    # upgrade packages
    sudo apt-get install -y git            # install git
    curl -fsSL https://get.docker.com | sh # install docker
    
  • Clone & Launch
    git clone https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
    cd Remote-Terminal-for-MeshCore/
    
    # configure the docker setup
    ./scripts/setup/install_docker.sh
    

Configuration Option Walkthrough

The README provides setup instructions for the application if you want to set everything up yourself. However, the Docker setup script above provides a guided setup that is a good fit for most environments. Below is a brief explanation of each configuration step:

Image Source

Docker images of the source code are published periodically. Using the published image means you don’t have to build your own, which can take significant time on small systems. Using the prebuilt image is recommended unless your Pi is powerful enough to build its own image.

Transport

This tutorial instructs you to set up a serial companion. TCP and Bluetooth companions are supported by RemoteTerm, but are slightly more complicated to set up. If you’ve flashed your radio as a serial companion, you need to select serial here.

In this step, the setup script will attempt to autodetect your radio. If it doesn’t appear here, ensure your radio is connected and powered on. The earlier command find /dev/serial/by-id -maxdepth 1 -type l -print | sort can help to show the port your radio is on. You may manually enter a port here, but if it’s not autodetected, there’s a good chance the radio isn’t visible to the Pi, and the setup will fail.

Bot System

RemoteTerm supports creating Python bots that can respond to messages on the MeshCore network. However, bots should not run on a RemoteTerm instance connected to an untrusted network, as this can allow other people with network access to execute arbitrary Python code on your radio host. If you enable bots, you should set a username/password for your instance and use a Snakeoil certificate. If you disable bots (recommended on untrusted networks), you can always rerun the setup script and enable them later. Disabling bots is the safest approach if you’re not certain about your security posture or don’t plan to run them.

HTTP Basic Auth

HTTP Basic Auth provides simple username/password protection for your RemoteTerm instance and is highly recommended, especially if you have enabled bots.

HTTPS / Snakeoil TLS

Some functionality (desktop notifications, GPU-accelerated channel finder) depends on an HTTPS connection. Generating a snakeoil certificate will provide access over HTTPS, though it will trigger a warning because the self-generated certificate is not trusted by browsers by default. However, your connection will still be encrypted. It is recommended to do this if you have enabled HTTP Basic Auth, as otherwise your username and password will be visible to others on the same network as your RemoteTerm server.

Booting the Server

After you’ve configured your dockerfile via the setup script, run

sudo docker compose up -d

to start the server. The following commands may also be helpful:

sudo docker compose up -d    # start RemoteTerm in the background
sudo docker compose logs -f  # follow the container logs live
sudo docker compose down     # stop and remove the running container
sudo docker compose restart  # restart the container without changing the image
sudo docker compose pull && sudo docker compose up -d   # upgrade to the latest published image and restart

Warning

RemoteTerm does full management of the radio, meaning that once a radio is connected to RemoteTerm, all contacts/channels will be imported and offloaded to RemoteTerm, and the contacts actually synced to the device will be governed by RemoteTerm. This means that RemoteTerm can be a poor fit for users who want to swap radios in and out while maintaining radio state (favorites, channels, etc.), regardless of app usage.

Hotspot (optional)This is optional and recommended only for radios that will be used away from your home. This configuration will disconnect the radio from your home wifi but set it to begin broadcasting its own wifi so it can be accessible without an internet connection.
  • Set up a hotspot

    sudo nmcli device wifi hotspot ssid <hotspot name> password <hotspot password> ifname wlan0
    
  • You will likely need to connect to the new Wi-Fi network and SSH into the Pi again. The URL of your Pi will be the “default gateway” in the wifi configuration of the new network when you connect to it.

  • Configure the hotspot to broadcast on boot

    nmcli connection
    # grab the UUID of the Hotspot connection
    sudo nmcli connection modify <hotspot UUID> connection.autoconnect yes connection.autoconnect-priority 100
    

Getting Started with RemoteTerm

Main Screen

Main application screen

Main application screen

The main RemoteTerm screen shows a list of known channels, repeaters, and room servers, with a space for Favorites on top. Click on any contact or channel to begin messaging, or click on a repeater to log in and view the administration interface. You can add channels or contacts with the green button at the top.

Above the contact list are a set of tools:

Packet FeedA display of raw, incoming packets as they arrive, with statistics from the current browser session. Click any packet to view an analysis of packet contents
Node MapVisualize node location on a map
Mesh VisualizerCustomizable 3D representation of nodes and traffic in real-time
TraceRun multi-hop traces between repeaters for signal strength information
Message SearchSearch all channel and direct message history for words or phrases
Channel FinderPublic group channels are identified with a leading “#” and an alphanumeric-with-dashes name. The channel finder uses your GPU to attempt to find channel names for heard-but-not decrypted packets – discover fun chats happening around you!

Messaging (Channels)

To create or join a channel, click the “Add Channel/Contact” button in the top-left corner of the app’s main menu. Private channels, identified only by their key, or Hashtag channels, identified by their name as a #hashtag, can be joined from the menu that appears.

The Public channel is added automatically to all new RemoteTerm instances. Your local mesh will likely have its share of active channels: Public, #test or #testing, and #bots or #bot can be good places to get started or find people who can point you to good local rooms for newcomers.

When a channel is selected, the screen will look something like this:

Channel status bar

Channel status bar

In the status bar:

[Room key]Provides the MeshCore key for the given room
bellConfigures notifications, either as desktop notifications (which require the browser to be open) or Web Push (which can deliver even when the browser is closed). These require a trusted HTTPS context (the snakeoil certificates available from the installer may or may not be sufficient to allow notifications, depending on your operating system and browser)
earthEnables regional routing overrides, which limit the radio’s regional scope when it sends messages only in this channel. Users with corresponding or different region settings will be able to see or auto-ignore your messages, respectively
chevrons-left-rightAllows for per-channel hop width overrides separate from your radio default
starAdds or removes a channel to your favorites
trash-2Deletes the channel, but retains historical messages if you choose to add it back later

In the upper left corner is the channel name with an info icon; you can click this to see information about the channel, like how active it is, who is sending messages, and its key (which you can also see by clicking the “Show key” text in the top status bar):

Example room analytics

Example room analytics

Messaging (Direct Messages)

Sending direct messages works similarly to channel messages, with a few different options.

DM status bar

DM status bar

[Contact key]Provides the MeshCore key for the given contact and information about when they were last heard (advert, message, etc.), and the method (direct, or what path the packet took)
routeSends a routed probe to the contact to discover a more ideal routing path for messages to follow
traceSends a direct trace to the contact, with remote and local SNR data
bellConfigures notifications, either as desktop notifications (which require the browser to be open) or Web Push (which can deliver even when the browser is closed). These require a trusted HTTPS context (the snakeoil certificates available from the installer may or may not be sufficient to allow notifications, depending on your operating system and browser)
starAdds or removes a contact to your favorites
trash-2Deletes the covnersation, but retains historical messages if you choose to add it back later

Similarly to channels, you can click the info icon or the name in the top bar of a selected contact to see analytics for the contact:

Contact analytics

Contact analytics

Settings

Settings area

Settings area

Access the settings pane from the “Settings” button in the upper right.

Radio

Set up your radio’s name, keys, radio parameters, location, and some tweakables, such as automatic retry, multiack, and advertisement intervals. You can also manually send advertisements and send our discovery requests to find nearby nodes.

Local Configuration

Customize your RemoteTerm’s look, feel, and behavior.

Database & Messaging

Manage historical data retention, DM decryption settings, tracked repeaters, and block or purge auto-added contacts.

Statistics

See data about the MeshCore nodes around you and your RemoteTerm instance.

MQTT & Automation

RemoteTerm shines in providing rich integrations for automated systems. Integrations perform actions when packets of certain types are heard (for example, when a message is sent in a channel or a DM is received). These include:

  • Forwarding packets over MQTT to:

    • Private brokers: send raw or decrypted packets/messages from all or selected channels/contacts
    • Community aggregators: send raw packets to communal MeshCore statistics and mapping projects like MeshRank or LetsMesh
    • Home Assistant: publish MQTT discovery payloads for tracked repeater battery level/noise floor/sensors, messages from all or selected channels, contact GPS location, and local radio status to Home Assistant via HA’s built-in MQTT integration
  • Webhooks, sending GET or POST requests to a specific URL with message data

  • Apprise, a flexible notification fanout tool that can forward to Discord, Telegram, SMS, email, and much, much more

  • Enqueueing packets or messages in Amazon SQS queues

  • Python bots that execute code on each received messages, and which can send messages back to users in DMs or channels

Let’s get some set up!

Set Up Your First Integration: Contribute to Community Mesh Mapping

MeshCore exists in many places, and the mesh is growing every day! However, not all MeshCore networks are linked, so not all messages are heard everywhere. However, an overall view of the mesh — what nodes are where, what messages are flowing in what patterns, and connectivity breakdowns, etc. — can be helpful to the community in understanding the network and troubleshooting. To provide this debug view, community aggregators ingest a feed of raw packets from observers. These observers report which packets they hear, which enables maps and statistics to be gathered about the mesh. Importantly, community aggregators receive only raw packets, meaning the packets you hear are sent to the aggregator while still encrypted. No private communications are sent to the aggregator in a way that allows it to read them, so your DMs and decrypted group messages remain securely yours.

RemoteTerm makes it easy to use the same radio you use to send and receive messages to contribute to these community mapping and visibility efforts as an observer node.

Note

MeshCore does not allow for messages to enter the mesh via MQTT nodes (as Meshtastic does); the community observability efforts are read-only.

There are many community observer platforms, but LetsMesh is one of the more popular, and that’s what we’ll set up.

  1. Go to https://analyzer.letsmesh.net/ in a browser. Click “Log In,” then follow the prompts to create an account on the LetsMesh forums, and log in to the LetsMesh analyzer.

  2. In RemoteTerm, navigate to “Settings”, then click “MQTT & Automation”.

  3. Click “Add Integration,” and choose “LetsMesh (US)” from the list on the left.

Integrations creation menu

Integrations creation menu

  1. Click “Create”.

  2. Fill in the same email you used to sign up with LetsMesh, and enter the nearest airport’s IATA code

    1. IATA codes are used for general geographical grouping. For example, SEA tells LetsMesh you’re in the general Seattle area; AKL tells LetsMesh you’re in Auckland. It doesn’t have to be a precise match, and you can look up your local airport here. Make sure you use the three-letter IATA code, and not the four-letter ICAO code.
  3. Click “Save as Enabled”.

You should see a green dot and a “Connected” status; a healthy integration should look like this:

Healthy community mesh integration

Healthy community mesh integration

If you instead see an amber dot, click the “ⓘ” symbol by the Edit button to see the error.

Now, navigate to https://analyzer.letsmesh.net/my-nodes. Within about 15 minutes, you should see your radio appear green there, and your own telemetry and packets being forwarded to LetsMesh!

Healthy community integration providing data to LetsMesh

Healthy community integration providing data to LetsMesh

You can repeat the setup with LetsMesh’s EU observer; the data being sent is the same, but having two servers to report to means that your observations can still be logged even if one of LetsMesh’s ingestion endpoints goes down.

Tip

You can have as many observer entries as you want. Some other community aggregators have their own presets, but any aggregator that supports the functionality of meshcoretomqtt can be configured to work with RemoteTerm via the general “Community MQTT/meshcoretomqtt” integration. When that integration is selected, you can configure the MQTT reporting that the aggregator needs, however.

Bots

RemoteTerm provides an interface to program your own bots. Bots consist of simple Python code that is run whenever a message is received from a contact or channel. When the message is determined to be addressed to the bot (for example, from a specific contact, containing a specific message, or in a specific room), the bot can respond to it.

Warning

Bots generate automated traffic in response to other messages and are capable of responding to their own messages. This means it is your responsibility to ensure that bots are carefully written to prevent flooding the mesh or infinitely looping. It’s highly recommended that you use a test room to try out your bot before letting it loose in a more public room.

Tip

Many local meshes prefer not to have automated traffic in their Public channel; others may have dedicated rooms where people deploy their bots by convention. Try checking in with people to see if there’s a standardized bot channel that is a good fit for your bot to respond in. Some meshes use #bot or #bot-[region] (e.g. #bot-pdx for Portland, OR).

Set Up a Simple Dice Rolling Bot

This bot provides simple dice-rolling capabilities, requested in standard form (e.g., “3d6” means roll 3 six-sided dice).

  1. In RemoteTerm, navigate to “Settings”, then click “MQTT & Automation”.
  2. Click “Add Integration,” and choose “Python Bot” from the list on the left.
  3. Click “Create”.
  4. Name the bot something descriptive, like “Dice Bot”.
  5. Erase the code from the editor, and paste the following code instead:
import random
import re

def bot(**kwargs) -> str | list[str] | None:
    """Dice roller — responds to !dice NdS in #bot only."""
    sender_name = kwargs.get("sender_name")
    message_text = kwargs.get("message_text", "")
    channel_name = kwargs.get("channel_name")
    is_outgoing = kwargs.get("is_outgoing", False)

    if is_outgoing or channel_name != "#bot":
        return None

    match = re.match(r"^!dice\s+(\d+)d(\d+)$", message_text.strip())
    if not match:
        return None

    count, sides = int(match.group(1)), int(match.group(2))
    if count < 1 or count > 20 or sides < 2 or sides > 100:
        return "I don't know how to roll that! [1-20]d[2-100]"

    rolls = [random.randint(1, sides) for _ in range(count)]
    parts = " + ".join(str(r) for r in rolls)
    name = sender_name or "Someone"
    return f"@[{name}] rolled {parts} = {sum(rolls)}"
  1. Click “Save as Enabled”.

Using another radio, navigate to the #bot room and request a dice roll (e.g. “3d6”). The output should look something like this:

Dice bot in action

Dice bot in action

If you don’t see the response as expected, you can check the server logs from your Docker container to see if there is an error reported when the Python code is executed.

Set Up a Path Width Bot

MeshCore firmware v1.14+ brought big changes to how messages traverse the mesh; packets traversing compatible routers can now carry multibyte hop identifiers, which makes it much simpler to determine the path a message took over the mesh. We can make a bot to help people determine whether a multibyte path is associated with their message (meaning both they and all repeaters along the path are running firmware 1.14+).

This script uses the ability to send multiple messages in response by providing a hop-width message and the packet hop data itself.

def bot(**kwargs) -> str | list[str] | None:
    """Report the sender's path hop width in #bot."""
    sender_name = kwargs.get("sender_name")
    message_text = kwargs.get("message_text", "")
    channel_name = kwargs.get("channel_name")
    is_outgoing = kwargs.get("is_outgoing", False)
    path = kwargs.get("path")
    path_bytes_per_hop = kwargs.get("path_bytes_per_hop")

    if is_outgoing or channel_name != "#bot":
        return None

    if "!hopwidth" not in message_text.lower():
        return None

    name = sender_name or "Someone"

    if path_bytes_per_hop is None or not path:
        return f"@[{name}]'s hop width is unknown (no path data)"

    hop_count = (len(path) // 2) // path_bytes_per_hop

    if path_bytes_per_hop == 1:
        msg = f"@[{name}]'s hops are 1 byte wide; they might not be on 1.14+ or there may be incompatible repeaters in the path."
    else:
        msg = f"@[{name}]'s hops are {path_bytes_per_hop} bytes wide; they are using firmware v1.14+!"

    return [msg, f"Heard via {hop_count} hop{'s' if hop_count != 1 else ''}"]

Forward MeshCore Channel Messages to Discord

Apprise is a flexible notification service supported by RemoteTerm. Apprise lets you choose one or many of hundreds of notification services to send channel or direct messages to, from Discord to Telegram to SMS, email, and much more.

As an example, we’ll use Apprise to forward all messages in a particular MeshCore channel to Discord via RemoteTerm.

  1. Open Discord to a channel you have administrator privileges in.
  2. Right-click the channel name and click “Edit Channel”.
  3. Click “Integrations” in the left menu, then click “Create Webhook”.
  4. Name the webhook appropriately and copy the webhook URL. The webhook should take the form of https://discord.com/api/webhooks/<WebhookID>/<WebhookToken>
Discord webhook configuration

Discord webhook configuration

  1. In RemoteTerm, navigate to “Settings”, then click “MQTT & Automation”.
  2. Click “Add Integration,” and choose “Apprise” from the list on the left.
  3. Click “Create”.
  4. Add a notification URL to the input. Per Apprise’s Discord documentation, this should take the form of discord://{WebhookID}/{WebhookToken}/. Use the WebhookID and WebhookToken from the copied URL.
  5. Choose whether to preserve the user’s identity by using their configured name and avatar when available (suggested to be enabled).
  6. Choose whether to include routing path data in the message (suggested to disable)
  7. Select the contacts/channels you would like forwarded to Discord, and “Save as Enabled”.
Configured Discord integration

Configured Discord integration

Once enabled, you should see messages begin to flow! If you have trouble or don’t see messages you expect, check the server logs for any reported errors, or try enabling all channels to ensure that at least the RemoteTerm <=> Discord linkage is working.

Discord integration in action

Discord integration in action

You can see a full list of the services that Apprise supports, and their config URI formats, in the Apprise documentation. Their URI builder makes it simple to configure complex Apprise URLs.

Integrate RemoteTerm with Home Assistant

Home Assistant provides a framework for simple-to-use, powerful home automation. RemoteTerm offers integration with Home Assistant via the built-in MQTT integration (MQTT Discovery). With this integration, you can trigger automations based on messages, monitor repeaters and local radio telemetry, and track contacts via GPS in their advertisements.

In order to take advantage of repeater telemetry tracking, you will need to enroll the desired repeaters in automatic telemetry fetch. Once enrolled, RemoteTerm will request telemetry at a configurable interval, retaining historical values and making up-to-date values visible in Home Assistant. To configure automatic telemetry fetch, log in to your desired repeater in RemoteTerm by selecting it in the sidebar and providing credentials. Scroll to the bottom and click “Opt Repeater into 8hr Interval Metrics Tracking”. Once enrolled, metrics will be fetched by default every 8th hour in the UTC time zone (midnight, 8:00 AM, 4:00 PM). After successful enrollment, data metrics will show collection within 24 hours.

Enrolled repeater telemetry history

Enrolled repeater telemetry history

Tip

Automated repeater telemetry fetch has a customizable interval; you may choose a frequency ranging from once hourly to once daily, with the restriction that automated fetches may not exceed 24 in one 24-hour period. Thus, a single repeater may be checked at most hourly, two repeaters may be checked at most every two hours, and the maximum of eight tracked repeaters may be checked at most every eight hours.

You may configure your automated telemetry fetch interval in Settings under the “Database & Messaging” section.

Repeater telemetry auto-fetch configuration

Repeater telemetry auto-fetch configuration

Once you have configured your tracked repeaters as desired and [MQTT Discovery is enabled in Home Assistant, you can configure an integration in RemoteTerm to start the data flow.

  1. In RemoteTerm, navigate to “Settings”, then click “MQTT & Automation”.
  2. Click “Add Integration,” and choose “Home Assistant MQTT Discovery” from the list on the left.
  3. Click “Create”.
  4. Enter your MQTT broker information, including host and port, and username/password/TLS configuration, if applicable.
  5. Choose contacts to track via advertisement GPS location in Home Assistant.
  6. Select your telemetry-tracked repeaters you wish to see in Home Assistant.
  7. Choose the message scope for triggers within Home Assistant.
  8. Click “Save as Enabled”.

Note that the RemoteTerm integration config screen provides the exact created topics/entities in the dropdowns “What gets created in Home Assistant” and “Published Topic Summary”.

Warning

Tracked contacts and repeaters will create entities in Home Assistant; you may wish to limit your selection to only the contacts you care about to avoid cluttering Home Assistant.

Message events do not create entities and are only used for triggers; there is generally no concern with allowing all messages to flow to Home Assistant.

To view the new entities, log in to your Home Assistant web interface. Click Settings, then Devices & Services, then MQTT. You should see your tracked entities displayed.

Example MQTT Home Assistant integration showing radio entities

Example MQTT Home Assistant integration showing radio entities

Click on them to view their available metrics. For repeaters, the most recently available non-stale metrics will be displayed; if you have only recently enabled automated telemetry fetch, you may need to wait up to eight hours for all repeater and radio telemetry fields to populate within Home Assistant.

Repeater telemetry within Home Assistant

Repeater telemetry within Home Assistant

For additional configuration guidance, review README_HA.md in the RemoteTerm repository. For example, following the instructions in “Full monitoring dashboard with message feed” will provide a dashboard like this:

Example Home Assistant dashboard

Example Home Assistant dashboard

Next Steps

MeshCore has a vibrant mix of people exploring radio, emergency preparedness, and the wonderful communities within local channels. Automate your home, install RemoteTerm via your browser as a mobile app and get push notifications for your favorite channels, or set up your own repeater to join the growing infrastructure! As you journey through MeshCore, RemoteTerm can serve as your base of operations and integration with other services as your mesh usage grows!

Keep up to date with RemoteTerm’s development or ask questions by joining the official MeshCore Discord, then following RemoteTerm’s development thread. The codebase updates often and you can catch the latest release in the Changelog, and, of course, RemoteTerm is free, ad-free, and open source forever — give it a star on GitHub.