forked from home-assistant/core
Compare commits
45 Commits
2025.5.0b6
...
2025.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 00627b82e0 | |||
| 13aba6201e | |||
| f392e0c1c7 | |||
| 181eca6c82 | |||
| 196d923ac6 | |||
| 4ad387c967 | |||
| cb475bf153 | |||
| 47acceea08 | |||
| fd6fb7e3bc | |||
| 30f7e9b441 | |||
| a8beec2691 | |||
| 23244fb79f | |||
| e5c56629e2 | |||
| a793503c8a | |||
| 054c7a0adc | |||
| 6eb2d1aa7c | |||
| 619fdea5df | |||
| e8bdc7286e | |||
| 18f2b120ef | |||
| 43d8345821 | |||
| 999e930fc8 | |||
| d4e99efc46 | |||
| fb01a0a9f1 | |||
| 9556285c59 | |||
| 2d40b1ec75 | |||
| 7eb690b125 | |||
| a23644debc | |||
| c98ba7f6ba | |||
| aa2b61f133 | |||
| f85d4afe45 | |||
| b4ab9177b8 | |||
| e7c310ca58 | |||
| 85a83f2553 | |||
| d2e7baeb38 | |||
| 07b2ce28b1 | |||
| 35c90d9bde | |||
| a9632bd0ff | |||
| 983e134ae9 | |||
| e217532f9e | |||
| 1eeab28eec | |||
| 2a3bd45901 | |||
| d16453a465 | |||
| de63dddc96 | |||
| ccffe19611 | |||
| 806bcf47d9 |
@@ -22,7 +22,7 @@ from . import util
|
||||
from .agent import BackupAgent
|
||||
from .const import DATA_MANAGER
|
||||
from .manager import BackupManager
|
||||
from .models import BackupNotFound
|
||||
from .models import AgentBackup, BackupNotFound
|
||||
|
||||
|
||||
@callback
|
||||
@@ -85,7 +85,15 @@ class DownloadBackupView(HomeAssistantView):
|
||||
request, headers, backup_id, agent_id, agent, manager
|
||||
)
|
||||
return await self._send_backup_with_password(
|
||||
hass, request, headers, backup_id, agent_id, password, agent, manager
|
||||
hass,
|
||||
backup,
|
||||
request,
|
||||
headers,
|
||||
backup_id,
|
||||
agent_id,
|
||||
password,
|
||||
agent,
|
||||
manager,
|
||||
)
|
||||
except BackupNotFound:
|
||||
return Response(status=HTTPStatus.NOT_FOUND)
|
||||
@@ -116,6 +124,7 @@ class DownloadBackupView(HomeAssistantView):
|
||||
async def _send_backup_with_password(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
backup: AgentBackup,
|
||||
request: Request,
|
||||
headers: dict[istr, str],
|
||||
backup_id: str,
|
||||
@@ -144,7 +153,8 @@ class DownloadBackupView(HomeAssistantView):
|
||||
|
||||
stream = util.AsyncIteratorWriter(hass)
|
||||
worker = threading.Thread(
|
||||
target=util.decrypt_backup, args=[reader, stream, password, on_done, 0, []]
|
||||
target=util.decrypt_backup,
|
||||
args=[backup, reader, stream, password, on_done, 0, []],
|
||||
)
|
||||
try:
|
||||
worker.start()
|
||||
|
||||
@@ -295,13 +295,26 @@ def validate_password_stream(
|
||||
raise BackupEmpty
|
||||
|
||||
|
||||
def _get_expected_archives(backup: AgentBackup) -> set[str]:
|
||||
"""Get the expected archives in the backup."""
|
||||
expected_archives = set()
|
||||
if backup.homeassistant_included:
|
||||
expected_archives.add("homeassistant")
|
||||
for addon in backup.addons:
|
||||
expected_archives.add(addon.slug)
|
||||
for folder in backup.folders:
|
||||
expected_archives.add(folder.value)
|
||||
return expected_archives
|
||||
|
||||
|
||||
def decrypt_backup(
|
||||
backup: AgentBackup,
|
||||
input_stream: IO[bytes],
|
||||
output_stream: IO[bytes],
|
||||
password: str | None,
|
||||
on_done: Callable[[Exception | None], None],
|
||||
minimum_size: int,
|
||||
nonces: list[bytes],
|
||||
nonces: NonceGenerator,
|
||||
) -> None:
|
||||
"""Decrypt a backup."""
|
||||
error: Exception | None = None
|
||||
@@ -315,7 +328,7 @@ def decrypt_backup(
|
||||
fileobj=output_stream, mode="w|", bufsize=BUF_SIZE
|
||||
) as output_tar,
|
||||
):
|
||||
_decrypt_backup(input_tar, output_tar, password)
|
||||
_decrypt_backup(backup, input_tar, output_tar, password)
|
||||
except (DecryptError, SecureTarError, tarfile.TarError) as err:
|
||||
LOGGER.warning("Error decrypting backup: %s", err)
|
||||
error = err
|
||||
@@ -333,15 +346,18 @@ def decrypt_backup(
|
||||
|
||||
|
||||
def _decrypt_backup(
|
||||
backup: AgentBackup,
|
||||
input_tar: tarfile.TarFile,
|
||||
output_tar: tarfile.TarFile,
|
||||
password: str | None,
|
||||
) -> None:
|
||||
"""Decrypt a backup."""
|
||||
expected_archives = _get_expected_archives(backup)
|
||||
for obj in input_tar:
|
||||
# We compare with PurePath to avoid issues with different path separators,
|
||||
# for example when backup.json is added as "./backup.json"
|
||||
if PurePath(obj.name) == PurePath("backup.json"):
|
||||
object_path = PurePath(obj.name)
|
||||
if object_path == PurePath("backup.json"):
|
||||
# Rewrite the backup.json file to indicate that the backup is decrypted
|
||||
if not (reader := input_tar.extractfile(obj)):
|
||||
raise DecryptError
|
||||
@@ -352,7 +368,13 @@ def _decrypt_backup(
|
||||
metadata_obj.size = len(updated_metadata_b)
|
||||
output_tar.addfile(metadata_obj, BytesIO(updated_metadata_b))
|
||||
continue
|
||||
if not obj.name.endswith((".tar", ".tgz", ".tar.gz")):
|
||||
prefix, _, suffix = object_path.name.partition(".")
|
||||
if suffix not in ("tar", "tgz", "tar.gz"):
|
||||
LOGGER.debug("Unknown file %s will not be decrypted", obj.name)
|
||||
output_tar.addfile(obj, input_tar.extractfile(obj))
|
||||
continue
|
||||
if prefix not in expected_archives:
|
||||
LOGGER.debug("Unknown inner tar file %s will not be decrypted", obj.name)
|
||||
output_tar.addfile(obj, input_tar.extractfile(obj))
|
||||
continue
|
||||
istf = SecureTarFile(
|
||||
@@ -371,12 +393,13 @@ def _decrypt_backup(
|
||||
|
||||
|
||||
def encrypt_backup(
|
||||
backup: AgentBackup,
|
||||
input_stream: IO[bytes],
|
||||
output_stream: IO[bytes],
|
||||
password: str | None,
|
||||
on_done: Callable[[Exception | None], None],
|
||||
minimum_size: int,
|
||||
nonces: list[bytes],
|
||||
nonces: NonceGenerator,
|
||||
) -> None:
|
||||
"""Encrypt a backup."""
|
||||
error: Exception | None = None
|
||||
@@ -390,7 +413,7 @@ def encrypt_backup(
|
||||
fileobj=output_stream, mode="w|", bufsize=BUF_SIZE
|
||||
) as output_tar,
|
||||
):
|
||||
_encrypt_backup(input_tar, output_tar, password, nonces)
|
||||
_encrypt_backup(backup, input_tar, output_tar, password, nonces)
|
||||
except (EncryptError, SecureTarError, tarfile.TarError) as err:
|
||||
LOGGER.warning("Error encrypting backup: %s", err)
|
||||
error = err
|
||||
@@ -408,17 +431,20 @@ def encrypt_backup(
|
||||
|
||||
|
||||
def _encrypt_backup(
|
||||
backup: AgentBackup,
|
||||
input_tar: tarfile.TarFile,
|
||||
output_tar: tarfile.TarFile,
|
||||
password: str | None,
|
||||
nonces: list[bytes],
|
||||
nonces: NonceGenerator,
|
||||
) -> None:
|
||||
"""Encrypt a backup."""
|
||||
inner_tar_idx = 0
|
||||
expected_archives = _get_expected_archives(backup)
|
||||
for obj in input_tar:
|
||||
# We compare with PurePath to avoid issues with different path separators,
|
||||
# for example when backup.json is added as "./backup.json"
|
||||
if PurePath(obj.name) == PurePath("backup.json"):
|
||||
object_path = PurePath(obj.name)
|
||||
if object_path == PurePath("backup.json"):
|
||||
# Rewrite the backup.json file to indicate that the backup is encrypted
|
||||
if not (reader := input_tar.extractfile(obj)):
|
||||
raise EncryptError
|
||||
@@ -429,16 +455,21 @@ def _encrypt_backup(
|
||||
metadata_obj.size = len(updated_metadata_b)
|
||||
output_tar.addfile(metadata_obj, BytesIO(updated_metadata_b))
|
||||
continue
|
||||
if not obj.name.endswith((".tar", ".tgz", ".tar.gz")):
|
||||
prefix, _, suffix = object_path.name.partition(".")
|
||||
if suffix not in ("tar", "tgz", "tar.gz"):
|
||||
LOGGER.debug("Unknown file %s will not be encrypted", obj.name)
|
||||
output_tar.addfile(obj, input_tar.extractfile(obj))
|
||||
continue
|
||||
if prefix not in expected_archives:
|
||||
LOGGER.debug("Unknown inner tar file %s will not be encrypted", obj.name)
|
||||
continue
|
||||
istf = SecureTarFile(
|
||||
None, # Not used
|
||||
gzip=False,
|
||||
key=password_to_key(password) if password is not None else None,
|
||||
mode="r",
|
||||
fileobj=input_tar.extractfile(obj),
|
||||
nonce=nonces[inner_tar_idx],
|
||||
nonce=nonces.get(inner_tar_idx),
|
||||
)
|
||||
inner_tar_idx += 1
|
||||
with istf.encrypt(obj) as encrypted:
|
||||
@@ -456,17 +487,33 @@ class _CipherWorkerStatus:
|
||||
writer: AsyncIteratorWriter
|
||||
|
||||
|
||||
class NonceGenerator:
|
||||
"""Generate nonces for encryption."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize the generator."""
|
||||
self._nonces: dict[int, bytes] = {}
|
||||
|
||||
def get(self, index: int) -> bytes:
|
||||
"""Get a nonce for the given index."""
|
||||
if index not in self._nonces:
|
||||
# Generate a new nonce for the given index
|
||||
self._nonces[index] = os.urandom(16)
|
||||
return self._nonces[index]
|
||||
|
||||
|
||||
class _CipherBackupStreamer:
|
||||
"""Encrypt or decrypt a backup."""
|
||||
|
||||
_cipher_func: Callable[
|
||||
[
|
||||
AgentBackup,
|
||||
IO[bytes],
|
||||
IO[bytes],
|
||||
str | None,
|
||||
Callable[[Exception | None], None],
|
||||
int,
|
||||
list[bytes],
|
||||
NonceGenerator,
|
||||
],
|
||||
None,
|
||||
]
|
||||
@@ -484,7 +531,7 @@ class _CipherBackupStreamer:
|
||||
self._hass = hass
|
||||
self._open_stream = open_stream
|
||||
self._password = password
|
||||
self._nonces: list[bytes] = []
|
||||
self._nonces = NonceGenerator()
|
||||
|
||||
def size(self) -> int:
|
||||
"""Return the maximum size of the decrypted or encrypted backup."""
|
||||
@@ -508,7 +555,15 @@ class _CipherBackupStreamer:
|
||||
writer = AsyncIteratorWriter(self._hass)
|
||||
worker = threading.Thread(
|
||||
target=self._cipher_func,
|
||||
args=[reader, writer, self._password, on_done, self.size(), self._nonces],
|
||||
args=[
|
||||
self._backup,
|
||||
reader,
|
||||
writer,
|
||||
self._password,
|
||||
on_done,
|
||||
self.size(),
|
||||
self._nonces,
|
||||
],
|
||||
)
|
||||
worker_status = _CipherWorkerStatus(
|
||||
done=asyncio.Event(), reader=reader, thread=worker, writer=writer
|
||||
@@ -538,17 +593,6 @@ class DecryptedBackupStreamer(_CipherBackupStreamer):
|
||||
class EncryptedBackupStreamer(_CipherBackupStreamer):
|
||||
"""Encrypt a backup."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
backup: AgentBackup,
|
||||
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
|
||||
password: str | None,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(hass, backup, open_stream, password)
|
||||
self._nonces = [os.urandom(16) for _ in range(self._num_tar_files())]
|
||||
|
||||
_cipher_func = staticmethod(encrypt_backup)
|
||||
|
||||
def backup(self) -> AgentBackup:
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bluemaestro",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["bluemaestro-ble==0.4.0"]
|
||||
"requirements": ["bluemaestro-ble==0.4.1"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.4.30"]
|
||||
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.5.7"]
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["devolo_home_control_api"],
|
||||
"requirements": ["devolo-home-control-api==0.18.3"],
|
||||
"requirements": ["devolo-home-control-api==0.19.0"],
|
||||
"zeroconf": ["_dvl-deviceapi._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ async def async_validate_hostname(
|
||||
result = False
|
||||
with contextlib.suppress(DNSError):
|
||||
result = bool(
|
||||
await aiodns.DNSResolver(
|
||||
await aiodns.DNSResolver( # type: ignore[call-overload]
|
||||
nameservers=[resolver], udp_port=port, tcp_port=port
|
||||
).query(hostname, qtype)
|
||||
)
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/dnsip",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["aiodns==3.3.0"]
|
||||
"requirements": ["aiodns==3.4.0"]
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class WanIpSensor(SensorEntity):
|
||||
async def async_update(self) -> None:
|
||||
"""Get the current DNS IP address for hostname."""
|
||||
try:
|
||||
response = await self.resolver.query(self.hostname, self.querytype)
|
||||
response = await self.resolver.query(self.hostname, self.querytype) # type: ignore[call-overload]
|
||||
except DNSError as err:
|
||||
_LOGGER.warning("Exception while resolving host: %s", err)
|
||||
response = None
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||
"requirements": ["py-sucks==0.9.10", "deebot-client==13.0.1"]
|
||||
"requirements": ["py-sucks==0.9.10", "deebot-client==13.1.0"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/forecast_solar",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["forecast-solar==4.1.0"]
|
||||
"requirements": ["forecast-solar==4.2.0"]
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat
|
||||
|
||||
available_main_ains = [
|
||||
ain
|
||||
for ain, dev in data.devices.items()
|
||||
for ain, dev in data.devices.items() | data.templates.items()
|
||||
if dev.device_and_unit_id[1] is None
|
||||
]
|
||||
device_reg = dr.async_get(self.hass)
|
||||
|
||||
@@ -45,7 +45,15 @@ type FroniusConfigEntry = ConfigEntry[FroniusSolarNet]
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: FroniusConfigEntry) -> bool:
|
||||
"""Set up fronius from a config entry."""
|
||||
host = entry.data[CONF_HOST]
|
||||
fronius = Fronius(async_get_clientsession(hass), host)
|
||||
fronius = Fronius(
|
||||
async_get_clientsession(
|
||||
hass,
|
||||
# Fronius Gen24 firmware 1.35.4-1 redirects to HTTPS with self-signed
|
||||
# certificate. See https://github.com/home-assistant/core/issues/138881
|
||||
verify_ssl=False,
|
||||
),
|
||||
host,
|
||||
)
|
||||
solar_net = FroniusSolarNet(hass, entry, fronius)
|
||||
await solar_net.init_devices()
|
||||
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20250502.1"]
|
||||
"requirements": ["home-assistant-frontend==20250509.0"]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ from pyhap.const import CATEGORY_AIR_PURIFIER
|
||||
from pyhap.service import Service
|
||||
from pyhap.util import callback as pyhap_callback
|
||||
|
||||
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import (
|
||||
Event,
|
||||
EventStateChangedData,
|
||||
@@ -43,7 +49,12 @@ from .const import (
|
||||
THRESHOLD_FILTER_CHANGE_NEEDED,
|
||||
)
|
||||
from .type_fans import ATTR_PRESET_MODE, CHAR_ROTATION_SPEED, Fan
|
||||
from .util import cleanup_name_for_homekit, convert_to_float, density_to_air_quality
|
||||
from .util import (
|
||||
cleanup_name_for_homekit,
|
||||
convert_to_float,
|
||||
density_to_air_quality,
|
||||
temperature_to_homekit,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -345,8 +356,13 @@ class AirPurifier(Fan):
|
||||
):
|
||||
return
|
||||
|
||||
unit = new_state.attributes.get(
|
||||
ATTR_UNIT_OF_MEASUREMENT, UnitOfTemperature.CELSIUS
|
||||
)
|
||||
current_temperature = temperature_to_homekit(current_temperature, unit)
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s: Linked temperature sensor %s changed to %d",
|
||||
"%s: Linked temperature sensor %s changed to %d °C",
|
||||
self.entity_id,
|
||||
self.linked_temperature_sensor,
|
||||
current_temperature,
|
||||
|
||||
@@ -110,10 +110,10 @@ class AutomowerLawnMowerEntity(AutomowerAvailableEntity, LawnMowerEntity):
|
||||
mower_attributes = self.mower_attributes
|
||||
if mower_attributes.mower.state in PAUSED_STATES:
|
||||
return LawnMowerActivity.PAUSED
|
||||
if mower_attributes.mower.activity in MOWING_ACTIVITIES:
|
||||
if mower_attributes.mower.state in MowerStates.IN_OPERATION:
|
||||
if mower_attributes.mower.activity == MowerActivities.GOING_HOME:
|
||||
return LawnMowerActivity.RETURNING
|
||||
return LawnMowerActivity.MOWING
|
||||
if mower_attributes.mower.activity == MowerActivities.GOING_HOME:
|
||||
return LawnMowerActivity.RETURNING
|
||||
if (mower_attributes.mower.state == "RESTRICTED") or (
|
||||
mower_attributes.mower.activity in DOCKED_ACTIVITIES
|
||||
):
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylamarzocco"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pylamarzocco==2.0.0"]
|
||||
"requirements": ["pylamarzocco==2.0.1"]
|
||||
}
|
||||
|
||||
@@ -132,17 +132,18 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up sensor entities."""
|
||||
coordinator = entry.runtime_data.config_coordinator
|
||||
config_coordinator = entry.runtime_data.config_coordinator
|
||||
statistic_coordinators = entry.runtime_data.statistics_coordinator
|
||||
|
||||
entities = [
|
||||
LaMarzoccoSensorEntity(coordinator, description)
|
||||
LaMarzoccoSensorEntity(config_coordinator, description)
|
||||
for description in ENTITIES
|
||||
if description.supported_fn(coordinator)
|
||||
if description.supported_fn(config_coordinator)
|
||||
]
|
||||
entities.extend(
|
||||
LaMarzoccoStatisticSensorEntity(coordinator, description)
|
||||
LaMarzoccoStatisticSensorEntity(statistic_coordinators, description)
|
||||
for description in STATISTIC_ENTITIES
|
||||
if description.supported_fn(coordinator)
|
||||
if description.supported_fn(statistic_coordinators)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
DOMAIN,
|
||||
SERVICE_VOLUME_SET,
|
||||
required_domains={DOMAIN},
|
||||
required_states={MediaPlayerState.PLAYING},
|
||||
required_features=MediaPlayerEntityFeature.VOLUME_SET,
|
||||
required_slots={
|
||||
ATTR_MEDIA_VOLUME_LEVEL: intent.IntentSlotInfo(
|
||||
@@ -159,7 +158,6 @@ class MediaUnpauseHandler(intent.ServiceIntentHandler):
|
||||
DOMAIN,
|
||||
SERVICE_MEDIA_PLAY,
|
||||
required_domains={DOMAIN},
|
||||
required_states={MediaPlayerState.PAUSED},
|
||||
description="Resumes a media player",
|
||||
platforms={DOMAIN},
|
||||
device_classes={MediaPlayerDeviceClass},
|
||||
|
||||
@@ -57,8 +57,8 @@ ATA_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATA_HVAC_MODE_LOOKUP.items()}
|
||||
|
||||
|
||||
ATW_ZONE_HVAC_MODE_LOOKUP = {
|
||||
atw.ZONE_OPERATION_MODE_HEAT: HVACMode.HEAT,
|
||||
atw.ZONE_OPERATION_MODE_COOL: HVACMode.COOL,
|
||||
atw.ZONE_STATUS_HEAT: HVACMode.HEAT,
|
||||
atw.ZONE_STATUS_COOL: HVACMode.COOL,
|
||||
}
|
||||
ATW_ZONE_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATW_ZONE_HVAC_MODE_LOOKUP.items()}
|
||||
|
||||
|
||||
@@ -498,8 +498,7 @@ def validate_light_platform_config(user_data: dict[str, Any]) -> dict[str, str]:
|
||||
if user_data.get(CONF_MIN_KELVIN, DEFAULT_MIN_KELVIN) >= user_data.get(
|
||||
CONF_MAX_KELVIN, DEFAULT_MAX_KELVIN
|
||||
):
|
||||
errors[CONF_MAX_KELVIN] = "max_below_min_kelvin"
|
||||
errors[CONF_MIN_KELVIN] = "max_below_min_kelvin"
|
||||
errors["advanced_settings"] = "max_below_min_kelvin"
|
||||
return errors
|
||||
|
||||
|
||||
@@ -1276,7 +1275,10 @@ def validate_user_input(
|
||||
try:
|
||||
validator(value)
|
||||
except (ValueError, vol.Error, vol.Invalid):
|
||||
errors[field] = data_schema_fields[field].error or "invalid_input"
|
||||
data_schema_field = data_schema_fields[field]
|
||||
errors[data_schema_field.section or field] = (
|
||||
data_schema_field.error or "invalid_input"
|
||||
)
|
||||
|
||||
if config_validator is not None:
|
||||
if TYPE_CHECKING:
|
||||
@@ -1385,8 +1387,11 @@ def subentry_schema_default_data_from_fields(
|
||||
return {
|
||||
key: field.default
|
||||
for key, field in data_schema_fields.items()
|
||||
if field.is_schema_default
|
||||
or (field.default is not vol.UNDEFINED and key not in component_data)
|
||||
if _check_conditions(field, component_data)
|
||||
and (
|
||||
field.is_schema_default
|
||||
or (field.default is not vol.UNDEFINED and key not in component_data)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2212,7 +2217,10 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
for component_data in self._subentry_data["components"].values():
|
||||
platform = component_data[CONF_PLATFORM]
|
||||
subentry_default_data = subentry_schema_default_data_from_fields(
|
||||
PLATFORM_ENTITY_FIELDS[platform] | COMMON_ENTITY_FIELDS, component_data
|
||||
COMMON_ENTITY_FIELDS
|
||||
| PLATFORM_ENTITY_FIELDS[platform]
|
||||
| PLATFORM_MQTT_FIELDS[platform],
|
||||
component_data,
|
||||
)
|
||||
component_data.update(subentry_default_data)
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from pypoint import PointSession
|
||||
from tempora.utc import fromtimestamp
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
@@ -62,7 +61,9 @@ class PointDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]
|
||||
or device.device_id not in self.device_updates
|
||||
or self.device_updates[device.device_id] < last_updated
|
||||
):
|
||||
self.device_updates[device.device_id] = last_updated or fromtimestamp(0)
|
||||
self.device_updates[device.device_id] = (
|
||||
last_updated or datetime.fromtimestamp(0)
|
||||
)
|
||||
self.data[device.device_id] = {
|
||||
k: await device.sensor(k)
|
||||
for k in ("temperature", "humidity", "sound_pressure")
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["renault_api"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["renault-api==0.3.0"]
|
||||
"requirements": ["renault-api==0.3.1"]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ from homeassistant.helpers import (
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
@@ -380,6 +380,14 @@ def migrate_entity_ids(
|
||||
if ch is None or is_chime:
|
||||
continue # Do not consider the NVR itself or chimes
|
||||
|
||||
# Check for wrongfully added MAC of the NVR/Hub to the camera
|
||||
# Can be removed in HA 2025.12
|
||||
host_connnection = (CONNECTION_NETWORK_MAC, host.api.mac_address)
|
||||
if host_connnection in device.connections:
|
||||
new_connections = device.connections.copy()
|
||||
new_connections.remove(host_connnection)
|
||||
device_reg.async_update_device(device.id, new_connections=new_connections)
|
||||
|
||||
ch_device_ids[device.id] = ch
|
||||
if host.api.supported(ch, "UID") and device_uid[1] != host.api.camera_uid(ch):
|
||||
if host.api.supported(None, "UID"):
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pysmartthings==3.2.0"]
|
||||
"requirements": ["pysmartthings==3.2.1"]
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ class SmartThingsSelectDescription(SelectEntityDescription):
|
||||
options_attribute: Attribute
|
||||
status_attribute: Attribute
|
||||
command: Command
|
||||
default_options: list[str] | None = None
|
||||
|
||||
|
||||
CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
@@ -46,6 +47,7 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
options_attribute=Attribute.SUPPORTED_MACHINE_STATES,
|
||||
status_attribute=Attribute.MACHINE_STATE,
|
||||
command=Command.SET_MACHINE_STATE,
|
||||
default_options=["run", "pause", "stop"],
|
||||
),
|
||||
Capability.WASHER_OPERATING_STATE: SmartThingsSelectDescription(
|
||||
key=Capability.WASHER_OPERATING_STATE,
|
||||
@@ -55,6 +57,7 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
options_attribute=Attribute.SUPPORTED_MACHINE_STATES,
|
||||
status_attribute=Attribute.MACHINE_STATE,
|
||||
command=Command.SET_MACHINE_STATE,
|
||||
default_options=["run", "pause", "stop"],
|
||||
),
|
||||
Capability.SAMSUNG_CE_AUTO_DISPENSE_DETERGENT: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_AUTO_DISPENSE_DETERGENT,
|
||||
@@ -114,8 +117,12 @@ class SmartThingsSelectEntity(SmartThingsEntity, SelectEntity):
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""Return the list of options."""
|
||||
return self.get_attribute_value(
|
||||
self.entity_description.key, self.entity_description.options_attribute
|
||||
return (
|
||||
self.get_attribute_value(
|
||||
self.entity_description.key, self.entity_description.options_attribute
|
||||
)
|
||||
or self.entity_description.default_options
|
||||
or []
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -631,7 +631,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="powerEnergy_meter",
|
||||
translation_key="power_energy",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["powerEnergy"] / 1000,
|
||||
|
||||
@@ -290,8 +290,10 @@
|
||||
"options": {
|
||||
"apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]",
|
||||
"aqi": "[%key:component::sensor::entity_component::aqi::name%]",
|
||||
"area": "[%key:component::sensor::entity_component::area::name%]",
|
||||
"atmospheric_pressure": "[%key:component::sensor::entity_component::atmospheric_pressure::name%]",
|
||||
"battery": "[%key:component::sensor::entity_component::battery::name%]",
|
||||
"blood_glucose_concentration": "[%key:component::sensor::entity_component::blood_glucose_concentration::name%]",
|
||||
"carbon_dioxide": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
|
||||
"carbon_monoxide": "[%key:component::sensor::entity_component::carbon_monoxide::name%]",
|
||||
"conductivity": "[%key:component::sensor::entity_component::conductivity::name%]",
|
||||
@@ -302,6 +304,7 @@
|
||||
"distance": "[%key:component::sensor::entity_component::distance::name%]",
|
||||
"duration": "[%key:component::sensor::entity_component::duration::name%]",
|
||||
"energy": "[%key:component::sensor::entity_component::energy::name%]",
|
||||
"energy_distance": "[%key:component::sensor::entity_component::energy_distance::name%]",
|
||||
"energy_storage": "[%key:component::sensor::entity_component::energy_storage::name%]",
|
||||
"frequency": "[%key:component::sensor::entity_component::frequency::name%]",
|
||||
"gas": "[%key:component::sensor::entity_component::gas::name%]",
|
||||
@@ -338,6 +341,7 @@
|
||||
"volume_storage": "[%key:component::sensor::entity_component::volume_storage::name%]",
|
||||
"water": "[%key:component::sensor::entity_component::water::name%]",
|
||||
"weight": "[%key:component::sensor::entity_component::weight::name%]",
|
||||
"wind_direction": "[%key:component::sensor::entity_component::wind_direction::name%]",
|
||||
"wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["uiprotect", "unifi_discovery"],
|
||||
"requirements": ["uiprotect==7.5.5", "unifi-discovery==1.2.0"],
|
||||
"requirements": ["uiprotect==7.6.0", "unifi-discovery==1.2.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
||||
@@ -97,6 +97,7 @@ SKU_TO_BASE_DEVICE = {
|
||||
"LAP-V102S-AASR": "Vital100S", # Alt ID Model Vital100S
|
||||
"LAP-V102S-WEU": "Vital100S", # Alt ID Model Vital100S
|
||||
"LAP-V102S-WUK": "Vital100S", # Alt ID Model Vital100S
|
||||
"LAP-V102S-AUSR": "Vital100S", # Alt ID Model Vital100S
|
||||
"EverestAir": "EverestAir",
|
||||
"LAP-EL551S-AUS": "EverestAir", # Alt ID Model EverestAir
|
||||
"LAP-EL551S-AEUR": "EverestAir", # Alt ID Model EverestAir
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["application_credentials"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/weheat",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["weheat==2025.3.7"]
|
||||
"requirements": ["weheat==2025.4.29"]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable, Coroutine
|
||||
from contextlib import suppress
|
||||
import dataclasses
|
||||
from functools import partial, wraps
|
||||
from typing import Any, Concatenate, Literal, cast
|
||||
@@ -86,6 +88,7 @@ from .const import (
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
EVENT_DEVICE_ADDED_TO_REGISTRY,
|
||||
RESTORE_NVM_DRIVER_READY_TIMEOUT,
|
||||
USER_AGENT,
|
||||
)
|
||||
from .helpers import (
|
||||
@@ -182,6 +185,8 @@ STRATEGY = "strategy"
|
||||
# https://github.com/zwave-js/node-zwave-js/blob/master/packages/core/src/security/QR.ts#L41
|
||||
MINIMUM_QR_STRING_LENGTH = 52
|
||||
|
||||
HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT = 60
|
||||
|
||||
|
||||
# Helper schemas
|
||||
PLANNED_PROVISIONING_ENTRY_SCHEMA = vol.All(
|
||||
@@ -2816,6 +2821,7 @@ async def websocket_hard_reset_controller(
|
||||
driver: Driver,
|
||||
) -> None:
|
||||
"""Hard reset controller."""
|
||||
unsubs: list[Callable[[], None]]
|
||||
|
||||
@callback
|
||||
def async_cleanup() -> None:
|
||||
@@ -2831,13 +2837,28 @@ async def websocket_hard_reset_controller(
|
||||
connection.send_result(msg[ID], device.id)
|
||||
async_cleanup()
|
||||
|
||||
@callback
|
||||
def set_driver_ready(event: dict) -> None:
|
||||
"Set the driver ready event."
|
||||
wait_driver_ready.set()
|
||||
|
||||
wait_driver_ready = asyncio.Event()
|
||||
|
||||
msg[DATA_UNSUBSCRIBE] = unsubs = [
|
||||
async_dispatcher_connect(
|
||||
hass, EVENT_DEVICE_ADDED_TO_REGISTRY, _handle_device_added
|
||||
)
|
||||
),
|
||||
driver.once("driver ready", set_driver_ready),
|
||||
]
|
||||
|
||||
await driver.async_hard_reset()
|
||||
|
||||
with suppress(TimeoutError):
|
||||
async with asyncio.timeout(HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT):
|
||||
await wait_driver_ready.wait()
|
||||
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
@@ -3043,14 +3064,28 @@ async def websocket_restore_nvm(
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def set_driver_ready(event: dict) -> None:
|
||||
"Set the driver ready event."
|
||||
wait_driver_ready.set()
|
||||
|
||||
wait_driver_ready = asyncio.Event()
|
||||
|
||||
# Set up subscription for progress events
|
||||
connection.subscriptions[msg["id"]] = async_cleanup
|
||||
msg[DATA_UNSUBSCRIBE] = unsubs = [
|
||||
controller.on("nvm convert progress", forward_progress),
|
||||
controller.on("nvm restore progress", forward_progress),
|
||||
driver.once("driver ready", set_driver_ready),
|
||||
]
|
||||
|
||||
await controller.async_restore_nvm_base64(msg["data"])
|
||||
|
||||
with suppress(TimeoutError):
|
||||
async with asyncio.timeout(RESTORE_NVM_DRIVER_READY_TIMEOUT):
|
||||
await wait_driver_ready.wait()
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
connection.send_message(
|
||||
websocket_api.event_message(
|
||||
msg[ID],
|
||||
|
||||
@@ -67,6 +67,7 @@ from .const import (
|
||||
CONF_USE_ADDON,
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
RESTORE_NVM_DRIVER_READY_TIMEOUT,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -78,7 +79,6 @@ ADDON_SETUP_TIMEOUT = 5
|
||||
ADDON_SETUP_TIMEOUT_ROUNDS = 40
|
||||
CONF_EMULATE_HARDWARE = "emulate_hardware"
|
||||
CONF_LOG_LEVEL = "log_level"
|
||||
RESTORE_NVM_DRIVER_READY_TIMEOUT = 60
|
||||
SERVER_VERSION_TIMEOUT = 10
|
||||
|
||||
ADDON_LOG_LEVELS = {
|
||||
@@ -907,10 +907,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Reset the current controller, and instruct the user to unplug it."""
|
||||
|
||||
if user_input is not None:
|
||||
config_entry = self._reconfigure_config_entry
|
||||
assert config_entry is not None
|
||||
# Unload the config entry before stopping the add-on.
|
||||
await self.hass.config_entries.async_unload(config_entry.entry_id)
|
||||
if self.usb_path:
|
||||
# USB discovery was used, so the device is already known.
|
||||
await self._async_set_addon_config({CONF_ADDON_DEVICE: self.usb_path})
|
||||
@@ -925,6 +921,11 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.error("Failed to reset controller: %s", err)
|
||||
return self.async_abort(reason="reset_failed")
|
||||
|
||||
config_entry = self._reconfigure_config_entry
|
||||
assert config_entry is not None
|
||||
# Unload the config entry before asking the user to unplug the controller.
|
||||
await self.hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="instruct_unplug",
|
||||
description_placeholders={
|
||||
|
||||
@@ -201,3 +201,7 @@ COVER_TILT_PROPERTY_KEYS: set[str | int | None] = {
|
||||
WindowCoveringPropertyKey.VERTICAL_SLATS_ANGLE,
|
||||
WindowCoveringPropertyKey.VERTICAL_SLATS_ANGLE_NO_POSITION,
|
||||
}
|
||||
|
||||
# Other constants
|
||||
|
||||
RESTORE_NVM_DRIVER_READY_TIMEOUT = 60
|
||||
|
||||
@@ -1204,7 +1204,7 @@ DISCOVERY_SCHEMAS = [
|
||||
property={RESET_METER_PROPERTY},
|
||||
type={ValueType.BOOLEAN},
|
||||
),
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
ZWaveDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
|
||||
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 5
|
||||
PATCH_VERSION: Final = "0b6"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)
|
||||
|
||||
@@ -575,9 +575,11 @@ class DeviceRegistryItems[_EntryTypeT: (DeviceEntry, DeletedDeviceEntry)](
|
||||
"""Unindex an entry."""
|
||||
old_entry = self.data[key]
|
||||
for connection in old_entry.connections:
|
||||
del self._connections[connection]
|
||||
if connection in self._connections:
|
||||
del self._connections[connection]
|
||||
for identifier in old_entry.identifiers:
|
||||
del self._identifiers[identifier]
|
||||
if identifier in self._identifiers:
|
||||
del self._identifiers[identifier]
|
||||
|
||||
def get_entry(
|
||||
self,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
aiodhcpwatcher==1.1.1
|
||||
aiodiscover==2.6.1
|
||||
aiodns==3.3.0
|
||||
aiodns==3.4.0
|
||||
aiohasupervisor==0.3.1
|
||||
aiohttp-asyncmdnsresolver==0.1.1
|
||||
aiohttp-fast-zlib==0.2.3
|
||||
@@ -38,8 +38,8 @@ habluetooth==3.48.2
|
||||
hass-nabucasa==0.96.0
|
||||
hassil==2.2.3
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20250502.1
|
||||
home-assistant-intents==2025.4.30
|
||||
home-assistant-frontend==20250509.0
|
||||
home-assistant-intents==2025.5.7
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.6
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.5.0b6"
|
||||
version = "2025.5.1"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
]
|
||||
requires-python = ">=3.13.2"
|
||||
dependencies = [
|
||||
"aiodns==3.3.0",
|
||||
"aiodns==3.4.0",
|
||||
# Integrations may depend on hassio integration without listing it to
|
||||
# change behavior based on presence of supervisor. Deprecated with #127228
|
||||
# Lib can be removed with 2025.11
|
||||
@@ -66,7 +66,7 @@ dependencies = [
|
||||
# onboarding->cloud->assist_pipeline->conversation->home_assistant_intents. Onboarding needs
|
||||
# to be setup in stage 0, but we don't want to also promote cloud with all its
|
||||
# dependencies to stage 0.
|
||||
"home-assistant-intents==2025.4.30",
|
||||
"home-assistant-intents==2025.5.7",
|
||||
"ifaddr==0.2.0",
|
||||
"Jinja2==3.1.6",
|
||||
"lru-dict==1.3.0",
|
||||
|
||||
Generated
+2
-2
@@ -3,7 +3,7 @@
|
||||
-c homeassistant/package_constraints.txt
|
||||
|
||||
# Home Assistant Core
|
||||
aiodns==3.3.0
|
||||
aiodns==3.4.0
|
||||
aiohasupervisor==0.3.1
|
||||
aiohttp==3.11.18
|
||||
aiohttp_cors==0.7.0
|
||||
@@ -27,7 +27,7 @@ hass-nabucasa==0.96.0
|
||||
hassil==2.2.3
|
||||
httpx==0.28.1
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-intents==2025.4.30
|
||||
home-assistant-intents==2025.5.7
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.6
|
||||
lru-dict==1.3.0
|
||||
|
||||
Generated
+12
-12
@@ -223,7 +223,7 @@ aiodhcpwatcher==1.1.1
|
||||
aiodiscover==2.6.1
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==3.3.0
|
||||
aiodns==3.4.0
|
||||
|
||||
# homeassistant.components.duke_energy
|
||||
aiodukeenergy==0.3.0
|
||||
@@ -628,7 +628,7 @@ blockchain==1.4.4
|
||||
bluecurrent-api==1.2.3
|
||||
|
||||
# homeassistant.components.bluemaestro
|
||||
bluemaestro-ble==0.4.0
|
||||
bluemaestro-ble==0.4.1
|
||||
|
||||
# homeassistant.components.decora
|
||||
# bluepy==1.3.0
|
||||
@@ -762,7 +762,7 @@ debugpy==1.8.13
|
||||
# decora==0.6
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==13.0.1
|
||||
deebot-client==13.1.0
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@@ -782,7 +782,7 @@ denonavr==1.0.1
|
||||
devialet==1.5.7
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.18.3
|
||||
devolo-home-control-api==0.19.0
|
||||
|
||||
# homeassistant.components.devolo_home_network
|
||||
devolo-plc-api==1.5.1
|
||||
@@ -958,7 +958,7 @@ fnv-hash-fast==1.5.0
|
||||
foobot_async==1.0.0
|
||||
|
||||
# homeassistant.components.forecast_solar
|
||||
forecast-solar==4.1.0
|
||||
forecast-solar==4.2.0
|
||||
|
||||
# homeassistant.components.fortios
|
||||
fortiosapi==1.0.5
|
||||
@@ -1161,10 +1161,10 @@ hole==0.8.0
|
||||
holidays==0.70
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250502.1
|
||||
home-assistant-frontend==20250509.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.4.30
|
||||
home-assistant-intents==2025.5.7
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==2.0.1.1
|
||||
@@ -2093,7 +2093,7 @@ pykwb==0.0.8
|
||||
pylacrosse==0.4
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
pylamarzocco==2.0.0
|
||||
pylamarzocco==2.0.1
|
||||
|
||||
# homeassistant.components.lastfm
|
||||
pylast==5.1.0
|
||||
@@ -2326,7 +2326,7 @@ pysma==0.7.5
|
||||
pysmappee==0.2.29
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.2.0
|
||||
pysmartthings==3.2.1
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.2
|
||||
@@ -2631,7 +2631,7 @@ refoss-ha==1.2.5
|
||||
regenmaschine==2024.03.0
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.3.0
|
||||
renault-api==0.3.1
|
||||
|
||||
# homeassistant.components.renson
|
||||
renson-endura-delta==1.7.2
|
||||
@@ -2975,7 +2975,7 @@ typedmonarchmoney==0.4.4
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==7.5.5
|
||||
uiprotect==7.6.0
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.7
|
||||
@@ -3074,7 +3074,7 @@ webio-api==0.1.11
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2025.3.7
|
||||
weheat==2025.4.29
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==0.20.0
|
||||
|
||||
Generated
+12
-12
@@ -211,7 +211,7 @@ aiodhcpwatcher==1.1.1
|
||||
aiodiscover==2.6.1
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==3.3.0
|
||||
aiodns==3.4.0
|
||||
|
||||
# homeassistant.components.duke_energy
|
||||
aiodukeenergy==0.3.0
|
||||
@@ -556,7 +556,7 @@ blinkpy==0.23.0
|
||||
bluecurrent-api==1.2.3
|
||||
|
||||
# homeassistant.components.bluemaestro
|
||||
bluemaestro-ble==0.4.0
|
||||
bluemaestro-ble==0.4.1
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bluetooth-adapters==0.21.4
|
||||
@@ -653,7 +653,7 @@ dbus-fast==2.43.0
|
||||
debugpy==1.8.13
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==13.0.1
|
||||
deebot-client==13.1.0
|
||||
|
||||
# homeassistant.components.ihc
|
||||
# homeassistant.components.namecheapdns
|
||||
@@ -673,7 +673,7 @@ denonavr==1.0.1
|
||||
devialet==1.5.7
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.18.3
|
||||
devolo-home-control-api==0.19.0
|
||||
|
||||
# homeassistant.components.devolo_home_network
|
||||
devolo-plc-api==1.5.1
|
||||
@@ -818,7 +818,7 @@ fnv-hash-fast==1.5.0
|
||||
foobot_async==1.0.0
|
||||
|
||||
# homeassistant.components.forecast_solar
|
||||
forecast-solar==4.1.0
|
||||
forecast-solar==4.2.0
|
||||
|
||||
# homeassistant.components.freebox
|
||||
freebox-api==1.2.2
|
||||
@@ -991,10 +991,10 @@ hole==0.8.0
|
||||
holidays==0.70
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250502.1
|
||||
home-assistant-frontend==20250509.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.4.30
|
||||
home-assistant-intents==2025.5.7
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==2.0.1.1
|
||||
@@ -1708,7 +1708,7 @@ pykrakenapi==0.1.8
|
||||
pykulersky==0.5.8
|
||||
|
||||
# homeassistant.components.lamarzocco
|
||||
pylamarzocco==2.0.0
|
||||
pylamarzocco==2.0.1
|
||||
|
||||
# homeassistant.components.lastfm
|
||||
pylast==5.1.0
|
||||
@@ -1899,7 +1899,7 @@ pysma==0.7.5
|
||||
pysmappee==0.2.29
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.2.0
|
||||
pysmartthings==3.2.1
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.2
|
||||
@@ -2138,7 +2138,7 @@ refoss-ha==1.2.5
|
||||
regenmaschine==2024.03.0
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.3.0
|
||||
renault-api==0.3.1
|
||||
|
||||
# homeassistant.components.renson
|
||||
renson-endura-delta==1.7.2
|
||||
@@ -2404,7 +2404,7 @@ typedmonarchmoney==0.4.4
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==7.5.5
|
||||
uiprotect==7.6.0
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.7
|
||||
@@ -2485,7 +2485,7 @@ webio-api==0.1.11
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2025.3.7
|
||||
weheat==2025.4.29
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==0.20.0
|
||||
|
||||
Generated
+1
-1
@@ -25,7 +25,7 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.7.1,source=/uv,target=/bin/uv \
|
||||
-c /usr/src/homeassistant/homeassistant/package_constraints.txt \
|
||||
-r /usr/src/homeassistant/requirements.txt \
|
||||
stdlib-list==0.10.0 pipdeptree==2.25.1 tqdm==4.67.1 ruff==0.11.0 \
|
||||
PyTurboJPEG==1.7.5 go2rtc-client==0.1.2 ha-ffmpeg==3.2.2 hassil==2.2.3 home-assistant-intents==2025.4.30 mutagen==1.47.0 pymicro-vad==1.0.1 pyspeex-noise==1.0.2
|
||||
PyTurboJPEG==1.7.5 go2rtc-client==0.1.2 ha-ffmpeg==3.2.2 hassil==2.2.3 home-assistant-intents==2025.5.7 mutagen==1.47.0 pymicro-vad==1.0.1 pyspeex-noise==1.0.2
|
||||
|
||||
LABEL "name"="hassfest"
|
||||
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -177,7 +177,7 @@ async def _test_downloading_encrypted_backup(
|
||||
enc_metadata = json.loads(outer_tar.extractfile("./backup.json").read())
|
||||
assert enc_metadata["protected"] is True
|
||||
with (
|
||||
outer_tar.extractfile("core.tar.gz") as inner_tar_file,
|
||||
outer_tar.extractfile("homeassistant.tar.gz") as inner_tar_file,
|
||||
pytest.raises(tarfile.ReadError, match="file could not be opened"),
|
||||
):
|
||||
# pylint: disable-next=consider-using-with
|
||||
@@ -209,7 +209,7 @@ async def _test_downloading_encrypted_backup(
|
||||
dec_metadata = json.loads(outer_tar.extractfile("./backup.json").read())
|
||||
assert dec_metadata == enc_metadata | {"protected": False}
|
||||
with (
|
||||
outer_tar.extractfile("core.tar.gz") as inner_tar_file,
|
||||
outer_tar.extractfile("homeassistant.tar.gz") as inner_tar_file,
|
||||
tarfile.open(fileobj=inner_tar_file, mode="r") as inner_tar,
|
||||
):
|
||||
assert inner_tar.getnames() == [
|
||||
|
||||
@@ -174,7 +174,10 @@ async def test_decrypted_backup_streamer(hass: HomeAssistant) -> None:
|
||||
)
|
||||
encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -218,7 +221,10 @@ async def test_decrypted_backup_streamer_interrupt_stuck_reader(
|
||||
"""Test the decrypted backup streamer."""
|
||||
encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -253,7 +259,10 @@ async def test_decrypted_backup_streamer_interrupt_stuck_writer(
|
||||
"""Test the decrypted backup streamer."""
|
||||
encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -283,7 +292,10 @@ async def test_decrypted_backup_streamer_wrong_password(hass: HomeAssistant) ->
|
||||
"""Test the decrypted backup streamer with wrong password."""
|
||||
encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -320,7 +332,10 @@ async def test_encrypted_backup_streamer(hass: HomeAssistant) -> None:
|
||||
)
|
||||
encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -353,15 +368,16 @@ async def test_encrypted_backup_streamer(hass: HomeAssistant) -> None:
|
||||
bytes.fromhex("00000000000000000000000000000000"),
|
||||
)
|
||||
encryptor = EncryptedBackupStreamer(hass, backup, open_backup, "hunter2")
|
||||
assert encryptor.backup() == dataclasses.replace(
|
||||
backup, protected=True, size=backup.size + len(expected_padding)
|
||||
)
|
||||
|
||||
encrypted_stream = await encryptor.open_stream()
|
||||
encrypted_output = b""
|
||||
async for chunk in encrypted_stream:
|
||||
encrypted_output += chunk
|
||||
await encryptor.wait()
|
||||
assert encryptor.backup() == dataclasses.replace(
|
||||
backup, protected=True, size=backup.size + len(expected_padding)
|
||||
)
|
||||
|
||||
encrypted_stream = await encryptor.open_stream()
|
||||
encrypted_output = b""
|
||||
async for chunk in encrypted_stream:
|
||||
encrypted_output += chunk
|
||||
await encryptor.wait()
|
||||
|
||||
# Expect the output to match the stored encrypted backup file, with additional
|
||||
# padding.
|
||||
@@ -377,7 +393,10 @@ async def test_encrypted_backup_streamer_interrupt_stuck_reader(
|
||||
"test_backups/c0cb53bd.tar.decrypted", DOMAIN
|
||||
)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -414,7 +433,10 @@ async def test_encrypted_backup_streamer_interrupt_stuck_writer(
|
||||
"test_backups/c0cb53bd.tar.decrypted", DOMAIN
|
||||
)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -447,7 +469,10 @@ async def test_encrypted_backup_streamer_random_nonce(hass: HomeAssistant) -> No
|
||||
)
|
||||
encrypted_backup_path = get_fixture_path("test_backups/c0cb53bd.tar", DOMAIN)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
@@ -490,7 +515,7 @@ async def test_encrypted_backup_streamer_random_nonce(hass: HomeAssistant) -> No
|
||||
await encryptor1.wait()
|
||||
await encryptor2.wait()
|
||||
|
||||
# Output from the two streames should differ but have the same length.
|
||||
# Output from the two streams should differ but have the same length.
|
||||
assert encrypted_output1 != encrypted_output3
|
||||
assert len(encrypted_output1) == len(encrypted_output3)
|
||||
|
||||
@@ -508,7 +533,10 @@ async def test_encrypted_backup_streamer_error(hass: HomeAssistant) -> None:
|
||||
"test_backups/c0cb53bd.tar.decrypted", DOMAIN
|
||||
)
|
||||
backup = AgentBackup(
|
||||
addons=["addon_1", "addon_2"],
|
||||
addons=[
|
||||
AddonInfo(name="Core 1", slug="core1", version="1.0.0"),
|
||||
AddonInfo(name="Core 2", slug="core2", version="1.0.0"),
|
||||
],
|
||||
backup_id="1234",
|
||||
date="2024-12-02T07:23:58.261875-05:00",
|
||||
database_included=False,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Mocks for tests."""
|
||||
|
||||
from datetime import UTC
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@@ -28,6 +29,7 @@ class BinarySensorPropertyMock(BinarySensorProperty):
|
||||
def __init__(self, **kwargs: Any) -> None: # pylint: disable=super-init-not-called
|
||||
"""Initialize the mock."""
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
self.element_uid = "Test"
|
||||
self.key_count = 1
|
||||
self.sensor_type = "door"
|
||||
@@ -41,6 +43,7 @@ class BinarySwitchPropertyMock(BinarySwitchProperty):
|
||||
def __init__(self, **kwargs: Any) -> None: # pylint: disable=super-init-not-called
|
||||
"""Initialize the mock."""
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
self.element_uid = "Test"
|
||||
self.state = False
|
||||
|
||||
@@ -51,6 +54,7 @@ class ConsumptionPropertyMock(ConsumptionProperty):
|
||||
def __init__(self, **kwargs: Any) -> None: # pylint: disable=super-init-not-called
|
||||
"""Initialize the mock."""
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
self.element_uid = "devolo.Meter:Test"
|
||||
self.current_unit = "W"
|
||||
self.total_unit = "kWh"
|
||||
@@ -68,6 +72,7 @@ class MultiLevelSensorPropertyMock(MultiLevelSensorProperty):
|
||||
self._unit = "°C"
|
||||
self._value = 20
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
|
||||
|
||||
class BrightnessSensorPropertyMock(MultiLevelSensorProperty):
|
||||
@@ -80,6 +85,7 @@ class BrightnessSensorPropertyMock(MultiLevelSensorProperty):
|
||||
self._unit = "%"
|
||||
self._value = 20
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
|
||||
|
||||
class MultiLevelSwitchPropertyMock(MultiLevelSwitchProperty):
|
||||
@@ -92,6 +98,7 @@ class MultiLevelSwitchPropertyMock(MultiLevelSwitchProperty):
|
||||
self.max = 24
|
||||
self._value = 20
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
|
||||
|
||||
class SirenPropertyMock(MultiLevelSwitchProperty):
|
||||
@@ -105,6 +112,7 @@ class SirenPropertyMock(MultiLevelSwitchProperty):
|
||||
self.switch_type = "tone"
|
||||
self._value = 0
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
|
||||
|
||||
class SettingsMock(SettingsProperty):
|
||||
@@ -113,6 +121,7 @@ class SettingsMock(SettingsProperty):
|
||||
def __init__(self, **kwargs: Any) -> None: # pylint: disable=super-init-not-called
|
||||
"""Initialize the mock."""
|
||||
self._logger = MagicMock()
|
||||
self._timezone = UTC
|
||||
self.name = "Test"
|
||||
self.zone = "Test"
|
||||
self.tone = 1
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
from . import FritzDeviceCoverMock, FritzDeviceSwitchMock
|
||||
from . import FritzDeviceCoverMock, FritzDeviceSwitchMock, FritzEntityBaseMock
|
||||
from .const import MOCK_CONFIG
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
@@ -84,6 +84,8 @@ async def test_coordinator_automatic_registry_cleanup(
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test automatic registry cleanup."""
|
||||
|
||||
# init with 2 devices and 1 template
|
||||
fritz().get_devices.return_value = [
|
||||
FritzDeviceSwitchMock(
|
||||
ain="fake ain switch",
|
||||
@@ -96,6 +98,13 @@ async def test_coordinator_automatic_registry_cleanup(
|
||||
name="fake_cover",
|
||||
),
|
||||
]
|
||||
fritz().get_templates.return_value = [
|
||||
FritzEntityBaseMock(
|
||||
ain="fake ain template",
|
||||
device_and_unit_id=("fake ain template", None),
|
||||
name="fake_template",
|
||||
)
|
||||
]
|
||||
entry = MockConfigEntry(
|
||||
domain=FB_DOMAIN,
|
||||
data=MOCK_CONFIG[FB_DOMAIN][CONF_DEVICES][0],
|
||||
@@ -105,9 +114,10 @@ async def test_coordinator_automatic_registry_cleanup(
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 19
|
||||
assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 2
|
||||
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 20
|
||||
assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 3
|
||||
|
||||
# remove one device, keep the template
|
||||
fritz().get_devices.return_value = [
|
||||
FritzDeviceSwitchMock(
|
||||
ain="fake ain switch",
|
||||
@@ -119,5 +129,14 @@ async def test_coordinator_automatic_registry_cleanup(
|
||||
async_fire_time_changed(hass, utcnow() + timedelta(seconds=35))
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 13
|
||||
assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 2
|
||||
|
||||
# remove the template, keep the device
|
||||
fritz().get_templates.return_value = []
|
||||
|
||||
async_fire_time_changed(hass, utcnow() + timedelta(seconds=35))
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert len(er.async_entries_for_config_entry(entity_registry, entry.entry_id)) == 12
|
||||
assert len(dr.async_entries_for_config_entry(device_registry, entry.entry_id)) == 1
|
||||
|
||||
@@ -34,9 +34,11 @@ from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_SUPPORTED_FEATURES,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
|
||||
@@ -437,6 +439,22 @@ async def test_expose_linked_sensors(
|
||||
assert acc.char_air_quality.value == 1
|
||||
assert len(broker.mock_calls) == 0
|
||||
|
||||
# Updated temperature with different unit should reflect in HomeKit
|
||||
broker = MagicMock()
|
||||
acc.char_current_temperature.broker = broker
|
||||
hass.states.async_set(
|
||||
temperature_entity_id,
|
||||
60,
|
||||
{
|
||||
ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
|
||||
ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.FAHRENHEIT,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_current_temperature.value == 15.6
|
||||
assert len(broker.mock_calls) == 2
|
||||
broker.reset_mock()
|
||||
|
||||
# Updated temperature should reflect in HomeKit
|
||||
broker = MagicMock()
|
||||
acc.char_current_temperature.broker = broker
|
||||
|
||||
@@ -21,37 +21,47 @@ from .const import TEST_MOWER_ID
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_lawn_mower_states(
|
||||
hass: HomeAssistant,
|
||||
mock_automower_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
values: dict[str, MowerAttributes],
|
||||
) -> None:
|
||||
"""Test lawn_mower state."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
state = hass.states.get("lawn_mower.test_mower_1")
|
||||
assert state is not None
|
||||
assert state.state == LawnMowerActivity.DOCKED
|
||||
|
||||
for activity, state, expected_state in (
|
||||
@pytest.mark.parametrize(
|
||||
("activity", "mower_state", "expected_state"),
|
||||
[
|
||||
(MowerActivities.UNKNOWN, MowerStates.PAUSED, LawnMowerActivity.PAUSED),
|
||||
(MowerActivities.MOWING, MowerStates.NOT_APPLICABLE, LawnMowerActivity.MOWING),
|
||||
(MowerActivities.MOWING, MowerStates.IN_OPERATION, LawnMowerActivity.MOWING),
|
||||
(MowerActivities.NOT_APPLICABLE, MowerStates.ERROR, LawnMowerActivity.ERROR),
|
||||
(
|
||||
MowerActivities.GOING_HOME,
|
||||
MowerStates.IN_OPERATION,
|
||||
LawnMowerActivity.RETURNING,
|
||||
),
|
||||
):
|
||||
values[TEST_MOWER_ID].mower.activity = activity
|
||||
values[TEST_MOWER_ID].mower.state = state
|
||||
mock_automower_client.get_status.return_value = values
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("lawn_mower.test_mower_1")
|
||||
assert state.state == expected_state
|
||||
(
|
||||
MowerActivities.NOT_APPLICABLE,
|
||||
MowerStates.IN_OPERATION,
|
||||
LawnMowerActivity.MOWING,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_lawn_mower_states(
|
||||
hass: HomeAssistant,
|
||||
mock_automower_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
values: dict[str, MowerAttributes],
|
||||
activity: MowerActivities,
|
||||
mower_state: MowerStates,
|
||||
expected_state: LawnMowerActivity,
|
||||
) -> None:
|
||||
"""Test lawn_mower state."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
state = hass.states.get("lawn_mower.test_mower_1")
|
||||
assert state is not None
|
||||
assert state.state == LawnMowerActivity.DOCKED
|
||||
values[TEST_MOWER_ID].mower.activity = activity
|
||||
values[TEST_MOWER_ID].mower.state = mower_state
|
||||
mock_automower_client.get_status.return_value = values
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get("lawn_mower.test_mower_1")
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -104,19 +104,6 @@ async def test_unpause_media_player_intent(hass: HomeAssistant) -> None:
|
||||
assert call.service == SERVICE_MEDIA_PLAY
|
||||
assert call.data == {"entity_id": entity_id}
|
||||
|
||||
# Test if not paused
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_PLAYING,
|
||||
)
|
||||
|
||||
with pytest.raises(intent.MatchFailedError):
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
media_player_intent.INTENT_MEDIA_UNPAUSE,
|
||||
)
|
||||
|
||||
|
||||
async def test_next_media_player_intent(hass: HomeAssistant) -> None:
|
||||
"""Test HassMediaNext intent for media players."""
|
||||
@@ -245,17 +232,6 @@ async def test_volume_media_player_intent(hass: HomeAssistant) -> None:
|
||||
assert call.service == SERVICE_VOLUME_SET
|
||||
assert call.data == {"entity_id": entity_id, "volume_level": 0.5}
|
||||
|
||||
# Test if not playing
|
||||
hass.states.async_set(entity_id, STATE_IDLE, attributes=attributes)
|
||||
|
||||
with pytest.raises(intent.MatchFailedError):
|
||||
response = await intent.async_handle(
|
||||
hass,
|
||||
"test",
|
||||
media_player_intent.INTENT_SET_VOLUME,
|
||||
{"volume_level": {"value": 50}},
|
||||
)
|
||||
|
||||
# Test feature not supported
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
|
||||
@@ -153,6 +153,10 @@ MOCK_SUBENTRY_LIGHT_BASIC_KELVIN_COMPONENT = {
|
||||
"state_topic": "test-topic",
|
||||
"color_temp_kelvin": True,
|
||||
"state_value_template": "{{ value_json.value }}",
|
||||
"brightness_scale": 255,
|
||||
"max_kelvin": 6535,
|
||||
"min_kelvin": 2000,
|
||||
"white_scale": 255,
|
||||
"entity_picture": "https://example.com/8131babc5e8d4f44b82e0761d39091a2",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2817,14 +2817,22 @@ async def test_migrate_of_incompatible_config_entry(
|
||||
},
|
||||
{"state_topic": "invalid_subscribe_topic"},
|
||||
),
|
||||
(
|
||||
{
|
||||
"command_topic": "test-topic",
|
||||
"light_brightness_settings": {
|
||||
"brightness_command_topic": "test-topic#invalid"
|
||||
},
|
||||
},
|
||||
{"light_brightness_settings": "invalid_publish_topic"},
|
||||
),
|
||||
(
|
||||
{
|
||||
"command_topic": "test-topic",
|
||||
"advanced_settings": {"max_kelvin": 2000, "min_kelvin": 2000},
|
||||
},
|
||||
{
|
||||
"max_kelvin": "max_below_min_kelvin",
|
||||
"min_kelvin": "max_below_min_kelvin",
|
||||
"advanced_settings": "max_below_min_kelvin",
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1005,102 +1005,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_driver_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_driver_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Driver door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'driver_door_status',
|
||||
'unique_id': 'vf1twingoiiivin_driver_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_driver_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-TWINGO-III Driver door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_driver_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_hatch-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': None,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_hatch',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Hatch',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'hatch_status',
|
||||
'unique_id': 'vf1twingoiiivin_hatch_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_hatch-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-TWINGO-III Hatch',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_hatch',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_hvac-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1148,102 +1052,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_lock-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': None,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_lock',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.LOCK: 'lock'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Lock',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'vf1twingoiiivin_lock_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_lock-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'lock',
|
||||
'friendly_name': 'REG-TWINGO-III Lock',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_lock',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_passenger_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_passenger_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Passenger door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'passenger_door_status',
|
||||
'unique_id': 'vf1twingoiiivin_passenger_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_passenger_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-TWINGO-III Passenger door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_passenger_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_plug-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1292,102 +1100,6 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_rear_left_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_rear_left_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Rear left door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'rear_left_door_status',
|
||||
'unique_id': 'vf1twingoiiivin_rear_left_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_rear_left_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-TWINGO-III Rear left door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_rear_left_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_rear_right_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_rear_right_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Rear right door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'rear_right_door_status',
|
||||
'unique_id': 'vf1twingoiiivin_rear_right_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[twingo_3_electric][binary_sensor.reg_twingo_iii_rear_right_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-TWINGO-III Rear right door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_twingo_iii_rear_right_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_40][binary_sensor.reg_zoe_40_charging-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1579,102 +1291,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_driver_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_driver_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Driver door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'driver_door_status',
|
||||
'unique_id': 'vf1zoe50vin_driver_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_driver_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-ZOE-50 Driver door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_driver_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_hatch-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': None,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_hatch',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Hatch',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'hatch_status',
|
||||
'unique_id': 'vf1zoe50vin_hatch_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_hatch-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-ZOE-50 Hatch',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_hatch',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_hvac-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1722,102 +1338,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_lock-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': None,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_lock',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.LOCK: 'lock'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Lock',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'vf1zoe50vin_lock_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_lock-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'lock',
|
||||
'friendly_name': 'REG-ZOE-50 Lock',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_lock',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_passenger_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_passenger_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Passenger door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'passenger_door_status',
|
||||
'unique_id': 'vf1zoe50vin_passenger_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_passenger_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-ZOE-50 Passenger door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_passenger_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_plug-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1866,99 +1386,3 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_rear_left_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_rear_left_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Rear left door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'rear_left_door_status',
|
||||
'unique_id': 'vf1zoe50vin_rear_left_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_rear_left_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-ZOE-50 Rear left door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_rear_left_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_rear_right_door-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': None,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_rear_right_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Rear right door',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'rear_right_door_status',
|
||||
'unique_id': 'vf1zoe50vin_rear_right_door_status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[zoe_50][binary_sensor.reg_zoe_50_rear_right_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'REG-ZOE-50 Rear right door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.reg_zoe_50_rear_right_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -3211,100 +3211,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[twingo_3_electric][sensor.reg_twingo_iii_remote_engine_start-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': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.reg_twingo_iii_remote_engine_start',
|
||||
'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': 'Remote engine start',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'res_state',
|
||||
'unique_id': 'vf1twingoiiivin_res_state',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[twingo_3_electric][sensor.reg_twingo_iii_remote_engine_start-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-TWINGO-III Remote engine start',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.reg_twingo_iii_remote_engine_start',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[twingo_3_electric][sensor.reg_twingo_iii_remote_engine_start_code-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': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.reg_twingo_iii_remote_engine_start_code',
|
||||
'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': 'Remote engine start code',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'res_state_code',
|
||||
'unique_id': 'vf1twingoiiivin_res_state_code',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[twingo_3_electric][sensor.reg_twingo_iii_remote_engine_start_code-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-TWINGO-III Remote engine start code',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.reg_twingo_iii_remote_engine_start_code',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[zoe_40][sensor.reg_zoe_40_battery-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -4737,97 +4643,3 @@
|
||||
'state': 'unplugged',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[zoe_50][sensor.reg_zoe_50_remote_engine_start-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': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.reg_zoe_50_remote_engine_start',
|
||||
'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': 'Remote engine start',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'res_state',
|
||||
'unique_id': 'vf1zoe50vin_res_state',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[zoe_50][sensor.reg_zoe_50_remote_engine_start-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-ZOE-50 Remote engine start',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.reg_zoe_50_remote_engine_start',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'Stopped, ready for RES',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[zoe_50][sensor.reg_zoe_50_remote_engine_start_code-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': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.reg_zoe_50_remote_engine_start_code',
|
||||
'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': 'Remote engine start code',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'res_state_code',
|
||||
'unique_id': 'vf1zoe50vin_res_state_code',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[zoe_50][sensor.reg_zoe_50_remote_engine_start_code-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-ZOE-50 Remote engine start code',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.reg_zoe_50_remote_engine_start_code',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '10',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -197,7 +197,7 @@ async def test_sensor_throttling_after_init(
|
||||
@pytest.mark.parametrize(
|
||||
("vehicle_type", "vehicle_count", "scan_interval"),
|
||||
[
|
||||
("zoe_50", 1, 420), # 7 coordinators => 7 minutes interval
|
||||
("zoe_50", 1, 300), # 5 coordinators => 5 minutes interval
|
||||
("captur_fuel", 1, 240), # 4 coordinators => 4 minutes interval
|
||||
("multi", 2, 480), # 8 coordinators => 8 minutes interval
|
||||
],
|
||||
@@ -236,7 +236,7 @@ async def test_dynamic_scan_interval(
|
||||
@pytest.mark.parametrize(
|
||||
("vehicle_type", "vehicle_count", "scan_interval"),
|
||||
[
|
||||
("zoe_50", 1, 300), # (7-2) coordinators => 5 minutes interval
|
||||
("zoe_50", 1, 240), # (6-2) coordinators => 4 minutes interval
|
||||
("captur_fuel", 1, 180), # (4-1) coordinators => 3 minutes interval
|
||||
("multi", 2, 360), # (8-2) coordinators => 6 minutes interval
|
||||
],
|
||||
|
||||
@@ -39,7 +39,7 @@ from homeassistant.helpers import (
|
||||
entity_registry as er,
|
||||
issue_registry as ir,
|
||||
)
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .conftest import (
|
||||
@@ -51,6 +51,7 @@ from .conftest import (
|
||||
TEST_HOST,
|
||||
TEST_HOST_MODEL,
|
||||
TEST_MAC,
|
||||
TEST_MAC_CAM,
|
||||
TEST_NVR_NAME,
|
||||
TEST_PORT,
|
||||
TEST_PRIVACY,
|
||||
@@ -614,6 +615,55 @@ async def test_migrate_with_already_existing_entity(
|
||||
assert entity_registry.async_get_entity_id(domain, DOMAIN, new_id)
|
||||
|
||||
|
||||
async def test_cleanup_mac_connection(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
reolink_connect: MagicMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test cleanup of the MAC of a IPC which was set to the MAC of the host."""
|
||||
reolink_connect.channels = [0]
|
||||
reolink_connect.baichuan.mac_address.return_value = None
|
||||
entity_id = f"{TEST_UID}_{TEST_UID_CAM}_record_audio"
|
||||
dev_id = f"{TEST_UID}_{TEST_UID_CAM}"
|
||||
domain = Platform.SWITCH
|
||||
|
||||
dev_entry = device_registry.async_get_or_create(
|
||||
identifiers={(DOMAIN, dev_id)},
|
||||
connections={(CONNECTION_NETWORK_MAC, TEST_MAC)},
|
||||
config_entry_id=config_entry.entry_id,
|
||||
disabled_by=None,
|
||||
)
|
||||
|
||||
entity_registry.async_get_or_create(
|
||||
domain=domain,
|
||||
platform=DOMAIN,
|
||||
unique_id=entity_id,
|
||||
config_entry=config_entry,
|
||||
suggested_object_id=entity_id,
|
||||
disabled_by=None,
|
||||
device_id=dev_entry.id,
|
||||
)
|
||||
|
||||
assert entity_registry.async_get_entity_id(domain, DOMAIN, entity_id)
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, dev_id)})
|
||||
assert device
|
||||
assert device.connections == {(CONNECTION_NETWORK_MAC, TEST_MAC)}
|
||||
|
||||
# setup CH 0 and host entities/device
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [domain]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert entity_registry.async_get_entity_id(domain, DOMAIN, entity_id)
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, dev_id)})
|
||||
assert device
|
||||
assert device.connections == set()
|
||||
|
||||
reolink_connect.baichuan.mac_address.return_value = TEST_MAC_CAM
|
||||
|
||||
|
||||
async def test_no_repair_issue(
|
||||
hass: HomeAssistant, config_entry: MockConfigEntry, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
|
||||
@@ -122,6 +122,7 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
"da_wm_wd_000001",
|
||||
"da_wm_wd_000001_1",
|
||||
"da_wm_wm_01011",
|
||||
"da_wm_wm_100001",
|
||||
"da_wm_wm_000001",
|
||||
"da_wm_wm_000001_1",
|
||||
"da_wm_sc_000001",
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"ocf": {
|
||||
"st": {
|
||||
"value": null,
|
||||
"timestamp": "2020-10-06T23:01:03.011Z"
|
||||
},
|
||||
"mndt": {
|
||||
"value": null,
|
||||
"timestamp": "2021-01-28T11:54:37.203Z"
|
||||
},
|
||||
"mnfv": {
|
||||
"value": null,
|
||||
"timestamp": "2020-12-20T14:21:43.766Z"
|
||||
},
|
||||
"mnhw": {
|
||||
"value": null,
|
||||
"timestamp": "2021-01-25T22:57:01.985Z"
|
||||
},
|
||||
"di": {
|
||||
"value": "C0972771-01D0-0000-0000-000000000000",
|
||||
"timestamp": "2019-08-10T18:37:20.487Z"
|
||||
},
|
||||
"mnsl": {
|
||||
"value": null,
|
||||
"timestamp": "2020-12-20T14:21:31.219Z"
|
||||
},
|
||||
"dmv": {
|
||||
"value": "res.1.1.0,sh.1.1.0",
|
||||
"timestamp": "2019-08-10T18:37:20.514Z"
|
||||
},
|
||||
"n": {
|
||||
"value": "Washer",
|
||||
"timestamp": "2019-08-10T18:37:20.555Z"
|
||||
},
|
||||
"mnmo": {
|
||||
"value": "TP6X_WA54M8750AV|20183944|20000101001111000100000000000000",
|
||||
"timestamp": "2019-08-10T18:37:20.409Z"
|
||||
},
|
||||
"vid": {
|
||||
"value": "DA-WM-WM-100001",
|
||||
"timestamp": "2019-08-10T18:37:20.381Z"
|
||||
},
|
||||
"mnmn": {
|
||||
"value": "Samsung Electronics",
|
||||
"timestamp": "2019-08-10T18:37:20.436Z"
|
||||
},
|
||||
"mnml": {
|
||||
"value": null,
|
||||
"timestamp": "2021-01-28T11:54:37.092Z"
|
||||
},
|
||||
"mnpv": {
|
||||
"value": null,
|
||||
"timestamp": "2021-01-26T20:55:28.663Z"
|
||||
},
|
||||
"mnos": {
|
||||
"value": null,
|
||||
"timestamp": "2021-01-26T20:55:28.411Z"
|
||||
},
|
||||
"pi": {
|
||||
"value": "shp",
|
||||
"timestamp": "2019-08-10T18:37:20.457Z"
|
||||
},
|
||||
"icv": {
|
||||
"value": "core.1.1.0",
|
||||
"timestamp": "2019-08-10T18:37:20.534Z"
|
||||
}
|
||||
},
|
||||
"remoteControlStatus": {
|
||||
"remoteControlEnabled": {
|
||||
"value": "false",
|
||||
"timestamp": "2025-04-06T17:30:05.372Z"
|
||||
}
|
||||
},
|
||||
"samsungce.driverVersion": {
|
||||
"versionNumber": {
|
||||
"value": 22100103,
|
||||
"timestamp": "2022-11-01T11:53:01.255Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"samsungce.washerOperatingState": {
|
||||
"washerJobState": {
|
||||
"value": "none",
|
||||
"timestamp": "2025-04-18T13:17:00.432Z"
|
||||
},
|
||||
"operatingState": {
|
||||
"value": "ready",
|
||||
"timestamp": "2025-04-18T13:17:00.432Z"
|
||||
},
|
||||
"supportedOperatingStates": {
|
||||
"value": ["ready", "running", "paused"],
|
||||
"timestamp": "2022-11-01T11:53:01.255Z"
|
||||
},
|
||||
"scheduledJobs": {
|
||||
"value": null
|
||||
},
|
||||
"scheduledPhases": {
|
||||
"value": null
|
||||
},
|
||||
"progress": {
|
||||
"value": null
|
||||
},
|
||||
"remainingTimeStr": {
|
||||
"value": "00:57",
|
||||
"timestamp": "2025-04-18T13:17:00.432Z"
|
||||
},
|
||||
"washerJobPhase": {
|
||||
"value": null
|
||||
},
|
||||
"operationTime": {
|
||||
"value": null
|
||||
},
|
||||
"remainingTime": {
|
||||
"value": 57,
|
||||
"unit": "min",
|
||||
"timestamp": "2025-04-18T13:17:00.432Z"
|
||||
}
|
||||
},
|
||||
"execute": {
|
||||
"data": {
|
||||
"value": null,
|
||||
"data": {},
|
||||
"timestamp": "2020-10-05T02:10:50.602Z"
|
||||
}
|
||||
},
|
||||
"washerOperatingState": {
|
||||
"completionTime": {
|
||||
"value": "2025-04-18T14:14:00Z",
|
||||
"timestamp": "2025-04-18T13:17:00.432Z"
|
||||
},
|
||||
"machineState": {
|
||||
"value": "stop",
|
||||
"timestamp": "2025-04-18T13:17:00.432Z"
|
||||
},
|
||||
"washerJobState": {
|
||||
"value": "none",
|
||||
"timestamp": "2025-04-18T13:17:00.432Z"
|
||||
},
|
||||
"supportedMachineStates": {
|
||||
"value": null,
|
||||
"timestamp": "2020-08-14T14:25:00.803Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null,
|
||||
"timestamp": "2020-09-13T18:32:28.637Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"name": "Washer",
|
||||
"label": "Washer",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"presentationId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"deviceManufacturerCode": "Samsung Electronics",
|
||||
"locationId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"ownerId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
"deviceTypeName": "Samsung OCF Washer",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "Washer",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "ocf",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "execute",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "remoteControlStatus",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "washerOperatingState",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.driverVersion",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.washerOperatingState",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Washer",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
],
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"createTime": "2019-08-10T18:37:20Z",
|
||||
"profile": {
|
||||
"id": "REDACTED"
|
||||
},
|
||||
"ocf": {
|
||||
"ocfDeviceType": "oic.d.washer",
|
||||
"name": "Washer",
|
||||
"specVersion": "core.1.1.0",
|
||||
"verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"modelNumber": "TP6X_WA54M8750AV|20183944|20000101001111000100000000000000",
|
||||
"vendorId": "DA-WM-WM-100001",
|
||||
"lastSignupTime": "2021-01-16T06:29:39.379382Z",
|
||||
"transferCandidate": false,
|
||||
"additionalAuthCodeRequired": false
|
||||
},
|
||||
"type": "OCF",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
||||
@@ -2089,6 +2089,101 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][binary_sensor.washer_power-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': None,
|
||||
'entity_id': 'binary_sensor.washer_power',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.POWER: 'power'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Power',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_main_switch_switch_switch',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][binary_sensor.washer_power-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'Washer Power',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.washer_power',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][binary_sensor.washer_remote_control-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': None,
|
||||
'entity_id': 'binary_sensor.washer_remote_control',
|
||||
'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': 'Remote control',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'remote_control',
|
||||
'unique_id': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_main_remoteControlStatus_remoteControlEnabled_remoteControlEnabled',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][binary_sensor.washer_remote_control-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Washer Remote control',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.washer_remote_control',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ecobee_sensor][binary_sensor.child_bedroom_motion-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -992,6 +992,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[da_wm_wm_100001]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': 'https://account.smartthings.com',
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Samsung Electronics',
|
||||
'model': 'TP6X_WA54M8750AV',
|
||||
'model_id': None,
|
||||
'name': 'Washer',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[ecobee_sensor]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
|
||||
@@ -525,3 +525,61 @@
|
||||
'state': 'standard',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][select.washer-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'run',
|
||||
'pause',
|
||||
'stop',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': None,
|
||||
'entity_id': 'select.washer',
|
||||
'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': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'operating_state',
|
||||
'unique_id': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_main_washerOperatingState_machineState_machineState',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][select.washer-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Washer',
|
||||
'options': list([
|
||||
'run',
|
||||
'pause',
|
||||
'stop',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.washer',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'stop',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -1364,7 +1364,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1402,7 +1402,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'AC Office Granit Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -1793,7 +1793,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1831,7 +1831,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Office AirFree Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2222,7 +2222,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -2260,7 +2260,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Aire Dormitorio Principal Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4000,7 +4000,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -4038,7 +4038,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Refrigerator Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4277,7 +4277,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -4315,7 +4315,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Refrigerator Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -4554,7 +4554,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -4592,7 +4592,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Frigo Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5128,7 +5128,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -5166,7 +5166,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Eco Heating System Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -5637,7 +5637,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -5675,7 +5675,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Dishwasher Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -6104,7 +6104,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -6142,7 +6142,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'AirDresser Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -6571,7 +6571,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -6609,7 +6609,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Dryer Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -7038,7 +7038,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -7076,7 +7076,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Seca-Roupa Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -7507,7 +7507,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -7545,7 +7545,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Washer Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -7976,7 +7976,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -8014,7 +8014,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Washing Machine Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -8445,7 +8445,7 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -8483,7 +8483,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Machine à Laver Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -8546,6 +8546,198 @@
|
||||
'state': '1642.2',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][sensor.washer_completion_time-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': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.washer_completion_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Completion time',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'completion_time',
|
||||
'unique_id': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_main_washerOperatingState_completionTime_completionTime',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][sensor.washer_completion_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Washer Completion time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.washer_completion_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2025-04-18T14:14:00+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][sensor.washer_job_state-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'air_wash',
|
||||
'ai_rinse',
|
||||
'ai_spin',
|
||||
'ai_wash',
|
||||
'cooling',
|
||||
'delay_wash',
|
||||
'drying',
|
||||
'finish',
|
||||
'none',
|
||||
'pre_wash',
|
||||
'rinse',
|
||||
'spin',
|
||||
'wash',
|
||||
'weight_sensing',
|
||||
'wrinkle_prevent',
|
||||
'freeze_protection',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.washer_job_state',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Job state',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'washer_job_state',
|
||||
'unique_id': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_main_washerOperatingState_washerJobState_washerJobState',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][sensor.washer_job_state-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Washer Job state',
|
||||
'options': list([
|
||||
'air_wash',
|
||||
'ai_rinse',
|
||||
'ai_spin',
|
||||
'ai_wash',
|
||||
'cooling',
|
||||
'delay_wash',
|
||||
'drying',
|
||||
'finish',
|
||||
'none',
|
||||
'pre_wash',
|
||||
'rinse',
|
||||
'spin',
|
||||
'wash',
|
||||
'weight_sensing',
|
||||
'wrinkle_prevent',
|
||||
'freeze_protection',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.washer_job_state',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'none',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][sensor.washer_machine_state-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'pause',
|
||||
'run',
|
||||
'stop',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.washer_machine_state',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Machine state',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'washer_machine_state',
|
||||
'unique_id': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee_main_washerOperatingState_machineState_machineState',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_wm_wm_100001][sensor.washer_machine_state-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Washer Machine state',
|
||||
'options': list([
|
||||
'pause',
|
||||
'run',
|
||||
'stop',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.washer_machine_state',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'stop',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[ecobee_sensor][sensor.child_bedroom_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -5,7 +5,7 @@ from http import HTTPStatus
|
||||
from io import BytesIO
|
||||
import json
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, call, patch
|
||||
|
||||
import pytest
|
||||
from zwave_js_server.const import (
|
||||
@@ -5078,53 +5078,97 @@ async def test_subscribe_node_statistics(
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason="The test needs to be updated to reflect what happens when resetting the controller"
|
||||
)
|
||||
async def test_hard_reset_controller(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
client,
|
||||
integration,
|
||||
listen_block,
|
||||
client: MagicMock,
|
||||
integration: MockConfigEntry,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test that the hard_reset_controller WS API call works."""
|
||||
entry = integration
|
||||
ws_client = await hass_ws_client(hass)
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])}
|
||||
)
|
||||
async def async_send_command_driver_ready(
|
||||
message: dict[str, Any],
|
||||
require_schema: int | None = None,
|
||||
) -> dict:
|
||||
"""Send a command and get a response."""
|
||||
client.driver.emit(
|
||||
"driver ready", {"event": "driver ready", "source": "driver"}
|
||||
)
|
||||
return {}
|
||||
|
||||
client.async_send_command.return_value = {}
|
||||
await ws_client.send_json(
|
||||
client.async_send_command.side_effect = async_send_command_driver_ready
|
||||
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
ID: 1,
|
||||
TYPE: "zwave_js/hard_reset_controller",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
}
|
||||
)
|
||||
|
||||
listen_block.set()
|
||||
listen_block.clear()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])}
|
||||
)
|
||||
assert device is not None
|
||||
assert msg["result"] == device.id
|
||||
assert msg["success"]
|
||||
|
||||
assert len(client.async_send_command.call_args_list) == 1
|
||||
assert client.async_send_command.call_args[0][0] == {"command": "driver.hard_reset"}
|
||||
assert client.async_send_command.call_count == 3
|
||||
# The first call is the relevant hard reset command.
|
||||
# 25 is the require_schema parameter.
|
||||
assert client.async_send_command.call_args_list[0] == call(
|
||||
{"command": "driver.hard_reset"}, 25
|
||||
)
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test sending command with driver not ready and timeout.
|
||||
|
||||
async def async_send_command_no_driver_ready(
|
||||
message: dict[str, Any],
|
||||
require_schema: int | None = None,
|
||||
) -> dict:
|
||||
"""Send a command and get a response."""
|
||||
return {}
|
||||
|
||||
client.async_send_command.side_effect = async_send_command_no_driver_ready
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.api.HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT",
|
||||
new=0,
|
||||
):
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
TYPE: "zwave_js/hard_reset_controller",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
}
|
||||
)
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
device = device_registry.async_get_device(
|
||||
identifiers={get_device_id(client.driver, client.driver.controller.nodes[1])}
|
||||
)
|
||||
assert device is not None
|
||||
assert msg["result"] == device.id
|
||||
assert msg["success"]
|
||||
|
||||
assert client.async_send_command.call_count == 3
|
||||
# The first call is the relevant hard reset command.
|
||||
# 25 is the require_schema parameter.
|
||||
assert client.async_send_command.call_args_list[0] == call(
|
||||
{"command": "driver.hard_reset"}, 25
|
||||
)
|
||||
|
||||
# Test FailedZWaveCommand is caught
|
||||
with patch(
|
||||
"zwave_js_server.model.driver.Driver.async_hard_reset",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
ID: 2,
|
||||
TYPE: "zwave_js/hard_reset_controller",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
}
|
||||
@@ -5139,9 +5183,8 @@ async def test_hard_reset_controller(
|
||||
await hass.config_entries.async_unload(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
ID: 3,
|
||||
TYPE: "zwave_js/hard_reset_controller",
|
||||
ENTRY_ID: entry.entry_id,
|
||||
}
|
||||
@@ -5151,9 +5194,8 @@ async def test_hard_reset_controller(
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == ERR_NOT_LOADED
|
||||
|
||||
await ws_client.send_json(
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
ID: 4,
|
||||
TYPE: "zwave_js/hard_reset_controller",
|
||||
ENTRY_ID: "INVALID",
|
||||
}
|
||||
@@ -5476,10 +5518,98 @@ async def test_restore_nvm(
|
||||
# Set up mocks for the controller events
|
||||
controller = client.driver.controller
|
||||
|
||||
# Test restore success
|
||||
with patch.object(
|
||||
controller, "async_restore_nvm_base64", return_value=None
|
||||
) as mock_restore:
|
||||
async def async_send_command_driver_ready(
|
||||
message: dict[str, Any],
|
||||
require_schema: int | None = None,
|
||||
) -> dict:
|
||||
"""Send a command and get a response."""
|
||||
client.driver.emit(
|
||||
"driver ready", {"event": "driver ready", "source": "driver"}
|
||||
)
|
||||
return {}
|
||||
|
||||
client.async_send_command.side_effect = async_send_command_driver_ready
|
||||
|
||||
# Send the subscription request
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
"type": "zwave_js/restore_nvm",
|
||||
"entry_id": integration.entry_id,
|
||||
"data": "dGVzdA==", # base64 encoded "test"
|
||||
}
|
||||
)
|
||||
|
||||
# Verify the finished event first
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["event"] == "finished"
|
||||
|
||||
# Verify subscription success
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["type"] == "result"
|
||||
assert msg["success"] is True
|
||||
|
||||
# Simulate progress events
|
||||
event = Event(
|
||||
"nvm restore progress",
|
||||
{
|
||||
"source": "controller",
|
||||
"event": "nvm restore progress",
|
||||
"bytesWritten": 25,
|
||||
"total": 100,
|
||||
},
|
||||
)
|
||||
controller.receive_event(event)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["event"]["event"] == "nvm restore progress"
|
||||
assert msg["event"]["bytesWritten"] == 25
|
||||
assert msg["event"]["total"] == 100
|
||||
|
||||
event = Event(
|
||||
"nvm restore progress",
|
||||
{
|
||||
"source": "controller",
|
||||
"event": "nvm restore progress",
|
||||
"bytesWritten": 50,
|
||||
"total": 100,
|
||||
},
|
||||
)
|
||||
controller.receive_event(event)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["event"]["event"] == "nvm restore progress"
|
||||
assert msg["event"]["bytesWritten"] == 50
|
||||
assert msg["event"]["total"] == 100
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify the restore was called
|
||||
# The first call is the relevant one for nvm restore.
|
||||
assert client.async_send_command.call_count == 3
|
||||
assert client.async_send_command.call_args_list[0] == call(
|
||||
{
|
||||
"command": "controller.restore_nvm",
|
||||
"nvmData": "dGVzdA==",
|
||||
},
|
||||
require_schema=14,
|
||||
)
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test sending command with driver not ready and timeout.
|
||||
|
||||
async def async_send_command_no_driver_ready(
|
||||
message: dict[str, Any],
|
||||
require_schema: int | None = None,
|
||||
) -> dict:
|
||||
"""Send a command and get a response."""
|
||||
return {}
|
||||
|
||||
client.async_send_command.side_effect = async_send_command_no_driver_ready
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.api.RESTORE_NVM_DRIVER_READY_TIMEOUT",
|
||||
new=0,
|
||||
):
|
||||
# Send the subscription request
|
||||
await ws_client.send_json_auto_id(
|
||||
{
|
||||
@@ -5491,6 +5621,7 @@ async def test_restore_nvm(
|
||||
|
||||
# Verify the finished event first
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["event"] == "finished"
|
||||
|
||||
@@ -5499,48 +5630,25 @@ async def test_restore_nvm(
|
||||
assert msg["type"] == "result"
|
||||
assert msg["success"] is True
|
||||
|
||||
# Simulate progress events
|
||||
event = Event(
|
||||
"nvm restore progress",
|
||||
{
|
||||
"source": "controller",
|
||||
"event": "nvm restore progress",
|
||||
"bytesWritten": 25,
|
||||
"total": 100,
|
||||
},
|
||||
)
|
||||
controller.receive_event(event)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["event"]["event"] == "nvm restore progress"
|
||||
assert msg["event"]["bytesWritten"] == 25
|
||||
assert msg["event"]["total"] == 100
|
||||
|
||||
event = Event(
|
||||
"nvm restore progress",
|
||||
{
|
||||
"source": "controller",
|
||||
"event": "nvm restore progress",
|
||||
"bytesWritten": 50,
|
||||
"total": 100,
|
||||
},
|
||||
)
|
||||
controller.receive_event(event)
|
||||
msg = await ws_client.receive_json()
|
||||
assert msg["event"]["event"] == "nvm restore progress"
|
||||
assert msg["event"]["bytesWritten"] == 50
|
||||
assert msg["event"]["total"] == 100
|
||||
|
||||
# Wait for the restore to complete
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Verify the restore was called
|
||||
assert mock_restore.called
|
||||
# Verify the restore was called
|
||||
# The first call is the relevant one for nvm restore.
|
||||
assert client.async_send_command.call_count == 3
|
||||
assert client.async_send_command.call_args_list[0] == call(
|
||||
{
|
||||
"command": "controller.restore_nvm",
|
||||
"nvmData": "dGVzdA==",
|
||||
},
|
||||
require_schema=14,
|
||||
)
|
||||
|
||||
client.async_send_command.reset_mock()
|
||||
|
||||
# Test restore failure
|
||||
with patch.object(
|
||||
controller,
|
||||
"async_restore_nvm_base64",
|
||||
side_effect=FailedCommand("failed_command", "Restore failed"),
|
||||
with patch(
|
||||
f"{CONTROLLER_PATCH_PREFIX}.async_restore_nvm_base64",
|
||||
side_effect=FailedZWaveCommand("failed_command", 1, "error message"),
|
||||
):
|
||||
# Send the subscription request
|
||||
await ws_client.send_json_auto_id(
|
||||
@@ -5554,7 +5662,7 @@ async def test_restore_nvm(
|
||||
# Verify error response
|
||||
msg = await ws_client.receive_json()
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == "Restore failed"
|
||||
assert msg["error"]["code"] == "zwave_error"
|
||||
|
||||
# Test entry_id not found
|
||||
await ws_client.send_json_auto_id(
|
||||
|
||||
@@ -1109,10 +1109,10 @@ async def test_usb_discovery_migration_driver_ready_timeout(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "instruct_unplug"
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "start_addon"
|
||||
assert set_addon_options.call_args == call(
|
||||
@@ -3776,6 +3776,7 @@ async def test_reconfigure_migrate_with_addon(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "instruct_unplug"
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
@@ -3790,7 +3791,6 @@ async def test_reconfigure_migrate_with_addon(
|
||||
},
|
||||
)
|
||||
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "start_addon"
|
||||
assert set_addon_options.call_args == call(
|
||||
@@ -3918,6 +3918,7 @@ async def test_reconfigure_migrate_driver_ready_timeout(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "instruct_unplug"
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
@@ -3932,7 +3933,6 @@ async def test_reconfigure_migrate_driver_ready_timeout(
|
||||
},
|
||||
)
|
||||
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
assert result["type"] == FlowResultType.SHOW_PROGRESS
|
||||
assert result["step_id"] == "start_addon"
|
||||
assert set_addon_options.call_args == call(
|
||||
@@ -4108,6 +4108,7 @@ async def test_reconfigure_migrate_start_addon_failure(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "instruct_unplug"
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
@@ -4202,6 +4203,7 @@ async def test_reconfigure_migrate_restore_failure(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "instruct_unplug"
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
@@ -4367,6 +4369,7 @@ async def test_choose_serial_port_usb_ports_failure(
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "instruct_unplug"
|
||||
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.zwave_js.config_flow.async_get_usb_ports",
|
||||
|
||||
@@ -431,10 +431,11 @@ async def test_rediscovery(
|
||||
|
||||
async def test_aeotec_smart_switch_7(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
aeotec_smart_switch_7: Node,
|
||||
integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that Smart Switch 7 has a light and a switch entity."""
|
||||
"""Test Smart Switch 7 discovery."""
|
||||
state = hass.states.get("light.smart_switch_7")
|
||||
assert state
|
||||
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [
|
||||
@@ -443,3 +444,9 @@ async def test_aeotec_smart_switch_7(
|
||||
|
||||
state = hass.states.get("switch.smart_switch_7")
|
||||
assert state
|
||||
|
||||
state = hass.states.get("button.smart_switch_7_reset_accumulated_values")
|
||||
assert state
|
||||
entity_entry = entity_registry.async_get(state.entity_id)
|
||||
assert entity_entry
|
||||
assert entity_entry.entity_category is EntityCategory.CONFIG
|
||||
|
||||
Reference in New Issue
Block a user