Files
meshcore-bot/README.md
T

678 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 =` key in each command's 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](docs/discord-bridge.md))
- **Telegram Bridge**: One-way bridge to post mesh messages to Telegram ([docs](docs/telegram-bridge.md))
- **Packet Capture**: Capture and publish packets to MQTT brokers ([docs](docs/packet-capture.md))
- **Map Uploader**: Upload node adverts to map.meshcore.dev ([docs](docs/map-uploader.md))
- **Weather Service**: Scheduled forecasts, alerts, and lightning detection ([docs](docs/weather-service.md))
- **Webhook Service**: Accept inbound HTTP POST payloads and relay to channels or DMs
## Requirements
- Python 3.10+
- MeshCore-compatible device (Heltec V3, RAK Wireless, etc.)
- USB cable or BLE capability
## Installation
### Quick Start (Development)
1. Clone the repository:
```bash
git clone <repository-url>
cd meshcore-bot
```
2. Create a virtual environment and install dependencies via Makefile:
```bash
make dev # creates .venv, installs all deps including test tools
```
Or for production dependencies only:
```bash
make install # creates .venv, installs runtime + optional deps
```
3. 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`:
```bash
make config
```
**Manual option (full config):** Enables all bot commands and provides all configuration options:
```bash
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):
```bash
cp config.ini.minimal-example config.ini
# Edit config.ini with your connection and bot settings
```
4. Run the bot:
```bash
.venv/bin/python meshcore_bot.py
```
5. Run tests and linting:
```bash
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:
```bash
sudo ./install-service.sh
```
2. Configure the bot:
```bash
sudo nano /opt/meshcore-bot/config.ini
```
3. Start the service:
```bash
sudo systemctl start meshcore-bot
```
4. Check status:
```bash
sudo systemctl status meshcore-bot
```
See [Service installation](docs/service-installation.md) for detailed service installation instructions.
### Debian Package (.deb)
Build and install a `.deb` package for Debian/Ubuntu systems:
```bash
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**:
```bash
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:
```ini
[Bot]
db_path = /data/databases/meshcore_bot.db
[Logging]
log_file = /data/logs/meshcore_bot.log
```
3. **Build and start with Docker Compose**:
```bash
docker compose up -d --build
```
4. **View logs**:
```bash
docker compose logs -f
```
See [Docker deployment](docs/docker.md) for detailed Docker deployment instructions, including serial port access, web viewer configuration, and troubleshooting.
## NixOS
Use the Nix flake via flake.nix
```nix
meshcore-bot.url = "github:agessaman/meshcore-bot/";
```
And in your system config
```nix
{
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`:
```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`:
```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:
```ini
[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:
```bash
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
```ini
[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
```ini
[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 = 30 # 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
radio_probe_interval_seconds = 300 # probe interval in seconds (300900 / 515 min)
radio_probe_fail_threshold = 3 # consecutive failures before zombie is declared and logged
send_timeout_seconds = 30 # max seconds to wait for a channel message send
radio_zombie_alert_enabled = false # send immediate alert email on zombie detection (default: log only)
radio_zombie_alert_email = # alert recipient(s); falls back to nightly email if blank
radio_offline_threshold = 3 # consecutive send timeouts before radio-offline state is entered
radio_offline_alert_enabled = true # send alert email when radio-offline state is entered
radio_offline_alert_email = # alert recipient(s); falls back to nightly email if blank
```
### Keywords
```ini
[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
```ini
[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
```ini
[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
Add an `aliases =` key to any command's config section. The value is a
comma-separated list of extra keywords that trigger the same command.
```ini
[Ping_Command]
aliases = p,ping-test
[WX_Command]
aliases = w,weather
```
### Inbound Webhook
```ini
[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:
```bash
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:
```bash
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
```ini
[External_Data]
# API keys for external services
n2yo_api_key = # Satellite pass data
airnow_api_key = # Air quality data
```
### Alert Command
```ini
[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
```ini
[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:
```json
{"timestamp":"2026-03-14T12:00:00.000Z","level":"INFO","logger":"MeshCoreBot","message":"Connected to radio"}
```
### Maintenance
```ini
[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
```ini
[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
```bash
.venv/bin/python meshcore_bot.py
```
Or if installed as a package entry point:
```bash
.venv/bin/meshcore-bot
```
### Available Commands
For a comprehensive list of all available commands with examples and detailed explanations, see [Command reference](docs/command-reference.md).
Quick reference:
- **Basic:** `test`, `ping`, `version`, `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):
```ini
[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:
```ini
[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`:
```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`:
```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`:
```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:
```ini
[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
```python
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](https://github.com/meshcore-dev/MeshCore) for the mesh networking protocol
- Some commands adapted from MeshingAround bot by K7MHI Kelly Keeton 2024
- Packet capture service based on [meshcore-packet-capture](https://github.com/agessaman/meshcore-packet-capture) by agessaman
- [meshcore-decoder](https://github.com/michaelhart/meshcore-decoder) by Michael Hart for client-side packet decoding and decryption in the web viewer