Bump dependency for HomematicIP Cloud (#43018)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
pull/43356/head
SukramJ 2020-11-12 10:33:01 +01:00 committed by Paulus Schoutsen
parent b17cf75583
commit 9e65eb53e4
14 changed files with 320 additions and 66 deletions

View File

@ -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 (
@ -20,6 +23,8 @@ from .generic_entity import HomematicipGenericEntity # noqa: F401
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(
@ -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(
@ -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
@ -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)

View File

@ -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]:

View File

@ -240,8 +240,9 @@ class HomematicipHAP:
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:

View File

@ -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"
}

View File

@ -6,6 +6,7 @@ from homematicip.aio.device import (
AsyncFullFlushSwitchMeasuring,
AsyncHeatingThermostat,
AsyncHeatingThermostatCompact,
AsyncHomeControlAccessPoint,
AsyncLightSensor,
AsyncMotionDetectorIndoor,
AsyncMotionDetectorOutdoor,
@ -39,7 +40,6 @@ from homeassistant.const import (
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"
@ -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))
@ -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:
@ -145,28 +136,13 @@ class HomematicipAccesspointStatus(HomematicipGenericEntity):
@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."""

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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
@ -196,7 +196,7 @@ class HomeTemplate(Home):
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__)

View File

@ -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]
@ -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

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) == 231
assert len(mock_hap.hmip_device_by_entity_id) == 233
async def test_hmip_remove_device(hass, default_mock_hap_factory):
@ -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"

View File

@ -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)
@ -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] == {}
@ -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)
@ -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)

View File

@ -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(
@ -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."""

View File

@ -14,6 +14,248 @@
}
},
"devices": {
"3014F711A000000BAD0CAAAA": {
"availableFirmwareVersion": "2.2.18",
"connectionType": "HMIP_LAN",
"firmwareVersion": "2.2.18",
"firmwareVersionInteger": 131602,
"functionalChannels": {
"0": {
"accessPointPriority": 0,
"busConfigMismatch": null,
"carrierSenseLevel": 2.0,
"coProFaulty": false,
"coProRestartNeeded": false,
"coProUpdateFailure": false,
"configPending": false,
"deviceId": "3014F711A000000BAD0CAAAA",
"deviceOverheated": false,
"deviceOverloaded": false,
"devicePowerFailureDetected": false,
"deviceUndervoltage": false,
"dutyCycle": false,
"dutyCycleLevel": 8.0,
"functionalChannelType": "ACCESS_CONTROLLER_CHANNEL",
"groupIndex": 0,
"groups": [],
"index": 0,
"label": "",
"lowBat": null,
"multicastRoutingEnabled": false,
"powerShortCircuit": null,
"routerModuleEnabled": false,
"routerModuleSupported": false,
"rssiDeviceValue": null,
"rssiPeerValue": null,
"shortCircuitDataLine": null,
"signalBrightness": 1.0,
"supportedOptionalFeatures": {
"IFeatureBusConfigMismatch": false,
"IFeatureDeviceCoProError": false,
"IFeatureDeviceCoProRestart": false,
"IFeatureDeviceCoProUpdate": false,
"IFeatureDeviceIdentify": false,
"IFeatureDeviceOverheated": false,
"IFeatureDeviceOverloaded": false,
"IFeatureDevicePowerFailure": false,
"IFeatureDeviceTemperatureOutOfRange": false,
"IFeatureDeviceUndervoltage": false,
"IFeatureMulticastRouter": false,
"IFeaturePowerShortCircuit": false,
"IFeatureRssiValue": false,
"IFeatureShortCircuitDataLine": false,
"IOptionalFeatureDutyCycle": true,
"IOptionalFeatureLowBat": false
},
"temperatureOutOfRange": false,
"unreach": false
}
},
"homeId": "00000000-0000-0000-0000-000000000001",
"id": "3014F711A000000BAD0CAAAA",
"label": "AP1",
"lastStatusUpdate": 1604522238580,
"liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED",
"manufacturerCode": 1,
"modelId": 270,
"modelType": "HmIP-HAP",
"oem": "eQ-3",
"permanentlyReachable": true,
"serializedGlobalTradeItemNumber": "3014F711A000000BAD0CAAAA",
"type": "HOME_CONTROL_ACCESS_POINT",
"updateState": "BACKGROUND_UPDATE_NOT_SUPPORTED"
},
"3014F711A000000BAD0C0DED": {
"availableFirmwareVersion": "2.2.18",
"connectionType": "HMIP_LAN",
"firmwareVersion": "2.2.18",
"firmwareVersionInteger": 131602,
"functionalChannels": {
"0": {
"accessPointPriority": 1,
"busConfigMismatch": null,
"carrierSenseLevel": 2.0,
"coProFaulty": false,
"coProRestartNeeded": false,
"coProUpdateFailure": false,
"configPending": false,
"deviceId": "3014F711A000000BAD0C0DED",
"deviceOverheated": false,
"deviceOverloaded": false,
"devicePowerFailureDetected": false,
"deviceUndervoltage": false,
"dutyCycle": false,
"dutyCycleLevel": 8.0,
"functionalChannelType": "ACCESS_CONTROLLER_CHANNEL",
"groupIndex": 0,
"groups": [],
"index": 0,
"label": "",
"lowBat": null,
"multicastRoutingEnabled": false,
"powerShortCircuit": null,
"routerModuleEnabled": false,
"routerModuleSupported": false,
"rssiDeviceValue": null,
"rssiPeerValue": null,
"shortCircuitDataLine": null,
"signalBrightness": 1.0,
"supportedOptionalFeatures": {
"IFeatureBusConfigMismatch": false,
"IFeatureDeviceCoProError": false,
"IFeatureDeviceCoProRestart": false,
"IFeatureDeviceCoProUpdate": false,
"IFeatureDeviceIdentify": false,
"IFeatureDeviceOverheated": false,
"IFeatureDeviceOverloaded": false,
"IFeatureDevicePowerFailure": false,
"IFeatureDeviceTemperatureOutOfRange": false,
"IFeatureDeviceUndervoltage": false,
"IFeatureMulticastRouter": false,
"IFeaturePowerShortCircuit": false,
"IFeatureRssiValue": false,
"IFeatureShortCircuitDataLine": false,
"IOptionalFeatureDutyCycle": true,
"IOptionalFeatureLowBat": false
},
"temperatureOutOfRange": false,
"unreach": false
}
},
"homeId": "00000000-0000-0000-0000-000000000001",
"id": "3014F711A000000BAD0C0DED",
"label": "HOME_CONTROL_ACCESS_POINT",
"lastStatusUpdate": 1604522238580,
"liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED",
"manufacturerCode": 1,
"modelId": 270,
"modelType": "HmIP-HAP",
"oem": "eQ-3",
"permanentlyReachable": true,
"serializedGlobalTradeItemNumber": "3014F711A000000BAD0C0DED",
"type": "HOME_CONTROL_ACCESS_POINT",
"updateState": "BACKGROUND_UPDATE_NOT_SUPPORTED"
},
"3014F71100BLIND_MODULE00": {
"availableFirmwareVersion": "0.0.0",
"connectionType": "HMIP_RF",
"firmwareVersion": "1.0.4",
"firmwareVersionInteger": 65540,
"functionalChannels": {
"0": {
"busConfigMismatch": null,
"coProFaulty": false,
"coProRestartNeeded": false,
"coProUpdateFailure": false,
"configPending": false,
"deviceId": "3014F71100BLIND_MODULE00",
"deviceOverheated": false,
"deviceOverloaded": false,
"devicePowerFailureDetected": false,
"deviceUndervoltage": false,
"dutyCycle": false,
"functionalChannelType": "DEVICE_BASE",
"groupIndex": 0,
"groups": [
],
"index": 0,
"label": "",
"lowBat": false,
"multicastRoutingEnabled": false,
"powerShortCircuit": null,
"routerModuleEnabled": false,
"routerModuleSupported": false,
"rssiDeviceValue": -85,
"rssiPeerValue": -78,
"shortCircuitDataLine": null,
"supportedOptionalFeatures": {
"IFeatureBusConfigMismatch": false,
"IFeatureDeviceCoProError": false,
"IFeatureDeviceCoProRestart": false,
"IFeatureDeviceCoProUpdate": false,
"IFeatureDeviceIdentify": false,
"IFeatureDeviceOverheated": false,
"IFeatureDeviceOverloaded": false,
"IFeatureDevicePowerFailure": false,
"IFeatureDeviceTemperatureOutOfRange": false,
"IFeatureDeviceUndervoltage": false,
"IFeatureMulticastRouter": false,
"IFeaturePowerShortCircuit": false,
"IFeatureRssiValue": true,
"IFeatureShortCircuitDataLine": false,
"IOptionalFeatureDutyCycle": true,
"IOptionalFeatureLowBat": true
},
"temperatureOutOfRange": false,
"unreach": false
},
"1": {
"automationDriveSpeed": "SLOW_SPEED",
"deviceId": "3014F71100BLIND_MODULE00",
"favoritePrimaryShadingPosition": 0.5,
"favoriteSecondaryShadingPosition": 0.5,
"functionalChannelType": "SHADING_CHANNEL",
"groupIndex": 1,
"groups": [
],
"identifyOemSupported": true,
"index": 1,
"label": "",
"manualDriveSpeed": "NOMINAL_SPEED",
"previousPrimaryShadingLevel": null,
"previousSecondaryShadingLevel": null,
"primaryCloseAdjustable": true,
"primaryOpenAdjustable": true,
"primaryShadingLevel": 0.94956,
"primaryShadingStateType": "POSITION_USED",
"processing": false,
"productId": 10,
"profileMode": "AUTOMATIC",
"secondaryCloseAdjustable": false,
"secondaryOpenAdjustable": false,
"secondaryShadingLevel": null,
"secondaryShadingStateType": "NOT_EXISTENT",
"shadingDriveVersion": null,
"shadingPackagePosition": "TOP",
"shadingPositionAdjustmentActive": null,
"shadingPositionAdjustmentClientId": null,
"userDesiredProfileMode": "AUTOMATIC"
}
},
"homeId": "00000000-0000-0000-0000-000000000001",
"id": "3014F71100BLIND_MODULE00",
"label": "Sonnenschutz Balkont\u00fcr",
"lastStatusUpdate": 1600002124559,
"liveUpdateState": "LIVE_UPDATE_NOT_SUPPORTED",
"manufacturerCode": 7,
"modelId": 1,
"modelType": "HmIP-HDM1",
"oem": "HunterDouglas",
"permanentlyReachable": true,
"serializedGlobalTradeItemNumber": "3014F71100BLIND_MODULE00",
"type": "BLIND_MODULE",
"updateState": "UP_TO_DATE"
},
"3014F7110TILTVIBRATIONSENSOR": {
"availableFirmwareVersion": "0.0.0",
"connectionType": "HMIP_RF",
@ -7307,6 +7549,11 @@
},
"home": {
"accessPointUpdateStates": {
"3014F711A000000BAD0CAAAA": {
"accessPointUpdateState": "UP_TO_DATE",
"successfulUpdateTimestamp": 0,
"updateStateChangedTimestamp": 0
},
"3014F711A000000BAD0C0DED": {
"accessPointUpdateState": "UP_TO_DATE",
"successfulUpdateTimestamp": 0,
@ -7449,4 +7696,4 @@
"windSpeed": 8.568
}
}
}
}