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
    #     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 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



# 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)
#   - kiss          (KISS-modem over a serial port; alias: kiss-modem)
#   - pymc_tcp      (pymc_usb firmware modem over Wi-Fi/TCP)
#   - pymc_usb      (pymc_usb firmware 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.
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

  # 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

# pymc_usb firmware modem over Wi-Fi/TCP (when radio_type: pymc_tcp).
# Requires pyMC_core with the TCPLoRaRadio driver
# pymc_tcp:
#   host: "pymc-3e2834.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

# pymc_usb firmware modem over USB-CDC (when radio_type: pymc_usb).
# Requires pyMC_core with the USBLoRaRadio driver
# pymc_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:
  # 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
