### Agents/Api Security # API Security & Authentication How evcc authenticates HTTP requests and how endpoints are classified by sensitivity. ## Threat Model evcc is designed for use within a trusted home network. The auth layer protects credential management, configuration changes, and system operations (logs, backup/restore/reset, shutdown). Read-only state and basic charging controls are intentionally unauthenticated. ## Auth Modes | Mode | Trigger | Behavior | |------------|-----------------------|---------------------------------------------------| | `Enabled` | default | password required; JWT or API key accepted | | `Disabled` | `--disable-auth` flag | all auth checks skipped | | `Locked` | demo mode | mutating endpoints return 403; reads still work | Mode is fixed at startup. The frontend mirrors the mode so admin actions can be greyed out and a banner shown. ## Endpoint Sensitivity Tiers Three tiers, by what the caller has to prove: **Public.** No auth. State, loadpoint controls, login. Anyone on the network can read and operate. **Secure.** Requires a valid session: either the auth cookie (browser, JWT) or an API key in the `Authorization: Bearer …` header (automation). Used for configuration and system administration. **Critical.** Secure plus an additional admin-password check inside the handler. Used for destructive or credential-scoped operations. For some Critical endpoints (backup, restore, reset) the password check is **skipped when the caller is authenticated via API key**, so unattended automation doesn't need to embed the admin password. For credential-management endpoints (rotate API key, change admin password) the password check is **strict**: a leaked API key must not be able to rotate itself or change the admin password. Disabling auth short-circuits all checks. ## Sessions Two transports, no overlap: - Browsers use a session cookie (JWT, 90-day TTL, issued on login). - Automation uses an API key in the `Authorization: Bearer …` header. API keys are random alphanumeric strings prefixed `evcc_`. The prefix makes leaked keys recognizable to secret-scanning tools. A single API key per installation; regenerating replaces the previous one. Plaintext is shown to the user **once** at generation time and cannot be retrieved afterwards. ## Credential Storage Admin password and API key are stored as bcrypt hashes. The JWT signing secret is a per-installation random value. Plaintext credentials are never persisted. Removing the admin password (CLI recovery) also clears the JWT secret and the API key, which invalidates all outstanding sessions and any previously-issued API key. Regenerating the API key replaces the stored hash; the previous key stops working immediately. ## API Key Lifecycle Two operations: - **Status.** Whether a key is configured. Secure tier; never returns plaintext. - **Regenerate.** Critical tier, strict password check. Returns the new plaintext key exactly once. There is no delete operation: regenerating and discarding the new key achieves the same effect (the previous key stops working immediately). ## Endpoint Matrix | Endpoint category | Tier | Additional Requirements | |----------------------------------------------|-----------|------------------------------------| | State / read-only / basic charging control | Public | | | Set or update admin password | Public | admin password | | Configuration | Secure | | | Configuration embedding a script plugin | Critical | api key or admin password | | System: logs, cache, shutdown | Secure | | | API key status | Secure | | | System: backup / restore / reset | Critical | api key or admin password | | API key regenerate | Critical | admin password | Device test, create, and update (`/api/config/test/{class}` and `/api/config/devices/{class}`) instantiate a config immediately, so a `script` plugin in the payload runs a shell command on the server. Because that command could read credentials a session is not otherwise allowed to see (for example the contents of the database), these requests are treated as Critical when the config embeds a script plugin, at any nesting depth. A session caller must supply the admin password in the `X-Admin-Password` header; an API-key caller passes without it. The `go` (yaegi) and `js` (otto) plugins are excluded: their interpreters are sandboxed to pure computation and cannot read files, spawn processes, or open network connections. **Public** endpoints accept any caller. **Secure** endpoints require a valid session (cookie or API key). **Critical** endpoints require extra authentication in the form of an admin password (or, for some, an API key). ## Frontend Auth management lives under **General Config → Security**, which links to two sub-flows: change admin password, and manage the API key. The API key flow has a reveal view that shows the plaintext exactly once with a copy-to-clipboard link. When auth is disabled, the security modals show a warning banner and disable all action buttons. This is UI-only; the backend still accepts the underlying calls so legitimate automation against a disabled-auth instance keeps working. ## OpenAPI The OpenAPI spec declares two security schemes (cookie and bearer); protected operations accept either. --- ### Agents/Core Domain # 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)` — watts - `MeterEnergy` — `TotalEnergy() (float64, error)` — kWh - `PhaseCurrents` / `PhaseVoltages` / `PhasePowers` — per-phase readings ### Battery - `Battery` — `Soc() (float64, error)` — 0-100% - `BatteryCapacity` — kWh - `BatteryController` — set charge/discharge/hold mode ### Charger - `Charger` — `Status()`, `Enabled()`, `Enable(bool)`, `MaxCurrent(int64)` - `ChargerEx` — milliamp-precision current via `MaxCurrentMillis(float64)` - `PhaseSwitcher` — `Phases1p3p(int) error` - `ChargeRater` — `ChargedEnergy() (float64, error)` - `ChargeTimer` — `ChargeDuration() (time.Duration, error)` ### Vehicle - `Vehicle` — `Soc()`, `Capacity()`, `Identifiers()`, `Phases()`, `OnIdentified()` - `VehicleRange`, `VehicleOdometer`, `VehicleClimater`, `VehicleFinishTimer`, `VehiclePosition` - `ChargeController` — remote start/stop on vehicle - `CurrentLimiter` — `GetMinMaxCurrent()` for vehicle-side current limits - `CurrentController` — some vehicles (Tesla, Fiat) also implement `MaxCurrent()` 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 connected - `B` — connected, not charging - `C` — 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 `RWMutex` for its state (meters, battery, tariffs) - **Loadpoint** owns `RWMutex` for its state (charger, vehicle, current) - **Coordinator** owns `RWMutex` for 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 deadline - `optimalPlan()` — cheapest non-contiguous slots - `continuousPlan()` — cheapest continuous window (fallback) ## Key File Locations - `api/api.go` — all core interfaces - `core/site.go` — Site orchestrator + control loop - `core/loadpoint.go` — Loadpoint state machine (pvMaxCurrent, mode switch) - `core/site_battery.go` — battery priority logic - `core/site_tariffs.go` — tariff integration - `core/planner/planner.go` — charge time optimization - `core/prioritizer/prioritizer.go` — power allocation across loadpoints - `core/circuit/circuit.go` — electrical domain limits --- ### Agents/Easee Architecture # Easee Charger Architecture The Easee integration communicates with the Easee cloud over two distinct channels: 1. **REST API** (`https://api.easee.com/api`) — synchronous control commands and configuration. 2. **SignalR WebSocket** (`https://streams.easee.com/hubs/chargers`) — asynchronous real-time state updates and command confirmations. Commands are sent via REST; their acknowledgement and state changes are delivered asynchronously via SignalR. ## Authentication ### Flow Authentication uses username/password credentials against: ``` POST https://api.easee.com/api/accounts/login ``` Returns a `Token` struct with `accessToken` (short-lived JWT), `refreshToken`, `expiresIn`, and `tokenType`. ### Token Lifecycle Wrapped in `oauth2.TokenSource` via `oauth.RefreshTokenSource`. Near expiry, automatically calls: ``` POST https://api.easee.com/api/accounts/refresh_token ``` Falls back to full re-login if refresh fails. ### Token Caching `TokenSource` is shared per user via `cache.New[oauth2.TokenSource]()`. Multiple `Easee` instances with the same user email share a single token source, preventing redundant re-authentication. ## Initialization Sequence `NewEasee` performs these steps in order: 1. **Charger Discovery** — If no serial provided, queries `GET /api/chargers` and expects exactly one charger. 2. **Site and Circuit Discovery** — `GET /api/chargers/{chargerID}/site`. Searches for a single-charger circuit for circuit-level phase control. 3. **SignalR Connection** — Creates client with `WithMaxElapsedTime(0)` to retry forever (default 15-min cap would silently stop updates). 4. **Subscription** — On every `ClientConnected`, sends `SubscribeWithCurrentState(chargerID, true)` to replay full current state before switching to push-on-change. 5. **Startup Gate** — Blocks until `CHARGER_OP_MODE` is received (one-shot `sync.OnceFunc`). 6. **Optional State Wait** — Waits up to 3s for `SESSION_ENERGY`, `LIFETIME_ENERGY`, `TOTAL_POWER`. WARN if missing but initialization succeeds. ## SignalR Back-Channel ### Why SignalR is Required 1. **Commands are fire-and-forget at HTTP level.** HTTP response only confirms cloud received the request. Success/failure arrives via SignalR `CommandResponse`. 2. **State is event-driven, not pollable.** No REST endpoint streams charger state. 3. **Ticks correlation only works with a live connection.** If SignalR drops mid-command, the waiter times out. ### Server -> Client Methods #### `ProductUpdate(json.RawMessage)` Primary state channel. Carries a single `Observation` with `ID` (ObservationID), `Value`, `DataType`, and `Timestamp`. - **Timestamp deduplication**: older timestamps for the same ID are silently dropped. - **Non-blocking fan-out**: observation sent on `obsC` via non-blocking select. #### `CommandResponse(json.RawMessage)` Async acknowledgement for REST commands. Contains `Ticks` (correlation key), `WasAccepted`, `ResultCode`, and `ID` (ObservationID). Routes through three maps in order: 1. `pendingTicks[res.Ticks]` — primary correlation for async (HTTP 202) commands 2. `pendingByID[ObservationID(res.ID)]` — fallback when Ticks mismatch 3. `expectedOrphans[ObservationID(res.ID)]` — counter for sync (HTTP 200) endpoints that still produce a CommandResponse Unmatched responses are logged as WARN (rogue response from external system). #### `ChargerUpdate` / `SubscribeToMyProduct` Logged at TRACE, not processed further. ## Command Flow and Async Correlation ### REST Command Endpoints ``` POST /api/chargers/{chargerID}/commands/{action} (start/stop/pause/resume) POST /api/chargers/{chargerID}/settings (enable, DCC, PhaseMode, SmartCharging) POST /api/sites/{siteID}/circuits/{circuitID}/settings (dynamic circuit currents) ``` ### Response Handling | HTTP Status | Meaning | Behavior | |-------------|---------|----------| | `200` | Synchronous / already applied | Returns immediately | | `202` | Asynchronous, Ticks provided | Waits for matching CommandResponse | | other | Error | Returns error | ### Ticks Correlation On 202, the body contains `RestCommandResponse` with a `Ticks` field (.NET DateTime.Ticks). If `Ticks == 0`, the command was a no-op. Each in-flight command creates a **buffered channel** (capacity 1), registered in both `pendingTicks` and `pendingByID`, cleaned up via `defer`. ### The Sync/Async Mismatch Some endpoints return HTTP `200` but still fire a `CommandResponse` via SignalR. The observed case is circuit settings (`POST /api/sites/{siteID}/circuits/{circuitID}/settings`) which returns `200` but generates `CommandResponse` with `ID=22` (`CIRCUIT_MAX_CURRENT_P1`). Handled via the **expected-orphan counter**: ```go expectedOrphans map[easee.ObservationID]int // protected by cmdMu ``` Before a POST to a known 200-returning endpoint, increment the counter. When `CommandResponse` arrives with no pending match, decrement and silently consume. Counter at 0 means genuinely rogue. ## State Management ### Internal State Fields (all protected by `sync.RWMutex`) | Field | Observation | Notes | |-------|------------|-------| | `opMode` | `CHARGER_OP_MODE` (109) | Central state machine | | `chargerEnabled` | `IS_ENABLED` (31) | Hardware enable state | | `smartCharging` | `SMART_CHARGING` (102) | LED color mode | | `currentPower` | `TOTAL_POWER` (120) | Watts (API sends kW, multiplied by 1000) | | `sessionEnergy` | `SESSION_ENERGY` (121) | kWh, special zero-handling | | `totalEnergy` | `LIFETIME_ENERGY` (124) | kWh, updated ~hourly | | `currentL1/L2/L3` | `IN_CURRENT_T3/T4/T5` (183/184/185) | Phase currents in A | | `phaseMode` | `PHASE_MODE` (38) | 1=single, 2=auto, 3=locked 3-phase | | `dynamicCircuitCurrent[3]` | `DYNAMIC_CIRCUIT_CURRENT_P1/P2/P3` (111/112/113) | Per-phase circuit limit | | `maxChargerCurrent` | `MAX_CHARGER_CURRENT` (47) | Hardware max (non-volatile) | | `dynamicChargerCurrent` | `DYNAMIC_CHARGER_CURRENT` (48) | Volatile current limit | | `reasonForNoCurrent` | `REASON_FOR_NO_CURRENT` (96) | Debug enum | | `pilotMode` | `PILOT_MODE` (100) | CP signal state A-F | | `rfid` | `USER_IDTOKEN` (128) | Last scanned RFID token | ### Session Energy Zero-value Protection `sessionEnergy` is never set to `0` from a `ProductUpdate` — the API sends spurious zeros erratically. Session reset is driven by op-mode transition: when `CHARGER_OP_MODE` transitions from disconnected to awaiting-start, `sessionEnergy` resets to `0` with a fresh timestamp. ## Charger Operation Modes ``` 0 = Offline — no cloud connection 1 = Disconnected — no car plugged in 2 = AwaitingStart — car plugged, waiting for authorization/start 3 = Charging — actively charging 4 = Completed — car full or finished, cable still plugged 5 = Error — fault condition 6 = ReadyToCharge — ready, current available 7 = AwaitingAuthentication — RFID auth required 8 = Deauthenticating — finishing authentication teardown ``` ### Mapping to evcc Status | opMode | evcc Status | |--------|------------| | 1 (Disconnected) | A | | 2, 4, 6, 7, 8 | B | | 3 (Charging) | C | | 0, 5 and others | error | ## Enable/Disable Flow ### Enable = true 1. If `chargerEnabled == false`: POST settings `{ enabled: true }` and wait. 2. If `opMode == Disconnected`: return (no cable). 3. If `opMode == AwaitingAuthentication && authorize`: action = `start_charging`. 4. Otherwise: action = `resume_charging`. 5. POST `/commands/{action}` and wait. 6. Wait for `opMode` to reach enabled state. 7. Wait for `dynamicChargerCurrent` to reach `32` (Easee sets this on resume). 8. Call `MaxCurrent(c.current)` to restore previous setpoint. ### Enable = false 1. If disconnected or (awaiting auth && !authorize): return. 2. POST `/commands/pause_charging` and wait. 3. Wait for `opMode` to reach disabled state. 4. Wait for `dynamicChargerCurrent` to reach `0`. ### State Waiting Pattern Both `waitForChargerEnabledState` and `waitForDynamicChargerCurrent` use: 1. Short-circuit check: if already in target state, return immediately. 2. Open a timer. 3. Loop on `obsC` channel. 4. On timer expiry: **one final check** before returning `api.ErrTimeout`. The final check handles the race where the state update arrived between the last channel read and the timer fire. ## Phase Control ### Circuit-Level (preferred, when circuit is known) Phase switching by zeroing dynamic circuit current on unused phases: ``` POST /api/sites/{siteID}/circuits/{circuitID}/settings ``` For 1-phase: set P2=0, P3=0. For 3-phase: restore all three. This POST returns HTTP `200` but still fires a `CommandResponse` with `ID=22` (expected orphan). ### Charger-Level (fallback) Uses `PhaseMode` setting: `1` for single-phase, `2` (auto) for 3-phase. After changing PhaseMode, `Enable(false)` is called — the loadpoint then re-enables, because PhaseMode changes only take effect after a charging cycle restart. ## Authorization Mode (`authorize`) When `authorize: true`, evcc sends `start_charging` to authorize sessions when the charger enters `ModeAwaitingAuthentication`. This enables fully unattended operation but is incompatible with RFID-based vehicle identification. When `authorize: false`, evcc does nothing in mode 7 — the charger waits for external authorization (RFID card or app). Setting `authorize: true` also prevents the charger from auto-starting at 32A on plug-in, giving evcc full control from the first amp. ## Concurrency Model ### Mutexes | Mutex | Type | Protects | |-------|------|----------| | `c.mux` | `sync.RWMutex` | All charger state fields | | `dispatcher.mu` | `sync.Mutex` | `pendingTicks`, `pendingByID`, `expectedOrphans` maps (inside `CommandDispatcher`) | Command dispatch was extracted into `charger/easee/dispatcher.go` (`CommandDispatcher` struct). The two mutexes are intentionally separate to prevent the SignalR receive loop from blocking on command dispatch operations. ### Observation Channel `obsC chan Observation` is unbuffered. `ProductUpdate` sends via non-blocking select — if no waiter is listening, the notification is dropped. The authoritative state is always in the struct fields; the channel is only a notification mechanism. **Design constraint**: any waiter on `obsC` must include a final state check after timer expiry before returning `api.ErrTimeout`. ## Known Design Concerns 1. **SESSION_ENERGY zero-value protection** — defensive measure based on field observations; root cause unverified. 2. **LIFETIME_ENERGY** — inaccurate by design, API pushes updates ~hourly. 3. **current vs dynamicChargerCurrent drift** — evcc's desired setpoint and charger's confirmed value can drift around pause/resume cycles. Resynced via `MaxCurrent(c.current)` after resume. 4. **Multi-charger circuits** — only circuit-level phase control when charger is alone on its circuit. Multi-charger circuits fall back to less precise charger-level control. 5. **Stale CommandResponses after reconnect** — if SignalR drops mid-command, the response may arrive after reconnect with no pending entry, triggering a false-positive rogue WARN. Acceptable trade-off. ## API Endpoints Summary | Method | Endpoint | Used For | |--------|----------|----------| | `POST` | `/accounts/login` | Initial authentication | | `POST` | `/accounts/refresh_token` | Token refresh | | `GET` | `/chargers` | Auto-discover charger ID | | `GET` | `/chargers/{id}/site` | Discover site and circuit | | `POST` | `/chargers/{id}/settings` | Enable/disable, DCC, PhaseMode, SmartCharging | | `POST` | `/chargers/{id}/commands/{action}` | start/stop/pause/resume charging | | `GET` | `/sites/{siteId}/circuits/{circuitId}/settings` | Read max circuit currents | | `POST` | `/sites/{siteId}/circuits/{circuitId}/settings` | Set dynamic circuit currents (phase switching) | ### SignalR Hub | Endpoint | `https://streams.easee.com/hubs/chargers` | |----------|------------------------------------------| | Client -> Server | `SubscribeWithCurrentState(chargerID, true)` | | Server -> Client | `ProductUpdate`, `ChargerUpdate`, `SubscribeToMyProduct`, `CommandResponse` | ## Configuration | Parameter | Required | Default | Notes | |-----------|----------|---------|-------| | `user` | yes | | Easee account email | | `password` | yes | | Easee account password | | `charger` | no | | Charger serial; auto-detected if exactly one on account | | `timeout` | no | `20s` | HTTP timeout for all API calls and command waits | | `authorize` | no | `false` | If true, evcc sends `start_charging` to authorize sessions | Supported products: Easee Home, Easee Charge, Easee Charge Lite, Easee Charge Core. Declared capabilities: `1p3p` (phase switching), `rfid` (RFID identification). Requires evcc sponsorship. --- ### Agents/Hardware Integrations # Hardware Integrations: Chargers, Meters, Vehicles ## Integration Pattern All device types use a registry-based factory pattern: ```go // Self-registration in init() func init() { registry.AddCtx("typename", NewFromConfig) } func NewFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) { // Parse config, create client, return implementation } ``` Optional interfaces are added via the decorator pattern: ```go //go:generate decorate -f decorateXxx -b *Xxx -t "api.PhaseSwitcher,Phases1p3p,func(int) error" ``` ## Charger Implementations ### By Protocol | Protocol | Examples | |----------|---------| | HTTP/REST | Easee (REST+SignalR), Wallbox, go-e, OpenWB, Shelly | | Modbus RTU/TCP | KEBA, Wallbe, CFOS, Bender, Delta, Mennekes | | OCPP 1.6 | Generic charge point server | | EEBus/ISO 15118 | EEBus SPINE protocol | | UDP/Custom | KEBA UDP, OpenEVSE, Wattpilot, NRGKick | | MQTT | OpenWB, Tasmota, Shelly | | Smart Socket | Shelly, Tapo, TP-Link, FritzDECT | ### Required Charger Interface ```go type Charger interface { ChargeState Enabled() (bool, error) Enable(enable bool) error CurrentController } ``` Where `ChargeState` provides `Status() (ChargeStatus, error)` (A/B/C) and `CurrentController` provides `MaxCurrent(current int64) error`. ### Optional Charger Interfaces - `ChargerEx` — `MaxCurrentMillis(float64)` for milliamp precision - `PhaseSwitcher` — `Phases1p3p(int)` to switch 1p/3p - `Meter` / `MeterEnergy` — built-in power/energy measurement - `PhaseCurrents` / `PhaseVoltages` — per-phase readings - `ChargeRater` — `ChargedEnergy()` for session energy - `ChargeTimer` — `ChargeDuration()` for session time - `Identifier` — `Identify()` for RFID/vehicle identification ### Key Implementations - **Easee** — REST + async SignalR; see `docs/agents/easee-architecture.md` for full detail - **OCPP** (`charger/ocpp.go`) — Full 1.6 with charge point management - **go-e** — Dual API (v1 local HTTP, v2 cloud); phase switching on v2 - **EEBus** — Complex SPINE protocol with USE cases (CEM, EV, EVCC) - **Generic configurable** (`charger/charger.go`) — plugin-driven via YAML template ### Adding a New Charger 1. Create `charger/xxx.go` (or YAML template in `templates/definition/charger/`) 2. Implement `NewXxxFromConfig()` returning `api.Charger` 3. Required: `Status()`, `Enabled()`, `Enable()`, `MaxCurrent()` 4. Register: `registry.AddCtx("xxx", factory)` in `init()` 5. Optional: add decorator for PhaseSwitcher, Meter, etc. 6. Add template: `templates/definition/charger/xxx.yaml` for UI metadata ## Meter Implementations ### By Category | Category | Examples | |----------|---------| | Modbus/SunSpec | SDM630, SMA, Fronius, Victron | | HTTP/REST | Homewizard, Shelly Gen3, E3DC | | Smart Home | HomeAssistant entities, Homematic | | Battery/Storage | Tesla Powerwall, LG ESS, Zendure | ### Required Meter Interface ```go type Meter interface { CurrentPower() (float64, error) // watts } ``` ### Optional: `MeterEnergy`, `PhaseCurrents/Voltages/Powers`, `Battery`, `BatteryCapacity` ### Key Implementations - **mbmd** (`meter/mbmd.go`) — RS485 device library with auto-detection - **SunSpec** (`plugin/sunspec.go`) — Modbus model-based point queries - **Generic** — plugin-driven (HTTP, Modbus, MQTT sources) ## Vehicle Integrations | Manufacturer | API Type | |-------------|----------| | Tesla | Fleet API + vehicle-command proxy | | VW Group | WeConnect (VW, Audi, Skoda, Seat, Cupra) | | Hyundai/Kia | BlueLink (regional variants) | | BMW/Mini | ConnectedDrive v2 | | Mercedes | Official API | | Renault/Nissan | Renault API + Carwings | | Ford | FordConnect (US/EU) | | Porsche | Porsche Connect | | PSA Group | Peugeot, Citroen, DS, Opel | | Generic | OVMS, Tronity | ### Required Vehicle Interface ```go type Vehicle interface { Battery // Soc() (float64, error) BatteryCapacity // Capacity() float64 IconDescriber // Icon() string FeatureDescriber // Features() []Feature PhaseDescriber // Phases() int TitleDescriber // GetTitle() string SetTitle(string) Identifiers() []string OnIdentified() ActionConfig } ``` ### Optional: `SocLimiter`, `ChargeState`, `VehicleRange`, `VehicleOdometer`, `VehicleClimater`, `VehicleFinishTimer`, `VehiclePosition`, `CurrentLimiter`, `CurrentController`, `ChargeController`, `Resurrector` ### Polling Strategy Configurable: always / while charging / while connected. Interval-based caching to avoid excessive cloud API calls. OAuth2 token handling built into each provider. ## Auto-Detection (`cmd/detect/`) Task-based parallel IP scanning: `ping` -> `tcp_http` -> `tcp_modbus` -> `sunspec` -> device-specific probes Detects: OpenWB, SMA, KEBA, E3DC, Sonnen, Tesla Powerwall, Wallbe, Fronius, Tasmota, Shelly, Phoenix, and many more. --- ### Agents/Ocpp Forwarder # OCPP Forwarder Architecture The OCPP forwarder (`charger/ocpp/forwarder.go`) is a hybrid proxy that lets a charger talk to evcc and an upstream OCPP server at the same time. Chargers connect directly to evcc's central system on the normal port. For each charger with a matching `ForwarderRule`, a "sidecar" WebSocket connection to the upstream server is opened and kept in parallel for the lifetime of the charger connection. The forwarder is opt-in: hooks (`chargerConnectHook`, `chargerDisconnectHook`, `chargerMessageHook` in `instance.go`) are nil unless a rule matches, so a charger without a rule behaves exactly as before. ## Forwarding modes Two modes apply at the same time, selected per message by its action. ### Transparent relay (billing-critical) For the actions in `actionsRelayedToUpstream` (`Authorize`, `StartTransaction`, `StopTransaction`, `DataTransfer`), upstream is the authoritative Central System: 1. Charger sends the Call to evcc. 2. The message hook forwards it to the upstream sidecar and bypasses evcc's OCPP handler. 3. Upstream's `CallResult`/`CallError` is relayed back to the charger. evcc's handler is never invoked for these. This lets the pay backend control authorization, issue its own transaction IDs, and see consistent Start/Stop pairs. ### Sidecar observation (informational) For all other messages (`BootNotification`, `StatusNotification`, `MeterValues`, `Heartbeat`, etc.): 1. Charger sends the Call to evcc, which processes it normally. 2. The same frame is also mirrored to the upstream sidecar. Upstream observes the session while evcc manages the charger as usual. ## Upstream to charger (commands) Calls (type 2) initiated by upstream are injected into the charger via `CS.Write`. The charger's `CallResult`/`CallError` is routed back to upstream. Examples: `RemoteStartTransaction`, `RemoteStopTransaction`, `GetConfiguration`, `ChangeConfiguration`, `TriggerMessage`, `SetChargingProfile`. `ChangeConfiguration` for `MeterValueSampleInterval` is intercepted: the forwarder absorbs it as a local throttle on `MeterValues` forwarded to upstream and replies `Accepted` without touching the charger's own config. evcc still processes every `MeterValues` frame for energy management. ## Read-only mode When a rule sets `ReadOnly`, upstream may observe but cannot control the charger. Any incoming Call from upstream is answered with a `SecurityError` and not forwarded. `ReadOnly` is applied live per message, so toggling it does not require reconnecting the sidecar. ## Connection lifecycle Frames that arrive from a charger before its sidecar finishes dialling are buffered (`pendingMsgs`) and flushed in order once the sidecar connects, so early messages such as `BootNotification` still reach upstream. If the dial fails or upstream drops mid-session, any buffered or in-flight relay Calls are answered to the charger with a `CallError` so it is not left hanging, and the failure is surfaced to the UI via `forwarderErrors`. When the upstream connection fails while the charger stays connected, the sidecar is re-dialled automatically with exponential backoff (`runUpstreamSidecar`, 5s up to 5min; a successful session resets the backoff). Between attempts the relay actions fall back to evcc's local handler, so charging continues but upstream misses those transactions. The reconnect loop ends when the charger disconnects, the rule is removed, or its connection parameters change (`ApplyForwarderRules` dials its own sidecar in that case). The last `BootNotification` of each connected charger is cached (`lastBoot`). When a sidecar connects mid-session (after an upstream reconnect or a rule added at runtime) and the pending buffer does not already carry a boot frame, the cached frame is replayed to upstream with a fresh message id, since many backends expect a boot before accepting transactions. Upstream's reply to the replay is discarded; evcc answered the charger's original boot long ago. Rules can be changed at runtime through `ApplyForwarderRules`. Sidecars for removed rules are closed, rules with changed connection parameters are re-dialled, and rules for chargers that are not connected are test-dialled to surface unreachable hosts immediately. --- ### Agents/Plugin System # Plugin System The plugin system (`plugin/`) provides protocol-level abstraction for device communication. Plugins implement typed getter/setter interfaces and are composed into charger, meter, or vehicle implementations via configuration. ## Plugin Types | Plugin | Protocol | Key Config | |--------|----------|------------| | `http` | HTTP/REST | `uri`, `method`, `headers`, `auth`, `cache`, `timeout` | | `mqtt` | MQTT | `topic`, `retained`, `payload` template, `timeout` | | `modbus` | Modbus TCP/RTU | `uri`, `register`, `scale`, `baudrate`, `rtu` | | `sunspec` | SunSpec/Modbus | Model-based point queries via device tree | | `js` | JavaScript/WASM | Inline script evaluation | | `go` | Go runtime | Dynamic Go code | | `gpio` | Linux GPIO | Digital I/O for relays | ## Getter/Setter Interfaces ```go type StringGetter func() (string, error) type FloatGetter func() (float64, error) type IntGetter func() (int64, error) type BoolGetter func() (bool, error) // + corresponding Setter types ``` ## Pipeline Transforms Plugins support chained transforms: `scale`, `offset`, `lookup`, `regex`. ## Template-Based Device Configuration Devices can be defined entirely via YAML templates using plugins: ```yaml # templates/definition/charger/example.yaml status: source: http uri: http://{{ .host }}/status enable: source: http uri: http://{{ .host }}/enable method: POST maxcurrent: source: http uri: http://{{ .host }}/current/{{ .maxcurrent }} ``` The generic configurable charger (`charger/charger.go`) wires these plugin configs into the `api.Charger` interface at runtime. ## Key Files - `plugin/config.go` — plugin registry and config types - `plugin/http.go` — HTTP plugin - `plugin/mqtt.go` — MQTT plugin - `plugin/modbus.go` — Modbus plugin - `plugin/sunspec.go` — SunSpec plugin - `charger/charger.go` — generic configurable charger using plugins --- ### Agents/Web Ui Api # Web UI & REST API ## Server Architecture - **Router:** gorilla/mux, strict slash - **Middleware:** GZIP, CORS (`*`), ETag caching, request logging, JSON headers, JWT auth - **Timeouts:** Read 5s, Write 10s, Idle 120s - **Static assets:** embedded in binary (`fs.FS`) - **Default port:** 7070 ## REST API (base `/api/`) ### Site-level - `POST /buffersoc/{value}`, `/prioritysoc/{value}`, `/residualpower/{value}` etc. - `GET /tariff/{tariff}` — tariff rates - `GET /sessions` — charging history - `GET /state` — complete system state (supports jq filtering) ### Per-loadpoint (`/loadpoints/{id}/...`) - `POST mode/{value}` — off/now/minpv/pv - `POST limitsoc/{value}`, `limitenergy/{value}` — charge limits - `POST mincurrent/{value}`, `maxcurrent/{value}` — current limits - `POST phases/{value}` — phase config - `POST priority/{value}`, `batteryboost/{value}` - `POST plan/energy/{value}/{time}` — schedule plan - `POST vehicle/{name}` — select vehicle - `POST smartcostlimit/{value}` — smart cost threshold ### Configuration (`/config/...`, auth required) - CRUD for devices (chargers, meters, vehicles, tariffs) - Template browsing and testing - Site, loadpoint, circuit, HEMS, messaging config - `GET /config/evcc.yaml` — YAML export ### System (`/system/...`, auth required) - Log viewing (`/log`, `/log/areas`), cache clear, shutdown ### Database (`/db/...`, auth + second factor required) - Backup download, restore from file, selective reset - Second factor: admin password in request body, or API key via Bearer token (bypasses password check) ### Handler Pattern Generic `handler[T]` with type conversion, setter, getter. Specialized: `floatHandler`, `intHandler`, `boolHandler`, `durationHandler`. ## WebSocket (`/ws`) - `coder/websocket` (RFC 6455) - Pub/sub via `SocketHub` - Buffered channels (1024 per subscriber) - Welcome message with full state snapshot - Incremental updates as JSON key-value pairs with dot-notation keys: ```json {"loadpoints.1.mode": "solar", "site.gridPower": 1234} ``` - Write timeout: 10s, compression (disabled for Safari) ## State Flow 1. WS connects -> receives welcome with full state 2. App emits `util.Param` on changes 3. Hub broadcasts to subscribers 4. Frontend `store.update(msg)` merges via dot-notation 5. Components reactively re-render ## Authentication - JWT, 90-day lifetime - HttpOnly cookie (`auth`) with `SameSite=Strict` - Also accepts `Authorization: Bearer ` header - Modes: Disabled, Locked (demo), Configured (password) - Protects `/api/config`, `/api/system`, and `/api/db` ## MQTT Integration - Publishes state changes to configurable broker - Subscribes to control topics - Retained messages for state persistence ## Key Files - `server/http.go` — router setup - `server/http_auth.go` — authentication - `server/http_site_handler.go` — state + request handlers - `server/http_config_*.go` — config endpoints - `server/http_loadpoint_handler.go` — per-loadpoint endpoints - `server/socket.go` — WebSocket pub/sub - `assets/js/app.ts` — Vue app entry - `assets/js/store.ts` — reactive state store - `assets/js/api.ts` — Axios clients - `assets/js/router.ts` — route definitions --- ### CONTRIBUTING # Contributing ## Developing ### Development environment Developing evcc requires [Go][1] [Node][2] and [Vite+][3]. We recommend VSCode with the [Go](https://marketplace.visualstudio.com/items?itemName=golang.Go), [Oxc](https://marketplace.visualstudio.com/items?itemName=oxc.oxc-vscode) and [Vue](https://marketplace.visualstudio.com/items?itemName=Vue.volar) extensions. Alternatively, if you use VS Code and [devcontainers](https://code.visualstudio.com/docs/devcontainers/containers), you can use the "Dev containers: Clone repository in container volume" action. This will create a devcontainer with the required toolchain and install the prerequisites as explained below. Wait until the startup log says "Done. Press any key to close the terminal." and check for any errors. We use linters (golangci-lint, oxlint/oxfmt via Vite+) to keep a coherent source code formatting. It's recommended to use the format-on-save feature of your editor. You can manually reformat your code by running: ```sh make lint make lint-ui ``` ### Device templates The software supports a massive amount of different devices (charger, meter, vehicle, tariff) that are defined by **templates**. A template can use the [plugin system](https://docs.evcc.io/docs/devices/plugins) (preferred) for communication with the device or reference a dedicated Go implementation. All bundled templates are located in the [`/templates/definition`](https://github.com/evcc-io/evcc/tree/master/templates/definition) directory. If you want to add a new plugin we recommend looking at existing, similar implementations for reference. When your template requires Go code you have to build the project from source (see instructions below). Otherwise you can use the evcc binary and point it to your new template file for testing. ```sh evcc --template-type charger --template new-charger-template.yaml ``` Besides the actual device configuration, templates contain meta-data like product name, manufacturer, instructions how to configure the device to work with evcc. On release, this data is extracted and pushed to the [`evcc-io/docs`](https://github.com/evcc-io/docs) repository to keep the documentation in sync. You can verify the generated meta-data by running: ```sh make docs ``` This will write the documentation-relevant data to `/templates/docs`. ## Building from source Install prerequisites (once): ```sh make install-ui make install ``` Build and run: ```sh make ./evcc ``` Open UI at http://127.0.0.1:7070 To run without creating the `evcc` binary use: go run ./... ### Cross Compiling To compile a version for an ARM device like a Raspberry Pi set GO command variables as needed, eg: ```sh GOOS=linux GOARCH=arm GOARM=6 make ``` ### Publishing docker images ```sh make docker DOCKER_IMAGE=my/docker DOCKER_TAG=0815 ``` ## Releases Releases are cut by pushing a `MAJOR.MINOR.PATCH` tag. Any other tag is ignored. Feature releases (`0.313.0`) must be tagged on `master`. The release workflow rejects a feature tag that is not reachable from `master`. Bugfix releases (`0.313.1`) may be tagged on any branch. That allows servicing an older release line without shipping everything that has landed on `master` since. Only the newest release moves the `latest` pointers, so a bugfix release of an older line publishes its artifacts, but leaves the `evcc/evcc:latest` docker tag, the homebrew formula, the GitHub latest release, the hassio addon and the demo instance untouched. To move a merged pull request onto a release branch, comment `/backport` on it. The pull request has to carry the `bug` label and must not be marked `(BC)`, only non-breaking bugfixes are backported. The commit is cherry-picked onto the branch of the next bugfix release, e.g. `release/0.313.1`, and a pull request is opened against it. The branch is created at the newest tag of that line if it does not exist yet. Pass a branch name, `/backport release/0.312.2`, to service an older line. Releasing a bugfix deletes its release branch, since the tag is the branch tip and everything on it has shipped. A branch that received further backports after the tag is kept. ## Debugging in VS Code ### evcc Core To debug a local evcc build in VS Code, add the following entry to your `launch.json`. You can adjust the referred configuration as needed to e.g. use your live configuration. ```json { "name": "Launch evcc local build with demo config", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceFolder}", "args": ["-c", "${workspaceFolder}/cmd/demo.yaml"], "cwd": "${workspaceFolder}", }, ``` ### UI For frontend development start the Vue toolchain in dev-mode. Open http://127.0.0.1:7071/ to get to the live reloading development server. It pulls its data from port 7070 (see above). ```sh vp install vp run dev ``` Start the backend with `--disable-auth` when checking the configuration UI of a throw-away instance. Without it the UI asks for an administrator password first. ```sh ./evcc --config tests/config-with-tariffs.evcc.yaml --disable-auth ``` ### Storybook We're using storybook to develop and visualize UI components in different states. Running the command below will open your browser at http://127.0.0.1:6006/. ```sh vp run storybook ``` ### Integration testing We use Playwright for end-to-end integration tests. They start a local evcc instance with different configuration yamls and prefilled databases. To run them, you have to do a local build first. ```sh make ui build vp run playwright ``` ### Simulating device state Since we don't want to run tests against real devices or cloud services, we've build a simple simulator that lets you emulated meters, vehicles and loadpoints. The simulators web interface runs on http://localhost:7072. ``` vp run simulator ``` Run an evcc instance that uses simulator data. This configuration runs with a very high refresh interval to speed up testing. ``` make ui build ./evcc --config tests/simulator.evcc.yaml ``` ## Communication Language evcc has a large German-speaking user base, but we want to be open and accessible to everyone in the global community. To balance these needs: - **Pull Requests** - 🇬🇧 English required - **Issues** - 🇬🇧 English recommended - 🇩🇪 German acceptable to start, must switch to English after first English comment - **GitHub Discussions** - 🇬🇧 🇩🇪 Both English and German allowed 💬 _Non-German speakers: We strongly encourage you to ask participants to switch to English. For pull requests, we have a language check bot that does this automatically._ Thank you all for helping make evcc accessible! 🌍 ## AI-Generated Content AI tools can be valuable aids for writing code, documentation, and creating issue reports. We welcome their use as part of the development process. When submitting AI-assisted contributions, keep these principles in mind: - **Understanding**: Fully understand all changes and be prepared to answer questions about them. - **Human-written intent**: Write issue descriptions, PR explanations, and commit messages in your own words. Keep them clear and concise, not lengthy generated text. - **Value**: Ensure contributions justify the review effort required from maintainers. - **Prior consensus**: Only open a PR if there is a related issue or discussion where the team has indicated a positive tendency toward the proposed change. Link to it in your PR description. Contributors remain responsible for their work regardless of which tools were used to create it. Contributions that appear to violate these principles will be closed with the following comment: ``` This contribution does not appear to meet our [AI contribution guidelines](https://github.com/evcc-io/evcc/blob/master/CONTRIBUTING.md#ai-generated-content). ``` ## Adding or modifying translations evcc already includes many translations for the UI. We're using [Weblate](https://hosted.weblate.org/projects/evcc/evcc/) to maintain translations. Feel free to add more languages or verify and edit existing translations. Weblate will automatically push all modifications to the evcc repository where they get reviewed and merged. If you find a text that is not yet translatable in [Weblate](https://hosted.weblate.org/projects/evcc/evcc/), you can help us by making it translatable. To do this, you can simply find the missing translation text in the code and apply similar changes as in these two Pull Requests: - [UI: Add missing translation for Error during startup](https://github.com/evcc-io/evcc/pull/14695) - [Translation: kein Plan, keine Grenze](https://github.com/evcc-io/evcc/pull/7461/) Note: To ensure the build succeeds after creating new translations, make sure to include your new translations in both the [de.json](i18n/de.json) and [en.json](i18n/en.json) files. [](https://hosted.weblate.org/engage/evcc/) [1]: https://go.dev [2]: https://nodejs.org/ [3]: https://viteplus.dev/guide/#install-vp ## Documentation, Website and iOS/Android App We're always thankful for contributions. Docs, website and app have dedicated repositories. Please open a GitHub pull request in the respective repository. - Documentation: [evcc-io/docs](https://github.com/evcc-io/docs) - Website: [evcc-io/evcc.io](https://github.com/evcc-io/evcc.io) - iOS/Android App: [evcc-io/app](https://github.com/evcc-io/app) ## License By contributing to evcc, you agree that your contributions will be licensed under the existing license terms that apply to the respective parts of the project. This constitutes an implicit Contributor License Agreement (CLA), following GitHub's standard practice where contributions are made under the same terms as the project license. --- ### README # evcc 🚘☀️ [](https://github.com/evcc-io/evcc/actions/workflows/nightly.yml) [](https://status.evcc.io/) [](https://hosted.weblate.org/engage/evcc/) [](https://cloudsmith.io/~evcc/packages/) [](https://github.com/evcc-io/evcc/releases)
[](https://depot.dev/?utm_source=evcc) evcc is an extensible EV Charge Controller and home energy management system. Our goal is to provide local energy management, without relying on cloud services. Featured in [PV Magazine](https://www.pv-magazine.de/2022/01/14/mit-open-source-lademanager-schnittstellen-zu-wallbox-und-photovoltaik-anlage-meistern/) and [c’t Magazin](https://www.youtube.com/watch?v=MoBpEXHMNjI). ## Features - simple and clean user interface - support for many [EV chargers](https://docs.evcc.io/en/docs/devices/chargers): - ABB, ABL, Alfen, Alphatec, Amperfied, Ampure, Audi, AUTEL, Autoaid, Bender, BMW, cFos, Charge Amps, Compleo, CUBOS, Cupra, Dadapower, DaheimLaden, Delta, E.ON Drive, E3/DC, Easee, Ebee, echarge, EcoHarmony, Edgetech, Elecq, eledio, Elli, EM2GO, EN+, enercab, Ensto, EntraTek, ESL, eSystems, Etrel, EVBox, Free2Move, Free2move eSolutions, Fronius, Garo, go-e, Hardy Barth, Heidelberg, Hesotec, Homecharge, Huawei, Innogy, INRO, Juice, Kathrein, KEBA, Kontron Solar, Kostal, KSE, LadeFoxx, LRT, Mennekes, NRGkick, OBO Bettermann, OpenEVSE, openWB, Optec, Orbis, PC Electric, Peblar, Phoenix Contact, Plugchoice, Porsche, Pracht, Pulsares, Pulsatrix, Qcells, Schneider, Schrack, SENEC, Siemens, Skoda, SMA, Smartfox, SolarEdge, Solax, Sonnen, Spelsberg, Stark in Strom, Sungrow, TechniSat, Tesla, Tigo, TinkerForge, Ubitricity, V2C Trydan, Vestel, Victron, Viridian EV, Volkswagen, Volt Time, Wallbe, wallbox, Walther Werke, Webasto, Weidmüller, Zaptec, ZJ Beny. [Read more.](https://docs.evcc.io/en/docs/devices/chargers) - **EEBus** support (Elli, PMCC) - **OCPP** support - **build-your-own:** Phoenix Contact (includes ESL Walli), EVSE DIN - **smart switches:** AVM, FRITZ!, Home Assistant, Homematic IP, HomeWizard, myStrom, Shelly, Tasmota, TP-Link. [Read more.](https://docs.evcc.io/en/docs/devices/smartswitches) - **heat pumps and electric heaters:** alpha innotec, Bosch, Buderus, Bösch, CTA All-In-One, Daikin, Elco, IDM, Junkers, Kermi, Lambda, my-PV, Nibe, Novelan, Roth, Stiebel Eltron, Tecalor, Vaillant, Viessmann, Wolf, Zewotherm. [Read more.](https://docs.evcc.io/en/docs/devices/heating) - support for many [energy meters](https://docs.evcc.io/en/docs/devices/meters): - **solar inverters and battery systems:** A-Tronix, Acrel, Ads-tec, Alpha ESS, Ampere, Anker, APsystems, AVM, Axitec, BGEtech, Bosch, Bosswerk, Carlo Gavazzi, Deye, E3/DC, Eastron, Enphase, FENECON, FRITZ!, FoxESS, Fronius, Ginlong, go-e, GoodWe, Growatt, Homematic IP, HomeWizard, Hoymiles, Huawei, IAMMETER, IGEN Tech, Kostal, LG, Loxone, M-TEC, Marstek, myStrom, OpenEMS, Powerfox, Qcells, RCT, SAJ, SAX, SENEC, Senergy, Shelly, Siemens, Sigenergy, SMA, Smartfox, SofarSolar, Solaranzeige, SolarEdge, SolarMax, Solarwatt, Solax, Solinteng, Sonnen, St-ems, Steca, Sungrow, Sunsynk, Sunway, Tasmota, Tesla, TP-Link, VARTA, Victron, Wattsonic, Youless, ZCS Azzurro, Zendure. [Read more.](https://docs.evcc.io/en/docs/devices/meters) - **general energy meters:** A-Tronix, ABB, Acrel, Alpha ESS, Ampere, AVM, Axitec, Bernecker Engineering, BGEtech, Bosch, Carlo Gavazzi, cFos, Deye, DSMR, DZG, E3/DC, Eastron, Enphase, ESPHome, FENECON, FoxESS, FRITZ!, Fronius, Ginlong, go-e, GoodWe, Growatt, Homematic IP, HomeWizard, Huawei, IAMMETER, inepro, IOmeter, Janitza, KEBA, Kostal, LG, Loxone, M-TEC, mhendriks, my-PV, myStrom, OpenEMS, ORNO, P1Monitor, Powerfox, Qcells, RCT, Saia-Burgess Controls (SBC), SAJ, SAX, Schneider Electric, SENEC, Shelly, Siemens, Sigenergy, SMA, Smartfox, SofarSolar, Solaranzeige, SolarEdge, SolarMax, Solarwatt, Solax, Solinteng, Sonnen, St-ems, Sungrow, Sunsynk, Sunway, Tasmota, Tesla, Tibber, TQ, VARTA, Victron, Volkszähler, Wago, Wattsonic, Weidmüller, Youless, ZCS Azzurro, Zuidwijk. [Read more.](https://docs.evcc.io/en/docs/devices/meters) - **integrated systems**: SMA Sunny Home Manager and Energy Meter, KOSTAL Smart Energy Meter (KSEM, EMxx) - **sunspec**-compatible inverter or home battery devices - **mbmd**-compatible devices, see [volkszaehler/mbmd](https://github.com/volkszaehler/mbmd#supported-devices) for a complete list - [vehicle](https://docs.evcc.io/en/docs/devices/vehicles) integrations (state of charge, remote charge, battery and preconditioning status): - Aiways, Audi, BMW, Citroën, Dacia, DS, Fiat, Ford, Genesis, Hyundai, Jeep, Kia, Mercedes-Benz, MG, Mini, Nissan, NIU, Opel, Peugeot, Polestar, Renault, Seat, Skoda, Smart, Subaru, Tesla, Toyota, Volkswagen, Volvo, Zero Motorcycles. [Read more.](https://docs.evcc.io/en/docs/devices/vehicles) - **services:** OVMS, Tronity, evNotify, ioBroker.bmw, mg2mqtt, mz2mqtt, TeslaLogger, TeslaMate, Tessi, volvo2mqtt - [plugins](https://docs.evcc.io/en/docs/devices/plugins) for integrating with any charger, smartswitch, heatpump, electric heater, meter, solar- / battery-inverter or vehicle: - Modbus, HTTP, MQTT, JavaScript, WebSocket, Go and shell scripts - status [notifications](https://docs.evcc.io/en/docs/reference/configuration/messaging) using [Telegram](https://telegram.org), [PushOver](https://pushover.net) and [many more](https://shoutrrr.nickfedor.com/) - logging using [InfluxDB](https://www.influxdata.com) and [Grafana](https://grafana.com/grafana/) - [REST](https://docs.evcc.io/en/docs/integrations/rest-api) and [MQTT](https://docs.evcc.io/en/docs/integrations/mqtt-api) APIs for integration with home automation systems - Add-ons for [Home Assistant](https://docs.evcc.io/en/docs/integrations/home-assistant) and [openHAB](https://www.openhab.org/addons/bindings/evcc) (not maintained by the evcc core team) ## Getting Started You'll find everything you need in our [documentation](https://docs.evcc.io/en/). ## Contributing Technical details on how to contribute, how to add translations and how to build evcc from source can be found [here](CONTRIBUTING.md). [](https://hosted.weblate.org/engage/evcc/) ## Sponsorship evcc believes in open source software. We're committed to provide best in class EV charging experience. Maintaining evcc consumes time and effort. With the vast amount of different devices to support, we depend on community and vendor support to keep evcc alive. While evcc is open source, we would also like to encourage vendors to provide open source hardware devices, public documentation and support open source projects like ours that provide additional value to otherwise closed hardware. Where this is not the case, evcc requires "sponsor token" to finance ongoing development and support of evcc. Learn more about our [sponsorship model](https://docs.evcc.io/en/docs/sponsorship). ## License This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. For additional license information regarding fonts, icons, and other assets, please see the [LICENSES](LICENSES/) folder. **Note:** All sponsor-required components are excluded from the MIT License. See file license header for details. If you want to use them in your own project, one evcc sponsorship token is required per evcc instance. Custom licensing agreements are available - please [contact us](mailto:info@evcc.io) to discuss your specific requirements. ---