agessaman 07a2db4ede Slim scheduler, add maintenance module
- Introduced a new `maintenance` module to handle data retention, log rotation, and nightly email tasks.
- Updated the `scheduler` to utilize the `MaintenanceRunner` for executing maintenance tasks, improving code organization and clarity.
- Enhanced documentation to reflect changes in logging configuration and data retention processes.
- Adjusted tests to accommodate the refactored scheduler methods and ensure proper functionality.
2026-03-19 19:40:33 -07:00

MeshCore Bot

A Python bot that connects to MeshCore mesh networks via serial port, BLE, or TCP/IP. The bot responds to messages containing configured keywords, executes commands, and provides various data services including weather, solar conditions, and satellite pass information. A web viewer provides a browser-based dashboard for monitoring and managing the bot.

Features

  • Connection Methods: Serial port, BLE (Bluetooth Low Energy), or TCP/IP
  • Keyword Responses: Configurable keyword-response pairs with template variables
  • Command System: Plugin-based command architecture with built-in commands
  • Command Aliases: Define shorthand aliases for any command via [Aliases] config section
  • Rate Limiting: Global, per-user (by pubkey or name), and per-channel rate limits to prevent spam
  • User Management: Ban/unban users with persistent storage
  • Scheduled Messages: Send messages at configured times
  • Direct Message Support: Respond to private messages
  • Inbound Webhook: Accept HTTP POST payloads and relay to MeshCore channels or DMs
  • Web Viewer: Browser-based dashboard for monitoring contacts, mesh graph, radio settings, feeds, live packets, and logs
  • Radio Control: Reboot or connect/disconnect the radio connection from the web viewer
  • Database Migrations: Versioned schema migrations via MigrationRunner — safe upgrades across versions
  • DB Backup Scheduling: Automated daily/weekly database backups with configurable retention
  • Nightly Maintenance Email: Daily digest with uptime, network activity, DB stats, and error counts
  • Log Rotation: Configurable log rotation via [Logging] section
  • Logging: Console and file logging with configurable levels; optional structured JSON mode for log aggregation (Loki, Elasticsearch, Splunk)

Service Plugins

  • Discord Bridge: One-way webhook bridge to post mesh messages to Discord (docs)
  • Telegram Bridge: One-way bridge to post mesh messages to Telegram (docs)
  • Packet Capture: Capture and publish packets to MQTT brokers (docs)
  • Map Uploader: Upload node adverts to map.meshcore.dev (docs)
  • Weather Service: Scheduled forecasts, alerts, and lightning detection (docs)
  • Webhook Service: Accept inbound HTTP POST payloads and relay to channels or DMs

Requirements

  • Python 3.9+
  • MeshCore-compatible device (Heltec V3, RAK Wireless, etc.)
  • USB cable or BLE capability

Installation

Quick Start (Development)

  1. Clone the repository:
git clone <repository-url>
cd meshcore-bot
  1. Create a virtual environment and install dependencies via Makefile:
make dev          # creates .venv, installs all deps including test tools

Or for production dependencies only:

make install      # creates .venv, installs runtime + optional deps
  1. Configure the bot:

Interactive TUI (recommended): Launch the ncurses config editor — it reads an existing config.ini or lets you start from config.ini.example:

make config

Manual option (full config): Enables all bot commands and provides all configuration options:

cp config.ini.example config.ini
# Edit config.ini with your settings

Manual option (minimal config): For users who only want core testing commands (ping, test, path, prefix, multitest):

cp config.ini.minimal-example config.ini
# Edit config.ini with your connection and bot settings
  1. Run the bot:
.venv/bin/python meshcore_bot.py
  1. Run tests and linting:
make test         # pytest with coverage
make test-no-cov  # pytest without coverage (faster)
make lint         # ruff check + mypy
make fix          # auto-fix ruff lint errors

Production Installation (Systemd Service)

For production deployment as a system service:

  1. Install as systemd service:
sudo ./install-service.sh
  1. Configure the bot:
sudo nano /opt/meshcore-bot/config.ini
  1. Start the service:
sudo systemctl start meshcore-bot
  1. Check status:
sudo systemctl status meshcore-bot

See Service installation for detailed service installation instructions.

Debian Package (.deb)

Build and install a .deb package for Debian/Ubuntu systems:

make deb
sudo dpkg -i dist/meshcore-bot_*.deb

The package installs the bot to /opt/meshcore-bot/, installs a systemd unit, and creates a meshcore-bot system user.

Docker Deployment

For containerized deployment using Docker:

  1. Create data directories and configuration:

    mkdir -p data/{config,databases,logs,backups}
    cp config.ini.example data/config/config.ini
    # Edit data/config/config.ini with your settings
    
  2. Update paths in config.ini to use /data/ directories:

    [Bot]
    db_path = /data/databases/meshcore_bot.db
    
    [Logging]
    log_file = /data/logs/meshcore_bot.log
    
  3. Build and start with Docker Compose:

    docker compose up -d --build
    
  4. View logs:

    docker compose logs -f
    

See Docker deployment for detailed Docker deployment instructions, including serial port access, web viewer configuration, and troubleshooting.

NixOS

Use the Nix flake via flake.nix

meshcore-bot.url = "github:agessaman/meshcore-bot/";

And in your system config

{
  imports = [inputs.meshcore-bot.nixosModules.default];
  services.meshcore-bot = {
    enable = true;
    webviewer.enable = true;
    settings = {
      Connection.connection_type = "serial";
      Connection.serial_port = "/dev/ttyUSB0";
      Bot.bot_name = "MyBot";
    };
  };
}

Web Viewer

The web viewer provides a browser-based dashboard for monitoring and managing the bot. Enable it in config.ini:

[Web_Viewer]
enabled = true
host = 0.0.0.0
port = 8080
web_viewer_password = yourpassword   # optional; omit to disable auth

Features:

  • Contacts — live contact list with signal, path, and location data; star any contact; purge inactive contacts by age threshold; export to CSV/JSON
  • Mesh Graph — interactive node graph of the mesh network
  • Radio Settings — manage channels, reboot or connect/disconnect the radio
  • Feeds — manage RSS/API feed subscriptions per channel
  • Packets — raw packet monitor
  • Live Activity — real-time color-coded packet/command/message feed with pause and clear controls
  • Live Channel Messages — real-time channel message monitor via SocketIO
  • Logs — real-time log viewer at /logs; level-based coloring, filter, pause, and clear

Configuration Tab

The /config page exposes bot settings in-browser — no config.ini edit required.

Email & Notifications — configure SMTP and opt in to a nightly maintenance digest:

Field Description
Server hostname SMTP host (e.g. smtp.gmail.com)
Port 587 (STARTTLS), 465 (SSL), or 25 (plain)
Security STARTTLS / SSL / None
Username / Password SMTP credentials (app-specific passwords recommended)
Sender display name Name shown in the From field
Sender email Address shown in the From field
Recipients Comma-separated list of addresses
Nightly email toggle Enable / disable the nightly maintenance digest

All settings are stored in the bot database (bot_metadata table) and take effect immediately. Use Send test email to verify SMTP settings before enabling the digest.

When Nightly maintenance email is enabled, the scheduler sends a digest every 24 hours:

MeshCore Bot — Nightly Maintenance Report
============================================
Period : 2026-03-14 06:00 UTC → 2026-03-15 06:00 UTC

BOT STATUS
──────────────────────────────
  Uptime    : 2d 4h 32m
  Connected : yes

NETWORK ACTIVITY (past 24 h)
──────────────────────────────
  Active contacts  : 12
  New contacts     : 3
  Total tracked    : 47

DATABASE
──────────────────────────────
  Size : 14.2 MB
  Last retention run : 2026-03-15T06:00:00

ERRORS (past 24 h)
──────────────────────────────
  ERROR    : 2
  CRITICAL : 0

LOG FILES
──────────────────────────────
  Current : meshcore_bot.log (2.1 MB)
  Rotated : no

Log Rotation — configure via the Config tab or [Logging] in config.ini:

[Logging]
log_max_bytes = 5242880    # 5 MB per file
log_backup_count = 3       # number of rotated files to keep

Database Backup — schedule automatic backups from the Config tab:

[Maintenance]
db_backup_enabled = true
db_backup_schedule = daily      # daily | weekly | manual
db_backup_time = 02:00          # HH:MM local time
db_backup_retention_count = 7
db_backup_dir = /data/backups

The Maintenance Status card in the Config tab shows the last backup time, next scheduled run, and log rotation status.

Radio Control

The Radio Settings page includes two control buttons next to the page heading:

  • Reboot Radio — disconnects and reconnects the bot's radio connection (requires confirmation)
  • Connect / Disconnect — toggles the bot's connection state; button color and label update live

These operations are queued via the database and processed by the bot's scheduler within ~5 seconds.

Configuration

The bot uses config.ini for all settings. The quickest way to create and edit config.ini is:

make config

This launches an interactive ncurses TUI that lets you browse sections, edit values, validate, and save. It can also migrate from config.ini.example if no config.ini exists. In the keys pane, r renames the selected key (useful for changing a scheduled-message time), a adds a new key+value, and d/Delete removes a key.

Key configuration sections:

Connection

[Connection]
connection_type = serial          # serial, ble, or tcp
serial_port = /dev/ttyUSB0        # Serial port path (for serial)
#hostname = 192.168.1.60         # TCP hostname/IP (for TCP)
#tcp_port = 5000                  # TCP port (for TCP)
#ble_device_name = MeshCore       # BLE device name (for BLE)
timeout = 30                      # Connection timeout

Bot Settings

[Bot]
bot_name = MeshCoreBot            # Bot identification name
enabled = true                    # Enable/disable bot
rate_limit_seconds = 2            # Global: min seconds between any bot reply
bot_tx_rate_limit_seconds = 1.0   # Min seconds between bot transmissions
per_user_rate_limit_seconds = 5   # Per-user: min seconds between replies to same user (pubkey or name)
per_user_rate_limit_enabled = true
startup_advert = flood            # Send advert on startup

Keywords

[Keywords]
# Format: keyword = response_template
# Variables: {sender}, {connection_info}, {snr}, {rssi}, {timestamp}, {path},
#            {hops}, {hops_label}, {elapsed}, {path_distance}, {firstlast_distance},
#            {total_contacts}, {total_repeaters}, {total_companions}, ...
test = "Message received from {sender} | {connection_info}"
help = "Bot Help: test, ping, help, hello, cmd, wx, aqi, sun, moon, solar, hfcond, satpass, dice, roll, joke, dadjoke, sports, channels, path, prefix, repeater, stats, alert"

Channels

[Channels]
monitor_channels = general,test,emergency  # Channels to monitor
respond_to_dms = true                      # Enable DM responses
# Optional: limit channel responses to certain keywords (DM gets all triggers)
# channel_keywords = help,ping,test,hello

Per-Channel Rate Limiting

[Rate_Limits]
# Format: channel.<name>_seconds = <float>
# Overrides the global rate_limit_seconds for a specific channel.
channel.general_seconds = 5.0
channel.emergency_seconds = 0.0   # no rate limit on emergency channel

Command Aliases

[Aliases]
# Format: alias = target_command
# Injects the alias string into the target command's keyword list.
w = wx
p = ping

Inbound Webhook

[Webhook]
enabled = false
host = 0.0.0.0
port = 8765
secret_token =            # Bearer token; leave blank to disable auth
max_message_length = 200
# allowed_channels =      # Comma-separated allowlist; blank = all channels

Send a message to a channel:

curl -X POST http://localhost:8765/webhook \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"channel": "general", "message": "Hello from webhook!"}'

Send a DM:

curl -X POST http://localhost:8765/webhook \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"dm_to": "NodeName", "message": "Private message"}'

External Data APIs

[External_Data]
# API keys for external services
n2yo_api_key =                    # Satellite pass data
airnow_api_key =                  # Air quality data

Alert Command

[Alert_Command]
enabled = true                           # Enable/disable alert command
max_incident_age_hours = 24             # Maximum age for incidents (hours)
max_distance_km = 20.0                  # Maximum distance for proximity queries (km)
agency.city.<city_name> = <agency_ids>   # City-specific agency IDs (e.g., agency.city.seattle = 17D20,17M15)
agency.county.<county_name> = <agency_ids> # County-specific agency IDs (aggregates all city agencies)

Logging

[Logging]
log_level = INFO                  # DEBUG, INFO, WARNING, ERROR, CRITICAL
log_file = meshcore_bot.log       # Log file path (empty = console only)
colored_output = true             # Enable colored console output
json_logging = false              # Emit one JSON object per log line (for Loki/Elasticsearch/Splunk)
                                  # When true, colored_output is ignored
log_max_bytes = 5242880           # Max log file size before rotation (bytes; default 5 MB)
log_backup_count = 3              # Number of rotated log files to keep

When json_logging = true each log line is a JSON object:

{"timestamp":"2026-03-14T12:00:00.000Z","level":"INFO","logger":"MeshCoreBot","message":"Connected to radio"}

Maintenance

[Maintenance]
db_backup_enabled = false
db_backup_schedule = daily      # daily | weekly | manual
db_backup_time = 02:00          # HH:MM local time
db_backup_retention_count = 7
db_backup_dir = /data/backups
email_attach_log = false        # attach current log file (≤ 5 MB) to nightly email before rotation

Notifications

[Notifications]
email_enabled = false
smtp_host =
smtp_port = 587
smtp_user =
smtp_password =
smtp_from =
email_recipients =              # comma-separated
email_send_time = 06:00         # nightly digest send time (HH:MM local)

Usage

Running the Bot

.venv/bin/python meshcore_bot.py

Or if installed as a package entry point:

.venv/bin/meshcore-bot

Available Commands

For a comprehensive list of all available commands with examples and detailed explanations, see Command reference.

Quick reference:

  • Basic: test, ping, help, hello, cmd
  • Information: wx, gwx, aqi, sun, moon, solar, solarforecast, hfcond, satpass, channels
  • Emergency: alert
  • Gaming: dice, roll, magic8
  • Entertainment: joke, dadjoke, hacker, catfact
  • Sports: sports
  • MeshCore Utility: path, prefix, stats, multitest, webviewer
  • Management (DM only): repeater, advert, feed, announcements, greeter, schedule

schedule Command (DM only)

The !schedule command (DM only by default) shows all upcoming scheduled messages and the current advert interval:

Scheduled Messages (2 configured):
  06:00 → #general: "Good morning, mesh!"
  18:00 → #general: "Evening check-in"
Advert interval: every 30 min

Message Response Templates

Keyword responses support these template variables:

  • {sender} - Sender's node ID
  • {connection_info} - Connection details (path | SNR | RSSI)
  • {snr} - Signal-to-noise ratio in dB
  • {rssi} - Received signal strength in dBm
  • {timestamp} - Current time (HH:MM:SS in configured timezone)
  • {elapsed} - Time since message was sent
  • {path} - Message routing path
  • {hops} - Hop count (integer or ?)
  • {hops_label} - Hop count with label ("1 hop", "3 hops", "?")
  • {path_distance} - Estimated total path distance in km
  • {firstlast_distance} - First-to-last repeater distance in km
  • {total_contacts}, {total_repeaters}, {total_companions} - Mesh network counts (for scheduled messages)

Adding Newlines

To add newlines in keyword responses, use \n (single backslash + n):

[Keywords]
test = "Line 1\nLine 2\nLine 3"

This will output:

Line 1
Line 2
Line 3

To use a literal backslash + n, use \\n (double backslash + n). Other escape sequences: \t (tab), \r (carriage return), \\ (literal backslash)

Example:

[Keywords]
test = "Message received from {sender} | {connection_info}"
ping = "Pong!"
help = "Bot Help: test, ping, help, hello, cmd, wx, gwx, aqi, sun, moon, solar, solarforecast, hfcond, satpass, dice, roll, joke, dadjoke, sports, channels, path, prefix, repeater, stats, multitest, alert, webviewer"

Hardware Setup

Serial Connection

  1. Flash MeshCore firmware to your device
  2. Connect via USB
  3. Configure serial port in config.ini:
    [Connection]
    connection_type = serial
    serial_port = /dev/ttyUSB0  # Linux
    # serial_port = COM3        # Windows
    # serial_port = /dev/tty.usbserial-*  # macOS
    

BLE Connection

  1. Ensure your MeshCore device supports BLE
  2. Configure BLE in config.ini:
    [Connection]
    connection_type = ble
    ble_device_name = MeshCore
    

TCP Connection

  1. Ensure your MeshCore device has TCP/IP connectivity (e.g., via gateway or bridge)
  2. Configure TCP in config.ini:
    [Connection]
    connection_type = tcp
    hostname = 192.168.1.60  # IP address or hostname
    tcp_port = 5000          # TCP port (default: 5000)
    

Troubleshooting

Common Issues

  1. Serial Port Not Found:

    • Check device connection
    • Verify port name in config
    • List available ports: python -c "import serial.tools.list_ports; print([p.device for p in serial.tools.list_ports.comports()])"
  2. BLE Connection Issues:

    • Ensure device is discoverable
    • Check device name in config
    • Verify BLE permissions
  3. TCP Connection Issues:

    • Verify hostname/IP address is correct
    • Check that TCP port is open and accessible
    • Ensure network connectivity to the device
    • Verify the MeshCore device supports TCP connections
    • Check firewall settings if connection fails
  4. Message Parsing Errors:

    • Enable DEBUG logging for detailed information
    • Check meshcore library documentation for protocol details
  5. Rate Limiting:

    • Global: rate_limit_seconds — minimum time between any two bot replies
    • Per-user: per_user_rate_limit_seconds and per_user_rate_limit_enabled — minimum time between replies to the same user (user identified by public key when available, else sender name)
    • Per-channel: [Rate_Limits] channel.<name>_seconds — override rate limit for a specific channel
    • Bot TX: bot_tx_rate_limit_seconds — minimum time between bot transmissions on the mesh
    • Check logs for rate limiting messages

Debug Mode

Enable debug logging:

[Logging]
log_level = DEBUG

Architecture

The bot uses a modular plugin architecture:

  • Core modules (modules/): Shared utilities and core functionality
  • Command plugins (modules/commands/): Individual command implementations
  • Service plugins (modules/service_plugins/): Background services (Discord/Telegram bridges, webhook, packet capture, etc.)
  • Web viewer (modules/web_viewer/): Flask + SocketIO browser dashboard
  • Plugin loaders: Dynamic discovery and loading of command and service plugins
  • Message handler: Processes incoming messages and routes to appropriate handlers
  • Scheduler: Background thread for timed tasks; dispatches async ops into the bot's event loop
  • Database migrations: modules/db_migrations.py — versioned MigrationRunner applied once on startup

Adding New Plugins

Command Plugin:

  1. Create a new file in modules/commands/
  2. Inherit from BaseCommand
  3. Implement the execute() method
  4. The plugin loader will automatically discover and load it
from .base_command import BaseCommand
from ..models import MeshMessage

class MyCommand(BaseCommand):
    name = "mycommand"
    keywords = ['mycommand']
    description = "My custom command"

    async def execute(self, message: MeshMessage) -> bool:
        await self.send_response(message, "Hello from my command!")
        return True

Service Plugin:

  1. Create a new file in modules/service_plugins/
  2. Inherit from BaseServicePlugin, set config_section = 'My_Section'
  3. Implement async start() and async stop() methods
  4. Add [My_Section] enabled = true to config.ini.example

Database Migration:

  1. Write _mNNNN_short_desc(cursor) in modules/db_migrations.py
  2. Append (NNNN, "description", _mNNNN_...) to MIGRATIONS
  3. Never modify or remove existing migrations — add a new one instead

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request against the dev branch

License

This project is licensed under the MIT License.

Acknowledgments

  • MeshCore Project for the mesh networking protocol
  • Some commands adapted from MeshingAround bot by K7MHI Kelly Keeton 2024
  • Packet capture service based on meshcore-packet-capture by agessaman
  • meshcore-decoder by Michael Hart for client-side packet decoding and decryption in the web viewer
Description
No description provided
Readme 17 MiB
Languages
Python 76.1%
HTML 10.9%
JavaScript 10.7%
Shell 1.8%
Nix 0.5%