mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-07 02:11:29 +02:00
Update OTA repeater stats to return correct uptime, airtime, packet counts, etc.
- Introduced `total_rx_airtime_ms` in `AirtimeManager` to track received packet airtime. - Added `record_rx` method to log received airtime in `AirtimeManager`. - Updated `RepeaterHandler` to count received packets and log RX airtime using the new method. - Enhanced statistics reporting in `get_stats` to include total received airtime. - Updated `ProtocolRequestHelper` to include total RX airtime in the RepeaterStats structure for better monitoring.
This commit is contained in:
@@ -129,70 +129,73 @@ class ProtocolRequestHelper:
|
||||
return False
|
||||
|
||||
def _handle_get_status(self, client, timestamp: int, req_data: bytes):
|
||||
"""Build 56-byte RepeaterStats (firmware layout from MeshCore simple_repeater/MyMesh.h)."""
|
||||
# RepeaterStats: uint16 batt, uint16 curr_tx_queue_len, int16 noise_floor, int16 last_rssi,
|
||||
# uint32 n_packets_recv, n_packets_sent, total_air_time_secs, total_up_time_secs,
|
||||
# n_sent_flood, n_sent_direct, n_recv_flood, n_recv_direct,
|
||||
# uint16 err_events, int16 last_snr (×4), uint16 n_direct_dups, n_flood_dups,
|
||||
# uint32 total_rx_air_time_secs, n_recv_errors → 56 bytes
|
||||
|
||||
# C++ struct RepeaterStats (44 bytes total):
|
||||
# uint16_t batt_milli_volts;
|
||||
# uint16_t curr_tx_queue_len;
|
||||
# int16_t noise_floor;
|
||||
# int16_t last_rssi;
|
||||
# uint32_t n_packets_recv;
|
||||
# uint32_t n_packets_sent;
|
||||
# uint32_t total_air_time_secs;
|
||||
# uint32_t total_up_time_secs;
|
||||
# uint32_t n_sent_flood;
|
||||
# uint32_t n_sent_direct;
|
||||
# uint32_t n_recv_flood;
|
||||
# uint32_t n_recv_direct;
|
||||
# uint32_t err_events;
|
||||
# int16_t last_snr;
|
||||
# uint32_t n_direct_dups;
|
||||
# uint32_t n_flood_dups;
|
||||
# uint32_t total_rx_air_time_secs;
|
||||
# Uptime: use engine start_time when available (fixes wrong "20521 days" from time.time())
|
||||
if self.engine and hasattr(self.engine, "start_time"):
|
||||
total_up_time_secs = int(time.time() - self.engine.start_time)
|
||||
else:
|
||||
total_up_time_secs = 0
|
||||
|
||||
# Get stats from radio/engine
|
||||
noise_floor = int(self.radio.get_noise_floor() * 1.0) if self.radio else -120
|
||||
last_rssi = (
|
||||
int(self.radio.last_rssi) if self.radio and hasattr(self.radio, "last_rssi") else -120
|
||||
)
|
||||
last_snr = int(
|
||||
(self.radio.last_snr * 4.0) if self.radio and hasattr(self.radio, "last_snr") else 0
|
||||
)
|
||||
# Radio: noise floor, last RSSI, last SNR (firmware stores SNR × 4)
|
||||
if self.radio:
|
||||
noise_floor = int(getattr(self.radio, "get_noise_floor", lambda: 0)() or 0)
|
||||
if callable(getattr(self.radio, "get_last_rssi", None)):
|
||||
last_rssi = int(self.radio.get_last_rssi() or -120)
|
||||
else:
|
||||
last_rssi = int(getattr(self.radio, "last_rssi", -120) or -120)
|
||||
if callable(getattr(self.radio, "get_last_snr", None)):
|
||||
last_snr = int((self.radio.get_last_snr() or 0) * 4)
|
||||
else:
|
||||
last_snr = int((getattr(self.radio, "last_snr", 0) or 0) * 4)
|
||||
else:
|
||||
noise_floor = 0
|
||||
last_rssi = -120
|
||||
last_snr = 0
|
||||
|
||||
# Get packet counts
|
||||
n_packets_recv = (
|
||||
self.radio.packets_received
|
||||
if self.radio and hasattr(self.radio, "packets_received")
|
||||
else 0
|
||||
)
|
||||
n_packets_sent = (
|
||||
self.radio.packets_sent if self.radio and hasattr(self.radio, "packets_sent") else 0
|
||||
)
|
||||
# Packet counts: prefer engine (rx_count, forwarded_count); fall back to radio if present
|
||||
if self.engine:
|
||||
n_packets_recv = getattr(self.engine, "rx_count", 0)
|
||||
n_packets_sent = getattr(self.engine, "forwarded_count", 0)
|
||||
elif self.radio:
|
||||
n_packets_recv = getattr(self.radio, "packets_received", 0) or 0
|
||||
n_packets_sent = getattr(self.radio, "packets_sent", 0) or 0
|
||||
else:
|
||||
n_packets_recv = 0
|
||||
n_packets_sent = 0
|
||||
|
||||
# Get airtime stats
|
||||
# Airtime (AirtimeManager uses total_airtime_ms for TX; total_rx_airtime_ms if we track RX)
|
||||
total_air_time_secs = 0
|
||||
total_rx_air_time_secs = 0
|
||||
if self.engine and hasattr(self.engine, "airtime_manager"):
|
||||
total_air_time_secs = int(self.engine.airtime_manager.total_tx_airtime_ms / 1000)
|
||||
|
||||
# Get routing stats
|
||||
n_sent_flood = 0
|
||||
n_sent_direct = 0
|
||||
n_recv_flood = 0
|
||||
n_recv_direct = 0
|
||||
n_direct_dups = 0
|
||||
n_flood_dups = 0
|
||||
|
||||
if self.engine:
|
||||
n_sent_flood = getattr(self.engine, "sent_flood_count", 0)
|
||||
n_sent_direct = getattr(self.engine, "sent_direct_count", 0)
|
||||
n_recv_flood = getattr(self.engine, "recv_flood_count", 0)
|
||||
n_recv_direct = getattr(self.engine, "recv_direct_count", 0)
|
||||
n_direct_dups = getattr(self.engine, "direct_dup_count", 0)
|
||||
n_flood_dups = getattr(self.engine, "flood_dup_count", 0)
|
||||
am = getattr(self.engine, "airtime_mgr", None) or getattr(
|
||||
self.engine, "airtime_manager", None
|
||||
)
|
||||
if am is not None:
|
||||
total_air_time_secs = int(getattr(am, "total_airtime_ms", 0) or 0) // 1000
|
||||
total_rx_air_time_secs = int(getattr(am, "total_rx_airtime_ms", 0) or 0) // 1000
|
||||
|
||||
# Pack struct (little-endian)
|
||||
# Routing stats (flood/direct and dups - from engine when available)
|
||||
n_sent_flood = getattr(self.engine, "sent_flood_count", 0) if self.engine else 0
|
||||
n_sent_direct = getattr(self.engine, "sent_direct_count", 0) if self.engine else 0
|
||||
n_recv_flood = getattr(self.engine, "recv_flood_count", 0) if self.engine else 0
|
||||
n_recv_direct = getattr(self.engine, "recv_direct_count", 0) if self.engine else 0
|
||||
n_direct_dups = getattr(self.engine, "direct_dup_count", 0) if self.engine else 0
|
||||
n_flood_dups = getattr(self.engine, "flood_dup_count", 0) if self.engine else 0
|
||||
n_recv_errors = (
|
||||
int(getattr(self.radio, "crc_error_count", 0) or 0)
|
||||
if self.radio
|
||||
else 0
|
||||
)
|
||||
|
||||
# Pack 56-byte RepeaterStats (layout matches firmware)
|
||||
stats = struct.pack(
|
||||
"<HHhhIIIIIIIIIhIII",
|
||||
"<HHhhIIIIIIIIHhHHII",
|
||||
0, # batt_milli_volts (not available on Pi)
|
||||
0, # curr_tx_queue_len (TODO)
|
||||
noise_floor,
|
||||
@@ -200,7 +203,7 @@ class ProtocolRequestHelper:
|
||||
n_packets_recv,
|
||||
n_packets_sent,
|
||||
total_air_time_secs,
|
||||
int(time.time()), # total_up_time_secs
|
||||
total_up_time_secs,
|
||||
n_sent_flood,
|
||||
n_sent_direct,
|
||||
n_recv_flood,
|
||||
@@ -210,8 +213,17 @@ class ProtocolRequestHelper:
|
||||
n_direct_dups,
|
||||
n_flood_dups,
|
||||
total_rx_air_time_secs,
|
||||
n_recv_errors,
|
||||
)
|
||||
|
||||
logger.debug(f"GET_STATUS: noise={noise_floor}dBm, rssi={last_rssi}dBm, snr={last_snr/4}dB")
|
||||
|
||||
|
||||
logger.debug(
|
||||
"GET_STATUS: uptime=%ds, noise=%ddBm, rssi=%ddBm, snr=%.1fdB, rx=%s, tx=%s",
|
||||
total_up_time_secs,
|
||||
noise_floor,
|
||||
last_rssi,
|
||||
last_snr / 4.0,
|
||||
n_packets_recv,
|
||||
n_packets_sent,
|
||||
)
|
||||
|
||||
return stats
|
||||
|
||||
Reference in New Issue
Block a user