Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions PyViCare/PyViCare.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,39 @@
from PyViCare.PyViCareAbstractOAuthManager import AbstractViCareOAuthManager
from PyViCare.PyViCareBrowserOAuthManager import ViCareBrowserOAuthManager
from PyViCare.PyViCareCachedService import ViCareCachedService
from PyViCare.PyViCareCachedServiceViaGateway import ViCareCachedServiceViaGateway
from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig
from PyViCare.PyViCareOAuthManager import ViCareOAuthManager
from PyViCare.PyViCareService import ViCareDeviceAccessor, ViCareService
from PyViCare.PyViCareServiceViaGateway import ViCareServiceViaGateway
from PyViCare.PyViCareUtils import PyViCareInvalidDataError

logger = logging.getLogger('ViCare')
logger.addHandler(logging.NullHandler())

def __buildService(oauth_manager, cacheDuration, viaGateway: bool, accessor, roles):
if cacheDuration > 0:
if viaGateway:
return ViCareCachedServiceViaGateway(oauth_manager, accessor, roles, cacheDuration)
return ViCareCachedService(oauth_manager, accessor, roles, cacheDuration)

if viaGateway:
return ViCareServiceViaGateway(oauth_manager, accessor, roles)
return ViCareService(oauth_manager, accessor, roles)

class PyViCare:
viaGateway = False

""""Viessmann ViCare API Python tools"""
def __init__(self) -> None:
self.cacheDuration = 60

def setCacheDuration(self, cache_duration):
self.cacheDuration = int(cache_duration)

def loadViaGateway(self, via_gateway: bool = True) -> None:
self.viaGateway = via_gateway

def initWithCredentials(self, username: str, password: str, client_id: str, token_file: str):
self.initWithExternalOAuth(ViCareOAuthManager(
username, password, client_id, token_file))
Expand All @@ -32,32 +48,29 @@ def initWithExternalOAuth(self, oauth_manager: AbstractViCareOAuthManager) -> No
def initWithBrowserOAuth(self, client_id: str, token_file: str) -> None:
self.initWithExternalOAuth(ViCareBrowserOAuthManager(client_id, token_file))

def __buildService(self, accessor, roles):
if self.cacheDuration > 0:
return ViCareCachedService(self.oauth_manager, accessor, roles, self.cacheDuration)
return ViCareService(self.oauth_manager, accessor, roles)

def __loadInstallations(self):
installations = self.oauth_manager.get(
"/equipment/installations?includeGateways=true")
if "data" not in installations:
logger.error("Missing 'data' property when fetching installations")
raise PyViCareInvalidDataError(installations)

data = installations['data']
self.installations = Wrap(data)
self.installations = Wrap(installations["data"])
self.devices = list(self.__extract_devices())

def __extract_devices(self):
for installation in self.installations:
for gateway in installation.gateways:
if self.viaGateway:
service = __buildService

for device in gateway.devices:
if device.deviceType not in ["heating", "zigbee", "vitoconnect", "electricityStorage", "tcu", "ventilation"]:
continue # we are only interested in heating, photovoltaic, electricityStorage, and ventilation devices

accessor = ViCareDeviceAccessor(
installation.id, gateway.serial, device.id)
service = self.__buildService(accessor, device.roles)
service = __buildService(self.oauth_manager, self.cacheDuration, self.viaGateway, accessor, device.roles)

logger.info("Device found: %s", device.modelId)

Expand Down
74 changes: 74 additions & 0 deletions PyViCare/PyViCareCachedServiceViaGateway.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import logging
import threading
from typing import Any, List

from PyViCare.PyViCareAbstractOAuthManager import AbstractViCareOAuthManager
from PyViCare.PyViCareService import (ViCareDeviceAccessor, ViCareService, ViCareServiceViaGateway, readFeature)

Check failure on line 6 in PyViCare/PyViCareCachedServiceViaGateway.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (F401)

PyViCare/PyViCareCachedServiceViaGateway.py:6:101: F401 `PyViCare.PyViCareService.readFeature` imported but unused

Check failure on line 6 in PyViCare/PyViCareCachedServiceViaGateway.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (F401)

PyViCare/PyViCareCachedServiceViaGateway.py:6:61: F401 `PyViCare.PyViCareService.ViCareService` imported but unused
from PyViCare.PyViCareUtils import PyViCareInvalidDataError, PyViCareNotSupportedFeatureError, ViCareTimer

logger = logging.getLogger('ViCare')
logger.addHandler(logging.NullHandler())

class ViCareCache:
def __init__(self, cache_duration: int) -> None:
self.cache_duration = cache_duration
self.data = None
self.cache_time = None
self.lock = threading.Lock()

def is_valid(self) -> bool:
return self.data is not None and self.cache_time is not None and not (ViCareTimer().now() - self.cache_time).seconds > self.cache_duration

def clear(self):
with self.lock:
self.data = None
self.cache_time = None

def set_cache(self, data: Any):
with self.lock:
self.data = data


class ViCareCachedServiceViaGateway(ViCareServiceViaGateway):
def __init__(self, oauth_manager: AbstractViCareOAuthManager, accessor: ViCareDeviceAccessor, roles: List[str], cache: ViCareCache) -> None:
ViCareServiceViaGateway.__init__(self, oauth_manager, accessor, roles)
self.__cache = cache

def getProperty(self, property_name: str) -> Any:
data = self.__get_data()
entities = data["data"]

# def readFeature(entities, property_name):
feature = next(
(f for f in entities if f["feature"] == property_name), None)

if feature is None:
raise PyViCareNotSupportedFeatureError(property_name)

return feature

# return readFeature(entities, property_name)

def setProperty(self, property_name, action, data):
response = super().setProperty(property_name, action, data)
self.__cache.clear()
return response

def fetch_all_features(self) -> Any:
url = f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/features?includeDevicesFeatures=true'
return self.oauth_manager.get(url)

def __get_data(self):
with self.__cache.lock:
if not self.__cache.is_valid():
# we always set the cache time before we fetch the data
# to avoid consuming all the api calls if the api is down
# see https://github.com/home-assistant/core/issues/67052
# we simply return the old cache in this case
self.__cache.cache_time = ViCareTimer().now()
data = self.fetch_all_features()
if "data" not in data:
logger.error("Missing 'data' property when fetching data.")
raise PyViCareInvalidDataError(data)
self.__cache.set(data)
return self.__cache.data
12 changes: 12 additions & 0 deletions PyViCare/PyViCareService.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def buildGetPropertyUrl(self, property_name):
return f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/features/{property_name}'
return f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/devices/{self.accessor.device_id}/features/{property_name}'

# def buildSetPropertyUrl(self, property_name, action):
# return f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/devices/{self.accessor.device_id}/features/{property_name}/commands/{action}'

def hasRoles(self, requested_roles) -> bool:
return hasRoles(requested_roles, self.roles)

Expand All @@ -67,3 +70,12 @@ def reboot_gateway(self) -> Any:
url = f'/equipment/installations/{self.accessor.id}/gateways/{self.accessor.serial}/reboot'
data = "{}"
return self.oauth_manager.post(url, data)


class ViCareGatewayService(ViCareService):

def getAllFeaturesURL(self) -> str:
return f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/features?includeDevicesFeatures=true'

def fetch_all_features(self) -> Any:
return self.oauth_manager.get(self.getAllFeaturesURL())
38 changes: 38 additions & 0 deletions tests/ViCareGatewayServiceMock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from PyViCare.PyViCareService import (ViCareDeviceAccessor, readFeature)
from tests.helper import readJson


def MockCircuitsData(circuits):
return {
"properties": {
"enabled": {
"value": circuits
}
},
"feature": "heating.circuits",
}


class ViCareGatewayServiceMock:

def __init__(self, filename, rawInput=None):
if rawInput is None:
testData = readJson(filename)
self.testData = testData
else:
self.testData = rawInput

self.accessor = ViCareDeviceAccessor('[id]', '[serial]', '[deviceid]')
self.setPropertyData = []

def getProperty(self, property_name):
entities = self.testData["data"]
return readFeature(entities, property_name)

# def setProperty(self, property_name, action, data):
# self.setPropertyData.append({
# "url": buildSetPropertyUrl(self.accessor, property_name, action),
# "property_name": property_name,
# "action": action,
# "data": data
# })
Loading
Loading