evcc-io/AGENTS.md

16 KiB

Agent Rules for evcc Project

This file provides guidance to AI coding agents when working with code in this repository.

Project Overview

  • 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

Essential Commands

  • make - build full application (UI + Go binary)
  • make build - build Go binary only
  • make ui - build UI assets only
  • make install - install Go tools and dependencies
  • make install-ui - install Node.js dependencies (npm ci)
  • make test - run Go tests
  • make test-ui - run frontend tests
  • make lint - run Go linting (golangci-lint)
  • make lint-ui - run frontend linting
  • npm run dev - start Vue dev server (http://127.0.0.1:7071)
  • npm run playwright - run integration tests
  • evcc --template-type [type] --template [file] - test device templates
  • make docs - generate template documentation

Domain Knowledge

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

Loading guide by task type

  • 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)

Architecture Guidelines

Core Components

  • 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

Frontend Structure

  • 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)

Writing Style

  • 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 evcc when 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)
  • Commit subjects: Component: short description, no trailing period. Sub-scope in parens: Meter (Home Assistant): .... Use chore:/fix:/docs: only for non-feature changes

Comment Style

  • 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 X summary starting with the identifier name

Go Coding Standards

Core Principles

  • Follow Go idioms and conventions (Effective Go)
  • Use gofmt for formatting, self-documenting names, early returns
  • Handle all errors explicitly with meaningful messages
  • Use interfaces for behavior contracts (small, focused, single responsibility)
  • Use context.Context for 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

File Patterns

  • _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/May only 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 plus var _ api.Interface = (*Type)(nil) instead. api.Cap resolves static methods via direct type assertion, so unconditional implement.Has is redundant. A type with no conditional capabilities needs neither the implement.Caps embed nor implement.New()

Error Handling

  • Wrap errors with context: fmt.Errorf("context: %w", err)
  • Use errors.As and errors.Is for type checking
  • Use errors.Join for combining errors (prefer custom joinErrors helper)
  • Create domain-specific error types (ClassError, DeviceError)
  • Use backoff.Permanent(err) for non-retryable errors
  • Implement panic recovery with defer and recover() in script contexts

Testing & Code Generation

  • Use testing package with testify/assert and testify/require
  • Table-driven tests with struct definitions for multiple cases
  • Use gomock for interface mocking, go:generate mockgen for generation
  • Test both success and failure scenarios, use require for setup, assert for tests
  • Use go:generate for code generation, regenerate after interface/enum changes
  • Never manually edit generated files

Context & Concurrency

  • Use context.Context as first parameter for I/O operations
  • Use context.WithTimeout, context.WithCancel appropriately
  • Check ctx.Done() in long-running loops
  • Propagate context through goroutines for proper cancellation
  • Handle concurrent operations safely with Go's concurrency primitives

Data Validation

  • Filter NaN and Infinity values using math.IsNaN() and math.IsInf()
  • Validate numeric inputs from external sources
  • Use helper functions like parseFloat() that reject invalid values

Vue.js/TypeScript Frontend Standards

Core Architecture

  • 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

Component Development

  • Use TypeScript for all new frontend code
  • Use const instead of function for 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
  • Use configured Axios instance for HTTP communication

State Management

  • 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)

TypeScript Patterns

  • 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

Styling & Internationalization

  • 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.json and i18n/de.json for 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.

Testing

  • Write integration tests using Playwright for user workflows
  • Use Storybook for component development and visual testing
  • Use semantic selectors (roles, labels, button text); data-testid only when necessary
  • Test error states and loading states

Playwright Integration Testing

Test Organization

  • Location: tests/ directory with .spec.ts files
  • Configuration: .evcc.yaml files for different test scenarios
  • Utilities: tests/utils.ts for common helpers, tests/evcc.ts for 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)

Test Configuration

  • Base URL: http://127.0.0.1:7070
  • Parallel execution with different ports per worker for isolation
  • Uses ./evcc binary with test-specific configuration files
  • Each worker uses isolated temporary database files
  • Always runs with English UI language

Essential Commands

  • Must build before testing executing playwright make ui build since it uses the binary. For manual testing assets are build and reloaded automatically (vite dev).
  • Run tests: npm run playwright or npx playwright test
  • Debug: npx playwright test --debug
  • Specific test: npx playwright test tests/config-loadpoint.spec.ts

Selector Strategy

  • Preferred: Semantic selectors using getByRole(), getByLabel(), getByText()
  • Fallback: data-testid only 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() or class and id-based selectors

Test Patterns

  • Use test-specific .evcc.yaml configurations
  • Import utilities from tests/utils.ts for common operations
  • Focus on complete user journeys rather than isolated interactions
  • Use expectModalVisible() and expectModalHidden() helpers
  • Test configuration persistence across application restarts
  • Standard structure: import { start, stop, baseUrl } from ./evcc, use test.afterEach(stop)
  • Never use fixed timeouts, use existance of elements or wait for network idle

Device Integration & Configuration

Plugin System

  • 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.yaml already 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 by name alone.
  • 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

Configuration

  • 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

Security & Performance Guidelines

Security

  • 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)

Performance

  • 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

Pull Request Descriptions

Structure PR descriptions in this order. No headlines. Be concise.

  1. 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).

  2. 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.

  3. Bullet list: most significant changes or user-facing implications. Lead with the most significant.

  4. 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, no generator footers.