diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index e308f96c208..b5b663055a1 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -39,7 +39,6 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.typing import HomeAssistantType from . import DOMAIN as HMIPC_DOMAIN, HMIPC_HAPID, HomematicipGenericDevice -from .device import ATTR_GROUP_MEMBER_UNREACHABLE from .hap import HomematicipHAP _LOGGER = logging.getLogger(__name__) @@ -48,7 +47,6 @@ ATTR_ACCELERATION_SENSOR_MODE = "acceleration_sensor_mode" ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION = "acceleration_sensor_neutral_position" ATTR_ACCELERATION_SENSOR_SENSITIVITY = "acceleration_sensor_sensitivity" ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE = "acceleration_sensor_trigger_angle" -ATTR_LOW_BATTERY = "low_battery" ATTR_MOISTURE_DETECTED = "moisture_detected" ATTR_MOTION_DETECTED = "motion_detected" ATTR_POWER_MAINS_FAILURE = "power_mains_failure" @@ -59,12 +57,10 @@ ATTR_WATER_LEVEL_DETECTED = "water_level_detected" ATTR_WINDOW_STATE = "window_state" GROUP_ATTRIBUTES = { - "lowBat": ATTR_LOW_BATTERY, "moistureDetected": ATTR_MOISTURE_DETECTED, "motionDetected": ATTR_MOTION_DETECTED, "powerMainsFailure": ATTR_POWER_MAINS_FAILURE, "presenceDetected": ATTR_PRESENCE_DETECTED, - "unreach": ATTR_GROUP_MEMBER_UNREACHABLE, "waterlevelDetected": ATTR_WATER_LEVEL_DETECTED, } @@ -408,17 +404,22 @@ class HomematicipSecuritySensorGroup( def is_on(self) -> bool: """Return true if safety issue detected.""" parent_is_on = super().is_on + if parent_is_on: + return True + if ( - parent_is_on - or self._device.powerMainsFailure + self._device.powerMainsFailure or self._device.moistureDetected or self._device.waterlevelDetected or self._device.lowBat + or self._device.dutyCycle ): return True + if ( self._device.smokeDetectorAlarmType is not None and self._device.smokeDetectorAlarmType != SmokeDetectorAlarmType.IDLE_OFF ): return True + return False diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py index b05c0e06928..6c81775b688 100644 --- a/homeassistant/components/homematicip_cloud/device.py +++ b/homeassistant/components/homematicip_cloud/device.py @@ -15,6 +15,9 @@ from .hap import HomematicipHAP _LOGGER = logging.getLogger(__name__) ATTR_MODEL_TYPE = "model_type" +ATTR_LOW_BATTERY = "low_battery" +ATTR_CONFIG_PENDING = "config_pending" +ATTR_DUTY_CYCLE_REACHED = "duty_cycle_reached" ATTR_ID = "id" ATTR_IS_GROUP = "is_group" # RSSI HAP -> Device @@ -26,27 +29,40 @@ ATTR_GROUP_MEMBER_UNREACHABLE = "group_member_unreachable" ATTR_DEVICE_OVERHEATED = "device_overheated" ATTR_DEVICE_OVERLOADED = "device_overloaded" ATTR_DEVICE_UNTERVOLTAGE = "device_undervoltage" +ATTR_EVENT_DELAY = "event_delay" DEVICE_ATTRIBUTE_ICONS = { "lowBat": "mdi:battery-outline", - "sabotage": "mdi:alert", + "sabotage": "mdi:shield-alert", + "dutyCycle": "mdi:alert", "deviceOverheated": "mdi:alert", "deviceOverloaded": "mdi:alert", "deviceUndervoltage": "mdi:alert", + "configPending": "mdi:alert-circle", } DEVICE_ATTRIBUTES = { "modelType": ATTR_MODEL_TYPE, "sabotage": ATTR_SABOTAGE, + "dutyCycle": ATTR_DUTY_CYCLE_REACHED, "rssiDeviceValue": ATTR_RSSI_DEVICE, "rssiPeerValue": ATTR_RSSI_PEER, "deviceOverheated": ATTR_DEVICE_OVERHEATED, "deviceOverloaded": ATTR_DEVICE_OVERLOADED, "deviceUndervoltage": ATTR_DEVICE_UNTERVOLTAGE, + "configPending": ATTR_CONFIG_PENDING, + "eventDelay": ATTR_EVENT_DELAY, "id": ATTR_ID, } -GROUP_ATTRIBUTES = {"modelType": ATTR_MODEL_TYPE} +GROUP_ATTRIBUTES = { + "modelType": ATTR_MODEL_TYPE, + "lowBat": ATTR_LOW_BATTERY, + "sabotage": ATTR_SABOTAGE, + "dutyCycle": ATTR_DUTY_CYCLE_REACHED, + "configPending": ATTR_CONFIG_PENDING, + "unreach": ATTR_GROUP_MEMBER_UNREACHABLE, +} class HomematicipGenericDevice(Entity): diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 9caa72ba15f..acbf72f6ae9 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -39,12 +39,21 @@ from .hap import HomematicipHAP _LOGGER = logging.getLogger(__name__) +ATTR_CURRENT_ILLUMINATION = "current_illumination" +ATTR_LOWEST_ILLUMINATION = "lowest_illumination" +ATTR_HIGHEST_ILLUMINATION = "highest_illumination" ATTR_LEFT_COUNTER = "left_counter" ATTR_RIGHT_COUNTER = "right_counter" ATTR_TEMPERATURE_OFFSET = "temperature_offset" ATTR_WIND_DIRECTION = "wind_direction" ATTR_WIND_DIRECTION_VARIATION = "wind_direction_variation_in_degree" +ILLUMINATION_DEVICE_ATTRIBUTES = { + "currentIllumination": ATTR_CURRENT_ILLUMINATION, + "lowestIllumination": ATTR_LOWEST_ILLUMINATION, + "highestIllumination": ATTR_HIGHEST_ILLUMINATION, +} + async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the HomematicIP Cloud sensors devices.""" @@ -273,6 +282,18 @@ class HomematicipIlluminanceSensor(HomematicipGenericDevice): """Return the unit this state is expressed in.""" return "lx" + @property + def device_state_attributes(self): + """Return the state attributes of the wind speed sensor.""" + state_attr = super().device_state_attributes + + for attr, attr_key in ILLUMINATION_DEVICE_ATTRIBUTES.items(): + attr_value = getattr(self._device, attr, None) + if attr_value: + state_attr[attr_key] = attr_value + + return state_attr + class HomematicipPowerSensor(HomematicipGenericDevice): """Representation of a HomematicIP power measuring device.""" diff --git a/tests/components/homematicip_cloud/test_binary_sensor.py b/tests/components/homematicip_cloud/test_binary_sensor.py index 0760518171e..38358f9ddff 100644 --- a/tests/components/homematicip_cloud/test_binary_sensor.py +++ b/tests/components/homematicip_cloud/test_binary_sensor.py @@ -8,8 +8,19 @@ from homeassistant.components.homematicip_cloud.binary_sensor import ( ATTR_ACCELERATION_SENSOR_NEUTRAL_POSITION, ATTR_ACCELERATION_SENSOR_SENSITIVITY, ATTR_ACCELERATION_SENSOR_TRIGGER_ANGLE, - ATTR_LOW_BATTERY, + ATTR_MOISTURE_DETECTED, ATTR_MOTION_DETECTED, + ATTR_POWER_MAINS_FAILURE, + ATTR_PRESENCE_DETECTED, + ATTR_WATER_LEVEL_DETECTED, + ATTR_WINDOW_STATE, +) +from homeassistant.components.homematicip_cloud.device import ( + ATTR_EVENT_DELAY, + ATTR_GROUP_MEMBER_UNREACHABLE, + ATTR_LOW_BATTERY, + ATTR_RSSI_DEVICE, + ATTR_SABOTAGE, ) from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.setup import async_setup_component @@ -105,6 +116,13 @@ async def test_hmip_shutter_contact(hass, default_mock_hap): ha_state = hass.states.get(entity_id) assert ha_state.state == STATE_OFF + # test common attributes + assert ha_state.attributes[ATTR_RSSI_DEVICE] == -54 + assert not ha_state.attributes.get(ATTR_SABOTAGE) + await async_manipulate_test_data(hass, hmip_device, "sabotage", True) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_SABOTAGE] + async def test_hmip_motion_detector(hass, default_mock_hap): """Test HomematicipMotionDetector.""" @@ -137,6 +155,11 @@ async def test_hmip_presence_detector(hass, default_mock_hap): ha_state = hass.states.get(entity_id) assert ha_state.state == STATE_ON + assert not ha_state.attributes.get(ATTR_EVENT_DELAY) + await async_manipulate_test_data(hass, hmip_device, "eventDelay", True) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_EVENT_DELAY] + async def test_hmip_smoke_detector(hass, default_mock_hap): """Test HomematicipSmokeDetector.""" @@ -267,10 +290,25 @@ async def test_hmip_security_zone_sensor_group(hass, default_mock_hap): ) assert ha_state.state == STATE_OFF + assert not ha_state.attributes.get(ATTR_MOTION_DETECTED) + assert not ha_state.attributes.get(ATTR_PRESENCE_DETECTED) + assert not ha_state.attributes.get(ATTR_GROUP_MEMBER_UNREACHABLE) + assert not ha_state.attributes.get(ATTR_SABOTAGE) + assert not ha_state.attributes.get(ATTR_WINDOW_STATE) + await async_manipulate_test_data(hass, hmip_device, "motionDetected", True) + await async_manipulate_test_data(hass, hmip_device, "presenceDetected", True) + await async_manipulate_test_data(hass, hmip_device, "unreach", True) + await async_manipulate_test_data(hass, hmip_device, "sabotage", True) + await async_manipulate_test_data(hass, hmip_device, "windowState", WindowState.OPEN) ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_ON - assert ha_state.attributes[ATTR_MOTION_DETECTED] is True + assert ha_state.attributes[ATTR_MOTION_DETECTED] + assert ha_state.attributes[ATTR_PRESENCE_DETECTED] + assert ha_state.attributes[ATTR_GROUP_MEMBER_UNREACHABLE] + assert ha_state.attributes[ATTR_SABOTAGE] + assert ha_state.attributes[ATTR_WINDOW_STATE] == WindowState.OPEN async def test_hmip_security_sensor_group(hass, default_mock_hap): @@ -283,14 +321,6 @@ async def test_hmip_security_sensor_group(hass, default_mock_hap): hass, default_mock_hap, entity_id, entity_name, device_model ) - assert ha_state.state == STATE_OFF - assert not ha_state.attributes.get("low_bat") - await async_manipulate_test_data(hass, hmip_device, "lowBat", True) - ha_state = hass.states.get(entity_id) - assert ha_state.state == STATE_ON - assert ha_state.attributes[ATTR_LOW_BATTERY] is True - - await async_manipulate_test_data(hass, hmip_device, "lowBat", False) await async_manipulate_test_data( hass, hmip_device, @@ -299,7 +329,45 @@ async def test_hmip_security_sensor_group(hass, default_mock_hap): ) ha_state = hass.states.get(entity_id) assert ha_state.state == STATE_ON + assert ( ha_state.attributes["smoke_detector_alarm"] == SmokeDetectorAlarmType.PRIMARY_ALARM ) + await async_manipulate_test_data( + hass, hmip_device, "smokeDetectorAlarmType", SmokeDetectorAlarmType.IDLE_OFF + ) + ha_state = hass.states.get(entity_id) + assert ha_state.state == STATE_OFF + + assert not ha_state.attributes.get(ATTR_LOW_BATTERY) + assert not ha_state.attributes.get(ATTR_MOTION_DETECTED) + assert not ha_state.attributes.get(ATTR_PRESENCE_DETECTED) + assert not ha_state.attributes.get(ATTR_POWER_MAINS_FAILURE) + assert not ha_state.attributes.get(ATTR_MOISTURE_DETECTED) + assert not ha_state.attributes.get(ATTR_WATER_LEVEL_DETECTED) + assert not ha_state.attributes.get(ATTR_GROUP_MEMBER_UNREACHABLE) + assert not ha_state.attributes.get(ATTR_SABOTAGE) + assert not ha_state.attributes.get(ATTR_WINDOW_STATE) + + await async_manipulate_test_data(hass, hmip_device, "lowBat", True) + await async_manipulate_test_data(hass, hmip_device, "motionDetected", True) + await async_manipulate_test_data(hass, hmip_device, "presenceDetected", True) + await async_manipulate_test_data(hass, hmip_device, "powerMainsFailure", True) + await async_manipulate_test_data(hass, hmip_device, "moistureDetected", True) + await async_manipulate_test_data(hass, hmip_device, "waterlevelDetected", True) + await async_manipulate_test_data(hass, hmip_device, "unreach", True) + await async_manipulate_test_data(hass, hmip_device, "sabotage", True) + await async_manipulate_test_data(hass, hmip_device, "windowState", WindowState.OPEN) + ha_state = hass.states.get(entity_id) + + assert ha_state.state == STATE_ON + assert ha_state.attributes[ATTR_LOW_BATTERY] + assert ha_state.attributes[ATTR_MOTION_DETECTED] + assert ha_state.attributes[ATTR_PRESENCE_DETECTED] + assert ha_state.attributes[ATTR_POWER_MAINS_FAILURE] + assert ha_state.attributes[ATTR_MOISTURE_DETECTED] + assert ha_state.attributes[ATTR_WATER_LEVEL_DETECTED] + assert ha_state.attributes[ATTR_GROUP_MEMBER_UNREACHABLE] + assert ha_state.attributes[ATTR_SABOTAGE] + assert ha_state.attributes[ATTR_WINDOW_STATE] == WindowState.OPEN diff --git a/tests/components/homematicip_cloud/test_config_flow.py b/tests/components/homematicip_cloud/test_config_flow.py index 54cb309755d..afaf71c67b5 100644 --- a/tests/components/homematicip_cloud/test_config_flow.py +++ b/tests/components/homematicip_cloud/test_config_flow.py @@ -98,6 +98,15 @@ async def test_init_flow_show_form(hass): assert result["type"] == "form" +async def test_init_flow_user_show_form(hass): + """Test config flow shows up with a form.""" + flow = config_flow.HomematicipCloudFlowHandler() + flow.hass = hass + + result = await flow.async_step_user(user_input=None) + assert result["type"] == "form" + + async def test_init_already_configured(hass): """Test accesspoint is already configured.""" MockConfigEntry( diff --git a/tests/components/homematicip_cloud/test_sensor.py b/tests/components/homematicip_cloud/test_sensor.py index 8412cd19f4d..f0a81c69074 100644 --- a/tests/components/homematicip_cloud/test_sensor.py +++ b/tests/components/homematicip_cloud/test_sensor.py @@ -2,8 +2,20 @@ from homematicip.base.enums import ValveState from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN +from homeassistant.components.homematicip_cloud.device import ( + ATTR_CONFIG_PENDING, + ATTR_DEVICE_OVERHEATED, + ATTR_DEVICE_OVERLOADED, + ATTR_DEVICE_UNTERVOLTAGE, + ATTR_DUTY_CYCLE_REACHED, + ATTR_RSSI_DEVICE, + ATTR_RSSI_PEER, +) from homeassistant.components.homematicip_cloud.sensor import ( + ATTR_CURRENT_ILLUMINATION, + ATTR_HIGHEST_ILLUMINATION, ATTR_LEFT_COUNTER, + ATTR_LOWEST_ILLUMINATION, ATTR_RIGHT_COUNTER, ATTR_TEMPERATURE_OFFSET, ATTR_WIND_DIRECTION, @@ -92,6 +104,9 @@ async def test_hmip_humidity_sensor(hass, default_mock_hap): await async_manipulate_test_data(hass, hmip_device, "humidity", 45) ha_state = hass.states.get(entity_id) assert ha_state.state == "45" + # test common attributes + assert ha_state.attributes[ATTR_RSSI_DEVICE] == -76 + assert ha_state.attributes[ATTR_RSSI_PEER] == -77 async def test_hmip_temperature_sensor1(hass, default_mock_hap): @@ -153,6 +168,23 @@ async def test_hmip_power_sensor(hass, default_mock_hap): await async_manipulate_test_data(hass, hmip_device, "currentPowerConsumption", 23.5) ha_state = hass.states.get(entity_id) assert ha_state.state == "23.5" + # test common attributes + assert not ha_state.attributes.get(ATTR_DEVICE_OVERHEATED) + assert not ha_state.attributes.get(ATTR_DEVICE_OVERLOADED) + assert not ha_state.attributes.get(ATTR_DEVICE_UNTERVOLTAGE) + assert not ha_state.attributes.get(ATTR_DUTY_CYCLE_REACHED) + assert not ha_state.attributes.get(ATTR_CONFIG_PENDING) + await async_manipulate_test_data(hass, hmip_device, "deviceOverheated", True) + await async_manipulate_test_data(hass, hmip_device, "deviceOverloaded", True) + await async_manipulate_test_data(hass, hmip_device, "deviceUndervoltage", True) + await async_manipulate_test_data(hass, hmip_device, "dutyCycle", True) + await async_manipulate_test_data(hass, hmip_device, "configPending", True) + ha_state = hass.states.get(entity_id) + assert ha_state.attributes[ATTR_DEVICE_OVERHEATED] + assert ha_state.attributes[ATTR_DEVICE_OVERLOADED] + assert ha_state.attributes[ATTR_DEVICE_UNTERVOLTAGE] + assert ha_state.attributes[ATTR_DUTY_CYCLE_REACHED] + assert ha_state.attributes[ATTR_CONFIG_PENDING] async def test_hmip_illuminance_sensor1(hass, default_mock_hap): @@ -187,6 +219,9 @@ async def test_hmip_illuminance_sensor2(hass, default_mock_hap): await async_manipulate_test_data(hass, hmip_device, "averageIllumination", 231) ha_state = hass.states.get(entity_id) assert ha_state.state == "231" + assert ha_state.attributes[ATTR_CURRENT_ILLUMINATION] == 785.2 + assert ha_state.attributes[ATTR_HIGHEST_ILLUMINATION] == 837.1 + assert ha_state.attributes[ATTR_LOWEST_ILLUMINATION] == 785.2 async def test_hmip_windspeed_sensor(hass, default_mock_hap): diff --git a/tests/components/homematicip_cloud/test_switch.py b/tests/components/homematicip_cloud/test_switch.py index 9e33d1d9587..b8ca7b4b67e 100644 --- a/tests/components/homematicip_cloud/test_switch.py +++ b/tests/components/homematicip_cloud/test_switch.py @@ -136,7 +136,7 @@ async def test_hmip_group_switch(hass, default_mock_hap): assert not ha_state.attributes.get(ATTR_GROUP_MEMBER_UNREACHABLE) await async_manipulate_test_data(hass, hmip_device, "unreach", True) ha_state = hass.states.get(entity_id) - assert ha_state.attributes[ATTR_GROUP_MEMBER_UNREACHABLE] is True + assert ha_state.attributes[ATTR_GROUP_MEMBER_UNREACHABLE] async def test_hmip_multi_switch(hass, default_mock_hap):