mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-06-11 08:44:46 +02:00
9bfe1259da
Adds support for the ENS210 relative humidity and temperature sensor as a new plug-in under repeater/sensors/ens210.py. Also adds a commented configuration example to config.yaml.example and a contributor guide at docs/adding_sensors.md explaining how to add further sensor plug-ins. ## Implementation notes ### Why smbus2 instead of an Adafruit/CircuitPython library The ENS210 has no maintained Adafruit CircuitPython driver. The available third-party options are either unmaintained or bring in the full Blinka/CircuitPython hardware-abstraction stack as a dependency. smbus2 is a thin, widely-packaged wrapper around the Linux i2c-dev kernel interface that is already present on Raspberry Pi OS and most Debian-based systems. It has no transitive dependencies and adds no abstraction cost. The ENS210 protocol is simple enough that direct register access is preferable: two writes to start a measurement (REG_SENS_RUN + REG_SENS_START) and two three-byte block reads to retrieve temperature and humidity. The status/validity bit is checked inline rather than relying on a library to surface it. There is no value a higher-level driver would add here. ### Read strategy A fixed post-trigger delay is unreliable — the sensor datasheet quotes ~130 ms typical conversion time but the actual ready time varies. The implementation instead polls the data-valid status bit (bit 1 of the third byte in each register block) every 50 ms for up to read_timeout_seconds (default 1.0 s), breaking as soon as both T and H report valid data. This is the same approach used in the validated reference script. The I2C bus is opened and closed on every read rather than kept open across poll cycles. Keeping a persistent SMBus handle caused subsequent reads to time out, consistent with the Linux i2c-dev file descriptor accumulating state between transactions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
536 lines
19 KiB
Plaintext
536 lines
19 KiB
Plaintext
# Default Repeater Configuration
|
||
# radio_type: sx1262 | kiss (use kiss for serial KISS TNC modem)
|
||
radio_type: sx1262
|
||
|
||
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
|
||
|
||
# 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
|
||
|
||
# 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
|
||
|
||
# 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)
|
||
|
||
# Security settings for login/authentication (shared across all identities)
|
||
security:
|
||
# Maximum number of authenticated clients (across all identities)
|
||
max_clients: 1
|
||
|
||
# Admin password for full access
|
||
admin_password: "admin123"
|
||
|
||
# Guest password for limited access
|
||
guest_password: "guest123"
|
||
|
||
# Allow read-only access for clients without password/not in ACL
|
||
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
|
||
|
||
# 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)
|
||
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/pymc_repeater/gps_nmea.txt"
|
||
poll_interval_seconds: 2.0
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 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
|
||
|
||
# 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
|
||
# 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: 67 # 0x43 in decimal (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)
|
||
|
||
# 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
|
||
|
||
# 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
|
||
|
||
# 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
|
||
# 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
|
||
# 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)
|
||
|
||
# Radio hardware type
|
||
# Supported:
|
||
# - sx1262 (Linux spidev + system GPIO)
|
||
# - sx1262_ch341 (CH341 USB-to-SPI + CH341 GPIO 0-7)
|
||
radio_type: sx1262
|
||
|
||
# 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.
|
||
ch341:
|
||
vid: 6790 # 0x1A86
|
||
pid: 21778 # 0x5512
|
||
|
||
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: 17
|
||
|
||
# Sync word (LoRa network ID)
|
||
sync_word: 13380
|
||
|
||
# Use implicit header mode
|
||
implicit_header: false
|
||
|
||
# KISS modem (when radio_type: kiss). Requires pyMC_core with KISS support.
|
||
# kiss:
|
||
# port: "/dev/ttyUSB0"
|
||
# baud_rate: 9600
|
||
|
||
# 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:
|
||
# TX delay factor for flood mode (multiplier)
|
||
tx_delay_factor: 1.0
|
||
|
||
# TX delay factor for direct mode (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
|
||
|
||
# Storage Configuration
|
||
storage:
|
||
# Directory for persistent storage files (SQLite, RRD).
|
||
# Use a writable path for local/dev (e.g. "./var/pymc_repeater" or "~/var/pymc_repeater").
|
||
storage_dir: "/var/lib/pymc_repeater"
|
||
|
||
# Data retention settings
|
||
retention:
|
||
# Clean up SQLite records older than this many days
|
||
sqlite_cleanup_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
|
||
|
||
|
||
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: []
|
||
|
||
# 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 pyMC_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)
|
||
# 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
|
||
|
||
# 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-b
|
||
# enabled: false
|
||
#
|
||
# 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/pymc_repeater/glass"
|
||
|
||
logging:
|
||
# Log level: DEBUG, INFO, WARNING, ERROR
|
||
level: INFO
|
||
|
||
# Log format
|
||
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||
|
||
# Web interface configuration
|
||
web:
|
||
# Enable Cross-Origin Resource Sharing (CORS) headers
|
||
# Allows web frontends from different origins to access the API
|
||
cors_enabled: false
|
||
|
||
# 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
|