diff --git a/repeater/data_acquisition/rrdtool_handler.py b/repeater/data_acquisition/rrdtool_handler.py index e89b4ed..be469e2 100644 --- a/repeater/data_acquisition/rrdtool_handler.py +++ b/repeater/data_acquisition/rrdtool_handler.py @@ -220,8 +220,8 @@ class RRDToolHandler: "type_7": "Anonymous Request (ANON_REQ)", "type_8": "Returned Path (PATH)", "type_9": "Trace (TRACE)", - "type_10": "Multi-part Packet", - "type_11": "Control Packet Data", + "type_10": "Multi-part Packet (MULTIPART)", + "type_11": "Control (CONTROL)", "type_12": "Reserved Type 12", "type_13": "Reserved Type 13", "type_14": "Reserved Type 14", diff --git a/repeater/data_acquisition/sqlite_handler.py b/repeater/data_acquisition/sqlite_handler.py index c006cc1..63e0ed4 100644 --- a/repeater/data_acquisition/sqlite_handler.py +++ b/repeater/data_acquisition/sqlite_handler.py @@ -817,10 +817,33 @@ class SQLiteHandler: try: cutoff = time.time() - (hours * 3600) - with sqlite3.connect(self.sqlite_path) as conn: - conn.row_factory = sqlite3.Row - - type_counts = {} + # Align with pyMC_core feat/newRadios PAYLOAD_TYPES (0x0B = CONTROL) + try: + from pymc_core.protocol.utils import PAYLOAD_TYPES as _PT + _human = { + "REQ": "Request", + "RESPONSE": "Response", + "TXT_MSG": "Plain Text Message", + "ACK": "Acknowledgment", + "ADVERT": "Node Advertisement", + "GRP_TXT": "Group Text Message", + "GRP_DATA": "Group Datagram", + "ANON_REQ": "Anonymous Request", + "PATH": "Returned Path", + "TRACE": "Trace", + "MULTIPART": "Multi-part Packet", + "CONTROL": "Control", + "RAW_CUSTOM": "Custom Packet", + } + packet_type_names = {} + for i in range(16): + code = _PT.get(i) + if code: + label = _human.get(code, code.replace("_", " ").title()) + packet_type_names[i] = f"{label} ({code})" + else: + packet_type_names[i] = f"Reserved Type {i}" + except ImportError: packet_type_names = { 0: "Request (REQ)", 1: "Response (RESPONSE)", @@ -832,14 +855,18 @@ class SQLiteHandler: 7: "Anonymous Request (ANON_REQ)", 8: "Returned Path (PATH)", 9: "Trace (TRACE)", - 10: "Multi-part Packet", - 11: "Reserved Type 11", + 10: "Multi-part Packet (MULTIPART)", + 11: "Control (CONTROL)", 12: "Reserved Type 12", 13: "Reserved Type 13", 14: "Reserved Type 14", 15: "Custom Packet (RAW_CUSTOM)", } + with sqlite3.connect(self.sqlite_path) as conn: + conn.row_factory = sqlite3.Row + + type_counts = {} for packet_type in range(16): count = conn.execute( "SELECT COUNT(*) FROM packets WHERE type = ? AND timestamp > ?", diff --git a/repeater/handler_helpers/advert.py b/repeater/handler_helpers/advert.py index 3a6fc91..e69de29 100644 --- a/repeater/handler_helpers/advert.py +++ b/repeater/handler_helpers/advert.py @@ -1,131 +0,0 @@ -""" -Advertisement packet handling helper for pyMC Repeater. - -This module processes advertisement packets for neighbor tracking and discovery. -""" - -import asyncio -import logging -import time - -from pymc_core.node.handlers.advert import AdvertHandler - -logger = logging.getLogger("AdvertHelper") - - -class AdvertHelper: - """Helper class for processing advertisement packets in the repeater.""" - - def __init__(self, local_identity, storage, log_fn=None): - """ - Initialize the advert helper. - - Args: - local_identity: The LocalIdentity instance for this repeater - storage: StorageCollector instance for persisting advert data - log_fn: Optional logging function for AdvertHandler - """ - self.local_identity = local_identity - self.storage = storage - - # Create AdvertHandler internally as a parsing utility - self.advert_handler = AdvertHandler(log_fn=log_fn or logger.info) - - # Cache for tracking known neighbors (avoid repeated database queries) - self._known_neighbors = set() - - async def process_advert_packet(self, packet, rssi: int, snr: float) -> None: - """ - Process an incoming advertisement packet. - - This method uses AdvertHandler to parse the packet, then stores - the neighbor information for tracking and discovery. - - Args: - packet: The advertisement packet to process - rssi: Received signal strength indicator - snr: Signal-to-noise ratio - """ - try: - # Set signal metrics on packet for handler to use - packet._snr = snr - packet._rssi = rssi - - # Use AdvertHandler to parse the packet - it now returns parsed data - advert_data = await self.advert_handler(packet) - - if not advert_data or not advert_data.get("valid"): - logger.warning("Invalid advert packet received, dropping.") - packet.mark_do_not_retransmit() - packet.drop_reason = "Invalid advert packet" - return - - # Extract data from parsed advert - pubkey = advert_data["public_key"] - node_name = advert_data["name"] - contact_type = advert_data["contact_type"] - - # Skip our own adverts - if self.local_identity: - local_pubkey = self.local_identity.get_public_key().hex() - if pubkey == local_pubkey: - logger.debug("Ignoring own advert in neighbor tracking") - return - - # Get route type from packet header - from pymc_core.protocol.constants import PH_ROUTE_MASK - - route_type = packet.header & PH_ROUTE_MASK - - # Check if this is a new neighbor (run DB read in thread to avoid blocking event loop) - current_time = time.time() - if pubkey not in self._known_neighbors: - # Only check database if not in cache - if self.storage: - current_neighbors = await asyncio.to_thread( - self.storage.get_neighbors - ) - else: - current_neighbors = {} - is_new_neighbor = pubkey not in current_neighbors - - if is_new_neighbor: - self._known_neighbors.add(pubkey) - logger.info(f"Discovered new neighbor: {node_name} ({pubkey[:16]}...)") - else: - is_new_neighbor = False - - # Determine zero-hop: direct routes are always zero-hop, - # flood routes are zero-hop if path_len <= 1 (received directly) - path_len = len(packet.path) if packet.path else 0 - zero_hop = path_len == 0 - - # Build advert record - advert_record = { - "timestamp": current_time, - "pubkey": pubkey, - "node_name": node_name, - "is_repeater": "REPEATER" in contact_type.upper(), - "route_type": route_type, - "contact_type": contact_type, - "latitude": advert_data["latitude"], - "longitude": advert_data["longitude"], - "rssi": rssi, - "snr": snr, - "is_new_neighbor": is_new_neighbor, - "zero_hop": zero_hop, - } - - # Store to database (run in thread so event loop stays responsive; - # blocking here can cause companion TCP clients to disconnect) - if self.storage: - try: - await asyncio.to_thread( - self.storage.record_advert, - advert_record, - ) - except Exception as e: - logger.error(f"Failed to store advert record: {e}") - - except Exception as e: - logger.error(f"Error processing advert packet: {e}", exc_info=True)