Welcome! An instance of USMPSession represents a fully established, secure USMP connection.
On the server side, a session object is passed directly into your registered @server.on_session callback handler. On the client side, the session logic is wrapped under the hood inside USMPClient. This guide explains the properties, interactive methods, and reassembly features of the session object.
Properties
You can query these properties to identify the connected device and monitor session health:
session.device_id(str): The client's unique hardware address, formatted as a hexadecimal string with colons (e.g.,"00:11:22:33:44:55").session.session_id(str): A unique session ID generated during the handshake, formatted as a 32-character hex string.session.is_connected(bool): ReturnsTrueif the session is currently established and active;Falseif closed or torn down.session.info(SessionInfo): Access underlying session metadata (negotiated cipher suite, active sequence counters, transport type).
Interactive Methods
await session.send(data: bytes | str)
Encrypts and transmits a payload to the device.
- String & Binary Support: Accepts raw
bytesor astr(which is automatically encoded as UTF-8). - Payload Fragmentation: If your payload exceeds the single-frame plaintext limit of 452 bytes,
send()automatically fragments it into multiple chunks. The initial chunks are transmitted asPKT_DATA_FRAGframes, followed by a finalPKT_DATAframe. - Size Constraint: The dynamic fragmenter supports up to 4 frames, giving an absolute maximum payload size of 1808 bytes (~1.8 KB). Attempting to send a larger payload raises a
PayloadErrorexception.
await session.recv(timeout: float | None = None) -> bytes
Blocks until the next decrypted message payload is received and verified.
- Payload Reassembly: Transparently accumulates
PKT_DATA_FRAGframes, decrypts each piece, and joins them back together once the finalPKT_DATAframe arrives. - Control Frame Filtering: Intercepts
PINGframes and automatically replies withPONGframes in the background without waking application code. - Timeout: If a custom
timeout(in seconds) is specified and no frame arrives,USMPTimeoutErroris raised.
await session.recv_str(timeout: float | None = None, encoding: str = "utf-8", errors: str = "strict") -> str
Convenience wrapper that awaits session.recv() and directly decodes the decrypted payload into a string using the specified encoding.
session.print(data: Any, *, tag: str | None = None, stream: Any = None)
Built-in terminal payload inspector. Formats incoming or outgoing data with ANSI timestamps, device ID badges, and byte counts. Automatically detects UTF-8 strings versus binary data (displaying readable text in quotes or a formatted hex:<hexbytes> dump):
# Displays: 18:20:15 [session] [00:11:22:33:44:55] (14 bytes): 'Device Status'
session.print("Device Status")
# Displays: 18:20:15 [session] [00:11:22:33:44:55] [SENSOR] (4 bytes): hex:01020304
session.print(b"\x01\x02\x03\x04", tag="SENSOR")
await session.ping()
Pushes an encrypted PKT_PING frame to the device. This is useful for checking if a device is still online and resetting the socket watchdog.
await session.rekey()
Performs transparent in-band session key rotation.
- Key Derivation: Generates 32 bytes of random entropy (
salt), sends an encryptedPKT_REKEY(0x0B) frame, and derives fresh transmit and receive keys usingHKDF-SHA256. - State Reset: Resets both sequence numbers (
tx_seq = 0,rx_seq = 0) and the 64-bit sliding anti-replay bitmap to0. - Non-Disruptive: Rotates keys while keeping the socket established, eliminating the overhead of a full X25519 handshake.
await session.bye()
Sends an encrypted PKT_BYE frame to notify the device of a clean shutdown, then closes the TCP connection.
Complete Session Reader/Writer Example
The standard way to manage a session is to run session.recv() inside a structured while True loop to process commands and handle timeouts:
from usmp import USMPSession
from usmp.errors import ConnectionClosedError, CryptoError, TimeoutError
async def handle_device_session(session: USMPSession):
print(f"[START] Monitoring session for device: {session.device_id}")
try:
while True:
# Set a 60-second read timeout. If the client goes silent (fails
# to send telemetry or a PING) for 60 seconds, we close the link.
data = await session.recv(timeout=60.0)
# Simple command parser
if data == b"GET_STATUS":
await session.send(b"STATUS_OK")
elif data == b"SHUTDOWN":
print(f"[{session.device_id}] Requested graceful exit.")
await session.send(b"BYE_ACK")
await session.bye()
break
else:
print(f"[{session.device_id}] Received data: {data!r}")
except TimeoutError:
print(f"[TIMEOUT] Device {session.device_id} went offline (no keepalives).")
except ConnectionClosedError:
print(f"[CLOSED] Device {session.device_id} disconnected cleanly.")
except CryptoError as e:
print(f"[CRYPTO ERROR] Decryption failed for {session.device_id}: {e}")
finally:
print(f"[STOP] Session handler terminated for {session.device_id}")