mirror of
https://github.com/openhop-dev/openhop_repeater.git
synced 2026-10-23 13:32:50 +00:00
949 lines
37 KiB
Plaintext
949 lines
37 KiB
Plaintext
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
|