Node API Reference¶
This section documents the node management classes and functions in pyMC_Core.
Dispatcher default path hash mode¶
The dispatcher can apply a default path hash mode to flood packets with 0 hops
that have not already had path hash mode set by the companion. This allows
packets built without the companion (e.g. PacketBuilder.create_flood_advert()
and send_packet()) to use 2- or 3-byte path hashes when the host sets a default.
path_hash_mode:Optional[int] = None— 0=1-byte, 1=2-byte, 2=3-byte;Nonedisables.set_default_path_hash_mode(mode): Set or clear the default;modemust beNone, 0, 1, or 2.
The default is applied only to flood-routed packets with 0 hops. Packets that
already have path hash mode applied by the companion (marked with
_path_hash_mode_applied) are never overwritten.
TRACE and flood: The TRACE payload type is only sent via direct routing in the
firmware (path/SNR in payload, path_len = 0). Flood TRACE is explicitly
unsupported. If send_packet() is called with a packet that is both
flood-routed and TRACE payload type, the dispatcher does not transmit and
returns False.
Repeater forwarding: When implementing repeater-style forwarding of packets as
flood (e.g. path-return or reply floods), preserve the incoming packet's path
hash size on the outgoing packet so repeaters use the same 1-, 2-, or 3-byte
path hashes. For example: use incoming.get_path_hash_size() and call
outgoing.apply_path_hash_mode(size - 1) when building the reply (mode 0=1-byte,
1=2-byte, 2=3-byte). This matches firmware behavior where
sendFlood(..., packet->getPathHashSize()) is used when forwarding.
MeshNode¶
pymc_core.node.node.MeshNode
¶
MeshNode(radio, local_identity, config=None, *, contacts=None, channel_db=None, logger=None, event_service=None)
Thin transport layer for mesh radio communication.
Owns a radio interface and a :class:Dispatcher that handles raw packet
I/O (TX lock, ACK management, handler dispatch). Application-layer
concerns — contact lookup, message building, response waiting — belong in
the companion layer (:class:CompanionBase and its subclasses).
Typical usage::
node = MeshNode(radio, identity, config={...})
await node.start() # blocks in dispatcher.run_forever()
node.stop()
Initialise a mesh network node instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
radio
|
Optional[Any]
|
Radio hardware interface for transmission/reception. |
required |
local_identity
|
LocalIdentity
|
Node's cryptographic identity for secure communication. |
required |
config
|
Optional[dict]
|
Optional configuration dictionary with node settings. |
None
|
contacts
|
Optional[Any]
|
Optional contact storage for managing known nodes. |
None
|
channel_db
|
Optional[Any]
|
Optional channel database for group communication. |
None
|
logger
|
Optional[Logger]
|
Optional logger instance; defaults to module logger. |
None
|
event_service
|
Optional[Any]
|
Optional event service for broadcasting mesh events. |
None
|
send_packet
async
¶
Send a raw packet via the dispatcher.
This is the single transport entry point. All message-building and response-waiting logic lives in the companion layer.
set_event_service
¶
Set the event service for broadcasting mesh events.
start
async
¶
Start the mesh node and begin processing radio communications.
Enters the dispatcher's main event loop for handling incoming/outgoing messages. This method blocks until the node is stopped.
Event System¶
pymc_core.node.events
¶
Mesh event system - event service and event definitions
EventService
¶
Generic event broadcasting service for the mesh library. Allows multiple subscribers to listen for different types of events.
EventSubscriber
¶
Bases: ABC
Abstract base class for event subscribers.
handle_event
abstractmethod
async
¶
Handle an event with the given type and data.
MeshEvents
¶
Standard mesh event types for the mesh library.
Packet Handlers¶
Base Handler¶
pymc_core.node.handlers.base
¶
ACK Handler¶
pymc_core.node.handlers.ack
¶
AckHandler
¶
Bases: BaseHandler
ACK handler that processes all ACK variants: 1. Discrete ACK packets (payload type 1) 2. Bundled ACKs in PATH packets 3. Encrypted ACK responses (20-byte PATH packets)
process_discrete_ack
async
¶
Process a discrete ACK packet and return the CRC if valid.
process_path_ack_variants
async
¶
Process PATH packets that may contain ACKs in different forms. Returns CRC if ACK found, None otherwise.
set_ack_received_callback
¶
Set callback to notify dispatcher when ACK is received.
set_dispatcher
¶
Set dispatcher reference for contact lookup and waiting ACKs.
Advert Handler¶
pymc_core.node.handlers.advert
¶
AdvertHandler
¶
Bases: BaseHandler
__call__
async
¶
Process advert packet and return parsed data with signature verification.
Text Handler¶
pymc_core.node.handlers.text
¶
TextMessageHandler
¶
TextMessageHandler(local_identity, contacts, log_fn, send_packet_fn, event_service=None, radio_config=None)
Bases: BaseHandler
set_command_response_callback
¶
Set callback function for command responses.
Group Text Handler¶
pymc_core.node.handlers.group_text
¶
GroupTextHandler
¶
GroupTextHandler(local_identity, contacts, log_fn, send_packet_fn, channel_db=None, event_service=None, our_node_name=None)
Bases: BaseHandler
Login Response Handler¶
pymc_core.node.handlers.login_response
¶
AnonReqResponseHandler
¶
LoginResponseHandler
¶
Bases: BaseHandler
Handles PAYLOAD_TYPE_RESPONSE packets for login authentication responses.
Expected response format from C++ server: - timestamp (4 bytes): Server response timestamp - response_code (1 byte): RESP_SERVER_LOGIN_OK (0x80) for success - keep_alive_interval (1 byte): Recommended keep-alive interval (secs / 16) - is_admin (1 byte): 1 if admin, 0 if guest - reserved (1 byte): Reserved for future use - random_blob (4 bytes): Random data for packet uniqueness
set_login_callback
¶
Set callback to notify when login response is received.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
Callable[[bool, dict], None]
|
Function that accepts (success: bool, response_data: dict) |
required |
set_protocol_response_handler
¶
Set protocol response handler for forwarding telemetry responses.
store_login_password
¶
Store password for response decryption by destination hash.
Path Handler¶
pymc_core.node.handlers.path
¶
Path packet handler for mesh network routing.
PathHandler
¶
Handler for PATH packets (payload type 0x08) - "Returned path" packets.
According to the official documentation, PATH packets are used for returning responses through the mesh network along discovered routing paths.
Official Packet Structure: - Header [1B]: Route type (0-1) + Payload type (2-5) + Version (6-7) - Path Length [1B]: Length of the path field - Path [up to 64B]: Routing path data (if applicable) - Payload [up to 184B]: The actual data being transmitted
For PATH packets, the payload typically contains: - [1B] dest_hash: Destination node hash - [1B] src_hash: Source node hash - [2B] MAC: Message Authentication Code (for payload version 0x00) - [NB] encrypted_data: Contains ACK or other response data
Protocol Response Handler¶
pymc_core.node.handlers.protocol_response
¶
Protocol response handler for mesh network protocol requests.
Handles responses to protocol requests (like stats, config, etc.) that come back as PATH packets with encrypted payloads.
ProtocolResponseHandler
¶
Handler for protocol responses that come back as encrypted PATH packets.
This handler specifically deals with responses to protocol requests like: - Protocol 0x01: Get repeater stats - Protocol 0x02: Get configuration - etc.
__call__
async
¶
Handle incoming PATH or RESPONSE packet that might be a protocol response.
clear_response_callback
¶
Clear callback for protocol responses from a specific contact.
set_binary_response_callback
¶
Set callback for binary responses. (tag_bytes, response_data, path_info=None). path_info = (out_path, in_path, contact_pubkey) for path-return format.
set_contact_path_updated_callback
¶
Set callback when contact out_path is updated from a decrypted PATH packet.
Signature: (contact_pubkey: bytes, path_len: int, path_bytes: bytes) -> None | Awaitable[None]. Called after _update_contact_path when the contact was found and updated.
set_login_response_handler
¶
Set login handler ref for checking active login state.
set_packet_injector
¶
Set packet injector for sending reciprocal PATH packets.
When the companion receives a flooded PATH from a remote repeater, the C++ firmware sends a reciprocal PATH back so the remote repeater learns the route to us (Mesh.cpp:168-169). Without this, the remote repeater has no out_path for us and must fall back to plain FLOOD for responses — which intermediate repeaters may drop due to transport-code region filtering.
set_response_callback
¶
Set callback for protocol responses from a specific contact.
Trace Handler¶
pymc_core.node.handlers.trace
¶
Trace packet handler for mesh network diagnostics.
Handles trace packets that contain SNR and routing information for network diagnostics and analysis.
TraceHandler
¶
Handler for trace packets (payload type 0x09).
Trace packets are used for network diagnostics and routing analysis. They contain tag, auth_code, flags, and trace path information with SNR data.
def __init__(self):
"""Initialize the packet dispatcher."""
def register_handler(
self,
packet_type: PacketType,
handler: Callable[[Packet], Awaitable[None]]
) -> None:
"""Register a handler for a specific packet type."""
def unregister_handler(self, packet_type: PacketType) -> None:
"""Remove handler for a packet type."""
async def dispatch_packet(self, packet: Packet) -> None:
"""Dispatch packet to registered handler."""
def get_registered_types(self) -> List[PacketType]:
"""Get list of registered packet types."""
```
Event System¶
python
class EventEmitter:
"""Simple event emission system for node events."""
def on(self, event: str, callback: Callable) -> None:
"""Register event callback."""
def off(self, event: str, callback: Callable) -> None:
"""Remove event callback."""
def emit(self, event: str, *args, **kwargs) -> None:
"""Emit an event to all registered callbacks."""
Node Events¶
The mesh node emits the following events:
packet_received: When a packet is receivedpacket_sent: When a packet is successfully sentnode_discovered: When a new node is discoverednode_lost: When a node becomes unreachablenetwork_error: When a network error occurs
# Example event handling
node = MeshNode(radio, identity)
@node.on('packet_received')
async def handle_packet(packet: Packet):
print(f"Received packet from {packet.source.hex()[:8]}")
@node.on('node_discovered')
async def handle_discovery(node_id: bytes):
print(f"Discovered node {node_id.hex()[:8]}")
Packet Handlers¶
ACK Handler¶
class AckHandler:
"""Handles acknowledgment packets."""
def __init__(self, node: MeshNode):
"""Initialize ACK handler."""
async def handle_ack(self, packet: Packet) -> None:
"""Process incoming ACK packet."""
Advert Handler¶
class AdvertHandler:
"""Handles node advertisement packets."""
def __init__(self, node: MeshNode):
"""Initialize advert handler."""
async def handle_advert(self, packet: Packet) -> None:
"""Process incoming advertisement."""
def get_known_nodes(self) -> List[bytes]:
"""Get list of known node IDs."""
Text Handler¶
class TextHandler:
"""Handles text message packets."""
def __init__(self, node: MeshNode):
"""Initialize text handler."""
async def handle_text(self, packet: Packet) -> None:
"""Process incoming text message."""
async def send_text(
self,
destination: bytes,
message: str
) -> None:
"""Send a text message to destination."""
Group Text Handler¶
class GroupTextHandler:
"""Handles group text message packets."""
def __init__(self, node: MeshNode):
"""Initialize group text handler."""
async def handle_group_text(self, packet: Packet) -> None:
"""Process incoming group message."""
async def send_group_text(
self,
group_id: bytes,
message: str
) -> None:
"""Send message to group."""
def create_group(self, group_name: str) -> bytes:
"""Create a new message group."""
Node Configuration¶
@dataclass
class NodeConfig:
"""Configuration options for mesh nodes."""
max_hops: int = 16
packet_timeout: float = 30.0
ack_timeout: float = 5.0
retransmit_attempts: int = 3
broadcast_interval: float = 60.0
keep_alive_interval: float = 300.0
Node Statistics¶
@dataclass
class NodeStats:
"""Runtime statistics for a mesh node."""
packets_sent: int = 0
packets_received: int = 0
packets_forwarded: int = 0
acks_sent: int = 0
acks_received: int = 0
retransmits: int = 0
known_nodes: int = 0
uptime: float = 0.0
def reset(self) -> None:
"""Reset all statistics to zero."""
Error Handling¶
class NodeError(Exception):
"""Base exception for node-related errors."""
pass
class NetworkTimeoutError(NodeError):
"""Raised when network operations timeout."""
pass
class InvalidPacketError(NodeError):
"""Raised when an invalid packet is received."""
pass
class RadioError(NodeError):
"""Raised when radio hardware errors occur."""
pass