5.6 KiB
5.6 KiB
Core Domain: Site, Loadpoint, and the Control Loop
Object Hierarchy
Site (orchestrator — core/site.go)
├── Meters: Grid, PV[], Battery[], Auxiliary[], External[]
├── Tariffs: Grid, FeedIn, CO2, Solar
├── Coordinator (vehicle <-> loadpoint assignment)
├── Prioritizer (power allocation fairness)
└── Loadpoints[] (core/loadpoint.go)
├── Charger (api.Charger — hardware controller)
├── Vehicle (api.Vehicle — EV battery state via cloud API)
├── ChargeMeter (api.Meter — AC power at charger)
└── Circuit (optional — electrical domain limits)
Key Interfaces (api/api.go)
Meter
Meter—CurrentPower() (float64, error)— wattsMeterEnergy—TotalEnergy() (float64, error)— kWhPhaseCurrents/PhaseVoltages/PhasePowers— per-phase readings
Battery
Battery—Soc() (float64, error)— 0-100%BatteryCapacity— kWhBatteryController— set charge/discharge/hold mode
Charger
Charger—Status(),Enabled(),Enable(bool),MaxCurrent(int64)ChargerEx— milliamp-precision current viaMaxCurrentMillis(float64)PhaseSwitcher—Phases1p3p(int) errorChargeRater—ChargedEnergy() (float64, error)ChargeTimer—ChargeDuration() (time.Duration, error)
Vehicle
Vehicle—Soc(),Capacity(),Identifiers(),Phases(),OnIdentified()VehicleRange,VehicleOdometer,VehicleClimater,VehicleFinishTimer,VehiclePositionChargeController— remote start/stop on vehicleCurrentLimiter—GetMinMaxCurrent()for vehicle-side current limitsCurrentController— some vehicles (Tesla, Fiat) also implementMaxCurrent()to set charge current from the vehicle side
Charge Modes
| Mode | Behavior |
|---|---|
OFF |
Disabled (unless welcome charge) |
NOW |
Max current immediately |
MINPV |
Min current when PV surplus; fast if cheap tariff |
PV |
Ramp current proportional to available solar |
Charge States (IEC 61851)
A— not connectedB— connected, not chargingC— connected, charging
The Control Loop (Site.update — runs every N seconds)
1. Update all meters (grid, PV, battery, aux)
2. For each loadpoint: UpdateChargePowerAndCurrents()
3. Calculate site power balance:
sitePower = gridPower + batteryPower + excessDCPower
+ residualPower - auxPower - flexiblePower
4. Apply battery priority rules (prioritySoc, bufferSoc)
5. Get tariff rates
6. For EACH loadpoint: Update(sitePower, ...)
├── Read charger status
├── Detect/identify vehicle
├── Check plan requirements (minSOC, target time)
├── Check limits (limitSOC, limitEnergy)
├── MODE switch -> calculate target current
├── Cap at maxCurrent, respect circuit limits
├── Send MaxCurrent() to charger
└── Record metrics
7. Push updates to WebSocket + metrics
The loop is stateless per cycle: always re-reads actual state, calculates optimal current, sends single command. Resilient to restarts and missed updates.
PV Surplus Charging (pvMaxCurrent in core/loadpoint.go)
1. Read effective min/max current limits
2. Reduce sitePower by battery boost power
3. Consider phase switching (1p <-> 3p) if supported
4. deltaCurrent = powerToCurrent(-sitePower, activePhases)
targetCurrent = effectiveCurrent + deltaCurrent
5. Below minCurrent -> start disable timer (default 3 min)
6. Surplus returns -> start enable timer (default 1 min)
7. Cap at maxCurrent
Battery Priority Rules
| Setting | Effect |
|---|---|
prioritySoc |
Below this: battery charges first, EV gets 0 |
bufferSoc |
Above this: EV can draw from battery reserves |
bufferStartSoc |
Above this: EV charging can begin even if importing |
Effective Price Calculation
greenShare = (max(pvPower,0) + max(batteryPower,0)) / totalChargePower
effectivePrice = gridPrice * (1 - greenShare) + feedInPrice * greenShare
Concurrency Model
- Site owns
RWMutexfor its state (meters, battery, tariffs) - Loadpoint owns
RWMutexfor its state (charger, vehicle, current) - Coordinator owns
RWMutexfor vehicle <-> loadpoint tracking - No global locks — ordering prevents deadlocks
Channels
| Channel | Scope | Buffer | Purpose |
|---|---|---|---|
valueChan |
Site | Unbounded (chanx.NewUnboundedChan) |
State changes -> DB + UI (ordering) |
lpUpdateChan |
Site | 1 | Early loadpoint update requests |
pushChan |
Loadpoint | 16 | User notifications, queued via valueChan so the message renders the state at event time |
Tariff Integration
Types: TariffUsageGrid, TariffUsageFeedIn, TariffUsageCo2, TariffUsagePlanner, TariffUsageSolar
Smart Features
- Cheap-tariff override — rate below threshold -> fast charge
- Smart feed-in — feed-in rate above threshold -> prioritize export
- Planner (
core/planner/planner.go) — finds cheapest time slots for target SOC/energy by deadlineoptimalPlan()— cheapest non-contiguous slotscontinuousPlan()— cheapest continuous window (fallback)
Key File Locations
api/api.go— all core interfacescore/site.go— Site orchestrator + control loopcore/loadpoint.go— Loadpoint state machine (pvMaxCurrent, mode switch)core/site_battery.go— battery priority logiccore/site_tariffs.go— tariff integrationcore/planner/planner.go— charge time optimizationcore/prioritizer/prioritizer.go— power allocation across loadpointscore/circuit/circuit.go— electrical domain limits