Skip to content

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; None disables.
  • set_default_path_hash_mode(mode): Set or clear the default; mode must be None, 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_packet(pkt, *, wait_for_ack=False, **kwargs)

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_event_service(event_service)

Set the event service for broadcasting mesh events.

start async

start()

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.

stop

stop()

Stop the mesh node and clean up associated services.

Event System

pymc_core.node.events

Mesh event system - event service and event definitions

EventService

EventService(logger=None)

Generic event broadcasting service for the mesh library. Allows multiple subscribers to listen for different types of events.

publish async

publish(event_type, data)

Publish an event to all subscribers.

publish_sync

publish_sync(event_type, data)

Publish an event synchronously (creates async task).

subscribe

subscribe(event_type, subscriber)

Subscribe to a specific event type.

subscribe_all

subscribe_all(subscriber)

Subscribe to all events (global subscriber).

unsubscribe

unsubscribe(event_type, subscriber)

Unsubscribe from a specific event type.

unsubscribe_all

unsubscribe_all(subscriber)

Unsubscribe from all events.

EventSubscriber

Bases: ABC

Abstract base class for event subscribers.

handle_event abstractmethod async

handle_event(event_type, data)

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

BaseHandler

Bases: ABC

payload_type abstractmethod staticmethod

payload_type()

Return the payload type this handler processes

ACK Handler

pymc_core.node.handlers.ack

AckHandler

AckHandler(log_fn, dispatcher=None)

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)

__call__ async

__call__(packet)

Handle discrete ACK packets (payload type 1).

process_discrete_ack async

process_discrete_ack(packet)

Process a discrete ACK packet and return the CRC if valid.

process_path_ack_variants async

process_path_ack_variants(packet)

Process PATH packets that may contain ACKs in different forms. Returns CRC if ACK found, None otherwise.

set_ack_received_callback

set_ack_received_callback(callback)

Set callback to notify dispatcher when ACK is received.

set_dispatcher

set_dispatcher(dispatcher)

Set dispatcher reference for contact lookup and waiting ACKs.

pymc_core.node.handlers.advert

AdvertHandler

AdvertHandler(log_fn, event_service=None)

Bases: BaseHandler

__call__ async

__call__(packet)

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_command_response_callback(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

__call__ async

__call__(packet)

Handle incoming group text messages according to the specification.

set_our_node_name

set_our_node_name(name)

Update the node name used for echo detection (e.g. after set_advert_name).

Login Response Handler

pymc_core.node.handlers.login_response

AnonReqResponseHandler

AnonReqResponseHandler(local_identity, contacts, log_fn)

Bases: BaseHandler

Handler for ANON_REQ packets that might be login responses.

__call__ async

__call__(packet)

Check if this ANON_REQ is actually a login response.

LoginResponseHandler

LoginResponseHandler(local_identity, contacts, log_fn, login_callback=None)

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

__call__ async

__call__(packet)

Handle RESPONSE or ANON_REQ packets for login authentication.

clear_login_password

clear_login_password(dest_hash)

Clear stored password for destination hash.

set_login_callback

set_login_callback(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(protocol_response_handler)

Set protocol response handler for forwarding telemetry responses.

store_login_password

store_login_password(dest_hash, password)

Store password for response decryption by destination hash.

Path Handler

pymc_core.node.handlers.path

Path packet handler for mesh network routing.

PathHandler

PathHandler(log_fn, ack_handler=None, protocol_response_handler=None, login_response_handler=None)

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

__call__ async

__call__(pkt)

Handle incoming PATH packet according to official specification.

set_ack_handler

set_ack_handler(ack_handler)

Set the ACK handler for processing bundled/encrypted ACKs in PATH packets.

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

ProtocolResponseHandler(log_fn, local_identity, contact_book)

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

__call__(pkt)

Handle incoming PATH or RESPONSE packet that might be a protocol response.

clear_response_callback

clear_response_callback(contact_hash)

Clear callback for protocol responses from a specific contact.

set_binary_response_callback

set_binary_response_callback(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_contact_path_updated_callback(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_response_handler(handler)

Set login handler ref for checking active login state.

set_packet_injector

set_packet_injector(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_response_callback(contact_hash, 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

TraceHandler(log_fn, protocol_response_handler=None)

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.

__call__ async

__call__(pkt)

Handle incoming trace packet and return parsed data.

clear_response_callback

clear_response_callback(contact_hash)

Clear callback for trace responses from a specific contact.

set_response_callback

set_response_callback(contact_hash, callback)

Set callback for trace responses from a specific contact.

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 received
  • packet_sent: When a packet is successfully sent
  • node_discovered: When a new node is discovered
  • node_lost: When a node becomes unreachable
  • network_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."""
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