Technical Deep-Dive

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

PUBLIC INTERNETVTNvtn.data-joule.comCaddy + Docker - Hetznerdata-joule.com/api/ingest - /api/stateNext.js - Vercel - RedisBrowserPolls /api/state every 5sLive dashboardHOME LAB — Montréal, QC [egress-only]mtl-ven-01private LAN node- VEN daemon (ven.py)- Zigbee2MQTT + Mosquitto- Workload orchestrator- ConBee II (Zigbee coord.)mtl-edge-01private LAN node- llama.cpp (Llama 3.2-3B)- Control agent- oled_status_writer.py- telemetry_pusher.pyZigbee Plug #1ThirdReality smart plugmeters mtl-edge-01 USB-CTier 4: cuts powerHTTPS + OAuth2HTTPS POST 5sHTTPZigbee 3.0SETTLEMENT — POLYGON MAINNETChainlink DONfunctions.chain.link- Independent oracle nodes- Consensus on kWh_reducedJouleCredit.solERC-20 · Polygon Mainnet- Mints kWh_reduced JLC tokens- 0x14b90C2E...8470101fulfillRequest()oracle fetch: /api/events/{name}

Signal Flow Walkthrough

Seven steps from grid event to on-chain settlement

1

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
2

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
3

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
4

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
5

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
6

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
7

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 Polygon

Telemetry Chain

How wattage gets from the smart plug to your browser

Zigbee Plug #1

Measures USB-C draw every second and publishes to the MQTT topic zigbee2mqtt/plug_1.

Mosquitto (MQTT broker)

Brokers messages between Zigbee2MQTT and subscribers on mtl-ven-01.

oled_status_writer.py

Subscribes to plug telemetry, polls llama.cpp health, and writes /tmp/flexcompute_state.json atomically.

telemetry_pusher.py

Reads the state file every 5 seconds and sends authenticated telemetry with exponential backoff.

/api/ingest

Vercel serverless function. Validates ingestion requests and writes latest plus rolling history entries to Redis.

Upstash Redis

Stores telemetry:latest and telemetry:history for the live dashboard.

/api/state

Returns latest telemetry plus recent history. Cache-Control: no-store.

Browser (5s poll)

fetch("/api/state") updates wattage, sparkline, and tier badge in React state.

Response Ladder — Mechanisms

What each tier actually does to the hardware

TIER 0Baseline~14 W-

CPU governor → ondemand. llama-server active. Inference at full speed (~28 tokens/s prompt, ~6 tokens/s gen).

TIER 1Throttle~11 W-21%

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.

TIER 2Power-save~8 W-43%

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.

TIER 3Suspend~4 W-71%

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.

TIER 4Halt~2 W-86%

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.

Baseline Measurements — mtl-edge-01 (2026-04-28)
OS idle (llama.cpp loaded)
2-3 W
Inference under load
8.7-10.3 W
Tier 4 restore time
~55 seconds

Stack

Every technology layer, from Zigbee to browser

LayerTechnology
VEN daemonPython 3.13
VTN (server)OpenADR 3.0 RI
Control agentPython 3.13 http.server
Zigbee stackZigbee2MQTT + Mosquitto
Telemetry pusherPython 3.13 urllib
Data storeUpstash Redis (serverless)
Web / APINext.js 16.2.4
StylingTailwind CSS v4
LLM runtimellama.cpp
SettlementChainlink Functions + ERC-20