Live snapshotUpdated 0s ago · automatic every 60s
05 / Connect real hardware
Swap the mock for your plant.
How it fits together
Three layers, one contract
Step 1
Describe the plant as a graph
One row per box in
C2C_Energy_devices and one row per wire in C2C_Energy_links (from → to, the way power flows). Any shape works: DC-coupled with MPPTs, an all-in-one hybrid, AC-coupled microinverters, several banks, generators, grid, wind, DC loads. Start from a template on the Sites page and edit from there.Step 2
Push samples
Anything that can speak HTTPS posts JSON to
/api/ingest with the site key. Each device reports what it measures: pv_watts for solar, gen_watts for generator / grid / wind, load_watts for consumption, soc_pct / batt_volts / batt_amps for storage, and BMS packs in battery. The app counts each watt once by walking the graph.Step 3
Read the dashboard
Pages read the latest sample per device, today's curve, the daily roll-up and the 7-day forecast with the anon key (RLS: select only). The runtime estimate, outlook and warnings are computed in
src/lib/analysis.ts, so tune thresholds there.Vocabulary
Device kinds
Sources
- pv_array
- ac_pv
- wind
- hydro
- generator
- grid
report pv_watts or gen_watts
Conversion
- charge_controller
- inverter
- inverter_charger
- dc_dc
report pv_watts in, load_watts out, soc_pct if they know it
Storage
- battery_bank
- battery_pack
- shunt
packs post BMS rows; a shunt or hybrid can report soc_pct for the bank; else voltage
Conduits
- dc_bus
- ac_bus
optional; draw a common bus
Sinks
- ac_circuit
- dc_load
- load_meter
report load_watts; meta.metered=false marks a gap
Useful
meta keys: isolated, metered, chemistry (lifepo4 / lead_acid / li_ion), reports_soc, available (grid), panels. Bank capacity and reserve come from the battery_bank row (capacity_wh, meta.reserve_soc_pct).Ingest
POST /api/ingest
curl -X POST https://your-host/api/ingest \
-H "content-type: application/json" \
-H "x-ingest-key: $INGEST_SECRET" \
-d '{
"site": "demo-ranch",
"readings": [
{ "device": "mppt-renogy", "pv_watts": 412.5, "pv_volts": 36.1, "pv_amps": 11.4, "batt_volts": 13.38, "batt_amps": 30.8, "status": "bulk" },
{ "device": "hybrid", "pv_watts": 2100, "load_watts": 900, "gen_watts": 0, "soc_pct": 71, "batt_volts": 52.4 },
{ "device": "gen", "gen_watts": 4200, "status": "running" },
{ "device": "meter-starlink", "load_watts": 31.2 }
],
"battery": [
{ "device": "pack-1", "soc_pct": 97.5, "volts": 13.39, "amps": 3.4, "cell_mv": [3348, 3351, 3346, 3352], "temp_c": 22.6, "cycle_count": 162 }
]
}'Rows upsert on (device, ts), so re-sending a sample is harmless. Unknown device keys are reported back, not written.
Bridge example
Raspberry Pi → Renogy
# bridge.py — read a Renogy Rover over RS485 every 60 s and post it
import time, requests, minimalmodbus
rover = minimalmodbus.Instrument("/dev/ttyUSB0", 1)
rover.serial.baudrate = 9600
while True:
pv_v = rover.read_register(0x0107, 1) # PV volts
pv_a = rover.read_register(0x0108, 2) # PV amps
batt = rover.read_register(0x0101, 1) # battery volts
requests.post("https://your-host/api/ingest",
headers={"x-ingest-key": "…"},
json={"site": "demo-ranch", "readings": [
{"device": "mppt-renogy", "pv_watts": round(pv_v*pv_a, 1),
"pv_volts": pv_v, "pv_amps": pv_a, "batt_volts": batt}]})
time.sleep(60)Same pattern for EPEVER (Modbus 0x3100…), Victron (VE.Direct), JK/JBD BMS over BLE, Shelly plugs (their local REST), or a Home Assistant automation that calls a REST command.
Checklist
Going live
- Run the files in
supabase/migrations/in order in your Supabase SQL editor (or reuse the shared project, which already has them). - Insert your site row and device rows, or run
npm run simulate:backfillto seed the demo plant. - Set
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY,NEXT_PUBLIC_SITE_SLUG; server-side setSUPABASE_SERVICE_ROLE_KEYandINGEST_SECRET. - Sign in, create your site on the Sites page from the closest template, edit the device and link rows to match your wiring, and copy the ingest key from Site settings.
- Point a bridge at
/api/ingestwith that key. Once real samples land, the Top bar switches from “Mock plant” to “Live snapshot”. - Schedule
select "C2C_Energy_rollup_daily"(site_id, today, today)hourly (pg_cron or your bridge) so History stays current. - Press Refresh forecast in Site settings (Open-Meteo, no key needed) and schedule
/api/weather/refreshwithCRON_SECRET.