From f69604d268bd1f97b0ead7b925a3b22fc2119719 Mon Sep 17 00:00:00 2001 From: Christopher Fenner <9592452+CFenner@users.noreply.github.com> Date: Wed, 27 Dec 2023 09:50:28 +0100 Subject: [PATCH] feat: add PyViCareGateway class (#334) --- PyViCare/PyViCareDeviceConfig.py | 7 ++- PyViCare/PyViCareGateway.py | 9 ++++ PyViCare/PyViCareService.py | 18 ++++--- tests/response/VitoconnectOpto1.json | 65 ++++++++++++++++++++++++++ tests/test_PyViCareDeviceConfig.py | 11 +++++ tests/test_PyViCareService.py | 16 ++++++- tests/test_TestForMissingProperties.py | 3 ++ tests/test_VitoconnectOpto1.py | 14 ++++++ 8 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 PyViCare/PyViCareGateway.py create mode 100644 tests/response/VitoconnectOpto1.json create mode 100644 tests/test_VitoconnectOpto1.py diff --git a/PyViCare/PyViCareDeviceConfig.py b/PyViCare/PyViCareDeviceConfig.py index 57f546f6..0a776a40 100644 --- a/PyViCare/PyViCareDeviceConfig.py +++ b/PyViCare/PyViCareDeviceConfig.py @@ -12,6 +12,7 @@ from PyViCare.PyViCareRadiatorActuator import RadiatorActuator from PyViCare.PyViCareRoomSensor import RoomSensor from PyViCare.PyViCareElectricalEnergySystem import ElectricalEnergySystem +from PyViCare.PyViCareGateway import Gateway from PyViCare.PyViCareVentilationDevice import VentilationDevice logger = logging.getLogger('ViCare') @@ -54,6 +55,9 @@ def asRoomSensor(self): def asElectricalEnergySystem(self): return ElectricalEnergySystem(self.service) + + def asGateway(self): + return Gateway(self.service) def asVentilation(self): return VentilationDevice(self.service) @@ -84,7 +88,8 @@ def asAutoDetectDevice(self): (self.asElectricalEnergySystem, r"E3_TCU10_x07", ["type:tcu"]), (self.asElectricalEnergySystem, r"E3_EEBus", ["type:eebus"]), (self.asElectricalEnergySystem, r"E3_VitoCharge_03", ["type:energy_storage"]), - (self.asVentilation, r"E3_ViAir", ["type:ventilation"]) + (self.asVentilation, r"E3_ViAir", ["type:ventilation"]), + (self.asGateway, r"Heatbox1", ["type:gateway;VitoconnectOpto1"]) ] for (creator_method, type_name, roles) in device_types: diff --git a/PyViCare/PyViCareGateway.py b/PyViCare/PyViCareGateway.py new file mode 100644 index 00000000..fbdf3174 --- /dev/null +++ b/PyViCare/PyViCareGateway.py @@ -0,0 +1,9 @@ +from PyViCare.PyViCareDevice import Device +from PyViCare.PyViCareUtils import handleNotSupported + + +class Gateway(Device): + + @handleNotSupported + def getWifiSignalStrength(self): + return self.service.getProperty("gateway.wifi")["properties"]["strength"]["value"] diff --git a/PyViCare/PyViCareService.py b/PyViCare/PyViCareService.py index 29e7f640..259deeee 100644 --- a/PyViCare/PyViCareService.py +++ b/PyViCare/PyViCareService.py @@ -27,10 +27,6 @@ def buildSetPropertyUrl(accessor, property_name, action): return f'/features/installations/{accessor.id}/gateways/{accessor.serial}/devices/{accessor.device_id}/features/{property_name}/commands/{action}' -def buildGetPropertyUrl(accessor, property_name): - return f'/features/installations/{accessor.id}/gateways/{accessor.serial}/devices/{accessor.device_id}/features/{property_name}' - - class ViCareDeviceAccessor: def __init__(self, id: int, serial: str, device_id: str) -> None: self.id = id @@ -45,13 +41,21 @@ def __init__(self, oauth_manager: AbstractViCareOAuthManager, accessor: ViCareDe self.roles = roles def getProperty(self, property_name: str) -> Any: - url = buildGetPropertyUrl( - self.accessor, property_name) + url = self.buildGetPropertyUrl(property_name) return self.oauth_manager.get(url) + + def buildGetPropertyUrl(self, 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}' + def hasRoles(self, requested_roles) -> bool: return hasRoles(requested_roles, self.roles) + def _isGateway(self) -> bool: + return self.hasRoles(["type:gateway;VitoconnectOpto1"]) + def setProperty(self, property_name: str, action: str, data: Any) -> Any: url = buildSetPropertyUrl( self.accessor, property_name, action) @@ -61,4 +65,6 @@ def setProperty(self, property_name: str, action: str, data: Any) -> Any: def fetch_all_features(self) -> Any: url = f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/devices/{self.accessor.device_id}/features/' + if self._isGateway(): + url = f'/features/installations/{self.accessor.id}/gateways/{self.accessor.serial}/features/' return self.oauth_manager.get(url) diff --git a/tests/response/VitoconnectOpto1.json b/tests/response/VitoconnectOpto1.json new file mode 100644 index 00000000..db0aa241 --- /dev/null +++ b/tests/response/VitoconnectOpto1.json @@ -0,0 +1,65 @@ +{ + "data": [ + { + "apiVersion": 1, + "commands": {}, + "feature": "gateway.devices", + "gatewayId": "################", + "isEnabled": true, + "isReady": true, + "properties": { + "devices": { + "type": "DeviceList", + "value": [ + { + "fingerprint": "xxx", + "id": "gateway", + "modelId": "Heatbox1", + "modelVersion": "xxx", + "name": "Heatbox 1, Vitoconnect", + "roles": [ + "type:gateway;VitoconnectOpto1", + "type:legacy" + ], + "status": "online", + "type": "vitoconnect" + }, + { + "fingerprint": "xxx", + "id": "0", + "modelId": "VScotHO1_40", + "modelVersion": "xxx", + "name": "VT 200 (HO1A / HO1B)", + "roles": [ + "type:boiler", + "type:legacy", + "type:product;VScotHO1" + ], + "status": "online", + "type": "heating" + } + ] + } + }, + "timestamp": "2023-12-25T04:01:00.448Z", + "uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/features/gateway.devices" + }, + { + "apiVersion": 1, + "commands": {}, + "feature": "gateway.wifi", + "gatewayId": "################", + "isEnabled": true, + "isReady": true, + "properties": { + "strength": { + "type": "number", + "unit": "", + "value": -69 + } + }, + "timestamp": "2023-12-26T20:44:41.417Z", + "uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/features/gateway.wifi" + } + ] +} \ No newline at end of file diff --git a/tests/test_PyViCareDeviceConfig.py b/tests/test_PyViCareDeviceConfig.py index f0953a5b..c95cc3ed 100644 --- a/tests/test_PyViCareDeviceConfig.py +++ b/tests/test_PyViCareDeviceConfig.py @@ -66,3 +66,14 @@ def test_autoDetect_Vitoair_FS_300E_asVentilation(self): c = PyViCareDeviceConfig(self.service, "0", "E3_ViAir_300F", "Online") device_type = c.asAutoDetectDevice() self.assertEqual("VentilationDevice", type(device_type).__name__) + + def test_autoDetect_VitoconnectOpto1_asGateway(self): + c = PyViCareDeviceConfig(self.service, "0", "Heatbox1", "Online") + device_type = c.asAutoDetectDevice() + self.assertEqual("Gateway", type(device_type).__name__) + + def test_autoDetect_RoleGateway_asGateway(self): + self.service.hasRoles = has_roles(["type:gateway;VitoconnectOpto1"]) + c = PyViCareDeviceConfig(self.service, "0", "Unknown", "Online") + device_type = c.asAutoDetectDevice() + self.assertEqual("Gateway", type(device_type).__name__) diff --git a/tests/test_PyViCareService.py b/tests/test_PyViCareService.py index 70adc98c..86885bc9 100644 --- a/tests/test_PyViCareService.py +++ b/tests/test_PyViCareService.py @@ -8,8 +8,8 @@ class PyViCareServiceTest(unittest.TestCase): def setUp(self): self.oauth_mock = Mock() - accessor = ViCareDeviceAccessor("[id]", "[serial]", "[device]") - self.service = ViCareService(self.oauth_mock, accessor, []) + self.accessor = ViCareDeviceAccessor("[id]", "[serial]", "[device]") + self.service = ViCareService(self.oauth_mock, self.accessor, []) def test_getProperty(self): self.service.getProperty("someprop") @@ -25,3 +25,15 @@ def test_setProperty_string(self): self.service.setProperty("someprop", "doaction", '{}') self.oauth_mock.post.assert_called_once_with( '/features/installations/[id]/gateways/[serial]/devices/[device]/features/someprop/commands/doaction', '{}') + + def test_getProperty_gateway(self): + self.service = ViCareService(self.oauth_mock, self.accessor, ["type:gateway;VitoconnectOpto1"]) + self.service.getProperty("someprop") + self.oauth_mock.get.assert_called_once_with( + '/features/installations/[id]/gateways/[serial]/features/someprop') + + def test_fetch_all_features_gateway(self): + self.service = ViCareService(self.oauth_mock, self.accessor, ["type:gateway;VitoconnectOpto1"]) + self.service.fetch_all_features() + self.oauth_mock.get.assert_called_once_with( + '/features/installations/[id]/gateways/[serial]/features/') diff --git a/tests/test_TestForMissingProperties.py b/tests/test_TestForMissingProperties.py index 7844e55e..4fc2af2e 100644 --- a/tests/test_TestForMissingProperties.py +++ b/tests/test_TestForMissingProperties.py @@ -61,6 +61,9 @@ def test_missingProperties(self): # Ignored for now as both are not documented in https://documentation.viessmann.com/static/iot/data-points 'device.messages.errors.raw', 'device.productIdentification', + + # gateway + 'gateway.devices', # not used ] all_features = self.read_all_features() diff --git a/tests/test_VitoconnectOpto1.py b/tests/test_VitoconnectOpto1.py new file mode 100644 index 00000000..671c32aa --- /dev/null +++ b/tests/test_VitoconnectOpto1.py @@ -0,0 +1,14 @@ +import unittest + +from PyViCare.PyViCareGateway import Gateway +from tests.ViCareServiceMock import ViCareServiceMock + + +class VitoconnectOpto1(unittest.TestCase): + def setUp(self): + self.service = ViCareServiceMock('response/VitoconnectOpto1.json') + self.device = Gateway(self.service) + + def test_getWifiSignalStrength(self): + self.assertEqual( + self.device.getWifiSignalStrength(), -69)