This file provides guidance to AI coding agents when working with code in this repository.
- evcc is an extensible EV Charge Controller and home energy management system written in Go with a Vue.js frontend
- The system manages electric vehicle charging, integrates with solar systems, and provides local energy management without cloud dependencies
- Architecture follows a plugin-based approach for device integrations
make- build full application (UI + Go binary)make build- build Go binary onlymake ui- build UI assets onlymake install- install Go tools and dependenciesmake install-ui- install Node.js dependencies (vp install)make test- run Go testsmake test-ui- run frontend testsmake lint- run Go linting (golangci-lint)make lint-ui- run frontend lintingvp run dev- start Vue dev server (http://127.0.0.1:7071)vp run playwright- run integration testsbuild,openapiandtestare cached tasks invite.config.ts, run throughvp runevcc --config [file] --disable-auth- run a throw-away instance for UI checks without password setupevcc --template-type [type] --template [file]- test device templatesmake docs- generate template documentation
Deep documentation on specific subsystems is available in docs/agents/. Load what you need based on the task:
| File | When to load |
|---|---|
| Core Domain | Control loop, loadpoint logic, PV surplus, charge modes, tariffs, interfaces |
| Hardware Integrations | Charger/meter/vehicle implementations, adding new devices |
| Easee Architecture | Easee charger (REST+SignalR, async correlation, concurrency) |
| OCPP Forwarder | OCPP proxy/forwarder (sidecar relay to upstream OCPP server, read-only mode) |
| Plugin System | Plugin layer (HTTP, MQTT, Modbus, SunSpec, JS) |
| Web UI & API | REST API, WebSocket, Vue frontend, authentication |
| API Security | Auth modes, JWT/API key/session, two-tier checks, credential storage |
- Charger implementation β hardware-integrations + core-domain
- Easee charger work β easee-architecture + core-domain
- Meter implementation β hardware-integrations + plugin-system
- Vehicle implementation β hardware-integrations
- UI/frontend work β web-ui-api
- API endpoint work β web-ui-api + core-domain
- Auth / login / API key / permissions β api-security + web-ui-api
- Config/template work β plugin-system
- Control loop / charging logic β core-domain
- Bug in any area β core-domain + relevant topic file(s)
- main.go serves as entry point and embeds web assets and i18n files
- cmd/ contains CLI commands, application setup, and various utility commands (configure, detect, migrate, etc.)
- core/ contains core business logic with main files (loadpoint.go, site.go) and subdirectories:
- loadpoint/ - EV charging point management modules
- planner/ - Smart charging planning algorithms
- coordinator/ - Multi-loadpoint coordination logic
- session/ - Charging session management
- vehicle/ - Vehicle-specific core logic
- soc/ - State of charge handling
- api/ contains API definitions and types
- server/ handles HTTP server, WebSocket, MQTT, database operations, and various handlers
- charger/, meter/, vehicle/ contain device integrations
- tariff/ contains tariff integrations
- plugin/ implements plugin system for device and tariff communication
- assets/ contains Vue.js frontend application
- assets/js/ contains the main TypeScript/Vue.js application with:
- views/ - Vue page components (App.vue, Config.vue, Sessions.vue, etc.)
- components/ - Reusable Vue components
- composables/ - Vue utility functions
- types/ - TypeScript type definitions
- utils/ - Utility functions
- mixins/ - Vue mixins
- assets/css/ contains application stylesheets
- assets/public/ contains static assets and metadata
- i18n/ contains internationalization files
- tests/ contains Playwright integration tests and test configuration files
- dist/ contains built frontend assets (generated)
- No em dashes (β) in comments, commit messages, or docs. Use periods, commas, or colons
- Project name is
evcc, always lowercase - In user-facing strings, only mention
evccwhen needed to understand the context. Inside evcc's own UI the self-reference is usually redundant - Acronyms uppercase in prose: OCPP, MQTT, HEMS, SoC
- Terminology: German "Phasensaldierung" (meter netting signed power across phases each instant) is "summative energy measurement" in English. Avoid "phase balancing" (means load balancing) and "net metering" (a billing scheme)
- Terminology: the top-level load management circuit is "root circuit" in English, "Hauptstromkreis" in German
- Commit subjects:
Component: short description, no trailing period. Sub-scope in parens:Meter (Home Assistant): .... Usechore:/fix:/docs:only for non-feature changes
- Prefer self-documenting code over comments; comment the why, not the what
- Default to no comment. Only add one for a non-obvious constraint, invariant, workaround, or surprising behavior. Keep it to one line, two if necessary
- Skip refs to the current task, PR, issue, or caller ("added for X flow", "see #1234"). Git history covers that
- Exception: Go exported identifiers follow godoc convention. Short
// FuncName does Xsummary starting with the identifier name
- Follow Go idioms and conventions (Effective Go)
- Use
gofmtfor formatting, self-documenting names, early returns - Handle all errors explicitly with meaningful messages
- Use interfaces for behavior contracts (small, focused, single responsibility)
- Use
context.Contextfor I/O, long-running, or cancelable operations - Organize code into logical packages with clear responsibilities
- Prefer composition over inheritance, minimize external dependencies
- Navigate Go symbols with go-to-definition and find-references rather than text search; reserve text search for comments and string literals
_blueprint.go- templates for new device implementations_enumer.go- generated enum code*_decorators.go- generated decorator pattern implementations- Validate interface implementations:
var _ Interface = (*Type)(nil) - Capabilities: register via
implement.Has/Mayonly when a capability is conditional (runtime/config detection, e.g.if cp.PhaseSwitching { implement.Has(...) }). For capabilities present on every code path, declare a plain exported method plusvar _ api.Interface = (*Type)(nil)instead.api.Capresolves static methods via direct type assertion, so unconditionalimplement.Hasis redundant. A type with no conditional capabilities needs neither theimplement.Capsembed norimplement.New()
- Wrap errors with context:
fmt.Errorf("context: %w", err) - Use
errors.Asanderrors.Isfor type checking - Use
errors.Joinfor combining errors (prefer customjoinErrorshelper) - Create domain-specific error types (ClassError, DeviceError)
- Use
backoff.Permanent(err)for non-retryable errors - Implement panic recovery with
deferandrecover()in script contexts
- Use
testingpackage withtestify/assertandtestify/require - Table-driven tests with struct definitions for multiple cases
- Use
gomockfor interface mocking,go:generate mockgenfor generation - Test both success and failure scenarios, use
requirefor setup,assertfor tests - Use
go:generatefor code generation, regenerate after interface/enum changes - Never manually edit generated files
- Use
context.Contextas first parameter for I/O operations - Use
context.WithTimeout,context.WithCancelappropriately - Check
ctx.Done()in long-running loops - Propagate context through goroutines for proper cancellation
- Handle concurrent operations safely with Go's concurrency primitives
- Filter
NaNandInfinityvalues usingmath.IsNaN()andmath.IsInf() - Validate numeric inputs from external sources
- Use helper functions like
parseFloat()that reject invalid values
- Use Vue 3 Options API (preferred over Composition API)
- Use reactive stores without Vuex/Pinia for cross-component state
- Use global app instance (
window.app) only for: notifications (raise()), offline status (setOffline()/setOnline()), clearing notifications (clear()) - Organize components by feature/domain in
assets/js/components/subdirectories
- Use TypeScript for all new frontend code
- Use
constinstead offunctionfor component methods (e.g.,const updateType = () =>) - Define TypeScript interfaces for component props, data, and API responses
- Implement accessibility features (tabindex, aria-label, keyboard handlers)
- Use descriptive names for variables, functions, and event handlers
- Use early returns for readability
- Prefer named computed properties over inline template expressions, even for single use. Readability beats saving lines
- Use configured Axios instance for HTTP communication
- Never access the store from sub-components; keep them stateless and pass the values they need as props (emit events back to the parent). Only top-level views read from the store. This keeps components reusable and testable (e.g. Storybook should never mock the store).
- Use
reactive()from Vue for simple global state - Implement property setters for nested object updates using helper functions
- Use localStorage with reactive wrappers for persistent settings
- Use Vue
watch()for automatic persistence of settings changes - Separate concerns with dedicated stores (settings, application state)
- Define comprehensive interfaces for API responses and application state
- Use enums for constants (e.g.,
THEME,CURRENCY) - Extend global interfaces for window object augmentation
- Use union types for flexible but type-safe configurations
- Use generic types for reusable utility functions
- Handle type assertions carefully with proper error handling
- Create focused utility functions with proper TypeScript typing
- Use CSS Custom Properties for theming (semantic names:
--evcc-green,--evcc-battery) - Use existing custom media queries for responsive breakpoints
- Use
$t()function for all user-facing strings - Update both
i18n/en.jsonandi18n/de.jsonfor new strings - Use hierarchical namespace:
{section}.{component}.{purpose} - Examples:
config.vehicle.titleAdd,main.vehicleStatus.charging - Action patterns:
titleAdd,titleEdit,save,cancel,delete,validateSave - Use placeholders for dynamic content:
{soc},{duration},{value} - Prefer context-specific keys over generic ones
- Test with German translations (20-40% longer text)
- Keep separators and trailing punctuation (
:,β¦,β) in the template, not in the translation value.
- Write integration tests using Playwright for user workflows
- Use Storybook for component development and visual testing
- Use semantic selectors (roles, labels, button text);
data-testidonly when necessary - Test error states and loading states
- Location:
tests/directory with.spec.tsfiles - Configuration:
.evcc.yamlfiles for different test scenarios - Utilities:
tests/utils.tsfor common helpers,tests/evcc.tsfor binary management - Categories:
config-*.spec.ts(UI config),sessions.spec.ts/plan.spec.ts(workflows),smart-cost.spec.ts/limits.spec.ts(features),backup-restore.spec.ts/auth.spec.ts(integration)
- Base URL:
http://127.0.0.1:7070 - Parallel execution with different ports per worker for isolation
- Uses
./evccbinary with test-specific configuration files - Each worker uses isolated temporary database files
- Always runs with English UI language
- Must build before testing executing playwright
make ui buildsince it uses the binary. For manual testing assets are build and reloaded automatically (vite dev). - Run tests:
vp run playwrightorvpx playwright test - Debug:
vpx playwright test --debug - Specific test:
vpx playwright test tests/config-loadpoint.spec.ts
- Preferred: Semantic selectors using
getByRole(),getByLabel(),getByText() - Fallback:
data-testidonly when semantic selectors aren't available - Examples:
page.getByRole("button", { name: "Add charger" })page.getByLabel("Manufacturer").selectOption("Demo charger")page.getByRole("listitem", { name: "Draggable: First Loadpoint" })(using aria-label)page.getByTestId("loadpoint")(fallback only)
- never use
.locator()orclassandid-based selectors
- Use test-specific
.evcc.yamlconfigurations - Import utilities from
tests/utils.tsfor common operations - Focus on complete user journeys rather than isolated interactions
- Use
expectModalVisible()andexpectModalHidden()helpers - Test configuration persistence across application restarts
- Standard structure: import
{ start, stop, baseUrl }from./evcc, usetest.afterEach(stop) - Never use fixed timeouts. Wait on element state (visibility, count, value) instead.
- Never use
page.waitForLoadState("networkidle"). SPAs keep emitting requests (websockets, polling), so it either races or hangs. Wait for the specific element / value you need instead. - Keep test names and describe titles short and concrete. They should complement each other, not repeat. Prefer
describe("aux meter") test("create")overdescribe("aux meter") test("create aux meter and verify it appears"). Drop scenario filler like "and lands in section", "appears correctly", "ensure".
- Device types: chargers, meters, vehicles, tariffs
- Plugin protocols: Modbus, HTTP, MQTT, JavaScript, Go
- Define device capabilities and configuration in templates at
templates/definition/[type]/ - Don't restate param properties that
util/templates/defaults.yamlalready defines for that param name. Properties (description, help, type, unit, default, example, required, advanced, mask, private, usages, β¦) are inherited from defaults; only specify a property in a template to give it a different value. Restating the same value is redundant duplication: reference the param bynamealone. - Test templates:
evcc --template-type [type] --template [file] - Update docs after template changes:
make docs - When implementing or debugging against a third-party device library (eebus-go/ship/spine-go, ocpp-go, modbus/SunSpec), consult the library's current upstream documentation before coding rather than relying on recalled API details
- Use YAML format for all configuration files (default:
evcc.yaml, or specify with--config) - Provide clear validation and error messages for invalid configurations
- Support template-based device configurations with meaningful defaults
- Use SQLite as default database (default:
evcc.db, or specify with--database) with proper migrations and data integrity
- Validate all user inputs and sanitize data before database storage
- Use secure protocols (TLS) for external integrations
- Implement proper authentication and authorization
- Never log sensitive information (passwords, tokens, personal data)
- Optimize database queries with appropriate indexes
- Handle concurrent operations safely with Go's concurrency primitives
- Implement proper caching strategies and connection pooling
- Avoid blocking operations in main application loop
Structure PR descriptions in this order. No headlines. Be concise.
-
References first line: link related issues or PRs (
fixes #1123,replaces #222,pairs with org/repo#345). PRs should almost always reference an issue or related PR β only skip in rare exceptions (e.g. trivial typo fixes). -
Intro: one or a few concise sentences framing what the PR does and why it was created this way. The full problem description belongs in the linked issue, not here.
-
Bullet list: most significant changes or user-facing implications. Lead with the most significant.
-
TODO section (only if open points remain):
**TODO** - [ ] item a - [ ] item b
Avoid file paths, line numbers, or code listings reproduced from the diff. Include a code snippet only when it conveys the contract (event shape, API signature) more clearly than prose. No testing checklists, no co-author footers.
Never state that go build, go vet, go test -race, or gofmt pass (or any "all checks/tests green" phrasing). These are non-negotiable givens that must already be fulfilled, not noteworthy results.
- After opening or updating a pull request, watch CI until every check has finished. Work is not done while checks are still running. Fix failures on the same branch and keep watching until the run is green.
- Do not argue with automated review bots such as Sourcery. Either implement the suggestion or resolve the thread. No rebuttal comments.
Work produced by an AI agent must be attributable as such on GitHub. Append the tool's attribution footer to every PR description, issue body, review, and comment written by an agent, for example:
π€ Generated with [Claude Code](https://claude.com/claude-code)
Commit messages are the exception: no footer, and no Co-Authored-By trailer. Never dress agent work up as human review (e.g. "PR by an agent but looks good to me") in place of the footer.