How It Works
End-to-end walkthrough of the Data Joule control loop: an OpenADR 3.0 event arrives, the edge node changes inference behavior, watts are measured at the plug, the result is published to the live dashboard, and curtailment is settled on-chain via Chainlink Functions.
System Architecture
Two-Pi home lab + VPS control plane + Vercel data plane + Chainlink settlement
Signal Flow Walkthrough
Seven steps from grid event to on-chain settlement
VTN creates an event
A grid operator or test script posts an OpenADR event with a SIMPLE payload. The payload value (1–4) maps directly to a response tier and includes a start time plus duration.
OpenADR event target: edge-compute resource group signal: SIMPLE tier value window: start time + duration protection: authenticated control plane
VEN polls every 10 seconds
The VEN checks the authorized event feed on a fixed cadence and only acts on events inside their active time window.
Poll cycle fetch authorized events keep only active windows ignore events already handled dispatch selected tier
is_active() checks interval timing
The OpenADR 3.0 RI does not set eventStatus reliably, so is_active() checks whether now falls within any interval window. ISO 8601 durations such as PT60S and PT1H are parsed locally.
Active-window check parse event start parse event duration compare with current UTC time act only inside the interval
run_event() walks the response ladder
The SIMPLE payload value becomes the tier. run_event() calls the matching action function and deduplicates handled event IDs in memory. When the interval expires, the VEN restores tier 0 and reports completion.
Response ladder tier 0: normal operation tier 1-2: reduce compute intensity tier 3: pause inference workload tier 4: controlled shutdown path
Control agent executes on mtl-edge-01
For tiers 1–2, the VEN changes the CPU governor. For tier 3, it pauses or resumes llama-server through the local control agent. For tier 4, it halts the node and controls the Zigbee plug through MQTT.
Local execution receive tier from trusted LAN controller apply constrained compute action preserve observable state restore baseline after event clears
VEN posts a completion report
After the event interval ends, the VEN restores normal operation and posts an OpenADR report with the actual duration and tier observed, closing the event lifecycle.
Completion report restore baseline summarize observed tier record actual response window close event lifecycle
Chainlink oracle settles on-chain
The VEN writes a completion report (event:report:{eventName}) to Redis with baseline_w, avg_curtailed_w, duration_s, and kwh_reduced. Chainlink Functions nodes independently fetch /api/events/{eventName}, run source.js to encode kWh_reduced × 1e9, and reach consensus. fulfillRequest() fires and JouleCredit.sol mints the equivalent JLC tokens on Polygon Mainnet — one token per kWh curtailed, with Chainlink request ID and Polygonscan transaction hash as a verifiable audit trail.
Settlement
event:report:{name} → written to Redis
Chainlink DON fetches /api/events/{name}
source.js encodes kWh_reduced × 1e9 → uint256
consensus fulfilled → fulfillRequest() fires
JouleCredit.sol mints kWh_reduced JLC on PolygonTelemetry Chain
How wattage gets from the smart plug to your browser
Measures USB-C draw every second and publishes to the MQTT topic zigbee2mqtt/plug_1.
Brokers messages between Zigbee2MQTT and subscribers on mtl-ven-01.
Subscribes to plug telemetry, polls llama.cpp health, and writes /tmp/flexcompute_state.json atomically.
Reads the state file every 5 seconds and sends authenticated telemetry with exponential backoff.
Vercel serverless function. Validates ingestion requests and writes latest plus rolling history entries to Redis.
Stores telemetry:latest and telemetry:history for the live dashboard.
Returns latest telemetry plus recent history. Cache-Control: no-store.
fetch("/api/state") updates wattage, sparkline, and tier badge in React state.
Response Ladder — Mechanisms
What each tier actually does to the hardware
CPU governor → ondemand. llama-server active. Inference at full speed (~28 tokens/s prompt, ~6 tokens/s gen).
CPU governor → conservative. This switches the CPU's frequency scaling algorithm from reactive to conservative: it raises frequency slowly and lowers it quickly. Net effect: 15–20% less power, ~10% slower inference. Fully transparent to the LLM.
CPU governor → powersave. Forces the CPU to its minimum frequency (1.5 GHz on Pi 5 vs 2.4 GHz max). Inference continues but throughput drops ~40%. Still usable for non-latency-sensitive workloads.
SIGSTOP sent to the llama-server process. The process freezes in memory — no CPU time consumed, no inference possible, state preserved. SIGCONT resumes it instantly when the event clears.
The control path performs an orderly shutdown and isolates compute power through the metered plug. When the event clears, the node is restored and returns to service in roughly 55 seconds.
Stack
Every technology layer, from Zigbee to browser
| Layer | Technology |
|---|---|
| VEN daemon | Python 3.13 |
| VTN (server) | OpenADR 3.0 RI |
| Control agent | Python 3.13 http.server |
| Zigbee stack | Zigbee2MQTT + Mosquitto |
| Telemetry pusher | Python 3.13 urllib |
| Data store | Upstash Redis (serverless) |
| Web / API | Next.js 16.2.4 |
| Styling | Tailwind CSS v4 |
| LLM runtime | llama.cpp |
| Settlement | Chainlink Functions + ERC-20 |