Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 9505d8c
Author: Mick Vleeshouwer <Mick.Vleeshouwer@microsoft.com>
Date:   Tue Aug 10 02:03:19 2021 -0700

    Add sensors dynamically based on available device state (#425)

    * Add temporary state sensor

    * Add extra sensors

    * Add more states

    * Add more sensors

    * Create supported_platforms variable

    * Use Overkiz label as device name

    * Suffix state sensor name with device index

    * Avoid to duplicate state key

    * Remove now usless rssi level attribute

    * Migrate LightSensor to TahomaStateSensor

    * Apply linters

    * Add PriorityLockOriginatorState sensor

    * Bump minimum version to 2021.8

    * Remove unneeded value key

    * Migrate to SensorEntityDescription

    * Small tweaks

    * Add battery level

    * Remove unused state keys

    * Test electricity sensor

    * Add extra sensors

    * Add sensor

    * Add all sensors from sensor.py

    * Add smoke text sensor

    * Make optional

    * Change all sensor to new structure

    * Improve sensor definitions

    * Ease state

    * Ease retrieve of the index

    * Rollback device_state_attributes

    * Revert battery changes

    * Remove const

    * Add LightSensor

    * Rollback icon logic in cover

    * Bugfixes

    * Add round for RSSI value

    Co-authored-by: Thibaut Etienne <thibaut@etienne.pw>

commit e7a4554
Author: Mick Vleeshouwer <Mick.Vleeshouwer@microsoft.com>
Date:   Tue Aug 10 01:56:39 2021 -0700

    Add binary sensors dynamically based on available device state (#505)

    * Refactor binary sensor

    * Make import relative

    * Style fix

    * Update custom_components/tahoma/__init__.py

    Co-authored-by: Thibaut <thibaut@etienne.pw>

    * Add extra binary sensors

    * Clean for final PR

    * Feedback applied

    Co-authored-by: Thibaut <thibaut@etienne.pw>

commit da5b26e
Author: Mick Vleeshouwer <Mick.Vleeshouwer@microsoft.com>
Date:   Tue Aug 10 01:34:16 2021 -0700

    Temporary bugfix for AwningValance (until this is fixed serverside) (#511)

    * Temporary bugfix for #486

    * Add note
  • Loading branch information
iMicknl committed Aug 10, 2021
1 parent d16f597 commit d146ad6
Show file tree
Hide file tree
Showing 11 changed files with 485 additions and 276 deletions.
11 changes: 10 additions & 1 deletion custom_components/tahoma/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import logging

from aiohttp import ClientError, ServerDisconnectedError
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
from homeassistant.components.scene import DOMAIN as SCENE
from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_EXCLUDE, CONF_PASSWORD, CONF_SOURCE, CONF_USERNAME
from homeassistant.core import HomeAssistant, ServiceCall
Expand Down Expand Up @@ -186,6 +188,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"update_listener": entry.add_update_listener(update_listener),
}

# Map Overkiz device to Home Assistant platform
for device in tahoma_coordinator.data.values():
platform = TAHOMA_DEVICE_TO_PLATFORM.get(
device.widget
Expand All @@ -202,7 +205,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if device.widget == HOMEKIT_STACK:
print_homekit_setup_code(device)

for platform in platforms:
supported_platforms = set(platforms.keys())

# Sensor and Binary Sensor will be added dynamically, based on the device states
supported_platforms.add(BINARY_SENSOR)
supported_platforms.add(SENSOR)

for platform in supported_platforms:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
Expand Down
196 changes: 107 additions & 89 deletions custom_components/tahoma/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,94 @@
"""Support for TaHoma binary sensors."""
from typing import Optional

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable

from homeassistant.components import binary_sensor
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_MOTION,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_SMOKE,
DOMAIN as BINARY_SENSOR,
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .tahoma_entity import OverkizEntity

CORE_ASSEMBLY_STATE = "core:AssemblyState"
CORE_BUTTON_STATE = "core:ButtonState"
CORE_CONTACT_STATE = "core:ContactState"
CORE_GAS_DETECTION_STATE = "core:GasDetectionState"
CORE_OCCUPANCY_STATE = "core:OccupancyState"
CORE_OPENING_STATE = "core:OpeningState"
CORE_OPEN_CLOSED_TILT_STATE = "core:OpenClosedTiltState"
CORE_RAIN_STATE = "core:RainState"
CORE_SMOKE_STATE = "core:SmokeState"
CORE_THREE_WAY_HANDLE_DIRECTION_STATE = "core:ThreeWayHandleDirectionState"
CORE_VIBRATION_STATE = "core:VibrationState"
CORE_WATER_DETECTION_STATE = "core:WaterDetectionState"

DEVICE_CLASS_BUTTON = "button"
DEVICE_CLASS_GAS = "gas"
DEVICE_CLASS_RAIN = "rain"
DEVICE_CLASS_WATER = "water"

ICON_WATER = "mdi:water"
ICON_WATER_OFF = "mdi:water-off"
ICON_WAVES = "mdi:waves"
ICON_WEATHER_RAINY = "mdi:weather-rainy"

IO_VIBRATION_DETECTED_STATE = "io:VibrationDetectedState"
from .coordinator import TahomaDataUpdateCoordinator
from .entity import OverkizEntity

STATE_OPEN = "open"
STATE_PERSON_INSIDE = "personInside"
STATE_DETECTED = "detected"
STATE_PRESSED = "pressed"

TAHOMA_BINARY_SENSOR_DEVICE_CLASSES = {
"AirFlowSensor": DEVICE_CLASS_GAS,
"CarButtonSensor": DEVICE_CLASS_BUTTON,
"ContactSensor": DEVICE_CLASS_OPENING,
"MotionSensor": DEVICE_CLASS_MOTION,
"OccupancySensor": DEVICE_CLASS_OCCUPANCY,
"RainSensor": DEVICE_CLASS_RAIN,
"SirenStatus": DEVICE_CLASS_OPENING,
"SmokeSensor": DEVICE_CLASS_SMOKE,
"WaterDetectionSensor": DEVICE_CLASS_WATER,
"WaterSensor": DEVICE_CLASS_WATER,
"WindowHandle": DEVICE_CLASS_OPENING,
}


@dataclass
class OverkizBinarySensorDescription(BinarySensorEntityDescription):
"""Class to describe a Overkiz binary sensor."""

is_on: Callable[[Any], Any] = lambda state: state


BINARY_SENSOR_DESCRIPTIONS = [
# RainSensor/RainSensor
OverkizBinarySensorDescription(
key="core:RainState",
name="Rain",
icon="mdi:weather-rainy",
is_on=lambda state: state == STATE_DETECTED,
),
# SmokeSensor/SmokeSensor
OverkizBinarySensorDescription(
key="core:SmokeState",
name="Smoke",
device_class=binary_sensor.DEVICE_CLASS_SMOKE,
is_on=lambda state: state == STATE_DETECTED,
),
# WaterSensor/WaterDetectionSensor
OverkizBinarySensorDescription(
key="core:WaterDetectionState",
name="Water",
icon="mdi:water",
is_on=lambda state: state == STATE_DETECTED,
),
# AirSensor/AirFlowSensor
OverkizBinarySensorDescription(
key="core:GasDetectionState",
name="Gas",
device_class=binary_sensor.DEVICE_CLASS_GAS,
is_on=lambda state: state == STATE_DETECTED,
),
# OccupancySensor/OccupancySensor
# OccupancySensor/MotionSensor
OverkizBinarySensorDescription(
key="core:OccupancyState",
name="Occupancy",
device_class=binary_sensor.DEVICE_CLASS_OCCUPANCY,
is_on=lambda state: state == STATE_PERSON_INSIDE,
),
# ContactSensor/WindowWithTiltSensor
OverkizBinarySensorDescription(
key="core:VibrationState",
name="Vibration",
device_class=binary_sensor.DEVICE_CLASS_VIBRATION,
is_on=lambda state: state == STATE_DETECTED,
),
# ContactSensor/ContactSensor
OverkizBinarySensorDescription(
key="core:ContactState",
name="Contact",
device_class=binary_sensor.DEVICE_CLASS_DOOR,
is_on=lambda state: state == STATE_OPEN,
),
# Unknown
OverkizBinarySensorDescription(
key="io:VibrationDetectedState",
name="Vibration",
device_class=binary_sensor.DEVICE_CLASS_VIBRATION,
is_on=lambda state: state == STATE_DETECTED,
),
]


async def async_setup_entry(
Expand All @@ -69,55 +99,43 @@ async def async_setup_entry(
"""Set up the TaHoma sensors from a config entry."""
data = hass.data[DOMAIN][entry.entry_id]
coordinator = data["coordinator"]
entities = []

key_supported_states = {
description.key: description for description in BINARY_SENSOR_DESCRIPTIONS
}

for device in coordinator.data.values():
for state in device.states:
description = key_supported_states.get(state.name)

if description:
entities.append(
TahomaBinarySensor(
device.deviceurl,
coordinator,
description,
)
)

entities = [
TahomaBinarySensor(device.deviceurl, coordinator)
for device in data["platforms"][BINARY_SENSOR]
]
async_add_entities(entities)


class TahomaBinarySensor(OverkizEntity, BinarySensorEntity):
"""Representation of a TaHoma Binary Sensor."""

def __init__(
self,
device_url: str,
coordinator: TahomaDataUpdateCoordinator,
description: OverkizBinarySensorDescription,
):
"""Initialize the device."""
super().__init__(device_url, coordinator)
self.entity_description = description

@property
def is_on(self):
"""Return the state of the sensor."""

return (
self.executor.select_state(
CORE_ASSEMBLY_STATE,
CORE_BUTTON_STATE,
CORE_CONTACT_STATE,
CORE_GAS_DETECTION_STATE,
CORE_OCCUPANCY_STATE,
CORE_OPENING_STATE,
CORE_OPEN_CLOSED_TILT_STATE,
CORE_RAIN_STATE,
CORE_SMOKE_STATE,
CORE_THREE_WAY_HANDLE_DIRECTION_STATE,
CORE_VIBRATION_STATE,
CORE_WATER_DETECTION_STATE,
IO_VIBRATION_DETECTED_STATE,
)
in [STATE_OPEN, STATE_PERSON_INSIDE, STATE_DETECTED, STATE_PRESSED]
)

@property
def device_class(self):
"""Return the class of the device."""
return TAHOMA_BINARY_SENSOR_DEVICE_CLASSES.get(
self.device.widget
) or TAHOMA_BINARY_SENSOR_DEVICE_CLASSES.get(self.device.ui_class)

@property
def icon(self) -> Optional[str]:
"""Return the icon to use in the frontend, if any."""
if self.device_class == DEVICE_CLASS_WATER:
if self.is_on:
return ICON_WATER
return ICON_WATER_OFF

icons = {DEVICE_CLASS_GAS: ICON_WAVES, DEVICE_CLASS_RAIN: ICON_WEATHER_RAINY}

return icons.get(self.device_class)
state = self.device.states[self.entity_description.key]
return self.entity_description.is_on(state)
50 changes: 24 additions & 26 deletions custom_components/tahoma/const.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""Constants for the TaHoma integration."""
from homeassistant.components.alarm_control_panel import DOMAIN as ALARM_CONTROL_PANEL
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR
from homeassistant.components.climate import DOMAIN as CLIMATE
from homeassistant.components.cover import DOMAIN as COVER
from homeassistant.components.light import DOMAIN as LIGHT
from homeassistant.components.lock import DOMAIN as LOCK
from homeassistant.components.sensor import DOMAIN as SENSOR
from homeassistant.components.switch import DOMAIN as SWITCH
from homeassistant.components.water_heater import DOMAIN as WATER_HEATER

Expand All @@ -20,13 +18,34 @@
IGNORED_TAHOMA_DEVICES = [
"ProtocolGateway",
"Pod",
# entries mapped to Sensor based on available states
"AirSensor",
"ConsumptionSensor",
"ElectricitySensor",
"GasSensor",
"GenericSensor",
"HumiditySensor",
"LightSensor",
"SunIntensitySensor",
"SunSensor",
"TemperatureSensor",
"ThermalEnergySensor",
"WaterSensor",
"WeatherSensor",
"WindSensor",
# entries mapped to Binary Sensor based on available states
"AirFlowSensor", # widgetName, uiClass is AirSensor (sensor)
"ContactSensor",
"MotionSensor",
"OccupancySensor",
"RainSensor",
"SmokeSensor",
"WaterDetectionSensor", # widgetName, uiClass is HumiditySensor (sensor)
]

# Used to map the Somfy widget and ui_class to the Home Assistant platform
TAHOMA_DEVICE_TO_PLATFORM = {
"AdjustableSlatsRollerShutter": COVER,
"AirFlowSensor": BINARY_SENSOR, # widgetName, uiClass is AirSensor (sensor)
"AirSensor": SENSOR,
"Alarm": ALARM_CONTROL_PANEL,
"AtlanticElectricalHeater": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
Expand All @@ -35,57 +54,36 @@
"AtlanticPassAPCHeatingAndCoolingZone": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"AtlanticPassAPCZoneControl": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"Awning": COVER,
"CarButtonSensor": BINARY_SENSOR,
"ConsumptionSensor": SENSOR,
"ContactSensor": BINARY_SENSOR,
"Curtain": COVER,
"DimmerExteriorHeating": CLIMATE, # widgetName, uiClass is ExteriorHeatingSystem (not supported)
"DomesticHotWaterProduction": WATER_HEATER, # widgetName, uiClass is WaterHeatingSystem (not supported)
"DomesticHotWaterTank": SWITCH, # widgetName, uiClass is WaterHeatingSystem (not supported)
"DoorLock": LOCK,
"ElectricitySensor": SENSOR,
"EvoHomeController": CLIMATE, # widgetName, uiClass is EvoHome (not supported)
"ExteriorScreen": COVER,
"ExteriorVenetianBlind": COVER,
"GarageDoor": COVER,
"GasSensor": SENSOR,
"Gate": COVER,
"GenericSensor": SENSOR,
"HeatingSetPoint": CLIMATE, # widgetName, uiClass is EvoHome (not supported)
"HitachiDHW": WATER_HEATER, # widgetName, uiClass is HitachiHeatingSystem (not supported)
"HitachiAirToWaterHeatingZone": CLIMATE, # widgetName, uiClass is HitachiHeatingSystem (not supported)
"HitachiAirToAirHeatPump": CLIMATE, # widgetName, uiClass is HitachiHeatingSystem (not supported)
"HumiditySensor": SENSOR,
"Light": LIGHT,
"LightSensor": SENSOR,
"MotionSensor": BINARY_SENSOR,
"MyFoxSecurityCamera": COVER, # widgetName, uiClass is Camera (not supported)
"OccupancySensor": BINARY_SENSOR,
"OnOff": SWITCH,
"Pergola": COVER,
"RainSensor": BINARY_SENSOR,
"RollerShutter": COVER,
"RTSGeneric": COVER, # widgetName, uiClass is Generic (not supported)
"Screen": COVER,
"Shutter": COVER,
"Siren": SWITCH,
"SirenStatus": BINARY_SENSOR, # widgetName, uiClass is Siren (switch)
"SmokeSensor": BINARY_SENSOR,
"SirenStatus": None, # widgetName, uiClass is Siren (switch)
"SomfyThermostat": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"StatelessExteriorHeating": CLIMATE, # widgetName, uiClass is ExteriorHeatingSystem.
"SunIntensitySensor": SENSOR,
"SunSensor": SENSOR,
"SwimmingPool": SWITCH,
"SwingingShutter": COVER,
"TemperatureSensor": SENSOR,
"ThermalEnergySensor": SENSOR,
"VenetianBlind": COVER,
"WaterDetectionSensor": BINARY_SENSOR, # widgetName, uiClass is HumiditySensor (sensor)
"WaterSensor": SENSOR,
"WeatherSensor": SENSOR,
"WindSensor": SENSOR,
"Window": COVER,
"WindowHandle": BINARY_SENSOR,
}

CORE_ON_OFF_STATE = "core:OnOffState"
Expand Down
6 changes: 4 additions & 2 deletions custom_components/tahoma/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,18 @@ async def async_setup_entry(
data = hass.data[DOMAIN][entry.entry_id]
coordinator = data["coordinator"]

# Includes fix for #486, which is waiting on Somfy back-end deployment
# Remove when DeploymentState will be returned for AwningValance
entities = [
Awning(device.deviceurl, coordinator)
for device in data["platforms"].get(COVER)
if device.ui_class == "Awning"
if device.ui_class == "Awning" and device.widget != "AwningValance"
]

entities += [
VerticalCover(device.deviceurl, coordinator)
for device in data["platforms"].get(COVER)
if device.ui_class != "Awning"
if device.ui_class != "Awning" or device.widget == "AwningValance"
]

async_add_entities(entities)
Expand Down
Loading

0 comments on commit d146ad6

Please sign in to comment.