feat(reticulum-daemon): airtime-limit args + radio_state RPC for live read-back
Groundwork for the .126 LoRa settings panel (operator top priority):
- --airtime-limit-short/--airtime-limit-long (percent duty-cycle locks,
e.g. EU868 25/10) written into the RNode interface config when set;
default None writes nothing — identical to older daemons.
- New socket RPC {"cmd":"radio_state"} returns the live RNodeInterface
state: requested config values AND the radio-confirmed r_* values
(r_frequency/r_bandwidth/r_txpower/r_sf/r_cr/r_st_alock/r_lt_alock,
online, port, airtime utilisation). The r_* values are what the RADIO
reported after detect/configure — the settings panel's proof that the
device is actually using what was applied.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
44522fc94a
commit
2afeafc92e
@@ -108,6 +108,13 @@ def _write_rns_config(
|
|||||||
f" spreadingfactor = {lora['spreadingfactor']}\n"
|
f" spreadingfactor = {lora['spreadingfactor']}\n"
|
||||||
f" codingrate = {lora['codingrate']}\n"
|
f" codingrate = {lora['codingrate']}\n"
|
||||||
)
|
)
|
||||||
|
# Regulatory duty-cycle limits (percent). Only written when set —
|
||||||
|
# absent keys keep RNS's own default (no software airtime lock),
|
||||||
|
# matching every daemon built before these args existed.
|
||||||
|
if lora.get("airtime_limit_short") is not None:
|
||||||
|
interfaces += f" airtime_limit_short = {lora['airtime_limit_short']}\n"
|
||||||
|
if lora.get("airtime_limit_long") is not None:
|
||||||
|
interfaces += f" airtime_limit_long = {lora['airtime_limit_long']}\n"
|
||||||
elif tcp_listen or tcp_connect:
|
elif tcp_listen or tcp_connect:
|
||||||
parts = []
|
parts = []
|
||||||
if tcp_listen:
|
if tcp_listen:
|
||||||
@@ -188,6 +195,8 @@ class ReticulumDaemon:
|
|||||||
"txpower": self.args.txpower,
|
"txpower": self.args.txpower,
|
||||||
"spreadingfactor": self.args.spreadingfactor,
|
"spreadingfactor": self.args.spreadingfactor,
|
||||||
"codingrate": self.args.codingrate,
|
"codingrate": self.args.codingrate,
|
||||||
|
"airtime_limit_short": self.args.airtime_limit_short,
|
||||||
|
"airtime_limit_long": self.args.airtime_limit_long,
|
||||||
},
|
},
|
||||||
no_radio=self.args.no_radio,
|
no_radio=self.args.no_radio,
|
||||||
tcp_listen=self.args.tcp_listen,
|
tcp_listen=self.args.tcp_listen,
|
||||||
@@ -358,6 +367,8 @@ class ReticulumDaemon:
|
|||||||
self.announce()
|
self.announce()
|
||||||
elif cmd == "status":
|
elif cmd == "status":
|
||||||
self._broadcast(self._status())
|
self._broadcast(self._status())
|
||||||
|
elif cmd == "radio_state":
|
||||||
|
self._broadcast(self._radio_state())
|
||||||
elif cmd == "send_resource":
|
elif cmd == "send_resource":
|
||||||
self._send_resource(req)
|
self._send_resource(req)
|
||||||
elif cmd == "shutdown":
|
elif cmd == "shutdown":
|
||||||
@@ -374,6 +385,48 @@ class ReticulumDaemon:
|
|||||||
return {"event": "status", "connected": self.router is not None,
|
return {"event": "status", "connected": self.router is not None,
|
||||||
"dest_hash": self.dest_hash_hex, "interfaces": ifaces}
|
"dest_hash": self.dest_hash_hex, "interfaces": ifaces}
|
||||||
|
|
||||||
|
def _radio_state(self) -> dict:
|
||||||
|
"""Radio-confirmed RNode parameters, straight from the live
|
||||||
|
RNodeInterface object. The r_* attributes are what the RADIO reported
|
||||||
|
after detect/configure (RNS/Interfaces/RNodeInterface.py) — this is
|
||||||
|
the read-back the settings panel shows as proof the device is
|
||||||
|
actually using the applied values, as opposed to what the config
|
||||||
|
asked for. Absent radio (TCP/no-radio builds) → configured=False."""
|
||||||
|
state = {"event": "radio_state", "configured": False, "online": False}
|
||||||
|
try:
|
||||||
|
import RNS
|
||||||
|
for iface in list(RNS.Transport.interfaces):
|
||||||
|
if type(iface).__name__ != "RNodeInterface":
|
||||||
|
continue
|
||||||
|
state.update({
|
||||||
|
"configured": True,
|
||||||
|
"online": bool(getattr(iface, "online", False)),
|
||||||
|
"port": getattr(iface, "port", None),
|
||||||
|
# Requested (config) values…
|
||||||
|
"frequency": getattr(iface, "frequency", None),
|
||||||
|
"bandwidth": getattr(iface, "bandwidth", None),
|
||||||
|
"txpower": getattr(iface, "txpower", None),
|
||||||
|
"spreadingfactor": getattr(iface, "sf", None),
|
||||||
|
"codingrate": getattr(iface, "cr", None),
|
||||||
|
"airtime_limit_short": getattr(iface, "st_alock", None),
|
||||||
|
"airtime_limit_long": getattr(iface, "lt_alock", None),
|
||||||
|
# …and what the radio itself confirmed it is running.
|
||||||
|
"r_frequency": getattr(iface, "r_frequency", None),
|
||||||
|
"r_bandwidth": getattr(iface, "r_bandwidth", None),
|
||||||
|
"r_txpower": getattr(iface, "r_txpower", None),
|
||||||
|
"r_spreadingfactor": getattr(iface, "r_sf", None),
|
||||||
|
"r_codingrate": getattr(iface, "r_cr", None),
|
||||||
|
"r_airtime_limit_short": getattr(iface, "r_st_alock", None),
|
||||||
|
"r_airtime_limit_long": getattr(iface, "r_lt_alock", None),
|
||||||
|
# Live utilisation, when the interface tracks it.
|
||||||
|
"airtime_short": getattr(iface, "airtime_short", None),
|
||||||
|
"airtime_long": getattr(iface, "airtime_long", None),
|
||||||
|
})
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return state
|
||||||
|
|
||||||
def _send(self, req: dict):
|
def _send(self, req: dict):
|
||||||
import RNS
|
import RNS
|
||||||
import LXMF
|
import LXMF
|
||||||
@@ -643,6 +696,11 @@ def _parse_args(argv):
|
|||||||
p.add_argument("--txpower", type=int, default=17)
|
p.add_argument("--txpower", type=int, default=17)
|
||||||
p.add_argument("--spreadingfactor", type=int, default=8)
|
p.add_argument("--spreadingfactor", type=int, default=8)
|
||||||
p.add_argument("--codingrate", type=int, default=5)
|
p.add_argument("--codingrate", type=int, default=5)
|
||||||
|
# Regulatory duty-cycle locks (percent of airtime, e.g. EU868 short=25
|
||||||
|
# long=10). None (the default) writes no config line, so RNS applies no
|
||||||
|
# software airtime lock — identical to daemons built before these existed.
|
||||||
|
p.add_argument("--airtime-limit-short", type=float, default=None)
|
||||||
|
p.add_argument("--airtime-limit-long", type=float, default=None)
|
||||||
p.add_argument("--enable-transport", action="store_true",
|
p.add_argument("--enable-transport", action="store_true",
|
||||||
help="run as an RNS transport node: relay traffic and rebroadcast "
|
help="run as an RNS transport node: relay traffic and rebroadcast "
|
||||||
"announces so nodes beyond direct RF range discover each other "
|
"announces so nodes beyond direct RF range discover each other "
|
||||||
|
|||||||
Reference in New Issue
Block a user