# wertigpar/ha-emaldo architecture

> Architecture diagram of the GitHub repository wertigpar/ha-emaldo, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-18). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/wertigpar/ha-emaldo
- Repository: https://github.com/wertigpar/ha-emaldo

## Overview

**1. Overall shape**
This is a Home Assistant custom integration, not a standalone app. Home Assistant loads `custom_components/emaldo`, creates entities and services from a config entry, and the integration continuously syncs Emaldo battery state into Home Assistant entities.

**2. Main external boundary**
The integration talks to Emaldo through two channels: a REST API for authentication, battery/power snapshots, and schedule data, and a separate end-to-end encrypted UDP channel for override and real-time control operations. That split is the core architectural boundary in the repo.

**3. Bundled Emaldo client library**
`custom_components/emaldo/emaldo_lib/` is the integration’s internal SDK for Emaldo. It contains the REST client, crypto helpers, E2E UDP transport, constants, and exception types. The Home Assistant layer depends on this library rather than calling the API directly.

**4. Setup and lifecycle**
`config_flow.py` handles initial setup, reconfiguration, and options. `__init__.py` wires the config entry into Home Assistant, forwards setup to the entity platforms, and keeps the integration reloadable without removal. This is the lifecycle boundary for credentials and polling settings.

**5. Shared runtime state**
The integration uses a shared client pattern so multiple entities and coordinators operate on the same authenticated Emaldo session and cached state. That avoids duplicated logins and keeps REST and E2E activity coordinated per Home Assistant config entry.

**6. Real-time and daily data coordinator**
`coordinator.py` is the main polling loop for battery, power, and daily energy data. It drives the fast-changing sensors and keeps last-known values when Emaldo is temporarily unreachable, using retry/backoff behavior rather than failing the entities outright.

**7. Schedule and override coordinator**
`schedule_coordinator.py` is the schedule-focused subsystem. It polls on a separate schedule, reads the AI plan plus any user override slots, and exposes chart-ready schedule data. It also handles the “next-day schedule ready” event when tomorrow’s plan first appears.

**8. Entity platform split**
The entity files map Emaldo capabilities onto Home Assistant entity types:
- `sensor.py` for power, energy, module diagnostics, balancing state, and schedule sensors
- `switch.py` for control toggles like third-party PV, sell-back, AI range override, and emergency charge
- `number.py` for numeric setpoints such as EV fixed charge amount and battery reserve thresholds
- `select.py` for mode selection such as EV charge mode
- `time.py` for emergency charge window start/end
- `calendar.py` for schedule visualization
These entities are mostly thin views over coordinator state and client actions.

**9. Service-driven control plane**
`services.py` exposes the higher-level mutation API: set slot ranges, apply bulk schedules, reset to internal AI plan, refresh schedule data, write EV charging schedules, backfill solar history, and set AI battery range. This is the main write path for automations and optimizer integrations.

**10. Data model and schedule encoding**
A major architectural detail is the 15-minute slot model. Schedule overrides are stored as byte values where `0` means idle, `1–100` encode charge targets, `128` means no override, and `129–255` encode discharge targets. The chart sensor surfaces this as structured attributes for dashboards, while services convert human-friendly actions into slot bytes.

**11. Dynamic and model-specific behavior**
The integration adapts entity creation to the detected Emaldo device family. Power Core-only entities such as EV controls and certain realtime solar sensors are only created when supported, while per-module battery sensors are registered dynamically after the first successful battery poll.

**12. Operational resilience**
The repo is built around unreliable external connectivity: the integration tolerates REST failures, retries E2E access, and keeps stale-but-useful sensor state until recovery. That makes the key diagrammatic flow “Emaldo cloud / E2E server ↔ coordinators ↔ entities/services ↔ Home Assistant,” with coordinators acting as the isolation layer between network volatility and entity state.

**13. Packaging and Home Assistant metadata**
`manifest.json`, `services.yaml`, `strings.json`, and the `translations/` files provide Home Assistant metadata, service schemas, and localization. `hacs.json` marks it for HACS distribution, so the repo is packaged as an installable HA custom integration rather than a general Python library.

Main entry points: [`custom_components/emaldo/__init__.py`](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/__init__.py) (Entry setup), [`custom_components/emaldo/services.py`](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/services.py) (Services), [`custom_components/emaldo/emaldo_lib/client.py`](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/client.py) (REST client), [`custom_components/emaldo/shared_client.py`](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/shared_client.py) (Shared client)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_ha_integration["Home Assistant integration"]
  node_init["Entry setup<br/>integration entrypoint<br/>[__init__.py]"]
  node_config_flow["Config flow<br/>setup flow<br/>[config_flow.py]"]
  node_shared_client[("Shared client<br/>session cache<br/>[shared_client.py]")]
  node_coordinator["Realtime coordinator<br/>polling coordinator<br/>[coordinator.py]"]
  node_schedule_coordinator["Schedule coordinator<br/>schedule polling"]
  node_services["Services<br/>mutation api<br/>[services.py]"]
  node_sensor(("Sensors<br/>entity platform<br/>[sensor.py]"))
  node_switch(("Switches<br/>entity platform<br/>[switch.py]"))
  node_number(("Numbers<br/>entity platform<br/>[number.py]"))
  node_select(("Selectors<br/>entity platform<br/>[select.py]"))
  node_time(("Time entities<br/>entity platform<br/>[time.py]"))
  node_calendar(("Calendar<br/>entity platform<br/>[calendar.py]"))
end

subgraph group_emaldo_sdk["Bundled Emaldo SDK"]
  node_client["REST client<br/>api client<br/>[client.py]"]
  node_e2e["UDP transport<br/>e2e transport<br/>[e2e.py]"]
  node_crypto["Crypto helpers<br/>[crypto.py]"]
  node_lib_const["SDK constants<br/>[const.py]"]
  node_lib_errors["SDK errors<br/>exceptions<br/>[exceptions.py]"]
end

node_home_assistant_core["Home Assistant<br/>runtime"]
node_emaldo_cloud["Emaldo cloud<br/>external api"]
node_emaldo_udp["E2E channel<br/>external transport"]

node_home_assistant_core -->|"loads"| node_init
node_home_assistant_core -->|"setup UI"| node_config_flow
node_init -->|"creates"| node_shared_client
node_init -->|"starts"| node_coordinator
node_init -->|"starts"| node_schedule_coordinator
node_init -->|"registers"| node_services
node_init -->|"forwards"| node_sensor
node_init -->|"forwards"| node_switch
node_init -->|"forwards"| node_number
node_init -->|"forwards"| node_select
node_init -->|"forwards"| node_time
node_init -->|"forwards"| node_calendar
node_config_flow -->|"persists"| node_shared_client
node_shared_client -->|"uses"| node_client
node_shared_client -->|"uses"| node_e2e
node_client -->|"REST"| node_emaldo_cloud
node_e2e -->|"encrypts"| node_crypto
node_e2e -->|"UDP"| node_emaldo_udp
node_coordinator -->|"reads"| node_shared_client
node_coordinator -->|"polls"| node_client
node_coordinator -->|"updates"| node_sensor
node_schedule_coordinator -->|"reads"| node_shared_client
node_schedule_coordinator -->|"polls"| node_client
node_schedule_coordinator -->|"feeds"| node_calendar
node_services -->|"mutates"| node_shared_client
node_services -->|"writes"| node_client
node_services -->|"controls"| node_e2e
node_sensor -->|"subscribes"| node_coordinator
node_sensor -->|"subscribes"| node_schedule_coordinator
node_switch -->|"invokes"| node_services
node_number -->|"invokes"| node_services
node_select -->|"invokes"| node_services
node_time -->|"invokes"| node_services
node_calendar -->|"renders"| node_schedule_coordinator
node_client -->|"depends on"| node_lib_const
node_client -->|"raises"| node_lib_errors
node_e2e -->|"depends on"| node_lib_const
node_e2e -->|"raises"| node_lib_errors
node_crypto -->|"depends on"| node_lib_const

click node_init "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/__init__.py"
click node_config_flow "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/config_flow.py"
click node_shared_client "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/shared_client.py"
click node_coordinator "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/coordinator.py"
click node_schedule_coordinator "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/schedule_coordinator.py"
click node_services "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/services.py"
click node_sensor "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/sensor.py"
click node_switch "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/switch.py"
click node_number "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/number.py"
click node_select "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/select.py"
click node_time "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/time.py"
click node_calendar "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/calendar.py"
click node_client "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/client.py"
click node_e2e "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/e2e.py"
click node_crypto "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/crypto.py"
click node_lib_const "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/const.py"
click node_lib_errors "https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/exceptions.py"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_init,node_config_flow,node_shared_client,node_coordinator,node_schedule_coordinator,node_services,node_sensor,node_switch,node_number,node_select,node_time,node_calendar toneBlue
class node_client,node_e2e,node_crypto,node_lib_const,node_lib_errors toneAmber
class node_home_assistant_core,node_emaldo_cloud,node_emaldo_udp toneNeutral
```

## Components

### Home Assistant integration

Lifecycle, coordinators, platforms, and service layer inside the custom integration.

- [Entry setup](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/__init__.py): integration entrypoint · Config entry setup, unload, and reload wiring for the integration. · `custom_components/emaldo/__init__.py`
- [Config flow](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/config_flow.py): setup flow · Initial setup, reconfiguration, and options management. · `custom_components/emaldo/config_flow.py`
- [Shared client](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/shared_client.py): session cache · Per-entry shared authenticated client and cached runtime state. · `custom_components/emaldo/shared_client.py`
- [Realtime coordinator](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/coordinator.py): polling coordinator · Main polling loop for battery, power, and daily energy state with retry and stale-state handling. · `custom_components/emaldo/coordinator.py`
- [Schedule coordinator](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/schedule_coordinator.py): schedule polling · Polls AI plan and override slots, and publishes chart-ready schedule state. · `custom_components/emaldo/schedule_coordinator.py`
- [Services](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/services.py): mutation api · Service layer for schedule writes, resets, refreshes, charging history, and AI range updates. · `custom_components/emaldo/services.py`
- [Sensors](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/sensor.py): entity platform · Power, energy, diagnostics, balancing, and schedule sensor entities. · `custom_components/emaldo/sensor.py`
- [Switches](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/switch.py): entity platform · Control toggles for PV, sell-back, AI override, and emergency charge. · `custom_components/emaldo/switch.py`
- [Numbers](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/number.py): entity platform · Numeric setpoints such as EV charge amount and battery reserve thresholds. · `custom_components/emaldo/number.py`
- [Selectors](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/select.py): entity platform · Mode selectors such as EV charging mode. · `custom_components/emaldo/select.py`
- [Time entities](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/time.py): entity platform · Emergency charge window start and end controls. · `custom_components/emaldo/time.py`
- [Calendar](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/calendar.py): entity platform · Schedule visualization entity for plan and override timelines. · `custom_components/emaldo/calendar.py`

### Bundled Emaldo SDK

Internal client library that handles REST, crypto, and E2E transport toward Emaldo.

- [REST client](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/client.py): api client · Authenticated REST API client for Emaldo cloud operations. · `custom_components/emaldo/emaldo_lib/client.py`
- [UDP transport](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/e2e.py): e2e transport · Encrypted E2E channel implementation for control traffic. · `custom_components/emaldo/emaldo_lib/e2e.py`
- [Crypto helpers](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/crypto.py): crypto · Cryptographic primitives and message handling for the E2E transport. · `custom_components/emaldo/emaldo_lib/crypto.py`
- [SDK constants](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/const.py): constants · Shared protocol and API constants for the bundled Emaldo client library. · `custom_components/emaldo/emaldo_lib/const.py`
- [SDK errors](https://github.com/wertigpar/ha-emaldo/blob/main/custom_components/emaldo/emaldo_lib/exceptions.py): exceptions · Exception types used by the bundled Emaldo client library. · `custom_components/emaldo/emaldo_lib/exceptions.py`

### Other components

- **Home Assistant**: runtime · Host platform that loads the custom integration and owns entities, services, and config entries.
- **Emaldo cloud**: external api · REST backend used for authentication, snapshots, schedules, and account data.
- **E2E channel**: external transport · Encrypted UDP path used for override and real-time control operations.

## Connections

- Home Assistant → Entry setup: loads · Home Assistant loads the config entry and integration entrypoint.
- Home Assistant → Config flow: setup UI · Home Assistant drives initial setup and reconfiguration through config flows.
- Entry setup → Shared client: creates · Integration entrypoint initializes shared per-entry runtime state.
- Entry setup → Realtime coordinator: starts · Entry setup starts the realtime coordinator.
- Entry setup → Schedule coordinator: starts · Entry setup starts the schedule coordinator.
- Entry setup → Services: registers · Integration entrypoint exposes service handlers.
- Entry setup → Sensors: forwards · Platform setup forwards entity creation to the sensor platform.
- Entry setup → Switches: forwards · Platform setup forwards entity creation to the switch platform.
- Entry setup → Numbers: forwards · Platform setup forwards entity creation to the number platform.
- Entry setup → Selectors: forwards · Platform setup forwards entity creation to the select platform.
- Entry setup → Time entities: forwards · Platform setup forwards entity creation to the time platform.
- Entry setup → Calendar: forwards · Platform setup forwards entity creation to the calendar platform.
- Config flow → Shared client: persists · Setup and options changes feed credentials and polling settings into shared runtime state.
- Shared client → REST client: uses · Shared runtime state wraps the authenticated REST client.
- Shared client → UDP transport: uses · Shared runtime state coordinates encrypted control transport.
- REST client → Emaldo cloud: REST · REST client communicates with Emaldo cloud services.
- UDP transport → Crypto helpers: encrypts · UDP transport relies on crypto helpers for secured message exchange.
- UDP transport → E2E channel: UDP · Encrypted control traffic flows over the external UDP channel.
- Realtime coordinator → Shared client: reads · Realtime polling uses the shared authenticated session and cached data.
- Realtime coordinator → REST client: polls · Realtime polling fetches snapshots and battery state from the REST API.
- Realtime coordinator → Sensors: updates · Coordinator state is exposed through sensor entities.
- Schedule coordinator → Shared client: reads · Schedule polling uses the shared authenticated session.
- Schedule coordinator → REST client: polls · Schedule data and AI plan data are read from the REST API.
- Schedule coordinator → Calendar: feeds · Schedule coordinator drives chart-ready calendar state.
- Services → Shared client: mutates · Service calls use the shared client state and credentials.
- Services → REST client: writes · High-level mutations are translated into REST API calls.
- Services → UDP transport: controls · Some service actions are delivered through the encrypted control channel.
- Sensors → Realtime coordinator: subscribes · Sensor entities render realtime coordinator state.
- Sensors → Schedule coordinator: subscribes · Schedule sensors render schedule coordinator state.
- Switches → Services: invokes · Control switches call into the service layer.
- Numbers → Services: invokes · Numeric setpoints are persisted through services.
- Selectors → Services: invokes · Selection changes are applied through services.
- Time entities → Services: invokes · Time window controls are persisted through services.
- Calendar → Schedule coordinator: renders · Calendar entity visualizes schedule coordinator data.
- REST client → SDK constants: depends on · REST client uses shared protocol constants.
- REST client → SDK errors: raises · REST client surfaces library exceptions.
- UDP transport → SDK constants: depends on · Transport implementation uses shared protocol constants.
- UDP transport → SDK errors: raises · Transport implementation surfaces library exceptions.
- Crypto helpers → SDK constants: depends on · Crypto helpers use shared protocol constants.
