Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ def restore_entities() -> None:
)


# The pylint disable is needed because of https://github.com/pylint-dev/pylint/issues/9138
class DevoloScannerEntity( # pylint: disable=hass-enforce-class-module
class DevoloScannerEntity(
CoordinatorEntity[DevoloDataUpdateCoordinator[dict[str, ConnectedStationInfo]]],
ScannerEntity,
):
Expand Down
9 changes: 9 additions & 0 deletions homeassistant/components/sma/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ def __init__(
url = f"{protocol}://{entry.data[CONF_HOST]}"

self._sensor = pysma_sensor
self._serial = coordinator.data.sma_device_info["serial"]
assert entry.unique_id

self._attr_device_info = DeviceInfo(
Expand Down Expand Up @@ -902,6 +903,14 @@ def name(self) -> str:

return f"{name_prefix} {super().name}"

@property
def available(self) -> bool:
"""Return if the device is available."""
return (
super().available
and self._serial == self.coordinator.data.sma_device_info["serial"]
)

@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
Expand Down
14 changes: 13 additions & 1 deletion homeassistant/components/volvo/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class VolvoButtonDescription(VolvoEntityDescription, ButtonEntityDescription):

api_command: str
required_command_key: str
data: dict[str, int] | None = None


_DESCRIPTIONS: tuple[VolvoButtonDescription, ...] = (
Expand All @@ -37,6 +38,17 @@ class VolvoButtonDescription(VolvoEntityDescription, ButtonEntityDescription):
api_command="climatization-stop",
required_command_key="CLIMATIZATION_STOP",
),
VolvoButtonDescription(
key="engine_start",
api_command="engine-start",
required_command_key="ENGINE_START",
data={"runtimeMinutes": 15},
),
VolvoButtonDescription(
key="engine_stop",
api_command="engine-stop",
required_command_key="ENGINE_STOP",
),
VolvoButtonDescription(
key="flash",
api_command="flash",
Expand Down Expand Up @@ -84,7 +96,7 @@ async def async_press(self) -> None:

try:
result = await self.entry.runtime_data.context.api.async_execute_command(
self.entity_description.api_command
self.entity_description.api_command, self.entity_description.data
)
except VolvoApiException as ex:
_LOGGER.debug("Command '%s' error", command)
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/volvo/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@
"climatization_stop": {
"default": "mdi:air-conditioner"
},
"engine_start": {
"default": "mdi:engine"
},
"engine_stop": {
"default": "mdi:engine-off"
},
"flash": {
"default": "mdi:alarm-light-outline"
},
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/volvo/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@
"climatization_stop": {
"name": "Stop climatization"
},
"engine_start": {
"name": "Start engine"
},
"engine_stop": {
"name": "Stop engine"
},
"flash": {
"name": "Flash"
},
Expand Down
34 changes: 31 additions & 3 deletions tests/components/sma/test_init.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
"""Test the sma init file."""

from collections.abc import AsyncGenerator
from collections.abc import AsyncGenerator, Generator

from pysma.exceptions import (
SmaAuthenticationException,
SmaConnectionException,
SmaReadException,
)
import pytest

from homeassistant.components.sma.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntryState
from homeassistant.core import HomeAssistant

from . import MOCK_DEVICE, MOCK_USER_INPUT
from . import MOCK_DEVICE, MOCK_USER_INPUT, setup_integration

from tests.common import MockConfigEntry

Expand All @@ -30,3 +37,24 @@ async def test_migrate_entry_minor_version_1_2(
assert entry.version == 1
assert entry.minor_version == 2
assert entry.unique_id == str(MOCK_DEVICE["serial"])


@pytest.mark.parametrize(
("exception", "expected_state"),
[
(SmaConnectionException, ConfigEntryState.SETUP_RETRY),
(SmaAuthenticationException, ConfigEntryState.SETUP_ERROR),
(SmaReadException, ConfigEntryState.SETUP_RETRY),
],
)
async def test_setup_exceptions(
hass: HomeAssistant,
mock_sma_client: Generator,
mock_config_entry: MockConfigEntry,
exception: Exception,
expected_state: ConfigEntryState,
) -> None:
"""Test the _async_setup."""
mock_sma_client.device_info.side_effect = exception
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state == expected_state
44 changes: 42 additions & 2 deletions tests/components/sma/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,25 @@
from collections.abc import Generator
from unittest.mock import patch

from freezegun.api import FrozenDateTimeFactory
from pysma.exceptions import (
SmaAuthenticationException,
SmaConnectionException,
SmaReadException,
)
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.const import Platform
from homeassistant.components.sma.const import DEFAULT_SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util import dt as dt_util

from . import setup_integration

from tests.common import MockConfigEntry, snapshot_platform
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform


@pytest.mark.usefixtures("entity_registry_enabled_by_default")
Expand All @@ -32,3 +41,34 @@ async def test_all_entities(
await snapshot_platform(
hass, entity_registry, snapshot, mock_config_entry.entry_id
)


@pytest.mark.usefixtures("entity_registry_enabled_by_default")
@pytest.mark.parametrize(
("exception"),
[
(SmaConnectionException),
(SmaAuthenticationException),
(SmaReadException),
(Exception),
],
)
async def test_refresh_exceptions(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_sma_client: Generator,
freezer: FrozenDateTimeFactory,
exception: Exception,
) -> None:
"""Test the coordinator refresh exceptions."""
await setup_integration(hass, mock_config_entry)
assert mock_config_entry.state is ConfigEntryState.LOADED

mock_sma_client.read.side_effect = exception

freezer.tick(DEFAULT_SCAN_INTERVAL)
async_fire_time_changed(hass, dt_util.utcnow())
await hass.async_block_till_done()

state = hass.states.get("sensor.sma_device_name_battery_capacity_a")
assert state.state == STATE_UNAVAILABLE
96 changes: 96 additions & 0 deletions tests/components/volvo/snapshots/test_button.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,54 @@
'state': 'unknown',
})
# ---
# name: test_button[xc90_petrol_2019][button.volvo_xc90_start_engine-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.volvo_xc90_start_engine',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Start engine',
'platform': 'volvo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'engine_start',
'unique_id': 'yv1abcdefg1234567_engine_start',
'unit_of_measurement': None,
})
# ---
# name: test_button[xc90_petrol_2019][button.volvo_xc90_start_engine-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Volvo XC90 Start engine',
}),
'context': <ANY>,
'entity_id': 'button.volvo_xc90_start_engine',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
# name: test_button[xc90_petrol_2019][button.volvo_xc90_stop_climatization-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
Expand Down Expand Up @@ -959,3 +1007,51 @@
'state': 'unknown',
})
# ---
# name: test_button[xc90_petrol_2019][button.volvo_xc90_stop_engine-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'button',
'entity_category': None,
'entity_id': 'button.volvo_xc90_stop_engine',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Stop engine',
'platform': 'volvo',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'engine_stop',
'unique_id': 'yv1abcdefg1234567_engine_stop',
'unit_of_measurement': None,
})
# ---
# name: test_button[xc90_petrol_2019][button.volvo_xc90_stop_engine-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Volvo XC90 Stop engine',
}),
'context': <ANY>,
'entity_id': 'button.volvo_xc90_stop_engine',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
})
# ---
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"errorCode": "OK",
"errorMessage": null
},
"powerState": "ConnectedStandby",
"powerState": "On",
"playbackState": "Stopped",
"loginState": null,
"focusAppAumid": "4DF9E0F8.Netflix_mcm4njqhnhss8!App",
Expand Down
Loading
Loading