Async Python client for the EAC (Electricity Authority of Cyprus) Distribution Web Portal
Project description
ha-eac
Home Assistant integration + Python library for the EAC (Electricity Authority of Cyprus) Distribution Web Portal — meterreading-dso.eac.com.cy.
What's in this repo
| Path | What it is |
|---|---|
src/eac_dso_portal/ |
Standalone async Python client for the (undocumented) EAC DSO REST API. Will be published to PyPI as eac-dso-portal. |
custom_components/eac_cyprus/ |
Home Assistant custom integration. Will be installable via HACS. |
tests/ |
Unit tests + optional smoke test against the real API. |
Status
Pre-alpha. Library covers login, service points, meter configs and readings (daily totals + 30-min load profile when the meter exposes them). HA integration is being scaffolded.
Design notes — devices, service points, and meters
How EAC structures the data
- A service point (
spId, 12-digit) is a permanent supply contract tied to an address. It does not change. - A meter is the physical hardware at that point. It can be replaced (e.g. mechanical → smart), and the API exposes the full history (
installDate,removalDate, serial number, model). - A meter can have multiple measurement channels (
mcList) over its lifetime — total kWh, peak/off-peak, 30-minute load profile, export, reactive, etc. Channels live inside meter configurations, which themselves can change over the meter's life.
What this integration does today
One Home Assistant device per service point. The device's manufacturer / model / serial_number reflect the currently installed meter. Sensors are created per measurement channel that the API actually returns data for — when a channel starts being populated (e.g. S-KWH-EXP after a PV install, or S-KWH-NORMAL/S-KWH-OFFPEAK after switching to a two-tariff plan), new sensors appear on the next refresh without any reconfiguration.
This is a deliberate choice for home automation, not industrial-grade meter accounting:
- The Energy Dashboard wants a single, continuous consumption track per address. Splitting that across two devices (one for the old meter, one for the new) would force the user to template-sum or live with broken graphs at the swap boundary.
- The physical meter is metadata about how the data is collected; the address is what the user actually cares about ("how much electricity did this flat use?").
- A separate per-meter device model is still possible later — see "Open question" below.
Counter resets when the meter is swapped
Cumulative kWh sensors are exposed with state_class = total_increasing, which is precisely Home Assistant's contract for "this is a monotonic counter that may reset to zero." When the DSO swaps the meter, the cumulative reading visibly jumps down (e.g. 79 304 kWh on the old ITRON → 0 kWh on the new Landis+Gyr). HA's recorder treats the drop as a reset, not as a negative consumption: long-term statistics keep accumulating the delta, so the lifetime total in the Energy Dashboard remains correct across the swap. No template, helper, or utility_meter workaround is required for this to be right.
What this does not preserve: the raw cumulative number on the old meter. It's available via the API and surfaced in entity attributes / diagnostics, but it isn't the headline state of any sensor.
Open question
Whether device = service point or device = physical meter is the right model is not settled. The current shape is optimised for home automation; an installation that cares about per-meter accountability (audit, billing reconciliation, regulator reporting) would probably prefer a hierarchical model:
- Service point as the primary device,
- Each historical meter as a sub-device linked via
via_device, - Frozen sensors on retired meters carrying their final readings.
If you have a use case for the latter, please open an issue — happy to add it as a config-flow option without breaking the existing device layout.
Development
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
The smoke test in tests/test_smoke.py talks to the real portal. It's auto-skipped unless .env exists with valid credentials:
EAC_USERNAME=you@example.com
EAC_PASSWORD=********
.env is git-ignored. Never commit credentials.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eac_dso_portal-0.1.0.tar.gz.
File metadata
- Download URL: eac_dso_portal-0.1.0.tar.gz
- Upload date:
- Size: 14.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90bf30ccffa4915d2ff137069bdd846b9ff8b32a2c1b513a2a01ef0e72da3c43
|
|
| MD5 |
62487d8bf35e4f4e5d21b756f086393f
|
|
| BLAKE2b-256 |
172f96f62b84fff92027d8ca87987f78ed08e619e21ff210d6fa6bc0d54370e3
|
File details
Details for the file eac_dso_portal-0.1.0-py3-none-any.whl.
File metadata
- Download URL: eac_dso_portal-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6620f9928da0625fb399d39dc94f0736a88f9e5b633e6dd0315bcfa10cf9cb3
|
|
| MD5 |
4215cc3970be8d372e6aca6ea4ed4033
|
|
| BLAKE2b-256 |
d116225597979062ec276890e9938b98401aff0ed280c950f45a0d4073401d94
|