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
- 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) orasyncio(CPython).
- 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.
mpremote mip install github:JarbasHiveMind/hivemind-micropython-clientOr from the REPL:
import mip
mip.install("github:JarbasHiveMind/hivemind-micropython-client")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 accelerators1. 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.
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.
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.
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.
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 |
- First connection takes 10-30 s on ESP32: pure-Python PBKDF2 at 100k iterations is slow. This is expected. For production, freeze the
_hivemind_cryptoC 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.
MemoryErroron device: send audio in small chunks and callgc.collect(). Consider a more compact encoding.- Authentication or handshake failures: verify that
username,access_key, andpasswordmatch the credential registered on the hub. - Connection refused: confirm the hub is listening and reachable at
host:port.
See docs/troubleshooting.md for more.
docs/getting-started.md: prerequisites and first satellite.docs/index.md: API and module reference.docs/examples.md: text, audio, and custom-message examples.docs/troubleshooting.md: common errors and fixes.docs/integration-testing.md: testing against a live hub.
See examples/ for the runnable text and mic satellites.