From 350e5ee9a7a5a4dbd21905b6b8b39e7d217c1533 Mon Sep 17 00:00:00 2001 From: Nick Whyte Date: Wed, 16 Aug 2023 21:56:52 +1000 Subject: [PATCH 01/28] Fix ness alarm armed_home state appearing as disarmed/armed_away (#94351) * Fix nessclient arm home appearing as arm away * patch arming mode enum and use dynamic access * Revert "patch arming mode enum and use dynamic access" This reverts commit b9cca8e92bcb382abe364381a8cb1674c32d1d2a. * Remove mock enums --- .../components/ness_alarm/__init__.py | 8 ++-- .../ness_alarm/alarm_control_panel.py | 22 ++++++++-- .../components/ness_alarm/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/ness_alarm/test_init.py | 44 +++++++------------ 6 files changed, 43 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/ness_alarm/__init__.py b/homeassistant/components/ness_alarm/__init__.py index c1d97f781af..b5d30219550 100644 --- a/homeassistant/components/ness_alarm/__init__.py +++ b/homeassistant/components/ness_alarm/__init__.py @@ -3,7 +3,7 @@ from collections import namedtuple import datetime import logging -from nessclient import ArmingState, Client +from nessclient import ArmingMode, ArmingState, Client import voluptuous as vol from homeassistant.components.binary_sensor import ( @@ -136,9 +136,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass, SIGNAL_ZONE_CHANGED, ZoneChangedData(zone_id=zone_id, state=state) ) - def on_state_change(arming_state: ArmingState): + def on_state_change(arming_state: ArmingState, arming_mode: ArmingMode | None): """Receives and propagates arming state updates.""" - async_dispatcher_send(hass, SIGNAL_ARMING_STATE_CHANGED, arming_state) + async_dispatcher_send( + hass, SIGNAL_ARMING_STATE_CHANGED, arming_state, arming_mode + ) client.on_zone_change(on_zone_change) client.on_state_change(on_state_change) diff --git a/homeassistant/components/ness_alarm/alarm_control_panel.py b/homeassistant/components/ness_alarm/alarm_control_panel.py index 2f54b3abde6..92feaba13aa 100644 --- a/homeassistant/components/ness_alarm/alarm_control_panel.py +++ b/homeassistant/components/ness_alarm/alarm_control_panel.py @@ -3,12 +3,15 @@ from __future__ import annotations import logging -from nessclient import ArmingState, Client +from nessclient import ArmingMode, ArmingState, Client import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import AlarmControlPanelEntityFeature from homeassistant.const import ( STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_ARMED_VACATION, STATE_ALARM_ARMING, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, @@ -23,6 +26,15 @@ from . import DATA_NESS, SIGNAL_ARMING_STATE_CHANGED _LOGGER = logging.getLogger(__name__) +ARMING_MODE_TO_STATE = { + ArmingMode.ARMED_AWAY: STATE_ALARM_ARMED_AWAY, + ArmingMode.ARMED_HOME: STATE_ALARM_ARMED_HOME, + ArmingMode.ARMED_DAY: STATE_ALARM_ARMED_AWAY, # no applicable state, fallback to away + ArmingMode.ARMED_NIGHT: STATE_ALARM_ARMED_NIGHT, + ArmingMode.ARMED_VACATION: STATE_ALARM_ARMED_VACATION, + ArmingMode.ARMED_HIGHEST: STATE_ALARM_ARMED_AWAY, # no applicable state, fallback to away +} + async def async_setup_platform( hass: HomeAssistant, @@ -79,7 +91,9 @@ class NessAlarmPanel(alarm.AlarmControlPanelEntity): await self._client.panic(code) @callback - def _handle_arming_state_change(self, arming_state: ArmingState) -> None: + def _handle_arming_state_change( + self, arming_state: ArmingState, arming_mode: ArmingMode | None + ) -> None: """Handle arming state update.""" if arming_state == ArmingState.UNKNOWN: @@ -91,7 +105,9 @@ class NessAlarmPanel(alarm.AlarmControlPanelEntity): elif arming_state == ArmingState.EXIT_DELAY: self._attr_state = STATE_ALARM_ARMING elif arming_state == ArmingState.ARMED: - self._attr_state = STATE_ALARM_ARMED_AWAY + self._attr_state = ARMING_MODE_TO_STATE.get( + arming_mode, STATE_ALARM_ARMED_AWAY + ) elif arming_state == ArmingState.ENTRY_DELAY: self._attr_state = STATE_ALARM_PENDING elif arming_state == ArmingState.TRIGGERED: diff --git a/homeassistant/components/ness_alarm/manifest.json b/homeassistant/components/ness_alarm/manifest.json index d92a3d02c7a..e4c5b5fb344 100644 --- a/homeassistant/components/ness_alarm/manifest.json +++ b/homeassistant/components/ness_alarm/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/ness_alarm", "iot_class": "local_push", "loggers": ["nessclient"], - "requirements": ["nessclient==0.10.0"] + "requirements": ["nessclient==1.0.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index c0a87e85810..6f31913f5d2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1243,7 +1243,7 @@ nad-receiver==0.3.0 ndms2-client==0.1.2 # homeassistant.components.ness_alarm -nessclient==0.10.0 +nessclient==1.0.0 # homeassistant.components.netdata netdata==1.1.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index a9cedeabbac..475d7921357 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -957,7 +957,7 @@ mutesync==0.0.1 ndms2-client==0.1.2 # homeassistant.components.ness_alarm -nessclient==0.10.0 +nessclient==1.0.0 # homeassistant.components.nmap_tracker netmap==0.7.0.2 diff --git a/tests/components/ness_alarm/test_init.py b/tests/components/ness_alarm/test_init.py index 908e23ec795..5bf48e0667e 100644 --- a/tests/components/ness_alarm/test_init.py +++ b/tests/components/ness_alarm/test_init.py @@ -1,7 +1,7 @@ """Tests for the ness_alarm component.""" -from enum import Enum from unittest.mock import MagicMock, patch +from nessclient import ArmingMode, ArmingState import pytest from homeassistant.components import alarm_control_panel @@ -24,6 +24,8 @@ from homeassistant.const import ( SERVICE_ALARM_DISARM, SERVICE_ALARM_TRIGGER, STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMING, STATE_ALARM_DISARMED, STATE_ALARM_PENDING, @@ -84,7 +86,7 @@ async def test_dispatch_state_change(hass: HomeAssistant, mock_nessclient) -> No await hass.async_block_till_done() on_state_change = mock_nessclient.on_state_change.call_args[0][0] - on_state_change(MockArmingState.ARMING) + on_state_change(ArmingState.ARMING, None) await hass.async_block_till_done() assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_ALARM_ARMING) @@ -174,13 +176,16 @@ async def test_dispatch_zone_change(hass: HomeAssistant, mock_nessclient) -> Non async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None: """Test arming state change handing.""" states = [ - (MockArmingState.UNKNOWN, STATE_UNKNOWN), - (MockArmingState.DISARMED, STATE_ALARM_DISARMED), - (MockArmingState.ARMING, STATE_ALARM_ARMING), - (MockArmingState.EXIT_DELAY, STATE_ALARM_ARMING), - (MockArmingState.ARMED, STATE_ALARM_ARMED_AWAY), - (MockArmingState.ENTRY_DELAY, STATE_ALARM_PENDING), - (MockArmingState.TRIGGERED, STATE_ALARM_TRIGGERED), + (ArmingState.UNKNOWN, None, STATE_UNKNOWN), + (ArmingState.DISARMED, None, STATE_ALARM_DISARMED), + (ArmingState.ARMING, None, STATE_ALARM_ARMING), + (ArmingState.EXIT_DELAY, None, STATE_ALARM_ARMING), + (ArmingState.ARMED, None, STATE_ALARM_ARMED_AWAY), + (ArmingState.ARMED, ArmingMode.ARMED_AWAY, STATE_ALARM_ARMED_AWAY), + (ArmingState.ARMED, ArmingMode.ARMED_HOME, STATE_ALARM_ARMED_HOME), + (ArmingState.ARMED, ArmingMode.ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT), + (ArmingState.ENTRY_DELAY, None, STATE_ALARM_PENDING), + (ArmingState.TRIGGERED, None, STATE_ALARM_TRIGGERED), ] await async_setup_component(hass, DOMAIN, VALID_CONFIG) @@ -188,24 +193,12 @@ async def test_arming_state_change(hass: HomeAssistant, mock_nessclient) -> None assert hass.states.is_state("alarm_control_panel.alarm_panel", STATE_UNKNOWN) on_state_change = mock_nessclient.on_state_change.call_args[0][0] - for arming_state, expected_state in states: - on_state_change(arming_state) + for arming_state, arming_mode, expected_state in states: + on_state_change(arming_state, arming_mode) await hass.async_block_till_done() assert hass.states.is_state("alarm_control_panel.alarm_panel", expected_state) -class MockArmingState(Enum): - """Mock nessclient.ArmingState enum.""" - - UNKNOWN = "UNKNOWN" - DISARMED = "DISARMED" - ARMING = "ARMING" - EXIT_DELAY = "EXIT_DELAY" - ARMED = "ARMED" - ENTRY_DELAY = "ENTRY_DELAY" - TRIGGERED = "TRIGGERED" - - class MockClient: """Mock nessclient.Client stub.""" @@ -253,10 +246,5 @@ def mock_nessclient(): with patch( "homeassistant.components.ness_alarm.Client", new=_mock_factory, create=True - ), patch( - "homeassistant.components.ness_alarm.ArmingState", new=MockArmingState - ), patch( - "homeassistant.components.ness_alarm.alarm_control_panel.ArmingState", - new=MockArmingState, ): yield _mock_instance From cf839d0ce4394db439eecc8c14fc32a33cbd88b1 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 18 Aug 2023 10:55:39 +0200 Subject: [PATCH 02/28] Correct modbus config validator: slave/swap (#97798) --- homeassistant/components/modbus/validators.py | 71 +++++++++++-------- tests/components/modbus/test_init.py | 11 ++- tests/components/modbus/test_sensor.py | 2 +- 3 files changed, 53 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index a583b93ea80..a87f8b2367c 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -65,25 +65,14 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]: name = config[CONF_NAME] structure = config.get(CONF_STRUCTURE) slave_count = config.get(CONF_SLAVE_COUNT, 0) + 1 - swap_type = config.get(CONF_SWAP) - if config[CONF_DATA_TYPE] != DataType.CUSTOM: - if structure: - error = f"{name} structure: cannot be mixed with {data_type}" + slave = config.get(CONF_SLAVE, 0) + swap_type = config.get(CONF_SWAP, CONF_SWAP_NONE) + if config[CONF_DATA_TYPE] == DataType.CUSTOM: + if slave or slave_count > 1: + error = f"{name}: `{CONF_STRUCTURE}` illegal with `{CONF_SLAVE_COUNT}` / `{CONF_SLAVE}`" raise vol.Invalid(error) - if data_type not in DEFAULT_STRUCT_FORMAT: - error = f"Error in sensor {name}. data_type `{data_type}` not supported" - raise vol.Invalid(error) - - structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}" - if CONF_COUNT not in config: - config[CONF_COUNT] = DEFAULT_STRUCT_FORMAT[data_type].register_count - if slave_count > 1: - structure = f">{slave_count}{DEFAULT_STRUCT_FORMAT[data_type].struct_id}" - else: - structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}" - else: - if slave_count > 1: - error = f"{name} structure: cannot be mixed with {CONF_SLAVE_COUNT}" + if swap_type != CONF_SWAP_NONE: + error = f"{name}: `{CONF_STRUCTURE}` illegal with `{CONF_SWAP}`" raise vol.Invalid(error) if not structure: error = ( @@ -102,19 +91,43 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]: f"Structure request {size} bytes, " f"but {count} registers have a size of {bytecount} bytes" ) + return { + **config, + CONF_STRUCTURE: structure, + CONF_SWAP: swap_type, + } - if swap_type != CONF_SWAP_NONE: - if swap_type == CONF_SWAP_BYTE: - regs_needed = 1 - else: # CONF_SWAP_WORD_BYTE, CONF_SWAP_WORD - regs_needed = 2 - if count < regs_needed or (count % regs_needed) != 0: - raise vol.Invalid( - f"Error in sensor {name} swap({swap_type}) " - "not possible due to the registers " - f"count: {count}, needed: {regs_needed}" - ) + if structure: + error = f"{name} structure: cannot be mixed with {data_type}" + raise vol.Invalid(error) + if data_type not in DEFAULT_STRUCT_FORMAT: + error = f"Error in sensor {name}. data_type `{data_type}` not supported" + raise vol.Invalid(error) + if (slave or slave_count > 1) and data_type == DataType.STRING: + error = ( + f"{name}: `{data_type}` illegal with `{CONF_SLAVE_COUNT}` / `{CONF_SLAVE}`" + ) + raise vol.Invalid(error) + if CONF_COUNT not in config: + config[CONF_COUNT] = DEFAULT_STRUCT_FORMAT[data_type].register_count + if swap_type != CONF_SWAP_NONE: + if swap_type == CONF_SWAP_BYTE: + regs_needed = 1 + else: # CONF_SWAP_WORD_BYTE, CONF_SWAP_WORD + regs_needed = 2 + count = config[CONF_COUNT] + if count < regs_needed or (count % regs_needed) != 0: + raise vol.Invalid( + f"Error in sensor {name} swap({swap_type}) " + "not possible due to the registers " + f"count: {count}, needed: {regs_needed}" + ) + structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}" + if slave_count > 1: + structure = f">{slave_count}{DEFAULT_STRUCT_FORMAT[data_type].struct_id}" + else: + structure = f">{DEFAULT_STRUCT_FORMAT[data_type].struct_id}" return { **config, CONF_STRUCTURE: structure, diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 2daf722bb05..19c6157133e 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -163,7 +163,6 @@ async def test_number_validator() -> None: CONF_COUNT: 2, CONF_DATA_TYPE: DataType.CUSTOM, CONF_STRUCTURE: ">i", - CONF_SWAP: CONF_SWAP_BYTE, }, ], ) @@ -221,6 +220,16 @@ async def test_ok_struct_validator(do_config) -> None: CONF_STRUCTURE: ">f", CONF_SLAVE_COUNT: 5, }, + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_DATA_TYPE: DataType.STRING, + CONF_SLAVE_COUNT: 2, + }, + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_DATA_TYPE: DataType.INT16, + CONF_SWAP: CONF_SWAP_WORD, + }, ], ) async def test_exception_struct_validator(do_config) -> None: diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index be9ea95d86a..2a230bb125a 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -243,7 +243,7 @@ async def test_config_sensor(hass: HomeAssistant, mock_modbus) -> None: }, ] }, - f"Error in sensor {TEST_ENTITY_NAME} swap(word) not possible due to the registers count: 1, needed: 2", + f"{TEST_ENTITY_NAME}: `structure` illegal with `swap`", ), ], ) From 2a0c121f65effd6dbd8af59954ce1a23af157d3d Mon Sep 17 00:00:00 2001 From: tronikos Date: Fri, 11 Aug 2023 04:47:49 -0700 Subject: [PATCH 03/28] Fix Opower utilities that have different ReadResolution than previously assumed (#97823) --- .../components/opower/coordinator.py | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/opower/coordinator.py b/homeassistant/components/opower/coordinator.py index b346df1211c..4e2b68df579 100644 --- a/homeassistant/components/opower/coordinator.py +++ b/homeassistant/components/opower/coordinator.py @@ -12,6 +12,7 @@ from opower import ( InvalidAuth, MeterType, Opower, + ReadResolution, ) from homeassistant.components.recorder import get_instance @@ -177,44 +178,55 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]): """Get all cost reads since account activation but at different resolutions depending on age. - month resolution for all years (since account activation) - - day resolution for past 3 years - - hour resolution for past 2 months, only for electricity, not gas + - day resolution for past 3 years (if account's read resolution supports it) + - hour resolution for past 2 months (if account's read resolution supports it) """ cost_reads = [] + start = None - end = datetime.now() - timedelta(days=3 * 365) + end = datetime.now() + if account.read_resolution != ReadResolution.BILLING: + end -= timedelta(days=3 * 365) cost_reads += await self.api.async_get_cost_reads( account, AggregateType.BILL, start, end ) + if account.read_resolution == ReadResolution.BILLING: + return cost_reads + start = end if not cost_reads else cost_reads[-1].end_time - end = ( - datetime.now() - timedelta(days=2 * 30) - if account.meter_type == MeterType.ELEC - else datetime.now() - ) + end = datetime.now() + if account.read_resolution != ReadResolution.DAY: + end -= timedelta(days=2 * 30) cost_reads += await self.api.async_get_cost_reads( account, AggregateType.DAY, start, end ) - if account.meter_type == MeterType.ELEC: - start = end if not cost_reads else cost_reads[-1].end_time - end = datetime.now() - cost_reads += await self.api.async_get_cost_reads( - account, AggregateType.HOUR, start, end - ) + if account.read_resolution == ReadResolution.DAY: + return cost_reads + + start = end if not cost_reads else cost_reads[-1].end_time + end = datetime.now() + cost_reads += await self.api.async_get_cost_reads( + account, AggregateType.HOUR, start, end + ) return cost_reads async def _async_get_recent_cost_reads( self, account: Account, last_stat_time: float ) -> list[CostRead]: - """Get cost reads within the past 30 days to allow corrections in data from utilities. - - Hourly for electricity, daily for gas. - """ + """Get cost reads within the past 30 days to allow corrections in data from utilities.""" + if account.read_resolution in [ + ReadResolution.HOUR, + ReadResolution.HALF_HOUR, + ReadResolution.QUARTER_HOUR, + ]: + aggregate_type = AggregateType.HOUR + elif account.read_resolution == ReadResolution.DAY: + aggregate_type = AggregateType.DAY + else: + aggregate_type = AggregateType.BILL return await self.api.async_get_cost_reads( account, - AggregateType.HOUR - if account.meter_type == MeterType.ELEC - else AggregateType.DAY, + aggregate_type, datetime.fromtimestamp(last_stat_time) - timedelta(days=30), datetime.now(), ) From 0daa9722175ecd9574b034f7dacab160c3bdd7c6 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 18 Aug 2023 13:10:13 +0200 Subject: [PATCH 04/28] modbus config: count and slave_count can normally not be mixed. (#97902) --- homeassistant/components/modbus/validators.py | 21 ++++++++++++------- tests/components/modbus/test_init.py | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/modbus/validators.py b/homeassistant/components/modbus/validators.py index a87f8b2367c..44ad596b520 100644 --- a/homeassistant/components/modbus/validators.py +++ b/homeassistant/components/modbus/validators.py @@ -67,6 +67,17 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]: slave_count = config.get(CONF_SLAVE_COUNT, 0) + 1 slave = config.get(CONF_SLAVE, 0) swap_type = config.get(CONF_SWAP, CONF_SWAP_NONE) + if ( + slave_count > 1 + and count > 1 + and data_type not in (DataType.CUSTOM, DataType.STRING) + ): + error = f"{name} {CONF_COUNT} cannot be mixed with {data_type}" + raise vol.Invalid(error) + if config[CONF_DATA_TYPE] != DataType.CUSTOM: + if structure: + error = f"{name} structure: cannot be mixed with {data_type}" + if config[CONF_DATA_TYPE] == DataType.CUSTOM: if slave or slave_count > 1: error = f"{name}: `{CONF_STRUCTURE}` illegal with `{CONF_SLAVE_COUNT}` / `{CONF_SLAVE}`" @@ -96,17 +107,11 @@ def struct_validator(config: dict[str, Any]) -> dict[str, Any]: CONF_STRUCTURE: structure, CONF_SWAP: swap_type, } - - if structure: - error = f"{name} structure: cannot be mixed with {data_type}" - raise vol.Invalid(error) if data_type not in DEFAULT_STRUCT_FORMAT: error = f"Error in sensor {name}. data_type `{data_type}` not supported" raise vol.Invalid(error) - if (slave or slave_count > 1) and data_type == DataType.STRING: - error = ( - f"{name}: `{data_type}` illegal with `{CONF_SLAVE_COUNT}` / `{CONF_SLAVE}`" - ) + if slave_count > 1 and data_type == DataType.STRING: + error = f"{name}: `{data_type}` illegal with `{CONF_SLAVE_COUNT}`" raise vol.Invalid(error) if CONF_COUNT not in config: diff --git a/tests/components/modbus/test_init.py b/tests/components/modbus/test_init.py index 19c6157133e..908e7209fb9 100644 --- a/tests/components/modbus/test_init.py +++ b/tests/components/modbus/test_init.py @@ -230,6 +230,12 @@ async def test_ok_struct_validator(do_config) -> None: CONF_DATA_TYPE: DataType.INT16, CONF_SWAP: CONF_SWAP_WORD, }, + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_COUNT: 2, + CONF_SLAVE_COUNT: 2, + CONF_DATA_TYPE: DataType.INT32, + }, ], ) async def test_exception_struct_validator(do_config) -> None: From d842b2574aa973cb54707cd4ef94303901f70702 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 16 Aug 2023 13:24:41 +0200 Subject: [PATCH 05/28] Create abstraction for Generic YeeLight (#97939) * Create abstraction for Generic YeeLight * Update light.py --- homeassistant/components/yeelight/light.py | 24 ++++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/yeelight/light.py b/homeassistant/components/yeelight/light.py index f5f39e9997d..a442540109a 100644 --- a/homeassistant/components/yeelight/light.py +++ b/homeassistant/components/yeelight/light.py @@ -238,7 +238,7 @@ def _parse_custom_effects(effects_config) -> dict[str, dict[str, Any]]: def _async_cmd(func): """Define a wrapper to catch exceptions from the bulb.""" - async def _async_wrap(self: YeelightGenericLight, *args, **kwargs): + async def _async_wrap(self: YeelightBaseLight, *args, **kwargs): for attempts in range(2): try: _LOGGER.debug("Calling %s with %s %s", func, args, kwargs) @@ -403,8 +403,8 @@ def _async_setup_services(hass: HomeAssistant): ) -class YeelightGenericLight(YeelightEntity, LightEntity): - """Representation of a Yeelight generic light.""" +class YeelightBaseLight(YeelightEntity, LightEntity): + """Abstract Yeelight light.""" _attr_color_mode = ColorMode.BRIGHTNESS _attr_supported_color_modes = {ColorMode.BRIGHTNESS} @@ -861,7 +861,13 @@ class YeelightGenericLight(YeelightEntity, LightEntity): await self._bulb.async_set_scene(scene_class, *args) -class YeelightColorLightSupport(YeelightGenericLight): +class YeelightGenericLight(YeelightBaseLight): + """Representation of a generic Yeelight.""" + + _attr_name = None + + +class YeelightColorLightSupport(YeelightBaseLight): """Representation of a Color Yeelight light support.""" _attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS, ColorMode.RGB} @@ -884,7 +890,7 @@ class YeelightColorLightSupport(YeelightGenericLight): return YEELIGHT_COLOR_EFFECT_LIST -class YeelightWhiteTempLightSupport(YeelightGenericLight): +class YeelightWhiteTempLightSupport(YeelightBaseLight): """Representation of a White temp Yeelight light.""" _attr_name = None @@ -904,7 +910,7 @@ class YeelightNightLightSupport: return PowerMode.NORMAL -class YeelightWithoutNightlightSwitchMixIn(YeelightGenericLight): +class YeelightWithoutNightlightSwitchMixIn(YeelightBaseLight): """A mix-in for yeelights without a nightlight switch.""" @property @@ -940,7 +946,7 @@ class YeelightColorLightWithoutNightlightSwitchLight( class YeelightColorLightWithNightlightSwitch( - YeelightNightLightSupport, YeelightColorLightSupport, YeelightGenericLight + YeelightNightLightSupport, YeelightColorLightSupport, YeelightBaseLight ): """Representation of a Yeelight with rgb support and nightlight. @@ -964,7 +970,7 @@ class YeelightWhiteTempWithoutNightlightSwitch( class YeelightWithNightLight( - YeelightNightLightSupport, YeelightWhiteTempLightSupport, YeelightGenericLight + YeelightNightLightSupport, YeelightWhiteTempLightSupport, YeelightBaseLight ): """Representation of a Yeelight with temp only support and nightlight. @@ -979,7 +985,7 @@ class YeelightWithNightLight( return super().is_on and not self.device.is_nightlight_enabled -class YeelightNightLightMode(YeelightGenericLight): +class YeelightNightLightMode(YeelightBaseLight): """Representation of a Yeelight when in nightlight mode.""" _attr_color_mode = ColorMode.BRIGHTNESS From 0070a5e83d49095919d30fcd74f588db0b61ffbc Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 18 Aug 2023 13:23:04 +0200 Subject: [PATCH 06/28] modbus: Repair swap for slaves (#97960) --- .../components/modbus/base_platform.py | 19 +- homeassistant/components/modbus/climate.py | 2 +- homeassistant/components/modbus/sensor.py | 5 +- tests/components/modbus/test_sensor.py | 183 +++++++++++++++++- 4 files changed, 192 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/modbus/base_platform.py b/homeassistant/components/modbus/base_platform.py index 337919c81f7..bd21e12368e 100644 --- a/homeassistant/components/modbus/base_platform.py +++ b/homeassistant/components/modbus/base_platform.py @@ -48,10 +48,12 @@ from .const import ( CONF_MIN_VALUE, CONF_PRECISION, CONF_SCALE, + CONF_SLAVE_COUNT, CONF_STATE_OFF, CONF_STATE_ON, CONF_SWAP, CONF_SWAP_BYTE, + CONF_SWAP_NONE, CONF_SWAP_WORD, CONF_SWAP_WORD_BYTE, CONF_VERIFY, @@ -155,15 +157,25 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): """Initialize the switch.""" super().__init__(hub, config) self._swap = config[CONF_SWAP] + if self._swap == CONF_SWAP_NONE: + self._swap = None self._data_type = config[CONF_DATA_TYPE] self._structure: str = config[CONF_STRUCTURE] self._precision = config[CONF_PRECISION] self._scale = config[CONF_SCALE] self._offset = config[CONF_OFFSET] - self._count = config[CONF_COUNT] + self._slave_count = config.get(CONF_SLAVE_COUNT, 0) + self._slave_size = self._count = config[CONF_COUNT] - def _swap_registers(self, registers: list[int]) -> list[int]: + def _swap_registers(self, registers: list[int], slave_count: int) -> list[int]: """Do swap as needed.""" + if slave_count: + swapped = [] + for i in range(0, self._slave_count + 1): + inx = i * self._slave_size + inx2 = inx + self._slave_size + swapped.extend(self._swap_registers(registers[inx:inx2], 0)) + return swapped if self._swap in (CONF_SWAP_BYTE, CONF_SWAP_WORD_BYTE): # convert [12][34] --> [21][43] for i, register in enumerate(registers): @@ -191,7 +203,8 @@ class BaseStructPlatform(BasePlatform, RestoreEntity): def unpack_structure_result(self, registers: list[int]) -> str | None: """Convert registers to proper result.""" - registers = self._swap_registers(registers) + if self._swap: + registers = self._swap_registers(registers, self._slave_count) byte_string = b"".join([x.to_bytes(2, byteorder="big") for x in registers]) if self._data_type == DataType.STRING: return byte_string.decode() diff --git a/homeassistant/components/modbus/climate.py b/homeassistant/components/modbus/climate.py index 0a8b8dabeeb..3e5155b574c 100644 --- a/homeassistant/components/modbus/climate.py +++ b/homeassistant/components/modbus/climate.py @@ -206,7 +206,7 @@ class ModbusThermostat(BaseStructPlatform, RestoreEntity, ClimateEntity): int.from_bytes(as_bytes[i : i + 2], "big") for i in range(0, len(as_bytes), 2) ] - registers = self._swap_registers(raw_regs) + registers = self._swap_registers(raw_regs, 0) if self._data_type in ( DataType.INT16, diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index a1c89677e4f..61230969443 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -132,10 +132,7 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity): self._coordinator.async_set_updated_data(None) else: self._attr_native_value = result - if self._attr_native_value is None: - self._attr_available = False - else: - self._attr_available = True + self._attr_available = self._attr_native_value is not None self._lazy_errors = self._lazy_error_count self.async_write_ha_state() diff --git a/tests/components/modbus/test_sensor.py b/tests/components/modbus/test_sensor.py index 2a230bb125a..e973f226913 100644 --- a/tests/components/modbus/test_sensor.py +++ b/tests/components/modbus/test_sensor.py @@ -603,9 +603,7 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None: CONF_ADDRESS: 51, CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING, CONF_DATA_TYPE: DataType.UINT32, - CONF_SCALE: 1, - CONF_OFFSET: 0, - CONF_PRECISION: 0, + CONF_SCAN_INTERVAL: 1, }, ], }, @@ -677,17 +675,184 @@ async def test_all_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None: ) async def test_slave_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None: """Run test for sensor.""" - assert hass.states.get(ENTITY_ID).state == expected[0] entity_registry = er.async_get(hass) - - for i in range(1, len(expected)): - entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}_{i}".replace(" ", "_") - assert hass.states.get(entity_id).state == expected[i] - unique_id = f"{SLAVE_UNIQUE_ID}_{i}" + for i in range(0, len(expected)): + entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") + unique_id = f"{SLAVE_UNIQUE_ID}" + if i: + entity_id = f"{entity_id}_{i}" + unique_id = f"{unique_id}_{i}" entry = entity_registry.async_get(entity_id) + state = hass.states.get(entity_id).state + assert state == expected[i] assert entry.unique_id == unique_id +@pytest.mark.parametrize( + "do_config", + [ + { + CONF_SENSORS: [ + { + CONF_NAME: TEST_ENTITY_NAME, + CONF_ADDRESS: 51, + CONF_INPUT_TYPE: CALL_TYPE_REGISTER_HOLDING, + CONF_SCAN_INTERVAL: 1, + }, + ], + }, + ], +) +@pytest.mark.parametrize( + ("config_addon", "register_words", "do_exception", "expected"), + [ + ( + { + CONF_SLAVE_COUNT: 0, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_SWAP: CONF_SWAP_BYTE, + CONF_DATA_TYPE: DataType.UINT16, + }, + [0x0102], + False, + [str(int(0x0201))], + ), + ( + { + CONF_SLAVE_COUNT: 0, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_SWAP: CONF_SWAP_WORD, + CONF_DATA_TYPE: DataType.UINT32, + }, + [0x0102, 0x0304], + False, + [str(int(0x03040102))], + ), + ( + { + CONF_SLAVE_COUNT: 0, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_SWAP: CONF_SWAP_WORD, + CONF_DATA_TYPE: DataType.UINT64, + }, + [0x0102, 0x0304, 0x0506, 0x0708], + False, + [str(int(0x0708050603040102))], + ), + ( + { + CONF_SLAVE_COUNT: 1, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_DATA_TYPE: DataType.UINT16, + CONF_SWAP: CONF_SWAP_BYTE, + }, + [0x0102, 0x0304], + False, + [str(int(0x0201)), str(int(0x0403))], + ), + ( + { + CONF_SLAVE_COUNT: 1, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_DATA_TYPE: DataType.UINT32, + CONF_SWAP: CONF_SWAP_WORD, + }, + [0x0102, 0x0304, 0x0506, 0x0708], + False, + [str(int(0x03040102)), str(int(0x07080506))], + ), + ( + { + CONF_SLAVE_COUNT: 1, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_DATA_TYPE: DataType.UINT64, + CONF_SWAP: CONF_SWAP_WORD, + }, + [0x0102, 0x0304, 0x0506, 0x0708, 0x0901, 0x0902, 0x0903, 0x0904], + False, + [str(int(0x0708050603040102)), str(int(0x0904090309020901))], + ), + ( + { + CONF_SLAVE_COUNT: 3, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_DATA_TYPE: DataType.UINT16, + CONF_SWAP: CONF_SWAP_BYTE, + }, + [0x0102, 0x0304, 0x0506, 0x0708], + False, + [str(int(0x0201)), str(int(0x0403)), str(int(0x0605)), str(int(0x0807))], + ), + ( + { + CONF_SLAVE_COUNT: 3, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_DATA_TYPE: DataType.UINT32, + CONF_SWAP: CONF_SWAP_WORD, + }, + [ + 0x0102, + 0x0304, + 0x0506, + 0x0708, + 0x090A, + 0x0B0C, + 0x0D0E, + 0x0F00, + ], + False, + [ + str(int(0x03040102)), + str(int(0x07080506)), + str(int(0x0B0C090A)), + str(int(0x0F000D0E)), + ], + ), + ( + { + CONF_SLAVE_COUNT: 3, + CONF_UNIQUE_ID: SLAVE_UNIQUE_ID, + CONF_DATA_TYPE: DataType.UINT64, + CONF_SWAP: CONF_SWAP_WORD, + }, + [ + 0x0601, + 0x0602, + 0x0603, + 0x0604, + 0x0701, + 0x0702, + 0x0703, + 0x0704, + 0x0801, + 0x0802, + 0x0803, + 0x0804, + 0x0901, + 0x0902, + 0x0903, + 0x0904, + ], + False, + [ + str(int(0x0604060306020601)), + str(int(0x0704070307020701)), + str(int(0x0804080308020801)), + str(int(0x0904090309020901)), + ], + ), + ], +) +async def test_slave_swap_sensor(hass: HomeAssistant, mock_do_cycle, expected) -> None: + """Run test for sensor.""" + for i in range(0, len(expected)): + entity_id = f"{SENSOR_DOMAIN}.{TEST_ENTITY_NAME}".replace(" ", "_") + if i: + entity_id = f"{entity_id}_{i}" + state = hass.states.get(entity_id).state + assert state == expected[i] + + @pytest.mark.parametrize( "do_config", [ From 3da1a611c2ded4ad03282969810f856d85629f19 Mon Sep 17 00:00:00 2001 From: VidFerris <29590790+VidFerris@users.noreply.github.com> Date: Wed, 16 Aug 2023 20:57:16 +1000 Subject: [PATCH 07/28] Use Local Timezone for Withings Integration (#98137) --- homeassistant/components/withings/common.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index 9282e3977c1..ef3b6456d20 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -438,22 +438,16 @@ class DataManager: async def async_get_sleep_summary(self) -> dict[Measurement, Any]: """Get the sleep summary data.""" _LOGGER.debug("Updating withing sleep summary") - now = dt_util.utcnow() + now = dt_util.now() yesterday = now - datetime.timedelta(days=1) - yesterday_noon = datetime.datetime( - yesterday.year, - yesterday.month, - yesterday.day, - 12, - 0, - 0, - 0, - datetime.UTC, + yesterday_noon = dt_util.start_of_local_day(yesterday) + datetime.timedelta( + hours=12 ) + yesterday_noon_utc = dt_util.as_utc(yesterday_noon) def get_sleep_summary() -> SleepGetSummaryResponse: return self._api.sleep_get_summary( - lastupdate=yesterday_noon, + lastupdate=yesterday_noon_utc, data_fields=[ GetSleepSummaryField.BREATHING_DISTURBANCES_INTENSITY, GetSleepSummaryField.DEEP_SLEEP_DURATION, From 16d2c8043777fea8db0ceac7f00c14b0d3a05a71 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 12 Aug 2023 11:37:43 -0700 Subject: [PATCH 08/28] Bump pyrainbird to 4.0.0 (#98271) --- homeassistant/components/rainbird/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/rainbird/manifest.json b/homeassistant/components/rainbird/manifest.json index 986e89783d7..07a0bc0a5f6 100644 --- a/homeassistant/components/rainbird/manifest.json +++ b/homeassistant/components/rainbird/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/rainbird", "iot_class": "local_polling", "loggers": ["pyrainbird"], - "requirements": ["pyrainbird==3.0.0"] + "requirements": ["pyrainbird==4.0.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 6f31913f5d2..5cc39788e33 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1952,7 +1952,7 @@ pyqwikswitch==0.93 pyrail==0.0.3 # homeassistant.components.rainbird -pyrainbird==3.0.0 +pyrainbird==4.0.0 # homeassistant.components.recswitch pyrecswitch==1.0.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 475d7921357..f724938214e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1453,7 +1453,7 @@ pyps4-2ndscreen==1.3.1 pyqwikswitch==0.93 # homeassistant.components.rainbird -pyrainbird==3.0.0 +pyrainbird==4.0.0 # homeassistant.components.risco pyrisco==0.5.7 From 07bb0fc16a0d7f2e6d8d4c32dead9430d818d5d2 Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 12 Aug 2023 12:31:14 -0400 Subject: [PATCH 09/28] Bump Python-Roborock to 0.32.3 (#98303) bump to 0.32.3 --- homeassistant/components/roborock/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roborock/manifest.json b/homeassistant/components/roborock/manifest.json index 05fff332c67..01548a6334c 100644 --- a/homeassistant/components/roborock/manifest.json +++ b/homeassistant/components/roborock/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/roborock", "iot_class": "local_polling", "loggers": ["roborock"], - "requirements": ["python-roborock==0.32.2"] + "requirements": ["python-roborock==0.32.3"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5cc39788e33..bc5126e2115 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2150,7 +2150,7 @@ python-qbittorrent==0.4.3 python-ripple-api==0.0.3 # homeassistant.components.roborock -python-roborock==0.32.2 +python-roborock==0.32.3 # homeassistant.components.smarttub python-smarttub==0.0.33 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f724938214e..47847a71775 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1579,7 +1579,7 @@ python-picnic-api==1.1.0 python-qbittorrent==0.4.3 # homeassistant.components.roborock -python-roborock==0.32.2 +python-roborock==0.32.3 # homeassistant.components.smarttub python-smarttub==0.0.33 From 9f69ab16048b00080b427a732b1446748a33a9e7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 12 Aug 2023 20:10:36 -0500 Subject: [PATCH 10/28] Bump flux-led to 1.0.2 (#98312) changelog: https://github.com/Danielhiversen/flux_led/compare/1.0.1...1.0.2 fixes #98310 --- homeassistant/components/flux_led/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/flux_led/manifest.json b/homeassistant/components/flux_led/manifest.json index 689f984722d..d3274738f75 100644 --- a/homeassistant/components/flux_led/manifest.json +++ b/homeassistant/components/flux_led/manifest.json @@ -54,5 +54,5 @@ "iot_class": "local_push", "loggers": ["flux_led"], "quality_scale": "platinum", - "requirements": ["flux-led==1.0.1"] + "requirements": ["flux-led==1.0.2"] } diff --git a/requirements_all.txt b/requirements_all.txt index bc5126e2115..d9174da9827 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -800,7 +800,7 @@ fjaraskupan==2.2.0 flipr-api==1.5.0 # homeassistant.components.flux_led -flux-led==1.0.1 +flux-led==1.0.2 # homeassistant.components.homekit # homeassistant.components.recorder diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 47847a71775..230286557e4 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -628,7 +628,7 @@ fjaraskupan==2.2.0 flipr-api==1.5.0 # homeassistant.components.flux_led -flux-led==1.0.1 +flux-led==1.0.2 # homeassistant.components.homekit # homeassistant.components.recorder From 69c61a263246d2206863cf8d1af28f65d52c052f Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Mon, 14 Aug 2023 11:51:08 +0200 Subject: [PATCH 11/28] Use default translations by removing names from tplink descriptions (#98338) --- homeassistant/components/tplink/sensor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/homeassistant/components/tplink/sensor.py b/homeassistant/components/tplink/sensor.py index ba4949434f7..46909f39dfe 100644 --- a/homeassistant/components/tplink/sensor.py +++ b/homeassistant/components/tplink/sensor.py @@ -50,7 +50,6 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfPower.WATT, device_class=SensorDeviceClass.POWER, state_class=SensorStateClass.MEASUREMENT, - name="Current Consumption", emeter_attr="power", precision=1, ), @@ -60,7 +59,6 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, - name="Total Consumption", emeter_attr="total", precision=3, ), @@ -70,7 +68,6 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = ( native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, device_class=SensorDeviceClass.ENERGY, state_class=SensorStateClass.TOTAL_INCREASING, - name="Today's Consumption", precision=3, ), TPLinkSensorEntityDescription( From a962d2b28d5e785f7538ec6569eeabf239ddb55f Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Mon, 14 Aug 2023 13:30:25 +0200 Subject: [PATCH 12/28] Fix tts notify config validation (#98381) * Add test * Require either entity_id or tts_service --- homeassistant/components/tts/notify.py | 23 +++++++++++++---------- tests/components/tts/test_notify.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/tts/notify.py b/homeassistant/components/tts/notify.py index 92244fc41f9..c2576e12bb5 100644 --- a/homeassistant/components/tts/notify.py +++ b/homeassistant/components/tts/notify.py @@ -20,16 +20,19 @@ ENTITY_LEGACY_PROVIDER_GROUP = "entity_or_legacy_provider" _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_NAME): cv.string, - vol.Exclusive(CONF_TTS_SERVICE, ENTITY_LEGACY_PROVIDER_GROUP): cv.entity_id, - vol.Exclusive(CONF_ENTITY_ID, ENTITY_LEGACY_PROVIDER_GROUP): cv.entities_domain( - DOMAIN - ), - vol.Required(CONF_MEDIA_PLAYER): cv.entity_id, - vol.Optional(ATTR_LANGUAGE): cv.string, - } +PLATFORM_SCHEMA = vol.All( + cv.has_at_least_one_key(CONF_TTS_SERVICE, CONF_ENTITY_ID), + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NAME): cv.string, + vol.Exclusive(CONF_TTS_SERVICE, ENTITY_LEGACY_PROVIDER_GROUP): cv.entity_id, + vol.Exclusive( + CONF_ENTITY_ID, ENTITY_LEGACY_PROVIDER_GROUP + ): cv.entities_domain(DOMAIN), + vol.Required(CONF_MEDIA_PLAYER): cv.entity_id, + vol.Optional(ATTR_LANGUAGE): cv.string, + } + ), ) diff --git a/tests/components/tts/test_notify.py b/tests/components/tts/test_notify.py index 22ab151b864..1a776140457 100644 --- a/tests/components/tts/test_notify.py +++ b/tests/components/tts/test_notify.py @@ -68,6 +68,21 @@ async def test_setup_platform(hass: HomeAssistant) -> None: assert hass.services.has_service(notify.DOMAIN, "tts_test") +async def test_setup_platform_missing_key(hass: HomeAssistant) -> None: + """Test platform without required tts_service or entity_id key.""" + config = { + notify.DOMAIN: { + "platform": "tts", + "name": "tts_test", + "media_player": "media_player.demo", + } + } + with assert_setup_component(0, notify.DOMAIN): + assert await async_setup_component(hass, notify.DOMAIN, config) + + assert not hass.services.has_service(notify.DOMAIN, "tts_test") + + async def test_setup_legacy_service(hass: HomeAssistant) -> None: """Set up the demo platform and call service.""" calls = async_mock_service(hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) From b76a4e07875e82b6338af73feec409fd701ca101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Matheson=20Wergeland?= Date: Thu, 17 Aug 2023 15:12:35 +0200 Subject: [PATCH 13/28] Fix GoGoGate2 configuration URL when remote access is disabled (#98387) --- homeassistant/components/gogogate2/common.py | 7 ++++--- tests/components/gogogate2/__init__.py | 2 +- tests/components/gogogate2/test_cover.py | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/gogogate2/common.py b/homeassistant/components/gogogate2/common.py index d45a4fb44ec..a6b2f344ab3 100644 --- a/homeassistant/components/gogogate2/common.py +++ b/homeassistant/components/gogogate2/common.py @@ -113,9 +113,10 @@ class GoGoGate2Entity(CoordinatorEntity[DeviceDataUpdateCoordinator]): def device_info(self) -> DeviceInfo: """Device info for the controller.""" data = self.coordinator.data - configuration_url = ( - f"https://{data.remoteaccess}" if data.remoteaccess else None - ) + if data.remoteaccessenabled: + configuration_url = f"https://{data.remoteaccess}" + else: + configuration_url = f"http://{self._config_entry.data[CONF_IP_ADDRESS]}" return DeviceInfo( configuration_url=configuration_url, identifiers={(DOMAIN, str(self._config_entry.unique_id))}, diff --git a/tests/components/gogogate2/__init__.py b/tests/components/gogogate2/__init__.py index f7e3d40a44b..08675c58709 100644 --- a/tests/components/gogogate2/__init__.py +++ b/tests/components/gogogate2/__init__.py @@ -77,7 +77,7 @@ def _mocked_ismartgate_closed_door_response(): ismartgatename="ismartgatename0", model="ismartgatePRO", apiversion="", - remoteaccessenabled=False, + remoteaccessenabled=True, remoteaccess="abc321.blah.blah", firmwareversion="555", pin=123, diff --git a/tests/components/gogogate2/test_cover.py b/tests/components/gogogate2/test_cover.py index 00cc0057d7c..ca6509d53b9 100644 --- a/tests/components/gogogate2/test_cover.py +++ b/tests/components/gogogate2/test_cover.py @@ -340,6 +340,7 @@ async def test_device_info_ismartgate( assert device.name == "mycontroller" assert device.model == "ismartgatePRO" assert device.sw_version == "555" + assert device.configuration_url == "https://abc321.blah.blah" @patch("homeassistant.components.gogogate2.common.GogoGate2Api") @@ -375,3 +376,4 @@ async def test_device_info_gogogate2( assert device.name == "mycontroller" assert device.model == "gogogate2" assert device.sw_version == "222" + assert device.configuration_url == "http://127.0.0.1" From a66e30885780a9eac065060db65f3b49fa304793 Mon Sep 17 00:00:00 2001 From: mkmer Date: Wed, 16 Aug 2023 12:59:34 -0400 Subject: [PATCH 14/28] Handle missing keys in Honeywell (#98392) --- homeassistant/components/honeywell/climate.py | 6 +++--- homeassistant/components/honeywell/sensor.py | 3 ++- tests/components/honeywell/test_climate.py | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/honeywell/climate.py b/homeassistant/components/honeywell/climate.py index db31baa53a6..0168e4407f7 100644 --- a/homeassistant/components/honeywell/climate.py +++ b/homeassistant/components/honeywell/climate.py @@ -146,13 +146,13 @@ class HoneywellUSThermostat(ClimateEntity): | ClimateEntityFeature.TARGET_TEMPERATURE_RANGE ) - if device._data["canControlHumidification"]: + if device._data.get("canControlHumidification"): self._attr_supported_features |= ClimateEntityFeature.TARGET_HUMIDITY - if device.raw_ui_data["SwitchEmergencyHeatAllowed"]: + if device.raw_ui_data.get("SwitchEmergencyHeatAllowed"): self._attr_supported_features |= ClimateEntityFeature.AUX_HEAT - if not device._data["hasFan"]: + if not device._data.get("hasFan"): return # not all honeywell fans support all modes diff --git a/homeassistant/components/honeywell/sensor.py b/homeassistant/components/honeywell/sensor.py index ae4ede2a079..6a91e493488 100644 --- a/homeassistant/components/honeywell/sensor.py +++ b/homeassistant/components/honeywell/sensor.py @@ -20,6 +20,7 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType +from . import HoneywellData from .const import DOMAIN, HUMIDITY_STATUS_KEY, TEMPERATURE_STATUS_KEY @@ -71,7 +72,7 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up the Honeywell thermostat.""" - data = hass.data[DOMAIN][config_entry.entry_id] + data: HoneywellData = hass.data[DOMAIN][config_entry.entry_id] sensors = [] for device in data.devices.values(): diff --git a/tests/components/honeywell/test_climate.py b/tests/components/honeywell/test_climate.py index afb49cbffca..4d6989d79e8 100644 --- a/tests/components/honeywell/test_climate.py +++ b/tests/components/honeywell/test_climate.py @@ -48,13 +48,13 @@ FAN_ACTION = "fan_action" PRESET_HOLD = "Hold" -async def test_no_thermostats( +async def test_no_thermostat_options( hass: HomeAssistant, device: MagicMock, config_entry: MagicMock ) -> None: - """Test the setup of the climate entities when there are no appliances available.""" + """Test the setup of the climate entities when there are no additional options available.""" device._data = {} await init_integration(hass, config_entry) - assert len(hass.states.async_all()) == 0 + assert len(hass.states.async_all()) == 1 async def test_static_attributes( From 9bbf855a7d3f909bc70575c22f2238e2137b22aa Mon Sep 17 00:00:00 2001 From: starkillerOG Date: Tue, 15 Aug 2023 10:43:19 +0200 Subject: [PATCH 15/28] Bump Reolink_aio to 0.7.7 (#98425) --- homeassistant/components/reolink/binary_sensor.py | 4 ++++ homeassistant/components/reolink/host.py | 2 +- homeassistant/components/reolink/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/reolink/binary_sensor.py b/homeassistant/components/reolink/binary_sensor.py index 850aa110171..996f2c6b3ab 100644 --- a/homeassistant/components/reolink/binary_sensor.py +++ b/homeassistant/components/reolink/binary_sensor.py @@ -5,6 +5,7 @@ from collections.abc import Callable from dataclasses import dataclass from reolink_aio.api import ( + DUAL_LENS_DUAL_MOTION_MODELS, FACE_DETECTION_TYPE, PERSON_DETECTION_TYPE, PET_DETECTION_TYPE, @@ -128,6 +129,9 @@ class ReolinkBinarySensorEntity(ReolinkChannelCoordinatorEntity, BinarySensorEnt super().__init__(reolink_data, channel) self.entity_description = entity_description + if self._host.api.model in DUAL_LENS_DUAL_MOTION_MODELS: + self._attr_name = f"{entity_description.name} lens {self._channel}" + self._attr_unique_id = ( f"{self._host.unique_id}_{self._channel}_{entity_description.key}" ) diff --git a/homeassistant/components/reolink/host.py b/homeassistant/components/reolink/host.py index 9bcafb8f00d..d505d92b8a9 100644 --- a/homeassistant/components/reolink/host.py +++ b/homeassistant/components/reolink/host.py @@ -163,7 +163,7 @@ class ReolinkHost: else: _LOGGER.debug( "Camera model %s most likely does not push its initial state" - "upon ONVIF subscription, do not check", + " upon ONVIF subscription, do not check", self._api.model, ) self._cancel_onvif_check = async_call_later( diff --git a/homeassistant/components/reolink/manifest.json b/homeassistant/components/reolink/manifest.json index fa61f873cca..f350bb4f948 100644 --- a/homeassistant/components/reolink/manifest.json +++ b/homeassistant/components/reolink/manifest.json @@ -18,5 +18,5 @@ "documentation": "https://www.home-assistant.io/integrations/reolink", "iot_class": "local_push", "loggers": ["reolink_aio"], - "requirements": ["reolink-aio==0.7.6"] + "requirements": ["reolink-aio==0.7.7"] } diff --git a/requirements_all.txt b/requirements_all.txt index d9174da9827..d5d67364c7d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2278,7 +2278,7 @@ renault-api==0.1.13 renson-endura-delta==1.5.0 # homeassistant.components.reolink -reolink-aio==0.7.6 +reolink-aio==0.7.7 # homeassistant.components.idteck_prox rfk101py==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 230286557e4..1c5737d40f2 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1674,7 +1674,7 @@ renault-api==0.1.13 renson-endura-delta==1.5.0 # homeassistant.components.reolink -reolink-aio==0.7.6 +reolink-aio==0.7.7 # homeassistant.components.rflink rflink==0.0.65 From d44d84b430034405954a7260e12c65aea3de2d15 Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Tue, 15 Aug 2023 03:02:38 -0500 Subject: [PATCH 16/28] Update rokuecp to 0.18.1 (#98432) --- homeassistant/components/roku/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/roku/manifest.json b/homeassistant/components/roku/manifest.json index f9b81dc8ddd..6fe70a3ab65 100644 --- a/homeassistant/components/roku/manifest.json +++ b/homeassistant/components/roku/manifest.json @@ -11,7 +11,7 @@ "iot_class": "local_polling", "loggers": ["rokuecp"], "quality_scale": "silver", - "requirements": ["rokuecp==0.18.0"], + "requirements": ["rokuecp==0.18.1"], "ssdp": [ { "st": "roku:ecp", diff --git a/requirements_all.txt b/requirements_all.txt index d5d67364c7d..092f90b5907 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2299,7 +2299,7 @@ rjpl==0.3.6 rocketchat-API==0.6.1 # homeassistant.components.roku -rokuecp==0.18.0 +rokuecp==0.18.1 # homeassistant.components.roomba roombapy==1.6.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1c5737d40f2..51ee7fd315e 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1683,7 +1683,7 @@ rflink==0.0.65 ring-doorbell==0.7.2 # homeassistant.components.roku -rokuecp==0.18.0 +rokuecp==0.18.1 # homeassistant.components.roomba roombapy==1.6.8 From c55b96eb657c966560b84f5f55caec812762e3eb Mon Sep 17 00:00:00 2001 From: Chris Talkington Date: Tue, 15 Aug 2023 02:57:10 -0500 Subject: [PATCH 17/28] Update pyipp to 0.14.3 (#98434) --- homeassistant/components/ipp/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ipp/manifest.json b/homeassistant/components/ipp/manifest.json index 7cdf6767362..e8bd4425ef3 100644 --- a/homeassistant/components/ipp/manifest.json +++ b/homeassistant/components/ipp/manifest.json @@ -8,6 +8,6 @@ "iot_class": "local_polling", "loggers": ["deepmerge", "pyipp"], "quality_scale": "platinum", - "requirements": ["pyipp==0.14.2"], + "requirements": ["pyipp==0.14.3"], "zeroconf": ["_ipps._tcp.local.", "_ipp._tcp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 092f90b5907..63e63a3492f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1746,7 +1746,7 @@ pyintesishome==1.8.0 pyipma==3.0.6 # homeassistant.components.ipp -pyipp==0.14.2 +pyipp==0.14.3 # homeassistant.components.iqvia pyiqvia==2022.04.0 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 51ee7fd315e..d4b24cb834a 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1292,7 +1292,7 @@ pyinsteon==1.4.3 pyipma==3.0.6 # homeassistant.components.ipp -pyipp==0.14.2 +pyipp==0.14.3 # homeassistant.components.iqvia pyiqvia==2022.04.0 From 06d36983fe16961448487ac5d91894f8ce42a97e Mon Sep 17 00:00:00 2001 From: Luca Leonardo Scorcia Date: Fri, 18 Aug 2023 04:52:22 -0400 Subject: [PATCH 18/28] Fix inconsistent lyric temperature unit (#98457) --- homeassistant/components/lyric/climate.py | 22 +++++++++++++--------- homeassistant/components/lyric/sensor.py | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/lyric/climate.py b/homeassistant/components/lyric/climate.py index 099a0a028d0..df90ebcd6cf 100644 --- a/homeassistant/components/lyric/climate.py +++ b/homeassistant/components/lyric/climate.py @@ -21,7 +21,12 @@ from homeassistant.components.climate import ( HVACMode, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TEMPERATURE +from homeassistant.const import ( + ATTR_TEMPERATURE, + PRECISION_HALVES, + PRECISION_WHOLE, + UnitOfTemperature, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_platform @@ -113,7 +118,6 @@ async def async_setup_entry( ), location, device, - hass.config.units.temperature_unit, ) ) @@ -140,10 +144,15 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): description: ClimateEntityDescription, location: LyricLocation, device: LyricDevice, - temperature_unit: str, ) -> None: """Initialize Honeywell Lyric climate entity.""" - self._temperature_unit = temperature_unit + # Use the native temperature unit from the device settings + if device.units == "Fahrenheit": + self._attr_temperature_unit = UnitOfTemperature.FAHRENHEIT + self._attr_precision = PRECISION_WHOLE + else: + self._attr_temperature_unit = UnitOfTemperature.CELSIUS + self._attr_precision = PRECISION_HALVES # Setup supported hvac modes self._attr_hvac_modes = [HVACMode.OFF] @@ -176,11 +185,6 @@ class LyricClimate(LyricDeviceEntity, ClimateEntity): return SUPPORT_FLAGS_LCC return SUPPORT_FLAGS_TCC - @property - def temperature_unit(self) -> str: - """Return the unit of measurement.""" - return self._temperature_unit - @property def current_temperature(self) -> float | None: """Return the current temperature.""" diff --git a/homeassistant/components/lyric/sensor.py b/homeassistant/components/lyric/sensor.py index 1201a675a5d..1e15ff58b18 100644 --- a/homeassistant/components/lyric/sensor.py +++ b/homeassistant/components/lyric/sensor.py @@ -17,7 +17,7 @@ from homeassistant.components.sensor import ( SensorStateClass, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE +from homeassistant.const import PERCENTAGE, UnitOfTemperature from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -76,6 +76,11 @@ async def async_setup_entry( for location in coordinator.data.locations: for device in location.devices: if device.indoorTemperature: + if device.units == "Fahrenheit": + native_temperature_unit = UnitOfTemperature.FAHRENHEIT + else: + native_temperature_unit = UnitOfTemperature.CELSIUS + entities.append( LyricSensor( coordinator, @@ -84,7 +89,7 @@ async def async_setup_entry( name="Indoor Temperature", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=hass.config.units.temperature_unit, + native_unit_of_measurement=native_temperature_unit, value=lambda device: device.indoorTemperature, ), location, @@ -108,6 +113,11 @@ async def async_setup_entry( ) ) if device.outdoorTemperature: + if device.units == "Fahrenheit": + native_temperature_unit = UnitOfTemperature.FAHRENHEIT + else: + native_temperature_unit = UnitOfTemperature.CELSIUS + entities.append( LyricSensor( coordinator, @@ -116,7 +126,7 @@ async def async_setup_entry( name="Outdoor Temperature", device_class=SensorDeviceClass.TEMPERATURE, state_class=SensorStateClass.MEASUREMENT, - native_unit_of_measurement=hass.config.units.temperature_unit, + native_unit_of_measurement=native_temperature_unit, value=lambda device: device.outdoorTemperature, ), location, From a835d07773c30b01629a30fad33e7809bdb499be Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Aug 2023 04:30:47 -0500 Subject: [PATCH 19/28] Bump aiohomekit to 2.6.16 (#98490) --- homeassistant/components/homekit_controller/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 52a91d42e67..5096544ba05 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -14,6 +14,6 @@ "documentation": "https://www.home-assistant.io/integrations/homekit_controller", "iot_class": "local_push", "loggers": ["aiohomekit", "commentjson"], - "requirements": ["aiohomekit==2.6.15"], + "requirements": ["aiohomekit==2.6.16"], "zeroconf": ["_hap._tcp.local.", "_hap._udp.local."] } diff --git a/requirements_all.txt b/requirements_all.txt index 63e63a3492f..5ce3056f8a0 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -249,7 +249,7 @@ aioguardian==2022.07.0 aioharmony==0.2.10 # homeassistant.components.homekit_controller -aiohomekit==2.6.15 +aiohomekit==2.6.16 # homeassistant.components.emulated_hue # homeassistant.components.http diff --git a/requirements_test_all.txt b/requirements_test_all.txt index d4b24cb834a..1fb103c6123 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -227,7 +227,7 @@ aioguardian==2022.07.0 aioharmony==0.2.10 # homeassistant.components.homekit_controller -aiohomekit==2.6.15 +aiohomekit==2.6.16 # homeassistant.components.emulated_hue # homeassistant.components.http From c81c0149a2013777e1c3410ca1b429252e2a37d3 Mon Sep 17 00:00:00 2001 From: tronikos Date: Wed, 16 Aug 2023 02:10:02 -0700 Subject: [PATCH 20/28] Bump opower to 0.0.29 (#98503) --- homeassistant/components/opower/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/opower/manifest.json b/homeassistant/components/opower/manifest.json index 73942231b40..31929df5bf6 100644 --- a/homeassistant/components/opower/manifest.json +++ b/homeassistant/components/opower/manifest.json @@ -6,5 +6,5 @@ "dependencies": ["recorder"], "documentation": "https://www.home-assistant.io/integrations/opower", "iot_class": "cloud_polling", - "requirements": ["opower==0.0.26"] + "requirements": ["opower==0.0.29"] } diff --git a/requirements_all.txt b/requirements_all.txt index 5ce3056f8a0..64625844828 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1368,7 +1368,7 @@ openwrt-luci-rpc==1.1.16 openwrt-ubus-rpc==0.0.2 # homeassistant.components.opower -opower==0.0.26 +opower==0.0.29 # homeassistant.components.oralb oralb-ble==0.17.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 1fb103c6123..95ee58edde0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1037,7 +1037,7 @@ openerz-api==0.2.0 openhomedevice==2.2.0 # homeassistant.components.opower -opower==0.0.26 +opower==0.0.29 # homeassistant.components.oralb oralb-ble==0.17.6 From aaefc29e32c69dfef49ad11420b5dd6ded2b97ae Mon Sep 17 00:00:00 2001 From: Erwin Douna Date: Thu, 17 Aug 2023 16:16:47 +0200 Subject: [PATCH 21/28] Revert "Integration tado bump" (#98505) Revert "Integration tado bump (#97791)" This reverts commit 65365d1db57a5e8cdf58d925c6e52871eb75f6be. --- homeassistant/components/tado/__init__.py | 39 +++++++++++---------- homeassistant/components/tado/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/tado/__init__.py b/homeassistant/components/tado/__init__.py index b57d384124c..1cd21634c8e 100644 --- a/homeassistant/components/tado/__init__.py +++ b/homeassistant/components/tado/__init__.py @@ -163,11 +163,12 @@ class TadoConnector: def setup(self): """Connect to Tado and fetch the zones.""" - self.tado = Tado(self._username, self._password, None, True) + self.tado = Tado(self._username, self._password) + self.tado.setDebugging(True) # Load zones and devices - self.zones = self.tado.get_zones() - self.devices = self.tado.get_devices() - tado_home = self.tado.get_me()["homes"][0] + self.zones = self.tado.getZones() + self.devices = self.tado.getDevices() + tado_home = self.tado.getMe()["homes"][0] self.home_id = tado_home["id"] self.home_name = tado_home["name"] @@ -180,7 +181,7 @@ class TadoConnector: def update_devices(self): """Update the device data from Tado.""" - devices = self.tado.get_devices() + devices = self.tado.getDevices() for device in devices: device_short_serial_no = device["shortSerialNo"] _LOGGER.debug("Updating device %s", device_short_serial_no) @@ -189,7 +190,7 @@ class TadoConnector: INSIDE_TEMPERATURE_MEASUREMENT in device["characteristics"]["capabilities"] ): - device[TEMP_OFFSET] = self.tado.get_device_info( + device[TEMP_OFFSET] = self.tado.getDeviceInfo( device_short_serial_no, TEMP_OFFSET ) except RuntimeError: @@ -217,7 +218,7 @@ class TadoConnector: def update_zones(self): """Update the zone data from Tado.""" try: - zone_states = self.tado.get_zone_states()["zoneStates"] + zone_states = self.tado.getZoneStates()["zoneStates"] except RuntimeError: _LOGGER.error("Unable to connect to Tado while updating zones") return @@ -229,7 +230,7 @@ class TadoConnector: """Update the internal data from Tado.""" _LOGGER.debug("Updating zone %s", zone_id) try: - data = self.tado.get_zone_state(zone_id) + data = self.tado.getZoneState(zone_id) except RuntimeError: _LOGGER.error("Unable to connect to Tado while updating zone %s", zone_id) return @@ -250,8 +251,8 @@ class TadoConnector: def update_home(self): """Update the home data from Tado.""" try: - self.data["weather"] = self.tado.get_weather() - self.data["geofence"] = self.tado.get_home_state() + self.data["weather"] = self.tado.getWeather() + self.data["geofence"] = self.tado.getHomeState() dispatcher_send( self.hass, SIGNAL_TADO_UPDATE_RECEIVED.format(self.home_id, "home", "data"), @@ -264,15 +265,15 @@ class TadoConnector: def get_capabilities(self, zone_id): """Return the capabilities of the devices.""" - return self.tado.get_capabilities(zone_id) + return self.tado.getCapabilities(zone_id) def get_auto_geofencing_supported(self): """Return whether the Tado Home supports auto geofencing.""" - return self.tado.get_auto_geofencing_supported() + return self.tado.getAutoGeofencingSupported() def reset_zone_overlay(self, zone_id): """Reset the zone back to the default operation.""" - self.tado.reset_zone_overlay(zone_id) + self.tado.resetZoneOverlay(zone_id) self.update_zone(zone_id) def set_presence( @@ -281,11 +282,11 @@ class TadoConnector: ): """Set the presence to home, away or auto.""" if presence == PRESET_AWAY: - self.tado.set_away() + self.tado.setAway() elif presence == PRESET_HOME: - self.tado.set_home() + self.tado.setHome() elif presence == PRESET_AUTO: - self.tado.set_auto() + self.tado.setAuto() # Update everything when changing modes self.update_zones() @@ -319,7 +320,7 @@ class TadoConnector: ) try: - self.tado.set_zone_overlay( + self.tado.setZoneOverlay( zone_id, overlay_mode, temperature, @@ -339,7 +340,7 @@ class TadoConnector: def set_zone_off(self, zone_id, overlay_mode, device_type="HEATING"): """Set a zone to off.""" try: - self.tado.set_zone_overlay( + self.tado.setZoneOverlay( zone_id, overlay_mode, None, None, device_type, "OFF" ) except RequestException as exc: @@ -350,6 +351,6 @@ class TadoConnector: def set_temperature_offset(self, device_id, offset): """Set temperature offset of device.""" try: - self.tado.set_temp_offset(device_id, offset) + self.tado.setTempOffset(device_id, offset) except RequestException as exc: _LOGGER.error("Could not set temperature offset: %s", exc) diff --git a/homeassistant/components/tado/manifest.json b/homeassistant/components/tado/manifest.json index bea608514bd..62f7a377239 100644 --- a/homeassistant/components/tado/manifest.json +++ b/homeassistant/components/tado/manifest.json @@ -14,5 +14,5 @@ }, "iot_class": "cloud_polling", "loggers": ["PyTado"], - "requirements": ["python-tado==0.16.0"] + "requirements": ["python-tado==0.15.0"] } diff --git a/requirements_all.txt b/requirements_all.txt index 64625844828..9c3faddfe6f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2159,7 +2159,7 @@ python-smarttub==0.0.33 python-songpal==0.15.2 # homeassistant.components.tado -python-tado==0.16.0 +python-tado==0.15.0 # homeassistant.components.telegram_bot python-telegram-bot==13.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 95ee58edde0..77696e217f0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1588,7 +1588,7 @@ python-smarttub==0.0.33 python-songpal==0.15.2 # homeassistant.components.tado -python-tado==0.16.0 +python-tado==0.15.0 # homeassistant.components.telegram_bot python-telegram-bot==13.1 From 1c7e3005aa1f9b5e14560f20428212362f653823 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Fri, 18 Aug 2023 08:49:43 +0000 Subject: [PATCH 22/28] Fix the availability condition for Shelly N current sensor (#98518) --- homeassistant/components/shelly/sensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index b52e176b521..d1e05e5b829 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -533,7 +533,8 @@ RPC_SENSORS: Final = { native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, device_class=SensorDeviceClass.CURRENT, state_class=SensorStateClass.MEASUREMENT, - available=lambda status: status["n_current"] is not None, + available=lambda status: (status and status["n_current"]) is not None, + removal_condition=lambda _config, status, _key: "n_current" not in status, entity_registry_enabled_default=False, ), "total_current": RpcSensorDescription( From 88352b6ca109d6db2d269afb43ab7f6e0960ad30 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Fri, 18 Aug 2023 10:48:57 +0200 Subject: [PATCH 23/28] Correct number of registers to read for sensors for modbus (#98534) --- homeassistant/components/modbus/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/modbus/sensor.py b/homeassistant/components/modbus/sensor.py index 61230969443..76d8c4e0b5a 100644 --- a/homeassistant/components/modbus/sensor.py +++ b/homeassistant/components/modbus/sensor.py @@ -68,7 +68,7 @@ class ModbusRegisterSensor(BaseStructPlatform, RestoreSensor, SensorEntity): """Initialize the modbus register sensor.""" super().__init__(hub, entry) if slave_count: - self._count = self._count * slave_count + self._count = self._count * (slave_count + 1) self._coordinator: DataUpdateCoordinator[list[int] | None] | None = None self._attr_native_unit_of_measurement = entry.get(CONF_UNIT_OF_MEASUREMENT) self._attr_state_class = entry.get(CONF_STATE_CLASS) From 8dcb04eab1f16eee89ca5b94d2e6a92b8c203f3f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Thu, 17 Aug 2023 17:36:22 +0200 Subject: [PATCH 24/28] Pin setuptools to 68.0.0 (#98582) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0710b813402..b486a7460f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools~=68.0", "wheel~=0.40.0"] +requires = ["setuptools==68.0.0", "wheel~=0.40.0"] build-backend = "setuptools.build_meta" [project] From 1c069539f578d0b39f750926c3c52ff75a63ca97 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 17 Aug 2023 10:39:35 -0500 Subject: [PATCH 25/28] Bump ESPHome recommended BLE version to 2023.8.0 (#98586) --- homeassistant/components/esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/esphome/const.py b/homeassistant/components/esphome/const.py index f0e3972f197..575c57c8672 100644 --- a/homeassistant/components/esphome/const.py +++ b/homeassistant/components/esphome/const.py @@ -11,7 +11,7 @@ DEFAULT_ALLOW_SERVICE_CALLS = True DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False -STABLE_BLE_VERSION_STR = "2023.6.0" +STABLE_BLE_VERSION_STR = "2023.8.0" STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR) PROJECT_URLS = { "esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/", From 7c5b1c8cd2d75c07374713dca9a85ddfcc76317e Mon Sep 17 00:00:00 2001 From: Niels Perfors Date: Fri, 18 Aug 2023 10:26:01 +0200 Subject: [PATCH 26/28] Verisure unpack (#98605) --- .../components/verisure/coordinator.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/verisure/coordinator.py b/homeassistant/components/verisure/coordinator.py index bc3b68922b0..bbfaed0a0a4 100644 --- a/homeassistant/components/verisure/coordinator.py +++ b/homeassistant/components/verisure/coordinator.py @@ -83,13 +83,16 @@ class VerisureDataUpdateCoordinator(DataUpdateCoordinator): raise UpdateFailed("Could not read overview") from err def unpack(overview: list, value: str) -> dict | list: - return next( - ( - item["data"]["installation"][value] - for item in overview - if value in item.get("data", {}).get("installation", {}) - ), - [], + return ( + next( + ( + item["data"]["installation"][value] + for item in overview + if value in item.get("data", {}).get("installation", {}) + ), + [], + ) + or [] ) # Store data in a way Home Assistant can easily consume it From 9291fab0b492cc536f0a1b433c20b36e0038a90f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 18 Aug 2023 13:09:35 +0200 Subject: [PATCH 27/28] Update frontend to 20230802.1 (#98616) --- homeassistant/components/frontend/manifest.json | 2 +- homeassistant/package_constraints.txt | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 84d1d4f5e27..986dfd6ba52 100644 --- a/homeassistant/components/frontend/manifest.json +++ b/homeassistant/components/frontend/manifest.json @@ -20,5 +20,5 @@ "documentation": "https://www.home-assistant.io/integrations/frontend", "integration_type": "system", "quality_scale": "internal", - "requirements": ["home-assistant-frontend==20230802.0"] + "requirements": ["home-assistant-frontend==20230802.1"] } diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index fa5b52478d0..d21007ae08b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -22,7 +22,7 @@ ha-av==10.1.1 hass-nabucasa==0.69.0 hassil==1.2.5 home-assistant-bluetooth==1.10.2 -home-assistant-frontend==20230802.0 +home-assistant-frontend==20230802.1 home-assistant-intents==2023.8.2 httpx==0.24.1 ifaddr==0.2.0 diff --git a/requirements_all.txt b/requirements_all.txt index 9c3faddfe6f..a95ebbc09ed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -988,7 +988,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230802.0 +home-assistant-frontend==20230802.1 # homeassistant.components.conversation home-assistant-intents==2023.8.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 77696e217f0..0178acea600 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -774,7 +774,7 @@ hole==0.8.0 holidays==0.28 # homeassistant.components.frontend -home-assistant-frontend==20230802.0 +home-assistant-frontend==20230802.1 # homeassistant.components.conversation home-assistant-intents==2023.8.2 From ce0f957ce4c31ddc8847c9a16e9feaeefb856507 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 18 Aug 2023 13:56:56 +0200 Subject: [PATCH 28/28] Bumped version to 2023.8.3 --- homeassistant/const.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index bcc9586e54b..db28d072aa5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -7,7 +7,7 @@ from typing import Final APPLICATION_NAME: Final = "HomeAssistant" MAJOR_VERSION: Final = 2023 MINOR_VERSION: Final = 8 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0) diff --git a/pyproject.toml b/pyproject.toml index b486a7460f7..d3e2f065e1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "homeassistant" -version = "2023.8.2" +version = "2023.8.3" license = {text = "Apache-2.0"} description = "Open-source home automation platform running on Python 3." readme = "README.rst"