expLoRation is a YAML-defined event engine for Meshtastic mesh networks. It listens to GPS positions and text messages from nodes on the mesh, evaluates configurable trigger conditions, and executes responses — sending DMs, stamping waypoints, tracking scores, and more. Describe a game or scenario in a single config file and the bot runs it, no code required.
- Proximity games — geocaching hunts, warmer/colder finders, checkpoint races. Triggers fire when nodes enter zones, approach waypoints, or come within range of each other. (see
geocache.yaml,trail_race.yaml) - Interactive waypoints — players broadcast Meshtastic waypoints that the bot adopts, renames, and tracks; combine with proximity triggers for supply-drop mechanics or visit stamps. (see
zombie_minigame.yaml) - Passive telemetry — track every node's movement automatically: distance traveled, move counts, rolling 24-hour windows, leaderboard on demand. (see
movement_tracker.yaml) - Guided experiences — linear or branching narrative delivered as nodes move through physical space: ghost tours, orienteering courses, escape-room-style hunts. (see
ghost_walk.yaml)
A config file defines triggers (what causes an event to fire — entering a zone, sending a DM, receiving a GPS fix, a timer expiring) and responses (what happens — a message, a flag, a waypoint, a variable update, a leaderboard). Responses target specific nodes, groups, channels, or everyone with a given flag. Exceptions can gate any event on node state, distance, or randomness.
YAML config → triggers → [exceptions] → responses → targets
- Python 3.10 or newer
- A Meshtastic device reachable via USB serial or TCP
- Meshtastic firmware 2.x on the device
Python dependencies (installed automatically with pip):
meshtastic>=2.7.0
PyYAML>=6.0
pypubsub>=4.0
git clone https://github.com/joeyleake/expLoRation.git
cd expLoRation
pip install -r requirements.txtConnect your Meshtastic device via USB, or have it reachable over TCP, before running.
Run the bot against a config file:
python bot.py --config game.yamlUSB serial — specify the port explicitly or let the bot auto-detect:
python bot.py --config game.yaml --port /dev/ttyUSB0TCP — connect to a device running the Meshtastic TCP server (default port 4403):
python bot.py --config game.yaml --host 192.168.1.50
python bot.py --config game.yaml --host 192.168.1.50 --tcp-port 4403A status summary prints every 5 minutes by default (--status-interval to change, 0 to
disable). Add --verbose to print every incoming position update.
Save this as quickstart.yaml, update the coordinates and node ID, and run it to see both core
trigger types working:
channels:
- label: main
name: LongFast
psk: AQ==
monitor: true
participate: true
zones:
- label: start_zone
# Replace with a small triangle around your test area
points:
- [37.7749, -122.4194]
- [37.7763, -122.4181]
- [37.7751, -122.4168]
messages:
- label: welcome_msg
text: "Welcome! You've entered the zone. Send '!hint' for your first clue."
- label: hint_request
text: "!hint"
- label: clue_msg
text: "Clue: head toward the big oak tree."
flags:
- label: greeted
events:
# Fires when a node enters the zone for the first time
- label: greet_on_enter
trigger:
type: enters_zone
target: start_zone
responses:
- type: send_message
message_label: welcome_msg
to_triggering_node: true
- type: add_flag
flag_label: greeted
to_triggering_node: true
exceptions:
- kind: node_has_flag
flag: greeted
# Fires when a node in the zone sends "!hint" as a DM
- label: hint_command
trigger:
type: dm
message_label: hint_request
zone_label: start_zone
responses:
- type: send_message
message_label: clue_msg
to_triggering_node: truepython bot.py --config quickstart.yaml --verboseThe examples/ directory contains ready-to-adapt scenarios:
| File | Scenario |
|---|---|
geocache.yaml |
Classic geocaching hunt — players enter a start zone, request clues via DM, and race to find a hidden cache |
ghost_walk.yaml |
Self-guided ghost tour — visitors receive atmospheric story fragments as they approach points of interest; collecting all five unlocks a final revelation |
trail_race.yaml |
Trail race director — automatic checkpoint splits, finish-line announcements, cutoff warnings, and sweep alerts |
castle_defense.yaml |
Zone-based security monitoring — three escalating alert tiers fire as unknown nodes approach a defended position |
augusta_national_rangefinder.yaml |
Golf rangefinder — players DM the hole number and receive their live distance to that green |
butterfly_hunt.yaml |
Randomized scavenger hunt — butterflies spawn at random campus locations every hour; walk within 10 m of one to catch it |
debug_monitor.yaml |
Monitoring tool — reports live distance to waypoints on each position update; responds to !update and !status commands |
movement_tracker.yaml |
Passive odometer — tracks distance traveled and move count for every visible node; DM leaderboard for top-5 |
orbital_cannon_survival.yaml |
Survival game — a random node is targeted by an orbital strike; nearby nodes have seconds to flee before the blast destroys them |
warmer_colder_geocache.yaml |
Hot/cold hunt — nodes receive distance-band hints as they walk toward a hidden cache |
zombie_minigame.yaml |
Zombie horde — players DM spawn to summon zombies within 200 m, kill them by walking within 20 m, throw grenades, and compete on a leaderboard |
All example files use placeholder coordinates and fictional node IDs. Replace them with your actual values before running.
All game logic is defined in a single YAML file. See CONFIG.md for the complete reference covering all trigger types, response types, targets, exceptions, variables, and validation rules.
expLoRation is an early public release and feedback is very welcome. If you run it in the field, find a bug, have a feature idea, or want to share a config you built, please open an issue or pull request on GitHub. The goal is to make this useful for the broader Meshtastic community, so real-world use reports are especially valuable.
MIT — see LICENSE.

