Files

949 lines
37 KiB
Plaintext
Raw Permalink 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.
repeater:
# Node name for logging and identification
node_name: "mesh-repeater-01"
# TX mode: forward | monitor | no_tx (default: forward)
# forward = repeat on; monitor = no repeat but companions/tenants can send; no_tx = all TX off
# mode: forward
# Geographic location (optional)
# Latitude in decimal degrees (-90 to 90)
latitude: 0.0
# Longitude in decimal degrees (-180 to 180)
longitude: 0.0
# Path to identity file (public/private key)
# If not specified, a new identity will be generated
identity_file: null
# Identity key (alternative to identity_file)
# Store the private key directly in config as binary (set by convert_firmware_key.sh)
# If both identity_file and identity_key are set, identity_key takes precedence
# identity_key: null
# Owner information (shown to clients requesting owner info)
owner_info: ""
# Duplicate packet cache TTL in seconds
cache_ttl: 3600
# Maximum number of hops a flood packet may have already traversed before
# this repeater forwards it.
max_flood_hops: 64
# Score-based transmission filtering
# Enable quality-based packet filtering and adaptive delays
use_score_for_tx: false
# Multi-ack redundancy (0 = off, 1 = on; matches MeshCore's multi.acks pref).
# When on, a relayed routed ACK is preceded by a MULTIPART-wrapped copy so
# ACKs survive lossy links. Also settable via the mesh CLI: set multi.acks
multi_acks: 0
# Score threshold for quality monitoring (future use)
# Currently reserved for potential future features like dashboard alerts,
# proactive statistics collection, or advanced filtering strategies.
# Changing this value has no effect on current packet processing.
score_threshold: 0.3
# Observation-only upstream neighbour link metrics.
# These metrics are currently not used for forwarding, dedupe, TX delay,
# route selection, or packet acceptance decisions yet ;-0.
neighbour_link_metrics_enabled: true
neighbour_link_ewma_alpha: 0.20
neighbour_link_ttl_seconds: 86400
neighbour_link_max_entries: 512
# Automatic advertisement interval in hours
# The repeater will send an advertisement packet at this interval
# Set to 0 to disable automatic adverts (manual only via web interface)
# Range: 0 (disabled) to 24+ hours
# Recommended: 10 hours for typical deployments
send_advert_interval_hours: 10
# Additional periodic advert stream in hours.
# Uses the same advert packet type and scheduler loop, but independent cadence.
# Set to 0 to disable.
# Range: 0 (disabled) or 1-168 hours
direct_advert_interval_hours: 0
# Respond to discovery requests from other nodes
# When enabled, the repeater will automatically respond to discovery packets
# with its node information (node type 2 - repeater)
allow_discovery: true
# Incoming advert rate limiter (per advert public key)
# Uses a token bucket to smooth bursts.
advert_rate_limit:
# Master switch for token bucket limiting
enabled: false
# Max burst size allowed immediately per pubkey
# Keep this small for long advert intervals.
bucket_capacity: 2
# Number of tokens added each refill interval
refill_tokens: 1
# Refill interval in seconds (10 hours)
refill_interval_seconds: 36000
# Optional hard minimum spacing between adverts from same pubkey
# Set 0 to disable (recommended - mesh retransmissions are normal in active networks)
min_interval_seconds: 0
# Penalty box for repeat advert limit violations (per pubkey)
advert_penalty_box:
# Master switch for escalating cooldowns
enabled: false
# Number of violations within decay window before cooldown starts
violation_threshold: 2
# Reset violation count if pubkey stays quiet for this long
violation_decay_seconds: 43200
# First penalty duration in seconds
base_penalty_seconds: 21600
# Exponential growth factor for repeated violations
penalty_multiplier: 2.0
# Maximum penalty duration cap
max_penalty_seconds: 86400
# Adaptive rate limiting based on mesh activity
# Rate limits scale with mesh busyness: quiet mesh = lenient, busy mesh = strict
advert_adaptive:
# Master switch for adaptive scaling
enabled: false
# EWMA smoothing factor (0.0-1.0, higher = faster response)
ewma_alpha: 0.1
# Seconds without metrics change before tier change takes effect (hysteresis)
hysteresis_seconds: 300
# Tier thresholds based on adverts per minute EWMA
thresholds:
quiet_max: 0.05 # Below this = QUIET tier (no limiting)
normal_max: 0.20 # Below this = NORMAL tier (1x limits)
busy_max: 0.50 # Below this = BUSY tier (0.5x capacity)
# Above busy_max = CONGESTED tier (0.25x capacity)
# Login security for the repeater identity. Each room server has its own
# passwords and max_clients in its identity's settings (default 50).
security:
# Maximum ACL entries for the repeater (firmware MAX_CLIENTS is 32).
# Admins are kept across restarts, and only a new admin login or
# setperm evicts one (the least recently seen), so a small table fills
# up with them and then refuses other newcomers.
max_clients: 32
# Admin password for full access
admin_password: "admin123"
# Guest password for limited access. Left empty, a blank password logs
# in as a guest, as in firmware.
guest_password: "guest123"
# Allow a blank-password guest login even when guest_password is set
allow_read_only: false
# JWT secret key for signing tokens (auto-generated if not provided)
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
jwt_secret: ""
# JWT token expiry time in minutes (default: 60 minutes / 1 hour)
# Controls how long users stay logged in before needing to re-authenticate
jwt_expiry_minutes: 60
# Policy engine configuration file.
# Relative paths are resolved from this config file directory.
policy:
policy_file: "policy.yaml"
metrics:
# Store historical metrics in RRDtool when available.
# When disabled, chart APIs calculate metrics from repeater.db.
rrd_enabled: true
# Local GPS receiver. When enabled, the daemon reads NMEA sentences from the
# configured source and exposes parsed data at /api/gps.
gps:
enabled: false
# ---------------------------------------------------------------------------
# Source
# ---------------------------------------------------------------------------
# Source type:
# serial = read directly from an attached GPS module
# file = read NMEA lines from source_path (useful for gpsd/sidecar bridges)
# modem_http = poll a modem's generic HTTP /api/stats GPS JSON
source: serial
# Serial source settings (used when source: serial)
device: "/dev/serial0"
baud_rate: 9600
read_timeout_seconds: 1.0
reconnect_interval_seconds: 5.0
# File source settings (used when source: file)
# The file may contain raw NMEA lines or JSON with a "sentences" list /
# "last_sentence" field.
source_path: "/var/lib/openhop_repeater/gps_nmea.txt"
poll_interval_seconds: 2.0
# Modem HTTP source settings (used when source: modem_http)
# modem's HTTP API; later it can point at another board such as Heltec V4
# if that modem exposes the same GPS JSON shape.
host: ""
port: 80
endpoint: "/api/stats"
scheme: "http"
username: "admin"
password: null
# ---------------------------------------------------------------------------
# Location behaviour
# Three independent controls - read the comments carefully, they do
# different things:
#
# api_fallback_to_config_location - what the API *displays* before a fix
# advertise_gps_location - what the mesh *advertises* in adverts
# persist_gps_fix_to_config - whether the fix is *written* to config
# ---------------------------------------------------------------------------
# API display: while GPS has no valid fix, show repeater.latitude/longitude
# from config in the /api/gps effective position instead of null/0,0.
# The default 0,0 repeater location is treated as unset (no fallback shown).
# Has no effect on mesh adverts or config persistence.
api_fallback_to_config_location: true
# Mesh adverts: use GPS coordinates in repeater-originated location fields
# (flood adverts, etc.). When false, repeater.latitude/longitude from config
# are always used for outgoing mesh packets.
advertise_gps_location: false
# Config persistence: write a valid GPS fix back into repeater.latitude/
# repeater.longitude so adverts location details follow the
# receiver across restarts. Updates are throttled to avoid rewriting config
# on every NMEA sentence. location_precision_digits is applied before saving.
persist_gps_fix_to_config: false
persist_gps_fix_interval_seconds: 600.0
# Optional privacy/obfuscation: round coordinates to this many decimal places
# before they are used for advertising or persisted to config (0–8).
# Leave null for full precision. Affects both advertise_gps_location and
# persist_gps_fix_to_config.
location_precision_digits: null
# ---------------------------------------------------------------------------
# Diagnostics
# ---------------------------------------------------------------------------
stale_after_seconds: 10.0
retain_sentences: 25
validate_checksum: true
require_checksum: false
# ---------------------------------------------------------------------------
# Time sync
# ---------------------------------------------------------------------------
# Automatically set the Linux system clock from GPS UTC time once the
# receiver has a valid non-stale fix. The systemd service grants
# CAP_SYS_TIME for this.
time_sync_enabled: true
time_sync_interval_seconds: 3600.0
time_sync_min_offset_seconds: 1.0
time_sync_min_valid_year: 2020
# Sensor plug-ins (polled in background and exposed under /api/stats -> sensors)
sensors:
# Master switch for sensor subsystem
enabled: true
# Poll interval for all configured sensors
poll_interval_seconds: 10.0
# If true, missing Python packages for a sensor may be installed at runtime
# using "python -m pip install ...".
auto_install_packages: true
# Sensor definitions. Add one item per sensor instance.
definitions:
# Built-in sensor: host CPU/memory/disk/network stats
- type: hardware_stats
name: system-health
enabled: true
# Read-only Victron VE.Direct USB telemetry; see docs/victron_vedirect.md.
# Standard dependency checks at initialization; auto-install disabled below.
# Stop cat/other serial readers before enabling. Unique name per device.
# - type: victron_vedirect
# name: solar-charger
# enabled: true
# auto_install_packages: false
# settings:
# serial_device: "/dev/serial/by-id/REPLACE_WITH_YOUR_ADAPTER"
# read_timeout_seconds: 1.5 # 0.05–2.0; each poll opens/reads/closes
# Example INA219 sensor (commented out by default)
# - type: ina219
# name: power-monitor
# enabled: true
# auto_install_packages: true
# settings:
# i2c_address: 64 # 0x40 in decimal
# bus_number: 1 # I2C bus number (1 for Raspberry Pi default)
# max_expected_amps: 2.0
# shunt_ohms: 0.1
# Example ENS210 temperature/humidity sensor (commented out by default)
# - type: ens210
# name: ambient
# enabled: true
# auto_install_packages: true
# settings:
# i2c_address: 0x43 # Default ENS210 address
# bus_number: 1 # I2C bus number (1 for Raspberry Pi default)
# read_timeout_seconds: 1.0 # Max seconds to wait for valid data (polls every 50 ms)
# Example SHTC3 Ambient
# - type: shtc3
# name: ambient
# enabled: true
# auto_install_packages: false
# settings:
# i2c_address: 0x70
# bus_number: 1
# Example BME280 temperature/pressure/humidity sensor (commented out by default)
# - type: bme280
# name: ambient
# enabled: true
# auto_install_packages: false
# settings:
# i2c_address: 0x76 # 0x77 when SDO is tied high
# bus_number: 1 # I2C bus number (1 for Raspberry Pi default)
# Example Waveshare UPS D Hat
# - type: waveshare_ups_d
# name: battery
# enabled: true
# auto_install_packages: false
# settings:
# i2c_address: 0x43
# bus_number: 1
# Example Waveshare UPS E Hat
# - type: waveshare_ups_e
# name: "battery"
# enabled: true
# auto_install_packages: false
# settings:
# i2c_address: 0x2D # Waveshare UPS HAT (E) fixed address
# bus_number: 1 # I2C bus number (1 for Raspberry Pi default)
# low_cell_mv: 3150 # Per-cell warning threshold in mV
# Example openHop Modem sensor. This reads a networked openHop Modem's
# HTTP /api/stats JSON API for modem diagnostics, including GPS fields
# when the modem exposes them. If password is set to the same value as
# modem_tcp.token, the repeater can use one shared modem secret.
# - type: openhop_modem
# name: modem
# enabled: true
# settings:
# host: "pymc-modem.local" # or the modem LAN IP
# port: 80
# endpoint: "/api/stats"
# username: "admin"
# password: "" # modem HTTP password; may match modem_tcp.token
# poll_interval_seconds: 60.0 # avoid contending with modem traffic
# timeout_seconds: 2.0
# discovery_include_paths: "" # optional reviewed exact JSON Pointers, comma-separated
# discovery_exclude_paths: "" # e.g. "/environment/experimental"
# Numeric/boolean stats under approved measurement sections are discovered
# automatically; metadata/values are API diagnostics only, not RF or RRD history.
# Mesh Network Configuration
mesh:
# Unscoped flood policy - controls whether the repeater allows or denies unscoped flooding
# true = allow unscoped flooding, false = deny flooding globally
unscoped_flood_allow: true
# Legacy alias for unscoped flood policy. Keep in sync with unscoped_flood_allow.
# true = allow unscoped flooding, false = deny
global_flood_allow: true
# Path hash mode for flood packets (0-hop): per-hop hash size in path encoding
# 0 = 1-byte hashes (legacy), 1 = 2-byte, 2 = 3-byte. Must match mesh convention.
# Affects originated adverts and any other flood packets sent by the repeater.
path_hash_mode: 0
# Flood loop detection mode
# off = disabled, minimal = allow up to 3 self-hashes, moderate = allow up to 1, strict = allow 0
loop_detect: minimal
# Default flood scope for locally-originated flood adverts.
# Use null or "<null>" via CLI command "region default <null>" to clear.
default_region: null
# Multiple Identity Configuration (Optional)
# Define additional identities for the repeater to manage
# Each identity operates independently with its own key pair and configuration
identities:
# Room Server Identities
# Each room server acts as a separate logical node on the mesh
room_servers:
# Example room server configuration (commented out by default)
# - name: "TestBBS"
# identity_key: "your_room_identity_key_hex_here"
# type: "room_server"
#
# # Room-specific settings
# settings:
# node_name: "Test BBS Room"
# latitude: 0.0
# longitude: 0.0
# flood_advert_interval_hours: 6
# direct_advert_interval_hours: 2
# admin_password: "room_admin_password"
# guest_password: "room_guest_password"
# Add more room servers as needed
# - name: "SocialHub"
# identity_key: "another_identity_key_hex_here"
# type: "room_server"
# settings:
# node_name: "Social Hub"
# latitude: 0.0
# longitude: 0.0
# flood_advert_interval_hours: 12
# direct_advert_interval_hours: 0
# admin_password: "social_admin_123"
# guest_password: "social_guest_123"
# Companion Identities
# Each companion exposes the MeshCore frame protocol over TCP for standard clients.
# One TCP client per companion at a time. Clients connect to repeater-ip:tcp_port.
companions:
# - name: "RepeaterCompanion"
# identity_key: "your_companion_identity_key_hex_here"
# settings:
# node_name: "RepeaterCompanion"
# tcp_port: 5000
# bind_address: "0.0.0.0"
# tcp_timeout: 120 # seconds; default 120 when omitted; 0 = disable (no timeout)
# - name: "BotCompanion"
# identity_key: "another_companion_identity_key_hex"
# settings:
# node_name: "meshcore-bot"
# tcp_port: 5001
# tcp_timeout: 120 # seconds; default 120 when omitted; 0 = disable (no timeout)
# ---------------------------------------------------------------------------
# Multi-radio / LoRa backhaul (optional)
# ---------------------------------------------------------------------------
# Leave ``radios:`` unset to keep the legacy single-radio config below.
# When present, each entry defines one physical radio.
#
# Top-level radio/hardware sections act as defaults for each entry.
# Mapping sections under radios[] overlay those defaults key-by-key:
# omitted values are inherited, while values explicitly present in an
# entry override the top-level setting.
#
# This allows board-specific settings such as use_dio3_tcxo,
# use_dio2_rf, dio3_tcxo_voltage, en_pin/en_pins, GPIO settings, etc.
# to remain in the top-level hardware configuration unless a particular
# radio needs a different value.
#
# radio_type may also be overridden per entry, so different transport
# types can be mixed in the same fabric. Different entries may use
# different frequency / SF / modem type.
#
# fabric:
# default_radio: local # TX default (first entry if omitted)
# tx_mode: bridge # default | sticky | bridge
# # default: always TX default_radio
# # sticky: TX on the radio that received this packet (same-band reply)
# # bridge: TX on the *other* radio (dual-freq LoRa backhaul)
# # RX local -> TX link, RX link -> TX local
# #
# # sticky and bridge route by the radio the packet in hand arrived on.
# # Packets this node originates arrived nowhere, so they go out on
# # default_radio (or on every radio, with origin_tx: all below).
# #
# # Against an openhop-core older than the release that passes the ingress
# # radio down to the TX policy, these modes fall back to what they did
# # before: they read whichever radio received most recently, at the moment
# # of the send. That is a different packet's arrival on a busy node, and
# # whichever radio last heard anything for locally originated traffic. The
# # duty-cycle gate compensates by holding every radio the node could pick
# # to its own budget, so nothing transmits past a limit either way.
#
# # Optional RF relay fan-out. Requires tx_mode: bridge and exactly two radios.
# # false: RX local -> TX link, RX link -> TX local
# # true: RX local -> TX link + local, RX link -> TX local + link
# # The bridge (other) radio is always attempted first.
# repeat_on_ingress: false
#
# # Packets this node originates (companions, repeater identity, room servers,
# # protocol/discovery replies):
# # default: existing Fabric TX selection (default_radio / tx_mode)
# # all: transmit once on every Fabric radio, default_radio first
# # (requires exactly two radios)
# # Formerly local_tx_mode, which is still accepted.
# origin_tx: default
#
# # Fan-out never runs MeshCore forwarding twice: each packet is validated,
# # deduplicated and path-updated once, then the same bytes are sent through
# # each selected radio. Those physical sends are serialised, never simultaneous,
# # and each passes the duty-cycle gate on its own, so a later radio can be
# # refused after an earlier one sent. The packet counts as forwarded when at
# # least one radio transmits it; a directed companion message still waits for
# # one ACK, accepted from either radio.
#
# Dual CH341 USB adapters example (different USB bus/address per radio):
# radios:
# - id: local
# radio_type: sx1262_ch341
# radio: { frequency: 869618000, tx_power: 14, bandwidth: 62500, spreading_factor: 8, coding_rate: 8, preamble_length: 32 }
# sx1262: { bus_id: 0, cs_id: 0, cs_pin: 0, reset_pin: 1, busy_pin: 2, irq_pin: 3, txen_pin: -1, rxen_pin: -1 }
# ch341: { vid: 0x1A86, pid: 0x5512, bus: 1, address: 5 }
# - id: link
# radio_type: sx1262_ch341
# radio: { frequency: 864200000, tx_power: 22, bandwidth: 62500, spreading_factor: 11, coding_rate: 8, preamble_length: 32 }
# sx1262: { bus_id: 0, cs_id: 0, cs_pin: 0, reset_pin: 1, busy_pin: 2, irq_pin: 3, txen_pin: -1, rxen_pin: -1 }
# ch341: { vid: 0x1A86, pid: 0x5512, bus: 1, address: 8 }
# use_fabric: false # true wraps even a single radio in FabricRadio
#
# radios:
# - id: local
# radio_type: sx1262
# radio:
# frequency: 869618000
# tx_power: 14
# bandwidth: 62500
# spreading_factor: 8
# coding_rate: 8
# preamble_length: 32
# sx1262:
# bus_id: 0
# cs_id: 0
# cs_pin: 21
# reset_pin: 18
# busy_pin: 20
# irq_pin: 16
# txen_pin: -1
# rxen_pin: -1
# - id: link
# radio_type: sx1262
# radio:
# frequency: 864200000 # different freq = LoRa backhaul band
# tx_power: 22
# bandwidth: 62500
# spreading_factor: 11 # different SF ok
# coding_rate: 8
# preamble_length: 32
# sx1262:
# bus_id: 0
# cs_id: 1 # distinct CS / pins from local
# cs_pin: 7
# reset_pin: 5
# busy_pin: 6
# irq_pin: 12
# txen_pin: -1
# rxen_pin: -1
#
# Entries only need the values that differ. The sx1262: section further down
# holds board-wide properties such as TCXO / RF-switch control and enable
# pins; those are inherited by every entry that does not override them:
#
# radios:
# - id: local
# radio:
# frequency: 910525000
# sx1262:
# cs_id: 0
#
# - id: link
# radio:
# frequency: 914500000
# sx1262:
# cs_id: 1
# reset_pin: 27
# irq_pin: 18
#
# Default radio hardware type.
# In legacy single-radio mode this selects the physical radio directly.
# With radios: configured, entries inherit this value unless they specify
# their own radio_type.
# Supported:
# - sx1262 (Linux spidev + system GPIO)
# - sx1262_ch341 (CH341 USB-to-SPI + CH341 GPIO 0-7)
# - kiss (KISS-modem over a serial port; alias: kiss-modem)
# - modem_tcp (openHop Modem over Wi-Fi/Ethernet TCP)
# - modem_usb (openHop Modem over USB-CDC)
# - null/none (disable radio hardware; daemon starts without RF I/O)
radio_type: null
# CH341 USB-to-SPI adapter settings (only used when radio_type: sx1262_ch341)
# NOTE: VID/PID are integers. Hex is also accepted in YAML, e.g. 0x1A86.
# With two+ CH341 adapters, select each device by USB location and/or serial:
# bus / address - from `lsusb -t` or `python -c "from openhop_core.hardware.ch341 import CH341Async; print(CH341Async.list_devices())"`
# serial_number - USB iSerial if the adapter exposes one
# Single-adapter setups may omit bus/address/serial_number.
ch341:
vid: 6790 # 0x1A86
pid: 21778 # 0x5512
# bus: 1
# address: 5
# serial_number: "ABC123"
radio:
# Frequency in Hz (869.618 MHz for EU)
frequency: 869618000
# TX power in dBm
tx_power: 14
# Bandwidth in Hz (62500 = 62.5 kHz)
bandwidth: 62500
# LoRa spreading factor (7-12)
spreading_factor: 8
# Coding rate (5-8)
coding_rate: 8
# Preamble length in symbols
preamble_length: 32
# LoRa sync word, used by modem_tcp/modem_usb radios (the SX1262 driver
# ignores it). MeshCore uses 0x12; a modem on any other value hears nothing.
# radios[] entries inherit this value unless they set their own.
# sync_word: 0x12
# Use implicit header mode
implicit_header: false
# KISS modem (when radio_type: kiss). Requires openhop-core with KISS support.
# kiss:
# port: "/dev/ttyUSB0" # 99-openhop-modem.rules symlinks to /dev/openhop-modem.
# # Prefer that over /dev/serial/by-id/ in Docker: an
# # ESP32-S3 by-id name contains colons and Docker reads
# # those as its host:container separator.
# baud_rate: 9600
# # Optional KISS key-up / CSMA tuning, forwarded to the modem firmware.
# # Omit to keep the wrapper/firmware defaults. For a host-managed repeater the
# # engine already staggers retransmits, so the firmware's p-persistent CSMA
# # backoff is redundant — kiss_persistence: 255 transmits as soon as the channel
# # is clear (carrier-sense still prevents talking over a packet already on air).
# kiss_persistence: 255 # 0-255; p(tx when clear) = (value+1)/256. Firmware default 63.
# kiss_slottime_ms: 20 # CSMA backoff slot; unused at persistence 255. Firmware default 100.
# tx_delay_ms: 50 # key-up delay; LoRa needs ~none. Firmware default 500.
# # kiss_txtail_ms: 0 # tail after TX (rarely needed)
# # kiss_full_duplex: false # disable carrier-sense/CSMA entirely (not recommended)
# openHop Modem over Wi-Fi/Ethernet TCP (when radio_type: modem_tcp).
# Requires openhop-core with the TCPLoRaRadio driver
# modem_tcp:
# host: "openhop-modem.local" # modem hostname / mDNS name / LAN IP
# port: 5055 # firmware default
# token: "" # empty = no auth; set to match firmware NVS token
# connect_timeout: 5.0 # seconds; deferred-connect retries with backoff on failure
# lbt_enabled: true # Listen-Before-Talk via firmware CAD
# lbt_max_attempts: 5
# openHop Modem over USB-CDC (when radio_type: modem_usb).
# Requires openhop-core with the USBLoRaRadio driver
# modem_usb:
# port: "/dev/ttyACM0" # USB-CDC device; udev rule may symlink to /dev/lora-modem
# baudrate: 921600 # must match firmware monitor_speed
# lbt_enabled: true
# lbt_max_attempts: 5
# SX1262 Hardware Configuration
# NOTE:
# - When radio_type: sx1262, these pins are BCM GPIO numbers.
# - When radio_type: sx1262_ch341, these pins are CH341 GPIO numbers (0-7).
sx1262:
# SPI bus and chip select
# NOTE: For CH341 these are not used but are still required parameters.
bus_id: 0
cs_id: 0
# GPIO pins
cs_pin: 21
reset_pin: 18
busy_pin: 20
irq_pin: 16
# TX/RX enable pins (-1 to disable)
txen_pin: -1
rxen_pin: -1
# Optional radio power-enable pin(s) driven HIGH during init
en_pin: -1
# en_pins: [26, 23]
# LED pins for TX/RX indication (-1 to disable)
txled_pin: -1
rxled_pin: -1
use_dio3_tcxo: false
dio3_tcxo_voltage: 1.8
use_dio2_rf: false
# Waveshare hardware flag
is_waveshare: false
delays:
# Flood TX delay: airtime multiplier, NOT seconds. The actual pre-transmit
# delay is a random value in [0, 5 * packet_airtime * factor] seconds.
tx_delay_factor: 1.0
# Direct TX delay: same random 0-5*airtime*factor window, usually lower so
# directed traffic clears faster.
direct_tx_delay_factor: 0.5
duty_cycle:
# Enable/disable duty cycle enforcement
# Set to false to disable airtime limits
enforcement_enabled: false
# Maximum airtime per minute in milliseconds
max_airtime_per_minute: 3600
# MULTI-RADIO NODES (see `radios:` above) read this as a limit PER CHANNEL,
# which is what a duty cycle is. Each radio is metered against its own budget
# at its own modulation, so a bridge on two frequencies may transmit up to
# this much on each of them - more total airtime than the same node allowed
# before, and correct, because the two bands are separate allowances. Two
# radios tuned to the same frequency and bandwidth share one budget, since
# they contend for the same spectrum.
#
# Bands differ: 868.0-868.6 MHz allows 1% where 869.4-869.65 allows 10%. Give
# a radio its own limit by putting a duty_cycle block in its radios[] entry:
#
# radios:
# - id: local
# duty_cycle:
# max_airtime_per_minute: 600 # 1% band
# - id: link
# duty_cycle:
# max_airtime_per_minute: 6000 # 10% band
#
# To go back to one budget shared across every radio - more conservative than
# the regulations require, and what this node did before per-channel metering:
# shared_budget: true
# Storage Configuration
storage:
# Directory for persistent storage files (SQLite, RRD).
# Use a writable path for local/dev (e.g. "./var/openhop_repeater" or "~/var/openhop_repeater").
storage_dir: "/var/lib/openhop_repeater"
# Data retention settings
retention:
# Clean up SQLite records older than this many days
sqlite_cleanup_days: 31
# Companion event journal / soft-consumed message history retention.
# Independent of sqlite_cleanup_days so packet RF history and companion
# sync history can be sized separately. Defaults to 31 when omitted.
companion_events_days: 31
# RRD archives are managed automatically:
# - 1 minute resolution for 1 week
# - 5 minute resolution for 1 month
# - 1 hour resolution for 1 year
# Plugin manager (optional external process lifecycle)
# plugins:
# # Master switch for the openhop-plugin-manager service (default: enabled when omitted)
# enabled: true
# # Install root (default: {storage.storage_dir}/plugins)
# # root: "/var/lib/openhop_repeater/plugins"
# # Unix domain socket used by Repeater HTTP → plugin manager IPC
# # socket: "/var/lib/openhop_repeater/plugin-manager.sock"
# Optional override for the curated catalogue hosted on openHop's R2 endpoint
# catalogue_url: "https://repeater-plugins.openhop.dev/catalogue.json"
mqtt_brokers:
iata_code: "Test" # e.g., "SFO", "LHR", "Test"
status_interval: 300 # How often a status message is sent (in seconds)
owner: ""
email: ""
brokers: []
# Periodic neighbours publication (the "neighbors" topic).
#
# Each cycle runs a zero-hop node-discovery broadcast to refresh the neighbour
# table, then asks each zero-hop repeater for its region scopes with an
# anonymous regions request, then publishes the assembled table. Scope queries
# are issued ONE AT A TIME - firing them as a burst makes the responses collide.
#
# Off unless a broker opts in below with `neighbors: true`; `enabled` here is a
# master kill switch for the whole feature.
# neighbors:
# enabled: true # master on/off (default true)
# interval_hours: 24 # 12-336, how often a cycle runs
# discovery_timeout_seconds: 60 # node-discovery collection window
# scope_response_timeout_seconds: 0 # 0 = size the window from radio settings
# max_neighbors: 32 # cap on neighbours queried per cycle
# max_neighbor_age_seconds: 86400 # ignore zero-hop rows older than this
# max_sweep_seconds: 900 # give up on a cycle after this long
# duty_cycle_abort_seconds: 30 # abandon the sweep when the airtime
# # backlog exceeds this
#
# This must be a block, not a boolean - `neighbors: true` here is ignored (with
# a startup warning) because the on/off switch that matters is the per-broker
# `neighbors: true` flag documented in the broker schema below.
#
# `discover.scopes` over the mesh CLI runs one cycle immediately.
# Below is the broker object schema:
# enabled: true|false # Enable this specific mqtt broker
# name: "" # Internal name for this broker
# host: "" # hostname or ip of mqtt endpoints
# port: # Typically 443 for websocket endpoints or 1883 for tcp
# transport: "tcp" or "websockets"
# audience: "" # For JWT auth'd endpoints, this is usually the host unless always stated by endpoint owners
# use_jwt_auth: true|false # Does this endpoint require JWT auth. Mutually Exclusive with Username & Password fields
# username: "" # Username for basic auth. If empty or missing, uses anonymous access
# password: "" # Password for basic auth. Required if username is set
# format: meshcoretomqtt|letsmesh|waev|mqtt
# meshcoretomqtt - canonical open-source MC2MQTT topic structure
# letsmesh, waev - MC2MQTT family flavors (same topic structure, network-specific identity)
# mqtt - legacy openhop-repeater local-broker convention (custom topic, singular 'packet')
# retain_status: true|false # Sets MQTT "retain" on status messages so they remain on the broker when disconnected. Also enforces a QOS of 1 (guaranteed delivery)
# neighbors: true|false # Publish the periodic neighbours table to this broker's "neighbors" topic (default false). Only enable for brokers that expect the topic - some reject unknown topics and close the connection.
# tls:
# enabled: true|false # Enable TLS. If the endpoint's certificate is self-signed, the Root CA should be added to the OS's certificate store.
# insecure: true|false # Validate TLS certificates
# Multi-radio nodes (see `radios:` above) additionally publish:
# - `rx_radio_id` and `tx_radio_ids` on each packet: the radio that heard
# it, and every radio that retransmitted it (a bridge repeating on both
# sides lists both). `tx_radio_ids` is always a list; it is absent when
# the packet was not repeated.
# - `radios` in the status message: `[{id, radio, tx_power}]` mapping each
# radio id to its `MHz,kHz,SF,CR` settings and its power in dBm, so a
# consumer can resolve those ids to frequencies. `tx_power` is omitted
# when the config does not set it and the radio type has no default. Ids
# are unique only within a node - key on (origin_id, id). The top-level
# `radio` field then reports the default radio.
# Single-radio nodes publish neither, exactly as before.
# Block specific packet types from being published to the MQTT endpoint
# If not specified or empty list, all types are published
# Available types: REQ, RESPONSE, TXT_MSG, ACK, ADVERT, GRP_TXT,
# GRP_DATA, ANON_REQ, PATH, TRACE, RAW_CUSTOM
# disallowed_packet_types: []
# - REQ # Don't publish requests
# - RESPONSE # Don't publish responses
# - TXT_MSG # Don't publish text messages
# - ACK # Don't publish acknowledgments
# - ADVERT # Don't publish advertisements
# - GRP_TXT # Don't publish group text messages
# - GRP_DATA # Don't publish group data
# - ANON_REQ # Don't publish anonymous requests
# - PATH # Don't publish path packets
# - TRACE # Don't publish trace packets
# - RAW_CUSTOM # Don't publish custom raw packets
# Bundled network presets (recommended). Endpoints ship with the package,
# so URL/audience updates arrive via `pip install -U`. Available presets:
# waev, letsmesh.
#
# brokers:
# - preset: waev
# - preset: letsmesh
#
# Override a preset broker by listing it again with the same name AFTER
# the preset entry. Later entries win on name collision.
#
# brokers:
# - preset: waev
# - name: Waev
# host: mqtt-a.waev.app
#
# Mix presets with fully custom brokers in the same list:
#
# brokers:
# - preset: waev
# - name: my-lan-mqtt
# enabled: true
# host: mqtt.lan
# port: 1883
# transport: tcp
# format: mqtt
# username: repeater
# password: secret
# pyMC_Glass control-plane integration (optional)
glass:
# Enable repeater -> pyMC_Glass /inform loop
enabled: false
# Base URL of Glass backend
# Example local dev: "http://localhost:8080"
# Example production: "https://glass.example.com"
base_url: "http://localhost:8080"
# Inform interval in seconds (used as initial/default interval;
# backend may override via noop.interval response)
inform_interval_seconds: 30
# HTTP timeout per inform request
request_timeout_seconds: 10
# Verify TLS certificates when using HTTPS
verify_tls: true
# Optional bearer token for future authenticated inform endpoints
api_token: ""
# Where cert_renewal payloads are written
cert_store_dir: "/etc/openhop_repeater/glass"
logging:
# Log level: TRACE, DEBUG, INFO, WARNING, ERROR
level: INFO
# Log format
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# HTTP server configuration (web UI + API)
http:
# Enable/disable the embedded HTTP server at runtime.
# Changes are applied live via ConfigManager (no daemon restart required).
enabled: true
# Listen address and port for the web UI/API server.
host: "0.0.0.0"
port: 8000
# CherryPy worker/thread tuning.
# thread_pool is the initial worker count (minimum enforced by server: 2).
thread_pool: 8
# thread_pool_max is the max worker count (always >= thread_pool).
thread_pool_max: 16
# Socket tuning.
# socket_timeout closes idle sockets so worker threads can be reused (min: 15 seconds).
socket_timeout: 65
# socket_queue_size controls pending TCP connection backlog (min: 10).
socket_queue_size: 100
# Web interface configuration
web:
# Enable Cross-Origin Resource Sharing (CORS) headers
# Allows web frontends from different origins to access the API
cors_enabled: false
# Optional CARTO Basemaps API key for theme-matched light and dark map tiles.
# The key is sent to CARTO by each browser; do not treat it as a server-side secret.
# When unset, the web interface uses light OpenStreetMap tiles in both themes.
carto_api_key: ""
# Custom path to web frontend files (optional)
# If not specified, uses the default built-in path
# Example: /opt/custom-web-ui or /home/user/my-frontend
# web_path: null