Skip to main content

hxtp-py (Python SDK)

The hxtp-py library lets Python scripts, automation tools, and AI services talk to Hestia Labs devices using HxTP/3.1.

Installation​

pip install hxtp-py

Quick Start​

import asyncio
from hxtp_py.client import HxTPClient

async def main():
client = HxTPClient(
url="https://api.hestialabs.in/api/v1",
tenant_id="your-tenant-id",
device_id="your-device-id",
private_key_hex="your-private-key-hex", # Ed25519 private key
client_id="my-app",
)

# Connects → sends HELLO → waits for HELLO_ACK → becomes ACTIVE
await client.connect()

response = await client.send_command({
"action": "set_level",
"params": {"brightness": 85},
})

print(f"Sent! Message ID: {response.message_id}")
await client.disconnect()

asyncio.run(main())

How It Works​

The Lifecycle​

Every client goes through a simple introduction process:

IDLE → HELLO_SENT (sends "hello" with public key)
→ ACTIVE (cloud replies "hello_ack")
→ DISCONNECTED (connection drops)
  • connect() sends a signed HELLO message with your public key.
  • The cloud replies HELLO_ACK — now the client is ACTIVE.
  • You can only send commands and receive messages while ACTIVE.
  • If the connection drops, the client reconnects and repeats the handshake.

Sending Commands​

response = await client.send_command({
"action": "toggle_led",
"params": {"brightness": 100},
})

Sending Other Message Types​

# Send telemetry data
await client.send_message("telemetry", {"temperature": 22.5})

# Send state update
await client.send_message("state", {"door": "open"})

Receiving Messages​

def on_message(event):
print(f"Received: {event.parsed}")
print(f"Action: {event.parsed.get('action')}")

client.on_message(on_message)

Client Configuration​

ParameterTypeRequiredDescription
urlstringYesServer address.
tenant_idstringYesYour organization's ID.
device_idstringYesYour device's unique ID.
private_key_hexstringYesYour Ed25519 private key (64 hex chars).
client_idstringYesA unique name for this app instance.
previous_private_key_hexstringNoPrevious private key (used during key rotation).
transportTransportNoTransport layer (default: WebSocket).
replay_protectionboolNoPrevent replay attacks (default: True).

Events​

MethodPayloadWhen it fires
on_connect(cb)NoneClient becomes ACTIVE.
on_disconnect(cb)code, reasonConnection lost.
on_message(cb)HxTPMessageEventA verified message arrives.
on_error(cb)HxTPErrorEventSomething went wrong.

Sync Wrapper​

For non-async code:

from hxtp_py.client import SyncHxTPClient

client = SyncHxTPClient(
url="https://api.hestialabs.in/api/v1",
tenant_id="your-tenant-id",
device_id="your-device-id",
private_key_hex="your-private-key-hex",
client_id="my-app",
)

client.connect()
response = client.send_command({"action": "toggle_led"})
client.disconnect()

Low-Level Protocol Usage​

Build a Canonical String​

from hxtp_py.core import build_canonical

canonical = build_canonical({
"version": "HxTP/3.1",
"device_id": "device-uuid",
"tenant_id": "tenant-uuid",
"client_id": "client-uuid",
"message_id": "msg-uuid",
"request_id": "req-uuid",
"sequence_number": 1,
"timestamp": 1708444800,
"nonce": "unique-nonce",
"message_type": "command",
"payload_hash": "sha256-of-params",
})

Sign and Verify​

from hxtp_py.crypto import sign_ed25519, verify_ed25519

from hxtp_py.crypto import get_public_key
# Sign
priv_key = bytes.fromhex("your-private-key-hex")
signature = sign_ed25519(priv_key, canonical)

# Derive public key from private key
pub_key = get_public_key(priv_key)

# Verify
valid = verify_ed25519(pub_key, canonical, signature)

Validate an Incoming Message​

from hxtp_py.validation import validate_message

result = validate_message(
parsed_message,
public_key_hex="device-public-key-hex",
)

7-Step Validation​

Every incoming message goes through these checks:

  1. Version — Is it HxTP/3.1?
  2. Timestamp — Was it sent recently?
  3. Payload Size — Is it within limits?
  4. Nonce — Is it unique? (replay protection)
  5. Payload Hash — Does the content match its fingerprint?
  6. Sequence — Are messages in order?
  7. Signature — Is the Ed25519 signature valid?

If any check fails, the message is rejected immediately.