Skip to content

Commit

Permalink
Bump dependency for HomematicIP Cloud (#43018)
Browse files Browse the repository at this point in the history
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
  • Loading branch information
SukramJ and balloob committed Nov 13, 2020
1 parent b17cf75 commit 9e65eb5
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 66 deletions.
36 changes: 31 additions & 5 deletions homeassistant/components/homematicip_cloud/__init__.py
@@ -1,11 +1,14 @@
"""Support for HomematicIP Cloud devices."""
import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_registry import async_entries_for_config_entry
from homeassistant.helpers.typing import ConfigType, HomeAssistantType

from .const import (
Expand All @@ -20,6 +23,8 @@
from .hap import HomematicipAuth, HomematicipHAP # noqa: F401
from .services import async_setup_services, async_unload_services

_LOGGER = logging.getLogger(__name__)

CONFIG_SCHEMA = vol.Schema(
{
vol.Optional(DOMAIN, default=[]): vol.All(
Expand Down Expand Up @@ -83,6 +88,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
return False

await async_setup_services(hass)
await async_remove_obsolete_entities(hass, entry, hap)

# Register on HA stop event to gracefully shutdown HomematicIP Cloud connection
hap.reset_connection_listener = hass.bus.async_listen_once(
Expand All @@ -91,16 +97,16 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool

# Register hap as device in registry.
device_registry = await dr.async_get_registry(hass)

home = hap.home
# Add the HAP name from configuration if set.
hapname = home.label if not home.name else f"{home.name} {home.label}"
hapname = home.label if home.label != entry.unique_id else f"Home-{home.label}"

device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, home.id)},
manufacturer="eQ-3",
# Add the name from config entry.
name=hapname,
model=home.modelType,
sw_version=home.currentAPVersion,
)
return True

Expand All @@ -113,3 +119,23 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo
await async_unload_services(hass)

return await hap.async_reset()


async def async_remove_obsolete_entities(
hass: HomeAssistantType, entry: ConfigEntry, hap: HomematicipHAP
):
"""Remove obsolete entities from entity registry."""

if hap.home.currentAPVersion < "2.2.12":
return

entity_registry = await er.async_get_registry(hass)
er_entries = async_entries_for_config_entry(entity_registry, entry.entry_id)
for er_entry in er_entries:
if er_entry.unique_id.startswith("HomematicipAccesspointStatus"):
entity_registry.async_remove(er_entry.entity_id)
continue

for hapid in hap.home.accessPointUpdateStates:
if er_entry.unique_id == f"HomematicipBatterySensor_{hapid}":
entity_registry.async_remove(er_entry.entity_id)
10 changes: 9 additions & 1 deletion homeassistant/components/homematicip_cloud/binary_sensor.py
Expand Up @@ -146,7 +146,15 @@ class HomematicipCloudConnectionSensor(HomematicipGenericEntity, BinarySensorEnt

def __init__(self, hap: HomematicipHAP) -> None:
"""Initialize the cloud connection sensor."""
super().__init__(hap, hap.home, "Cloud Connection")
super().__init__(hap, hap.home)

@property
def name(self) -> str:
"""Return the name cloud connection entity."""

name = "Cloud Connection"
# Add a prefix to the name if the homematic ip home has a name.
return name if not self._home.name else f"{self._home.name} {name}"

@property
def device_info(self) -> Dict[str, Any]:
Expand Down
5 changes: 3 additions & 2 deletions homeassistant/components/homematicip_cloud/hap.py
Expand Up @@ -240,8 +240,9 @@ async def get_hap(
home = AsyncHome(hass.loop, async_get_clientsession(hass))

home.name = name
home.label = "Access Point"
home.modelType = "HmIP-HAP"
# Use the title of the config entry as title for the home.
home.label = self.config_entry.title
home.modelType = "HomematicIP Cloud Home"

home.set_auth_token(authtoken)
try:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/homematicip_cloud/manifest.json
Expand Up @@ -3,7 +3,7 @@
"name": "HomematicIP Cloud",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/homematicip_cloud",
"requirements": ["homematicip==0.11.0"],
"requirements": ["homematicip==0.12.1"],
"codeowners": ["@SukramJ"],
"quality_scale": "platinum"
}
40 changes: 8 additions & 32 deletions homeassistant/components/homematicip_cloud/sensor.py
Expand Up @@ -6,6 +6,7 @@
AsyncFullFlushSwitchMeasuring,
AsyncHeatingThermostat,
AsyncHeatingThermostatCompact,
AsyncHomeControlAccessPoint,
AsyncLightSensor,
AsyncMotionDetectorIndoor,
AsyncMotionDetectorOutdoor,
Expand Down Expand Up @@ -39,7 +40,6 @@
from homeassistant.helpers.typing import HomeAssistantType

from . import DOMAIN as HMIPC_DOMAIN, HomematicipGenericEntity
from .generic_entity import ATTR_IS_GROUP, ATTR_MODEL_TYPE
from .hap import HomematicipHAP

ATTR_CURRENT_ILLUMINATION = "current_illumination"
Expand All @@ -63,8 +63,10 @@ async def async_setup_entry(
) -> None:
"""Set up the HomematicIP Cloud sensors from a config entry."""
hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id]
entities = [HomematicipAccesspointStatus(hap)]
entities = []
for device in hap.home.devices:
if isinstance(device, AsyncHomeControlAccessPoint):
entities.append(HomematicipAccesspointDutyCycle(hap, device))
if isinstance(device, (AsyncHeatingThermostat, AsyncHeatingThermostatCompact)):
entities.append(HomematicipHeatingThermostat(hap, device))
entities.append(HomematicipTemperatureSensor(hap, device))
Expand Down Expand Up @@ -119,23 +121,12 @@ async def async_setup_entry(
async_add_entities(entities)


class HomematicipAccesspointStatus(HomematicipGenericEntity):
class HomematicipAccesspointDutyCycle(HomematicipGenericEntity):
"""Representation of then HomeMaticIP access point."""

def __init__(self, hap: HomematicipHAP) -> None:
def __init__(self, hap: HomematicipHAP, device) -> None:
"""Initialize access point status entity."""
super().__init__(hap, device=hap.home, post="Duty Cycle")

@property
def device_info(self) -> Dict[str, Any]:
"""Return device specific attributes."""
# Adds a sensor to the existing HAP device
return {
"identifiers": {
# Serial numbers of Homematic IP device
(HMIPC_DOMAIN, self._home.id)
}
}
super().__init__(hap, device, post="Duty Cycle")

@property
def icon(self) -> str:
Expand All @@ -145,28 +136,13 @@ def icon(self) -> str:
@property
def state(self) -> float:
"""Return the state of the access point."""
return self._home.dutyCycle

@property
def available(self) -> bool:
"""Return if access point is available."""
return self._home.connected
return self._device.dutyCycleLevel

@property
def unit_of_measurement(self) -> str:
"""Return the unit this state is expressed in."""
return PERCENTAGE

@property
def device_state_attributes(self) -> Dict[str, Any]:
"""Return the state attributes of the access point."""
state_attr = super().device_state_attributes

state_attr[ATTR_MODEL_TYPE] = "HmIP-HAP"
state_attr[ATTR_IS_GROUP] = False

return state_attr


class HomematicipHeatingThermostat(HomematicipGenericEntity):
"""Representation of the HomematicIP heating thermostat."""
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Expand Up @@ -777,7 +777,7 @@ homeassistant-pyozw==0.1.10
homeconnect==0.6.3

# homeassistant.components.homematicip_cloud
homematicip==0.11.0
homematicip==0.12.1

# homeassistant.components.horizon
horimote==0.4.1
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Expand Up @@ -403,7 +403,7 @@ homeassistant-pyozw==0.1.10
homeconnect==0.6.3

# homeassistant.components.homematicip_cloud
homematicip==0.11.0
homematicip==0.12.1

# homeassistant.components.google
# homeassistant.components.remember_the_milk
Expand Down
2 changes: 1 addition & 1 deletion tests/components/homematicip_cloud/conftest.py
Expand Up @@ -56,7 +56,7 @@ def hmip_config_entry_fixture() -> config_entries.ConfigEntry:
config_entry = MockConfigEntry(
version=1,
domain=HMIPC_DOMAIN,
title=HAPID,
title="Home Test SN",
unique_id=HAPID,
data=entry_data,
source=SOURCE_IMPORT,
Expand Down
6 changes: 3 additions & 3 deletions tests/components/homematicip_cloud/helper.py
Expand Up @@ -136,9 +136,9 @@ class HomeTemplate(Home):
def __init__(self, connection=None, home_name="", test_devices=[], test_groups=[]):
"""Init template with connection."""
super().__init__(connection=connection)
self.label = "Access Point"
self.name = home_name
self.model_type = "HmIP-HAP"
self.label = "Home"
self.model_type = "HomematicIP Home"
self.init_json_state = None
self.test_devices = test_devices
self.test_groups = test_groups
Expand Down Expand Up @@ -196,7 +196,7 @@ def get_async_home_mock(self):
and sets required attributes.
"""
mock_home = Mock(
spec=AsyncHome, wraps=self, label="Access Point", modelType="HmIP-HAP"
spec=AsyncHome, wraps=self, label="Home", modelType="HomematicIP Home"
)
mock_home.__dict__.update(self.__dict__)

Expand Down
10 changes: 4 additions & 6 deletions tests/components/homematicip_cloud/test_binary_sensor.py
Expand Up @@ -38,12 +38,10 @@ async def test_manually_configured_platform(hass):
assert not hass.data.get(HMIPC_DOMAIN)


async def test_hmip_access_point_cloud_connection_sensor(
hass, default_mock_hap_factory
):
async def test_hmip_home_cloud_connection_sensor(hass, default_mock_hap_factory):
"""Test HomematicipCloudConnectionSensor."""
entity_id = "binary_sensor.access_point_cloud_connection"
entity_name = "Access Point Cloud Connection"
entity_id = "binary_sensor.cloud_connection"
entity_name = "Cloud Connection"
device_model = None
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
test_devices=[entity_name]
Expand All @@ -55,7 +53,7 @@ async def test_hmip_access_point_cloud_connection_sensor(

assert ha_state.state == STATE_ON

await async_manipulate_test_data(hass, hmip_device, "connected", False)
await async_manipulate_test_data(hass, mock_hap.home, "connected", False)

ha_state = hass.states.get(entity_id)
assert ha_state.state == STATE_OFF
Expand Down
4 changes: 2 additions & 2 deletions tests/components/homematicip_cloud/test_device.py
Expand Up @@ -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) == 231
assert len(mock_hap.hmip_device_by_entity_id) == 233


async def test_hmip_remove_device(hass, default_mock_hap_factory):
Expand Down Expand Up @@ -268,4 +268,4 @@ async def test_hmip_multi_area_device(hass, default_mock_hap_factory):

# get the hap
hap_device = device_registry.async_get(device.via_device_id)
assert hap_device.name == "Access Point"
assert hap_device.name == "Home"
5 changes: 4 additions & 1 deletion tests/components/homematicip_cloud/test_init.py
Expand Up @@ -145,6 +145,7 @@ async def test_unload_entry(hass):
instance.home.id = "1"
instance.home.modelType = "mock-type"
instance.home.name = "mock-name"
instance.home.label = "mock-label"
instance.home.currentAPVersion = "mock-ap-version"
instance.async_reset = AsyncMock(return_value=True)

Expand All @@ -158,7 +159,7 @@ async def test_unload_entry(hass):
assert config_entries[0].state == ENTRY_STATE_LOADED
await hass.config_entries.async_unload(config_entries[0].entry_id)
assert config_entries[0].state == ENTRY_STATE_NOT_LOADED
assert mock_hap.return_value.mock_calls[3][0] == "async_reset"
assert mock_hap.return_value.mock_calls[2][0] == "async_reset"
# entry is unloaded
assert hass.data[HMIPC_DOMAIN] == {}

Expand Down Expand Up @@ -187,6 +188,7 @@ async def test_setup_services_and_unload_services(hass):
instance.home.id = "1"
instance.home.modelType = "mock-type"
instance.home.name = "mock-name"
instance.home.label = "mock-label"
instance.home.currentAPVersion = "mock-ap-version"
instance.async_reset = AsyncMock(return_value=True)

Expand Down Expand Up @@ -220,6 +222,7 @@ async def test_setup_two_haps_unload_one_by_one(hass):
instance.home.id = "1"
instance.home.modelType = "mock-type"
instance.home.name = "mock-name"
instance.home.label = "mock-label"
instance.home.currentAPVersion = "mock-ap-version"
instance.async_reset = AsyncMock(return_value=True)

Expand Down
13 changes: 4 additions & 9 deletions tests/components/homematicip_cloud/test_sensor.py
Expand Up @@ -46,11 +46,11 @@ async def test_manually_configured_platform(hass):

async def test_hmip_accesspoint_status(hass, default_mock_hap_factory):
"""Test HomematicipSwitch."""
entity_id = "sensor.access_point_duty_cycle"
entity_name = "Access Point Duty Cycle"
device_model = None
entity_id = "sensor.home_control_access_point_duty_cycle"
entity_name = "HOME_CONTROL_ACCESS_POINT Duty Cycle"
device_model = "HmIP-HAP"
mock_hap = await default_mock_hap_factory.async_get_mock_hap(
test_devices=[entity_name]
test_devices=["HOME_CONTROL_ACCESS_POINT"]
)

ha_state, hmip_device = get_and_check_entity_basics(
Expand All @@ -60,11 +60,6 @@ async def test_hmip_accesspoint_status(hass, default_mock_hap_factory):
assert ha_state.state == "8.0"
assert ha_state.attributes[ATTR_UNIT_OF_MEASUREMENT] == PERCENTAGE

await async_manipulate_test_data(hass, hmip_device, "dutyCycle", 17.3)

ha_state = hass.states.get(entity_id)
assert ha_state.state == "17.3"


async def test_hmip_heating_thermostat(hass, default_mock_hap_factory):
"""Test HomematicipHeatingThermostat."""
Expand Down

0 comments on commit 9e65eb5

Please sign in to comment.