Edge firmware that turns raw power readings from an ADE7880 energy-metering IC into real-time, per-appliance on/off state — entirely on-device, no cloud round-trip required for inference. This is the SPI variant, built for a Linux single-board computer (SBC) with an NFS-mounted root filesystem; the sibling py_nano/ folder is an I2C/NanoPI port of the same pipeline.
It implements Non-Intrusive Load Monitoring (NILM): instead of metering each appliance separately, it watches the aggregate load on the mains line, detects the step changes that occur when something switches on or off, and classifies which appliance caused each step using a small trained Decision Tree.
Two daemon threads run for the life of the process:
ade_worker_function— every ~1 s, reads 15 registers off the ADE7880 over SPI (RMS voltage/current, fundamental and harmonic real/reactive power) and pushes the averaged sample onto an in-memory queue.queue_worker_function— drains that queue and, for each sample:- runs it through
Events, a small state machine (util.py) that watches for a power delta abovestate_threshold(15 W), waits formin_n_samplesconsecutive stable readings, and confirms the transition if the settled change exceedsnoise_level(30 W) — filtering out sensor noise and mid-transition jitter; - feeds any confirmed event's
[ΔFWATT, ΔFVAR]into aDecisionTreeClassifier(Disaggregateclass) to predict"appliance,state"; - updates a running per-appliance power/state table and publishes it over MQTT.
- runs it through
Everything — appliance registry, recorded training samples, and the trained model — is trained on the device itself, driven entirely by MQTT commands, so a technician can teach the meter a household's appliances without flashing new firmware.
ADE7880 (SPI) → ade_worker → queue → Events (state machine) → DecisionTree → MQTT
| File | Purpose |
|---|---|
run_ade_with_ml.py |
Entry point. Owns the two worker threads, the MQTT client, and the on-device training/recording control flow. |
util.py |
Events (event/state detector), Disaggregate (inference), model_training / model_traning_preprocssing, and the custom JSON serializer/deserializer for DecisionTreeClassifier (keeps the model portable across Python/sklearn versions instead of pickling it). |
spi_utils.py |
Raw SPI driver for the ADE7880 — register read/write framing over spidev. |
initialize.py |
Power-cycles the ADE7880 (GPIO16) and verifies the SPI link by checksum-reading a known register. |
initialize_ade_with_calibration.py |
Uploads voltage/current gain and offset calibration registers, then starts the ADE7880's internal DSP. |
run_ade.sh |
Orchestrates the three steps above in sequence (init → calibrate → run). |
initialize_linux.sh |
Full cold-boot sequence for the SBC: network (udhcpc), NTP time sync, PATH/LD_LIBRARY_PATH setup for the NFS-mounted toolchain, then hands off to run_ade.sh. |
reset_on.sh / reset_off.sh |
Drive GPIO16 to reset the ADE7880 / ESP companion chip. |
mqtt_subscribe.py |
Standalone CLI client for poking the running system's MQTT interface (add appliance, start/stop recording, trigger training, factory reset) without a real app. |
db.json |
TinyDB store — appliance registry and recorded training samples. |
vega_decision_model.json |
The trained Decision Tree + label encoder, serialized to plain JSON. |
test_data.json |
Sample 120 s measurement capture for offline testing. |
demo_defaults/ |
Pristine copies of db.json / vega_decision_model.json, restored on a swadeshi/reset_hw command. |
| Topic | Direction | Purpose |
|---|---|---|
swadeshi/raw |
publish | Live raw sensor fields + inferred appliance state, every processed sample |
swadeshi/appliance |
publish | Current appliance registry |
swadeshi/appliance/add |
subscribe | Register a new appliance {appliance, type} |
swadeshi/record |
subscribe | Start/stop capturing a training recording {appliance, command} |
swadeshi/train_hw |
subscribe | Retrain the Decision Tree from all recordings collected so far |
swadeshi/train_hw_ack |
publish | Training result |
swadeshi/record_start_ack / swadeshi/record_stop_ack |
publish | Recording lifecycle acknowledgements |
swadeshi/reset_hw |
subscribe | Restore db.json / model from demo_defaults/ |
Broker: shakti.build.avrio.energy:1883.
pip install -r requirements.txt
# Full cold-boot (network + NTP + init + calibrate + run):
bash initialize_linux.sh
# Or step by step, once the network/toolchain is already set up:
python initialize.py # GPIO reset + SPI link check
python initialize_ade_with_calibration.py # gain/offset calibration + start DSP
python run_ade_with_ml.py --debugExercise the MQTT control surface from another machine on the broker:
python mqtt_subscribe.py- The Decision Tree is serialized to hand-rolled JSON (
util.py'sserialize_tree/deserialize_tree) rather than pickled, so a model trained under one sklearn/Python version keeps loading correctly on the meter after firmware updates. Events.detect_eventsvoltage-normalizes power readings (FWATT × (240/VRMS)²) before comparing deltas, so classification stays consistent across mains voltage sag/swell.