PyViCare

repository·master·Indexed 41 days ago

https://github.com/openviess/pyvicare

A Python library for communicating with the Viessmann ViCare API via the official Viessmann Developer Portal. It supports OAuth2 authentication (standard, browser-based, and custom managers), device discovery for heating, zigbee, and other device types, and provides caching mechanisms including per-gateway bulk fetching to optimize API calls.

Tokens
26.3K
Snippets
94
Records
130
Agent score
48%

What's inside PyViCare

  1. How to handle API rate limits

    master

    The Viessmann API enforces rate limits. When a limit is exceeded, the library raises a PyViCareRateLimitError.

    You can inspect the error object to find the limitResetDate, which tells you when you can resume making requests.

    try:
        # Perform API call
        device.getSomething()
    except PyViCareRateLimitError as e:
        print(f"Rate limit hit. Resets at: {e.limitResetDate}")
  2. How to handle unsupported device features

    master

    Depending on your specific hardware, some API methods may not be supported. If you call a method that your device does not support, the library raises a PyViCareNotSupportedFeatureError.

    To handle this gracefully without crashing your application, it is recommended to use Python's contextlib.suppress.

    from contextlib import suppress
    from PyViCare.exceptions import PyViCareNotSupportedFeatureError
    
    with suppress(PyViCareNotSupportedFeatureError):
        # Call a feature that might not be supported by this specific device
        device.some_unsupported_method()
  3. Accessing the Viessmann API via Postman

    master

    You can manually investigate the Viessmann API using Postman by following these steps:

    1. Configure OAuth 2.0

    In the Authorization tab, set the type to OAuth 2.0 with these settings:

    • Token Name: PyViCare
    • Grant Type: Authorization Code (With PKCE)
    • Callback URL: vicare://oauth-callback/everest
    • Authorize using browser: Disabled
    • Auth URL: https://iam.viessmann-climatesolutions.com/idp/v3/authorize
    • Access Token URL: https://iam.viessmann-climatesolutions.com/idp/v3/token
    • Client ID: Your personal Client ID
    • Client Secret: (Leave blank)
    • Code Challenge Method: SHA-256
    • Code Verifier: (Leave blank)
    • Scope: IoT User
    • Client Authentication: Send client credentials in body

    Note: A login popup will appear; use your ViCare credentials.

    2. Retrieve IDs

    Use this URL to find your installationId, gatewaySerial, and deviceId: https://api.viessmann-climatesolutions.com/iot/v1/equipment/installations?includeGateways=true

    • installationId is found at data[0].id
    • gatewaySerial is found at data[0].gateways[0].serial
    • deviceId is found at data[0].gateways[0].devices[0].id

    3. Investigate Features

    Replace the placeholders in the following URL with the IDs retrieved above: https://api.viessmann-climatesolutions.com/iot/v1/features/installations/{installationId}/gateways/{gatewaySerial}/devices/{deviceId}/features

  4. Prerequisites for PyViCare

    master

    Before using PyViCare, you must register and create a personal API client via the Viessmann Developer Portal using your existing ViCare app credentials.

    Steps to create a client:

    1. Login to the portal with your ViCare username/password.
    2. In the developer dashboard, click add in the clients section.
    3. Configure the new client with these exact settings:
      • Name: PyViCare
      • Google reCAPTCHA: Disabled
      • Redirect URIs: vicare://oauth-callback/everest
    4. Copy the generated Client ID. You will need to pass this to the initWithCredentials method in your code.
  5. Basic Usage of PyViCare

    master

    To interact with your Viessmann devices, instantiate the PyViCare class, authenticate using initWithCredentials, and then access devices from the devices list.

    Common patterns include:

    • Using asAutoDetectDevice() to get a generic device interface.
    • Accessing specific heating types using methods like asGazBoiler, asHeatPump, asFuelCell, asPelletsBoiler, asOilBoiler, or asHybridDevice.
    • Iterating over circuits to manage heating loops.
    • Accessing burners or compressors for specific hardware control.
    import sys
    import logging
    from PyViCare.PyViCare import PyViCare
    
    client_id = "INSERT CLIENT ID"
    email = "email@domain"
    password = "password"
    
    vicare = PyViCare()
    vicare.initWithCredentials(email, password, client_id, "token.save")
    device = vicare.devices[1]
    print(device.getModel())
    print("Online" if device.isOnline() else "Offline")
    
    t = device.asAutoDetectDevice()
    print(t.getDomesticHotWaterConfiguredTemperature())
    print(t.getDomesticHotWaterStorageTemperature())
    print(t.getOutsideTemperature())
    print(t.getRoomTemperature())
    print(t.getBoilerTemperature())
    print(t.setDomesticHotWaterTemperature(59))
    
    circuit = t.circuits[0] # select heating circuit
    
    print(circuit.getSupplyTemperature())
    print(circuit.getHeatingCurveShift())
    print(circuit.getHeatingCurveSlope())
    
    print(circuit.getActiveProgram())
    print(circuit.getPrograms())
    
    print(circuit.getCurrentDesiredTemperature())
    print(circuit.getDesiredTemperatureForProgram("comfort"))
    print(circuit.getActiveMode())
    
    print(circuit.getDesiredTemperatureForProgram("comfort"))
    print(circuit.setProgramTemperature("comfort", 21))
    print(circuit.activateProgram("comfort"))
    print(circuit.deactivateComfort())
    
    # For gas/heat pump components
    # Note: In version 2.8.x+, these are accessed via .burners and .compressors
    # rather than through .circuits
    
    # Example for burner (if applicable)
    try:
        burner = t.burners[0]
        print(burner.getActive())
    except (AttributeError, IndexError):
        pass
    
    # Example for compressor (if applicable)
    try:
        compressor = t.compressors[0]
        print(compressor.getActive())
    except (AttributeError, IndexError):
        pass
  6. Generating secure device data dumps for testing

    master

    To help improve PyViCare, you can provide device response data for new test cases. Use the dump_secure() method to generate a JSON file that automatically removes sensitive information like installation IDs and serial numbers.

    You can provide credentials via environment variables: PYVICARE_CLIENT_ID, PYVICARE_EMAIL, and PYVICARE_PASSWORD.

    Steps to create a test dump:

    1. Run the following script to generate dump.json.
    2. Sort the JSON using jq to ensure compatibility with future updates:
      jq ".data|=sort_by(.feature)" --sort-keys testData.json > testDataSorted.json
    3. Commit the resulting file to tests/response using the naming convention <family><model>.
    import os
    from PyViCare.PyViCare import PyViCare
    
    client_id = os.getenv("PYVICARE_CLIENT_ID", "INSERT_CLIENT_ID")
    email = os.getenv("PYVICARE_EMAIL", "email@domain")
    password = os.getenv("PYVICARE_PASSWORD", "password")
    
    vicare = PyViCare()
    vicare.initWithCredentials(email, password, client_id, "token.save")
    
    with open(f"dump.json", mode='w') as output:
       output.write(vicare.devices[0].dump_secure())
  7. Implement a custom cached service with ViCareCachedServiceBase

    master

    To create a new service that uses time-based caching, subclass ViCareCachedServiceBase. You must implement two protected methods:

    1. _fetch_uncached(accessor: ViCareDeviceAccessor) -> Any: Performs the actual HTTP fetch to retrieve the raw data.
    2. _extract_entities(data: dict, accessor: ViCareDeviceAccessor) -> list[dict[str, Any]]: Parses the raw data to extract the specific entities relevant to this service.

    The base class handles thread-safe caching, cache invalidation based on a duration, and error handling (such as returning stale cache during API downtime).

    from PyViCare.PyViCareCachedServiceBase import ViCareCachedServiceBase
    
    class MyCustomService(ViCareCachedServiceBase):
        def _fetch_uncached(self, accessor):
            # Implement actual API call here
            return {"data": {"some_key": "some_value"}}
    
        def _extract_entities(self, data, accessor):
            # Extract entities from the data dictionary
            return [data["data"]]
  8. Access discovered devices

    master

    Once initialized, the PyViCare instance contains a list of devices that match the supported device types.

    Supported device types include:

    • heating
    • zigbee
    • vitoconnect
    • electricityStorage
    • tcu
    • ventilation
    • roomControl

    Each device in the client.devices list is represented as a PyViCareDeviceConfig object, which provides the necessary accessor and service to interact with that specific device.

    from PyViCare.PyViCare import PyViCare
    
    client = PyViCare()
    client.initWithCredentials("user", "pass", "id", "token.json")
    
    for device_config in client.devices:
        print(f"Model: {device_config.model_id}, Type: {device_config.device_type}")
        # Use device_config.service to interact with the device
  9. Manage burners with GazBoiler

    master

    A GazBoiler can have multiple burners. You can access them using the burners property, which returns a list of GazBurner objects.

    GazBurner API

    Each GazBurner object allows you to check the status and statistics of a specific burner component:

    • getActive(): Returns whether the burner is currently active.
    • getHours(): Returns the total operating hours.
    • getStarts(): Returns the number of times the burner has started.
    • getModulation(): Returns the current modulation value.
    burners = boiler.burners
    for b in burners:
        print(f"Burner: {b.burner}, Active: {b.getActive()}")
        print(f"Hours: {b.getHours()}")
  10. How PyViCareDeviceConfig manages device specialization

    master

    The PyViCareDeviceConfig class serves as a bridge between raw device data (from the ViCareService) and specialized functional classes.

    Instead of the user manually determining if a device is a boiler or a heat pump, they interact with a PyViCareDeviceConfig object which contains the logic to:

    1. Identify: Use asAutoDetectDevice() to match model strings (like Vitodens or Vitocal) to specific classes.
    2. Specialize: Use as<Type>() methods to instantiate the correct class (e.g., GazBoiler) using the existing accessor and service.
    3. Upgrade: Detect Hybrid capabilities by inspecting feature sets (checking for both heating.burners and heating.compressors) even if the model name suggests a single type.

    This pattern ensures that developers can work with high-level device abstractions while the configuration object handles the complexity of Viessmann's diverse hardware ecosystem.

  11. Access gas and power consumption via GazBoiler

    master

    The GazBoiler class provides extensive methods for monitoring resource usage.

    Gas Consumption

    Methods are split between Heating and Domestic Hot Water (DHW), and offer different time granularities:

    • Granular methods: getGasConsumptionHeatingDays(), getGasConsumptionHeatingWeeks(), getGasConsumptionHeatingMonths(), getGasConsumptionHeatingYears().
    • Current period methods: getGasConsumptionHeatingToday(), getGasConsumptionHeatingThisWeek(), getGasConsumptionHeatingThisMonth(), getGasConsumptionHeatingThisYear().
    • Summary API (Vitodens-100W): getGasSummaryConsumptionHeatingCurrentDay(), getGasSummaryConsumptionHeatingLastMonth(), etc.

    Power Consumption

    Similar to gas, power consumption can be queried for Total, Heating, or Domestic Hot Water (DHW) across various timeframes (Today, This Week, This Month, This Year, etc.).

  12. How heating circuits and devices work together

    master

    In PyViCare, the relationship between a device and its circuits follows a hierarchical pattern:

    1. HeatingDevice: The top-level abstraction representing the entire heating system. It manages global settings like DHW, solar, and general sensors.
    2. HeatingCircuit: A sub-component of the HeatingDevice. You access circuits through the device.circuits property, which returns a list of HeatingCircuit objects.

    To control a specific area of a house, you first obtain the HeatingDevice instance, then iterate through or select a specific HeatingCircuit from its .circuits list. Each HeatingCircuit object maintains a reference back to its parent HeatingDevice to perform API calls.

    # Accessing a circuit from a device
    for circuit in device.circuits:
        print(f"Circuit Name: {circuit.getName()}")
        print(f"Current Temp: {circuit.getTargetTemperature()}")
        circuit.setNormalTemperature(21)