Async client for PlusPortal energy customer portals (Thüga SmartService white-label)
Project description
ha-plusportal — Home Assistant integration for PlusPortal
Unofficial. This is an independent, community-built project. It is not affiliated with, endorsed by, or supported by Thüga SmartService GmbH — who develop and operate PlusPortal — nor by any utility running a PlusPortal instance. "PlusPortal" is their product name and is used here only to describe what this software connects to. For problems with this integration, open an issue here; do not contact your utility.
Read your metered electricity consumption out of a PlusPortal energy customer portal and get it into Home Assistant — including a cent-accurate bill projection.
PlusPortal is a white-label customer portal operated by many German utilities, each under
its own subdomain of the form https://<tenant>.plusportal.de. This project uses your own
account and your own data, through the same API the official web interface uses, so it
works for any tenant.
The repository contains two independent layers:
| Layer | Path | Depends on Home Assistant? | Status |
|---|---|---|---|
pyplusportal — async API client + CLI |
src/pyplusportal/ |
no | implemented |
plusportal — HACS custom integration |
custom_components/plusportal/ |
yes | implemented |
The client is deliberately usable and testable on its own, without Home Assistant.
Quick start (client only)
uv sync
cp .env.example .env # fill in tenant, username, password
uv run pyplusportal meters
pyplusportal is a single CLI with five sub-commands:
| Command | Purpose |
|---|---|
meters |
list metering points and their channels |
overview |
show the portal's dashboard aggregates |
readings |
fetch daily consumption values |
cost |
project this billing year's cost and settlement |
probe |
record redacted responses as test fixtures |
Tenant, username and password are picked up from .env (or the real environment), so
once .env is set up the commands need no other flags:
uv run pyplusportal readings --from 2026-06-18 --to 2026-07-24 --meter 1000 --format csv
cost additionally needs your tariff, since the portal itself exposes no price data:
PLUSPORTAL_ENERGY_PRICE_CT (required), plus PLUSPORTAL_BASE_PRICE_EUR,
PLUSPORTAL_MONTHLY_ADVANCE_EUR and PLUSPORTAL_BILLING_YEAR_START (optional, see
.env.example):
PLUSPORTAL_ENERGY_PRICE_CT=32.5 uv run pyplusportal cost
Run uv run pyplusportal help for the full option and environment-variable reference,
or uv run pyplusportal help <command> for one command's own options.
Verifying against your own portal
The test suite runs offline against recorded fixtures. A second, opt-in suite talks to a real portal and reconciles what this client computes against what the portal itself reports — the quarter-hourly series, the daily series and the portal's month total must agree to the last digit:
uv run pytest -m live # skips cleanly unless .env is filled in
It reads tenant, username and password from the environment only, so no account detail ever ends up in the repository.
Running the integration in a real Home Assistant
Neither test suite catches what only a running instance shows — a blocking call in the event loop, a timeout that is too short for the first backfill, an attribute Home Assistant cannot serialise. All three of those were real, and all three were found this way.
scripts/ha-dev.sh up # starts Home Assistant on :8124 with this integration
scripts/ha-dev.sh state # what the entities report
scripts/ha-dev.sh stats # what reached the Energy dashboard
scripts/ha-dev.sh logs # follow the integration
scripts/ha-dev.sh sync # after a change: rebuild, reinstall, restart
scripts/ha-dev.sh reset # throw the instance away
The integration is bind-mounted, so editing and restarting is the whole loop. The library
is installed into the container from a locally built wheel, which is what lets this work
before pyplusportal exists on PyPI — Home Assistant skips the pin in manifest.json once
the requirement is already satisfied.
On first start, open http://localhost:8124, create a throwaway owner account and add the
PlusPortal integration with the same values as your .env. Home Assistant's state
lives in .dev/, which is gitignored: once configured it holds your portal password.
Installing it in Home Assistant
Requires Home Assistant 2026.2.0 or newer — the integration uses statistics APIs that changed in that release.
The interface is available in English and German and follows whatever language Home Assistant is set to; there is nothing to choose. Field and entity names in this document are the English ones, so a German instance will show their German equivalents instead (Energy price → Arbeitspreis, Standing charge, accrued → Grundpreis kumuliert). Entity ids stay English in both, so templates and automations keep working if the language changes.
Through HACS
-
In Home Assistant, open HACS → ⋮ → Custom repositories.
-
Add
https://github.com/pklinger/ha-plusportalwith category Integration. -
Open the new PlusPortal entry and choose Download.
-
Restart Home Assistant.
-
Settings → Devices & Services → Add Integration → PlusPortal, then enter:
Field Where to find it Tenant number the six digits at the start of your portal address Username your portal login — often the customer number, not an e-mail Password your portal password
Home Assistant installs the pyplusportal library from PyPI on first setup. If that fails,
the entry will report "not ready" — check the log rather than retrying.
Without HACS
Copy custom_components/plusportal/ into your Home Assistant config/custom_components/
directory and restart. Nothing else differs; HACS only automates the copying and the
update notification.
Tariff, and what appears
The tariff is optional. Without it you get consumption; with it you also get cost. Settings → Devices & Services → PlusPortal → Configure:
| Option | Meaning |
|---|---|
| Energy price (ct/kWh) | required for any cost figure |
| Standing charge (EUR/year) | apportioned across the billing year |
| Monthly advance payment (EUR) | needed for the settlement forecast |
| Billing year starts (MM-DD) | defaults to 01-01 |
| Update interval (hours) | defaults to 6; the portal publishes once a day |
Per metering point you get consumption for the last day, the current and previous month, when the portal last had a value, and what share of the data is final. With a tariff, also the accrued energy cost and standing charge, the forecast for the billing year and the expected settlement.
The Energy dashboard
Consumption is written to long-term statistics rather than exposed as a
total_increasing sensor, because portal values arrive backdated by a day or more — a
state-based sensor would book all of it at the moment of import.
Under Settings → Dashboards → Energy → Grid consumption, pick the statistic named after
your meter with plusportal underneath it (a chart icon, not a lightning bolt). For cost,
choose Use an entity with the current price and select Energy price.
Quarter-hourly readings are aggregated into hourly statistics. Provisional values are left out and substitute values are included, which is the rule the supplier bills by, and each refresh re-reads a rolling three-week window so corrections land.
Legal
Unofficial and independent, as stated at the top. It uses your own credentials against your own account, over the same API the official web interface uses, and stores nothing outside your Home Assistant instance. No trademark, logo or branding of Thüga SmartService GmbH or any utility is used or distributed with this project. Use at your own risk.
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 pyplusportal-0.1.0.tar.gz.
File metadata
- Download URL: pyplusportal-0.1.0.tar.gz
- Upload date:
- Size: 533.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc84db4e98a6b15f3082e6d1c4ecfc9a608e8bbbf448c57a64effd67e076af1f
|
|
| MD5 |
33d3d641c65a2d304b717da41469db3b
|
|
| BLAKE2b-256 |
1869ec58a0629149dc9b7eaf98f4cf921a73c8f42a100e549ab1ba583bf7cec4
|
Provenance
The following attestation bundles were made for pyplusportal-0.1.0.tar.gz:
Publisher:
release.yml on pklinger/ha-plusportal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyplusportal-0.1.0.tar.gz -
Subject digest:
cc84db4e98a6b15f3082e6d1c4ecfc9a608e8bbbf448c57a64effd67e076af1f - Sigstore transparency entry: 2256460322
- Sigstore integration time:
-
Permalink:
pklinger/ha-plusportal@c6a9f3b1b6372505cae071ea8f29d47f1e7e599f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/pklinger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c6a9f3b1b6372505cae071ea8f29d47f1e7e599f -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyplusportal-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyplusportal-0.1.0-py3-none-any.whl
- Upload date:
- Size: 31.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59075fc9371c9a0f343d8e4ccb70a1178f6603b424cb6772a7486cbc870fc209
|
|
| MD5 |
75ecbb690b05b7d5ba1598e018e0cbf0
|
|
| BLAKE2b-256 |
435167fabf992dd707b7a84f8740e9f83cf0ba8622d97aee514cce8190224440
|
Provenance
The following attestation bundles were made for pyplusportal-0.1.0-py3-none-any.whl:
Publisher:
release.yml on pklinger/ha-plusportal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyplusportal-0.1.0-py3-none-any.whl -
Subject digest:
59075fc9371c9a0f343d8e4ccb70a1178f6603b424cb6772a7486cbc870fc209 - Sigstore transparency entry: 2256460326
- Sigstore integration time:
-
Permalink:
pklinger/ha-plusportal@c6a9f3b1b6372505cae071ea8f29d47f1e7e599f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/pklinger
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c6a9f3b1b6372505cae071ea8f29d47f1e7e599f -
Trigger Event:
push
-
Statement type: