Welcome! The USMPClient class represents a client-side connection. While microcontrollers typically run the C core, you can use the Python USMPClient to build desktop control tools, manage device-to-gateway tests, or create Python-to-Python secure channels.
Class Constructor & Parameters
To instantiate a client, provide the destination server details and your Pre-Shared Key (PSK):
from usmp import USMPClient
client = USMPClient(
host: str,
port: int,
psk: bytes,
device_id: bytes | None = None,
protocol: USMPProtocol | str = USMPProtocol.TCP
)
Parameter Details
host(str): Target hostname or IP address of the USMP server.port(int): Target port number (default is9000).psk(bytes): Pre-Shared Key (PSK). This must match the key expected by the server for this device!device_id(bytes, optional): A unique 6-byte hardware identity. If you omit this parameter, the client automatically generates a random 6-byte identifier at runtime usingos.urandom(6).protocol(USMPProtocol | str, optional): The transport protocol to use (default is"tcp"orUSMPProtocol.TCP):"tcp"(orUSMPProtocol.TCP): Connect over standard TCP socket."udp"(orUSMPProtocol.UDP): Connect over UDP socket with USMP reliability wrapper.
Interface Methods
The client class exposes several asynchronous methods to manage the connection state and exchange data:
await client.connect()
Establishes a raw TCP socket connection and initiates the 4-step cryptographic handshake.
- Raises:
HandshakeErrorif the handshake times out or fails validation;AuthErrorif the server signature does not match.
await client.send(data: bytes)
Encrypts and transmits the payload as a secure PKT_DATA frame (or multiple frames if payload fragmentation is required).
await client.recv() -> bytes
Blocks until the next decrypted PKT_DATA packet is received.
- Note: Under the hood, keepalive
PINGandPONGframes are filtered out automatically.
await client.ping()
Forces an immediate encrypted PKT_PING packet to keep the session active and reset the server-side watchdog timer.
await client.disconnect()
Gracefully sends a PKT_BYE frame to notify the server and closes the underlying TCP socket.
Properties
client.session_id(str | None): Once the handshake completes successfully, this returns the active Session ID as a 32-character hexadecimal string. ReturnsNoneif the client is not connected.
Complete Client Example
Here is a complete script demonstrating how a client connects to a local gateway, exchanges a message, handles responses, and disconnects:
import asyncio
import logging
from usmp import USMPClient
from usmp.errors import AuthError, HandshakeError, USMPError
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("usmp_client")
async def run_client():
# Set up client pointing to localhost
client = USMPClient(
host="127.0.0.1",
port=9000,
psk=b"usmp-dev-psk-change-me-before-prod",
device_id=b"\xaa\xbb\xcc\xdd\xee\xff"
)
try:
logger.info("Connecting to USMP server...")
await client.connect()
logger.info(f"Handshake succeeded! Active Session ID: {client.session_id}")
# Send an encrypted message
message = b"Hello Gateway! Let's talk secure."
logger.info(f"TX: {message.decode('utf-8')}")
await client.send(message)
# Wait for the response
response = await client.recv()
logger.info(f"RX: {response.decode('utf-8')}")
# Disconnect cleanly
logger.info("Closing session...")
await client.disconnect()
logger.info("Disconnected successfully.")
except HandshakeError as e:
logger.error(f"Handshake timed out or failed: {e}")
except AuthError as e:
logger.error(f"PSK authentication failed: {e}")
except USMPError as e:
logger.error(f"USMP Protocol error: {e}")
except Exception as e:
logger.error(f"System error: {e}")
## 5. Robust Reconnection Loop
Unlike the Arduino wrapper which includes a background reconnection engine, a Python client script typically handles reconnects explicitly using an outer retry loop. Here is the recommended pattern:
```python
async def resilient_client():
client = USMPClient(
host="127.0.0.1",
port=9000,
psk=b"usmp-dev-psk-change-me-before-prod",
protocol="udp" # Or "tcp"
)
backoff = 2.0
while True:
try:
logger.info("Attempting connection...")
await client.connect()
logger.info("Connected!")
backoff = 2.0 # Reset backoff on successful connect
# Keep reading telemetry in a loop
while True:
data = await client.recv()
logger.info(f"Received: {data}")
except (USMPError, ConnectionResetError, OSError) as e:
logger.warning(f"Connection lost ({e}). Retrying in {backoff}s...")
await asyncio.sleep(backoff)
backoff = min(backoff * 2, 60.0) # Exponential backoff capped at 60s
finally:
try:
await client.disconnect()
except Exception:
pass
if __name__ == "__main__":
asyncio.run(run_client())