mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-06-03 22:31:18 +00:00
refactor(core): extract admin server and default config from core.py
Moves _BotAdminServer to modules/admin_server.py (renamed BotAdminServer) and the create_default_config method + 360-line template string to modules/default_config.py. core.py shrinks from 2,289 to ~1,870 lines; all 44 core tests and 2866 total tests still pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal Flask HTTP admin server for the MeshCore Bot.
|
||||
|
||||
Runs in a daemon thread alongside the bot's asyncio loop.
|
||||
Configured via the ``[Admin]`` section in config.ini:
|
||||
|
||||
[Admin]
|
||||
enabled = true
|
||||
port = 5001
|
||||
token = <secret> ; required; requests without matching Bearer token are rejected
|
||||
"""
|
||||
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .core import MeshCoreBot
|
||||
|
||||
|
||||
class BotAdminServer(threading.Thread):
|
||||
"""Minimal Flask HTTP server exposing bot admin endpoints."""
|
||||
|
||||
def __init__(self, bot: "MeshCoreBot", port: int, token: str) -> None:
|
||||
super().__init__(daemon=True, name="BotAdminServer")
|
||||
self._bot = bot
|
||||
self._port = port
|
||||
self._token = token
|
||||
|
||||
def run(self) -> None:
|
||||
try:
|
||||
from flask import Flask, Response, jsonify
|
||||
from flask import request as flask_request
|
||||
|
||||
app = Flask("bot_admin")
|
||||
# Suppress Flask startup banner and request logs
|
||||
import logging as _logging
|
||||
_logging.getLogger("werkzeug").setLevel(_logging.ERROR)
|
||||
|
||||
def _check_auth() -> "Response | None":
|
||||
auth = flask_request.headers.get("Authorization", "")
|
||||
if not auth.startswith("Bearer ") or auth[7:] != self._token:
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
return None
|
||||
|
||||
@app.post("/api/admin/reload")
|
||||
def reload_config(): # type: ignore[no-untyped-def]
|
||||
denied = _check_auth()
|
||||
if denied is not None:
|
||||
return denied
|
||||
success, msg = self._bot.reload_config()
|
||||
status = 200 if success else 409
|
||||
return jsonify({"success": success, "message": msg}), status
|
||||
|
||||
@app.get("/api/admin/health")
|
||||
def health(): # type: ignore[no-untyped-def]
|
||||
denied = _check_auth()
|
||||
if denied is not None:
|
||||
return denied
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
app.run(host="127.0.0.1", port=self._port, threaded=True)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._bot.logger.error("BotAdminServer failed to start: %s", exc)
|
||||
+5
-424
@@ -25,9 +25,11 @@ import colorlog
|
||||
import meshcore
|
||||
from meshcore import EventType
|
||||
|
||||
from .admin_server import BotAdminServer
|
||||
from .channel_manager import ChannelManager
|
||||
from .command_manager import CommandManager
|
||||
from shared.db_manager import AsyncDBManager, DBManager
|
||||
from .default_config import create_default_config
|
||||
from .feed_manager import FeedManager
|
||||
from .i18n import Translator
|
||||
from .message_handler import MessageHandler
|
||||
@@ -62,61 +64,6 @@ class _JsonFormatter(logging.Formatter):
|
||||
return json.dumps(obj, ensure_ascii=False)
|
||||
|
||||
|
||||
class _BotAdminServer(threading.Thread):
|
||||
"""Minimal Flask HTTP server exposing bot admin endpoints.
|
||||
|
||||
Runs in a daemon thread alongside the bot's asyncio loop.
|
||||
Configured via ``[Admin]`` section in config.ini:
|
||||
|
||||
[Admin]
|
||||
enabled = true
|
||||
port = 5001
|
||||
token = <secret> ; required; requests without matching Bearer token are rejected
|
||||
"""
|
||||
|
||||
def __init__(self, bot: "MeshCoreBot", port: int, token: str) -> None:
|
||||
super().__init__(daemon=True, name="BotAdminServer")
|
||||
self._bot = bot
|
||||
self._port = port
|
||||
self._token = token
|
||||
|
||||
def run(self) -> None:
|
||||
try:
|
||||
from flask import Flask, Response, jsonify
|
||||
from flask import request as flask_request
|
||||
|
||||
app = Flask("bot_admin")
|
||||
# Suppress Flask startup banner and request logs
|
||||
import logging as _logging
|
||||
_logging.getLogger("werkzeug").setLevel(_logging.ERROR)
|
||||
|
||||
def _check_auth() -> "Response | None":
|
||||
auth = flask_request.headers.get("Authorization", "")
|
||||
if not auth.startswith("Bearer ") or auth[7:] != self._token:
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
return None
|
||||
|
||||
@app.post("/api/admin/reload")
|
||||
def reload_config(): # type: ignore[no-untyped-def]
|
||||
denied = _check_auth()
|
||||
if denied is not None:
|
||||
return denied
|
||||
success, msg = self._bot.reload_config()
|
||||
status = 200 if success else 409
|
||||
return jsonify({"success": success, "message": msg}), status
|
||||
|
||||
@app.get("/api/admin/health")
|
||||
def health(): # type: ignore[no-untyped-def]
|
||||
denied = _check_auth()
|
||||
if denied is not None:
|
||||
return denied
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
app.run(host="127.0.0.1", port=self._port, threaded=True)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._bot.logger.error("BotAdminServer failed to start: %s", exc)
|
||||
|
||||
|
||||
class MeshCoreBot:
|
||||
"""MeshCore Bot using official meshcore package.
|
||||
|
||||
@@ -195,12 +142,12 @@ class MeshCoreBot:
|
||||
self.web_viewer_integration = None
|
||||
|
||||
# Admin HTTP server (optional — [Admin] section)
|
||||
self._admin_server: _BotAdminServer | None = None
|
||||
self._admin_server: BotAdminServer | None = None
|
||||
if self.config.getboolean('Admin', 'enabled', fallback=False):
|
||||
admin_port = self.config.getint('Admin', 'port', fallback=5001)
|
||||
admin_token = self.config.get('Admin', 'token', fallback='')
|
||||
if admin_token:
|
||||
self._admin_server = _BotAdminServer(self, admin_port, admin_token)
|
||||
self._admin_server = BotAdminServer(self, admin_port, admin_token)
|
||||
else:
|
||||
self.logger.warning("Admin server enabled but no token configured — skipping")
|
||||
|
||||
@@ -445,7 +392,7 @@ class MeshCoreBot:
|
||||
does not exist, a default configuration is created first.
|
||||
"""
|
||||
if not Path(self.config_file).exists():
|
||||
self.create_default_config()
|
||||
create_default_config(self.config_file)
|
||||
|
||||
# Force UTF-8 so emoji and non-ASCII characters in config.ini parse on Windows.
|
||||
self.config.read(self.config_file, encoding="utf-8")
|
||||
@@ -626,372 +573,6 @@ class MeshCoreBot:
|
||||
self.logger.error(traceback.format_exc())
|
||||
return (False, error_msg)
|
||||
|
||||
def create_default_config(self) -> None:
|
||||
"""Create default configuration file.
|
||||
|
||||
Writes a default 'config.ini' file to disk with standard settings
|
||||
and comments explaining each option.
|
||||
"""
|
||||
default_config = """[Connection]
|
||||
# Connection type: serial, ble, or tcp
|
||||
# serial: Connect via USB serial port
|
||||
# ble: Connect via Bluetooth Low Energy
|
||||
# tcp: Connect via TCP/IP
|
||||
connection_type = serial
|
||||
|
||||
# Serial port (for serial connection)
|
||||
# Common ports: /dev/ttyUSB0, /dev/tty.usbserial-*, COM3 (Windows)
|
||||
serial_port = /dev/ttyUSB0
|
||||
|
||||
# BLE device name (for BLE connection)
|
||||
# Leave commented out for auto-detection, or specify exact device name
|
||||
#ble_device_name = MeshCore
|
||||
|
||||
# TCP hostname or IP address (for TCP connection)
|
||||
#hostname = 192.168.1.60
|
||||
# TCP port (for TCP connection)
|
||||
#tcp_port = 5000
|
||||
|
||||
# Connection timeout in seconds
|
||||
timeout = 30
|
||||
|
||||
[Bot]
|
||||
# Bot name for identification and logging
|
||||
bot_name = MeshCoreBot
|
||||
|
||||
# RF Data Correlation Settings
|
||||
# Time window for correlating RF data with messages (seconds)
|
||||
rf_data_timeout = 15.0
|
||||
|
||||
# Time to wait for RF data correlation (seconds)
|
||||
message_correlation_timeout = 10.0
|
||||
|
||||
# Enable enhanced correlation strategies
|
||||
enable_enhanced_correlation = true
|
||||
|
||||
# Bot node ID (leave empty for auto-assignment)
|
||||
node_id =
|
||||
|
||||
# Enable/disable bot responses
|
||||
# true: Bot will respond to keywords and commands
|
||||
# false: Bot will only listen and log messages
|
||||
enabled = true
|
||||
|
||||
# Passive mode (only listen, don't respond)
|
||||
# true: Bot will not send any messages
|
||||
# false: Bot will respond normally
|
||||
passive_mode = false
|
||||
|
||||
# Rate limiting in seconds between messages
|
||||
# Prevents spam by limiting how often the bot can send messages
|
||||
rate_limit_seconds = 2
|
||||
|
||||
# Bot transmission rate limit in seconds between bot messages
|
||||
# Prevents bot from overwhelming the mesh network
|
||||
bot_tx_rate_limit_seconds = 1.0
|
||||
|
||||
# Transmission delay in milliseconds before sending messages
|
||||
# Helps prevent message collisions on the mesh network
|
||||
# Recommended: 100-500ms for busy networks, 0 for quiet networks
|
||||
tx_delay_ms = 250
|
||||
|
||||
# DM retry settings for improved reliability (meshcore-2.1.6+)
|
||||
# Maximum number of retry attempts for failed DM sends
|
||||
dm_max_retries = 3
|
||||
|
||||
# Maximum flood attempts (when path reset is needed)
|
||||
dm_max_flood_attempts = 2
|
||||
|
||||
# Number of attempts before switching to flood mode
|
||||
dm_flood_after = 2
|
||||
|
||||
# Timezone for bot operations
|
||||
# Use standard timezone names (e.g., "America/New_York", "Europe/London", "UTC")
|
||||
# Leave empty to use system timezone
|
||||
timezone =
|
||||
|
||||
# Bot location for geographic proximity calculations and astronomical data
|
||||
# Default latitude for bot location (decimal degrees)
|
||||
# Example: 40.7128 for New York City, 48.50 for Victoria BC
|
||||
bot_latitude = 40.7128
|
||||
|
||||
# Default longitude for bot location (decimal degrees)
|
||||
# Example: -74.0060 for New York City, -123.00 for Victoria BC
|
||||
bot_longitude = -74.0060
|
||||
|
||||
# Interval-based advertising settings
|
||||
# Send periodic flood adverts at specified intervals
|
||||
# 0: Disabled (default)
|
||||
# >0: Send flood advert every N hours
|
||||
advert_interval_hours = 0
|
||||
|
||||
# Send startup advert when bot finishes initializing
|
||||
# false: No startup advert (default)
|
||||
# zero-hop: Send local broadcast advert
|
||||
# flood: Send network-wide flood advert
|
||||
startup_advert = false
|
||||
|
||||
# Auto-manage contact list when new contacts are discovered
|
||||
# device: Device handles auto-addition using standard auto-discovery mode, bot manages contact list capacity (purge old contacts when near limits)
|
||||
# bot: Bot automatically adds new companion contacts to device, bot manages contact list capacity (purge old contacts when near limits)
|
||||
# false: Manual mode - no automatic actions, use !repeater commands to manage contacts (default)
|
||||
auto_manage_contacts = false
|
||||
|
||||
[Admin_ACL]
|
||||
# Admin Access Control List (ACL) for restricted commands
|
||||
# Only users with public keys listed here can execute admin commands
|
||||
# Format: comma-separated list of public keys (without spaces)
|
||||
# Example: f5d2b56d19b24412756933e917d4632e088cdd5daeadc9002feca73bf5d2b56d,another_key_here
|
||||
admin_pubkeys =
|
||||
|
||||
# Commands that require admin access (comma-separated)
|
||||
# These commands will only work for users in the admin_pubkeys list
|
||||
admin_commands = repeater
|
||||
|
||||
[Keywords]
|
||||
# Keyword-response pairs (keyword = response format)
|
||||
# Available fields: {sender}, {connection_info}, {snr}, {rssi}, {timestamp}, {path}, {path_distance}, {firstlast_distance}
|
||||
# {sender}: Name/ID of message sender
|
||||
# {connection_info}: Path info, SNR, and RSSI combined (e.g., "01,5f (2 hops) | SNR: 15 dB | RSSI: -120 dBm")
|
||||
# {snr}: Signal-to-noise ratio in dB
|
||||
# {rssi}: Received signal strength indicator in dBm
|
||||
# {timestamp}: Message timestamp in HH:MM:SS format
|
||||
# {path}: Message routing path (e.g., "01,5f (2 hops)")
|
||||
# {hops}: Total hop count only (e.g., "2" or "0"); same value as in path/connection_info
|
||||
# {hops_label}: Same as hops with "hop"/"hops" and pluralization (e.g., "1 hop", "2 hops")
|
||||
# {path_distance}: Total distance between all hops in path with locations (e.g., "123.4km (3 segs, 1 no-loc)")
|
||||
# {firstlast_distance}: Distance between first and last repeater in path (e.g., "45.6km" or empty if locations missing)
|
||||
test = "ack [@{sender}]{phrase_part} | {connection_info} | Received at: {timestamp}"
|
||||
ping = "Pong!"
|
||||
pong = "Ping!"
|
||||
help = "Bot Help: test, ping, help, hello, cmd, advert, t phrase, @string, wx, aqi, sun, moon, solar, hfcond, satpass | Use 'help <command>' for details"
|
||||
cmd = "Available commands: test, ping, help, hello, cmd, advert, t phrase, @string, wx, aqi, sun, moon, solar, hfcond, satpass"
|
||||
|
||||
[Channels]
|
||||
# Channels to monitor (comma-separated)
|
||||
# Bot will only respond to messages on these channels
|
||||
# Use exact channel names as configured on your MeshCore node
|
||||
monitor_channels = general,test,emergency
|
||||
|
||||
# Enable DM responses
|
||||
# true: Bot will respond to direct messages
|
||||
# false: Bot will ignore direct messages
|
||||
respond_to_dms = true
|
||||
|
||||
[Banned_Users]
|
||||
# List of banned sender names (comma-separated). Matching is prefix (starts-with):
|
||||
# "Awful Username" also matches "Awful Username 🍆". No bot responses in channels or DMs.
|
||||
banned_users =
|
||||
|
||||
[Feed_Manager]
|
||||
# Enable or disable RSS/API feed subscriptions
|
||||
# true: Feed manager polls configured feeds and sends updates to channels
|
||||
# false: Feed manager disabled (default)
|
||||
feed_manager_enabled = false
|
||||
|
||||
[Scheduled_Messages]
|
||||
# Scheduled message format: HHMM = channel:message
|
||||
# Time format: HHMM (24-hour, no colon)
|
||||
# Bot will send these messages at the specified times daily
|
||||
0800 = general:Good morning! Bot is online and ready.
|
||||
1200 = general:Midday status check - all systems operational.
|
||||
1800 = general:Evening update - bot status: Good
|
||||
|
||||
[Logging]
|
||||
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
# DEBUG: Most verbose, shows all details
|
||||
# INFO: Standard logging level
|
||||
# WARNING: Only warnings and errors
|
||||
# ERROR: Only errors
|
||||
# CRITICAL: Only critical errors
|
||||
log_level = INFO
|
||||
|
||||
# Log file path (leave empty for console only)
|
||||
# Bot will write logs to this file in addition to console
|
||||
# Use absolute path for Docker compatibility (e.g., /data/logs/meshcore_bot.log)
|
||||
# Relative paths will resolve relative to the config file directory
|
||||
log_file = meshcore_bot.log
|
||||
|
||||
# Enable colored console output
|
||||
# true: Use colors in console output
|
||||
# false: Plain text output
|
||||
colored_output = true
|
||||
|
||||
# MeshCore library log level (separate from bot log level)
|
||||
# Controls debug output from the meshcore library itself
|
||||
# Options: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
meshcore_log_level = INFO
|
||||
|
||||
[Custom_Syntax]
|
||||
# Custom syntax patterns for special message formats
|
||||
# Format: pattern = "response_format"
|
||||
# Available fields: {sender}, {phrase}, {connection_info}, {snr}, {timestamp}, {path}
|
||||
# {phrase}: The text after the trigger (for t_phrase syntax)
|
||||
#
|
||||
# Special syntax: Messages starting with "t " or "T " followed by a phrase
|
||||
# Example: "t hello world" -> "ack {sender}: hello world | {connection_info}"
|
||||
t_phrase = "ack {sender}: {phrase} | {connection_info}"
|
||||
|
||||
|
||||
[External_Data]
|
||||
# Weather API key (future feature)
|
||||
weather_api_key =
|
||||
|
||||
# Weather update interval in seconds (future feature)
|
||||
weather_update_interval = 3600
|
||||
|
||||
# Tide API key (future feature)
|
||||
tide_api_key =
|
||||
|
||||
# Tide update interval in seconds (future feature)
|
||||
tide_update_interval = 1800
|
||||
|
||||
# N2YO API key for satellite pass information
|
||||
# Get free key at: https://www.n2yo.com/login/
|
||||
n2yo_api_key =
|
||||
|
||||
# AirNow API key for AQI data
|
||||
# Get free key at: https://docs.airnowapi.org/
|
||||
airnow_api_key =
|
||||
|
||||
# Repeater prefix API URL for prefix command
|
||||
# Leave empty to disable prefix command functionality
|
||||
# Configure your own regional API endpoint
|
||||
repeater_prefix_api_url =
|
||||
|
||||
# Repeater prefix cache duration in hours
|
||||
# How long to cache prefix data before refreshing from API
|
||||
# Recommended: 1-6 hours (data doesn't change frequently)
|
||||
repeater_prefix_cache_hours = 1
|
||||
|
||||
[Prefix_Command]
|
||||
# Enable or disable repeater geolocation in prefix command
|
||||
# true: Show city names with repeaters when location data is available
|
||||
# false: Show only repeater names without location information
|
||||
show_repeater_locations = true
|
||||
|
||||
# Use reverse geocoding for coordinates without city names
|
||||
# true: Automatically look up city names from GPS coordinates
|
||||
# false: Only show coordinates if no city name is available
|
||||
use_reverse_geocoding = true
|
||||
|
||||
# Hide prefix source information
|
||||
# true: Hide "Source: domain.com" line from prefix command output
|
||||
# false: Show source information (default)
|
||||
hide_source = false
|
||||
|
||||
# Prefix heard time window (days)
|
||||
# Number of days to look back when showing prefix results (default command behavior)
|
||||
# Only repeaters heard within this window will be shown by default
|
||||
# Use "prefix XX all" to show all repeaters regardless of time
|
||||
prefix_heard_days = 7
|
||||
|
||||
# Prefix free time window (days)
|
||||
# Number of days to look back when determining which prefixes are "free"
|
||||
# Only repeaters heard within this window will be considered as using a prefix
|
||||
# Repeaters not heard in this window will be excluded from used prefixes list
|
||||
prefix_free_days = 30
|
||||
|
||||
[Weather]
|
||||
# Default state for city name disambiguation
|
||||
# When users type "wx seattle", it will search for "seattle, WA, USA"
|
||||
# Use 2-letter state abbreviation (e.g., WA, CA, NY, TX)
|
||||
default_state = WA
|
||||
|
||||
# Default country for city name disambiguation (for international weather plugin)
|
||||
# Use 2-letter country code (e.g., US, CA, GB, AU)
|
||||
default_country = US
|
||||
|
||||
# Temperature unit for weather display
|
||||
# Options: fahrenheit, celsius
|
||||
# Default: fahrenheit
|
||||
temperature_unit = fahrenheit
|
||||
|
||||
# Wind speed unit for weather display
|
||||
# Options: mph, kmh, ms (meters per second)
|
||||
# Default: mph
|
||||
wind_speed_unit = mph
|
||||
|
||||
# Precipitation unit for weather display
|
||||
# Options: inch, mm
|
||||
# Default: inch
|
||||
precipitation_unit = inch
|
||||
|
||||
[Path_Command]
|
||||
# Optional prefix on path command replies: {sender}, {connection_info}, {path}, {timestamp}, {snr}, {rssi}
|
||||
# reply_prefix =
|
||||
# Bytes per hop before repeater name lookup (0/1 = always; 2/3 = gate to hex + tip if shorter)
|
||||
# minimum_path_bytes = 0
|
||||
# Geographic proximity calculation method
|
||||
# simple: Use proximity to bot location (default)
|
||||
# path: Use proximity to previous/next nodes in the path for more realistic routing
|
||||
proximity_method = simple
|
||||
|
||||
# Enable path proximity fallback
|
||||
# When path proximity can't be calculated (missing location data), fall back to simple proximity
|
||||
# true: Fall back to bot location proximity when path data unavailable
|
||||
# false: Show collision warning when path proximity unavailable
|
||||
path_proximity_fallback = true
|
||||
|
||||
# Maximum range for geographic proximity guessing (kilometers)
|
||||
# Repeaters beyond this distance will have reduced confidence or be rejected
|
||||
# Set to 0 to disable range limiting
|
||||
max_proximity_range = 200
|
||||
|
||||
# Maximum age for repeater data in path matching (days)
|
||||
# Only include repeaters that have been heard within this many days
|
||||
# Helps filter out stale or inactive repeaters from path decoding
|
||||
# Set to 0 to disable age filtering
|
||||
max_repeater_age_days = 14
|
||||
|
||||
# Confidence indicator symbols for path command
|
||||
# High confidence (>= 0.9): Shows when path decoding is very reliable
|
||||
high_confidence_symbol = 🎯
|
||||
|
||||
# Medium confidence (>= 0.8): Shows when path decoding is reasonably reliable
|
||||
medium_confidence_symbol = 📍
|
||||
|
||||
# Low confidence (< 0.8): Shows when path decoding has uncertainty
|
||||
low_confidence_symbol = ❓
|
||||
|
||||
[Solar_Config]
|
||||
# URL timeout for external API calls (seconds)
|
||||
url_timeout = 10
|
||||
|
||||
# Use Zulu/UTC time for astronomical data
|
||||
# true: Use 24-hour UTC format
|
||||
# false: Use 12-hour local format
|
||||
use_zulu_time = false
|
||||
|
||||
[Joke_Command]
|
||||
# Enable or disable the joke command (true/false)
|
||||
enabled = true
|
||||
|
||||
# Enable seasonal joke defaults (October: spooky, December: Christmas)
|
||||
# true: Seasonal defaults are applied (default)
|
||||
# false: No seasonal defaults (always random)
|
||||
seasonal_jokes = true
|
||||
|
||||
# Handle long jokes (over 130 characters)
|
||||
# false: Fetch new jokes until we get a short one (default)
|
||||
# true: Split long jokes into multiple messages
|
||||
long_jokes = false
|
||||
|
||||
[DadJoke_Command]
|
||||
# Enable or disable the dad joke command (true/false)
|
||||
enabled = true
|
||||
|
||||
# Handle long jokes (over 130 characters)
|
||||
# false: Fetch new jokes until we get a short one (default)
|
||||
# true: Split long jokes into multiple messages
|
||||
long_jokes = false
|
||||
|
||||
"""
|
||||
with open(self.config_file, 'w') as f:
|
||||
f.write(default_config)
|
||||
# Note: Using print here since logger may not be initialized yet
|
||||
print(f"Created default config file: {self.config_file}")
|
||||
|
||||
def setup_logging(self) -> None:
|
||||
"""Setup logging configuration.
|
||||
|
||||
|
||||
@@ -0,0 +1,372 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Default configuration template for MeshCore Bot.
|
||||
|
||||
Call ``create_default_config(path)`` to write config.ini when none exists.
|
||||
"""
|
||||
|
||||
DEFAULT_CONFIG = """[Connection]
|
||||
# Connection type: serial, ble, or tcp
|
||||
# serial: Connect via USB serial port
|
||||
# ble: Connect via Bluetooth Low Energy
|
||||
# tcp: Connect via TCP/IP
|
||||
connection_type = serial
|
||||
|
||||
# Serial port (for serial connection)
|
||||
# Common ports: /dev/ttyUSB0, /dev/tty.usbserial-*, COM3 (Windows)
|
||||
serial_port = /dev/ttyUSB0
|
||||
|
||||
# BLE device name (for BLE connection)
|
||||
# Leave commented out for auto-detection, or specify exact device name
|
||||
#ble_device_name = MeshCore
|
||||
|
||||
# TCP hostname or IP address (for TCP connection)
|
||||
#hostname = 192.168.1.60
|
||||
# TCP port (for TCP connection)
|
||||
#tcp_port = 5000
|
||||
|
||||
# Connection timeout in seconds
|
||||
timeout = 30
|
||||
|
||||
[Bot]
|
||||
# Bot name for identification and logging
|
||||
bot_name = MeshCoreBot
|
||||
|
||||
# RF Data Correlation Settings
|
||||
# Time window for correlating RF data with messages (seconds)
|
||||
rf_data_timeout = 15.0
|
||||
|
||||
# Time to wait for RF data correlation (seconds)
|
||||
message_correlation_timeout = 10.0
|
||||
|
||||
# Enable enhanced correlation strategies
|
||||
enable_enhanced_correlation = true
|
||||
|
||||
# Bot node ID (leave empty for auto-assignment)
|
||||
node_id =
|
||||
|
||||
# Enable/disable bot responses
|
||||
# true: Bot will respond to keywords and commands
|
||||
# false: Bot will only listen and log messages
|
||||
enabled = true
|
||||
|
||||
# Passive mode (only listen, don't respond)
|
||||
# true: Bot will not send any messages
|
||||
# false: Bot will respond normally
|
||||
passive_mode = false
|
||||
|
||||
# Rate limiting in seconds between messages
|
||||
# Prevents spam by limiting how often the bot can send messages
|
||||
rate_limit_seconds = 2
|
||||
|
||||
# Bot transmission rate limit in seconds between bot messages
|
||||
# Prevents bot from overwhelming the mesh network
|
||||
bot_tx_rate_limit_seconds = 1.0
|
||||
|
||||
# Transmission delay in milliseconds before sending messages
|
||||
# Helps prevent message collisions on the mesh network
|
||||
# Recommended: 100-500ms for busy networks, 0 for quiet networks
|
||||
tx_delay_ms = 250
|
||||
|
||||
# DM retry settings for improved reliability (meshcore-2.1.6+)
|
||||
# Maximum number of retry attempts for failed DM sends
|
||||
dm_max_retries = 3
|
||||
|
||||
# Maximum flood attempts (when path reset is needed)
|
||||
dm_max_flood_attempts = 2
|
||||
|
||||
# Number of attempts before switching to flood mode
|
||||
dm_flood_after = 2
|
||||
|
||||
# Timezone for bot operations
|
||||
# Use standard timezone names (e.g., "America/New_York", "Europe/London", "UTC")
|
||||
# Leave empty to use system timezone
|
||||
timezone =
|
||||
|
||||
# Bot location for geographic proximity calculations and astronomical data
|
||||
# Default latitude for bot location (decimal degrees)
|
||||
# Example: 40.7128 for New York City, 48.50 for Victoria BC
|
||||
bot_latitude = 40.7128
|
||||
|
||||
# Default longitude for bot location (decimal degrees)
|
||||
# Example: -74.0060 for New York City, -123.00 for Victoria BC
|
||||
bot_longitude = -74.0060
|
||||
|
||||
# Interval-based advertising settings
|
||||
# Send periodic flood adverts at specified intervals
|
||||
# 0: Disabled (default)
|
||||
# >0: Send flood advert every N hours
|
||||
advert_interval_hours = 0
|
||||
|
||||
# Send startup advert when bot finishes initializing
|
||||
# false: No startup advert (default)
|
||||
# zero-hop: Send local broadcast advert
|
||||
# flood: Send network-wide flood advert
|
||||
startup_advert = false
|
||||
|
||||
# Auto-manage contact list when new contacts are discovered
|
||||
# device: Device handles auto-addition using standard auto-discovery mode, bot manages contact list capacity (purge old contacts when near limits)
|
||||
# bot: Bot automatically adds new companion contacts to device, bot manages contact list capacity (purge old contacts when near limits)
|
||||
# false: Manual mode - no automatic actions, use !repeater commands to manage contacts (default)
|
||||
auto_manage_contacts = false
|
||||
|
||||
[Admin_ACL]
|
||||
# Admin Access Control List (ACL) for restricted commands
|
||||
# Only users with public keys listed here can execute admin commands
|
||||
# Format: comma-separated list of public keys (without spaces)
|
||||
# Example: f5d2b56d19b24412756933e917d4632e088cdd5daeadc9002feca73bf5d2b56d,another_key_here
|
||||
admin_pubkeys =
|
||||
|
||||
# Commands that require admin access (comma-separated)
|
||||
# These commands will only work for users in the admin_pubkeys list
|
||||
admin_commands = repeater
|
||||
|
||||
[Keywords]
|
||||
# Keyword-response pairs (keyword = response format)
|
||||
# Available fields: {sender}, {connection_info}, {snr}, {rssi}, {timestamp}, {path}, {path_distance}, {firstlast_distance}
|
||||
# {sender}: Name/ID of message sender
|
||||
# {connection_info}: Path info, SNR, and RSSI combined (e.g., "01,5f (2 hops) | SNR: 15 dB | RSSI: -120 dBm")
|
||||
# {snr}: Signal-to-noise ratio in dB
|
||||
# {rssi}: Received signal strength indicator in dBm
|
||||
# {timestamp}: Message timestamp in HH:MM:SS format
|
||||
# {path}: Message routing path (e.g., "01,5f (2 hops)")
|
||||
# {hops}: Total hop count only (e.g., "2" or "0"); same value as in path/connection_info
|
||||
# {hops_label}: Same as hops with "hop"/"hops" and pluralization (e.g., "1 hop", "2 hops")
|
||||
# {path_distance}: Total distance between all hops in path with locations (e.g., "123.4km (3 segs, 1 no-loc)")
|
||||
# {firstlast_distance}: Distance between first and last repeater in path (e.g., "45.6km" or empty if locations missing)
|
||||
test = "ack [@{sender}]{phrase_part} | {connection_info} | Received at: {timestamp}"
|
||||
ping = "Pong!"
|
||||
pong = "Ping!"
|
||||
help = "Bot Help: test, ping, help, hello, cmd, advert, t phrase, @string, wx, aqi, sun, moon, solar, hfcond, satpass | Use 'help <command>' for details"
|
||||
cmd = "Available commands: test, ping, help, hello, cmd, advert, t phrase, @string, wx, aqi, sun, moon, solar, hfcond, satpass"
|
||||
|
||||
[Channels]
|
||||
# Channels to monitor (comma-separated)
|
||||
# Bot will only respond to messages on these channels
|
||||
# Use exact channel names as configured on your MeshCore node
|
||||
monitor_channels = general,test,emergency
|
||||
|
||||
# Enable DM responses
|
||||
# true: Bot will respond to direct messages
|
||||
# false: Bot will ignore direct messages
|
||||
respond_to_dms = true
|
||||
|
||||
[Banned_Users]
|
||||
# List of banned sender names (comma-separated). Matching is prefix (starts-with):
|
||||
# "Awful Username" also matches "Awful Username 🍆". No bot responses in channels or DMs.
|
||||
banned_users =
|
||||
|
||||
[Feed_Manager]
|
||||
# Enable or disable RSS/API feed subscriptions
|
||||
# true: Feed manager polls configured feeds and sends updates to channels
|
||||
# false: Feed manager disabled (default)
|
||||
feed_manager_enabled = false
|
||||
|
||||
[Scheduled_Messages]
|
||||
# Scheduled message format: HHMM = channel:message
|
||||
# Time format: HHMM (24-hour, no colon)
|
||||
# Bot will send these messages at the specified times daily
|
||||
0800 = general:Good morning! Bot is online and ready.
|
||||
1200 = general:Midday status check - all systems operational.
|
||||
1800 = general:Evening update - bot status: Good
|
||||
|
||||
[Logging]
|
||||
# Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
# DEBUG: Most verbose, shows all details
|
||||
# INFO: Standard logging level
|
||||
# WARNING: Only warnings and errors
|
||||
# ERROR: Only errors
|
||||
# CRITICAL: Only critical errors
|
||||
log_level = INFO
|
||||
|
||||
# Log file path (leave empty for console only)
|
||||
# Bot will write logs to this file in addition to console
|
||||
# Use absolute path for Docker compatibility (e.g., /data/logs/meshcore_bot.log)
|
||||
# Relative paths will resolve relative to the config file directory
|
||||
log_file = meshcore_bot.log
|
||||
|
||||
# Enable colored console output
|
||||
# true: Use colors in console output
|
||||
# false: Plain text output
|
||||
colored_output = true
|
||||
|
||||
# MeshCore library log level (separate from bot log level)
|
||||
# Controls debug output from the meshcore library itself
|
||||
# Options: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
meshcore_log_level = INFO
|
||||
|
||||
[Custom_Syntax]
|
||||
# Custom syntax patterns for special message formats
|
||||
# Format: pattern = "response_format"
|
||||
# Available fields: {sender}, {phrase}, {connection_info}, {snr}, {timestamp}, {path}
|
||||
# {phrase}: The text after the trigger (for t_phrase syntax)
|
||||
#
|
||||
# Special syntax: Messages starting with "t " or "T " followed by a phrase
|
||||
# Example: "t hello world" -> "ack {sender}: hello world | {connection_info}"
|
||||
t_phrase = "ack {sender}: {phrase} | {connection_info}"
|
||||
|
||||
|
||||
[External_Data]
|
||||
# Weather API key (future feature)
|
||||
weather_api_key =
|
||||
|
||||
# Weather update interval in seconds (future feature)
|
||||
weather_update_interval = 3600
|
||||
|
||||
# Tide API key (future feature)
|
||||
tide_api_key =
|
||||
|
||||
# Tide update interval in seconds (future feature)
|
||||
tide_update_interval = 1800
|
||||
|
||||
# N2YO API key for satellite pass information
|
||||
# Get free key at: https://www.n2yo.com/login/
|
||||
n2yo_api_key =
|
||||
|
||||
# AirNow API key for AQI data
|
||||
# Get free key at: https://docs.airnowapi.org/
|
||||
airnow_api_key =
|
||||
|
||||
# Repeater prefix API URL for prefix command
|
||||
# Leave empty to disable prefix command functionality
|
||||
# Configure your own regional API endpoint
|
||||
repeater_prefix_api_url =
|
||||
|
||||
# Repeater prefix cache duration in hours
|
||||
# How long to cache prefix data before refreshing from API
|
||||
# Recommended: 1-6 hours (data doesn't change frequently)
|
||||
repeater_prefix_cache_hours = 1
|
||||
|
||||
[Prefix_Command]
|
||||
# Enable or disable repeater geolocation in prefix command
|
||||
# true: Show city names with repeaters when location data is available
|
||||
# false: Show only repeater names without location information
|
||||
show_repeater_locations = true
|
||||
|
||||
# Use reverse geocoding for coordinates without city names
|
||||
# true: Automatically look up city names from GPS coordinates
|
||||
# false: Only show coordinates if no city name is available
|
||||
use_reverse_geocoding = true
|
||||
|
||||
# Hide prefix source information
|
||||
# true: Hide "Source: domain.com" line from prefix command output
|
||||
# false: Show source information (default)
|
||||
hide_source = false
|
||||
|
||||
# Prefix heard time window (days)
|
||||
# Number of days to look back when showing prefix results (default command behavior)
|
||||
# Only repeaters heard within this window will be shown by default
|
||||
# Use "prefix XX all" to show all repeaters regardless of time
|
||||
prefix_heard_days = 7
|
||||
|
||||
# Prefix free time window (days)
|
||||
# Number of days to look back when determining which prefixes are "free"
|
||||
# Only repeaters heard within this window will be considered as using a prefix
|
||||
# Repeaters not heard in this window will be excluded from used prefixes list
|
||||
prefix_free_days = 30
|
||||
|
||||
[Weather]
|
||||
# Default state for city name disambiguation
|
||||
# When users type "wx seattle", it will search for "seattle, WA, USA"
|
||||
# Use 2-letter state abbreviation (e.g., WA, CA, NY, TX)
|
||||
default_state = WA
|
||||
|
||||
# Default country for city name disambiguation (for international weather plugin)
|
||||
# Use 2-letter country code (e.g., US, CA, GB, AU)
|
||||
default_country = US
|
||||
|
||||
# Temperature unit for weather display
|
||||
# Options: fahrenheit, celsius
|
||||
# Default: fahrenheit
|
||||
temperature_unit = fahrenheit
|
||||
|
||||
# Wind speed unit for weather display
|
||||
# Options: mph, kmh, ms (meters per second)
|
||||
# Default: mph
|
||||
wind_speed_unit = mph
|
||||
|
||||
# Precipitation unit for weather display
|
||||
# Options: inch, mm
|
||||
# Default: inch
|
||||
precipitation_unit = inch
|
||||
|
||||
[Path_Command]
|
||||
# Optional prefix on path command replies: {sender}, {connection_info}, {path}, {timestamp}, {snr}, {rssi}
|
||||
# reply_prefix =
|
||||
# Bytes per hop before repeater name lookup (0/1 = always; 2/3 = gate to hex + tip if shorter)
|
||||
# minimum_path_bytes = 0
|
||||
# Geographic proximity calculation method
|
||||
# simple: Use proximity to bot location (default)
|
||||
# path: Use proximity to previous/next nodes in the path for more realistic routing
|
||||
proximity_method = simple
|
||||
|
||||
# Enable path proximity fallback
|
||||
# When path proximity can't be calculated (missing location data), fall back to simple proximity
|
||||
# true: Fall back to bot location proximity when path data unavailable
|
||||
# false: Show collision warning when path proximity unavailable
|
||||
path_proximity_fallback = true
|
||||
|
||||
# Maximum range for geographic proximity guessing (kilometers)
|
||||
# Repeaters beyond this distance will have reduced confidence or be rejected
|
||||
# Set to 0 to disable range limiting
|
||||
max_proximity_range = 200
|
||||
|
||||
# Maximum age for repeater data in path matching (days)
|
||||
# Only include repeaters that have been heard within this many days
|
||||
# Helps filter out stale or inactive repeaters from path decoding
|
||||
# Set to 0 to disable age filtering
|
||||
max_repeater_age_days = 14
|
||||
|
||||
# Confidence indicator symbols for path command
|
||||
# High confidence (>= 0.9): Shows when path decoding is very reliable
|
||||
high_confidence_symbol = 🎯
|
||||
|
||||
# Medium confidence (>= 0.8): Shows when path decoding is reasonably reliable
|
||||
medium_confidence_symbol = 📍
|
||||
|
||||
# Low confidence (< 0.8): Shows when path decoding has uncertainty
|
||||
low_confidence_symbol = ❓
|
||||
|
||||
[Solar_Config]
|
||||
# URL timeout for external API calls (seconds)
|
||||
url_timeout = 10
|
||||
|
||||
# Use Zulu/UTC time for astronomical data
|
||||
# true: Use 24-hour UTC format
|
||||
# false: Use 12-hour local format
|
||||
use_zulu_time = false
|
||||
|
||||
[Joke_Command]
|
||||
# Enable or disable the joke command (true/false)
|
||||
enabled = true
|
||||
|
||||
# Enable seasonal joke defaults (October: spooky, December: Christmas)
|
||||
# true: Seasonal defaults are applied (default)
|
||||
# false: No seasonal defaults (always random)
|
||||
seasonal_jokes = true
|
||||
|
||||
# Handle long jokes (over 130 characters)
|
||||
# false: Fetch new jokes until we get a short one (default)
|
||||
# true: Split long jokes into multiple messages
|
||||
long_jokes = false
|
||||
|
||||
[DadJoke_Command]
|
||||
# Enable or disable the dad joke command (true/false)
|
||||
enabled = true
|
||||
|
||||
# Handle long jokes (over 130 characters)
|
||||
# false: Fetch new jokes until we get a short one (default)
|
||||
# true: Split long jokes into multiple messages
|
||||
long_jokes = false
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def create_default_config(config_file: str) -> None:
|
||||
"""Write DEFAULT_CONFIG to ``config_file``.
|
||||
|
||||
Uses print() instead of logger since this is called before logging is set up.
|
||||
"""
|
||||
with open(config_file, 'w') as f:
|
||||
f.write(DEFAULT_CONFIG)
|
||||
print(f"Created default config file: {config_file}")
|
||||
Reference in New Issue
Block a user