diff --git a/homeassistant/components/homekit/services.yaml b/homeassistant/components/homekit/services.yaml index 6f9c005ed64..a6b09a80e7f 100644 --- a/homeassistant/components/homekit/services.yaml +++ b/homeassistant/components/homekit/services.yaml @@ -9,3 +9,5 @@ reload: reset_accessory: description: Reset a HomeKit accessory target: + entity: {} + diff --git a/homeassistant/components/insteon/fan.py b/homeassistant/components/insteon/fan.py index 2c397188640..f89aeb294fa 100644 --- a/homeassistant/components/insteon/fan.py +++ b/homeassistant/components/insteon/fan.py @@ -17,7 +17,7 @@ from .const import SIGNAL_ADD_ENTITIES from .insteon_entity import InsteonEntity from .utils import async_add_insteon_entities -SPEED_RANGE = (0x00, 0xFF) # off is not included +SPEED_RANGE = (1, 255) # off is not included async def async_setup_entry(hass, config_entry, async_add_entities): diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 7b7f983b1e4..e2a16c25329 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -280,15 +280,26 @@ class MqttCover(MqttEntity, CoverEntity): payload ) - if payload.isnumeric() and ( + if not payload.isnumeric(): + _LOGGER.warning("Payload '%s' is not numeric", payload) + elif ( self._config[CONF_TILT_MIN] <= int(payload) <= self._config[CONF_TILT_MAX] + or self._config[CONF_TILT_MAX] + <= int(payload) + <= self._config[CONF_TILT_MIN] ): - level = self.find_percentage_in_range(float(payload)) self._tilt_value = level self.async_write_ha_state() + else: + _LOGGER.warning( + "Payload '%s' is out of range, must be between '%s' and '%s' inclusive", + payload, + self._config[CONF_TILT_MIN], + self._config[CONF_TILT_MAX], + ) @callback @log_messages(self.hass, self.entity_id) diff --git a/homeassistant/components/netatmo/webhook.py b/homeassistant/components/netatmo/webhook.py index 1fe7302038e..e41f83b8cc0 100644 --- a/homeassistant/components/netatmo/webhook.py +++ b/homeassistant/components/netatmo/webhook.py @@ -77,16 +77,18 @@ async def async_send_event(hass, event_type, data): {"type": event_type, "data": data}, ) - if event_type not in EVENT_ID_MAP: - return + event_data = { + "type": event_type, + "data": data, + } - data_device_id = data[EVENT_ID_MAP[event_type]] + if event_type in EVENT_ID_MAP: + data_device_id = data[EVENT_ID_MAP[event_type]] + event_data[ATTR_DEVICE_ID] = hass.data[DOMAIN][DATA_DEVICE_IDS].get( + data_device_id + ) hass.bus.async_fire( event_type=NETATMO_EVENT, - event_data={ - "type": event_type, - "data": data, - ATTR_DEVICE_ID: hass.data[DOMAIN][DATA_DEVICE_IDS].get(data_device_id), - }, + event_data=event_data, ) diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 49388bdfdb6..1319e4bbf49 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.4.0", + "plexapi==4.4.1", "plexauth==0.0.6", "plexwebsocket==0.0.12" ], diff --git a/homeassistant/components/simplisafe/alarm_control_panel.py b/homeassistant/components/simplisafe/alarm_control_panel.py index 7634f1cce86..8f394890ad4 100644 --- a/homeassistant/components/simplisafe/alarm_control_panel.py +++ b/homeassistant/components/simplisafe/alarm_control_panel.py @@ -163,6 +163,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): return self._state = STATE_ALARM_DISARMED + self.async_write_ha_state() async def async_alarm_arm_home(self, code=None): """Send arm home command.""" @@ -178,6 +179,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): return self._state = STATE_ALARM_ARMED_HOME + self.async_write_ha_state() async def async_alarm_arm_away(self, code=None): """Send arm away command.""" @@ -193,6 +195,7 @@ class SimpliSafeAlarm(SimpliSafeEntity, AlarmControlPanelEntity): return self._state = STATE_ALARM_ARMING + self.async_write_ha_state() @callback def async_update_from_rest_api(self): diff --git a/homeassistant/components/simplisafe/lock.py b/homeassistant/components/simplisafe/lock.py index 08ffb82d24f..a4d823efe38 100644 --- a/homeassistant/components/simplisafe/lock.py +++ b/homeassistant/components/simplisafe/lock.py @@ -55,6 +55,9 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): LOGGER.error('Error while locking "%s": %s', self._lock.name, err) return + self._is_locked = True + self.async_write_ha_state() + async def async_unlock(self, **kwargs): """Unlock the lock.""" try: @@ -63,6 +66,9 @@ class SimpliSafeLock(SimpliSafeEntity, LockEntity): LOGGER.error('Error while unlocking "%s": %s', self._lock.name, err) return + self._is_locked = False + self.async_write_ha_state() + @callback def async_update_from_rest_api(self): """Update the entity with the provided REST API data.""" diff --git a/homeassistant/components/zwave_js/climate.py b/homeassistant/components/zwave_js/climate.py index 26e9e730283..4f62d5792fc 100644 --- a/homeassistant/components/zwave_js/climate.py +++ b/homeassistant/components/zwave_js/climate.py @@ -287,7 +287,12 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity): @property def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" - return self.target_temperature + if self._current_mode and self._current_mode.value is None: + # guard missing value + return None + if len(self._current_mode_setpoint_enums) > 1: + return self.target_temperature + return None @property def preset_mode(self) -> Optional[str]: diff --git a/homeassistant/const.py b/homeassistant/const.py index ae748b3ccc2..d7bb7ea25b5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 3 -PATCH_VERSION = "3" +PATCH_VERSION = "4" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) diff --git a/requirements_all.txt b/requirements_all.txt index 5eb66a3a200..05dcf9f233e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1135,7 +1135,7 @@ pillow==8.1.1 pizzapi==0.0.3 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 628b62899cd..4dba304310e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -581,7 +581,7 @@ pilight==0.1.1 pillow==8.1.1 # homeassistant.components.plex -plexapi==4.4.0 +plexapi==4.4.1 # homeassistant.components.plex plexauth==0.0.6 diff --git a/tests/components/mqtt/test_cover.py b/tests/components/mqtt/test_cover.py index 44144642f40..d6899d5149a 100644 --- a/tests/components/mqtt/test_cover.py +++ b/tests/components/mqtt/test_cover.py @@ -1315,6 +1315,112 @@ async def test_tilt_via_topic_altered_range(hass, mqtt_mock): assert current_cover_tilt_position == 50 +async def test_tilt_status_out_of_range_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt out of range warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "60") + + assert ( + "Payload '60' is out of range, must be between '0' and '50' inclusive" + ) in caplog.text + + +async def test_tilt_status_not_numeric_warning(hass, caplog, mqtt_mock): + """Test tilt status via MQTT tilt not numeric warning message.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 0, + "tilt_max": 50, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "abc") + + assert ("Payload 'abc' is not numeric") in caplog.text + + +async def test_tilt_via_topic_altered_range_inverted(hass, mqtt_mock): + """Test tilt status via MQTT with altered tilt range and inverted tilt position.""" + assert await async_setup_component( + hass, + cover.DOMAIN, + { + cover.DOMAIN: { + "platform": "mqtt", + "name": "test", + "state_topic": "state-topic", + "command_topic": "command-topic", + "qos": 0, + "payload_open": "OPEN", + "payload_close": "CLOSE", + "payload_stop": "STOP", + "tilt_command_topic": "tilt-command-topic", + "tilt_status_topic": "tilt-status-topic", + "tilt_min": 50, + "tilt_max": 0, + } + }, + ) + await hass.async_block_till_done() + + async_fire_mqtt_message(hass, "tilt-status-topic", "0") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 100 + + async_fire_mqtt_message(hass, "tilt-status-topic", "50") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 0 + + async_fire_mqtt_message(hass, "tilt-status-topic", "25") + + current_cover_tilt_position = hass.states.get("cover.test").attributes[ + ATTR_CURRENT_TILT_POSITION + ] + assert current_cover_tilt_position == 50 + + async def test_tilt_via_topic_template_altered_range(hass, mqtt_mock): """Test tilt status via MQTT and template with altered tilt range.""" assert await async_setup_component(