mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-05-13 19:13:20 +00:00
4979687299
BUG-001: web viewer login/session auth (in web viewer commit) BUG-002: db_manager ALTER TABLE for missing channel_operations and feed_message_queue columns on startup BUG-015: scheduler thread blocked on future.result(); replaced all blocking waits with add_done_callback (fire-and-forget) BUG-016: reboot_radio sends meshcore.commands.reboot() before disconnect BUG-017: radio disconnect uses asyncio.wait_for(timeout=10) BUG-022: custom asyncio loop exception handler suppresses IndexError from meshcore parser at DEBUG level BUG-024: last_db_backup_run updated after each run; 2-min startup window; last-run seeded from DB on restart BUG-025: send_channel_message retries up to 2 times (2s delay) on no_event_received via _is_no_event_received() helper BUG-026: split_text_into_chunks() and get_max_message_length() added to CommandManager; keyword dispatch uses send_response_chunked() BUG-028: byte_data = b"" initialised before try block in decode_meshcore_packet to prevent UnboundLocalError in except handler TraceCommand: path nodes reversed and return path truncated; fixed format_elapsed_display: UTC normalisation before elapsed computation (#75) RepeaterManager: auto_manage_contacts guard before any purge logic (#50) Command aliases: [Aliases] config section injects shorthands at startup JSON logging: _JsonFormatter; json_logging = true in [Logging] Structured JSON logging compatible with Loki, Elasticsearch, Splunk Discord bridge, Telegram bridge, and all service plugins updated MeshGraph edge promotion logic corrected Shutdown: scheduler and meshcore disconnect joined cleanly; log spam fixed All modules: ruff and mypy cleanup applied (type annotations, imports)
112 lines
3.3 KiB
Python
112 lines
3.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Base service plugin class for background services
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Any, Optional
|
|
|
|
|
|
class BaseServicePlugin(ABC):
|
|
"""Base class for background service plugins.
|
|
|
|
This class defines the interface for service plugins, which are long-running
|
|
background tasks that can interact with the bot and mesh network. It manages
|
|
service lifecycle (start/stop) and metadata.
|
|
"""
|
|
|
|
# Optional: Config section name (if different from class name)
|
|
# If not set, will be derived from class name (e.g., PacketCaptureService -> PacketCapture)
|
|
config_section: Optional[str] = None
|
|
|
|
# Optional: Service description for metadata
|
|
description: str = ""
|
|
|
|
def __init__(self, bot: Any):
|
|
"""Initialize the service plugin.
|
|
|
|
Args:
|
|
bot: The MeshCoreBot instance containing the service.
|
|
"""
|
|
self.bot = bot
|
|
self.logger = bot.logger
|
|
self.enabled = True
|
|
self._running = False
|
|
|
|
@abstractmethod
|
|
async def start(self) -> None:
|
|
"""Start the service.
|
|
|
|
This method should:
|
|
- Setup event handlers if needed
|
|
- Start background tasks
|
|
- Initialize any required resources
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
async def stop(self) -> None:
|
|
"""Stop the service.
|
|
|
|
This method should:
|
|
- Clean up event handlers
|
|
- Stop background tasks
|
|
- Close any open resources
|
|
"""
|
|
pass
|
|
|
|
def get_metadata(self) -> dict[str, Any]:
|
|
"""Get service metadata.
|
|
|
|
Returns:
|
|
Dict[str, Any]: Dictionary containing service metadata (name, status, etc.).
|
|
"""
|
|
return {
|
|
'name': self._derive_service_name(),
|
|
'class_name': self.__class__.__name__,
|
|
'description': getattr(self, 'description', ''),
|
|
'enabled': self.enabled,
|
|
'running': self._running,
|
|
'config_section': self.config_section or self._derive_config_section()
|
|
}
|
|
|
|
def _derive_service_name(self) -> str:
|
|
"""Derive service name from class name.
|
|
|
|
Returns:
|
|
str: Derived service name (e.g., 'PacketCaptureService' -> 'packetcapture').
|
|
"""
|
|
class_name = self.__class__.__name__
|
|
if class_name.endswith('Service'):
|
|
return class_name[:-7].lower() # Remove 'Service' suffix and lowercase
|
|
return class_name.lower()
|
|
|
|
def _derive_config_section(self) -> str:
|
|
"""Derive config section name from class name.
|
|
|
|
Returns:
|
|
str: Derived config section name.
|
|
"""
|
|
if self.config_section:
|
|
return self.config_section
|
|
|
|
class_name = self.__class__.__name__
|
|
if class_name.endswith('Service'):
|
|
return class_name[:-7] # Remove 'Service' suffix
|
|
return class_name
|
|
|
|
def is_running(self) -> bool:
|
|
"""Check if the service is currently running.
|
|
|
|
Returns:
|
|
bool: True if the service is running, False otherwise.
|
|
"""
|
|
return self._running
|
|
|
|
def is_healthy(self) -> bool:
|
|
"""Report whether the service is healthy. Default: healthy if running.
|
|
Override in subclasses for connection-specific checks (e.g. meshcore, MQTT).
|
|
"""
|
|
return self._running
|
|
|