import json import logging import re from PyViCare.PyViCareFloorHeating import FloorHeating, FloorHeatingChannel from PyViCare.PyViCareFuelCell import FuelCell from PyViCare.PyViCareRoomControl import RoomControl from PyViCare.PyViCareGazBoiler import GazBoiler from PyViCare.PyViCareHeatingDevice import HeatingDevice from PyViCare.PyViCareHeatPump import HeatPump from PyViCare.PyViCareHybrid import Hybrid from PyViCare.PyViCareOilBoiler import OilBoiler from PyViCare.PyViCarePelletsBoiler import PelletsBoiler from PyViCare.PyViCareRadiatorActuator import RadiatorActuator from PyViCare.PyViCareRoomSensor import RoomSensor from PyViCare.PyViCareRepeater import Repeater from PyViCare.PyViCareElectricalEnergySystem import ElectricalEnergySystem from PyViCare.PyViCareGateway import Gateway from PyViCare.PyViCareVentilationDevice import VentilationDevice logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) class PyViCareDeviceConfig: # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-instance-attributes def __init__(self, service, device_id, device_model, status, device_type=None, roles=None): self.service = service self.device_id = device_id self.device_model = device_model 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) def asGazBoiler(self): return GazBoiler(self.service) def asFuelCell(self): return FuelCell(self.service) def asHeatPump(self): return HeatPump(self.service) def asOilBoiler(self): return OilBoiler(self.service) def asPelletsBoiler(self): return PelletsBoiler(self.service) def asHybridDevice(self): return Hybrid(self.service) def asRadiatorActuator(self): return RadiatorActuator(self.service) def asFloorHeating(self): return FloorHeating(self.service) 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 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) def asElectricalEnergySystem(self): return ElectricalEnergySystem(self.service) def asGateway(self): return Gateway(self.service) def asVentilation(self): return VentilationDevice(self.service) def getConfig(self): return self.service.accessor def getId(self): return self.device_id def getModel(self): return self.device_model def isOnline(self): return self.status == "Online" def getDeviceType(self): return self.device_type def getRoles(self): return self.roles def isGateway(self): return self.service._isGateway() # pylint: disable=protected-access # see: https://vitodata300.viessmann.com/vd300/ApplicationHelp/VD300/1031_de_DE/Ger%C3%A4teliste.html def asAutoDetectDevice(self): device_types = [ (self.asFuelCell, r"Vitovalor|Vitocharge|Vitoblo", []), (self.asPelletsBoiler, r"Vitoligno|Ecotronic|VBC550P", []), (self.asOilBoiler, r"Vitoladens|Vitoradial|Vitorondens|VPlusH|V200KW2_6", []), (self.asGazBoiler, r"Vitodens|VScotH|Vitocrossal|VDensH|Vitopend|VPendH|OT_Heating_System", ["type:boiler"]), (self.asHeatPump, r"Vitocal|VBC70|V200WO1A|CU401B", ["type:heatpump"]), (self.asElectricalEnergySystem, r"E3_VitoCharge_03", ["type:ees"]), # ees, it this a typo? (self.asElectricalEnergySystem, r"E3_VitoCharge_05", ["type:ess"]), (self.asVentilation, r"E3_ViAir", ["type:ventilation"]), (self.asVentilation, r"E3_ViAir", ["type:ventilation;central"]), (self.asVentilation, r"E3_VitoPure", ["type:ventilation;purifier"]), (self.asRadiatorActuator, r"E3_RadiatorActuator", ["type:radiator"]), (self.asFloorHeating, r"Smart_zigbee_fht_main|E3_FloorHeatingCircuitDistributorBox", ["type:fhtMain"]), (self.asFloorHeatingChannel, r"Smart_zigbee_fht_channel", ["type:fhtChannel"]), (self.asRoomControl, r"E3_RoomControl|Smart_RoomControl", ["type:virtual;smartRoomControl"]), (self.asRoomSensor, r"E3_RoomSensor", ["type:climateSensor"]), (self.asRepeater, r"E3_Repeater", ["type:repeater"]), (self.asGateway, r"E3_TCU41_x04", ["type:gateway;TCU100"]), (self.asGateway, r"E3_TCU19_x05", ["type:gateway;TCU200"]), (self.asGateway, r"E3_TCU10_x07", ["type:gateway;TCU300"]), (self.asGateway, r"Heatbox1", ["type:gateway;VitoconnectOpto1"]), (self.asGateway, r"Heatbox2", ["type:gateway;VitoconnectOpto2/OT2"]) ] for (creator_method, type_name, roles) in device_types: if re.search(type_name, self.device_model) or self.service.hasRoles(roles): logger.info("detected %s %s", self.device_model, creator_method.__name__) device = creator_method() if isinstance(device, (GazBoiler, HeatPump)) and not isinstance(device, Hybrid): if self._isHybridByFeatures(): logger.info("upgrading %s to Hybrid based on API features", self.device_model) return self.asHybridDevice() return device logger.info("Could not auto detect %s. Use generic device.", self.device_model) return self.asGeneric() def _isHybridByFeatures(self): """Check API features to detect hybrid devices (both burners and compressors).""" try: features = self.service.fetch_all_features() feature_names = [f["feature"] for f in features.get("data", [])] has_burners = any(f.startswith("heating.burners") for f in feature_names) has_compressors = any(f.startswith("heating.compressors") for f in feature_names) return has_burners and has_compressors except (KeyError, TypeError, AttributeError, OSError): logger.debug("Could not fetch features for hybrid detection of %s", self.device_model) return False def get_raw_json(self): return self.service.fetch_all_features() def dump_secure(self, flat=False): raw_data = self.get_raw_json() device_info = { "id": self.device_id, "modelId": self.device_model, "type": self.device_type, "status": self.status, "roles": self.roles } output = { "device": device_info, "data": raw_data['data'] } if flat: inner = ',\n'.join([json.dumps(x, sort_keys=True) for x in output['data']]) outer = json.dumps({'device': output['device'], 'data': ['placeholder']}, indent=0, sort_keys=True) dumpJSON = outer.replace('"placeholder"', inner) else: dumpJSON = json.dumps(output, indent=4, sort_keys=True) def repl(m): return m.group(1) + ('#' * len(m.group(2))) + m.group(3) return re.sub(r'(["\/])(\d{6,})(["\/])', repl, dumpJSON)