Files
meshcore-bot/configuration/index.html
T
2026-09-20 19:56:33 +00:00

2319 lines
64 KiB
HTML
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.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Documentation for the MeshCore bot and its services">
<link rel="canonical" href="https://agessaman.github.io/meshcore-bot/configuration/">
<link rel="prev" href="../service-installation/">
<link rel="next" href="../data-retention/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7">
<title>Overview - Meshcore Bot Documentation</title>
<link rel="stylesheet" href="../assets/stylesheets/main.ec1eaa64.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="cyan">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#configuration" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="Meshcore Bot Documentation" class="md-header__button md-logo" aria-label="Meshcore Bot Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Meshcore Bot Documentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Overview
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="indigo" data-md-color-accent="cyan" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="indigo" data-md-color-accent="cyan" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12s-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12z"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/agessaman/meshcore-bot" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
meshcore-bot
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href=".." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../getting-started/" class="md-tabs__link">
Quick Start
</a>
</li>
<li class="md-tabs__item">
<a href="../installation/" class="md-tabs__link">
Installation
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="./" class="md-tabs__link">
Configuration
</a>
</li>
<li class="md-tabs__item">
<a href="../web-viewer/" class="md-tabs__link">
Web Viewer
</a>
</li>
<li class="md-tabs__item">
<a href="../command-reference/" class="md-tabs__link">
Command Reference
</a>
</li>
<li class="md-tabs__item">
<a href="../service-plugins/" class="md-tabs__link">
Service Plugins
</a>
</li>
<li class="md-tabs__item">
<a href="../faq/" class="md-tabs__link">
FAQ
</a>
</li>
<li class="md-tabs__item">
<a href="../upgrade/" class="md-tabs__link">
Upgrade
</a>
</li>
<li class="md-tabs__item">
<a href="../solar-conditions-provenance/" class="md-tabs__link">
Project
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted md-nav--integrated" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="Meshcore Bot Documentation" class="md-nav__button md-logo" aria-label="Meshcore Bot Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
Meshcore Bot Documentation
</label>
<div class="md-nav__source">
<a href="https://github.com/agessaman/meshcore-bot" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
meshcore-bot
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../getting-started/" class="md-nav__link">
<span class="md-ellipsis">
Quick Start
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" >
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
<span class="md-ellipsis">
Installation
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_3">
<span class="md-nav__icon md-icon"></span>
Installation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../installation/" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../docker/" class="md-nav__link">
<span class="md-ellipsis">
Docker
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../service-installation/" class="md-nav__link">
<span class="md-ellipsis">
Service
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_4" checked>
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="">
<span class="md-ellipsis">
Configuration
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="true">
<label class="md-nav__title" for="__nav_4">
<span class="md-nav__icon md-icon"></span>
Configuration
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Overview
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Overview
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#configini-structure" class="md-nav__link">
<span class="md-ellipsis">
config.ini structure
</span>
</a>
<nav class="md-nav" aria-label="config.ini structure">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#connection-type-and-precedence" class="md-nav__link">
<span class="md-ellipsis">
Connection: type and precedence
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#connection-transport-reconnect" class="md-nav__link">
<span class="md-ellipsis">
Connection: transport reconnect
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#logging-and-log-rotation" class="md-nav__link">
<span class="md-ellipsis">
Logging and log rotation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#localization" class="md-nav__link">
<span class="md-ellipsis">
Localization
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#channels-section" class="md-nav__link">
<span class="md-ellipsis">
Channels section
</span>
</a>
<nav class="md-nav" aria-label="Channels section">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#outgoing_flood_scope_override-vs-flood_scopes" class="md-nav__link">
<span class="md-ellipsis">
outgoing_flood_scope_override vs flood_scopes
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#managing-region-scopes-from-the-web-viewer" class="md-nav__link">
<span class="md-ellipsis">
Managing region scopes from the web viewer
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#public-channel-guard" class="md-nav__link">
<span class="md-ellipsis">
Public channel guard
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#command-and-feature-sections" class="md-nav__link">
<span class="md-ellipsis">
Command and feature sections
</span>
</a>
<nav class="md-nav" aria-label="Command and feature sections">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#enabling-and-disabling-commands" class="md-nav__link">
<span class="md-ellipsis">
Enabling and disabling commands
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#command-specific-sections" class="md-nav__link">
<span class="md-ellipsis">
Command-specific sections
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#command-prefix" class="md-nav__link">
<span class="md-ellipsis">
Command prefix
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#config-templates" class="md-nav__link">
<span class="md-ellipsis">
Config templates
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#data-retention" class="md-nav__link">
<span class="md-ellipsis">
Data retention
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#path-command-configuration" class="md-nav__link">
<span class="md-ellipsis">
Path Command configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#service-plugin-configuration" class="md-nav__link">
<span class="md-ellipsis">
Service plugin configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#config-validation" class="md-nav__link">
<span class="md-ellipsis">
Config validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#reloading-configuration" class="md-nav__link">
<span class="md-ellipsis">
Reloading configuration
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#pausing-channel-responses-remote" class="md-nav__link">
<span class="md-ellipsis">
Pausing channel responses (remote)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#scheduled-messages-scheduled_messages" class="md-nav__link">
<span class="md-ellipsis">
Scheduled messages ([Scheduled_Messages])
</span>
</a>
<nav class="md-nav" aria-label="Scheduled messages ([Scheduled_Messages])">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#managing-schedules-from-the-web-viewer" class="md-nav__link">
<span class="md-ellipsis">
Managing schedules from the web viewer
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#broadcasting-a-commands-output-cmd" class="md-nav__link">
<span class="md-ellipsis">
Broadcasting a command's output ({cmd:...})
</span>
</a>
<nav class="md-nav" aria-label="Broadcasting a command&#39;s output ({cmd:...})">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#airtime-guards" class="md-nav__link">
<span class="md-ellipsis">
Airtime guards
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#schedule-keys-apscheduler-cron-not-vixie" class="md-nav__link">
<span class="md-ellipsis">
Schedule keys (APScheduler cron, not Vixie)
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../data-retention/" class="md-nav__link">
<span class="md-ellipsis">
Data retention
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../local-plugins/" class="md-nav__link">
<span class="md-ellipsis">
Local plugins and services
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../checkin-api/" class="md-nav__link">
<span class="md-ellipsis">
Check-in API
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../path-command-config/" class="md-nav__link">
<span class="md-ellipsis">
Path Command
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../config-validation/" class="md-nav__link">
<span class="md-ellipsis">
Config validation
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../region-warnings/" class="md-nav__link">
<span class="md-ellipsis">
Region warnings
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../web-viewer/" class="md-nav__link">
<span class="md-ellipsis">
Web Viewer
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_6" >
<label class="md-nav__link" for="__nav_6" id="__nav_6_label" tabindex="0">
<span class="md-ellipsis">
Command Reference
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_6_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_6">
<span class="md-nav__icon md-icon"></span>
Command Reference
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../command-reference/" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../repeater-commands/" class="md-nav__link">
<span class="md-ellipsis">
Repeater Commands
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../command-reference-website/" class="md-nav__link">
<span class="md-ellipsis">
Custom Command Website
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../develop-command-scripts/" class="md-nav__link">
<span class="md-ellipsis">
Developing Commands
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_7" >
<label class="md-nav__link" for="__nav_7" id="__nav_7_label" tabindex="0">
<span class="md-ellipsis">
Service Plugins
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_7_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_7">
<span class="md-nav__icon md-icon"></span>
Service Plugins
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../service-plugins/" class="md-nav__link">
<span class="md-ellipsis">
Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../discord-bridge/" class="md-nav__link">
<span class="md-ellipsis">
Discord Bridge
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../telegram-bridge/" class="md-nav__link">
<span class="md-ellipsis">
Telegram Bridge
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../packet-capture/" class="md-nav__link">
<span class="md-ellipsis">
Packet Capture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../map-uploader/" class="md-nav__link">
<span class="md-ellipsis">
Map Uploader
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../weather-service/" class="md-nav__link">
<span class="md-ellipsis">
Weather Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../earthquake-service/" class="md-nav__link">
<span class="md-ellipsis">
Earthquake Service
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../repeater-prefix-collision-service/" class="md-nav__link">
<span class="md-ellipsis">
Repeater Prefix Collision
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../worldcup/" class="md-nav__link">
<span class="md-ellipsis">
World Cup
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../FEEDS/" class="md-nav__link">
<span class="md-ellipsis">
Feed Management
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../faq/" class="md-nav__link">
<span class="md-ellipsis">
FAQ
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../upgrade/" class="md-nav__link">
<span class="md-ellipsis">
Upgrade
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_10" >
<label class="md-nav__link" for="__nav_10" id="__nav_10_label" tabindex="0">
<span class="md-ellipsis">
Project
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_10_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_10">
<span class="md-nav__icon md-icon"></span>
Project
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../solar-conditions-provenance/" class="md-nav__link">
<span class="md-ellipsis">
Solar conditions provenance
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="configuration">Configuration</h1>
<p>The bot is configured via <code>config.ini</code> in the project root (or the path given with <code>--config</code>). This page describes how configuration is organized and where to find command-specific options.</p>
<h2 id="configini-structure">config.ini structure</h2>
<ul>
<li><strong>Sections</strong> are named in square brackets, e.g. <code>[Bot]</code>, <code>[Connection]</code>, <code>[Path_Command]</code>.</li>
<li><strong>Options</strong> are <code>key = value</code> (or <code>key=value</code>). Comments start with <code>#</code> or <code>;</code>.</li>
<li><strong>Paths</strong> can be relative (to the directory containing the config file) or absolute. For Docker, use absolute paths under <code>/data/</code> (see <a href="../docker/">Docker deployment</a>).</li>
</ul>
<p>The main sections include:</p>
<table>
<thead>
<tr>
<th>Section</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>[Bot]</code></td>
<td>Bot name, database path, response toggles, command prefix</td>
</tr>
<tr>
<td><code>[Connection]</code></td>
<td>Serial, BLE, or TCP connection to the MeshCore device</td>
</tr>
<tr>
<td><code>[Channels]</code></td>
<td>Channels to monitor, DM behavior, optional channel keyword whitelist</td>
</tr>
<tr>
<td><code>[Localization]</code></td>
<td>Default response language and optional sender-language detection</td>
</tr>
<tr>
<td><code>[Admin_ACL]</code></td>
<td>Admin public keys and admin-only commands</td>
</tr>
<tr>
<td><code>[Keywords]</code></td>
<td>Keyword → response pairs</td>
</tr>
<tr>
<td><code>[Weather]</code></td>
<td>Units and settings shared by <code>wx</code> / <code>gwx</code> and Weather Service</td>
</tr>
<tr>
<td><code>[Logging]</code></td>
<td>Log file path and level</td>
</tr>
</tbody>
</table>
<h3 id="connection-type-and-precedence">Connection: type and precedence</h3>
<p><code>connection_type</code> in <code>[Connection]</code> selects the transport. <strong>Only the matching keys are read</strong>; other keys in the section are ignored at runtime (no error).</p>
<table>
<thead>
<tr>
<th><code>connection_type</code></th>
<th>Keys used</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>serial</code></td>
<td><code>serial_port</code></td>
<td>USB serial device path</td>
</tr>
<tr>
<td><code>ble</code></td>
<td><code>ble_device_name</code></td>
<td>Empty = auto-detect first BLE device</td>
</tr>
<tr>
<td><code>tcp</code></td>
<td><code>hostname</code>, <code>tcp_port</code></td>
<td><code>hostname</code> required; <code>tcp_port</code> defaults to 5000</td>
</tr>
</tbody>
</table>
<p>Do not use <code>host</code> or <code>port</code> under <code>[Connection]</code> — those names are for <code>[Web_Viewer]</code> and <code>[Webhook]</code> listen addresses. TCP client connect uses <code>hostname</code> and <code>tcp_port</code>.</p>
<p><code>config.ini.example</code> lists all connection keys uncommented so the config TUI and migrate tool recognize them. Lean templates (<code>minimal-example</code>, <code>quickstart</code>) comment out BLE/TCP keys by default because they ship with <code>connection_type = serial</code>.</p>
<h3 id="connection-transport-reconnect">Connection: transport reconnect</h3>
<p><code>[Connection]</code> options <code>reconnect_max_retries</code> (0 = unlimited), <code>reconnect_delay_seconds</code>, and <code>reconnect_max_delay_seconds</code> apply to <strong>serial, BLE, and TCP</strong>. When the meshcore transport drops, the bot schedules reconnect with exponential backoff.</p>
<ul>
<li><strong>TCP</strong> — meshcore emits <code>DISCONNECTED</code> on socket loss; the bot reconnects immediately (the main loop also polls every 5s as a backup). If <code>radio_probe_fail_threshold</code> consecutive <code>get_time</code> probes fail or time out, the bot reconnects the TCP session instead of declaring a zombie radio (serial/BLE probes still use zombie detection for unresponsive firmware).</li>
<li><strong>Serial</strong> — USB unplug triggers the same reconnect path; zombie detection remains for “port open but firmware dead” cases.</li>
</ul>
<p>See <code>config.ini.example</code> for defaults and <code>radio_probe_*</code> / <code>radio_offline_*</code> alert options.</p>
<h3 id="logging-and-log-rotation">Logging and log rotation</h3>
<ul>
<li>
<p><strong>Startup (config.ini):</strong> Under <code>[Logging]</code>, <code>log_file</code>, <code>log_max_bytes</code>, and <code>log_backup_count</code> are read when the bot starts. They control the initial <code>RotatingFileHandler</code> for the bot log file (see <code>config.ini.example</code>).</p>
</li>
<li>
<p><strong>Live changes (web viewer):</strong> The Config tab can store <strong><code>maint.log_max_bytes</code></strong> and <strong><code>maint.log_backup_count</code></strong> in the database (<code>bot_metadata</code>). The scheduler’s maintenance loop applies those values to the existing rotating file handler <strong>without restarting</strong> the bot—<strong>but only after</strong> you save rotation settings from the web UI (which writes the metadata keys). Editing <code>config.ini</code> alone does not update <code>bot_metadata</code>, so hot-apply will not see a change until you save from the viewer (or set the keys another way).</p>
</li>
</ul>
<p>If you rely on config-file-only workflows, restart the bot after changing <code>[Logging]</code> rotation options.</p>
<h3 id="localization">Localization</h3>
<p><code>[Localization] language</code> selects the bot's default translation catalog.
Set <code>auto_detect_language = true</code> to let greeting-style commands reply in the
sender's detected language when that translation is installed. Detection is
keyword-first so short mesh greetings such as <code>hola</code>, <code>bonjour</code>, and <code>hallo</code>
work without another dependency.</p>
<p>For statistical detection of longer messages, install the optional extra:</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a>pip<span class="w"> </span>install<span class="w"> </span><span class="s2">"meshcore-bot[lang]"</span>
</code></pre></div>
<p>Detection is opt-in and falls back to the configured default language whenever
the message is ambiguous, the detector is unavailable, or the corresponding
translation catalog is absent.</p>
<h2 id="channels-section">Channels section</h2>
<p><code>[Channels]</code> controls where the bot responds:</p>
<ul>
<li><strong><code>monitor_channels</code></strong> – Comma-separated channel names. The bot only responds to messages on these channels (and in DMs if enabled).</li>
<li><strong><code>respond_to_dms</code></strong> – If <code>true</code>, the bot responds to direct messages; if <code>false</code>, it ignores DMs.</li>
<li><strong><code>channel_keywords</code></strong> – Optional. When set (comma-separated command/keyword names), only those triggers are answered <strong>in channels</strong>; DMs always get all triggers. Use this to reduce channel traffic by making heavy triggers (e.g. <code>wx</code>, <code>satpass</code>, <code>joke</code>) DM-only. Leave empty or omit to allow all triggers in monitored channels. Per-command <strong><code>channels =</code></strong> (empty) in a command’s section also forces that command to be DM-only; see <code>config.ini.example</code> for examples (e.g. <code>[Joke_Command]</code>).</li>
<li><strong><code>max_response_hops</code></strong> - Default: 64 (code fallback); 7 in the shipped config templates. The bot will ignore messages that have traveled more than this number of hops. A value at or below 10 is recommended — in most meshes, anything higher is almost never an intentional message meant to trigger this bot, so lowering it keeps the bot from amplifying long flood traffic (#161).</li>
<li><strong><code>outgoing_flood_scope_override</code></strong> – Optional. Fixed regional scope for outbound channel sends when no per-message scope is passed to <code>send_channel_message</code>. When <strong>not set</strong> (default), replies use <strong><code>reply_scope</code></strong> from inbound TC_FLOOD correlation (auto-mirror). When <strong>set</strong> (e.g. <code>#west</code>), that scope is used for proactive sends (webhooks, scheduled messages, feeds) and whenever <code>reply_scope</code> is unset. It does <strong>not</strong> override an explicit <code>reply_scope</code> on a reply. Unscoped FLOOD uses global flood unless this override or <code>reply_scope</code> applies.</li>
<li><strong><code>flood_scopes</code></strong> – Optional. Comma-separated list of named scopes the bot will <strong>accept and reply to</strong>. When set, this acts as an allowlist: only TC_FLOOD messages matching one of these scopes receive a reply, and the reply is sent using the same scope as the incoming message (auto-mirror via <code>reply_scope</code>). Regular (unscoped) FLOOD messages are blocked unless <code>*</code> is included in the list. Leave empty or omit to accept all messages regardless of scope. <strong>Auto-mirror requires correct RF correlation</strong> (TC_FLOOD / GRP_TXT in the RF cache); if correlation fails, the bot will not use a stale ADVERT or other packet for scope and may ignore the message when <code>*</code> is not listed.</li>
</ul>
<h3 id="outgoing_flood_scope_override-vs-flood_scopes">outgoing_flood_scope_override vs flood_scopes</h3>
<p>These two options are independent and serve different purposes:</p>
<table>
<thead>
<tr>
<th>Option</th>
<th>Controls</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>outgoing_flood_scope_override</code></td>
<td>Default/fallback outbound scope when <code>reply_scope</code> is unset (proactive sends); does not override <code>reply_scope</code> on replies</td>
</tr>
<tr>
<td><code>flood_scopes</code></td>
<td>Which incoming scopes the bot <em>accepts</em> (allowlist + per-message <code>reply_scope</code> from RF correlation)</td>
</tr>
</tbody>
</table>
<p><strong>Example — auto-mirror incoming scope (default, no override needed):</strong>
<div class="highlight"><pre><span></span><code><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="na">flood_scopes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="c1">#west, #east</span>
</code></pre></div>
Only TC_FLOOD messages scoped to <code>#west</code> or <code>#east</code> receive a reply; unscoped FLOOD is silently ignored. Replies automatically use the same scope as the incoming message (<code>#west</code> → reply with <code>#west</code>, etc.).</p>
<p><strong>Example — accept specific regions plus unscoped FLOOD:</strong>
<div class="highlight"><pre><span></span><code><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a><span class="na">flood_scopes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="c1">#west, #east, *</span>
</code></pre></div>
Same as above, but <code>*</code> opts in to also accepting regular (unscoped) FLOOD messages.</p>
<p><strong>Example — fixed outbound scope for proactive sends and when mirror fails:</strong>
<div class="highlight"><pre><span></span><code><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="na">outgoing_flood_scope_override</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="c1">#west</span>
</code></pre></div>
Channel replies still prefer <code>reply_scope</code> from correlated TC_FLOOD when present. Override applies when <code>reply_scope</code> is unset (webhooks, scheduled jobs, or failed RF scope correlation).</p>
<p><strong>Example — fixed outbound scope, restricted to a matching inbound scope:</strong>
<div class="highlight"><pre><span></span><code><a id="__codelineno-4-1" name="__codelineno-4-1" href="#__codelineno-4-1"></a><span class="na">outgoing_flood_scope_override</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="c1">#west</span>
<a id="__codelineno-4-2" name="__codelineno-4-2" href="#__codelineno-4-2"></a><span class="na">flood_scopes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="c1">#west</span>
</code></pre></div></p>
<h3 id="managing-region-scopes-from-the-web-viewer">Managing region scopes from the web viewer</h3>
<p>The <strong>Region Scopes</strong> card on the web viewer's <strong>Radio</strong> page edits both keys
for you. It writes <code>[Channels]</code> in <code>config.ini</code> and queues a config reload, so
changes take effect <strong>without restarting the bot</strong>, and it reports what the
reload did rather than only that the file was written.</p>
<p>It is an editor for the same two keys, not a separate store, so values set by
hand and values set in the UI are the same thing. Scope names are normalized on
save the way the bot normalizes them (<code>west</code> is stored as <code>#west</code>), and a name
containing <code>,</code>, <code>%</code> or an inner <code>#</code> is refused before anything is written.</p>
<p>Per-channel <code>flood_scope.&lt;channel&gt;</code> entries are shown read-only there; edit
those in <code>config.ini</code>.</p>
<h3 id="public-channel-guard">Public channel guard</h3>
<p>The bot <strong>refuses to start</strong> if <code>monitor_channels</code> includes the Public channel, unless an explicit override key is set in <code>[Bot]</code>. This prevents accidental bot deployments on the shared channel that is visible to all mesh users by default.</p>
<p>If you genuinely intend to run the bot on Public, add to <code>[Bot]</code>:</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="na">i_understand_that_running_the_bot_on_the_public_channel_is_potentially_disruptive_to_other_users_enjoyment_of_the_mesh_and_i_would_like_to_do_it_anyway</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
</code></pre></div>
<h2 id="command-and-feature-sections">Command and feature sections</h2>
<p>Many commands and features have their own section. Options there control whether the command is enabled and how it behaves.</p>
<h3 id="enabling-and-disabling-commands">Enabling and disabling commands</h3>
<ul>
<li><strong><code>enabled</code></strong> – Common option to turn a command or plugin on or off. Example:
<div class="highlight"><pre><span></span><code><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a><span class="k">[Aurora_Command]</span>
<a id="__codelineno-6-2" name="__codelineno-6-2" href="#__codelineno-6-2"></a><span class="na">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">true</span>
</code></pre></div></li>
<li>Commands without an <code>enabled</code> key are typically always available (subject to <a href="https://github.com/agessaman/meshcore-bot/blob/main/README.md">Admin_ACL</a> for admin-only commands).</li>
</ul>
<h3 id="command-specific-sections">Command-specific sections</h3>
<p>Examples of sections that configure specific commands or features:</p>
<ul>
<li><strong><code>[Path_Command]</code></strong> – Path decoding and repeater selection. See <a href="../path-command-config/">Path Command</a> for all options.</li>
<li><strong><code>[Test_Command]</code></strong> – <code>test</code> / <code>t</code> behavior. Optional <strong><code>response_format</code></strong> overrides the legacy <strong><code>[Keywords] test</code></strong> string. Templates support the same placeholders as Keywords, plus <strong>feed-style pipe filters</strong> on placeholders (e.g. <code>{path_distance|pathbytes_min:2}</code>, <code>{firstlast_distance|hops_min:1}</code>) implemented in <code>modules/response_template.py</code>—see comments under <code>[Test_Command]</code> in <code>config.ini.example</code>.</li>
<li><strong><code>[Prefix_Command]</code></strong> – Prefix lookup, prefix best, range limits.</li>
<li><strong><code>[Cmd_Command]</code></strong> – <code>cmd</code> behavior. Set <code>cmd_reference_url</code> to return <code>Full command reference: &lt;url&gt;</code> instead of the generated compact command list.</li>
<li><strong><code>[Weather]</code></strong> – Used by the <code>wx</code> / <code>gwx</code> commands and the Weather Service plugin (see <a href="../weather-service/">Weather Service</a>).</li>
<li><strong><code>[Airplanes_Command]</code></strong> – Aircraft/ADS-B command (API URL, radius, limits). Default <code>api_url</code> is <code>https://api.adsb.lol/v2/</code>.</li>
<li><strong><code>[Aurora_Command]</code></strong> – Aurora command (default coordinates).</li>
<li><strong><code>[Alert_Command]</code></strong> – Emergency alerts (agency IDs, etc.).</li>
<li><strong><code>[Sports_Command]</code></strong> – Sports scores (teams, leagues).</li>
<li><strong><code>[Joke_Command]</code></strong>, <strong><code>[DadJoke_Command]</code></strong> – Joke sources and options.</li>
<li><strong><code>[RandomLine]</code></strong> – Trigger-based random-line responses via <code>triggers.&lt;key&gt;</code>, <code>file.&lt;key&gt;</code>, optional <code>prefix.&lt;key&gt;</code>, optional channel restriction (<code>channel.&lt;key&gt;</code>/<code>channels.&lt;key&gt;</code>), and optional website category override (<code>category.&lt;key&gt;</code>). Website command reference groups RandomLine entries under <strong>Fun Commands</strong> by default unless <code>category.&lt;key&gt;</code> is set.</li>
</ul>
<p>Common per-command options (when supported by that command):</p>
<ul>
<li><strong><code>channels</code></strong> – Restrict where that command runs in channels:</li>
<li>Omit key: follow global <code>[Channels] monitor_channels</code></li>
<li>Empty (<code>channels =</code>): DM-only</li>
<li>Comma list: only those channels</li>
<li><strong><code>aliases</code></strong> – Extra trigger words for that command, comma-separated <strong>stems only</strong> (e.g. <code>aliases = weather, w</code>). Do not put the bot's <strong><code>command_prefix</code></strong> or punctuation in this value (no <code>!</code> or <code>.</code>)</li>
</ul>
<h3 id="command-prefix">Command prefix</h3>
<p>Under <code>[Bot]</code>:</p>
<ul>
<li><strong><code>command_prefix</code></strong> – Optional global prefix(es) for commands. A single value (<code>!</code>, <code>abc</code>), comma-separated list (<code>!, ~, .</code>), or concatenated decorative characters (<code>!~.</code>) where the <strong>first</strong> entry is shown in help/docs. Leave empty for bare commands (legacy leading <code>!</code> is still accepted).</li>
<li><strong><code>require_command_prefix</code></strong> – When <code>true</code> (default), messages must start with a configured prefix. When <code>false</code>, configured prefix(es) are stripped when present but bare commands also work. Ignored when <code>command_prefix</code> is empty.</li>
</ul>
<p>Full reference: see <code>config.ini.example</code> in the repository for every section and option, with inline comments.</p>
<h3 id="config-templates">Config templates</h3>
<ul>
<li><strong><code>config.ini.example</code></strong> – Authoritative full reference; edit this when adding options or sections.</li>
<li><strong><code>config.ini.minimal-example</code></strong> – Lean config for core testing commands only (ping, version, test, path, prefix, multitest). Hand-maintained; see its header for purpose. Point users to <code>config.ini.example</code> for full options when enabling more features.</li>
<li><strong><code>config.ini.quickstart</code></strong> – Short easy-start config with a few common commands enabled. Hand-maintained.</li>
<li><strong><code>scripts/config_tui.py</code></strong> (<code>make config</code>) – Uses documented keys from <code>config.ini.example</code> (including commented <code>#key =</code> lines) for validation and migrate.</li>
</ul>
<h2 id="data-retention">Data retention</h2>
<p>Database tables (packet stream, stats, repeater data, mesh graph) are pruned automatically. Retention periods and defaults are described in <strong><a href="../data-retention/">Data retention</a></strong>. The bot’s scheduler runs cleanup daily even when the standalone web viewer is not running.</p>
<h2 id="path-command-configuration">Path Command configuration</h2>
<p>The Path command has many options (presets, proximity, graph validation, etc.). All are documented in:</p>
<p><strong><a href="../path-command-config/">Path Command</a></strong> – Presets, geographic and graph settings, and tuning.</p>
<h2 id="service-plugin-configuration">Service plugin configuration</h2>
<p>Service plugins (Discord Bridge, Telegram Bridge, Packet Capture, Map Uploader, Weather Service, Earthquake Service, Repeater Prefix Collision Service, and Webhook Service) each have their own section and are documented under <a href="../service-plugins/">Service Plugins</a>. The MQTT weather relay uses the <code>MqttWeather</code> section plus custom topic keys under <code>[Weather]</code>.</p>
<h2 id="config-validation">Config validation</h2>
<p>Before starting the bot, you can validate section names and path writability. See <a href="../config-validation/">Config validation</a> for how to run <code>validate_config.py</code> or <code>meshcore_bot.py --validate-config</code>, and what is checked (required sections, typos like <code>[WebViewer]</code> → <code>[Web_Viewer]</code>, and writable paths).</p>
<h2 id="reloading-configuration">Reloading configuration</h2>
<p>Some configuration can be reloaded without restarting the bot using the <strong><code>reload</code></strong> command (admin only). Radio/connection settings are not changed by reload; restart the bot for those.</p>
<h2 id="pausing-channel-responses-remote">Pausing channel responses (remote)</h2>
<p>Admins can DM <strong><code>channelpause</code></strong> or <strong><code>channelresume</code></strong> (see <code>[Admin_ACL]</code> in <code>config.ini</code>) to stop or resume bot reactions on <strong>public channels</strong> only—greeter, keywords, and commands on channels are skipped; DMs still work. The setting is <strong>in memory only</strong> (back to responding on channels after restart). Scheduled channel posts from the scheduler are <strong>not</strong> blocked by this toggle.</p>
<h2 id="scheduled-messages-scheduled_messages">Scheduled messages (<code>[Scheduled_Messages]</code>)</h2>
<p>Each entry is <code>&lt;schedule_key&gt; = &lt;value&gt;</code> where the value is normally <strong><code>channel:message</code></strong> (first colon separates channel from body). For <strong>regional flood scope</strong> on that send only, use <strong><code>channel:#scope:message</code></strong>: the middle segment must start with <code>#</code> (same convention as <code>flood_scopes</code> / <code>outgoing_flood_scope_override</code>). The message body may contain more colons. Omit the middle field for classic global flood. See <code>config.ini.example</code> under <code>[Scheduled_Messages]</code> for examples. The <strong><code>schedule</code></strong> command lists each job with <code>(#scope)</code> when set.</p>
<h3 id="managing-schedules-from-the-web-viewer">Managing schedules from the web viewer</h3>
<p>The <strong>Schedule</strong> page in the web viewer edits this section for you. It writes <code>config.ini</code> and queues a config reload, so new and changed schedules take effect <strong>without restarting the bot</strong>.</p>
<p>It is an editor for the same <code>[Scheduled_Messages]</code> section, not a separate store, so entries added by hand and entries added in the UI are the same thing and the <code>schedule</code> command lists both.</p>
<p>The editor builds the cron key from plain-language options (every day at a time, several times a day, certain days of the week, every N hours or minutes) and shows the resolved expression plus the next five run times before you save. "Advanced (cron)" accepts a raw expression. Existing entries always open in Advanced so a stored expression is never silently rewritten.</p>
<p>Schedules that the bot cannot run are listed rather than hidden, marked <strong>Not scheduled</strong> with the reason, so a typo that stops a message from airing is visible instead of silent. The 15-minute floor for <code>{cmd:...}</code> messages is enforced in the editor too, so it is refused when you save rather than dropped later at startup.</p>
<h3 id="broadcasting-a-commands-output-cmd">Broadcasting a command's output (<code>{cmd:...}</code>)</h3>
<p>A scheduled message can embed the reply of any bot command with <strong><code>{cmd:&lt;command&gt; [args]}</code></strong>. The command runs for its text only — it transmits nothing itself — and the scheduled message carries the result. This is how you put a recurring forecast (or any other command) on the air without that command needing its own scheduling settings:</p>
<div class="highlight"><pre><span></span><code><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a><span class="k">[Scheduled_Messages]</span>
<a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a><span class="na">0 6,12,18 * * *</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Public:{cmd:wx Seattle}</span>
<a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a><span class="na">*/30 * * * *</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Public:Conditions now: {cmd:wx 98101}</span>
<a id="__codelineno-7-4" name="__codelineno-7-4" href="#__codelineno-7-4"></a><span class="na">@hourly</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">Public:{cmd:aqi Tacoma}</span>
</code></pre></div>
<p>The trigger is matched against command <strong>names and their keywords</strong>, so <code>{cmd:weather Seattle}</code> and <code>{cmd:wx Seattle}</code> are the same command. Arguments are passed through exactly as a user would type them.</p>
<p>A placeholder expands to <strong>nothing</strong> (and logs a warning) when the command is unknown, disabled in config, admin-only, times out, or returns no output — the literal <code>{cmd:...}</code> is never transmitted. If a message is empty after expansion, nothing is sent at all. Command output is not re-scanned, so a reply that happens to contain <code>{cmd:...}</code> cannot recurse.</p>
<h4 id="airtime-guards">Airtime guards</h4>
<p>Every firing is a transmission on a shared medium, and a command placeholder makes it easy to write a cron that airs several times an hour. Two guards apply, and neither is configurable:</p>
<ul>
<li><strong>A 15-minute floor.</strong> A schedule containing <code>{cmd:...}</code> may not fire more often than every 15 minutes. An entry that does is <strong>rejected at startup</strong> with an error and is not scheduled at all — it does not silently run at a slower rate. The check measures the <em>tightest</em> gap between firings, so <code>0,1 * * * *</code> is treated as a 60-second schedule rather than an hourly one. Schedules with no command placeholder are unaffected.</li>
<li><strong>The command's own cooldown still applies.</strong> <code>[&lt;Name&gt;_Command] cooldown_seconds</code> is not bypassed by scheduling. If the command is on cooldown when the schedule fires, the placeholder expands to nothing for that round and logs a warning.</li>
</ul>
<p>Other limits worth knowing:</p>
<ul>
<li><strong>Only commands marked <code>render_safe</code> can be rendered.</strong> Capture intercepts <code>send_response</code> and <code>send_response_chunked</code>, so a command that transmits by other means (<code>advert</code>), posts its own messages (<code>announcements</code>), or is DM-only (<code>schedule</code>, which would broadcast configuration) is refused. This is opt-in rather than a denylist, so a new command is never renderable by accident. The read-only informational commands (<code>wx</code>, <code>aqi</code>, <code>sun</code>, <code>moon</code>, <code>solar</code>, <code>hfcond</code>, <code>satpass</code>, <code>rain</code>, <code>stats</code>, and similar) are marked safe.</li>
<li><code>[Bot] scheduled_command_timeout_seconds</code> (default <code>30</code>) bounds each render. Network-backed commands like <code>wx</code> need the headroom.</li>
</ul>
<p>A rendered reply longer than one message is split to the RF body budget and sent as several messages rather than failing at the device.</p>
<p>Even within the floor, mind the cost: a <code>*/15 * * * *</code> forecast is 96 transmissions a day.</p>
<h3 id="schedule-keys-apscheduler-cron-not-vixie">Schedule keys (APScheduler cron, not Vixie)</h3>
<p>Schedule keys are parsed by <strong>APScheduler</strong> <code>CronTrigger.from_crontab</code> (plus <code>@</code> presets and deprecated <code>HHMM</code>). Field order is the usual five: <code>minute hour day-of-month month day-of-week</code>.</p>
<p><strong>Day-of-week numbering differs from classic Vixie / crontab(5):</strong></p>
<table>
<thead>
<tr>
<th></th>
<th>APScheduler (this bot)</th>
<th>Vixie cron</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>0</code></td>
<td>Monday</td>
<td>Sunday</td>
</tr>
<tr>
<td><code>1</code> … <code>6</code></td>
<td>Tuesday … Sunday</td>
<td>Monday … Saturday</td>
</tr>
<tr>
<td><code>7</code></td>
<td>Invalid</td>
<td>Often accepted as Sunday</td>
</tr>
</tbody>
</table>
<p>Prefer <strong><code>mon</code>–<code>sun</code></strong> names in the DOW field so expressions stay unambiguous. Example: Monday 12:30 is <code>30 12 * * mon</code> or <code>30 12 * * 0</code> — <strong>not</strong> Vixie’s <code>30 12 * * 1</code> (that is Tuesday here).</p>
<p>Preset aliases expand to those same APScheduler forms. In particular <strong><code>@weekly</code></strong> is Monday 00:00 (<code>0 0 * * 0</code>), not Sunday midnight as on many Unix crons.</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["navigation.instant", "navigation.tracking", "navigation.tabs", "navigation.sections", "toc.integrate", "content.code.copy"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.d7400e89.min.js"></script>
</body>
</html>