Skip to main content

pyzonneplan

GitHub Release Python Versions Project Stage Project Maintenance License

GitHub Activity PyPI Downloads GitHub Last Commit Open in Dev Containers

Build Status Typing Status Code Coverage

Asynchronous Python client for the Zonneplan API.

About

pyzonneplan is an async client for the Zonneplan API, focused on:

  • Dynamic electricity and gas prices
  • Electricity and gas consumption, also without a P1 meter
  • Solar (PV) installation data
  • Home battery monitoring and control modes
  • EV charge point sessions and schedules

The library is under active development and endpoint coverage will keep expanding.

Installation

pip install pyzonneplan

Usage

Zonneplan authenticates with a one-time password (OTP) mailed to the account address instead of a password. Log in once, then store the token and pass it back in later:

import asyncio
import json
from datetime import date
from pathlib import Path

from pyzonneplan import Token, Zonneplan

TOKEN_FILE = Path("token.json")


async def main() -> None:
    """Log in (or restore the saved token) and read some data."""
    token = Token.from_dict(json.loads(TOKEN_FILE.read_text())) if TOKEN_FILE.exists() else None

    async with Zonneplan(email="you@example.com", token=token) as client:
        if token is None:
            challenge = await client.async_request_otp(source_name="pyzonneplan")
            await client.async_submit_otp(challenge, input("One-time password: "))

        account = await client.async_get_account()
        for connection in account.connections:
            print(connection.market_segment, connection.uuid)

        electricity = next(c for c in account.connections if c.market_segment == "electricity")
        summary = await client.async_get_summary(electricity.uuid)
        print("Tariff group now:", summary.usage.type)

        chart = await client.async_get_electricity_chart(electricity.uuid, date.today())
        if chart.group is not None and chart.group.has_data:
            print("Used today:", chart.group.delivered_kwh, "kWh")

        # The client refreshes the token when it nears expiry, which rotates it: save the latest one.
        if client.token is not None:
            TOKEN_FILE.write_text(json.dumps(client.token.as_dict()))


if __name__ == "__main__":
    asyncio.run(main())

Available data

Method Returns
async_get_account() The account, its addresses, connections and contracts
async_get_consumer_prices(chart) Electricity (hourly or quarter-hourly) or daily gas prices, with price_at() and cheapest/most expensive helpers
async_get_summary(connection_uuid) Live usage (P1 only), the current tariff group and a price forecast of about two days
async_get_electricity_chart(connection_uuid, day, interval) Electricity used and returned per hour, day or month
async_get_gas_chart(connection_uuid, day, interval) Gas used per hour, day or month
async_get_electricity_delivered(connection_uuid) / async_get_gas(connection_uuid) P1 totals and live readings (meters), or None without a P1 meter
async_get_pv_installation(connection_uuid) Every solar inverter on the connection and today's yield, or None without solar panels
async_get_battery(connection_uuid, contract_uuid) Home battery state, results and modes
async_get_battery_chart(contract_uuid, day, interval) Home battery results per day or month
async_get_battery_control_mode(contract_uuid) / async_get_battery_home_optimization(contract_uuid) The battery's control mode, and the charge and discharge power for home optimization with the range each accepts
async_get_charge_point(connection_uuid, contract_uuid) Charge point state, schedules and vehicles
async_set_locale(locale) Sets the language of the API's texts, e.g. nl-NL

The contract UUIDs come from the account, e.g. connection.contracts_of_type(ContractType.HOME_BATTERY) (constants such as ContractType, ChartInterval and BatteryMode live in pyzonneplan.const). For a contract the account doesn't have, the battery and charge point methods raise ZonneplanNotFoundError. Fields keep the API's raw units (1e-7 EUR, Wh, dm³, permille); properties such as delivered_kwh, electricity_price_euro and state_of_charge_percent convert them.

Usage without a P1 meter arrives a day or more late from the grid operator: check has_data before trusting zeros, and expect the last hours of a day to be revised when the next day arrives.

The PV, battery and charge point models follow the responses other projects have seen, but haven't been tested against live hardware yet. Reports and captured (anonymised) responses are welcome.

Actions

Method Does
async_enable_battery_self_consumption(connection_uuid, contract_uuid) / async_disable_... Turns self consumption mode on or off
async_enable_battery_home_optimization(connection_uuid, contract_uuid, *, max_charge_power_w, max_discharge_power_w) / async_disable_... Turns home optimization mode on (with new power limits, or the current ones) or off
async_set_battery_backup_reserve(connection_uuid, contract_uuid, reserved_wh) Reserves energy for backup power
async_set_battery_control_mode(connection_uuid, contract_uuid, mode) Switches to a BatteryMode, turning the other modes off the way the app does
async_start_charge_point_boost(connection_uuid, contract_uuid) / async_stop_charge_point(...) Starts charging now at full power, or stops charging
async_resume_charge_point_auto_charging(connection_uuid, contract_uuid) Lets the charge point charge on cheap prices again after a manual stop
async_start_charge_point_dynamic_session(connection_uuid, contract_uuid, end, *, kilometers or percentage, vehicle_uuid) Charges an amount by end at the cheapest prices
async_reset_charge_point_schedule(connection_uuid, contract_uuid) Clears the planned charging

A battery with both modes off trades on the dynamic prices (BatteryMode.DYNAMIC_CHARGING). The API doesn't switch the other mode off for you; async_set_battery_control_mode() does, like the Zonneplan app. The battery and charge point confirm a change asynchronously; until then their state reports processing. Actions are sent once and never retried.

Errors

Every error derives from ZonneplanError:

  • ZonneplanAuthenticationError: the token is invalid or expired (log in again); ZonneplanInvalidOtpError when the OTP is rejected.
  • ZonneplanRateLimitError: HTTP 429; retry_after holds the seconds to wait. It isn't retried.
  • ZonneplanRequestError: the API rejected the request (HTTP 4xx), e.g. an unknown chart interval; ZonneplanNotFoundError for a 404, e.g. a device the account doesn't have. Not retried.
  • ZonneplanResponseError: the response isn't JSON or doesn't have the expected shape.
  • ZonneplanConnectionError / ZonneplanTimeoutError: a network error, a timeout or an HTTP 5xx. GET requests are retried with backoff first (max_retries, default 3); actions are never retried.

More examples can be found in the examples directory.

Documentation

Project documentation and API reference: https://erwindouna.github.io/pyzonneplan/

Contributing

Contributions are welcome. Please open an issue or pull request.

For local development:

uv sync --all-groups && uv run pre-commit install

Run checks:

uv run pre-commit run --all-files

Run tests:

uv run pytest

License

MIT License

Copyright (c) 2026 Erwin Douna

Metadata

Release files for pyzonneplan 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyzonneplan 0.2.0
File Size Uploaded
pyzonneplan-0.2.0.tar.gz 27.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyzonneplan 0.2.0
File Interpreter ABI Platform
pyzonneplan-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 57.3 kB

Release files / pyzonneplan-0.2.0.tar.gz

Download URL pyzonneplan-0.2.0.tar.gz
Size 27.2 kB
Tags Source
SHA-256 checksum
How to use checksums
fe3960724798d8470fa2827d98dcc57b0cf4d5cddb2c055506b867ebce070b9f
BLAKE2b-256 checksum
How to use checksums
c623737bc3bd06c590ba4eef9858bd43187ec18eba99d9ad121bd50cfaa66185
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / pyzonneplan-0.2.0-py3-none-any.whl

Download URL pyzonneplan-0.2.0-py3-none-any.whl
Size 30.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
197971314bedd8cbad1e27ef7d012a3e953728480142b16f6deef0dd7a8b7d6d
BLAKE2b-256 checksum
How to use checksums
1f1ab6c4e99b4265a0e9e833e9508bee159ca2abf9fd04c1760a81009dc34a96
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page