Example scripts demonstrating the ebus-sdk.
Basic Homie device that publishes sensor data (temperature, humidity, air pressure).
./simple-device --config /path/to/broker-cfg.jsonA parent/child device TREE over one MQTT connection: a root distribution-enclosure (panel-1) with circuit and BESS children and a MID grandchild, all sharing the root's single connection. It shows building the tree inside one with root.state_transition(): (so the root publishes one INIT->READY for the whole tree, not one per child), a settable property on a child whose /set routes back over the shared connection, and the per-device topics each tree node publishes under. Reach for this over simple-device when you need to model a device that contains sub-devices. --check builds the tree, logs its structure, and exits (it works even with the broker down, since connect is asynchronous).
./simple-tree-device --config /path/to/broker-cfg.json
./simple-tree-device --config /path/to/broker-cfg.json --check # build + print + exitPublishes a single eBus utility-meter device (energy.ebus.device.utility-meter, per the Electrification Bus data-models/utility-meter.md, v0.3) with the data model's capabilities: the required info / meter / status plus the optional grid / doe / price / demand / power-quality a meter publishes when it has the signal. The doe and price capabilities are json properties (doe/import-limit/export-limit, price/import-price/export-price), each a JSON array of time-windowed objects that advertises its schema as a $format JSONSchema; enum properties advertise their allowed values via $format. A local HTTP endpoint stands in for the utility's out-of-band backhaul for runtime doe/price updates, validating each posted body against the property's $format before publish (install the validation extra to enable it: pip install 'ebus-sdk[validation]').
./utility-meter --config ./utility-meter-cfg.example.json --broker-config /path/to/broker-cfg.jsonNeed a broker to run against? broker-quickstart brings up a local eBus broker on macOS with one command (python -m laptop.run), including the mDNS advertisement that --discover (below) resolves. Its scripts/laptop-bench.sh runs this simulator against that broker with --discover, end to end; see the laptop quickstart.
Add --discover to find the broker over mDNS (_secure-mqtt._tcp) instead of using the host/port in the broker config; the broker config still supplies the TLS material. Needs the mdns extra (pip install 'ebus-sdk[mdns]'):
./utility-meter --config ./utility-meter-cfg.example.json --broker-config /path/to/broker-cfg.json --discoverSet a DOE envelope at runtime. The body is a single envelope object or an array of them (the retained schedule); it is set atomically as one json value:
curl -X POST http://localhost:8765/doe/import-limit \
-H 'Content-Type: application/json' \
-d '[{"power-limit": 12000, "source": "GRID", "start-time": "2026-07-01T16:00:00Z", "end-time": "2026-07-01T20:00:00Z"}]'Envelope fields: power-limit (integer W) and/or apparent-power-limit (integer VA) — at least one required; source (one of CONTRACT / REGULATOR / EQUIPMENT / GRID / UNKNOWN, optional); start-time / end-time (ISO-8601 UTC, optional). POST an empty body or null to clear the signal for that direction. The same shape is served at /doe/export-limit, and the price schedules at /price/import-price and /price/export-price (price-window objects: price + currency, price-level, source, start-time / end-time). A body that violates the property's $format schema is rejected with 400 and the validation error.
Discovers eBus devices and walks them as a TREE (roots then descendants), the current parent/child model: it reports each device's EFFECTIVE state (a child of a LOST root reads LOST, not its own stale ready), prints per-node property values, flags settable properties, and can send one /set to a settable property. Pairs with simple-tree-device: run that against a broker, point this at the same broker, and watch the tree appear. Wildcard discovery by default; --root <device-id> discovers one root and its descendants.
./simple-controller --config /path/to/broker-cfg.json
./simple-controller --config broker-cfg.json --root panel-1
./simple-controller --config broker-cfg.json --set circuit-3/switch/relay=trueConnects to a live SPAN panel via mDNS + MQTTS and determines whether the home is islanded from the grid, watching the MID child's grid capability (islanding-state / grid-state / grid-forming-entity) plus the enclosure's power-flows. Targets the current parent/child data model (the legacy flat core/dominant-power-source is retired).
Requires the mdns extra:
pip install ebus-sdk[mdns]Basic usage (requires password):
./simple-span-controller <serial-number> <password>
./simple-span-controller <serial-number> <password> --broker-host 192.168.1.100With SPAN-API utilities (automatic credentials and CA cert):
If you have access to the SPAN-API-Client-Docs repository, you can enable automatic credential and certificate management:
# Add SPAN-API-Client-Docs lib to PYTHONPATH
export PYTHONPATH=$PYTHONPATH:/path/to/SPAN-API-Client-Docs/lib
# Run without password - uses ~/.span-auth.json
./simple-span-controller <serial-number>
# Force insecure mode (skip CA cert verification)
./simple-span-controller <serial-number> --insecureWhen SPAN-API utilities are available:
- Password is retrieved from
~/.span-auth.jsonif not provided on command line - CA certificate is fetched/cached in
~/.span-ca-certs/for secure TLS verification - Use
--insecureto skip certificate verification even when CA cert is available
Bridges eBus/Homie devices to Home Assistant MQTT discovery (the reverse of the HA -> eBus path). Publishes a synthetic eBus device (meter / battery / settable control), runs a Controller + HaDiscoveryBridge that discovers it and emits homeassistant/device/<id>/config, then subscribes an independent client (standing in for Home Assistant) to capture and verify the emitted config, and finally shows the config being cleared on device removal. Runs against a real broker but needs no running Home Assistant. See doc/ha-discovery-bridge.md for the full guide.
./ha-discovery-bridge --config /path/to/broker-cfg.json
./ha-discovery-bridge # defaults to 127.0.0.1:1883, no auth
./ha-discovery-bridge --keep-running # leave it up for a real HA to consumeNo broker handy? A throwaway local one works: mosquitto -c <(printf 'listener 1883 127.0.0.1\nallow_anonymous true\n'), then run with no --config.
All examples accept broker configuration via:
- Command line:
--config /path/to/broker-cfg.json - Environment variable:
EBUS_BROKER_CFG=/path/to/broker-cfg.json
{
"host": "mqtt.example.com",
"port": 1883,
"authentication": {
"type": "USER_PASS",
"username": "myuser",
"password": "mypassword"
}
}For MQTTS (TLS) with insecure mode (no certificate verification):
{
"host": "secure-broker.example.com",
"port": 8883,
"use_tls": true,
"tls_insecure": true,
"authentication": {
"type": "USER_PASS",
"username": "myuser",
"password": "mypassword"
}
}For MQTTS with CA certificate verification (secure mode):
{
"host": "secure-broker.example.com",
"port": 8883,
"use_tls": true,
"tls_ca_cert": "/path/to/ca-cert.crt",
"tls_insecure": false,
"authentication": {
"type": "USER_PASS",
"username": "myuser",
"password": "mypassword"
}
}TLS Options:
use_tls: Enable TLS/SSL connection (required for port 8883)tls_ca_cert: Path to CA certificate file for server verificationtls_ca_data: CA certificate content as PEM string or DER bytes (alternative to file)tls_insecure: Skip certificate verification (default: true for backwards compatibility)