Remove deprecated sensors and update remaning for Alexa Devices (#151230)

This commit is contained in:
Simone Chemelli
2025-09-25 18:59:53 +02:00
committed by Franck Nijhof
parent cdf613d3f8
commit cee88473a2
20 changed files with 250 additions and 209 deletions

View File

@@ -10,6 +10,7 @@ from aioamazondevices.api import AmazonDevice
from aioamazondevices.const import SENSOR_STATE_OFF from aioamazondevices.const import SENSOR_STATE_OFF
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DOMAIN as BINARY_SENSOR_DOMAIN,
BinarySensorDeviceClass, BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription, BinarySensorEntityDescription,
@@ -20,6 +21,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AmazonConfigEntry from .coordinator import AmazonConfigEntry
from .entity import AmazonEntity from .entity import AmazonEntity
from .utils import async_update_unique_id
# Coordinator is used to centralize the data updates # Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@@ -31,6 +33,7 @@ class AmazonBinarySensorEntityDescription(BinarySensorEntityDescription):
is_on_fn: Callable[[AmazonDevice, str], bool] is_on_fn: Callable[[AmazonDevice, str], bool]
is_supported: Callable[[AmazonDevice, str], bool] = lambda device, key: True is_supported: Callable[[AmazonDevice, str], bool] = lambda device, key: True
is_available_fn: Callable[[AmazonDevice, str], bool] = lambda device, key: True
BINARY_SENSORS: Final = ( BINARY_SENSORS: Final = (
@@ -41,46 +44,15 @@ BINARY_SENSORS: Final = (
is_on_fn=lambda device, _: device.online, is_on_fn=lambda device, _: device.online,
), ),
AmazonBinarySensorEntityDescription( AmazonBinarySensorEntityDescription(
key="bluetooth", key="detectionState",
entity_category=EntityCategory.DIAGNOSTIC,
translation_key="bluetooth",
is_on_fn=lambda device, _: device.bluetooth_state,
),
AmazonBinarySensorEntityDescription(
key="babyCryDetectionState",
translation_key="baby_cry_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="beepingApplianceDetectionState",
translation_key="beeping_appliance_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="coughDetectionState",
translation_key="cough_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="dogBarkDetectionState",
translation_key="dog_bark_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="humanPresenceDetectionState",
device_class=BinarySensorDeviceClass.MOTION, device_class=BinarySensorDeviceClass.MOTION,
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF), is_on_fn=lambda device, key: bool(
is_supported=lambda device, key: device.sensors.get(key) is not None, device.sensors[key].value != SENSOR_STATE_OFF
), ),
AmazonBinarySensorEntityDescription(
key="waterSoundsDetectionState",
translation_key="water_sounds_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None, is_supported=lambda device, key: device.sensors.get(key) is not None,
is_available_fn=lambda device, key: (
device.online and device.sensors[key].error is False
),
), ),
) )
@@ -94,6 +66,22 @@ async def async_setup_entry(
coordinator = entry.runtime_data coordinator = entry.runtime_data
# Replace unique id for "detectionState" binary sensor
await async_update_unique_id(
hass,
coordinator,
BINARY_SENSOR_DOMAIN,
"humanPresenceDetectionState",
"detectionState",
)
async_add_entities(
AmazonBinarySensorEntity(coordinator, serial_num, sensor_desc)
for sensor_desc in BINARY_SENSORS
for serial_num in coordinator.data
if sensor_desc.is_supported(coordinator.data[serial_num], sensor_desc.key)
)
known_devices: set[str] = set() known_devices: set[str] = set()
def _check_device() -> None: def _check_device() -> None:
@@ -125,3 +113,13 @@ class AmazonBinarySensorEntity(AmazonEntity, BinarySensorEntity):
return self.entity_description.is_on_fn( return self.entity_description.is_on_fn(
self.device, self.entity_description.key self.device, self.entity_description.key
) )
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
self.entity_description.is_available_fn(
self.device, self.entity_description.key
)
and super().available
)

View File

@@ -64,7 +64,7 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
data = await validate_input(self.hass, user_input) data = await validate_input(self.hass, user_input)
except CannotConnect: except CannotConnect:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except (CannotAuthenticate, TypeError): except CannotAuthenticate:
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
except CannotRetrieveData: except CannotRetrieveData:
errors["base"] = "cannot_retrieve_data" errors["base"] = "cannot_retrieve_data"
@@ -112,7 +112,7 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
) )
except CannotConnect: except CannotConnect:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except (CannotAuthenticate, TypeError): except CannotAuthenticate:
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
except CannotRetrieveData: except CannotRetrieveData:
errors["base"] = "cannot_retrieve_data" errors["base"] = "cannot_retrieve_data"

View File

@@ -68,7 +68,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
translation_key="cannot_retrieve_data_with_error", translation_key="cannot_retrieve_data_with_error",
translation_placeholders={"error": repr(err)}, translation_placeholders={"error": repr(err)},
) from err ) from err
except (CannotAuthenticate, TypeError) as err: except CannotAuthenticate as err:
raise ConfigEntryAuthFailed( raise ConfigEntryAuthFailed(
translation_domain=DOMAIN, translation_domain=DOMAIN,
translation_key="invalid_auth", translation_key="invalid_auth",

View File

@@ -60,7 +60,5 @@ def build_device_data(device: AmazonDevice) -> dict[str, Any]:
"online": device.online, "online": device.online,
"serial number": device.serial_number, "serial number": device.serial_number,
"software version": device.software_version, "software version": device.software_version,
"do not disturb": device.do_not_disturb, "sensors": device.sensors,
"response style": device.response_style,
"bluetooth state": device.bluetooth_state,
} }

View File

@@ -1,44 +1,4 @@
{ {
"entity": {
"binary_sensor": {
"bluetooth": {
"default": "mdi:bluetooth-off",
"state": {
"on": "mdi:bluetooth"
}
},
"baby_cry_detection": {
"default": "mdi:account-voice-off",
"state": {
"on": "mdi:account-voice"
}
},
"beeping_appliance_detection": {
"default": "mdi:bell-off",
"state": {
"on": "mdi:bell-ring"
}
},
"cough_detection": {
"default": "mdi:blur-off",
"state": {
"on": "mdi:blur"
}
},
"dog_bark_detection": {
"default": "mdi:dog-side-off",
"state": {
"on": "mdi:dog-side"
}
},
"water_sounds_detection": {
"default": "mdi:water-pump-off",
"state": {
"on": "mdi:water-pump"
}
}
}
},
"services": { "services": {
"send_sound": { "send_sound": {
"service": "mdi:cast-audio" "service": "mdi:cast-audio"

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aioamazondevices"], "loggers": ["aioamazondevices"],
"quality_scale": "platinum", "quality_scale": "platinum",
"requirements": ["aioamazondevices==6.0.0"] "requirements": ["aioamazondevices==6.2.6"]
} }

View File

@@ -31,6 +31,9 @@ class AmazonSensorEntityDescription(SensorEntityDescription):
"""Amazon Devices sensor entity description.""" """Amazon Devices sensor entity description."""
native_unit_of_measurement_fn: Callable[[AmazonDevice, str], str] | None = None native_unit_of_measurement_fn: Callable[[AmazonDevice, str], str] | None = None
is_available_fn: Callable[[AmazonDevice, str], bool] = lambda device, key: (
device.online and device.sensors[key].error is False
)
SENSORS: Final = ( SENSORS: Final = (
@@ -99,3 +102,13 @@ class AmazonSensorEntity(AmazonEntity, SensorEntity):
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.device.sensors[self.entity_description.key].value return self.device.sensors[self.entity_description.key].value
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
self.entity_description.is_available_fn(
self.device, self.entity_description.key
)
and super().available
)

View File

@@ -58,26 +58,6 @@
} }
}, },
"entity": { "entity": {
"binary_sensor": {
"bluetooth": {
"name": "Bluetooth"
},
"baby_cry_detection": {
"name": "Baby crying"
},
"beeping_appliance_detection": {
"name": "Beeping appliance"
},
"cough_detection": {
"name": "Coughing"
},
"dog_bark_detection": {
"name": "Dog barking"
},
"water_sounds_detection": {
"name": "Water sounds"
}
},
"notify": { "notify": {
"speak": { "speak": {
"name": "Speak" "name": "Speak"

View File

@@ -8,13 +8,17 @@ from typing import TYPE_CHECKING, Any, Final
from aioamazondevices.api import AmazonDevice from aioamazondevices.api import AmazonDevice
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AmazonConfigEntry from .coordinator import AmazonConfigEntry
from .entity import AmazonEntity from .entity import AmazonEntity
from .utils import alexa_api_call from .utils import alexa_api_call, async_update_unique_id
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@@ -24,16 +28,17 @@ class AmazonSwitchEntityDescription(SwitchEntityDescription):
"""Alexa Devices switch entity description.""" """Alexa Devices switch entity description."""
is_on_fn: Callable[[AmazonDevice], bool] is_on_fn: Callable[[AmazonDevice], bool]
subkey: str is_available_fn: Callable[[AmazonDevice, str], bool] = lambda device, key: (
device.online and device.sensors[key].error is False
)
method: str method: str
SWITCHES: Final = ( SWITCHES: Final = (
AmazonSwitchEntityDescription( AmazonSwitchEntityDescription(
key="do_not_disturb", key="dnd",
subkey="AUDIO_PLAYER",
translation_key="do_not_disturb", translation_key="do_not_disturb",
is_on_fn=lambda _device: _device.do_not_disturb, is_on_fn=lambda device: bool(device.sensors["dnd"].value),
method="set_do_not_disturb", method="set_do_not_disturb",
), ),
) )
@@ -48,6 +53,11 @@ async def async_setup_entry(
coordinator = entry.runtime_data coordinator = entry.runtime_data
# Replace unique id for "DND" switch and remove from Speaker Group
await async_update_unique_id(
hass, coordinator, SWITCH_DOMAIN, "do_not_disturb", "dnd"
)
known_devices: set[str] = set() known_devices: set[str] = set()
def _check_device() -> None: def _check_device() -> None:
@@ -59,7 +69,7 @@ async def async_setup_entry(
AmazonSwitchEntity(coordinator, serial_num, switch_desc) AmazonSwitchEntity(coordinator, serial_num, switch_desc)
for switch_desc in SWITCHES for switch_desc in SWITCHES
for serial_num in new_devices for serial_num in new_devices
if switch_desc.subkey in coordinator.data[serial_num].capabilities if switch_desc.key in coordinator.data[serial_num].sensors
) )
_check_device() _check_device()
@@ -94,3 +104,13 @@ class AmazonSwitchEntity(AmazonEntity, SwitchEntity):
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return True if switch is on.""" """Return True if switch is on."""
return self.entity_description.is_on_fn(self.device) return self.entity_description.is_on_fn(self.device)
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
self.entity_description.is_available_fn(
self.device, self.entity_description.key
)
and super().available
)

View File

@@ -6,9 +6,12 @@ from typing import Any, Concatenate
from aioamazondevices.exceptions import CannotConnect, CannotRetrieveData from aioamazondevices.exceptions import CannotConnect, CannotRetrieveData
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.entity_registry as er
from .const import DOMAIN from .const import _LOGGER, DOMAIN
from .coordinator import AmazonDevicesCoordinator
from .entity import AmazonEntity from .entity import AmazonEntity
@@ -38,3 +41,23 @@ def alexa_api_call[_T: AmazonEntity, **_P](
) from err ) from err
return cmd_wrapper return cmd_wrapper
async def async_update_unique_id(
hass: HomeAssistant,
coordinator: AmazonDevicesCoordinator,
domain: str,
old_key: str,
new_key: str,
) -> None:
"""Update unique id for entities created with old format."""
entity_registry = er.async_get(hass)
for serial_num in coordinator.data:
unique_id = f"{serial_num}-{old_key}"
if entity_id := entity_registry.async_get_entity_id(domain, DOMAIN, unique_id):
_LOGGER.debug("Updating unique_id for %s", entity_id)
new_unique_id = unique_id.replace(old_key, new_key)
# Update the registry with the new unique_id
entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id)

2
requirements_all.txt generated
View File

@@ -185,7 +185,7 @@ aioairzone-cloud==0.7.2
aioairzone==1.0.1 aioairzone==1.0.1
# homeassistant.components.alexa_devices # homeassistant.components.alexa_devices
aioamazondevices==6.0.0 aioamazondevices==6.2.6
# homeassistant.components.ambient_network # homeassistant.components.ambient_network
# homeassistant.components.ambient_station # homeassistant.components.ambient_station

View File

@@ -173,7 +173,7 @@ aioairzone-cloud==0.7.2
aioairzone==1.0.1 aioairzone==1.0.1
# homeassistant.components.alexa_devices # homeassistant.components.alexa_devices
aioamazondevices==6.0.0 aioamazondevices==6.2.6
# homeassistant.components.ambient_network # homeassistant.components.ambient_network
# homeassistant.components.ambient_station # homeassistant.components.ambient_station

View File

@@ -18,15 +18,13 @@ TEST_DEVICE_1 = AmazonDevice(
online=True, online=True,
serial_number=TEST_DEVICE_1_SN, serial_number=TEST_DEVICE_1_SN,
software_version="echo_test_software_version", software_version="echo_test_software_version",
do_not_disturb=False,
response_style=None,
bluetooth_state=True,
entity_id="11111111-2222-3333-4444-555555555555", entity_id="11111111-2222-3333-4444-555555555555",
appliance_id="G1234567890123456789012345678A", endpoint_id="G1234567890123456789012345678A",
sensors={ sensors={
"dnd": AmazonDeviceSensor(name="dnd", value=False, error=False, scale=None),
"temperature": AmazonDeviceSensor( "temperature": AmazonDeviceSensor(
name="temperature", value="22.5", scale="CELSIUS" name="temperature", value="22.5", error=False, scale="CELSIUS"
) ),
}, },
) )
@@ -42,14 +40,11 @@ TEST_DEVICE_2 = AmazonDevice(
online=True, online=True,
serial_number=TEST_DEVICE_2_SN, serial_number=TEST_DEVICE_2_SN,
software_version="echo_test_2_software_version", software_version="echo_test_2_software_version",
do_not_disturb=False,
response_style=None,
bluetooth_state=True,
entity_id="11111111-2222-3333-4444-555555555555", entity_id="11111111-2222-3333-4444-555555555555",
appliance_id="G1234567890123456789012345678A", endpoint_id="G1234567890123456789012345678A",
sensors={ sensors={
"temperature": AmazonDeviceSensor( "temperature": AmazonDeviceSensor(
name="temperature", value="22.5", scale="CELSIUS" name="temperature", value="22.5", error=False, scale="CELSIUS"
) )
}, },
) )

View File

@@ -1,52 +1,4 @@
# serializer version: 1 # serializer version: 1
# name: test_all_entities[binary_sensor.echo_test_bluetooth-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': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.echo_test_bluetooth',
'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': 'Bluetooth',
'platform': 'alexa_devices',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'bluetooth',
'unique_id': 'echo_test_serial_number-bluetooth',
'unit_of_measurement': None,
})
# ---
# name: test_all_entities[binary_sensor.echo_test_bluetooth-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Echo Test Bluetooth',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.echo_test_bluetooth',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_all_entities[binary_sensor.echo_test_connectivity-entry] # name: test_all_entities[binary_sensor.echo_test_connectivity-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({

View File

@@ -2,7 +2,6 @@
# name: test_device_diagnostics # name: test_device_diagnostics
dict({ dict({
'account name': 'Echo Test', 'account name': 'Echo Test',
'bluetooth state': True,
'capabilities': list([ 'capabilities': list([
'AUDIO_PLAYER', 'AUDIO_PLAYER',
'MICROPHONE', 'MICROPHONE',
@@ -12,9 +11,17 @@
]), ]),
'device family': 'mine', 'device family': 'mine',
'device type': 'echo', 'device type': 'echo',
'do not disturb': False,
'online': True, 'online': True,
'response style': None, 'sensors': dict({
'dnd': dict({
'__type': "<class 'aioamazondevices.api.AmazonDeviceSensor'>",
'repr': "AmazonDeviceSensor(name='dnd', value=False, error=False, scale=None)",
}),
'temperature': dict({
'__type': "<class 'aioamazondevices.api.AmazonDeviceSensor'>",
'repr': "AmazonDeviceSensor(name='temperature', value='22.5', error=False, scale='CELSIUS')",
}),
}),
'serial number': 'echo_test_serial_number', 'serial number': 'echo_test_serial_number',
'software version': 'echo_test_software_version', 'software version': 'echo_test_software_version',
}) })
@@ -25,7 +32,6 @@
'devices': list([ 'devices': list([
dict({ dict({
'account name': 'Echo Test', 'account name': 'Echo Test',
'bluetooth state': True,
'capabilities': list([ 'capabilities': list([
'AUDIO_PLAYER', 'AUDIO_PLAYER',
'MICROPHONE', 'MICROPHONE',
@@ -35,9 +41,17 @@
]), ]),
'device family': 'mine', 'device family': 'mine',
'device type': 'echo', 'device type': 'echo',
'do not disturb': False,
'online': True, 'online': True,
'response style': None, 'sensors': dict({
'dnd': dict({
'__type': "<class 'aioamazondevices.api.AmazonDeviceSensor'>",
'repr': "AmazonDeviceSensor(name='dnd', value=False, error=False, scale=None)",
}),
'temperature': dict({
'__type': "<class 'aioamazondevices.api.AmazonDeviceSensor'>",
'repr': "AmazonDeviceSensor(name='temperature', value='22.5', error=False, scale='CELSIUS')",
}),
}),
'serial number': 'echo_test_serial_number', 'serial number': 'echo_test_serial_number',
'software version': 'echo_test_software_version', 'software version': 'echo_test_software_version',
}), }),

View File

@@ -4,8 +4,6 @@
tuple( tuple(
dict({ dict({
'account_name': 'Echo Test', 'account_name': 'Echo Test',
'appliance_id': 'G1234567890123456789012345678A',
'bluetooth_state': True,
'capabilities': list([ 'capabilities': list([
'AUDIO_PLAYER', 'AUDIO_PLAYER',
'MICROPHONE', 'MICROPHONE',
@@ -16,12 +14,18 @@
'device_family': 'mine', 'device_family': 'mine',
'device_owner_customer_id': 'amazon_ower_id', 'device_owner_customer_id': 'amazon_ower_id',
'device_type': 'echo', 'device_type': 'echo',
'do_not_disturb': False, 'endpoint_id': 'G1234567890123456789012345678A',
'entity_id': '11111111-2222-3333-4444-555555555555', 'entity_id': '11111111-2222-3333-4444-555555555555',
'online': True, 'online': True,
'response_style': None,
'sensors': dict({ 'sensors': dict({
'dnd': dict({
'error': False,
'name': 'dnd',
'scale': None,
'value': False,
}),
'temperature': dict({ 'temperature': dict({
'error': False,
'name': 'temperature', 'name': 'temperature',
'scale': 'CELSIUS', 'scale': 'CELSIUS',
'value': '22.5', 'value': '22.5',
@@ -41,8 +45,6 @@
tuple( tuple(
dict({ dict({
'account_name': 'Echo Test', 'account_name': 'Echo Test',
'appliance_id': 'G1234567890123456789012345678A',
'bluetooth_state': True,
'capabilities': list([ 'capabilities': list([
'AUDIO_PLAYER', 'AUDIO_PLAYER',
'MICROPHONE', 'MICROPHONE',
@@ -53,12 +55,18 @@
'device_family': 'mine', 'device_family': 'mine',
'device_owner_customer_id': 'amazon_ower_id', 'device_owner_customer_id': 'amazon_ower_id',
'device_type': 'echo', 'device_type': 'echo',
'do_not_disturb': False, 'endpoint_id': 'G1234567890123456789012345678A',
'entity_id': '11111111-2222-3333-4444-555555555555', 'entity_id': '11111111-2222-3333-4444-555555555555',
'online': True, 'online': True,
'response_style': None,
'sensors': dict({ 'sensors': dict({
'dnd': dict({
'error': False,
'name': 'dnd',
'scale': None,
'value': False,
}),
'temperature': dict({ 'temperature': dict({
'error': False,
'name': 'temperature', 'name': 'temperature',
'scale': 'CELSIUS', 'scale': 'CELSIUS',
'value': '22.5', 'value': '22.5',

View File

@@ -30,7 +30,7 @@
'suggested_object_id': None, 'suggested_object_id': None,
'supported_features': 0, 'supported_features': 0,
'translation_key': 'do_not_disturb', 'translation_key': 'do_not_disturb',
'unique_id': 'echo_test_serial_number-do_not_disturb', 'unique_id': 'echo_test_serial_number-dnd',
'unit_of_measurement': None, 'unit_of_measurement': None,
}) })
# --- # ---

View File

@@ -134,10 +134,38 @@ async def test_unit_of_measurement(
mock_amazon_devices_client.get_devices_data.return_value[ mock_amazon_devices_client.get_devices_data.return_value[
TEST_DEVICE_1_SN TEST_DEVICE_1_SN
].sensors = {sensor: AmazonDeviceSensor(name=sensor, value=api_value, scale=scale)} ].sensors = {
sensor: AmazonDeviceSensor(
name=sensor, value=api_value, error=False, scale=scale
)
}
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
assert (state := hass.states.get(entity_id)) assert (state := hass.states.get(entity_id))
assert state.state == state_value assert state.state == state_value
assert state.attributes["unit_of_measurement"] == unit assert state.attributes["unit_of_measurement"] == unit
async def test_sensor_unavailable(
hass: HomeAssistant,
freezer: FrozenDateTimeFactory,
mock_amazon_devices_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test sensor is unavailable."""
entity_id = "sensor.echo_test_illuminance"
mock_amazon_devices_client.get_devices_data.return_value[
TEST_DEVICE_1_SN
].sensors = {
"illuminance": AmazonDeviceSensor(
name="illuminance", value="800", error=True, scale=None
)
}
await setup_integration(hass, mock_config_entry)
assert (state := hass.states.get(entity_id))
assert state.state == STATE_UNAVAILABLE

View File

@@ -1,7 +1,9 @@
"""Tests for the Alexa Devices switch platform.""" """Tests for the Alexa Devices switch platform."""
from copy import deepcopy
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from aioamazondevices.api import AmazonDeviceSensor
from freezegun.api import FrozenDateTimeFactory from freezegun.api import FrozenDateTimeFactory
import pytest import pytest
from syrupy.assertion import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
@@ -23,10 +25,12 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
from . import setup_integration from . import setup_integration
from .conftest import TEST_DEVICE_1_SN from .conftest import TEST_DEVICE_1, TEST_DEVICE_1_SN
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
ENTITY_ID = "switch.echo_test_do_not_disturb"
@pytest.mark.usefixtures("entity_registry_enabled_by_default") @pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_all_entities( async def test_all_entities(
@@ -52,48 +56,59 @@ async def test_switch_dnd(
"""Test switching DND.""" """Test switching DND."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
entity_id = "switch.echo_test_do_not_disturb" assert (state := hass.states.get(ENTITY_ID))
assert (state := hass.states.get(entity_id))
assert state.state == STATE_OFF assert state.state == STATE_OFF
await hass.services.async_call( await hass.services.async_call(
SWITCH_DOMAIN, SWITCH_DOMAIN,
SERVICE_TURN_ON, SERVICE_TURN_ON,
{ATTR_ENTITY_ID: entity_id}, {ATTR_ENTITY_ID: ENTITY_ID},
blocking=True, blocking=True,
) )
assert mock_amazon_devices_client.set_do_not_disturb.call_count == 1 assert mock_amazon_devices_client.set_do_not_disturb.call_count == 1
mock_amazon_devices_client.get_devices_data.return_value[ device_data = deepcopy(TEST_DEVICE_1)
TEST_DEVICE_1_SN device_data.sensors = {
].do_not_disturb = True "dnd": AmazonDeviceSensor(name="dnd", value=True, error=False, scale=None),
"temperature": AmazonDeviceSensor(
name="temperature", value="22.5", error=False, scale="CELSIUS"
),
}
mock_amazon_devices_client.get_devices_data.return_value = {
TEST_DEVICE_1_SN: device_data
}
freezer.tick(SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert (state := hass.states.get(entity_id)) assert (state := hass.states.get(ENTITY_ID))
assert state.state == STATE_ON assert state.state == STATE_ON
await hass.services.async_call( await hass.services.async_call(
SWITCH_DOMAIN, SWITCH_DOMAIN,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: entity_id}, {ATTR_ENTITY_ID: ENTITY_ID},
blocking=True, blocking=True,
) )
mock_amazon_devices_client.get_devices_data.return_value[ device_data.sensors = {
TEST_DEVICE_1_SN "dnd": AmazonDeviceSensor(name="dnd", value=False, error=False, scale=None),
].do_not_disturb = False "temperature": AmazonDeviceSensor(
name="temperature", value="22.5", error=False, scale="CELSIUS"
),
}
mock_amazon_devices_client.get_devices_data.return_value = {
TEST_DEVICE_1_SN: device_data
}
freezer.tick(SCAN_INTERVAL) freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_amazon_devices_client.set_do_not_disturb.call_count == 2 assert mock_amazon_devices_client.set_do_not_disturb.call_count == 2
assert (state := hass.states.get(entity_id)) assert (state := hass.states.get(ENTITY_ID))
assert state.state == STATE_OFF assert state.state == STATE_OFF
@@ -104,16 +119,13 @@ async def test_offline_device(
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
) -> None: ) -> None:
"""Test offline device handling.""" """Test offline device handling."""
entity_id = "switch.echo_test_do_not_disturb"
mock_amazon_devices_client.get_devices_data.return_value[ mock_amazon_devices_client.get_devices_data.return_value[
TEST_DEVICE_1_SN TEST_DEVICE_1_SN
].online = False ].online = False
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
assert (state := hass.states.get(entity_id)) assert (state := hass.states.get(ENTITY_ID))
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
mock_amazon_devices_client.get_devices_data.return_value[ mock_amazon_devices_client.get_devices_data.return_value[
@@ -124,5 +136,5 @@ async def test_offline_device(
async_fire_time_changed(hass) async_fire_time_changed(hass)
await hass.async_block_till_done() await hass.async_block_till_done()
assert (state := hass.states.get(entity_id)) assert (state := hass.states.get(ENTITY_ID))
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE

View File

@@ -10,8 +10,10 @@ from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN, SERVICE_TUR
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
from . import setup_integration from . import setup_integration
from .const import TEST_DEVICE_1_SN
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@@ -54,3 +56,41 @@ async def test_alexa_api_call_exceptions(
assert exc_info.value.translation_domain == DOMAIN assert exc_info.value.translation_domain == DOMAIN
assert exc_info.value.translation_key == key assert exc_info.value.translation_key == key
assert exc_info.value.translation_placeholders == {"error": error} assert exc_info.value.translation_placeholders == {"error": error}
async def test_alexa_unique_id_migration(
hass: HomeAssistant,
mock_amazon_devices_client: AsyncMock,
mock_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test unique_id migration."""
mock_config_entry.add_to_hass(hass)
device = device_registry.async_get_or_create(
config_entry_id=mock_config_entry.entry_id,
identifiers={(DOMAIN, mock_config_entry.entry_id)},
name=mock_config_entry.title,
manufacturer="Amazon",
model="Echo Dot",
entry_type=dr.DeviceEntryType.SERVICE,
)
entity = entity_registry.async_get_or_create(
SWITCH_DOMAIN,
DOMAIN,
unique_id=f"{TEST_DEVICE_1_SN}-do_not_disturb",
device_id=device.id,
config_entry=mock_config_entry,
has_entity_name=True,
)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
migrated_entity = entity_registry.async_get(entity.entity_id)
assert migrated_entity is not None
assert migrated_entity.config_entry_id == mock_config_entry.entry_id
assert migrated_entity.unique_id == f"{TEST_DEVICE_1_SN}-dnd"