From e492518c94a15862e8c8b7d0e3779f582c698b08 Mon Sep 17 00:00:00 2001 From: Filip Pytloun Date: Wed, 22 Apr 2020 16:58:12 +0200 Subject: [PATCH] Add patched files --- Dockerfile | 2 + src/components/fibaro/__init__.py | 500 ++++++++++++++++++++++++++++++ src/components/fibaro/climate.py | 331 ++++++++++++++++++++ 3 files changed, 833 insertions(+) create mode 100644 src/components/fibaro/__init__.py create mode 100644 src/components/fibaro/climate.py diff --git a/Dockerfile b/Dockerfile index 6a41e08..3975355 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ FROM homeassistant/home-assistant:0.108.3 +COPY src/ /usr/src/homeassistant/homeassistant/ + RUN /usr/local/bin/python3 -m pip install --quiet --no-cache-dir --upgrade --constraint /usr/src/homeassistant/homeassistant/package_constraints.txt --find-links https://wheels.home-assistant.io/alpine-3.10/amd64/ --prefer-binary \ aiofiles==0.4.0 \ aiogithubapi==0.4.3 \ diff --git a/src/components/fibaro/__init__.py b/src/components/fibaro/__init__.py new file mode 100644 index 0000000..00ed8b9 --- /dev/null +++ b/src/components/fibaro/__init__.py @@ -0,0 +1,500 @@ +"""Support for the Fibaro devices.""" +from collections import defaultdict +import logging +from typing import Optional + +from fiblary3.client.v4.client import Client as FibaroClient, StateHandler +import voluptuous as vol + +from homeassistant.const import ( + ATTR_ARMED, + ATTR_BATTERY_LEVEL, + CONF_DEVICE_CLASS, + CONF_EXCLUDE, + CONF_ICON, + CONF_PASSWORD, + CONF_URL, + CONF_USERNAME, + CONF_WHITE_VALUE, + EVENT_HOMEASSISTANT_STOP, +) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.util import convert, slugify + +_LOGGER = logging.getLogger(__name__) + +ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh" +ATTR_CURRENT_POWER_W = "current_power_w" + +CONF_COLOR = "color" +CONF_DEVICE_CONFIG = "device_config" +CONF_DIMMING = "dimming" +CONF_GATEWAYS = "gateways" +CONF_PLUGINS = "plugins" +CONF_RESET_COLOR = "reset_color" +DOMAIN = "fibaro" +FIBARO_CONTROLLERS = "fibaro_controllers" +FIBARO_DEVICES = "fibaro_devices" +FIBARO_COMPONENTS = [ + "binary_sensor", + "climate", + "cover", + "light", + "scene", + "sensor", + "switch", +] + +FIBARO_TYPEMAP = { + "com.fibaro.multilevelSensor": "sensor", + "com.fibaro.binarySwitch": "switch", + "com.fibaro.multilevelSwitch": "switch", + "com.fibaro.FGD212": "light", + "com.fibaro.FGR": "cover", + "com.fibaro.doorSensor": "binary_sensor", + "com.fibaro.doorWindowSensor": "binary_sensor", + "com.fibaro.FGMS001": "binary_sensor", + "com.fibaro.heatDetector": "binary_sensor", + "com.fibaro.lifeDangerSensor": "binary_sensor", + "com.fibaro.smokeSensor": "binary_sensor", + "com.fibaro.remoteSwitch": "switch", + "com.fibaro.sensor": "sensor", + "com.fibaro.colorController": "light", + "com.fibaro.securitySensor": "binary_sensor", + "com.fibaro.hvac": "climate", + "com.fibaro.setpoint": "climate", + "com.fibaro.FGT001": "climate", + "com.fibaro.thermostatDanfoss": "climate", +} + +DEVICE_CONFIG_SCHEMA_ENTRY = vol.Schema( + { + vol.Optional(CONF_DIMMING): cv.boolean, + vol.Optional(CONF_COLOR): cv.boolean, + vol.Optional(CONF_WHITE_VALUE): cv.boolean, + vol.Optional(CONF_RESET_COLOR): cv.boolean, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_ICON): cv.string, + } +) + +FIBARO_ID_LIST_SCHEMA = vol.Schema([cv.string]) + +GATEWAY_CONFIG = vol.Schema( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_URL): cv.url, + vol.Optional(CONF_PLUGINS, default=False): cv.boolean, + vol.Optional(CONF_EXCLUDE, default=[]): FIBARO_ID_LIST_SCHEMA, + vol.Optional(CONF_DEVICE_CONFIG, default={}): vol.Schema( + {cv.string: DEVICE_CONFIG_SCHEMA_ENTRY} + ), + }, + extra=vol.ALLOW_EXTRA, +) + +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + {vol.Required(CONF_GATEWAYS): vol.All(cv.ensure_list, [GATEWAY_CONFIG])} + ) + }, + extra=vol.ALLOW_EXTRA, +) + + +class FibaroController: + """Initiate Fibaro Controller Class.""" + + def __init__(self, config): + """Initialize the Fibaro controller.""" + + self._client = FibaroClient( + config[CONF_URL], config[CONF_USERNAME], config[CONF_PASSWORD] + ) + self._scene_map = None + # Whether to import devices from plugins + self._import_plugins = config[CONF_PLUGINS] + self._device_config = config[CONF_DEVICE_CONFIG] + self._room_map = None # Mapping roomId to room object + self._device_map = None # Mapping deviceId to device object + self.fibaro_devices = None # List of devices by type + self._callbacks = {} # Update value callbacks by deviceId + self._state_handler = None # Fiblary's StateHandler object + self._excluded_devices = config[CONF_EXCLUDE] + self.hub_serial = None # Unique serial number of the hub + + def connect(self): + """Start the communication with the Fibaro controller.""" + try: + login = self._client.login.get() + info = self._client.info.get() + self.hub_serial = slugify(info.serialNumber) + except AssertionError: + _LOGGER.error("Can't connect to Fibaro HC. " "Please check URL.") + return False + if login is None or login.status is False: + _LOGGER.error( + "Invalid login for Fibaro HC. " "Please check username and password" + ) + return False + + self._room_map = {room.id: room for room in self._client.rooms.list()} + self._read_devices() + self._read_scenes() + return True + + def enable_state_handler(self): + """Start StateHandler thread for monitoring updates.""" + self._state_handler = StateHandler(self._client, self._on_state_change) + + def disable_state_handler(self): + """Stop StateHandler thread used for monitoring updates.""" + self._state_handler.stop() + self._state_handler = None + + def _on_state_change(self, state): + """Handle change report received from the HomeCenter.""" + callback_set = set() + _LOGGER.debug("State change: %s", state) + for change in state.get("changes", []): + try: + dev_id = change.pop("id") + if dev_id not in self._device_map.keys(): + continue + device = self._device_map[dev_id] + for property_name, value in change.items(): + if property_name == "log": + if value and value != "transfer OK": + _LOGGER.debug("LOG %s: %s", device.friendly_name, value) + continue + if property_name == "logTemp": + continue + if property_name in device.properties: + device.properties[property_name] = value + _LOGGER.debug( + "<- %s.%s = %s", device.ha_id, property_name, str(value) + ) + else: + _LOGGER.warning("%s.%s not found", device.ha_id, property_name) + if dev_id in self._callbacks: + callback_set.add(dev_id) + except (ValueError, KeyError): + pass + for item in callback_set: + self._callbacks[item]() + + def register(self, device_id, callback): + """Register device with a callback for updates.""" + self._callbacks[device_id] = callback + + def get_children(self, device_id): + """Get a list of child devices.""" + return [ + device + for device in self._device_map.values() + if device.parentId == device_id + ] + + def get_siblings(self, device_id): + """Get the siblings of a device.""" + return self.get_children(self._device_map[device_id].parentId) + + @staticmethod + def _map_device_to_type(device): + """Map device to HA device type.""" + # Use our lookup table to identify device type + device_type = None + if "type" in device: + device_type = FIBARO_TYPEMAP.get(device.type) + if device_type is None and "baseType" in device: + device_type = FIBARO_TYPEMAP.get(device.baseType) + + # We can also identify device type by its capabilities + if device_type is None: + if "setBrightness" in device.actions: + device_type = "light" + elif "turnOn" in device.actions: + device_type = "switch" + elif "open" in device.actions: + device_type = "cover" + elif "value" in device.properties: + if device.properties.value in ("true", "false"): + device_type = "binary_sensor" + else: + device_type = "sensor" + + # Switches that control lights should show up as lights + if ( + device_type == "switch" + and device.properties.get("isLight", "false") == "true" + ): + device_type = "light" + return device_type + + def _read_scenes(self): + scenes = self._client.scenes.list() + self._scene_map = {} + for device in scenes: + if not device.visible: + continue + device.fibaro_controller = self + if device.roomID == 0: + room_name = "Unknown" + else: + room_name = self._room_map[device.roomID].name + device.room_name = room_name + device.friendly_name = f"{room_name} {device.name}" + device.ha_id = "scene_{}_{}_{}".format( + slugify(room_name), slugify(device.name), device.id + ) + device.unique_id_str = f"{self.hub_serial}.scene.{device.id}" + self._scene_map[device.id] = device + self.fibaro_devices["scene"].append(device) + + def _read_devices(self): + """Read and process the device list.""" + devices = self._client.devices.list() + self._device_map = {} + self.fibaro_devices = defaultdict(list) + last_climate_parent = None + for device in devices: + try: + device.fibaro_controller = self + if device.roomID == 0: + room_name = "Unknown" + else: + room_name = self._room_map[device.roomID].name + device.room_name = room_name + device.friendly_name = room_name + " " + device.name + device.ha_id = "{}_{}_{}".format( + slugify(room_name), slugify(device.name), device.id + ) + if ( + device.enabled + and ( + "isPlugin" not in device + or (not device.isPlugin or self._import_plugins) + ) + and device.ha_id not in self._excluded_devices + ): + device.mapped_type = self._map_device_to_type(device) + device.device_config = self._device_config.get(device.ha_id, {}) + else: + device.mapped_type = None + dtype = device.mapped_type + if dtype: + device.unique_id_str = f"{self.hub_serial}.{device.id}" + self._device_map[device.id] = device + if dtype != "climate": + self.fibaro_devices[dtype].append(device) + else: + # if a sibling of this has been added, skip this one + # otherwise add the first visible device in the group + # which is a hack, but solves a problem with FGT having + # hidden compatibility devices before the real device + if last_climate_parent != device.parentId and device.visible: + self.fibaro_devices[dtype].append(device) + last_climate_parent = device.parentId + _LOGGER.debug( + "%s (%s, %s) -> %s %s", + device.ha_id, + device.type, + device.baseType, + dtype, + str(device), + ) + except (KeyError, ValueError): + pass + + +def setup(hass, base_config): + """Set up the Fibaro Component.""" + gateways = base_config[DOMAIN][CONF_GATEWAYS] + hass.data[FIBARO_CONTROLLERS] = {} + + def stop_fibaro(event): + """Stop Fibaro Thread.""" + _LOGGER.info("Shutting down Fibaro connection") + for controller in hass.data[FIBARO_CONTROLLERS].values(): + controller.disable_state_handler() + + hass.data[FIBARO_DEVICES] = {} + for component in FIBARO_COMPONENTS: + hass.data[FIBARO_DEVICES][component] = [] + + for gateway in gateways: + controller = FibaroController(gateway) + if controller.connect(): + hass.data[FIBARO_CONTROLLERS][controller.hub_serial] = controller + for component in FIBARO_COMPONENTS: + hass.data[FIBARO_DEVICES][component].extend( + controller.fibaro_devices[component] + ) + + if hass.data[FIBARO_CONTROLLERS]: + for component in FIBARO_COMPONENTS: + discovery.load_platform(hass, component, DOMAIN, {}, base_config) + for controller in hass.data[FIBARO_CONTROLLERS].values(): + controller.enable_state_handler() + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_fibaro) + return True + + return False + + +class FibaroDevice(Entity): + """Representation of a Fibaro device entity.""" + + def __init__(self, fibaro_device): + """Initialize the device.""" + self.fibaro_device = fibaro_device + self.controller = fibaro_device.fibaro_controller + self._name = fibaro_device.friendly_name + self.ha_id = fibaro_device.ha_id + + async def async_added_to_hass(self): + """Call when entity is added to hass.""" + #_LOGGER.debug("Registering device: %s", self.fibaro_device_id) + self.controller.register(self.fibaro_device.id, self._update_callback) + + def _update_callback(self): + """Update the state.""" + self.schedule_update_ha_state(True) + + @property + def level(self): + """Get the level of Fibaro device.""" + if "value" in self.fibaro_device.properties: + return self.fibaro_device.properties.value + return None + + @property + def level2(self): + """Get the tilt level of Fibaro device.""" + if "value2" in self.fibaro_device.properties: + return self.fibaro_device.properties.value2 + return None + + def dont_know_message(self, action): + """Make a warning in case we don't know how to perform an action.""" + _LOGGER.warning( + "Not sure how to setValue: %s " "(available actions: %s)", + str(self.ha_id), + str(self.fibaro_device.actions), + ) + + def set_level(self, level): + """Set the level of Fibaro device.""" + self.action("setValue", level) + if "value" in self.fibaro_device.properties: + self.fibaro_device.properties.value = level + if "brightness" in self.fibaro_device.properties: + self.fibaro_device.properties.brightness = level + + def set_level2(self, level): + """Set the level2 of Fibaro device.""" + self.action("setValue2", level) + if "value2" in self.fibaro_device.properties: + self.fibaro_device.properties.value2 = level + + def call_turn_on(self): + """Turn on the Fibaro device.""" + self.action("turnOn") + + def call_turn_off(self): + """Turn off the Fibaro device.""" + self.action("turnOff") + + def call_set_color(self, red, green, blue, white): + """Set the color of Fibaro device.""" + red = int(max(0, min(255, red))) + green = int(max(0, min(255, green))) + blue = int(max(0, min(255, blue))) + white = int(max(0, min(255, white))) + color_str = f"{red},{green},{blue},{white}" + self.fibaro_device.properties.color = color_str + self.action("setColor", str(red), str(green), str(blue), str(white)) + + def action(self, cmd, *args): + """Perform an action on the Fibaro HC.""" + if cmd in self.fibaro_device.actions: + getattr(self.fibaro_device, cmd)(*args) + _LOGGER.debug("-> %s.%s%s called", str(self.ha_id), str(cmd), str(args)) + else: + self.dont_know_message(cmd) + + @property + def hidden(self) -> bool: + """Return True if the entity should be hidden from UIs.""" + return self.fibaro_device.visible is False + + @property + def current_power_w(self): + """Return the current power usage in W.""" + if "power" in self.fibaro_device.properties: + power = self.fibaro_device.properties.power + if power: + return convert(power, float, 0.0) + else: + return None + + @property + def current_binary_state(self): + """Return the current binary state.""" + if self.fibaro_device.properties.value == "false": + return False + if ( + self.fibaro_device.properties.value == "true" + or int(self.fibaro_device.properties.value) > 0 + ): + return True + return False + + @property + def unique_id(self) -> str: + """Return a unique ID.""" + return self.fibaro_device.unique_id_str + + @property + def name(self) -> Optional[str]: + """Return the name of the device.""" + return self._name + + @property + def should_poll(self): + """Get polling requirement from fibaro device.""" + return False + + def update(self): + """Call to update state.""" + pass + + @property + def device_state_attributes(self): + """Return the state attributes of the device.""" + attr = {} + + try: + if "battery" in self.fibaro_device.interfaces: + attr[ATTR_BATTERY_LEVEL] = int( + self.fibaro_device.properties.batteryLevel + ) + if "fibaroAlarmArm" in self.fibaro_device.interfaces: + attr[ATTR_ARMED] = bool(self.fibaro_device.properties.armed) + if "power" in self.fibaro_device.interfaces: + attr[ATTR_CURRENT_POWER_W] = convert( + self.fibaro_device.properties.power, float, 0.0 + ) + if "energy" in self.fibaro_device.interfaces: + attr[ATTR_CURRENT_ENERGY_KWH] = convert( + self.fibaro_device.properties.energy, float, 0.0 + ) + except (ValueError, KeyError): + pass + + attr["fibaro_id"] = self.fibaro_device.id + return attr diff --git a/src/components/fibaro/climate.py b/src/components/fibaro/climate.py new file mode 100644 index 0000000..4e4cfd5 --- /dev/null +++ b/src/components/fibaro/climate.py @@ -0,0 +1,331 @@ +"""Support for Fibaro thermostats.""" +import logging +import re + +from homeassistant.components.climate import ClimateDevice +from homeassistant.components.climate.const import ( + HVAC_MODE_AUTO, + HVAC_MODE_COOL, + HVAC_MODE_DRY, + HVAC_MODE_FAN_ONLY, + HVAC_MODE_HEAT, + HVAC_MODE_OFF, + PRESET_AWAY, + PRESET_BOOST, + SUPPORT_FAN_MODE, + SUPPORT_PRESET_MODE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT + +from . import FIBARO_DEVICES, FibaroDevice + +PRESET_RESUME = "resume" +PRESET_MOIST = "moist" +PRESET_FURNACE = "furnace" +PRESET_CHANGEOVER = "changeover" +PRESET_ECO_HEAT = "eco_heat" +PRESET_ECO_COOL = "eco_cool" +PRESET_FORCE_OPEN = "force_open" + +_LOGGER = logging.getLogger(__name__) + +# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04 +# Table 128, Thermostat Fan Mode Set version 4::Fan Mode encoding +FANMODES = { + 0: "off", + 1: "low", + 2: "auto_high", + 3: "medium", + 4: "auto_medium", + 5: "high", + 6: "circulation", + 7: "humidity_circulation", + 8: "left_right", + 9: "up_down", + 10: "quiet", + 128: "auto", +} + +HA_FANMODES = {v: k for k, v in FANMODES.items()} + +# SDS13781-10 Z-Wave Application Command Class Specification 2019-01-04 +# Table 130, Thermostat Mode Set version 3::Mode encoding. +# 4 AUXILIARY +OPMODES_PRESET = { + 5: PRESET_RESUME, + 7: PRESET_FURNACE, + 9: PRESET_MOIST, + 10: PRESET_CHANGEOVER, + 11: PRESET_ECO_HEAT, + 12: PRESET_ECO_COOL, + 13: PRESET_AWAY, + 15: PRESET_BOOST, + 31: PRESET_FORCE_OPEN, +} + +HA_OPMODES_PRESET = {v: k for k, v in OPMODES_PRESET.items()} + +OPMODES_HVAC = { + 0: HVAC_MODE_OFF, + 1: HVAC_MODE_HEAT, + 2: HVAC_MODE_COOL, + 3: HVAC_MODE_AUTO, + 4: HVAC_MODE_HEAT, + 5: HVAC_MODE_AUTO, + 6: HVAC_MODE_FAN_ONLY, + 7: HVAC_MODE_HEAT, + 8: HVAC_MODE_DRY, + 9: HVAC_MODE_DRY, + 10: HVAC_MODE_AUTO, + 11: HVAC_MODE_HEAT, + 12: HVAC_MODE_COOL, + 13: HVAC_MODE_AUTO, + 15: HVAC_MODE_AUTO, + 31: HVAC_MODE_HEAT, +} + +HA_OPMODES_HVAC = { + HVAC_MODE_OFF: 0, + HVAC_MODE_HEAT: 1, + HVAC_MODE_COOL: 2, + HVAC_MODE_AUTO: 3, + HVAC_MODE_FAN_ONLY: 6, +} + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Perform the setup for Fibaro controller devices.""" + if discovery_info is None: + return + + add_entities( + [FibaroThermostat(device) for device in hass.data[FIBARO_DEVICES]["climate"]], + True, + ) + + +class FibaroThermostat(FibaroDevice, ClimateDevice): + """Representation of a Fibaro Thermostat.""" + + def __init__(self, fibaro_device): + """Initialize the Fibaro device.""" + super().__init__(fibaro_device) + self._temp_sensor_device = None + self._target_temp_device = None + self._op_mode_device = None + self._fan_mode_device = None + self._support_flags = 0 + self.entity_id = f"climate.{self.ha_id}" + self._hvac_support = [] + self._preset_support = [] + self._fan_support = [] + + siblings = fibaro_device.fibaro_controller.get_siblings(fibaro_device.id) + tempunit = "C" + for device in siblings: + if device.type == "com.fibaro.temperatureSensor": + self._temp_sensor_device = FibaroDevice(device) + tempunit = device.properties.unit + if ( + "setTargetLevel" in device.actions + or "setThermostatSetpoint" in device.actions + ): + self._target_temp_device = FibaroDevice(device) + self._support_flags |= SUPPORT_TARGET_TEMPERATURE + tempunit = device.properties.unit + if "setMode" in device.actions or "setOperatingMode" in device.actions: + self._op_mode_device = FibaroDevice(device) + self._support_flags |= SUPPORT_PRESET_MODE + if "setFanMode" in device.actions: + self._fan_mode_device = FibaroDevice(device) + self._support_flags |= SUPPORT_FAN_MODE + + if tempunit == "F": + self._unit_of_temp = TEMP_FAHRENHEIT + else: + self._unit_of_temp = TEMP_CELSIUS + + if self._fan_mode_device: + fan_modes = self._fan_mode_device.fibaro_device.properties.supportedModes.split( + "," + ) + for mode in fan_modes: + mode = int(mode) + if mode not in FANMODES: + _LOGGER.warning("%d unknown fan mode", mode) + continue + self._fan_support.append(FANMODES[int(mode)]) + + if self._op_mode_device: + prop = self._op_mode_device.fibaro_device.properties + if "supportedOperatingModes" in prop: + op_modes = prop.supportedOperatingModes.split(",") + elif "supportedModes" in prop: + op_modes = prop.supportedModes.split(",") + else: + _LOGGER.error(prop) + for mode in op_modes: + mode = int(mode) + if mode in OPMODES_HVAC: + mode_ha = OPMODES_HVAC[mode] + if mode_ha not in self._hvac_support: + self._hvac_support.append(mode_ha) + if mode in OPMODES_PRESET: + self._preset_support.append(OPMODES_PRESET[mode]) + + async def async_added_to_hass(self): + """Call when entity is added to hass.""" + _LOGGER.debug( + "Climate %s\n" + "- _temp_sensor_device %s\n" + "- _target_temp_device %s\n" + "- _op_mode_device %s\n" + "- _fan_mode_device %s", + self.ha_id, + self._temp_sensor_device.ha_id if self._temp_sensor_device else "None", + self._target_temp_device.ha_id if self._target_temp_device else "None", + self._op_mode_device.ha_id if self._op_mode_device else "None", + self._fan_mode_device.ha_id if self._fan_mode_device else "None", + ) + await super().async_added_to_hass() + + # Register update callback for child devices + siblings = self.fibaro_device.fibaro_controller.get_siblings( + self.fibaro_device.id + ) + for device in siblings: + if device != self.fibaro_device: + self.controller.register(device.id, self._update_callback) + + @property + def supported_features(self): + """Return the list of supported features.""" + return self._support_flags + + @property + def fan_modes(self): + """Return the list of available fan modes.""" + if not self._fan_mode_device: + return None + return self._fan_support + + @property + def fan_mode(self): + """Return the fan setting.""" + if not self._fan_mode_device: + return None + mode = int(self._fan_mode_device.fibaro_device.properties.mode) + return FANMODES[mode] + + def set_fan_mode(self, fan_mode): + """Set new target fan mode.""" + if not self._fan_mode_device: + return + self._fan_mode_device.action("setFanMode", HA_FANMODES[fan_mode]) + + @property + def fibaro_op_mode(self): + """Return the operating mode of the device.""" + if not self._op_mode_device: + return 1 # Heat + + if "operatingMode" in self._op_mode_device.fibaro_device.properties: + return int(self._op_mode_device.fibaro_device.properties.operatingMode) + + return int(self._op_mode_device.fibaro_device.properties.mode) + + @property + def hvac_mode(self): + """Return current operation ie. heat, cool, idle.""" + return OPMODES_HVAC[self.fibaro_op_mode] + + @property + def hvac_modes(self): + """Return the list of available operation modes.""" + if not self._op_mode_device: + return [HVAC_MODE_HEAT] + return self._hvac_support + + def set_hvac_mode(self, hvac_mode): + """Set new target operation mode.""" + if not self._op_mode_device: + return + if self.preset_mode: + return + + if "setOperatingMode" in self._op_mode_device.fibaro_device.actions: + self._op_mode_device.action("setOperatingMode", HA_OPMODES_HVAC[hvac_mode]) + elif "setMode" in self._op_mode_device.fibaro_device.actions: + self._op_mode_device.action("setMode", HA_OPMODES_HVAC[hvac_mode]) + + @property + def preset_mode(self): + """Return the current preset mode, e.g., home, away, temp. + + Requires SUPPORT_PRESET_MODE. + """ + if not self._op_mode_device: + return None + + if "operatingMode" in self._op_mode_device.fibaro_device.properties: + mode = int(self._op_mode_device.fibaro_device.properties.operatingMode) + else: + mode = int(self._op_mode_device.fibaro_device.properties.mode) + + if mode not in OPMODES_PRESET: + return None + return OPMODES_PRESET[mode] + + @property + def preset_modes(self): + """Return a list of available preset modes. + + Requires SUPPORT_PRESET_MODE. + """ + if not self._op_mode_device: + return None + return self._preset_support + + def set_preset_mode(self, preset_mode: str) -> None: + """Set new preset mode.""" + if self._op_mode_device is None: + return + if "setOperatingMode" in self._op_mode_device.fibaro_device.actions: + self._op_mode_device.action( + "setOperatingMode", HA_OPMODES_PRESET[preset_mode] + ) + elif "setMode" in self._op_mode_device.fibaro_device.actions: + self._op_mode_device.action("setMode", HA_OPMODES_PRESET[preset_mode]) + + @property + def temperature_unit(self): + """Return the unit of measurement.""" + return self._unit_of_temp + + @property + def current_temperature(self): + """Return the current temperature.""" + if self._temp_sensor_device: + device = self._temp_sensor_device.fibaro_device + return float(device.properties.value) + return None + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + if self._target_temp_device: + device = self._target_temp_device.fibaro_device + return float(device.properties.value) + #return float(device.properties.targetLevel) + return None + + def set_temperature(self, **kwargs): + """Set new target temperatures.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + target = self._target_temp_device + if temperature is not None: + if "setThermostatSetpoint" in target.fibaro_device.actions: + target.action("setThermostatSetpoint", self.fibaro_op_mode, temperature) + else: + target.action("setTargetLevel", temperature)