diff --git a/homeassistant/components/incomfort/water_heater.py b/homeassistant/components/incomfort/water_heater.py index 84ed0212d3b..469e3571334 100644 --- a/homeassistant/components/incomfort/water_heater.py +++ b/homeassistant/components/incomfort/water_heater.py @@ -67,13 +67,13 @@ class IncomfortWaterHeater(IncomfortEntity, WaterHeaterEntity): @property def min_temp(self) -> float: - """Return max valid temperature that can be set.""" - return 80.0 + """Return min valid temperature that can be set.""" + return 30.0 @property def max_temp(self) -> float: """Return max valid temperature that can be set.""" - return 30.0 + return 80.0 @property def temperature_unit(self) -> str: diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index b8e72c3be5c..cd3e376b792 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -110,16 +110,10 @@ class IntegrationSensor(RestoreEntity, SensorEntity): self._method = integration_method self._name = name if name is not None else f"{source_entity} integral" - - if unit_of_measurement is None: - self._unit_template = ( - f"{'' if unit_prefix is None else unit_prefix}{{}}{unit_time}" - ) - # we postpone the definition of unit_of_measurement to later - self._unit_of_measurement = None - else: - self._unit_of_measurement = unit_of_measurement - + self._unit_template = ( + f"{'' if unit_prefix is None else unit_prefix}{{}}{unit_time}" + ) + self._unit_of_measurement = unit_of_measurement self._unit_prefix = UNIT_PREFIXES[unit_prefix] self._unit_time = UNIT_TIME[unit_time] self._attr_state_class = STATE_CLASS_TOTAL_INCREASING @@ -135,10 +129,10 @@ class IntegrationSensor(RestoreEntity, SensorEntity): _LOGGER.warning("Could not restore last state: %s", err) else: self._attr_device_class = state.attributes.get(ATTR_DEVICE_CLASS) - - self._unit_of_measurement = state.attributes.get( - ATTR_UNIT_OF_MEASUREMENT - ) + if self._unit_of_measurement is None: + self._unit_of_measurement = state.attributes.get( + ATTR_UNIT_OF_MEASUREMENT + ) @callback def calc_integration(event): diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 8992ca2d7fc..8bbfd08314d 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -48,7 +48,7 @@ from homeassistant.helpers.integration_platform import ( from homeassistant.loader import bind_hass import homeassistant.util.dt as dt_util -ENTITY_ID_JSON_TEMPLATE = '"entity_id": ?"{}"' +ENTITY_ID_JSON_TEMPLATE = '"entity_id":"{}"' ENTITY_ID_JSON_EXTRACT = re.compile('"entity_id": ?"([^"]+)"') DOMAIN_JSON_EXTRACT = re.compile('"domain": ?"([^"]+)"') ICON_JSON_EXTRACT = re.compile('"icon": ?"([^"]+)"') diff --git a/homeassistant/components/mazda/manifest.json b/homeassistant/components/mazda/manifest.json index cc12653f5cb..7eb85f722ae 100644 --- a/homeassistant/components/mazda/manifest.json +++ b/homeassistant/components/mazda/manifest.json @@ -3,7 +3,7 @@ "name": "Mazda Connected Services", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mazda", - "requirements": ["pymazda==0.2.0"], + "requirements": ["pymazda==0.2.1"], "codeowners": ["@bdr99"], "quality_scale": "platinum", "iot_class": "cloud_polling" diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index a4177a7ff30..df4fe3c1e62 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -25,9 +25,11 @@ from homeassistant.const import ( from .const import ( CONF_DATA_TYPE, + CONF_INPUT_TYPE, CONF_SWAP, CONF_SWAP_BYTE, CONF_SWAP_NONE, + CONF_WRITE_TYPE, DATA_TYPE_CUSTOM, DATA_TYPE_FLOAT, DATA_TYPE_FLOAT16, @@ -212,6 +214,10 @@ def duplicate_entity_validator(config: dict) -> dict: for index, entry in enumerate(hub[conf_key]): name = entry[CONF_NAME] addr = str(entry[CONF_ADDRESS]) + if CONF_INPUT_TYPE in entry: + addr += "_" + str(entry[CONF_INPUT_TYPE]) + elif CONF_WRITE_TYPE in entry: + addr += "_" + str(entry[CONF_WRITE_TYPE]) if CONF_COMMAND_ON in entry: addr += "_" + str(entry[CONF_COMMAND_ON]) if CONF_COMMAND_OFF in entry: @@ -242,7 +248,10 @@ def duplicate_modbus_validator(config: list) -> list: errors = [] for index, hub in enumerate(config): name = hub.get(CONF_NAME, DEFAULT_HUB) - host = hub[CONF_PORT] if hub[CONF_TYPE] == SERIAL else hub[CONF_HOST] + if hub[CONF_TYPE] == SERIAL: + host = hub[CONF_PORT] + else: + host = f"{hub[CONF_HOST]}_{hub[CONF_PORT]}" if host in hosts: err = f"Modbus {name}  contains duplicate host/port {host}, not loaded!" _LOGGER.warning(err) diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index 7ce986d7082..fd3be53bfda 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -75,7 +75,7 @@ class RfxtrxSensorEntityDescription(SensorEntityDescription): SENSOR_TYPES = ( RfxtrxSensorEntityDescription( - key="Barameter", + key="Barometer", device_class=DEVICE_CLASS_PRESSURE, state_class=STATE_CLASS_MEASUREMENT, native_unit_of_measurement=PRESSURE_HPA, diff --git a/homeassistant/components/surepetcare/__init__.py b/homeassistant/components/surepetcare/__init__.py index 58890090d57..00c45701423 100644 --- a/homeassistant/components/surepetcare/__init__.py +++ b/homeassistant/components/surepetcare/__init__.py @@ -144,7 +144,7 @@ class SurePetcareAPI: """Get the latest data from Sure Petcare.""" try: - self.states = await self.surepy.get_entities() + self.states = await self.surepy.get_entities(refresh=True) except SurePetcareError as error: _LOGGER.error("Unable to fetch data: %s", error) return diff --git a/homeassistant/components/thinkingcleaner/switch.py b/homeassistant/components/thinkingcleaner/switch.py index 75cfc51a511..cad94b72023 100644 --- a/homeassistant/components/thinkingcleaner/switch.py +++ b/homeassistant/components/thinkingcleaner/switch.py @@ -80,7 +80,7 @@ class ThinkingCleanerSwitch(SwitchEntity): self.last_lock_time = None self.graceful_state = False - self._attr_name = f"{tc_object} {description.name}" + self._attr_name = f"{tc_object.name} {description.name}" def lock_update(self): """Lock the update since TC clean takes some time to update.""" diff --git a/homeassistant/components/xiaomi_miio/const.py b/homeassistant/components/xiaomi_miio/const.py index b670582c069..dce1fdd5e95 100644 --- a/homeassistant/components/xiaomi_miio/const.py +++ b/homeassistant/components/xiaomi_miio/const.py @@ -116,6 +116,13 @@ MODEL_AIRQUALITYMONITOR_B1 = "cgllc.airmonitor.b1" MODEL_AIRQUALITYMONITOR_S1 = "cgllc.airmonitor.s1" MODEL_AIRQUALITYMONITOR_CGDN1 = "cgllc.airm.cgdn1" +MODELS_AIR_QUALITY_MONITOR = [ + MODEL_AIRQUALITYMONITOR_V1, + MODEL_AIRQUALITYMONITOR_B1, + MODEL_AIRQUALITYMONITOR_S1, + MODEL_AIRQUALITYMONITOR_CGDN1, +] + # Light Models MODELS_LIGHT_EYECARE = ["philips.light.sread1"] MODELS_LIGHT_CEILING = ["philips.light.ceiling", "philips.light.zyceiling"] diff --git a/homeassistant/components/xiaomi_miio/fan.py b/homeassistant/components/xiaomi_miio/fan.py index 42828943d93..70f51019ec9 100644 --- a/homeassistant/components/xiaomi_miio/fan.py +++ b/homeassistant/components/xiaomi_miio/fan.py @@ -405,36 +405,42 @@ class XiaomiAirPurifier(XiaomiGenericDevice): self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO self._supported_features = SUPPORT_PRESET_MODE self._speed_count = 1 + self._operation_mode_class = AirpurifierOperationMode elif self._model == MODEL_AIRPURIFIER_PRO_V7: self._device_features = FEATURE_FLAGS_AIRPURIFIER_PRO_V7 self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_PRO_V7 self._preset_modes = PRESET_MODES_AIRPURIFIER_PRO_V7 self._supported_features = SUPPORT_PRESET_MODE self._speed_count = 1 + self._operation_mode_class = AirpurifierOperationMode elif self._model in [MODEL_AIRPURIFIER_2S, MODEL_AIRPURIFIER_2H]: self._device_features = FEATURE_FLAGS_AIRPURIFIER_2S self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_COMMON self._preset_modes = PRESET_MODES_AIRPURIFIER_2S self._supported_features = SUPPORT_PRESET_MODE self._speed_count = 1 + self._operation_mode_class = AirpurifierOperationMode elif self._model in MODELS_PURIFIER_MIOT: self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIOT self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_MIOT self._preset_modes = PRESET_MODES_AIRPURIFIER_MIOT self._supported_features = SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE self._speed_count = 3 + self._operation_mode_class = AirpurifierMiotOperationMode elif self._model == MODEL_AIRPURIFIER_V3: self._device_features = FEATURE_FLAGS_AIRPURIFIER_V3 self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER_V3 self._preset_modes = PRESET_MODES_AIRPURIFIER_V3 self._supported_features = SUPPORT_PRESET_MODE self._speed_count = 1 + self._operation_mode_class = AirpurifierOperationMode else: self._device_features = FEATURE_FLAGS_AIRPURIFIER_MIIO self._available_attributes = AVAILABLE_ATTRIBUTES_AIRPURIFIER self._preset_modes = PRESET_MODES_AIRPURIFIER self._supported_features = SUPPORT_PRESET_MODE self._speed_count = 1 + self._operation_mode_class = AirpurifierOperationMode self._state_attrs.update( {attribute: None for attribute in self._available_attributes} @@ -446,7 +452,7 @@ class XiaomiAirPurifier(XiaomiGenericDevice): def preset_mode(self): """Get the active preset mode.""" if self._state: - preset_mode = AirpurifierOperationMode(self._state_attrs[ATTR_MODE]).name + preset_mode = self._operation_mode_class(self._mode).name return preset_mode if preset_mode in self._preset_modes else None return None @@ -455,7 +461,7 @@ class XiaomiAirPurifier(XiaomiGenericDevice): def percentage(self): """Return the current percentage based speed.""" if self._state: - mode = AirpurifierOperationMode(self._state_attrs[ATTR_MODE]) + mode = self._operation_mode_class(self._state_attrs[ATTR_MODE]) if mode in self.REVERSE_SPEED_MODE_MAPPING: return ranged_value_to_percentage( (1, self._speed_count), self.REVERSE_SPEED_MODE_MAPPING[mode] @@ -479,7 +485,7 @@ class XiaomiAirPurifier(XiaomiGenericDevice): await self._try_command( "Setting operation mode of the miio device failed.", self._device.set_mode, - AirpurifierOperationMode(self.SPEED_MODE_MAPPING[speed_mode]), + self._operation_mode_class(self.SPEED_MODE_MAPPING[speed_mode]), ) async def async_set_preset_mode(self, preset_mode: str) -> None: @@ -490,11 +496,13 @@ class XiaomiAirPurifier(XiaomiGenericDevice): if preset_mode not in self.preset_modes: _LOGGER.warning("'%s'is not a valid preset mode", preset_mode) return - await self._try_command( + if await self._try_command( "Setting operation mode of the miio device failed.", self._device.set_mode, self.PRESET_MODE_MAPPING[preset_mode], - ) + ): + self._mode = self._operation_mode_class[preset_mode].value + self.async_write_ha_state() async def async_set_extra_features(self, features: int = 1): """Set the extra features.""" @@ -538,15 +546,6 @@ class XiaomiAirPurifierMiot(XiaomiAirPurifier): return None - @property - def preset_mode(self): - """Get the active preset mode.""" - if self._state: - preset_mode = AirpurifierMiotOperationMode(self._mode).name - return preset_mode if preset_mode in self._preset_modes else None - - return None - async def async_set_percentage(self, percentage: int) -> None: """Set the percentage of the fan. diff --git a/homeassistant/components/xiaomi_miio/humidifier.py b/homeassistant/components/xiaomi_miio/humidifier.py index aa26faae2b3..584d5caf6b5 100644 --- a/homeassistant/components/xiaomi_miio/humidifier.py +++ b/homeassistant/components/xiaomi_miio/humidifier.py @@ -210,7 +210,7 @@ class XiaomiAirHumidifier(XiaomiGenericHumidifier, HumidifierEntity): self._available_modes = AVAILABLE_MODES_MJJSQ self._min_humidity = 30 self._max_humidity = 80 - self._humidity_steps = 10 + self._humidity_steps = 100 else: self._available_modes = AVAILABLE_MODES_OTHER self._min_humidity = 30 diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 63535e88a2d..3a50ffe89c0 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -66,6 +66,7 @@ from .const import ( MODEL_FAN_ZA1, MODEL_FAN_ZA3, MODEL_FAN_ZA4, + MODELS_AIR_QUALITY_MONITOR, MODELS_HUMIDIFIER_MIIO, MODELS_HUMIDIFIER_MIOT, MODELS_HUMIDIFIER_MJJSQ, @@ -371,23 +372,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities): host = config_entry.data[CONF_HOST] token = config_entry.data[CONF_TOKEN] model = config_entry.data[CONF_MODEL] - device = hass.data[DOMAIN][config_entry.entry_id].get(KEY_DEVICE) - sensors = [] + if model in (MODEL_FAN_ZA1, MODEL_FAN_ZA3, MODEL_FAN_ZA4, MODEL_FAN_P5): return - if model in MODEL_TO_SENSORS_MAP: - sensors = MODEL_TO_SENSORS_MAP[model] - elif model in MODELS_HUMIDIFIER_MIOT: - sensors = HUMIDIFIER_MIOT_SENSORS - elif model in MODELS_HUMIDIFIER_MJJSQ: - sensors = HUMIDIFIER_MJJSQ_SENSORS - elif model in MODELS_HUMIDIFIER_MIIO: - sensors = HUMIDIFIER_MIIO_SENSORS - elif model in MODELS_PURIFIER_MIIO: - sensors = PURIFIER_MIIO_SENSORS - elif model in MODELS_PURIFIER_MIOT: - sensors = PURIFIER_MIOT_SENSORS - else: + + if model in MODELS_AIR_QUALITY_MONITOR: unique_id = config_entry.unique_id name = config_entry.title _LOGGER.debug("Initializing with host %s (token %s...)", host, token[:5]) @@ -399,19 +388,35 @@ async def async_setup_entry(hass, config_entry, async_add_entities): name, device, config_entry, unique_id, description ) ) - for sensor, description in SENSOR_TYPES.items(): - if sensor not in sensors: - continue - entities.append( - XiaomiGenericSensor( - f"{config_entry.title} {description.name}", - device, - config_entry, - f"{sensor}_{config_entry.unique_id}", - hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR], - description, + else: + device = hass.data[DOMAIN][config_entry.entry_id][KEY_DEVICE] + sensors = [] + if model in MODEL_TO_SENSORS_MAP: + sensors = MODEL_TO_SENSORS_MAP[model] + elif model in MODELS_HUMIDIFIER_MIOT: + sensors = HUMIDIFIER_MIOT_SENSORS + elif model in MODELS_HUMIDIFIER_MJJSQ: + sensors = HUMIDIFIER_MJJSQ_SENSORS + elif model in MODELS_HUMIDIFIER_MIIO: + sensors = HUMIDIFIER_MIIO_SENSORS + elif model in MODELS_PURIFIER_MIIO: + sensors = PURIFIER_MIIO_SENSORS + elif model in MODELS_PURIFIER_MIOT: + sensors = PURIFIER_MIOT_SENSORS + + for sensor, description in SENSOR_TYPES.items(): + if sensor not in sensors: + continue + entities.append( + XiaomiGenericSensor( + f"{config_entry.title} {description.name}", + device, + config_entry, + f"{sensor}_{config_entry.unique_id}", + hass.data[DOMAIN][config_entry.entry_id][KEY_COORDINATOR], + description, + ) ) - ) async_add_entities(entities) diff --git a/homeassistant/components/zha/config_flow.py b/homeassistant/components/zha/config_flow.py index 4bf255e95a0..b98878da776 100644 --- a/homeassistant/components/zha/config_flow.py +++ b/homeassistant/components/zha/config_flow.py @@ -108,7 +108,7 @@ class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured( updates={ CONF_DEVICE: { - **current_entry.data[CONF_DEVICE], + **current_entry.data.get(CONF_DEVICE, {}), CONF_DEVICE_PATH: dev_path, }, } @@ -172,7 +172,7 @@ class ZhaFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): self._abort_if_unique_id_configured( updates={ CONF_DEVICE: { - **current_entry.data[CONF_DEVICE], + **current_entry.data.get(CONF_DEVICE, {}), CONF_DEVICE_PATH: device_path, }, } diff --git a/homeassistant/components/zwave_js/cover.py b/homeassistant/components/zwave_js/cover.py index 7fceaf64c0e..9060e13a9a5 100644 --- a/homeassistant/components/zwave_js/cover.py +++ b/homeassistant/components/zwave_js/cover.py @@ -5,7 +5,7 @@ import logging from typing import Any from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.const.command_class.barrior_operator import BarrierState +from zwave_js_server.const.command_class.barrier_operator import BarrierState from zwave_js_server.model.value import Value as ZwaveValue from homeassistant.components.cover import ( diff --git a/homeassistant/components/zwave_js/discovery_data_template.py b/homeassistant/components/zwave_js/discovery_data_template.py index 974cd2bfa44..5c3c49e894f 100644 --- a/homeassistant/components/zwave_js/discovery_data_template.py +++ b/homeassistant/components/zwave_js/discovery_data_template.py @@ -32,8 +32,8 @@ from zwave_js_server.const.command_class.multilevel_sensor import ( ) from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import Value as ZwaveValue, get_value_id -from zwave_js_server.util.command_class import ( - get_meter_scale_type, +from zwave_js_server.util.command_class.meter import get_meter_scale_type +from zwave_js_server.util.command_class.multilevel_sensor import ( get_multilevel_sensor_type, ) diff --git a/homeassistant/components/zwave_js/manifest.json b/homeassistant/components/zwave_js/manifest.json index ad8ec22befb..c7b2b35837b 100644 --- a/homeassistant/components/zwave_js/manifest.json +++ b/homeassistant/components/zwave_js/manifest.json @@ -3,7 +3,7 @@ "name": "Z-Wave JS", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zwave_js", - "requirements": ["zwave-js-server-python==0.29.1"], + "requirements": ["zwave-js-server-python==0.30.0"], "codeowners": ["@home-assistant/z-wave"], "dependencies": ["usb", "http", "websocket_api"], "iot_class": "local_push", diff --git a/homeassistant/components/zwave_js/sensor.py b/homeassistant/components/zwave_js/sensor.py index 09d44f7f24a..6532da8a5e0 100644 --- a/homeassistant/components/zwave_js/sensor.py +++ b/homeassistant/components/zwave_js/sensor.py @@ -15,7 +15,7 @@ from zwave_js_server.const.command_class.meter import ( ) from zwave_js_server.model.node import Node as ZwaveNode from zwave_js_server.model.value import ConfigurationValue -from zwave_js_server.util.command_class import get_meter_type +from zwave_js_server.util.command_class.meter import get_meter_type from homeassistant.components.sensor import ( DEVICE_CLASS_ENERGY, diff --git a/homeassistant/components/zwave_js/switch.py b/homeassistant/components/zwave_js/switch.py index bd86a3b8377..44aa3a5566f 100644 --- a/homeassistant/components/zwave_js/switch.py +++ b/homeassistant/components/zwave_js/switch.py @@ -5,7 +5,7 @@ import logging from typing import Any from zwave_js_server.client import Client as ZwaveClient -from zwave_js_server.const.command_class.barrior_operator import ( +from zwave_js_server.const.command_class.barrier_operator import ( BarrierEventSignalingSubsystemState, ) diff --git a/homeassistant/const.py b/homeassistant/const.py index c7bac6e5d1d..edba8a1a739 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 9 -PATCH_VERSION: Final = "3" +PATCH_VERSION: Final = "4" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) diff --git a/requirements_all.txt b/requirements_all.txt index 11f70be6c1c..6562f28a5c3 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1599,7 +1599,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.2.0 +pymazda==0.2.1 # homeassistant.components.mediaroom pymediaroom==0.6.4.1 @@ -2489,4 +2489,4 @@ zigpy==0.37.1 zm-py==0.5.2 # homeassistant.components.zwave_js -zwave-js-server-python==0.29.1 +zwave-js-server-python==0.30.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 67e30937a72..5b8a0dbb6ba 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -921,7 +921,7 @@ pymailgunner==1.4 pymata-express==1.19 # homeassistant.components.mazda -pymazda==0.2.0 +pymazda==0.2.1 # homeassistant.components.melcloud pymelcloud==2.5.3 @@ -1400,4 +1400,4 @@ zigpy-znp==0.5.4 zigpy==0.37.1 # homeassistant.components.zwave_js -zwave-js-server-python==0.29.1 +zwave-js-server-python==0.30.0 diff --git a/tests/components/logbook/test_init.py b/tests/components/logbook/test_init.py index 3dab7e6c2fb..b95ef2e148a 100644 --- a/tests/components/logbook/test_init.py +++ b/tests/components/logbook/test_init.py @@ -1289,6 +1289,45 @@ async def test_logbook_entity_matches_only(hass, hass_client): assert json_dict[1]["context_user_id"] == "9400facee45711eaa9308bfd3d19e474" +async def test_custom_log_entry_discoverable_via_entity_matches_only(hass, hass_client): + """Test if a custom log entry is later discoverable via entity_matches_only.""" + await hass.async_add_executor_job(init_recorder_component, hass) + await async_setup_component(hass, "logbook", {}) + await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + logbook.async_log_entry( + hass, + "Alarm", + "is triggered", + "switch", + "switch.test_switch", + ) + await hass.async_block_till_done() + await hass.async_add_executor_job(trigger_db_commit, hass) + await hass.async_block_till_done() + await hass.async_add_executor_job(hass.data[recorder.DATA_INSTANCE].block_till_done) + + client = await hass_client() + + # Today time 00:00:00 + start = dt_util.utcnow().date() + start_date = datetime(start.year, start.month, start.day) + + # Test today entries with filter by end_time + end_time = start + timedelta(hours=24) + response = await client.get( + f"/api/logbook/{start_date.isoformat()}?end_time={end_time.isoformat()}&entity=switch.test_switch&entity_matches_only" + ) + assert response.status == 200 + json_dict = await response.json() + + assert len(json_dict) == 1 + + assert json_dict[0]["name"] == "Alarm" + assert json_dict[0]["message"] == "is triggered" + assert json_dict[0]["entity_id"] == "switch.test_switch" + + async def test_logbook_entity_matches_only_multiple(hass, hass_client): """Test the logbook view with a multiple entities and entity_matches_only.""" await hass.async_add_executor_job(init_recorder_component, hass) diff --git a/tests/components/surepetcare/conftest.py b/tests/components/surepetcare/conftest.py index 43738f22587..cecdaababa9 100644 --- a/tests/components/surepetcare/conftest.py +++ b/tests/components/surepetcare/conftest.py @@ -7,12 +7,16 @@ from surepy import MESTART_RESOURCE from . import MOCK_API_DATA +async def _mock_call(method, resource): + if method == "GET" and resource == MESTART_RESOURCE: + return {"data": MOCK_API_DATA} + + @pytest.fixture async def surepetcare(): """Mock the SurePetcare for easier testing.""" - with patch("surepy.SureAPIClient", autospec=True) as mock_client_class, patch( - "surepy.find_token" - ): + with patch("surepy.SureAPIClient", autospec=True) as mock_client_class: client = mock_client_class.return_value - client.resources = {MESTART_RESOURCE: {"data": MOCK_API_DATA}} + client.resources = {} + client.call = _mock_call yield client diff --git a/tests/components/surepetcare/test_sensor.py b/tests/components/surepetcare/test_sensor.py index 8e7160364ea..cbf69bb97dc 100644 --- a/tests/components/surepetcare/test_sensor.py +++ b/tests/components/surepetcare/test_sensor.py @@ -12,7 +12,7 @@ EXPECTED_ENTITY_IDS = { } -async def test_binary_sensors(hass, surepetcare) -> None: +async def test_sensors(hass, surepetcare) -> None: """Test the generation of unique ids.""" assert await async_setup_component(hass, DOMAIN, MOCK_CONFIG) await hass.async_block_till_done() diff --git a/tests/components/zha/test_config_flow.py b/tests/components/zha/test_config_flow.py index 732b7cf440d..f551e9dac1f 100644 --- a/tests/components/zha/test_config_flow.py +++ b/tests/components/zha/test_config_flow.py @@ -113,6 +113,34 @@ async def test_discovery_via_zeroconf_ip_change(detect_mock, hass): } +@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) +@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True) +async def test_discovery_via_zeroconf_ip_change_ignored(detect_mock, hass): + """Test zeroconf flow that was ignored gets updated.""" + entry = MockConfigEntry( + domain=DOMAIN, + unique_id="tube_zb_gw_cc2652p2_poe", + source=config_entries.SOURCE_IGNORE, + ) + entry.add_to_hass(hass) + + service_info = { + "host": "192.168.1.22", + "port": 6053, + "hostname": "tube_zb_gw_cc2652p2_poe.local.", + "properties": {"address": "tube_zb_gw_cc2652p2_poe.local"}, + } + result = await hass.config_entries.flow.async_init( + "zha", context={"source": SOURCE_ZEROCONF}, data=service_info + ) + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert entry.data[CONF_DEVICE] == { + CONF_DEVICE_PATH: "socket://192.168.1.22:6638", + } + + @patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True) async def test_discovery_via_usb(detect_mock, hass): """Test usb flow -- radio detected.""" @@ -317,6 +345,37 @@ async def test_discovery_via_usb_deconz_ignored(detect_mock, hass): assert result["step_id"] == "confirm" +@patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True) +async def test_discovery_via_usb_zha_ignored_updates(detect_mock, hass): + """Test usb flow that was ignored gets updated.""" + entry = MockConfigEntry( + domain=DOMAIN, + source=config_entries.SOURCE_IGNORE, + data={}, + unique_id="AAAA:AAAA_1234_test_zigbee radio", + ) + entry.add_to_hass(hass) + await hass.async_block_till_done() + discovery_info = { + "device": "/dev/ttyZIGBEE", + "pid": "AAAA", + "vid": "AAAA", + "serial_number": "1234", + "description": "zigbee radio", + "manufacturer": "test", + } + result = await hass.config_entries.flow.async_init( + "zha", context={"source": SOURCE_USB}, data=discovery_info + ) + await hass.async_block_till_done() + + assert result["type"] == RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + assert entry.data[CONF_DEVICE] == { + CONF_DEVICE_PATH: "/dev/ttyZIGBEE", + } + + @patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True)) @patch("zigpy_znp.zigbee.application.ControllerApplication.probe", return_value=True) async def test_discovery_already_setup(detect_mock, hass): diff --git a/tests/components/zwave_js/test_api.py b/tests/components/zwave_js/test_api.py index ee05724a9cb..b3bb924413d 100644 --- a/tests/components/zwave_js/test_api.py +++ b/tests/components/zwave_js/test_api.py @@ -338,7 +338,7 @@ async def test_add_node_secure( assert len(client.async_send_command.call_args_list) == 1 assert client.async_send_command.call_args[0][0] == { "command": "controller.begin_inclusion", - "options": {"inclusionStrategy": InclusionStrategy.SECURITY_S0}, + "options": {"strategy": InclusionStrategy.SECURITY_S0}, } client.async_send_command.reset_mock() @@ -363,7 +363,7 @@ async def test_add_node( assert len(client.async_send_command.call_args_list) == 1 assert client.async_send_command.call_args[0][0] == { "command": "controller.begin_inclusion", - "options": {"inclusionStrategy": InclusionStrategy.INSECURE}, + "options": {"strategy": InclusionStrategy.INSECURE}, } event = Event( @@ -671,7 +671,7 @@ async def test_replace_failed_node_secure( assert client.async_send_command.call_args[0][0] == { "command": "controller.replace_failed_node", "nodeId": nortek_thermostat.node_id, - "options": {"inclusionStrategy": InclusionStrategy.SECURITY_S0}, + "options": {"strategy": InclusionStrategy.SECURITY_S0}, } client.async_send_command.reset_mock() @@ -720,7 +720,7 @@ async def test_replace_failed_node( assert client.async_send_command.call_args[0][0] == { "command": "controller.replace_failed_node", "nodeId": nortek_thermostat.node_id, - "options": {"inclusionStrategy": InclusionStrategy.INSECURE}, + "options": {"strategy": InclusionStrategy.INSECURE}, } client.async_send_command.reset_mock()