Skip to content

About

Lightweight HiveMind satellite client for MicroPython 1.20+ and CPython 3.10+ on constrained devices

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

hivemind-micropython-client

Experimental — not yet tested on hardware. This client was written without a device in the loop. It has not run on a physical microcontroller yet. Treat it as a starting point, not a finished product. Test it on your own hardware before you rely on it, and please report what breaks.

Lightweight HiveMind satellite client for MicroPython 1.20+ and CPython 3.10+, from one shared pure-Python codebase.

A satellite is an edge device that captures user input (text, audio, sensors) and forwards it to a central hub (hivemind-core). The hub runs the AI reasoning (intent parsing, skills, TTS) and sends responses back. This client runs the full HiveMind handshake and encrypted messaging on a microcontroller. That lets an ESP32 or Raspberry Pi Pico act as a satellite without running OVOS on-device.

ESP32 / Pico (this client)  ⇄  HiveMind hub (hivemind-core)  ⇄  OVOS skills

Features

  • Tiny: pure Python plus the MicroPython stdlib. No pip packages are required on-device.
  • Encrypted: the protocol v3 Noise handshake with ChaCha20-Poly1305, keyed by a 32-byte PSK provisioned from your password (see Protocol v3). The PBKDF2-HMAC-SHA256 password handshake is only for a legacy hub, with legacy_hub=True.
  • Dual platform: runs on MicroPython hardware and on CPython for development and testing, auto-detected at import.
  • Binary transport: bitstring-encoded frames for streaming audio to and from the hub.
  • Async: non-blocking I/O on uasyncio (MicroPython) or asyncio (CPython).

Prerequisites

  • A microcontroller with networking running MicroPython 1.20+ (ESP32, Raspberry Pi Pico W, etc.), or CPython 3.10+ for desktop testing.
  • A running HiveMind hub (hivemind-core) reachable on the same network.
  • A client credential (username, access key, password) issued by the hub with hivemind-core add-client.

Install

MicroPython (on-device)

mpremote mip install github:JarbasHiveMind/hivemind-micropython-client

Or from the REPL:

import mip
mip.install("github:JarbasHiveMind/hivemind-micropython-client")

CPython (development / testing)

Run from a checkout with the hivemind/ package on sys.path. For the CPython transport and faster crypto, install the optional websockets and cryptography packages. The JSON-Z85B / JSON-Z85P / JSON-B91 encodings also need z85base91. All of these are optional. The code falls back to pure Python when they are absent.

git clone https://github.com/JarbasHiveMind/hivemind-micropython-client
cd hivemind-micropython-client
pip install websockets cryptography z85base91   # optional accelerators

Quickstart

1. Register the satellite on the hub (where hivemind-core is installed):

hivemind-core add-client --name esp32 \
  --access-key "your-access-key" --password "your-password"

Note the access key and password.

2. Connect and send a message. On CPython this runs as-is. On MicroPython, bring up Wi-Fi first (see examples/text_satellite.py).

import asyncio
from hivemind.client import HiveMindClient

async def main():
    client = HiveMindClient(
        host="192.168.1.100",     # the hub's address
        port=5678,
        username="esp32",
        access_key="your-access-key",
        password="your-password",
        site_id="my-esp32",
    )

    def on_bus_message(msg_type, data, context):
        if msg_type == "speak":
            print("hub says:", data.get("utterance"))

    async def on_connected():
        print("connected to hub")
        await client.send_utterance("what time is it")

    client.on_bus_message = on_bus_message
    client.on_connected = on_connected

    await client.connect()   # handshake, then receive loop

asyncio.run(main())

connect() performs the encrypted handshake, fires on_connected, then runs the receive loop. send_utterance forwards text to the hub. The hub's speak reply arrives on on_bus_message.

How the connection works

The handshake derives a session key from your password and negotiates a cipher, then all messages are encrypted:

Client                          Hub
  │ ── WebSocket connect ──────→ │
  │ ←─ HELLO (+ server pubkey) ─ │
  │ ── SHAKE (hsub) ───────────→ │
  │ ←─ SHAKE (cipher choice) ─── │
  │   PBKDF2-HMAC-SHA256 key derivation (100k iterations, both sides)
  │ ── HELLO (encrypted) ──────→ │
  │ ←─ READY ──────────────────  │
  └─ on_connected(); ready to send/receive

The session key is PBKDF2(password) mixed with the two 8-byte IVs exchanged in the handshake. Both ciphers and all encodings are byte-for-byte compatible with hivemind-core and the reference hivemind-websocket-client.

Protocol v3 (Noise handshake)

The Noise handshake is mandatory on every connection: the client never falls back to the legacy exchange above on its own, regardless of what the hub advertises (HIVEMIND-CRYPTO-1 §3, §5). With a 32-byte PSK provisioned, it runs Noise_XXpsk2_25519_ChaChaPoly_SHA256, or Noise_KKpsk0 when the server's static key is pinned, adding mutual authentication, forward secrecy, replay resistance, and downgrade protection. All session traffic then flows as Noise transport frames with implicit counter nonces. A hub that cannot complete this handshake — no PSK provisioned, capped below protocol v3, or no mutual pattern/suite — is rejected.

Legacy hubs (legacy_hub)

A hub that has not been upgraded past the pre-v3 password handshake can still be reached, but only when the operator explicitly opts in with legacy_hub=True. It is off by default and is never chosen from what the hub offers. Setting it makes connect() print a loud warning every time: the legacy handshake is not a PAKE and provides no forward secrecy, and it is scheduled for removal. Migrate the hub to protocol v3 and drop legacy_hub as soon as possible.

client = HiveMindClient(
    host="192.168.1.10", port=5678,
    username="satellite", access_key="...", password="...",
    legacy_hub=True,  # operator-acknowledged: hub cannot run Noise yet
)

A microcontroller never derives the PSK on-device, because argon2id is infeasible there. Compute it once on a capable host with argon2id(password, SHA-256(node_id)), for example through hivemind-core derive-psk, and pass the 32 bytes as psk:

client = HiveMindClient(
    host="192.168.1.10", port=5678,
    username="satellite", access_key="...", password="...",
    psk=bytes.fromhex("<64-hex-char provisioned PSK>"),
    # optional, enables KKpsk0 + TOFU pinning:
    # server_noise_key=bytes.fromhex("<server static X25519 pubkey>"),
    # noise_static_key=bytes.fromhex("<this node's static X25519 privkey>"),
)

After an XXpsk2 handshake, the server's static key is available in client.server_noise_key. Persist it and pass it back as server_noise_key so later connections pin it. A mismatch then aborts the connection as a possible man-in-the-middle.

Configuration

HiveMindClient(...) parameters:

Parameter Description Default
host Hub address none
port Hub port none
username Client name registered on the hub none
access_key Access key from hivemind-core add-client none
password Password from hivemind-core add-client none
site_id Site identifier reported to the hub "micropython"
preferred_cipher "AES-GCM" or "CHACHA20-POLY1305" "AES-GCM"
preferred_encoding One of the 7 JSON encodings (see docs) "JSON-HEX"
reconnect_ms Reconnect delay after a drop 5000
psk Provisioned 32-byte Noise PSK (bytes or hex), enables protocol v3 None
noise_static_key This node's static X25519 private key (bytes or hex) generated
server_noise_key Pinned server static X25519 public key (bytes or hex) None
max_protocol_version Highest protocol version to offer; below 3 requires legacy_hub=True 3
legacy_hub Operator opt-in to the pre-v3 password handshake for hubs that cannot run Noise; prints a removal warning at connect time False

Troubleshooting

  • First connection takes 10-30 s on ESP32: pure-Python PBKDF2 at 100k iterations is slow. This is expected. For production, freeze the _hivemind_crypto C module into the firmware to drop the handshake to about 2-3 s.
  • Protocol v3 handshake is slow on-device: pure-Python X25519 takes seconds per DH operation on an ESP32 (XXpsk2 does 3, plus key generation). It runs only once per connection. Transport encryption afterward is ChaCha20-Poly1305, the fastest pure-Python option.
  • MemoryError on device: send audio in small chunks and call gc.collect(). Consider a more compact encoding.
  • Authentication or handshake failures: verify that username, access_key, and password match the credential registered on the hub.
  • Connection refused: confirm the hub is listening and reachable at host:port.

See docs/troubleshooting.md for more.

Documentation

See examples/ for the runnable text and mic satellites.

About

Lightweight HiveMind satellite client for MicroPython 1.20+ and CPython 3.10+ on constrained devices

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages