mirror of
https://github.com/rightup/pyMC_Repeater.git
synced 2026-03-28 17:43:06 +01:00
feat(api): Add POST /api/update_radio_config endpoint
Adds a new endpoint to update radio and repeater configuration via HTTP API, supporting both persisted storage and live in-memory updates. Supported settings: - Radio: tx_power (2-30 dBm) - Delays: tx_delay_factor, direct_tx_delay_factor, rx_delay_base - Repeater: node_name, latitude, longitude, max_flood_hops, flood_advert_interval_hours, advert_interval_minutes Features: - Validates all input parameters with appropriate ranges - Saves to config.yaml for persistence - Updates daemon's in-memory config for immediate effect (no restart needed) - Returns live_update status so clients know if restart is required This enables web dashboards like pyMC Console to configure the repeater without requiring SSH access or service restarts for most settings. Co-Authored-By: Warp <agent@warp.dev>
This commit is contained in:
@@ -596,6 +596,171 @@ class APIEndpoints:
|
||||
logger.error(f"Error saving CAD settings: {e}")
|
||||
return self._error(e)
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@cherrypy.tools.json_in()
|
||||
def update_radio_config(self):
|
||||
"""Update radio and repeater configuration with live updates.
|
||||
|
||||
POST /api/update_radio_config
|
||||
Body: {
|
||||
"tx_power": 22, # TX power in dBm (2-30)
|
||||
"tx_delay_factor": 1.0, # TX delay factor (0.0-5.0)
|
||||
"direct_tx_delay_factor": 0.5, # Direct TX delay (0.0-5.0)
|
||||
"rx_delay_base": 0.0, # RX delay base (>= 0)
|
||||
"node_name": "MyNode", # Node name
|
||||
"latitude": 0.0, # Latitude (-90 to 90)
|
||||
"longitude": 0.0, # Longitude (-180 to 180)
|
||||
"max_flood_hops": 3, # Max flood hops (0-64)
|
||||
"flood_advert_interval_hours": 10, # Flood advert interval (0 or 3-48)
|
||||
"advert_interval_minutes": 120 # Local advert interval (0 or 1-10080)
|
||||
}
|
||||
|
||||
Returns: {"success": true, "data": {"applied": [...], "live_update": true}}
|
||||
"""
|
||||
try:
|
||||
self._require_post()
|
||||
data = cherrypy.request.json or {}
|
||||
|
||||
applied = []
|
||||
|
||||
# Ensure config sections exist
|
||||
if "radio" not in self.config:
|
||||
self.config["radio"] = {}
|
||||
if "delays" not in self.config:
|
||||
self.config["delays"] = {}
|
||||
if "repeater" not in self.config:
|
||||
self.config["repeater"] = {}
|
||||
|
||||
# Update TX power (up to 30 dBm for high-power radios)
|
||||
if "tx_power" in data:
|
||||
power = int(data["tx_power"])
|
||||
if power < 2 or power > 30:
|
||||
return self._error("TX power must be 2-30 dBm")
|
||||
self.config["radio"]["tx_power"] = power
|
||||
applied.append(f"power={power}dBm")
|
||||
|
||||
# Update TX delay factor
|
||||
if "tx_delay_factor" in data:
|
||||
tdf = float(data["tx_delay_factor"])
|
||||
if tdf < 0.0 or tdf > 5.0:
|
||||
return self._error("TX delay factor must be 0.0-5.0")
|
||||
self.config["delays"]["tx_delay_factor"] = tdf
|
||||
applied.append(f"txdelay={tdf}")
|
||||
|
||||
# Update direct TX delay factor
|
||||
if "direct_tx_delay_factor" in data:
|
||||
dtdf = float(data["direct_tx_delay_factor"])
|
||||
if dtdf < 0.0 or dtdf > 5.0:
|
||||
return self._error("Direct TX delay factor must be 0.0-5.0")
|
||||
self.config["delays"]["direct_tx_delay_factor"] = dtdf
|
||||
applied.append(f"direct.txdelay={dtdf}")
|
||||
|
||||
# Update RX delay base
|
||||
if "rx_delay_base" in data:
|
||||
rxd = float(data["rx_delay_base"])
|
||||
if rxd < 0.0:
|
||||
return self._error("RX delay cannot be negative")
|
||||
self.config["delays"]["rx_delay_base"] = rxd
|
||||
applied.append(f"rxdelay={rxd}")
|
||||
|
||||
# Update node name
|
||||
if "node_name" in data:
|
||||
name = str(data["node_name"]).strip()
|
||||
if not name:
|
||||
return self._error("Node name cannot be empty")
|
||||
self.config["repeater"]["node_name"] = name
|
||||
applied.append(f"name={name}")
|
||||
|
||||
# Update latitude
|
||||
if "latitude" in data:
|
||||
lat = float(data["latitude"])
|
||||
if lat < -90 or lat > 90:
|
||||
return self._error("Latitude must be -90 to 90")
|
||||
self.config["repeater"]["latitude"] = lat
|
||||
applied.append(f"lat={lat}")
|
||||
|
||||
# Update longitude
|
||||
if "longitude" in data:
|
||||
lon = float(data["longitude"])
|
||||
if lon < -180 or lon > 180:
|
||||
return self._error("Longitude must be -180 to 180")
|
||||
self.config["repeater"]["longitude"] = lon
|
||||
applied.append(f"lon={lon}")
|
||||
|
||||
# Update max flood hops
|
||||
if "max_flood_hops" in data:
|
||||
hops = int(data["max_flood_hops"])
|
||||
if hops < 0 or hops > 64:
|
||||
return self._error("Max flood hops must be 0-64")
|
||||
self.config["repeater"]["max_flood_hops"] = hops
|
||||
applied.append(f"flood.max={hops}")
|
||||
|
||||
# Update flood advert interval (hours)
|
||||
if "flood_advert_interval_hours" in data:
|
||||
hours = int(data["flood_advert_interval_hours"])
|
||||
if hours != 0 and (hours < 3 or hours > 48):
|
||||
return self._error("Flood advert interval must be 0 (off) or 3-48 hours")
|
||||
self.config["repeater"]["send_advert_interval_hours"] = hours
|
||||
applied.append(f"flood.advert.interval={hours}h")
|
||||
|
||||
# Update local advert interval (minutes)
|
||||
if "advert_interval_minutes" in data:
|
||||
mins = int(data["advert_interval_minutes"])
|
||||
if mins != 0 and (mins < 1 or mins > 10080):
|
||||
return self._error("Advert interval must be 0 (off) or 1-10080 minutes")
|
||||
self.config["repeater"]["advert_interval_minutes"] = mins
|
||||
applied.append(f"advert.interval={mins}m")
|
||||
|
||||
if not applied:
|
||||
return self._error("No valid settings provided")
|
||||
|
||||
# Save to config file
|
||||
config_path = getattr(self, '_config_path', '/etc/pymc_repeater/config.yaml')
|
||||
self._save_config_to_file(config_path)
|
||||
|
||||
# Live update: Also update daemon's in-memory config for immediate effect
|
||||
live_updated = False
|
||||
if self.daemon_instance and hasattr(self.daemon_instance, 'config'):
|
||||
try:
|
||||
daemon_config = self.daemon_instance.config
|
||||
|
||||
# Update repeater section in daemon config
|
||||
if 'repeater' not in daemon_config:
|
||||
daemon_config['repeater'] = {}
|
||||
for key in ['node_name', 'latitude', 'longitude', 'max_flood_hops',
|
||||
'advert_interval_minutes', 'send_advert_interval_hours']:
|
||||
if key in self.config.get('repeater', {}):
|
||||
daemon_config['repeater'][key] = self.config['repeater'][key]
|
||||
|
||||
# Update delays section in daemon config
|
||||
if 'delays' not in daemon_config:
|
||||
daemon_config['delays'] = {}
|
||||
for key in ['tx_delay_factor', 'direct_tx_delay_factor', 'rx_delay_base']:
|
||||
if key in self.config.get('delays', {}):
|
||||
daemon_config['delays'][key] = self.config['delays'][key]
|
||||
|
||||
live_updated = True
|
||||
logger.info("Live updated daemon config")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not live update daemon config: {e}")
|
||||
|
||||
logger.info(f"Radio config updated: {', '.join(applied)}")
|
||||
|
||||
return self._success({
|
||||
"applied": applied,
|
||||
"persisted": True,
|
||||
"live_update": live_updated,
|
||||
"restart_required": not live_updated,
|
||||
"message": "Settings applied immediately." if live_updated else "Settings saved. Restart service to apply changes."
|
||||
})
|
||||
|
||||
except cherrypy.HTTPError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating radio config: {e}")
|
||||
return self._error(str(e))
|
||||
|
||||
def _save_config_to_file(self, config_path):
|
||||
try:
|
||||
import yaml
|
||||
|
||||
Reference in New Issue
Block a user