mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-13 21:31:31 +02:00
add disallowed packet types configuration for LetsMesh and implement filtering in storage collector
This commit is contained in:
+18
-1
@@ -144,7 +144,24 @@ letsmesh:
|
|||||||
enabled: true
|
enabled: true
|
||||||
iata_code: "test" # e.g., "SFO", "LHR", "test"
|
iata_code: "test" # e.g., "SFO", "LHR", "test"
|
||||||
broker_index: 0 # Which LetsMesh broker (0=EU, 1=US West)
|
broker_index: 0 # Which LetsMesh broker (0=EU, 1=US West)
|
||||||
status_interval: 60
|
status_interval: 60
|
||||||
|
|
||||||
|
# Block specific packet types from being published to LetsMesh
|
||||||
|
# If not specified or empty list, all types are published
|
||||||
|
# Available types: REQ, RESPONSE, TXT_MSG, ACK, ADVERT, GRP_TXT,
|
||||||
|
# GRP_DATA, ANON_REQ, PATH, TRACE, RAW_CUSTOM
|
||||||
|
disallowed_packet_types: []
|
||||||
|
# - REQ # Don't publish requests
|
||||||
|
# - RESPONSE # Don't publish responses
|
||||||
|
# - TXT_MSG # Don't publish text messages
|
||||||
|
# - ACK # Don't publish acknowledgments
|
||||||
|
# - ADVERT # Don't publish advertisements
|
||||||
|
# - GRP_TXT # Don't publish group text messages
|
||||||
|
# - GRP_DATA # Don't publish group data
|
||||||
|
# - ANON_REQ # Don't publish anonymous requests
|
||||||
|
# - PATH # Don't publish path packets
|
||||||
|
# - TRACE # Don't publish trace packets
|
||||||
|
# - RAW_CUSTOM # Don't publish custom raw packets
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
# Log level: DEBUG, INFO, WARNING, ERROR
|
# Log level: DEBUG, INFO, WARNING, ERROR
|
||||||
|
|||||||
+10
-1
@@ -29,13 +29,22 @@ def get_node_info(config: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
|
|
||||||
letsmesh_config = config.get("letsmesh", {})
|
letsmesh_config = config.get("letsmesh", {})
|
||||||
|
|
||||||
|
from pymc_core.protocol.utils import PAYLOAD_TYPES
|
||||||
|
|
||||||
|
disallowed_types = letsmesh_config.get("disallowed_packet_types", [])
|
||||||
|
type_name_map = {name: code for code, name in PAYLOAD_TYPES.items()}
|
||||||
|
|
||||||
|
disallowed_hex = [type_name_map.get(name.upper(), None) for name in disallowed_types]
|
||||||
|
disallowed_hex = [val for val in disallowed_hex if val is not None] # Filter out invalid names
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"node_name": node_name,
|
"node_name": node_name,
|
||||||
"radio_config": radio_config_str,
|
"radio_config": radio_config_str,
|
||||||
"iata_code": letsmesh_config.get("iata_code", "TEST"),
|
"iata_code": letsmesh_config.get("iata_code", "TEST"),
|
||||||
"broker_index": letsmesh_config.get("broker_index", 0),
|
"broker_index": letsmesh_config.get("broker_index", 0),
|
||||||
"status_interval": letsmesh_config.get("status_interval", 60),
|
"status_interval": letsmesh_config.get("status_interval", 60),
|
||||||
"model": letsmesh_config.get("model", "PyMC-Repeater")
|
"model": letsmesh_config.get("model", "PyMC-Repeater"),
|
||||||
|
"disallowed_packet_types": disallowed_hex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import base64
|
|||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
from datetime import datetime, timedelta, UTC
|
from datetime import datetime, timedelta, UTC
|
||||||
|
from dataclasses import dataclass, asdict
|
||||||
from nacl.signing import SigningKey
|
from nacl.signing import SigningKey
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
from .. import __version__
|
from .. import __version__
|
||||||
@@ -301,3 +302,80 @@ class MeshCoreToMqttJwtPusher:
|
|||||||
result = self.client.publish(topic, message, retain=retain)
|
result = self.client.publish(topic, message, retain=retain)
|
||||||
logging.debug(f"Published to {topic}: {message}")
|
logging.debug(f"Published to {topic}: {message}")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# ====================================================================
|
||||||
|
# LetsMesh Packet Data Class
|
||||||
|
# ====================================================================
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LetsMeshPacket:
|
||||||
|
"""
|
||||||
|
Data class for LetsMesh packet format.
|
||||||
|
Converts internal packet_record format to LetsMesh publish format.
|
||||||
|
"""
|
||||||
|
origin: str
|
||||||
|
origin_id: str
|
||||||
|
timestamp: str
|
||||||
|
type: str
|
||||||
|
direction: str
|
||||||
|
time: str
|
||||||
|
date: str
|
||||||
|
len: str
|
||||||
|
packet_type: str
|
||||||
|
route: str
|
||||||
|
payload_len: str
|
||||||
|
raw: str
|
||||||
|
SNR: str
|
||||||
|
RSSI: str
|
||||||
|
score: str
|
||||||
|
duration: str
|
||||||
|
hash: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_packet_record(cls, packet_record: dict, origin: str, origin_id: str) -> Optional['LetsMeshPacket']:
|
||||||
|
"""
|
||||||
|
Create LetsMeshPacket from internal packet_record format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
packet_record: Internal packet record dictionary
|
||||||
|
origin: Node name
|
||||||
|
origin_id: Public key of the node
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LetsMeshPacket instance or None if raw_packet is missing
|
||||||
|
"""
|
||||||
|
if "raw_packet" not in packet_record or not packet_record["raw_packet"]:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract timestamp and format date/time
|
||||||
|
timestamp = packet_record.get("timestamp", 0)
|
||||||
|
dt = datetime.fromtimestamp(timestamp)
|
||||||
|
|
||||||
|
# Format route type (1=Flood->F, 2=Direct->D, etc)
|
||||||
|
route_map = {1: "F", 2: "D"}
|
||||||
|
route = route_map.get(packet_record.get("route", 0), str(packet_record.get("route", 0)))
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
origin=origin,
|
||||||
|
origin_id=origin_id,
|
||||||
|
timestamp=dt.isoformat(),
|
||||||
|
type="PACKET",
|
||||||
|
direction="rx",
|
||||||
|
time=dt.strftime("%H:%M:%S"),
|
||||||
|
date=dt.strftime("%-d/%-m/%Y"),
|
||||||
|
len=str(len(packet_record["raw_packet"]) // 2),
|
||||||
|
packet_type=str(packet_record.get("type", 0)),
|
||||||
|
route=route,
|
||||||
|
payload_len=str(packet_record.get("payload_length", 0)),
|
||||||
|
raw=packet_record["raw_packet"],
|
||||||
|
SNR=str(packet_record.get("snr", 0)),
|
||||||
|
RSSI=str(packet_record.get("rssi", 0)),
|
||||||
|
score=str(int(packet_record.get("score", 0) * 1000)),
|
||||||
|
duration="0",
|
||||||
|
hash=packet_record.get("packet_hash", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""Convert to dictionary for JSON serialization"""
|
||||||
|
return asdict(self)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from typing import Optional, Dict, Any
|
|||||||
from .sqlite_handler import SQLiteHandler
|
from .sqlite_handler import SQLiteHandler
|
||||||
from .rrdtool_handler import RRDToolHandler
|
from .rrdtool_handler import RRDToolHandler
|
||||||
from .mqtt_handler import MQTTHandler
|
from .mqtt_handler import MQTTHandler
|
||||||
from .letsmesh_handler import MeshCoreToMqttJwtPusher
|
from .letsmesh_handler import MeshCoreToMqttJwtPusher, LetsMeshPacket
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("StorageCollector")
|
logger = logging.getLogger("StorageCollector")
|
||||||
@@ -42,10 +42,23 @@ class StorageCollector:
|
|||||||
stats_provider=self._get_live_stats
|
stats_provider=self._get_live_stats
|
||||||
)
|
)
|
||||||
self.letsmesh_handler.connect()
|
self.letsmesh_handler.connect()
|
||||||
|
|
||||||
|
# Get disallowed packet types from config
|
||||||
|
from ..config import get_node_info
|
||||||
|
node_info = get_node_info(config)
|
||||||
|
self.disallowed_packet_types = set(node_info["disallowed_packet_types"])
|
||||||
|
|
||||||
logger.info(f"LetsMesh handler initialized with public key: {public_key_hex[:16]}...")
|
logger.info(f"LetsMesh handler initialized with public key: {public_key_hex[:16]}...")
|
||||||
|
if self.disallowed_packet_types:
|
||||||
|
logger.info(f"Disallowed packet types: {sorted(self.disallowed_packet_types)}")
|
||||||
|
else:
|
||||||
|
logger.info("All packet types allowed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to initialize LetsMesh handler: {e}")
|
logger.error(f"Failed to initialize LetsMesh handler: {e}")
|
||||||
self.letsmesh_handler = None
|
self.letsmesh_handler = None
|
||||||
|
self.disallowed_packet_types = set()
|
||||||
|
else:
|
||||||
|
self.disallowed_packet_types = set()
|
||||||
|
|
||||||
def _get_live_stats(self) -> dict:
|
def _get_live_stats(self) -> dict:
|
||||||
"""Get live stats from RepeaterHandler"""
|
"""Get live stats from RepeaterHandler"""
|
||||||
@@ -75,40 +88,28 @@ class StorageCollector:
|
|||||||
# Publish to LetsMesh if enabled
|
# Publish to LetsMesh if enabled
|
||||||
if self.letsmesh_handler:
|
if self.letsmesh_handler:
|
||||||
try:
|
try:
|
||||||
# Format packet data for LetsMesh publish_packet
|
# Check if packet has type field
|
||||||
if "raw_packet" in packet_record and packet_record["raw_packet"]:
|
if "type" not in packet_record:
|
||||||
# Extract timestamp and format date/time
|
logger.error("Cannot publish to LetsMesh: packet_record missing 'type' field")
|
||||||
timestamp = packet_record.get("timestamp", time.time())
|
return
|
||||||
dt = datetime.fromtimestamp(timestamp)
|
|
||||||
|
packet_type = packet_record["type"]
|
||||||
|
|
||||||
|
if packet_type in self.disallowed_packet_types:
|
||||||
|
logger.debug(f"Skipped publishing packet type 0x{packet_type:02X} (in disallowed types)")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Create LetsMesh packet from record
|
||||||
|
node_name = self.config.get("repeater", {}).get("node_name", "Unknown")
|
||||||
|
letsmesh_packet = LetsMeshPacket.from_packet_record(
|
||||||
|
packet_record,
|
||||||
|
origin=node_name,
|
||||||
|
origin_id=self.letsmesh_handler.public_key
|
||||||
|
)
|
||||||
|
|
||||||
|
if letsmesh_packet:
|
||||||
|
self.letsmesh_handler.publish_packet(letsmesh_packet.to_dict())
|
||||||
|
|
||||||
# Get node name from config
|
|
||||||
node_name = self.config.get("repeater", {}).get("node_name", "Unknown")
|
|
||||||
|
|
||||||
# Format route type (1=Flood->F, 2=Direct->D, etc)
|
|
||||||
route_map = {1: "F", 2: "D"}
|
|
||||||
route = route_map.get(packet_record.get("route", 0), str(packet_record.get("route", 0)))
|
|
||||||
|
|
||||||
letsmesh_packet = {
|
|
||||||
"origin": node_name,
|
|
||||||
"origin_id": self.letsmesh_handler.public_key,
|
|
||||||
"timestamp": dt.isoformat(),
|
|
||||||
"type": "PACKET",
|
|
||||||
"direction": "rx",
|
|
||||||
"time": dt.strftime("%H:%M:%S"),
|
|
||||||
"date": dt.strftime("%-d/%-m/%Y"),
|
|
||||||
"len": str(len(packet_record["raw_packet"]) // 2), # Raw packet length in bytes
|
|
||||||
"packet_type": str(packet_record.get("type", 0)),
|
|
||||||
"route": route,
|
|
||||||
"payload_len": str(packet_record.get("payload_length", 0)),
|
|
||||||
"raw": packet_record["raw_packet"],
|
|
||||||
"SNR": str(packet_record.get("snr", 0)),
|
|
||||||
"RSSI": str(packet_record.get("rssi", 0)),
|
|
||||||
"score": str(int(packet_record.get("score", 0) * 1000)), # Convert to integer score
|
|
||||||
"duration": "0", # Not available in our packet record
|
|
||||||
"hash": packet_record.get("packet_hash", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
self.letsmesh_handler.publish_packet(letsmesh_packet)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to publish packet to LetsMesh: {e}")
|
logger.error(f"Failed to publish packet to LetsMesh: {e}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user