Add HmIP-HDM1 and HmIPW-DRD3 to Homematic IP Cloud (#43132)

* cleanup const.py

* Add wired multi dimmer HMIPW-DRD3 to Homematic IP Cloud

* Add HmIP-HDM1 to Homematic IP Cloud
This commit is contained in:
SukramJ 2020-12-03 06:56:05 +01:00 committed by GitHub
parent 69a438e2fc
commit 40408eb0eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 299 additions and 11 deletions

View File

@ -1,19 +1,30 @@
"""Constants for the HomematicIP Cloud component."""
import logging
from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN,
)
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
_LOGGER = logging.getLogger(".")
DOMAIN = "homematicip_cloud"
COMPONENTS = [
"alarm_control_panel",
"binary_sensor",
"climate",
"cover",
"light",
"sensor",
"switch",
"weather",
ALARM_CONTROL_PANEL_DOMAIN,
BINARY_SENSOR_DOMAIN,
CLIMATE_DOMAIN,
COVER_DOMAIN,
LIGHT_DOMAIN,
SENSOR_DOMAIN,
SWITCH_DOMAIN,
WEATHER_DOMAIN,
]
CONF_ACCESSPOINT = "accesspoint"

View File

@ -2,6 +2,7 @@
from typing import Optional
from homematicip.aio.device import (
AsyncBlindModule,
AsyncFullFlushBlind,
AsyncFullFlushShutter,
AsyncGarageDoorModuleTormatic,
@ -34,7 +35,9 @@ async def async_setup_entry(
hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = []
for device in hap.home.devices:
if isinstance(device, AsyncFullFlushBlind):
if isinstance(device, AsyncBlindModule):
entities.append(HomematicipBlindModule(hap, device))
elif isinstance(device, AsyncFullFlushBlind):
entities.append(HomematicipCoverSlats(hap, device))
elif isinstance(device, AsyncFullFlushShutter):
entities.append(HomematicipCoverShutter(hap, device))
@ -51,6 +54,82 @@ async def async_setup_entry(
async_add_entities(entities)
class HomematicipBlindModule(HomematicipGenericEntity, CoverEntity):
"""Representation of the HomematicIP blind module."""
@property
def current_cover_position(self) -> int:
"""Return current position of cover."""
if self._device.primaryShadingLevel is not None:
return int((1 - self._device.primaryShadingLevel) * 100)
return None
@property
def current_cover_tilt_position(self) -> int:
"""Return current tilt position of cover."""
if self._device.secondaryShadingLevel is not None:
return int((1 - self._device.secondaryShadingLevel) * 100)
return None
async def async_set_cover_position(self, **kwargs) -> None:
"""Move the cover to a specific position."""
position = kwargs[ATTR_POSITION]
# HmIP cover is closed:1 -> open:0
level = 1 - position / 100.0
await self._device.set_primary_shading_level(primaryShadingLevel=level)
async def async_set_cover_tilt_position(self, **kwargs) -> None:
"""Move the cover to a specific tilt position."""
position = kwargs[ATTR_TILT_POSITION]
# HmIP slats is closed:1 -> open:0
level = 1 - position / 100.0
await self._device.set_secondary_shading_level(
primaryShadingLevel=self._device.primaryShadingLevel,
secondaryShadingLevel=level,
)
@property
def is_closed(self) -> Optional[bool]:
"""Return if the cover is closed."""
if self._device.primaryShadingLevel is not None:
return self._device.primaryShadingLevel == HMIP_COVER_CLOSED
return None
async def async_open_cover(self, **kwargs) -> None:
"""Open the cover."""
await self._device.set_primary_shading_level(
primaryShadingLevel=HMIP_COVER_OPEN
)
async def async_close_cover(self, **kwargs) -> None:
"""Close the cover."""
await self._device.set_primary_shading_level(
primaryShadingLevel=HMIP_COVER_CLOSED
)
async def async_stop_cover(self, **kwargs) -> None:
"""Stop the device if in motion."""
await self._device.stop()
async def async_open_cover_tilt(self, **kwargs) -> None:
"""Open the slats."""
await self._device.set_secondary_shading_level(
primaryShadingLevel=self._device.primaryShadingLevel,
secondaryShadingLevel=HMIP_SLATS_OPEN,
)
async def async_close_cover_tilt(self, **kwargs) -> None:
"""Close the slats."""
await self._device.set_secondary_shading_level(
primaryShadingLevel=self._device.primaryShadingLevel,
secondaryShadingLevel=HMIP_SLATS_CLOSED,
)
async def async_stop_cover_tilt(self, **kwargs) -> None:
"""Stop the device if in motion."""
await self._device.stop()
class HomematicipCoverShutter(HomematicipGenericEntity, CoverEntity):
"""Representation of the HomematicIP cover shutter."""

View File

@ -8,6 +8,7 @@ from homematicip.aio.device import (
AsyncDimmer,
AsyncFullFlushDimmer,
AsyncPluggableDimmer,
AsyncWiredDimmer3,
)
from homematicip.base.enums import RGBColorState
from homematicip.base.functionalChannels import NotificationLightChannel
@ -51,6 +52,9 @@ async def async_setup_entry(
hap, device, device.bottomLightChannelIndex
)
)
elif isinstance(device, AsyncWiredDimmer3):
for channel in range(1, 4):
entities.append(HomematicipMultiDimmer(hap, device, channel=channel))
elif isinstance(
device,
(AsyncDimmer, AsyncPluggableDimmer, AsyncBrandDimmer, AsyncFullFlushDimmer),
@ -99,6 +103,45 @@ class HomematicipLightMeasuring(HomematicipLight):
return state_attr
class HomematicipMultiDimmer(HomematicipGenericEntity, LightEntity):
"""Representation of HomematicIP Cloud dimmer."""
def __init__(self, hap: HomematicipHAP, device, channel: int) -> None:
"""Initialize the dimmer light entity."""
super().__init__(hap, device, channel=channel)
@property
def is_on(self) -> bool:
"""Return true if dimmer is on."""
func_channel = self._device.functionalChannels[self._channel]
return func_channel.dimLevel is not None and func_channel.dimLevel > 0.0
@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return int(
(self._device.functionalChannels[self._channel].dimLevel or 0.0) * 255
)
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_BRIGHTNESS
async def async_turn_on(self, **kwargs) -> None:
"""Turn the dimmer on."""
if ATTR_BRIGHTNESS in kwargs:
await self._device.set_dim_level(
kwargs[ATTR_BRIGHTNESS] / 255.0, self._channel
)
else:
await self._device.set_dim_level(1, self._channel)
async def async_turn_off(self, **kwargs) -> None:
"""Turn the dimmer off."""
await self._device.set_dim_level(0, self._channel)
class HomematicipDimmer(HomematicipGenericEntity, LightEntity):
"""Representation of HomematicIP Cloud dimmer."""

View File

@ -160,6 +160,109 @@ async def test_hmip_cover_slats(hass, default_mock_hap_factory):
assert ha_state.state == STATE_UNKNOWN
async def test_hmip_blind_module(hass, default_mock_hap_factory):
"""Test HomematicipBlindModule."""
entity_id = "cover.sonnenschutz_balkontur"
entity_name = "Sonnenschutz Balkontür"
device_model = "HmIP-HDM1"
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
test_devices=[entity_name]
)
ha_state, hmip_device = get_and_check_entity_basics(
hass, mock_hap, entity_id, entity_name, device_model
)
assert ha_state.state == STATE_OPEN
assert ha_state.attributes[ATTR_CURRENT_POSITION] == 5
assert ha_state.attributes[ATTR_CURRENT_TILT_POSITION] == 100
service_call_counter = len(hmip_device.mock_calls)
await hass.services.async_call(
"cover", "open_cover_tilt", {"entity_id": entity_id}, blocking=True
)
assert len(hmip_device.mock_calls) == service_call_counter + 1
assert hmip_device.mock_calls[-1][0] == "set_secondary_shading_level"
assert hmip_device.mock_calls[-1][2] == {
"primaryShadingLevel": 0.94956,
"secondaryShadingLevel": 0,
}
await async_manipulate_test_data(hass, hmip_device, "primaryShadingLevel", 0)
await async_manipulate_test_data(hass, hmip_device, "secondaryShadingLevel", 0)
await hass.services.async_call(
"cover", "open_cover", {"entity_id": entity_id}, blocking=True
)
assert len(hmip_device.mock_calls) == service_call_counter + 4
assert hmip_device.mock_calls[-1][0] == "set_primary_shading_level"
assert hmip_device.mock_calls[-1][2] == {"primaryShadingLevel": 0}
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OPEN
assert ha_state.attributes[ATTR_CURRENT_POSITION] == 100
assert ha_state.attributes[ATTR_CURRENT_TILT_POSITION] == 100
await async_manipulate_test_data(hass, hmip_device, "primaryShadingLevel", 0.5)
await async_manipulate_test_data(hass, hmip_device, "secondaryShadingLevel", 0.5)
await hass.services.async_call(
"cover",
"set_cover_tilt_position",
{"entity_id": entity_id, "tilt_position": "50"},
blocking=True,
)
await hass.services.async_call(
"cover",
"set_cover_position",
{"entity_id": entity_id, "position": "50"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 8
assert hmip_device.mock_calls[-1][0] == "set_primary_shading_level"
assert hmip_device.mock_calls[-1][2] == {"primaryShadingLevel": 0.5}
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OPEN
assert ha_state.attributes[ATTR_CURRENT_POSITION] == 50
assert ha_state.attributes[ATTR_CURRENT_TILT_POSITION] == 50
await async_manipulate_test_data(hass, hmip_device, "primaryShadingLevel", 1)
await async_manipulate_test_data(hass, hmip_device, "secondaryShadingLevel", 1)
await hass.services.async_call(
"cover", "close_cover", {"entity_id": entity_id}, blocking=True
)
await hass.services.async_call(
"cover", "close_cover_tilt", {"entity_id": entity_id}, blocking=True
)
assert len(hmip_device.mock_calls) == service_call_counter + 12
assert hmip_device.mock_calls[-1][0] == "set_secondary_shading_level"
assert hmip_device.mock_calls[-1][2] == {
"primaryShadingLevel": 1,
"secondaryShadingLevel": 1,
}
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_CLOSED
assert ha_state.attributes[ATTR_CURRENT_POSITION] == 0
assert ha_state.attributes[ATTR_CURRENT_TILT_POSITION] == 0
await hass.services.async_call(
"cover", "stop_cover", {"entity_id": entity_id}, blocking=True
)
assert len(hmip_device.mock_calls) == service_call_counter + 13
assert hmip_device.mock_calls[-1][0] == "stop"
assert hmip_device.mock_calls[-1][1] == ()
await async_manipulate_test_data(hass, hmip_device, "secondaryShadingLevel", None)
ha_state = hass.states.get(entity_id)
assert not ha_state.attributes.get(ATTR_CURRENT_TILT_POSITION)
await async_manipulate_test_data(hass, hmip_device, "primaryShadingLevel", None)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_UNKNOWN
async def test_hmip_garage_door_tormatic(hass, default_mock_hap_factory):
"""Test HomematicipCoverShutte."""
entity_id = "cover.garage_door_module"

View File

@ -22,7 +22,7 @@ async def test_hmip_load_all_supported_devices(hass, default_mock_hap_factory):
test_devices=None, test_groups=None
)
assert len(mock_hap.hmip_device_by_entity_id) == 233
assert len(mock_hap.hmip_device_by_entity_id) == 236
async def test_hmip_remove_device(hass, default_mock_hap_factory):

View File

@ -245,3 +245,55 @@ async def test_hmip_light_measuring(hass, default_mock_hap_factory):
await async_manipulate_test_data(hass, hmip_device, "on", False)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
async def test_hmip_wired_multi_dimmer(hass, default_mock_hap_factory):
"""Test HomematicipMultiDimmer."""
entity_id = "light.raumlich_kuche"
entity_name = "Raumlich (Küche)"
device_model = "HmIPW-DRD3"
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
test_devices=["Wired Dimmaktor 3-fach (Küche)"]
)
ha_state, hmip_device = get_and_check_entity_basics(
hass, mock_hap, entity_id, entity_name, device_model
)
assert ha_state.state == STATE_OFF
service_call_counter = len(hmip_device.mock_calls)
await hass.services.async_call(
"light", "turn_on", {"entity_id": entity_id}, blocking=True
)
assert hmip_device.mock_calls[-1][0] == "set_dim_level"
assert hmip_device.mock_calls[-1][1] == (1, 1)
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": entity_id, "brightness": "100"},
blocking=True,
)
assert len(hmip_device.mock_calls) == service_call_counter + 2
assert hmip_device.mock_calls[-1][0] == "set_dim_level"
assert hmip_device.mock_calls[-1][1] == (0.39215686274509803, 1)
await async_manipulate_test_data(hass, hmip_device, "dimLevel", 1, channel=1)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_ON
assert ha_state.attributes[ATTR_BRIGHTNESS] == 255
await hass.services.async_call(
"light", "turn_off", {"entity_id": entity_id}, blocking=True
)
assert len(hmip_device.mock_calls) == service_call_counter + 4
assert hmip_device.mock_calls[-1][0] == "set_dim_level"
assert hmip_device.mock_calls[-1][1] == (0, 1)
await async_manipulate_test_data(hass, hmip_device, "dimLevel", 0, channel=1)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
await async_manipulate_test_data(hass, hmip_device, "dimLevel", None, channel=1)
ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
assert not ha_state.attributes.get(ATTR_BRIGHTNESS)

View File

@ -233,7 +233,7 @@
"profileMode": "AUTOMATIC",
"secondaryCloseAdjustable": false,
"secondaryOpenAdjustable": false,
"secondaryShadingLevel": null,
"secondaryShadingLevel": 0,
"secondaryShadingStateType": "NOT_EXISTENT",
"shadingDriveVersion": null,
"shadingPackagePosition": "TOP",