fix(plugin): implement BasePlatformAdapter abstract methods and proper build_source event
release / test-and-release (push) Failing after 12s

This commit is contained in:
2026-09-07 19:45:37 -07:00
parent 7136f8c0fd
commit 2d9106b864
2 changed files with 47 additions and 20 deletions
+46 -19
View File
@@ -14,13 +14,24 @@ import time
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
try: try:
from gateway.platforms.base import BasePlatformAdapter, SendResult from gateway.platforms.base import (
from gateway.platforms.event import MessageEvent, MessageType BasePlatformAdapter,
MessageEvent,
MessageType,
SendResult,
)
except ImportError: except ImportError:
# Allow importing and testing utility functions without gateway in environment # Allow importing and testing utility functions without gateway in environment
class BasePlatformAdapter: # type: ignore[no-redef] class BasePlatformAdapter: # type: ignore[no-redef]
def __init__(self, config: Any): def __init__(self, config: Any, platform: Any = "meshtastic"):
self.config = config self.config = config
self.platform = platform
def build_source(self, **kwargs):
return kwargs
async def handle_message(self, event: Any) -> None:
pass
class SendResult: # type: ignore[no-redef] class SendResult: # type: ignore[no-redef]
def __init__(self, success: bool, message_id: Optional[str] = None, error: Optional[str] = None): def __init__(self, success: bool, message_id: Optional[str] = None, error: Optional[str] = None):
@@ -107,7 +118,7 @@ class MeshtasticAdapter(BasePlatformAdapter):
"""Hermes gateway adapter for Meshtastic LoRa radios over TCP.""" """Hermes gateway adapter for Meshtastic LoRa radios over TCP."""
def __init__(self, config: Any): def __init__(self, config: Any):
super().__init__(config) super().__init__(config, platform="meshtastic")
self.extra = getattr(config, "extra", {}) or {} self.extra = getattr(config, "extra", {}) or {}
self.host = self.extra.get("host", "meshtastic.local") self.host = self.extra.get("host", "meshtastic.local")
self.port = int(self.extra.get("port", 4403)) self.port = int(self.extra.get("port", 4403))
@@ -191,24 +202,32 @@ class MeshtasticAdapter(BasePlatformAdapter):
to_id = packet.get("toId") to_id = packet.get("toId")
is_dm = to_id != "^all" and packet.get("to") == my_node_num is_dm = to_id != "^all" and packet.get("to") == my_node_num
chat_id = from_id if is_dm else f"channel_{ch}" chat_id = from_id if is_dm else f"channel_{ch}"
chat_type = "dm" if is_dm else "channel"
rx_snr = packet.get("rxSnr", 0.0) rx_snr = packet.get("rxSnr", 0.0)
hop_limit = packet.get("hopLimit", 0)
logger.info( logger.info(
"[Meshtastic] Inbound message from %s on ch=%s (is_dm=%s, SNR=%.1fdB): %s", "[Meshtastic] Inbound message from %s on ch=%s (is_dm=%s, SNR=%.1fdB): %s",
from_id, ch, is_dm, rx_snr, text from_id, ch, is_dm, rx_snr, text
) )
# Build Hermes MessageEvent # Build Hermes SessionSource and MessageEvent
event = MessageEvent( source = self.build_source(
platform="meshtastic",
message_type=MessageType.TEXT,
text=text,
sender_id=from_id,
sender_name=from_id,
chat_id=chat_id, chat_id=chat_id,
chat_name=f"Meshtastic {chat_id}",
chat_type=chat_type,
user_id=from_id,
user_name=from_id,
message_id=str(packet.get("id", int(time.time()))),
)
event = MessageEvent(
text=text,
message_type=MessageType.TEXT,
user_id=from_id,
user_name=from_id,
source=source,
raw_message=packet,
message_id=str(packet.get("id", int(time.time()))), message_id=str(packet.get("id", int(time.time()))),
raw_event=packet,
) )
if self._loop and self._loop.is_running(): if self._loop and self._loop.is_running():
@@ -217,18 +236,18 @@ class MeshtasticAdapter(BasePlatformAdapter):
except Exception as exc: except Exception as exc:
logger.exception("[Meshtastic] Error processing received packet: %s", exc) logger.exception("[Meshtastic] Error processing received packet: %s", exc)
async def send_message( async def send(
self, self,
chat_id: str, chat_id: str,
message: str, content: str,
reply_to_id: Optional[str] = None, reply_to: Optional[str] = None,
**kwargs: Any, metadata: Optional[Dict[str, Any]] = None,
) -> SendResult: ) -> SendResult:
"""Send a message out over LoRa.""" """Send a message out over LoRa (implements BasePlatformAdapter.send)."""
if not self._connected or not self._iface: if not self._connected or not self._iface:
return SendResult(success=False, error="Meshtastic radio not connected") return SendResult(success=False, error="Meshtastic radio not connected")
clean_text = strip_markdown(message) clean_text = strip_markdown(content)
chunks = chunk_text(clean_text, MAX_LORA_MESSAGE_LENGTH) chunks = chunk_text(clean_text, MAX_LORA_MESSAGE_LENGTH)
# Destination: if chat_id starts with '!', it is a direct node ID # Destination: if chat_id starts with '!', it is a direct node ID
@@ -265,6 +284,14 @@ class MeshtasticAdapter(BasePlatformAdapter):
logger.exception("[Meshtastic] Failed to send message: %s", exc) logger.exception("[Meshtastic] Failed to send message: %s", exc)
return SendResult(success=False, error=str(exc)) return SendResult(success=False, error=str(exc))
async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
"""Get chat/channel info (implements BasePlatformAdapter.get_chat_info)."""
is_dm = chat_id.startswith("!")
return {
"name": f"Node {chat_id}" if is_dm else f"Channel {chat_id}",
"type": "dm" if is_dm else "channel",
}
async def disconnect(self) -> None: async def disconnect(self) -> None:
"""Disconnect and clean up resources.""" """Disconnect and clean up resources."""
self._running = False self._running = False
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "hermes-meshtastic" name = "hermes-meshtastic"
version = "0.1.1" version = "0.1.2"
description = "Meshtastic LoRa mesh gateway adapter plugin for Hermes Agent" description = "Meshtastic LoRa mesh gateway adapter plugin for Hermes Agent"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"