neuron-explorer
Steering-first neuron explorer for Qwen3.5-4B: push every MLP neuron, keep every rollout, browse the results
eBus (Electrification Bus) is an open interoperability framework enabling Home Energy Infrastructure devices to communicate and coordinate using MQTT, mDNS, and the Homie Convention.
Developer toolsRust · PythonGitHub @electrification-busOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Rust SDK for eBus/Homie devices and controllers: MQTT transport, mDNS discovery
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Rust SDK for Electrification Bus (eBus): Homie 5 devices and controllers over MQTT, with mDNS broker discovery. It targets Linux single-board computers first and microcontrollers later.
Status: pre-alpha. The controller and device roles are implemented, and mDNS broker discovery and host advertisement run on the Avahi and mdns-sd backends.
Coming from python-sdk? doc/python-sdk.md maps its patterns to rust-sdk.
A controller watches the devices on a broker and sends them commands. It:Read the full README ↗Rust SDK for Electrification Bus (eBus): Homie 5 devices and controllers over MQTT, with mDNS broker discovery. It targets Linux single-board computers first and microcontrollers later.
Status: pre-alpha. The controller and device roles are implemented, and mDNS broker discovery and host advertisement run on the Avahi and mdns-sd backends.
Coming from python-sdk? doc/python-sdk.md maps its patterns to rust-sdk.
A controller watches the devices on a broker and sends them commands. It:
ebus domain, the devices of one tree (a root and its children), or a single device;$description and checks every value against its property's datatype and format;$state and its effective state (a child of a lost root is lost);DeviceSynced), when its values do not arrive (DeviceStuck), and when it goes quiet after a reconnect (DeviceAbsent);/set and $broadcast, checking a /set payload against the property first;The application reads the latest state as cheap snapshots and receives changes as events.
A device publishes a tree of Homie devices (a root and its children) and takes commands for them. It:
$description from typed properties (property::<f64>, settable::<bool>, event::<i64>) and publishes init, the description, the values and ready, the root's $state last;/set to the application, which accepts it, accepts it with the value the device reached, or rejects it; $target properties report progress toward the target;lost state as the connection's last will;disconnected, by letting the will fire, or by removing every topic.A device and a controller can share one connection (ebus_rumqttc::pair).
A typical site has one MQTT broker, and every eBus host at the site connects to it. A host finds it in one of three ways:
The client sets which broker transports it accepts and its preference among them, by default the specification's order with _secure-mqtt._tcp first.
The host also advertises its own eBus services over mDNS. ebus-discovery-tokio finds the broker and advertises the host through avahi-daemon over D-Bus on Linux (its avahi feature), or on the pure-Rust mdns-sd backend (its mdns-sd feature) for Linux without Avahi and for macOS.
The crates are on crates.io. Most programs need only ebus-rumqttc, which re-exports the others it uses:
cargo add ebus-rumqttc
cargo add tokio --features macros,rt-multi-thread
Watch every device on a broker:
use ebus_rumqttc::ebus_homie_tokio::ebus_homie::controller::{Config, Event};
use ebus_rumqttc::ebus_homie_tokio::{ControllerOptions, Delivered};
use ebus_rumqttc::rumqttc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut mqtt = rumqttc::MqttOptions::new(ebus_rumqttc::controller_client_id(), "localhost", 1883);
// A `$description` can exceed rumqttc's 10 KiB default packet size.
mqtt.set_max_packet_size(4 << 20, 4 << 20);
let run = ebus_rumqttc::controller(mqtt, Config::wildcard(), ControllerOptions::default())?;
tokio::spawn(run.task.run());
let mut events = run.events;
while let Some(delivered) = events.recv().await {
if let Delivered::Event(Event::DeviceSynced { device, .. }) = delivered {
if let Some(view) = run.handle.device(&device) {
for (node, property, p) in view.properties() {
let value = p.value.as_ref().map(|v| &*v.raw);
println!("{device}/{node}/{property} = {value:?}");
}
}
}
}
Ok(())
}
Find the broker over mDNS instead, and find it again if its host gets a new address (ebus-discovery-tokio with its mdns-sd or avahi feature):
use std::time::Duration;
use ebus_discovery_tokio::ebus_discovery::BrokerConfig;
use ebus_discovery_tokio::{Mdns, find_broker, reresolve};
use ebus_rumqttc::ebus_homie_tokio::ControllerOptions;
use ebus_rumqttc::ebus_homie_tokio::ebus_homie::controller::Config;
use ebus_rumqttc::rumqttc::MqttOptions;
let mdns = Mdns::system().await?;
let cfg = BrokerConfig::default(); // discovery-only: the first broker advertised
let broker = find_broker(&mdns, cfg.clone()).await?;
let id = ebus_rumqttc::controller_client_id();
let options = |id: &str, host: &str, port: u16| {
let mut mqtt = MqttOptions::new(id, host, port);
mqtt.set_max_packet_size(4 << 20, 4 << 20);
mqtt
};
let mqtt = options(&id, &broker.connect_host(), broker.port);
let run = ebus_rumqttc::controller(mqtt, Config::wildcard(), ControllerOptions::default())?;
let task = run.task.reresolve(Box::new(move || {
let (mdns, cfg, broker, id) = (mdns.clone(), cfg.clone(), broker.clone(), id.clone());
Box::pin(async move {
let found = reresolve(&mdns, &broker, &cfg, Duration::from_secs(3)).await.ok()??;
Some(options(&id, &found.connect_host(), found.port))
})
}));
tokio::spawn(task.run());
The watch example prints every controller event. With neither EBUS_MQTT_HOST nor EBUS_MQTT_PORT set it finds the broker over mDNS; with either, it connects to that broker:
cargo run -p ebus-rumqttc --example watch -- wildcard
EBUS_MQTT_HOST=localhost cargo run -p ebus-rumqttc --example watch -- tree <root>
EBUS_MQTT_HOST=broker.local EBUS_MQTT_CA=ca.pem cargo run -p ebus-rumqttc --example watch --features rustls -- wildcard
Its source lists every setting, including EBUS_BROKER_MODE and EBUS_MDNS_BACKEND: crates/ebus-rumqttc/examples/watch.rs.
Publish a device:
use ebus_rumqttc::ebus_homie_tokio::device::{
DeviceOptions, DeviceSpec, NodeSpec, PropSpec, TreeBuilder, TreeConfig,
};
use ebus_rumqttc::rumqttc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mqtt = rumqttc::MqttOptions::new("my-device", "localhost", 1883);
let mut b = TreeBuilder::new(TreeConfig::default(), "my-device", DeviceSpec::default())?;
let node = b.node(b.root(), "sensor", NodeSpec::typed("example.sensor"))?;
let temperature = b.property::<f64>(node, "temperature", PropSpec::new().unit("°C"))?;
let mut led = b.settable::<bool>(node, "led", PropSpec::new())?;
temperature.set(21.5)?;
led.set(false)?;
let run = ebus_rumqttc::device(mqtt, b, DeviceOptions::default())?;
tokio::spawn(run.task.run());
while let Some(req) = led.next_set().await {
println!("led -> {}", req.value());
req.accept();
}
Ok(())
}
The device example adds a duration, an event and a clean shutdown on Ctrl-C, and takes the watch example's broker settings:
cargo run -p ebus-rumqttc --example device -- my-device
/set examplesThe tree-device example publishes a parent/child tree on one connection, as python-sdk's simple-tree-device does: a distribution enclosure with three circuits and a battery as children and a microgrid interconnect under the battery, built in one TreeBuilder so the root goes ready once for the tree. It prints each /set it accepts on circuit-3's switch/relay and the battery's control/reserve.
The set example pairs with it, as the --set option of python-sdk's simple-controller does with simple-tree-device: it waits for the device to sync, sends one /set and waits until the device reports the value. The controller's own checks refuse an unknown or non-settable property and an invalid value before anything is sent; the exit code tells these outcomes apart. Both take the watch example's broker settings:
cargo run -p ebus-rumqttc --example tree-device
cargo run -p ebus-rumqttc --example set -- circuit-3/switch/relay true
cargo run -p ebus-rumqttc --example set -- --root rust-tree bess-1/control/reserve 50
The simple-span-controller example, a port of python-sdk's simple-span-controller, reports whether a SPAN panel's home is islanded from the grid, watching the MID child's grid node and the enclosure's power-flows node. It finds the panel's broker over mDNS as the _secure-mqtt._tcp advertisement whose SRV target is span-<serial-number>.local, else connects to that host name after 5 s:
cargo run -p ebus-rumqttc --example simple-span-controller --features rustls -- <serial-number> [password]
Without a password argument it reads the panel's ebus_broker_password from SPAN_AUTH_FILE, else ~/.span-auth.json, the credential file of span_auth_utils in SPAN-API-Client-Docs. That file is optional, a convenience: its scripts/span-auth writes it (span-auth setup --wait registers with a panel, span-auth list shows the panels, span-auth default picks one). It verifies the broker against the panel's CA certificate, cached as <serial-number>.crt in SPAN_CA_CERT_DIR, else ~/.span-ca-certs, and downloaded from http://<host>/api/v2/certificate/ca when missing or expiring within a day; --insecure skips the check. Its source lists every option: crates/ebus-rumqttc/examples/simple-span-controller.rs.
A service can share its own MQTT client with the controller instead of opening a second connection: ebus_homie_tokio::attach_controller returns an inbox the client's reader feeds and a command stream the client sends. The host keeps its client version, TLS, last will and reconnect loop. The contract is in the ebus-homie-tokio crate docs.
A device can share the connection the same way (ebus_homie_tokio::device::attach_device, or attach_pair with a controller), provided the host installs the device's last will on it. The contract is in the device module docs.
| Crate | Role | no_std | Version |
|---|---|---|---|
ebus-rumqttc | The controller, the device, or both, with their own rumqttc 0.25 connection: TCP, or TLS with the rustls or native-tls feature; a reresolve hook finds a discovered broker again after its address changes | no | |
ebus-homie-tokio | Runs the controller and the device on tokio over a host-owned MQTT connection, with typed property handles | no | |
ebus-discovery-tokio | Finds the site's broker over mDNS, finds it again after an address change, and advertises the host's services, on tokio; Avahi backend under the avahi feature (Linux), mdns-sd backend under the mdns-sd feature | no | |
ebus-homie | Homie 5 protocol core: identifiers, topics, datatypes, $description, and the controller and device state machines. Performs no I/O | yes, with optional alloc | |
ebus-mqtt | MQTT vocabulary: messages and commands, topic filters and their ownership, and pairing two roles on one connection | yes, with optional alloc |
The core crates take std (the default), alloc, or neither as cargo features, and are driven by the caller: they receive messages and the current time and return the MQTT commands to send. The design, including subscription pacing, reconnect handling and the Homie state rules, is in doc/design.md.
Each v* tag on this repository publishes all six crates to crates.io at that version and creates the matching GitHub release, whose notes are that version's CHANGELOG section; CONTRIBUTING.md has the steps.
The Homie 5 convention and the eBus specification decide wire behavior. Where the spec leaves a choice open, rust-sdk follows python-sdk; where python-sdk deviates from the spec, rust-sdk follows the spec (python-sdk#110). Shared test vectors generated from python-sdk and python-service-discovery are in vectors/.
The minimum supported Rust version is 1.85. CI also tests 1.91.1 and the current stable release, and builds the core crates for thumbv7em-none-eabihf.
cargo test --workspace
The end-to-end tests start their own mosquitto and skip when it is not installed. Build commands, test settings and house style are in AGENTS.md; how to contribute is in CONTRIBUTING.md.
MIT; see LICENSE.
ebus-discovery | eBus DNS-SD contract: service types, TXT records, the advertisement model, and broker ranking and selection. Performs no I/O | yes, with optional alloc |