Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
feat: add Support for more Garage door operators and modernize code (#29
Browse files Browse the repository at this point in the history
)

* remove deprecated typing library

'typing.List' is deprecated, use 'list' instead
pylint(deprecated-typing-alias)

* fix const import

In binary_sensor.py the import of "const" was with an improper path.

* Check if attributes are editable to find features

There are garage operators, that have a position attribute which is not editable. In that case, they show position, but can not be operated by set_position.

Also tackle some deprecation notices.

* Add more switch Attribute Types

Adding all Impulse type switching attributes from pymee, to discover more switches on devices.
Naming changed, so that special impulses always get named by their type..

tackled some deprecation messages.

* Improve naming of entities

If a sensor neither has a name nor a device class, name it after it's AttributeType string

* remove unused import

* Update to latest HA version

* revert back to original

* Update to latest blueprint file

* Update to latest  blueprint file

* Fix error in substring generation

* remove debug log statement

* revert devcontainer setting back to original

* Log all data about devices + comments added

* change try to if, to get rid of lint warning

* Update code for deprecations and linter warnings

* Add UP_DOWN attribute to sensors

Closes #26
  • Loading branch information
Taraman17 committed Mar 8, 2023
1 parent d731476 commit 3865dc4
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 124 deletions.
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
"problemMatcher": []
}
]
}
}
28 changes: 16 additions & 12 deletions custom_components/homee/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up homee from a config entry."""
# Create the Homee api object using host, user and password from the config
homee = Homee(
entry.data[CONF_HOST],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD]
entry.data[CONF_HOST], entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]
)

# Migrate initial options
Expand All @@ -58,10 +56,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
# Log info about nodes, to facilitate recognition of unknown nodes.
for node in homee.nodes:
_LOGGER.info(
"Found node %s, with note %s and Attributes %s",
"Found node %s, with following Data: %s",
node.name,
node.note,
node.attributes_raw,
node._data,
)

hass.data[DOMAIN][entry.entry_id] = homee
Expand All @@ -88,7 +85,7 @@ def handle_set_value(call: ServiceCall):
name=homee.settings.homee_name,
model="homee",
sw_version=homee.settings.version,
hw_version="TBD"
hw_version="TBD",
)

# Forward entry setup to the platforms
Expand Down Expand Up @@ -124,7 +121,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):

return unload_ok


class HomeeNodeEntity:
"""Representation of a Node in Homee."""

def __init__(self, node: HomeeNode, entity: Entity, entry: ConfigEntry) -> None:
"""Initialize the wrapper using a HomeeNode and target entity."""
self._node = node
Expand All @@ -150,20 +150,24 @@ async def async_will_remove_from_hass(self):

@property
def device_info(self):
try:
"""Holds the available information about the device"""
if self.has_attribute(AttributeType.SOFTWARE_REVISION):
sw_version = self.attribute(AttributeType.SOFTWARE_REVISION)
except:
sw_version = "undefined"
else:
sw_version = "undefined"

return {
"identifiers": {
# Serial numbers are unique identifiers within a specific domain
(DOMAIN, self._node.id)
},
"name": self._node.name,
"default_manufacturer": "unknown",
"default_model": get_attribute_for_enum(NodeProfile, self._homee_data["profile"]),
"default_model": get_attribute_for_enum(
NodeProfile, self._homee_data["profile"]
),
"sw_version": sw_version,
"via_device": (DOMAIN, self._entry.entry_id)
"via_device": (DOMAIN, self._entry.entry_id),
}

@property
Expand Down
29 changes: 13 additions & 16 deletions custom_components/homee/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
"""The homee binary sensor platform."""

from custom_components.homee.const import CONF_DOOR_GROUPS, CONF_WINDOW_GROUPS
import logging

import homeassistant
from homeassistant.core import HomeAssistant
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_DOOR,
DEVICE_CLASS_LOCK,
DEVICE_CLASS_OPENING,
DEVICE_CLASS_PLUG,
BinarySensorEntity,
DEVICE_CLASS_WINDOW,
BinarySensorDeviceClass,
)
from homeassistant.config_entries import ConfigEntry
from pymee.const import AttributeType, NodeProfile
from pymee.model import HomeeNode

from . import HomeeNodeEntity, helpers
from .const import CONF_DOOR_GROUPS, CONF_WINDOW_GROUPS

_LOGGER = logging.getLogger(__name__)


def get_device_class(node: HomeeNodeEntity) -> int:
"""Determine the device class a homee node based on the available attributes."""
device_class = DEVICE_CLASS_OPENING
device_class = BinarySensorDeviceClass.OPENING
state_attr = AttributeType.OPEN_CLOSE

if node.has_attribute(AttributeType.ON_OFF):
state_attr = AttributeType.ON_OFF
device_class = DEVICE_CLASS_PLUG
device_class = BinarySensorDeviceClass.PLUG

if node.has_attribute(AttributeType.LOCK_STATE):
state_attr = AttributeType.LOCK_STATE
device_class = DEVICE_CLASS_LOCK
device_class = BinarySensorDeviceClass.LOCK

return (device_class, state_attr)

Expand All @@ -47,7 +43,7 @@ def is_binary_sensor_node(node: HomeeNode):
]


async def async_setup_entry(hass, config_entry, async_add_devices):
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_devices):
"""Add the homee platform for the binary sensor integration."""

devices = []
Expand All @@ -59,21 +55,22 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
async_add_devices(devices)


async def async_unload_entry(hass: homeassistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
return True


class HomeeBinarySensor(HomeeNodeEntity, BinarySensorEntity):
"""Representation of a homee binary sensor device."""

_attr_has_entity_name = True
_attr_name = None

def __init__(self, node: HomeeNode, entry: ConfigEntry):
def __init__(self, node: HomeeNode, entry: ConfigEntry) -> None:
"""Initialize a homee binary sensor entity."""
HomeeNodeEntity.__init__(self, node, self, entry)

self._device_class = DEVICE_CLASS_OPENING
self._device_class = BinarySensorDeviceClass.OPENING
self._state_attr = AttributeType.OPEN_CLOSE

self._configure_device_class()
Expand All @@ -90,12 +87,12 @@ def _configure_device_class(self):
str(group.id) in self._entry.options.get(CONF_WINDOW_GROUPS, [])
for group in self._node.groups
):
self._device_class = DEVICE_CLASS_WINDOW
self._device_class = BinarySensorDeviceClass.WINDOW
elif any(
str(group.id) in self._entry.options.get(CONF_DOOR_GROUPS, [])
for group in self._node.groups
):
self._device_class = DEVICE_CLASS_DOOR
self._device_class = BinarySensorDeviceClass.DOOR

@property
def is_on(self):
Expand Down
29 changes: 16 additions & 13 deletions custom_components/homee/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,43 @@

import logging

import homeassistant
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import (
HVACMode,
ATTR_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_RANGE,
ClimateEntityFeature,
ClimateEntity,
)
from homeassistant.components.climate.const import HVAC_MODE_HEAT
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.const import UnitOfTemperature
from pymee.const import AttributeType, NodeProfile
from pymee.model import HomeeNode

from . import HomeeNodeEntity, helpers

_LOGGER = logging.getLogger(__name__)

HOMEE_UNIT_TO_HA_UNIT = {"°C": TEMP_CELSIUS, "°F": TEMP_FAHRENHEIT}
HOMEE_UNIT_TO_HA_UNIT = {
"°C": UnitOfTemperature.CELSIUS,
"°F": UnitOfTemperature.FAHRENHEIT,
}


def get_climate_features(node: HomeeNodeEntity, default=0) -> int:
"""Determine the supported climate features of a homee node based on the available attributes."""
features = default

if node.has_attribute(AttributeType.TARGET_TEMPERATURE):
features |= SUPPORT_TARGET_TEMPERATURE
features |= ClimateEntityFeature.TARGET_TEMPERATURE
if node.has_attribute(AttributeType.TARGET_TEMPERATURE_LOW) and node.has_attribute(
AttributeType.TARGET_TEMPERATURE_HIGH
):
features |= SUPPORT_TARGET_TEMPERATURE_RANGE
features |= ClimateEntityFeature.TARGET_TEMPERATURE_RANGE

return features


async def async_setup_entry(hass, config_entry, async_add_devices):
async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_devices):
"""Add the homee platform for the light integration."""
# homee: Homee = hass.data[DOMAIN][config_entry.entry_id]

Expand All @@ -49,7 +51,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
async_add_devices(devices)


async def async_unload_entry(hass: homeassistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
return True

Expand All @@ -65,10 +67,11 @@ def is_climate_node(node: HomeeNode):

class HomeeClimate(HomeeNodeEntity, ClimateEntity):
"""Representation of a homee climate device."""

_attr_has_entity_name = True
_attr_name = None

def __init__(self, node: HomeeNode, entry: ConfigEntry):
def __init__(self, node: HomeeNode, entry: ConfigEntry) -> None:
"""Initialize a homee climate entity."""
HomeeNodeEntity.__init__(self, node, self, entry)
self._supported_features = get_climate_features(self)
Expand All @@ -86,12 +89,12 @@ def temperature_unit(self) -> str:
@property
def hvac_modes(self):
"""Return the available hvac operation modes."""
return [HVAC_MODE_HEAT]
return [HVACMode.HEAT]

@property
def hvac_mode(self):
"""Return the hvac operation mode."""
return HVAC_MODE_HEAT
return HVACMode.HEAT

@property
def current_temperature(self):
Expand Down
68 changes: 37 additions & 31 deletions custom_components/homee/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@

import logging

import homeassistant
from homeassistant.core import HomeAssistant
from homeassistant.components.cover import (
ATTR_POSITION,
SUPPORT_OPEN,
SUPPORT_CLOSE,
SUPPORT_STOP,
SUPPORT_SET_POSITION,
CoverEntityFeature,
CoverEntity,
CoverDeviceClass,
)
from typing import Any, cast
from typing import cast
from homeassistant.config_entries import ConfigEntry
from pymee.const import AttributeType, NodeProfile
from pymee.model import HomeeNode
Expand All @@ -25,16 +23,30 @@ def get_cover_features(node: HomeeNodeEntity, default=0) -> int:
"""Determine the supported cover features of a homee node based on the available attributes."""
features = default

if node.has_attribute(AttributeType.UP_DOWN) and node.has_attribute(AttributeType.MANUAL_OPERATION):
features |= SUPPORT_OPEN
features |= SUPPORT_CLOSE
features |= SUPPORT_STOP
if node.has_attribute(AttributeType.POSITION):
features |= SUPPORT_SET_POSITION
for attribute in node.attributes:
if attribute.type == AttributeType.UP_DOWN:
if attribute.editable:
features |= CoverEntityFeature.OPEN
features |= CoverEntityFeature.CLOSE
features |= CoverEntityFeature.STOP

if attribute.type == AttributeType.POSITION:
if attribute.editable:
features |= CoverEntityFeature.SET_POSITION

return features


async def async_setup_entry(hass, config_entry, async_add_devices):
def get_device_class(node: HomeeNode) -> int:
"""Determine the device class a homee node based on the node profile."""
if node.profile == NodeProfile.GARAGE_DOOR_OPERATOR:
return CoverDeviceClass.GARAGE

if node.profile == NodeProfile.SHUTTER_POSITION_SWITCH:
return CoverDeviceClass.SHUTTER


async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_devices):
"""Add the homee platform for the cover integration."""
# homee: Homee = hass.data[DOMAIN][config_entry.entry_id]

Expand All @@ -47,7 +59,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
async_add_devices(devices)


async def async_unload_entry(hass: homeassistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
return True

Expand All @@ -58,19 +70,20 @@ def is_cover_node(node: HomeeNode):
NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH,
NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH_WITHOUT_SLAT_POSITION,
NodeProfile.GARAGE_DOOR_OPERATOR,
NodeProfile.GARAGE_DOOR_IMPULSE_OPERATOR,
NodeProfile.SHUTTER_POSITION_SWITCH
NodeProfile.SHUTTER_POSITION_SWITCH,
]


class HomeeCover(HomeeNodeEntity, CoverEntity):
"""Representation of a homee cover device."""

_attr_has_entity_name = True

def __init__(self, node: HomeeNode, entry: ConfigEntry):
def __init__(self, node: HomeeNode, entry: ConfigEntry) -> None:
"""Initialize a homee cover entity."""
HomeeNodeEntity.__init__(self, node, self, entry)
self._supported_features = get_cover_features(self)
self._supported_features = get_cover_features(node)
self._device_class = get_device_class(node)

self._unique_id = f"{self._node.id}-cover"

Expand Down Expand Up @@ -106,25 +119,18 @@ def is_closed(self):

async def async_open_cover(self, **kwargs):
"""Open the cover."""
await self.async_set_value(
AttributeType.UP_DOWN, 0
)
await self.async_set_value(AttributeType.UP_DOWN, 0)

async def async_close_cover(self, **kwargs):
"""Close cover."""
await self.async_set_value(
AttributeType.UP_DOWN, 1
)
await self.async_set_value(AttributeType.UP_DOWN, 1)

async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = 100 - cast(int, kwargs[ATTR_POSITION])
await self.async_set_value(
AttributeType.POSITION, position
)
if CoverEntityFeature.SET_POSITION in self._supported_features:
position = 100 - cast(int, kwargs[ATTR_POSITION])
await self.async_set_value(AttributeType.POSITION, position)

async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
await self.async_set_value(
AttributeType.UP_DOWN, 2
)
await self.async_set_value(AttributeType.UP_DOWN, 2)

0 comments on commit 3865dc4

Please sign in to comment.