C2C
ENERGY
Live snapshotUpdated 0s ago · automatic every 60s
Sign in
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

  1. Run the files in supabase/migrations/ in order in your Supabase SQL editor (or reuse the shared project, which already has them).
  2. Insert your site row and device rows, or run npm run simulate:backfill to seed the demo plant.
  3. Set NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_SITE_SLUG; server-side set SUPABASE_SERVICE_ROLE_KEY and INGEST_SECRET.
  4. 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.
  5. Point a bridge at /api/ingest with that key. Once real samples land, the Top bar switches from “Mock plant” to “Live snapshot”.
  6. Schedule select "C2C_Energy_rollup_daily"(site_id, today, today) hourly (pg_cron or your bridge) so History stays current.
  7. Press Refresh forecast in Site settings (Open-Meteo, no key needed) and schedule /api/weather/refresh with CRON_SECRET.