diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 384c5bfd0ae..63f76e1d6ea 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -791,7 +791,7 @@ class TemperatureSettingTrait(_Trait): if preset in self.preset_to_google: response["thermostatMode"] = self.preset_to_google[preset] else: - response["thermostatMode"] = self.hvac_to_google.get(operation) + response["thermostatMode"] = self.hvac_to_google.get(operation, "none") current_temp = attrs.get(climate.ATTR_CURRENT_TEMPERATURE) if current_temp is not None: diff --git a/homeassistant/components/met/manifest.json b/homeassistant/components/met/manifest.json index 38b77a0afd2..2724818ad49 100644 --- a/homeassistant/components/met/manifest.json +++ b/homeassistant/components/met/manifest.json @@ -3,6 +3,6 @@ "name": "Meteorologisk institutt (Met.no)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/met", - "requirements": ["pyMetno==0.8.1"], + "requirements": ["pyMetno==0.8.2"], "codeowners": ["@danielhiversen", "@thimic"] } diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 7aa08070d67..b2b8e27b8c8 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -319,7 +319,19 @@ class ModbusRegisterSensor(RestoreEntity, SensorEntity): # If unpack() returns a tuple greater than 1, don't try to process the value. # Instead, return the values of unpack(...) separated by commas. if len(val) > 1: - self._value = ",".join(map(str, val)) + # Apply scale and precision to floats and ints + v_result = [] + for entry in val: + v_temp = self._scale * entry + self._offset + + # We could convert int to float, and the code would still work; however + # we lose some precision, and unit tests will fail. Therefore, we do + # the conversion only when it's absolutely necessary. + if isinstance(v_temp, int) and self._precision == 0: + v_result.append(str(v_temp)) + else: + v_result.append(f"{float(v_temp):.{self._precision}f}") + self._value = ",".join(map(str, v_result)) else: val = val[0] diff --git a/homeassistant/components/norway_air/air_quality.py b/homeassistant/components/norway_air/air_quality.py index 788f900ef70..480121846e9 100644 --- a/homeassistant/components/norway_air/air_quality.py +++ b/homeassistant/components/norway_air/air_quality.py @@ -67,7 +67,7 @@ def round_state(func): class AirSensor(AirQualityEntity): - """Representation of an Yr.no sensor.""" + """Representation of an air quality sensor.""" def __init__(self, name, coordinates, forecast, session): """Initialize the sensor.""" diff --git a/homeassistant/components/norway_air/manifest.json b/homeassistant/components/norway_air/manifest.json index 193d96e2a18..5306fa8e3e6 100644 --- a/homeassistant/components/norway_air/manifest.json +++ b/homeassistant/components/norway_air/manifest.json @@ -2,6 +2,6 @@ "domain": "norway_air", "name": "Om Luftkvalitet i Norge (Norway Air)", "documentation": "https://www.home-assistant.io/integrations/norway_air", - "requirements": ["pyMetno==0.8.1"], + "requirements": ["pyMetno==0.8.2"], "codeowners": [] } diff --git a/homeassistant/components/roomba/config_flow.py b/homeassistant/components/roomba/config_flow.py index 45c2d8b9a1b..92d9ff05dc0 100644 --- a/homeassistant/components/roomba/config_flow.py +++ b/homeassistant/components/roomba/config_flow.py @@ -328,9 +328,8 @@ async def _async_discover_roombas(hass, host): discovery = _async_get_roomba_discovery() try: if host: - discovered = [ - await hass.async_add_executor_job(discovery.get, host) - ] + device = await hass.async_add_executor_job(discovery.get, host) + discovered = [device] if device else [] else: discovered = await hass.async_add_executor_job(discovery.get_all) except OSError: diff --git a/homeassistant/const.py b/homeassistant/const.py index 9ebf7516628..472a5401c6a 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 2021 MINOR_VERSION = 4 -PATCH_VERSION = "5" +PATCH_VERSION = "6" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER = (3, 8, 0) diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 52be3866639..f2afe152569 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1203,12 +1203,9 @@ class Script: self._changed() raise - async def _async_stop(self, update_state, spare=None): - aws = [ - asyncio.create_task(run.async_stop()) for run in self._runs if run != spare - ] - if not aws: - return + async def _async_stop( + self, aws: list[asyncio.Task], update_state: bool, spare: _ScriptRun | None + ) -> None: await asyncio.wait(aws) if update_state: self._changed() @@ -1217,7 +1214,15 @@ class Script: self, update_state: bool = True, spare: _ScriptRun | None = None ) -> None: """Stop running script.""" - await asyncio.shield(self._async_stop(update_state, spare)) + # Collect a a list of script runs to stop. This must be done before calling + # asyncio.shield as asyncio.shield yields to the event loop, which would cause + # us to wait for script runs added after the call to async_stop. + aws = [ + asyncio.create_task(run.async_stop()) for run in self._runs if run != spare + ] + if not aws: + return + await asyncio.shield(self._async_stop(aws, update_state, spare)) async def _async_get_condition(self, config): if isinstance(config, template.Template): diff --git a/requirements_all.txt b/requirements_all.txt index df61a7be783..704ac3fdf01 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1229,7 +1229,7 @@ pyHS100==0.3.5.2 # homeassistant.components.met # homeassistant.components.norway_air -pyMetno==0.8.1 +pyMetno==0.8.2 # homeassistant.components.rfxtrx pyRFXtrx==0.26.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5c2fdb06cb0..4808f3c7c2e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -648,7 +648,7 @@ pyHS100==0.3.5.2 # homeassistant.components.met # homeassistant.components.norway_air -pyMetno==0.8.1 +pyMetno==0.8.2 # homeassistant.components.rfxtrx pyRFXtrx==0.26.1 diff --git a/tests/components/modbus/test_modbus_sensor.py b/tests/components/modbus/test_modbus_sensor.py index dd485e59835..516979b22d2 100644 --- a/tests/components/modbus/test_modbus_sensor.py +++ b/tests/components/modbus/test_modbus_sensor.py @@ -14,6 +14,7 @@ from homeassistant.components.modbus.const import ( CONF_REVERSE_ORDER, CONF_SCALE, CONF_SENSORS, + DATA_TYPE_CUSTOM, DATA_TYPE_FLOAT, DATA_TYPE_INT, DATA_TYPE_STRING, @@ -26,6 +27,7 @@ from homeassistant.const import ( CONF_NAME, CONF_OFFSET, CONF_SLAVE, + CONF_STRUCTURE, ) from .conftest import base_config_test, base_test @@ -338,6 +340,7 @@ async def test_config_sensor(hass, do_discovery, do_config): ) async def test_all_sensor(hass, cfg, regs, expected): """Run test for sensor.""" + sensor_name = "modbus_test_sensor" state = await base_test( hass, @@ -352,3 +355,41 @@ async def test_all_sensor(hass, cfg, regs, expected): scan_interval=5, ) assert state == expected + + +async def test_struct_sensor(hass): + """Run test for sensor struct.""" + + sensor_name = "modbus_test_sensor" + # floats: 7.931250095367432, 10.600000381469727, + # 1.000879611487865e-28, 10.566553115844727 + expected = "7.93,10.60,0.00,10.57" + state = await base_test( + hass, + { + CONF_NAME: sensor_name, + CONF_REGISTER: 1234, + CONF_COUNT: 8, + CONF_PRECISION: 2, + CONF_DATA_TYPE: DATA_TYPE_CUSTOM, + CONF_STRUCTURE: ">4f", + }, + sensor_name, + SENSOR_DOMAIN, + CONF_SENSORS, + CONF_REGISTERS, + [ + 0x40FD, + 0xCCCD, + 0x4129, + 0x999A, + 0x10FD, + 0xC0CD, + 0x4129, + 0x109A, + ], + expected, + method_discovery=False, + scan_interval=5, + ) + assert state == expected diff --git a/tests/components/roomba/test_config_flow.py b/tests/components/roomba/test_config_flow.py index ee3b7d4b497..a15ad7e43a6 100644 --- a/tests/components/roomba/test_config_flow.py +++ b/tests/components/roomba/test_config_flow.py @@ -711,7 +711,7 @@ async def test_dhcp_discovery_and_roomba_discovery_finds(hass, discovery_data): @pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES_WITHOUT_MATCHING_IP) async def test_dhcp_discovery_falls_back_to_manual(hass, discovery_data): - """Test we can process the discovery from dhcp but roomba discovery cannot find the device.""" + """Test we can process the discovery from dhcp but roomba discovery cannot find the specific device.""" await setup.async_setup_component(hass, "persistent_notification", {}) mocked_roomba = _create_mocked_roomba( @@ -782,6 +782,68 @@ async def test_dhcp_discovery_falls_back_to_manual(hass, discovery_data): assert len(mock_setup_entry.mock_calls) == 1 +@pytest.mark.parametrize("discovery_data", DHCP_DISCOVERY_DEVICES_WITHOUT_MATCHING_IP) +async def test_dhcp_discovery_no_devices_falls_back_to_manual(hass, discovery_data): + """Test we can process the discovery from dhcp but roomba discovery cannot find any devices.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + + mocked_roomba = _create_mocked_roomba( + roomba_connected=True, + master_state={"state": {"reported": {"name": "myroomba"}}}, + ) + + with patch( + "homeassistant.components.roomba.config_flow.RoombaDiscovery", + _mocked_no_devices_found_discovery, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_DHCP}, + data=discovery_data, + ) + await hass.async_block_till_done() + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["errors"] is None + assert result["step_id"] == "manual" + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_HOST: MOCK_IP, CONF_BLID: "blid"}, + ) + await hass.async_block_till_done() + assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result2["errors"] is None + + with patch( + "homeassistant.components.roomba.config_flow.Roomba", + return_value=mocked_roomba, + ), patch( + "homeassistant.components.roomba.config_flow.RoombaPassword", + _mocked_getpassword, + ), patch( + "homeassistant.components.roomba.async_setup_entry", + return_value=True, + ) as mock_setup_entry: + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {}, + ) + await hass.async_block_till_done() + + assert result3["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result3["title"] == "myroomba" + assert result3["result"].unique_id == "BLID" + assert result3["data"] == { + CONF_BLID: "BLID", + CONF_CONTINUOUS: True, + CONF_DELAY: 1, + CONF_HOST: MOCK_IP, + CONF_PASSWORD: "password", + } + assert len(mock_setup_entry.mock_calls) == 1 + + async def test_dhcp_discovery_with_ignored(hass): """Test ignored entries do not break checking for existing entries.""" await setup.async_setup_component(hass, "persistent_notification", {})