mirror of
https://github.com/rightup/pyMC_Repeater.git
synced 2026-05-08 06:24:27 +02:00
refactor: update GPS configuration parameters and improve documentation
This commit is contained in:
+55
-32
@@ -126,64 +126,87 @@ repeater:
|
||||
# Controls how long users stay logged in before needing to re-authenticate
|
||||
jwt_expiry_minutes: 60
|
||||
|
||||
# Local GPS diagnostics. When enabled, the daemon reads NMEA sentences from the
|
||||
# configured source and exposes parsed attributes at /api/gps.
|
||||
# Local GPS receiver. When enabled, the daemon reads NMEA sentences from the
|
||||
# configured source and exposes parsed data at /api/gps.
|
||||
gps:
|
||||
enabled: false
|
||||
|
||||
# Use repeater.latitude/repeater.longitude as the displayed receiver location
|
||||
# until the GPS has a valid non-stale fix. This prevents early no-fix GPS
|
||||
# estimates from replacing the configured site location in API clients.
|
||||
# The default 0,0 repeater location is treated as unset.
|
||||
use_manual_location_until_fix: true
|
||||
|
||||
# Opt-in: use GPS coordinates for repeater-originated location fields
|
||||
# (for example flood adverts). When disabled, repeater.latitude/longitude
|
||||
# from config are always used.
|
||||
use_gps_for_repeater_location: false
|
||||
|
||||
# Optional privacy/obfuscation control when GPS is used for repeater
|
||||
# location. If set, coordinates are rounded to this many decimal places
|
||||
# before use (0-8). Leave null for full reported precision.
|
||||
repeater_location_precision_digits: null
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Source type:
|
||||
# serial = read directly from an attached GPS module
|
||||
# file = read NMEA lines from source_path, useful for gpsd/sidecar bridges
|
||||
# file = read NMEA lines from source_path (useful for gpsd/sidecar bridges)
|
||||
source: serial
|
||||
|
||||
# Serial source settings
|
||||
# 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. The file can contain raw NMEA lines or JSON with a
|
||||
# "sentences" list / "last_sentence" field.
|
||||
# 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
|
||||
|
||||
# Diagnostics behavior
|
||||
# ---------------------------------------------------------------------------
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 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
|
||||
|
||||
# Opt-in: feed a valid GPS fix back into repeater.latitude/repeater.longitude
|
||||
# so pyMC Repeater adverts and pyMC Console location details follow the
|
||||
# receiver. repeater_location_precision_digits is applied before saving so
|
||||
# deployments can fuzz the stored/advertised location. Updates are throttled
|
||||
# to avoid rewriting config on every NMEA sentence.
|
||||
update_repeater_location_from_fix: false
|
||||
location_update_interval_seconds: 600.0
|
||||
|
||||
# Mesh Network Configuration
|
||||
mesh:
|
||||
# Unscoped flood policy - controls whether the repeater allows or denies unscoped flooding
|
||||
|
||||
+5
-5
@@ -82,9 +82,9 @@ def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
|
||||
if "gps" not in config:
|
||||
config["gps"] = {
|
||||
"enabled": False,
|
||||
"use_manual_location_until_fix": True,
|
||||
"use_gps_for_repeater_location": False,
|
||||
"repeater_location_precision_digits": None,
|
||||
"api_fallback_to_config_location": True,
|
||||
"advertise_gps_location": False,
|
||||
"location_precision_digits": None,
|
||||
"source": "serial",
|
||||
"device": "/dev/serial0",
|
||||
"baud_rate": 9600,
|
||||
@@ -98,8 +98,8 @@ def load_config(config_path: Optional[str] = None) -> Dict[str, Any]:
|
||||
"time_sync_interval_seconds": 3600.0,
|
||||
"time_sync_min_offset_seconds": 1.0,
|
||||
"time_sync_min_valid_year": 2020,
|
||||
"update_repeater_location_from_fix": False,
|
||||
"location_update_interval_seconds": 600.0,
|
||||
"persist_gps_fix_to_config": False,
|
||||
"persist_gps_fix_interval_seconds": 600.0,
|
||||
}
|
||||
|
||||
# Ensure repeater.security exists with defaults for upgrades from older configs
|
||||
|
||||
@@ -591,14 +591,14 @@ class GPSService:
|
||||
repeater_config = config.get("repeater", {}) if isinstance(config, dict) else {}
|
||||
self.config = gps_config
|
||||
self.enabled = bool(gps_config.get("enabled", False))
|
||||
self.use_manual_location_until_fix = bool(
|
||||
gps_config.get("use_manual_location_until_fix", True)
|
||||
self.api_fallback_to_config_location = bool(
|
||||
gps_config.get("api_fallback_to_config_location", True)
|
||||
)
|
||||
self.use_gps_for_repeater_location = bool(
|
||||
gps_config.get("use_gps_for_repeater_location", False)
|
||||
self.advertise_gps_location = bool(
|
||||
gps_config.get("advertise_gps_location", False)
|
||||
)
|
||||
self.repeater_location_precision_digits = _normalize_precision_digits(
|
||||
gps_config.get("repeater_location_precision_digits")
|
||||
self.location_precision_digits = _normalize_precision_digits(
|
||||
gps_config.get("location_precision_digits")
|
||||
)
|
||||
self.source = str(gps_config.get("source", "serial")).lower()
|
||||
self.device = gps_config.get("device", "/dev/serial0")
|
||||
@@ -618,24 +618,24 @@ class GPSService:
|
||||
self.time_sync_min_valid_year = int(gps_config.get("time_sync_min_valid_year", 2020))
|
||||
self._clock_setter = clock_setter or _set_system_clock_from_datetime
|
||||
self._time_provider = time_provider or time.time
|
||||
self.location_update_enabled = bool(
|
||||
gps_config.get("update_repeater_location_from_fix", False)
|
||||
self.persist_gps_fix_enabled = bool(
|
||||
gps_config.get("persist_gps_fix_to_config", False)
|
||||
)
|
||||
self.location_update_interval_seconds = max(
|
||||
1.0, float(gps_config.get("location_update_interval_seconds", 600.0))
|
||||
self.persist_gps_fix_interval_seconds = max(
|
||||
1.0, float(gps_config.get("persist_gps_fix_interval_seconds", 600.0))
|
||||
)
|
||||
self._location_update_callback = location_update_callback
|
||||
self._location_update_lock = threading.RLock()
|
||||
self._last_location_update_monotonic: Optional[float] = None
|
||||
self._location_update_status: Dict[str, Any] = {
|
||||
"enabled": self.location_update_enabled,
|
||||
"state": "disabled" if not self.location_update_enabled else "waiting_for_fix",
|
||||
"enabled": self.persist_gps_fix_enabled,
|
||||
"state": "disabled" if not self.persist_gps_fix_enabled else "waiting_for_fix",
|
||||
"last_attempt": None,
|
||||
"last_success": None,
|
||||
"last_error": None,
|
||||
"last_latitude": None,
|
||||
"last_longitude": None,
|
||||
"interval_seconds": self.location_update_interval_seconds,
|
||||
"interval_seconds": self.persist_gps_fix_interval_seconds,
|
||||
}
|
||||
self._time_sync_lock = threading.RLock()
|
||||
self._last_time_sync_monotonic: Optional[float] = None
|
||||
@@ -718,9 +718,9 @@ class GPSService:
|
||||
def _apply_precision(self, value: Optional[float]) -> Optional[float]:
|
||||
if value is None:
|
||||
return None
|
||||
if self.repeater_location_precision_digits is None:
|
||||
if self.location_precision_digits is None:
|
||||
return value
|
||||
return round(value, self.repeater_location_precision_digits)
|
||||
return round(value, self.location_precision_digits)
|
||||
|
||||
def _resolve_repeater_location(self, snapshot: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
fallback_lat = _to_float(self.repeater_config.get("latitude"))
|
||||
@@ -729,11 +729,11 @@ class GPSService:
|
||||
"latitude": fallback_lat if fallback_lat is not None else 0.0,
|
||||
"longitude": fallback_lon if fallback_lon is not None else 0.0,
|
||||
"source": "config",
|
||||
"gps_enabled_for_repeater_location": self.use_gps_for_repeater_location,
|
||||
"precision_digits": self.repeater_location_precision_digits,
|
||||
"advertise_gps_location": self.advertise_gps_location,
|
||||
"location_precision_digits": self.location_precision_digits,
|
||||
}
|
||||
|
||||
if not self.use_gps_for_repeater_location:
|
||||
if not self.advertise_gps_location:
|
||||
return fallback_location
|
||||
|
||||
snapshot = snapshot or {}
|
||||
@@ -747,8 +747,8 @@ class GPSService:
|
||||
"latitude": self._apply_precision(gps_lat),
|
||||
"longitude": self._apply_precision(gps_lon),
|
||||
"source": "gps",
|
||||
"gps_enabled_for_repeater_location": True,
|
||||
"precision_digits": self.repeater_location_precision_digits,
|
||||
"advertise_gps_location": True,
|
||||
"location_precision_digits": self.location_precision_digits,
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -781,8 +781,8 @@ class GPSService:
|
||||
"read_timeout_seconds": self.read_timeout_seconds,
|
||||
"poll_interval_seconds": self.poll_interval_seconds,
|
||||
"stale_after_seconds": self.parser.stale_after_seconds,
|
||||
"use_gps_for_repeater_location": self.use_gps_for_repeater_location,
|
||||
"repeater_location_precision_digits": self.repeater_location_precision_digits,
|
||||
"advertise_gps_location": self.advertise_gps_location,
|
||||
"location_precision_digits": self.location_precision_digits,
|
||||
},
|
||||
"time_sync": self._get_time_sync_status(snapshot),
|
||||
"repeater_location": self._resolve_repeater_location(snapshot),
|
||||
@@ -802,7 +802,7 @@ class GPSService:
|
||||
with self._location_update_lock:
|
||||
status = deepcopy(self._location_update_status)
|
||||
|
||||
if not self.enabled or not self.location_update_enabled:
|
||||
if not self.enabled or not self.persist_gps_fix_enabled:
|
||||
status["state"] = "disabled"
|
||||
return status
|
||||
if self._location_update_callback is None:
|
||||
@@ -837,20 +837,20 @@ class GPSService:
|
||||
with self._location_update_lock:
|
||||
self._location_update_status.update(
|
||||
{
|
||||
"enabled": self.location_update_enabled,
|
||||
"enabled": self.persist_gps_fix_enabled,
|
||||
"state": state,
|
||||
"last_attempt": timestamp,
|
||||
"last_error": error,
|
||||
"last_latitude": latitude,
|
||||
"last_longitude": longitude,
|
||||
"interval_seconds": self.location_update_interval_seconds,
|
||||
"interval_seconds": self.persist_gps_fix_interval_seconds,
|
||||
}
|
||||
)
|
||||
if success:
|
||||
self._location_update_status["last_success"] = timestamp
|
||||
|
||||
def _maybe_update_repeater_location(self):
|
||||
if not self.enabled or not self.location_update_enabled:
|
||||
if not self.enabled or not self.persist_gps_fix_enabled:
|
||||
return
|
||||
if self._location_update_callback is None:
|
||||
return
|
||||
@@ -860,7 +860,7 @@ class GPSService:
|
||||
if (
|
||||
self._last_location_update_monotonic is not None
|
||||
and now_monotonic - self._last_location_update_monotonic
|
||||
< self.location_update_interval_seconds
|
||||
< self.persist_gps_fix_interval_seconds
|
||||
):
|
||||
return
|
||||
|
||||
@@ -891,7 +891,7 @@ class GPSService:
|
||||
"fix": deepcopy(snapshot.get("fix") or {}),
|
||||
"status": deepcopy(snapshot.get("status") or {}),
|
||||
"time": deepcopy(snapshot.get("time") or {}),
|
||||
"precision_digits": self.repeater_location_precision_digits,
|
||||
"location_precision_digits": self.location_precision_digits,
|
||||
}
|
||||
try:
|
||||
updated = bool(self._location_update_callback(payload))
|
||||
@@ -935,7 +935,7 @@ class GPSService:
|
||||
manual_position = deepcopy(self._extract_manual_position(self.repeater_config))
|
||||
gps_fix_valid = bool(snapshot.get("status", {}).get("fix_valid"))
|
||||
use_manual = (
|
||||
self.use_manual_location_until_fix
|
||||
self.api_fallback_to_config_location
|
||||
and manual_position is not None
|
||||
and not gps_fix_valid
|
||||
)
|
||||
@@ -959,8 +959,8 @@ class GPSService:
|
||||
snapshot["position_meta"] = {
|
||||
"source": position_source,
|
||||
"source_label": position_source_label,
|
||||
"policy": "manual_until_gps_fix"
|
||||
if self.use_manual_location_until_fix
|
||||
"policy": "fallback_to_config"
|
||||
if self.api_fallback_to_config_location
|
||||
else "gps_only",
|
||||
"manual_config_available": manual_position is not None,
|
||||
"gps_fix_valid": gps_fix_valid,
|
||||
|
||||
Reference in New Issue
Block a user