diff --git a/PyViCare/PyViCare.py b/PyViCare/PyViCare.py index b408eea8..64115a7d 100644 --- a/PyViCare/PyViCare.py +++ b/PyViCare/PyViCare.py @@ -6,7 +6,6 @@ from PyViCare.PyViCareCachedService import ViCareCachedService from PyViCare.PyViCareDeviceConfig import PyViCareDeviceConfig from PyViCare.PyViCareOAuthManager import ViCareOAuthManager -from PyViCare.PyViCareRoomControl import RoomControl from PyViCare.PyViCareService import ViCareDeviceAccessor, ViCareService from PyViCare.PyViCareUtils import PyViCareInvalidDataError @@ -50,7 +49,6 @@ def __loadInstallations(self): self.all_devices = list(self.__extract_all_devices()) self.devices = [d for d in self.all_devices if d.device_type in self.SUPPORTED_DEVICE_TYPES] - self.__enrichZigbeeDevices() SUPPORTED_DEVICE_TYPES = [ "heating", "zigbee", "vitoconnect", "electricityStorage", @@ -69,35 +67,6 @@ def __extract_all_devices(self): yield PyViCareDeviceConfig(service, device.id, device.modelId, device.status, device.deviceType, device.roles) - def __enrichZigbeeDevices(self): - """Enrich Zigbee devices with sensor data from RoomControl. - - Viessmann moved temperature/humidity data from physical Zigbee - sensors to the RoomControl virtual device. This reverses that - mapping by cross-referencing RoomControl actors with Zigbee - device IDs. - """ - devices_by_id = {device_config.device_id: device_config for device_config in self.devices} - - for device_config in self.devices: - if device_config.device_type != "roomControl": - continue - - room_control = RoomControl(device_config.service) - try: - actor_map = room_control.buildActorRoomMap() - except Exception: # pylint: disable=broad-exception-caught - logger.debug("Could not build actor map for %s", device_config.getModel(), exc_info=True) - continue - - for device_id, room_id in actor_map.items(): - zigbee_config = devices_by_id.get(device_id) - if zigbee_config is None: - continue - zigbee_config.setRoomControlEnrichment(room_control, room_id) - logger.info("Enriched %s with room %s data from %s", - zigbee_config.device_id, room_id, device_config.getModel()) - class DictWrap(object): def __init__(self, d): diff --git a/PyViCare/PyViCareDeviceConfig.py b/PyViCare/PyViCareDeviceConfig.py index b4a64a24..364193fa 100644 --- a/PyViCare/PyViCareDeviceConfig.py +++ b/PyViCare/PyViCareDeviceConfig.py @@ -31,8 +31,6 @@ def __init__(self, service, device_id, device_model, status, device_type=None, r self.status = status self.device_type = device_type self.roles = roles if roles is not None else [] - self._room_control = None - self._room_id = None def asGeneric(self): return HeatingDevice(self.service) @@ -65,19 +63,11 @@ def asFloorHeatingChannel(self): return FloorHeatingChannel(self.service) def asRoomSensor(self): - sensor = RoomSensor(self.service) - if self._room_control is not None: - sensor.setRoomControl(self._room_control, self._room_id) - return sensor + return RoomSensor(self.service) def asRoomControl(self): return RoomControl(self.service) - def setRoomControlEnrichment(self, room_control, room_id): - """Store RoomControl enrichment data to apply when creating a RoomSensor.""" - self._room_control = room_control - self._room_id = room_id - def asRepeater(self): return Repeater(self.service) diff --git a/PyViCare/PyViCareRoomControl.py b/PyViCare/PyViCareRoomControl.py index 018e358c..2a5127ed 100644 --- a/PyViCare/PyViCareRoomControl.py +++ b/PyViCare/PyViCareRoomControl.py @@ -11,8 +11,7 @@ class RoomControl(Device): """Viessmann RoomControl virtual device. - Aggregates room sensor data and heating programs. - Used to enrich physical Zigbee devices with room data. + Aggregates room sensor data and heating programs per room. """ @handleNotSupported @@ -119,18 +118,3 @@ def activateRoomManualTillNextSchedule(self, room_id: str, temperature: float) - @handleAPICommandErrors def deactivateRoomManualTillNextSchedule(self, room_id: str) -> None: self.service.setProperty(f"rooms.{room_id}.quickmodes.manualTillNextSchedule", "deactivate", {}) - - # --- Mapping --- - - def buildActorRoomMap(self) -> dict[str, str]: - """Build a mapping of actor device ID -> room ID.""" - actor_map: dict[str, str] = {} - try: - rooms = self.getAvailableRooms() - except PyViCareNotSupportedFeatureError: - return actor_map - - for room_id in rooms: - for actor_id in self.getRoomActorIds(room_id): - actor_map[actor_id] = room_id - return actor_map diff --git a/PyViCare/PyViCareRoomSensor.py b/PyViCare/PyViCareRoomSensor.py index aaf0e8cd..4713bc91 100644 --- a/PyViCare/PyViCareRoomSensor.py +++ b/PyViCare/PyViCareRoomSensor.py @@ -1,132 +1,17 @@ -from __future__ import annotations - -from typing import Any, TYPE_CHECKING - from PyViCare.PyViCareDevice import ZigbeeBatteryDevice -from PyViCare.PyViCareUtils import handleNotSupported, handleAPICommandErrors - -if TYPE_CHECKING: - from PyViCare.PyViCareRoomControl import RoomControl +from PyViCare.PyViCareUtils import handleNotSupported class RoomSensor(ZigbeeBatteryDevice): - _room_control: RoomControl | None = None - _room_id: str | None = None - - def setRoomControl(self, room_control: RoomControl, room_id: str) -> None: - """Enrich this sensor with data from a RoomControl device.""" - self._room_control = room_control - self._room_id = room_id - - def _getRoomContext(self) -> tuple[RoomControl, str]: - """Return (room_control, room_id), raising if not enriched.""" - if self._room_control is None or self._room_id is None: - raise KeyError("roomControl") - return self._room_control, self._room_id - @handleNotSupported def getSerial(self) -> str: return str(self.getProperty("device.sensors.temperature")["deviceId"]) - # --- Sensors (enriched from RoomControl) --- - @handleNotSupported def getTemperature(self) -> float: - if self._room_control is not None and self._room_id is not None: - return self._room_control.getRoomTemperature(self._room_id) return float(self.getProperty("device.sensors.temperature")["properties"]["value"]["value"]) @handleNotSupported def getHumidity(self) -> float: - if self._room_control is not None and self._room_id is not None: - return self._room_control.getRoomHumidity(self._room_id) return float(self.getProperty("device.sensors.humidity")["properties"]["value"]["value"]) - - @handleNotSupported - def getCO2(self) -> int: - rc, rid = self._getRoomContext() - return rc.getRoomCO2(rid) - - @handleNotSupported - def getRoomName(self) -> str | None: - rc, rid = self._getRoomContext() - return rc.getRoomName(rid) - - @handleNotSupported - def getRoomType(self) -> str | None: - rc, rid = self._getRoomContext() - return rc.getRoomType(rid) - - @handleNotSupported - def getCondensationRisk(self) -> bool: - rc, rid = self._getRoomContext() - return rc.getRoomCondensationRisk(rid) - - # --- Operating state --- - - @handleNotSupported - def getOperatingStateLevel(self) -> str: - rc, rid = self._getRoomContext() - return rc.getRoomOperatingStateLevel(rid) - - @handleNotSupported - def getOperatingStateDemand(self) -> str: - rc, rid = self._getRoomContext() - return rc.getRoomOperatingStateDemand(rid) - - # --- Heating programs --- - - @handleNotSupported - def getNormalHeatingTemperature(self) -> float: - rc, rid = self._getRoomContext() - return rc.getRoomNormalHeatingTemperature(rid) - - @handleAPICommandErrors - def setNormalHeatingTemperature(self, temperature: float) -> None: - rc, rid = self._getRoomContext() - rc.setRoomNormalHeatingTemperature(rid, temperature) - - @handleNotSupported - def getReducedHeatingTemperature(self) -> float: - rc, rid = self._getRoomContext() - return rc.getRoomReducedHeatingTemperature(rid) - - @handleAPICommandErrors - def setReducedHeatingTemperature(self, temperature: float) -> None: - rc, rid = self._getRoomContext() - rc.setRoomReducedHeatingTemperature(rid, temperature) - - @handleNotSupported - def getComfortHeatingTemperature(self) -> float: - rc, rid = self._getRoomContext() - return rc.getRoomComfortHeatingTemperature(rid) - - @handleAPICommandErrors - def setComfortHeatingTemperature(self, temperature: float) -> None: - rc, rid = self._getRoomContext() - rc.setRoomComfortHeatingTemperature(rid, temperature) - - # --- Quick modes --- - - @handleNotSupported - def getManualTillNextScheduleActive(self) -> bool: - rc, rid = self._getRoomContext() - return rc.getRoomManualTillNextScheduleActive(rid) - - @handleAPICommandErrors - def activateManualTillNextSchedule(self, temperature: float) -> None: - rc, rid = self._getRoomContext() - rc.activateRoomManualTillNextSchedule(rid, temperature) - - @handleAPICommandErrors - def deactivateManualTillNextSchedule(self) -> None: - rc, rid = self._getRoomContext() - rc.deactivateRoomManualTillNextSchedule(rid) - - # --- Schedule --- - - @handleNotSupported - def getSchedule(self) -> dict[str, Any]: - rc, rid = self._getRoomContext() - return rc.getRoomSchedule(rid) diff --git a/tests/test_RoomControl.py b/tests/test_RoomControl.py index 8b431e87..9c5b9611 100644 --- a/tests/test_RoomControl.py +++ b/tests/test_RoomControl.py @@ -1,12 +1,6 @@ import unittest from PyViCare.PyViCareRoomControl import RoomControl -from PyViCare.PyViCareRoomSensor import RoomSensor -from PyViCare.PyViCareUtils import ( - PyViCareCommandError, - PyViCareNotSupportedFeatureError, - isSupported, -) from tests.ViCareServiceMock import ViCareServiceMock @@ -67,75 +61,3 @@ def test_getRoomSchedule(self): def test_getRoomManualTillNextScheduleActive(self): result = self.device.getRoomManualTillNextScheduleActive("0") self.assertIsInstance(result, bool) - - def test_buildActorRoomMap(self): - actor_map = self.device.buildActorRoomMap() - self.assertIsInstance(actor_map, dict) - self.assertTrue(len(actor_map) > 0) - for room_id in actor_map.values(): - self.assertIsInstance(room_id, str) - - -class RoomSensorEnrichmentTest(unittest.TestCase): - def setUp(self): - self.room_control_service = ViCareServiceMock('response/RoomControl.json') - self.room_control = RoomControl(self.room_control_service) - self.sensor_service = ViCareServiceMock('response/RoomControl.json', - rawInput={"data": []}) - self.sensor = RoomSensor(self.sensor_service) - self.sensor.setRoomControl(self.room_control, "0") - - def test_getTemperature(self): - self.assertAlmostEqual(self.sensor.getTemperature(), 20.7) - - def test_getHumidity(self): - self.assertEqual(self.sensor.getHumidity(), 53) - - def test_getRoomName(self): - self.assertEqual(self.sensor.getRoomName(), "Bedroom") - - def test_getRoomType(self): - self.assertEqual(self.sensor.getRoomType(), "bedroom") - - def test_getCondensationRisk(self): - result = self.sensor.getCondensationRisk() - self.assertIsNotNone(result) - - def test_getOperatingStateLevel(self): - result = self.sensor.getOperatingStateLevel() - self.assertIsNotNone(result) - - def test_getNormalHeatingTemperature(self): - temp = self.sensor.getNormalHeatingTemperature() - self.assertIsInstance(temp, (int, float)) - - def test_getReducedHeatingTemperature(self): - temp = self.sensor.getReducedHeatingTemperature() - self.assertIsInstance(temp, (int, float)) - - def test_getComfortHeatingTemperature(self): - temp = self.sensor.getComfortHeatingTemperature() - self.assertIsInstance(temp, (int, float)) - - def test_getSchedule(self): - schedule = self.sensor.getSchedule() - self.assertIn("active", schedule) - self.assertIn("mon", schedule) - - def test_getManualTillNextScheduleActive(self): - result = self.sensor.getManualTillNextScheduleActive() - self.assertIsInstance(result, bool) - - def test_without_enrichment_reports_not_supported(self): - sensor = RoomSensor(self.sensor_service) - self.assertFalse(isSupported(sensor.getRoomName)) - self.assertFalse(isSupported(sensor.getNormalHeatingTemperature)) - - with self.assertRaises(PyViCareNotSupportedFeatureError): - sensor.getRoomName() - - with self.assertRaises(PyViCareNotSupportedFeatureError): - sensor.getNormalHeatingTemperature() - - with self.assertRaises(PyViCareCommandError): - sensor.setNormalHeatingTemperature(20)