-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathinterface.py
More file actions
123 lines (98 loc) · 3.77 KB
/
Copy pathinterface.py
File metadata and controls
123 lines (98 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""Abstract :class:`RadioInterface` the bot talks to.
Concrete adapters live under their protocol package (e.g.
:mod:`src.meshtastic.radio` for Meshtastic). The bot must never reach past
this interface — anything protocol-specific belongs behind it.
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Callable, Optional
from src.radio.events import (
ConnectionEstablished,
IncomingPacket,
IncomingTextMessage,
NodeUpdate,
)
@dataclass
class RadioHandlers:
"""Callback set the bot registers with a :class:`RadioInterface`.
Any handler may be ``None``; an adapter must tolerate missing ones.
All handlers are invoked synchronously inside the radio's receive thread,
so adapters wrap them with error boundaries (see :mod:`src.radio.errors`).
"""
on_packet: Optional[Callable[[IncomingPacket], None]] = None
on_text_message: Optional[Callable[[IncomingTextMessage], None]] = None
on_node_update: Optional[Callable[[NodeUpdate], None]] = None
on_connection_established: Optional[Callable[[ConnectionEstablished], None]] = None
on_disconnected: Optional[Callable[[Optional[Exception]], None]] = None
class RadioInterface(ABC):
"""Protocol-agnostic radio façade.
Lifecycle: :meth:`set_handlers` → :meth:`connect` → events flow → optionally
:meth:`disconnect`.
"""
@abstractmethod
def set_handlers(self, handlers: RadioHandlers) -> None:
"""Register the callback set the radio invokes for incoming events."""
@abstractmethod
def connect(self) -> None:
"""Open the radio connection and start receiving.
On a successful connection the adapter must invoke
``handlers.on_connection_established`` exactly once. Adapters are
expected to handle their own reconnect/backoff and translate fatal
failures into ``handlers.on_disconnected`` events; the bot does not
manage the transport.
"""
@abstractmethod
def disconnect(self) -> None:
"""Close the radio connection. Idempotent."""
@property
@abstractmethod
def is_connected(self) -> bool:
"""``True`` once a connection has been established and not yet lost."""
@property
@abstractmethod
def local_node_id(self) -> Optional[str]:
"""Local node id in canonical hex form (``!aabbccdd``), or ``None``
before the connection is established."""
@property
@abstractmethod
def local_nodenum(self) -> Optional[int]:
"""Meshtastic local node id as an unsigned int, or ``None`` pre-connect.
MeshCore radios return ``None``; use ``local_node_id`` (``mc:…``) instead.
"""
# --- send-side ---------------------------------------------------------
@abstractmethod
def send_text(
self,
text: str,
*,
destination_id: Optional[str] = None,
channel: int = 0,
want_ack: bool = False,
hop_limit: Optional[int] = None,
) -> None:
"""Send a text message.
``destination_id`` is a canonical hex id for a DM, or ``None`` to
broadcast on ``channel``.
"""
@abstractmethod
def send_reaction(
self,
emoji: str,
message_id: int,
*,
destination_id: Optional[str] = None,
channel: int = 0,
) -> None:
"""React to a previously-received message with an emoji."""
@abstractmethod
def send_traceroute(
self,
target_node_id: int,
*,
channel_index: int = 0,
) -> None:
"""Send a Meshtastic traceroute (``target_node_id`` is numeric nodenum).
MeshCore and other adapters that do not support traceroute should raise
:class:`~src.radio.errors.RadioError`.
"""