This commit is contained in:
Franck Nijhof 2024-12-20 11:45:57 +01:00 committed by GitHub
commit 6974f61703
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 151 additions and 49 deletions

View File

@ -4,8 +4,6 @@ from __future__ import annotations
from typing import Any
from fjaraskupan import COMMAND_LIGHT_ON_OFF
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@ -62,7 +60,6 @@ class Light(CoordinatorEntity[FjaraskupanCoordinator], LightEntity):
if self.is_on:
async with self.coordinator.async_connect_and_update() as device:
await device.send_dim(0)
await device.send_command(COMMAND_LIGHT_ON_OFF)
@property
def is_on(self) -> bool:

View File

@ -14,5 +14,5 @@
"documentation": "https://www.home-assistant.io/integrations/fjaraskupan",
"iot_class": "local_polling",
"loggers": ["bleak", "fjaraskupan"],
"requirements": ["fjaraskupan==2.3.0"]
"requirements": ["fjaraskupan==2.3.2"]
}

View File

@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/freebox",
"iot_class": "local_polling",
"loggers": ["freebox_api"],
"requirements": ["freebox-api==1.1.0"],
"requirements": ["freebox-api==1.2.1"],
"zeroconf": ["_fbx-api._tcp.local."]
}

View File

@ -14,5 +14,5 @@
"documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth",
"iot_class": "local_polling",
"loggers": ["bleak", "bleak_esphome", "gardena_bluetooth"],
"requirements": ["gardena-bluetooth==1.4.4"]
"requirements": ["gardena-bluetooth==1.5.0"]
}

View File

@ -576,7 +576,7 @@ class IntegrationSensor(RestoreSensor):
if (
self._max_sub_interval is not None
and source_state is not None
and (source_state_dec := _decimal_state(source_state.state))
and (source_state_dec := _decimal_state(source_state.state)) is not None
):
@callback

View File

@ -661,7 +661,7 @@ class MQTT:
self.conf.get(CONF_PORT, DEFAULT_PORT),
self.conf.get(CONF_KEEPALIVE, DEFAULT_KEEPALIVE),
)
except OSError as err:
except (OSError, mqtt.WebsocketConnectionError) as err:
_LOGGER.error("Failed to connect to MQTT server due to exception: %s", err)
self._async_connection_result(False)
finally:

View File

@ -566,17 +566,13 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
# shuffle and repeat are not (yet) supported for external sources
self._attr_shuffle = None
self._attr_repeat = None
if TYPE_CHECKING:
assert player.elapsed_time is not None
self._attr_media_position = int(player.elapsed_time)
self._attr_media_position = int(player.elapsed_time or 0)
self._attr_media_position_updated_at = (
utc_from_timestamp(player.elapsed_time_last_updated)
if player.elapsed_time_last_updated
else None
)
if TYPE_CHECKING:
assert player.elapsed_time is not None
self._prev_time = player.elapsed_time
self._prev_time = player.elapsed_time or 0
return
if queue is None:

View File

@ -15,8 +15,8 @@ CONF_REFRESH_TOKEN_CREATION_TIME = "refresh_token_creation_time"
REFRESH_TOKEN_EXPIRY_TIME = timedelta(days=30)
SUPPORTED_DEVICE_TYPES = {
Platform.LIGHT: ["WallStation"],
Platform.SWITCH: ["WallStation"],
Platform.LIGHT: ["WallStation", "WallStation_ESP32"],
Platform.SWITCH: ["WallStation", "WallStation_ESP32"],
}
KNOWN_UNSUPPORTED_DEVICE_TYPES = {
Platform.LIGHT: ["Mms100"],

View File

@ -239,7 +239,6 @@ class NiceGOUpdateCoordinator(DataUpdateCoordinator[dict[str, NiceGODevice]]):
].type, # Device type is not sent in device state update, and it can't change, so we just reuse the existing one
BarrierState(
deviceId=raw_data["deviceId"],
desired=json.loads(raw_data["desired"]),
reported=json.loads(raw_data["reported"]),
connectionState=ConnectionState(
connected=raw_data["connectionState"]["connected"],

View File

@ -21,6 +21,7 @@ from .entity import NiceGOEntity
DEVICE_CLASSES = {
"WallStation": CoverDeviceClass.GARAGE,
"Mms100": CoverDeviceClass.GATE,
"WallStation_ESP32": CoverDeviceClass.GARAGE,
}
PARALLEL_UPDATES = 1

View File

@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["nice_go"],
"requirements": ["nice-go==0.3.10"]
"requirements": ["nice-go==1.0.0"]
}

View File

@ -76,7 +76,7 @@ class OverkizConfigFlow(ConfigFlow, domain=DOMAIN):
for gateway in gateways:
if is_overkiz_gateway(gateway.id):
gateway_id = gateway.id
await self.async_set_unique_id(gateway_id)
await self.async_set_unique_id(gateway_id, raise_on_progress=False)
return user_input

View File

@ -20,7 +20,7 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["boto3", "botocore", "pyhumps", "pyoverkiz", "s3transfer"],
"requirements": ["pyoverkiz==1.15.0"],
"requirements": ["pyoverkiz==1.15.3"],
"zeroconf": [
{
"type": "_kizbox._tcp.local.",

View File

@ -7,7 +7,7 @@
"iot_class": "local_polling",
"loggers": ["roborock"],
"requirements": [
"python-roborock==2.7.2",
"python-roborock==2.8.1",
"vacuum-map-parser-roborock==0.1.2"
]
}

View File

@ -4,6 +4,7 @@ import logging
from typing import Any
from screenlogicpy import ScreenLogicError, ScreenLogicGateway
from screenlogicpy.const.common import ScreenLogicConnectionError
from screenlogicpy.const.data import SHARED_VALUES
from homeassistant.config_entries import ConfigEntry
@ -64,7 +65,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ScreenLogicConfigEntry)
try:
await gateway.async_connect(**connect_info)
await gateway.async_update()
except ScreenLogicError as ex:
except (ScreenLogicConnectionError, ScreenLogicError) as ex:
raise ConfigEntryNotReady(ex.msg) from ex
coordinator = ScreenlogicDataUpdateCoordinator(

View File

@ -45,7 +45,9 @@ class TwinklyConfigFlow(ConfigFlow, domain=DOMAIN):
except (TimeoutError, ClientError):
errors[CONF_HOST] = "cannot_connect"
else:
await self.async_set_unique_id(device_info[DEV_ID])
await self.async_set_unique_id(
device_info[DEV_ID], raise_on_progress=False
)
self._abort_if_unique_id_configured()
return self._create_entry_from_device(device_info, host)

View File

@ -25,7 +25,7 @@ if TYPE_CHECKING:
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2024
MINOR_VERSION: Final = 12
PATCH_VERSION: Final = "4"
PATCH_VERSION: Final = "5"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)

View File

@ -5,7 +5,7 @@ aiodiscover==2.1.0
aiodns==3.2.0
aiohasupervisor==0.2.1
aiohttp-fast-zlib==0.2.0
aiohttp==3.11.10
aiohttp==3.11.11
aiohttp_cors==0.7.0
aiozoneinfo==0.2.1
astral==2.2

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "homeassistant"
version = "2024.12.4"
version = "2024.12.5"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"
@ -29,7 +29,7 @@ dependencies = [
# change behavior based on presence of supervisor. Deprecated with #127228
# Lib can be removed with 2025.11
"aiohasupervisor==0.2.1",
"aiohttp==3.11.10",
"aiohttp==3.11.11",
"aiohttp_cors==0.7.0",
"aiohttp-fast-zlib==0.2.0",
"aiozoneinfo==0.2.1",

View File

@ -5,7 +5,7 @@
# Home Assistant Core
aiodns==3.2.0
aiohasupervisor==0.2.1
aiohttp==3.11.10
aiohttp==3.11.11
aiohttp_cors==0.7.0
aiohttp-fast-zlib==0.2.0
aiozoneinfo==0.2.1

View File

@ -912,7 +912,7 @@ fivem-api==0.1.2
fixerio==1.0.0a0
# homeassistant.components.fjaraskupan
fjaraskupan==2.3.0
fjaraskupan==2.3.2
# homeassistant.components.flexit_bacnet
flexit_bacnet==2.2.1
@ -937,7 +937,7 @@ forecast-solar==4.0.0
fortiosapi==1.0.5
# homeassistant.components.freebox
freebox-api==1.1.0
freebox-api==1.2.1
# homeassistant.components.free_mobile
freesms==0.2.0
@ -953,7 +953,7 @@ fyta_cli==0.7.0
gTTS==2.2.4
# homeassistant.components.gardena_bluetooth
gardena-bluetooth==1.4.4
gardena-bluetooth==1.5.0
# homeassistant.components.google_assistant_sdk
gassist-text==0.0.11
@ -1460,7 +1460,7 @@ nextdns==4.0.0
nibe==2.13.0
# homeassistant.components.nice_go
nice-go==0.3.10
nice-go==1.0.0
# homeassistant.components.niko_home_control
niko-home-control==0.2.1
@ -2149,7 +2149,7 @@ pyotgw==2.2.2
pyotp==2.8.0
# homeassistant.components.overkiz
pyoverkiz==1.15.0
pyoverkiz==1.15.3
# homeassistant.components.onewire
pyownet==0.10.0.post1
@ -2402,7 +2402,7 @@ python-rabbitair==0.0.8
python-ripple-api==0.0.3
# homeassistant.components.roborock
python-roborock==2.7.2
python-roborock==2.8.1
# homeassistant.components.smarttub
python-smarttub==0.0.38

View File

@ -771,7 +771,7 @@ fitbit==0.3.1
fivem-api==0.1.2
# homeassistant.components.fjaraskupan
fjaraskupan==2.3.0
fjaraskupan==2.3.2
# homeassistant.components.flexit_bacnet
flexit_bacnet==2.2.1
@ -793,7 +793,7 @@ foobot_async==1.0.0
forecast-solar==4.0.0
# homeassistant.components.freebox
freebox-api==1.1.0
freebox-api==1.2.1
# homeassistant.components.fritz
# homeassistant.components.fritzbox_callmonitor
@ -806,7 +806,7 @@ fyta_cli==0.7.0
gTTS==2.2.4
# homeassistant.components.gardena_bluetooth
gardena-bluetooth==1.4.4
gardena-bluetooth==1.5.0
# homeassistant.components.google_assistant_sdk
gassist-text==0.0.11
@ -1220,7 +1220,7 @@ nextdns==4.0.0
nibe==2.13.0
# homeassistant.components.nice_go
nice-go==0.3.10
nice-go==1.0.0
# homeassistant.components.nfandroidtv
notifications-android-tv==0.1.5
@ -1736,7 +1736,7 @@ pyotgw==2.2.2
pyotp==2.8.0
# homeassistant.components.overkiz
pyoverkiz==1.15.0
pyoverkiz==1.15.3
# homeassistant.components.onewire
pyownet==0.10.0.post1
@ -1923,7 +1923,7 @@ python-picnic-api==1.1.0
python-rabbitair==0.0.8
# homeassistant.components.roborock
python-roborock==2.7.2
python-roborock==2.8.1
# homeassistant.components.smarttub
python-smarttub==0.0.38

View File

@ -843,6 +843,39 @@ async def test_on_valid_source_expect_update_on_time(
assert float(state.state) < 1.8
async def test_on_0_source_expect_0_and_update_when_source_gets_positive(
hass: HomeAssistant,
) -> None:
"""Test whether time based integration updates the integral on a valid zero source."""
start_time = dt_util.utcnow()
with freeze_time(start_time) as freezer:
await _setup_integral_sensor(hass, max_sub_interval=DEFAULT_MAX_SUB_INTERVAL)
await _update_source_sensor(hass, 0)
await hass.async_block_till_done()
# wait one minute and one second
freezer.tick(61)
async_fire_time_changed(hass, dt_util.now())
await hass.async_block_till_done()
state = hass.states.get("sensor.integration")
assert condition.async_numeric_state(hass, state) is True
assert float(state.state) == 0 # integral is 0 after integration of 0
# wait one second and update state
freezer.tick(1)
async_fire_time_changed(hass, dt_util.now())
await _update_source_sensor(hass, 100)
await hass.async_block_till_done()
state = hass.states.get("sensor.integration")
# approx 100*1/3600 (right method after 1 second since last integration)
assert 0.027 < float(state.state) < 0.029
async def test_on_unvailable_source_expect_no_update_on_time(
hass: HomeAssistant,
) -> None:

View File

@ -1403,8 +1403,15 @@ async def test_handle_mqtt_timeout_on_callback(
assert not mock_debouncer.is_set()
@pytest.mark.parametrize(
"exception",
[
OSError("Connection error"),
paho_mqtt.WebsocketConnectionError("Connection error"),
],
)
async def test_setup_raises_config_entry_not_ready_if_no_connect_broker(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, exception: Exception
) -> None:
"""Test for setup failure if connection to broker is missing."""
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={mqtt.CONF_BROKER: "test-broker"})
@ -1413,7 +1420,7 @@ async def test_setup_raises_config_entry_not_ready_if_no_connect_broker(
with patch(
"homeassistant.components.mqtt.async_client.AsyncMQTTClient"
) as mock_client:
mock_client().connect = MagicMock(side_effect=OSError("Connection error"))
mock_client().connect = MagicMock(side_effect=exception)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert "Failed to connect to MQTT server due to exception:" in caplog.text

View File

@ -20,7 +20,7 @@
"power",
"enqueue"
],
"elapsed_time": 0,
"elapsed_time": null,
"elapsed_time_last_updated": 0,
"state": "idle",
"volume_level": 20,

View File

@ -11,7 +11,6 @@
],
"state": {
"deviceId": "1",
"desired": { "key": "value" },
"reported": {
"displayName": "Test Garage 1",
"autoDisabled": false,
@ -42,7 +41,6 @@
],
"state": {
"deviceId": "2",
"desired": { "key": "value" },
"reported": {
"displayName": "Test Garage 2",
"autoDisabled": false,
@ -73,7 +71,6 @@
],
"state": {
"deviceId": "3",
"desired": { "key": "value" },
"reported": {
"displayName": "Test Garage 3",
"autoDisabled": false,
@ -101,7 +98,6 @@
],
"state": {
"deviceId": "4",
"desired": { "key": "value" },
"reported": {
"displayName": "Test Garage 4",
"autoDisabled": false,

View File

@ -81,7 +81,6 @@ async def test_firmware_update_required(
"displayName": "test-display-name",
"migrationStatus": "NOT_STARTED",
},
desired=None,
connectionState=None,
version=None,
timestamp=None,

View File

@ -4,12 +4,14 @@ from dataclasses import dataclass
from unittest.mock import DEFAULT, patch
import pytest
from screenlogicpy import ScreenLogicGateway
from screenlogicpy import ScreenLogicError, ScreenLogicGateway
from screenlogicpy.const.common import ScreenLogicConnectionError
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
from homeassistant.components.screenlogic import DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.util import slugify
@ -284,3 +286,35 @@ async def test_platform_setup(
for entity_id in tested_entity_ids:
assert hass.states.get(entity_id) is not None
@pytest.mark.parametrize(
"exception",
[ScreenLogicConnectionError, ScreenLogicError],
)
async def test_retry_on_connect_exception(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, exception: Exception
) -> None:
"""Test setup retries on expected exceptions."""
def stub_connect(*args, **kwargs):
raise exception
mock_config_entry.add_to_hass(hass)
with (
patch(
GATEWAY_DISCOVERY_IMPORT_PATH,
return_value={},
),
patch.multiple(
ScreenLogicGateway,
async_connect=stub_connect,
is_connected=False,
_async_connected_request=DEFAULT,
),
):
assert not await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY

View File

@ -5,6 +5,7 @@ from unittest.mock import patch
from homeassistant import config_entries
from homeassistant.components import dhcp
from homeassistant.components.twinkly.const import DOMAIN as TWINKLY_DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import CONF_HOST, CONF_ID, CONF_MODEL, CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
@ -157,3 +158,39 @@ async def test_dhcp_already_exists(hass: HomeAssistant) -> None:
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "already_configured"
async def test_user_flow_works_discovery(hass: HomeAssistant) -> None:
"""Test user flow can continue after discovery happened."""
client = ClientMock()
with (
patch(
"homeassistant.components.twinkly.config_flow.Twinkly", return_value=client
),
patch("homeassistant.components.twinkly.async_setup_entry", return_value=True),
):
await hass.config_entries.flow.async_init(
TWINKLY_DOMAIN,
context={"source": config_entries.SOURCE_DHCP},
data=dhcp.DhcpServiceInfo(
hostname="Twinkly_XYZ",
ip="1.2.3.4",
macaddress="aabbccddeeff",
),
)
result = await hass.config_entries.flow.async_init(
TWINKLY_DOMAIN,
context={"source": SOURCE_USER},
)
assert len(hass.config_entries.flow.async_progress(TWINKLY_DOMAIN)) == 2
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "10.0.0.131"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
# Verify the discovery flow was aborted
assert not hass.config_entries.flow.async_progress(TWINKLY_DOMAIN)