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
10 changes: 5 additions & 5 deletions PyViCare/PyViCare.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ 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):
def __buildService(self, roles):
if self.cacheDuration > 0:
return ViCareCachedService(self.oauth_manager, accessor, roles, self.cacheDuration)
return ViCareService(self.oauth_manager, accessor, roles)
return ViCareCachedService(self.oauth_manager, roles, self.cacheDuration)
return ViCareService(self.oauth_manager, roles)

def __loadInstallations(self):
installations = self.oauth_manager.get(
Expand All @@ -57,11 +57,11 @@ def __extract_devices(self):

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

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

yield PyViCareDeviceConfig(service, device.id, device.modelId, device.status)
yield PyViCareDeviceConfig(accessor, service, device.modelId, device.status)


class DictWrap(object):
Expand Down
18 changes: 9 additions & 9 deletions PyViCare/PyViCareCachedService.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,24 @@

class ViCareCachedService(ViCareService):

def __init__(self, oauth_manager: AbstractViCareOAuthManager, accessor: ViCareDeviceAccessor, roles: List[str], cacheDuration: int) -> None:
ViCareService.__init__(self, oauth_manager, accessor, roles)
def __init__(self, oauth_manager: AbstractViCareOAuthManager, roles: List[str], cacheDuration: int) -> None:
ViCareService.__init__(self, oauth_manager, roles)
self.__cacheDuration = cacheDuration
self.__cache = None
self.__cacheTime = None
self.__lock = threading.Lock()

def getProperty(self, property_name: str) -> Any:
data = self.__get_or_update_cache()
def getProperty(self, accessor: ViCareDeviceAccessor, property_name: str) -> Any:
data = self.__get_or_update_cache(accessor)
entities = data["data"]
return readFeature(entities, property_name)
return readFeature(accessor, entities, property_name)

def setProperty(self, property_name, action, data):
response = super().setProperty(property_name, action, data)
def setProperty(self, accessor: ViCareDeviceAccessor, property_name, action, data):
response = super().setProperty(accessor, property_name, action, data)
self.clear_cache()
return response

def __get_or_update_cache(self):
def __get_or_update_cache(self, accessor: ViCareDeviceAccessor):
with self.__lock:
if self.is_cache_invalid():
# we always sett the cache time before we fetch the data
Expand All @@ -39,7 +39,7 @@ def __get_or_update_cache(self):
# we simply return the old cache in this case
self.__cacheTime = ViCareTimer().now()

data = self.fetch_all_features()
data = self.fetch_all_features(accessor)
if "data" not in data:
logger.error("Missing 'data' property when fetching data.")
raise PyViCareInvalidDataError(data)
Expand Down
9 changes: 5 additions & 4 deletions PyViCare/PyViCareDevice.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any

from PyViCare.PyViCareService import ViCareService
from PyViCare.PyViCareService import ViCareService, ViCareDeviceAccessor
from PyViCare.PyViCareUtils import PyViCareNotSupportedFeatureError, handleAPICommandErrors, handleNotSupported


Expand All @@ -11,14 +11,15 @@ class Device:
Note that currently, a new token is generated for each run.
"""

def __init__(self, service: ViCareService) -> None:
def __init__(self, accessor: ViCareDeviceAccessor, service: ViCareService) -> None:
self.accessor = accessor
self.service = service

def getProperty(self, property_name: str) -> Any:
return self.service.getProperty(property_name)
return self.service.getProperty(self.accessor, property_name)

def setProperty(self, property_name: str, action: str, data: Any) -> Any:
return self.service.setProperty(property_name, action, data)
return self.service.setProperty(self.accessor, property_name, action, data)

@handleNotSupported
def getSerial(self):
Expand Down
38 changes: 20 additions & 18 deletions PyViCare/PyViCareDeviceConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from PyViCare.PyViCareRadiatorActuator import RadiatorActuator
from PyViCare.PyViCareRoomSensor import RoomSensor
from PyViCare.PyViCareRepeater import Repeater
from PyViCare.PyViCareService import ViCareDeviceAccessor, ViCareService
from PyViCare.PyViCareElectricalEnergySystem import ElectricalEnergySystem
from PyViCare.PyViCareGateway import Gateway
from PyViCare.PyViCareVentilationDevice import VentilationDevice
Expand All @@ -22,59 +23,60 @@


class PyViCareDeviceConfig:
def __init__(self, service, device_id, device_model, status):
def __init__(self, accessor: ViCareDeviceAccessor, service: ViCareService, device_model, status):
self.accessor = accessor
self.service = service
self.device_id = device_id
self.device_id = accessor.device_id
self.device_model = device_model
self.status = status

def asGeneric(self):
return HeatingDevice(self.service)
return HeatingDevice(self.accessor, self.service)

def asGazBoiler(self):
return GazBoiler(self.service)
return GazBoiler(self.accessor, self.service)

def asFuelCell(self):
return FuelCell(self.service)
return FuelCell(self.accessor, self.service)

def asHeatPump(self):
return HeatPump(self.service)
return HeatPump(self.accessor, self.service)

def asOilBoiler(self):
return OilBoiler(self.service)
return OilBoiler(self.accessor, self.service)

def asPelletsBoiler(self):
return PelletsBoiler(self.service)
return PelletsBoiler(self.accessor, self.service)

def asHybridDevice(self):
return Hybrid(self.service)
return Hybrid(self.accessor, self.service)

def asRadiatorActuator(self):
return RadiatorActuator(self.service)
return RadiatorActuator(self.accessor, self.service)

def asFloorHeating(self):
return FloorHeating(self.service)
return FloorHeating(self.accessor, self.service)

def asFloorHeatingChannel(self):
return FloorHeatingChannel(self.service)
return FloorHeatingChannel(self.accessor, self.service)

def asRoomSensor(self):
return RoomSensor(self.service)
return RoomSensor(self.accessor, self.service)

def asRepeater(self):
return Repeater(self.service)
return Repeater(self.accessor, self.service)

def asElectricalEnergySystem(self):
return ElectricalEnergySystem(self.service)
return ElectricalEnergySystem(self.accessor, self.service)

def asGateway(self):
return Gateway(self.service)
return Gateway(self.accessor, self.service)

def asVentilation(self):
return VentilationDevice(self.service)
return VentilationDevice(self.accessor, self.service)

def getConfig(self):
return self.service.accessor
return self.accessor

def getId(self):
return self.device_id
Expand Down
2 changes: 1 addition & 1 deletion PyViCare/PyViCareFuelCell.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def getBurner(self, burner):

@handleNotSupported
def getAvailableBurners(self):
return get_available_burners(self.service)
return get_available_burners(self.accessor, self.service)

@handleNotSupported
def getReturnTemperature(self):
Expand Down
2 changes: 1 addition & 1 deletion PyViCare/PyViCareGazBoiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def getBurner(self, burner):

@handleNotSupported
def getAvailableBurners(self):
return get_available_burners(self.service)
return get_available_burners(self.accessor, self.service)

@handleNotSupported
def getGasConsumptionHeatingUnit(self):
Expand Down
5 changes: 3 additions & 2 deletions PyViCare/PyViCareHeatingDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any, List, Optional

from PyViCare.PyViCareDevice import Device
from PyViCare.PyViCareService import ViCareService, ViCareDeviceAccessor
from PyViCare.PyViCareHeatCurveCalculation import (
heat_curve_formular_variant1, heat_curve_formular_variant2)
from PyViCare.PyViCareUtils import (VICARE_DAYS,
Expand All @@ -17,13 +18,13 @@ def all_set(_list: List[Any]) -> bool:
return all(v is not None for v in _list)


def get_available_burners(service):
def get_available_burners(accessor: ViCareDeviceAccessor, service: ViCareService):
# workaround starting from 25.01.2022
# see: https://github.com/somm15/PyViCare/issues/243
available_burners = []
for burner in ['0', '1', '2', '3', '4', '5']:
with suppress(PyViCareNotSupportedFeatureError):
if service.getProperty(f"heating.burners.{burner}") is not None:
if service.getProperty(accessor, f"heating.burners.{burner}") is not None:
available_burners.append(burner)

return available_burners
Expand Down
2 changes: 1 addition & 1 deletion PyViCare/PyViCareOilBoiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def getBurner(self, burner):

@handleNotSupported
def getAvailableBurners(self):
return get_available_burners(self.service)
return get_available_burners(self.accessor, self.service)

@handleNotSupported
def getBoilerTemperature(self):
Expand Down
2 changes: 1 addition & 1 deletion PyViCare/PyViCarePelletsBoiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def getBurner(self, burner) -> PelletsBurner:

@handleNotSupported
def getAvailableBurners(self):
return get_available_burners(self.service)
return get_available_burners(self.accessor, self.service)

@handleNotSupported
def getBoilerTemperature(self):
Expand Down
44 changes: 21 additions & 23 deletions PyViCare/PyViCareService.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@
logger = logging.getLogger('ViCare')
logger.addHandler(logging.NullHandler())

def readFeature(entities, property_name):
class ViCareDeviceAccessor:
def __init__(self, _id: int, serial: str, device_id: str) -> None:
self.id = _id
self.serial = serial
self.device_id = device_id

def readFeature(accessor: ViCareDeviceAccessor, entities, property_name):
feature = next(
(f for f in entities if f["feature"] == property_name), None)
(f for f in entities if (not f.get("deviceId") or f["deviceId"] == accessor.device_id) and f["feature"] == property_name), None)

if feature is None:
raise PyViCareNotSupportedFeatureError(property_name)
Expand All @@ -23,47 +29,39 @@ def hasRoles(requested_roles: List[str], existing_roles: List[str]) -> bool:
def buildSetPropertyUrl(accessor, property_name, action):
return f'/features/installations/{accessor.id}/gateways/{accessor.serial}/devices/{accessor.device_id}/features/{property_name}/commands/{action}'

class ViCareDeviceAccessor:
def __init__(self, _id: int, serial: str, device_id: str) -> None:
self.id = _id
self.serial = serial
self.device_id = device_id

class ViCareService:
def __init__(self, oauth_manager: AbstractViCareOAuthManager, accessor: ViCareDeviceAccessor, roles: List[str]) -> None:
def __init__(self, oauth_manager: AbstractViCareOAuthManager, roles: List[str]) -> None:
self.oauth_manager = oauth_manager
self.accessor = accessor
self.roles = roles

def getProperty(self, property_name: str) -> Any:
url = self.buildGetPropertyUrl(property_name)
def getProperty(self, accessor: ViCareDeviceAccessor, property_name: str) -> Any:
url = self.buildGetPropertyUrl(accessor, property_name)
return self.oauth_manager.get(url)

def buildGetPropertyUrl(self, property_name):
def buildGetPropertyUrl(self, accessor: ViCareDeviceAccessor, property_name):
if self._isGateway():
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}'
return f'/features/installations/{accessor.id}/gateways/{accessor.serial}/features/{property_name}'
return f'/features/installations/{accessor.id}/gateways/{accessor.serial}/devices/{accessor.device_id}/features/{property_name}'

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

def _isGateway(self) -> bool:
return self.hasRoles(["type:gateway;VitoconnectOpto1"]) or self.hasRoles(["type:gateway;VitoconnectOpto2/OT2"]) or self.hasRoles(["type:gateway;TCU100"]) or self.hasRoles(["type:gateway;TCU200"]) or self.hasRoles(["type:gateway;TCU300"])

def setProperty(self, property_name: str, action: str, data: Any) -> Any:
url = buildSetPropertyUrl(
self.accessor, property_name, action)
def setProperty(self, accessor: ViCareDeviceAccessor, property_name: str, action: str, data: Any) -> Any:
url = buildSetPropertyUrl(accessor, property_name, action)

post_data = data if isinstance(data, str) else json.dumps(data)
return self.oauth_manager.post(url, post_data)

def fetch_all_features(self) -> Any:
url = f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/devices/{self.accessor.device_id}/features/'
def fetch_all_features(self, accessor: ViCareDeviceAccessor) -> Any:
url = f'/features/installations/{accessor.id}/gateways/{accessor.serial}/devices/{accessor.device_id}/features/'
if self._isGateway():
url = f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/features/'
url = f'/features/installations/{accessor.id}/gateways/{accessor.serial}/features/'
return self.oauth_manager.get(url)

def reboot_gateway(self) -> Any:
url = f'/equipment/installations/{self.accessor.id}/gateways/{self.accessor.serial}/reboot'
def reboot_gateway(self, accessor: ViCareDeviceAccessor) -> Any:
url = f'/equipment/installations/{accessor.id}/gateways/{accessor.serial}/reboot'
data = "{}"
return self.oauth_manager.post(url, data)
10 changes: 4 additions & 6 deletions tests/ViCareServiceMock.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,15 @@ def __init__(self, filename, rawInput=None):
else:
self.testData = rawInput

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

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

def setProperty(self, property_name, action, data):
def setProperty(self, accessor: ViCareDeviceAccessor, property_name, action, data):
self.setPropertyData.append({
"url": buildSetPropertyUrl(self.accessor, property_name, action),
"url": buildSetPropertyUrl(accessor, property_name, action),
"property_name": property_name,
"action": action,
"data": data
Expand Down
Loading
Loading