mirror of
https://github.com/home-assistant/core.git
synced 2026-02-28 04:51:41 +01:00
Compare commits
18 Commits
github_mer
...
remove-vol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15094c0693 | ||
|
|
7ef6c34149 | ||
|
|
5b32e42b8c | ||
|
|
1be8b8e525 | ||
|
|
3fae15c430 | ||
|
|
c7e78568d0 | ||
|
|
492b542136 | ||
|
|
0f4852d8c2 | ||
|
|
737c0c1823 | ||
|
|
5fadcb01e9 | ||
|
|
2b4f46a739 | ||
|
|
44fe37da1f | ||
|
|
abd4e89577 | ||
|
|
033798835a | ||
|
|
83c77957c1 | ||
|
|
b1bc1dc102 | ||
|
|
40b8a2c380 | ||
|
|
fb23a6fbf8 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -605,7 +605,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Dependency review
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
|
||||
with:
|
||||
license-check: false # We use our own license audit checks
|
||||
|
||||
|
||||
2
CODEOWNERS
generated
2
CODEOWNERS
generated
@@ -401,8 +401,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
|
||||
/homeassistant/components/duckdns/ @tr4nt0r
|
||||
/tests/components/duckdns/ @tr4nt0r
|
||||
/homeassistant/components/duke_energy/ @hunterjm
|
||||
/tests/components/duke_energy/ @hunterjm
|
||||
/homeassistant/components/duotecno/ @cereal2nd
|
||||
/tests/components/duotecno/ @cereal2nd
|
||||
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192
|
||||
|
||||
@@ -807,6 +807,7 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
|
||||
# The lovelace app loops media to prevent timing out, don't show that
|
||||
if self.app_id == CAST_APP_ID_HOMEASSISTANT_LOVELACE:
|
||||
return MediaPlayerState.PLAYING
|
||||
|
||||
if (media_status := self._media_status()[0]) is not None:
|
||||
if media_status.player_state == MEDIA_PLAYER_STATE_PLAYING:
|
||||
return MediaPlayerState.PLAYING
|
||||
@@ -817,19 +818,19 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
|
||||
if media_status.player_is_idle:
|
||||
return MediaPlayerState.IDLE
|
||||
|
||||
if self._chromecast is not None and self._chromecast.is_idle:
|
||||
# If library consider us idle, that is our off state
|
||||
# it takes HDMI status into account for cast devices.
|
||||
return MediaPlayerState.OFF
|
||||
|
||||
if self.app_id in APP_IDS_UNRELIABLE_MEDIA_INFO:
|
||||
# Some apps don't report media status, show the player as playing
|
||||
return MediaPlayerState.PLAYING
|
||||
|
||||
if self.app_id is not None:
|
||||
if self.app_id is not None and self.app_id != pychromecast.config.APP_BACKDROP:
|
||||
# We have an active app
|
||||
return MediaPlayerState.IDLE
|
||||
|
||||
if self._chromecast is not None and self._chromecast.is_idle:
|
||||
# If library consider us idle, that is our off state
|
||||
# it takes HDMI status into account for cast devices.
|
||||
return MediaPlayerState.OFF
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
"""The Duke Energy integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .coordinator import DukeEnergyConfigEntry, DukeEnergyCoordinator
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: DukeEnergyConfigEntry) -> bool:
|
||||
"""Set up Duke Energy from a config entry."""
|
||||
|
||||
coordinator = DukeEnergyCoordinator(hass, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: DukeEnergyConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return True
|
||||
@@ -1,67 +0,0 @@
|
||||
"""Config flow for Duke Energy integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiodukeenergy import DukeEnergy
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class DukeEnergyConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Duke Energy."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
session = async_get_clientsession(self.hass)
|
||||
api = DukeEnergy(
|
||||
user_input[CONF_USERNAME], user_input[CONF_PASSWORD], session
|
||||
)
|
||||
try:
|
||||
auth = await api.authenticate()
|
||||
except ClientResponseError as e:
|
||||
errors["base"] = "invalid_auth" if e.status == 404 else "cannot_connect"
|
||||
except ClientError, TimeoutError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception:
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
username = auth["internalUserID"].lower()
|
||||
await self.async_set_unique_id(username)
|
||||
self._abort_if_unique_id_configured()
|
||||
email = auth["loginEmailAddress"].lower()
|
||||
data = {
|
||||
CONF_EMAIL: email,
|
||||
CONF_USERNAME: username,
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
}
|
||||
self._async_abort_entries_match(data)
|
||||
return self.async_create_entry(title=email, data=data)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
"""Constants for the Duke Energy integration."""
|
||||
|
||||
DOMAIN = "duke_energy"
|
||||
@@ -1,222 +0,0 @@
|
||||
"""Coordinator to handle Duke Energy connections."""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
from aiodukeenergy import DukeEnergy
|
||||
from aiohttp import ClientError
|
||||
|
||||
from homeassistant.components.recorder import get_instance
|
||||
from homeassistant.components.recorder.models import (
|
||||
StatisticData,
|
||||
StatisticMeanType,
|
||||
StatisticMetaData,
|
||||
)
|
||||
from homeassistant.components.recorder.statistics import (
|
||||
async_add_external_statistics,
|
||||
get_last_statistics,
|
||||
statistics_during_period,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, UnitOfEnergy, UnitOfVolume
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.unit_conversion import EnergyConverter
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
_SUPPORTED_METER_TYPES = ("ELECTRIC",)
|
||||
|
||||
type DukeEnergyConfigEntry = ConfigEntry[DukeEnergyCoordinator]
|
||||
|
||||
|
||||
class DukeEnergyCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Handle inserting statistics."""
|
||||
|
||||
config_entry: DukeEnergyConfigEntry
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: DukeEnergyConfigEntry
|
||||
) -> None:
|
||||
"""Initialize the data handler."""
|
||||
super().__init__(
|
||||
hass,
|
||||
_LOGGER,
|
||||
config_entry=config_entry,
|
||||
name="Duke Energy",
|
||||
# Data is updated daily on Duke Energy.
|
||||
# Refresh every 12h to be at most 12h behind.
|
||||
update_interval=timedelta(hours=12),
|
||||
)
|
||||
self.api = DukeEnergy(
|
||||
config_entry.data[CONF_USERNAME],
|
||||
config_entry.data[CONF_PASSWORD],
|
||||
async_get_clientsession(hass),
|
||||
)
|
||||
self._statistic_ids: set = set()
|
||||
|
||||
@callback
|
||||
def _dummy_listener() -> None:
|
||||
pass
|
||||
|
||||
# Force the coordinator to periodically update by registering at least one listener.
|
||||
# Duke Energy does not provide forecast data, so all information is historical.
|
||||
# This makes _async_update_data get periodically called so we can insert statistics.
|
||||
self.async_add_listener(_dummy_listener)
|
||||
|
||||
self.config_entry.async_on_unload(self._clear_statistics)
|
||||
|
||||
def _clear_statistics(self) -> None:
|
||||
"""Clear statistics."""
|
||||
get_instance(self.hass).async_clear_statistics(list(self._statistic_ids))
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Insert Duke Energy statistics."""
|
||||
meters: dict[str, dict[str, Any]] = await self.api.get_meters()
|
||||
for serial_number, meter in meters.items():
|
||||
if (
|
||||
not isinstance(meter["serviceType"], str)
|
||||
or meter["serviceType"] not in _SUPPORTED_METER_TYPES
|
||||
):
|
||||
_LOGGER.debug(
|
||||
"Skipping unsupported meter type %s", meter["serviceType"]
|
||||
)
|
||||
continue
|
||||
|
||||
id_prefix = f"{meter['serviceType'].lower()}_{serial_number}"
|
||||
consumption_statistic_id = f"{DOMAIN}:{id_prefix}_energy_consumption"
|
||||
self._statistic_ids.add(consumption_statistic_id)
|
||||
_LOGGER.debug(
|
||||
"Updating Statistics for %s",
|
||||
consumption_statistic_id,
|
||||
)
|
||||
|
||||
last_stat = await get_instance(self.hass).async_add_executor_job(
|
||||
get_last_statistics, self.hass, 1, consumption_statistic_id, True, set()
|
||||
)
|
||||
if not last_stat:
|
||||
_LOGGER.debug("Updating statistic for the first time")
|
||||
usage = await self._async_get_energy_usage(meter)
|
||||
consumption_sum = 0.0
|
||||
last_stats_time = None
|
||||
else:
|
||||
usage = await self._async_get_energy_usage(
|
||||
meter,
|
||||
last_stat[consumption_statistic_id][0]["start"],
|
||||
)
|
||||
if not usage:
|
||||
_LOGGER.debug("No recent usage data. Skipping update")
|
||||
continue
|
||||
stats = await get_instance(self.hass).async_add_executor_job(
|
||||
statistics_during_period,
|
||||
self.hass,
|
||||
min(usage.keys()),
|
||||
None,
|
||||
{consumption_statistic_id},
|
||||
"hour",
|
||||
None,
|
||||
{"sum"},
|
||||
)
|
||||
consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"])
|
||||
last_stats_time = stats[consumption_statistic_id][0]["start"]
|
||||
|
||||
consumption_statistics = []
|
||||
|
||||
for start, data in usage.items():
|
||||
if last_stats_time is not None and start.timestamp() <= last_stats_time:
|
||||
continue
|
||||
consumption_sum += data["energy"]
|
||||
|
||||
consumption_statistics.append(
|
||||
StatisticData(
|
||||
start=start, state=data["energy"], sum=consumption_sum
|
||||
)
|
||||
)
|
||||
|
||||
name_prefix = (
|
||||
f"Duke Energy {meter['serviceType'].capitalize()} {serial_number}"
|
||||
)
|
||||
consumption_metadata = StatisticMetaData(
|
||||
mean_type=StatisticMeanType.NONE,
|
||||
has_sum=True,
|
||||
name=f"{name_prefix} Consumption",
|
||||
source=DOMAIN,
|
||||
statistic_id=consumption_statistic_id,
|
||||
unit_class=EnergyConverter.UNIT_CLASS,
|
||||
unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR
|
||||
if meter["serviceType"] == "ELECTRIC"
|
||||
else UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||
)
|
||||
|
||||
_LOGGER.debug(
|
||||
"Adding %s statistics for %s",
|
||||
len(consumption_statistics),
|
||||
consumption_statistic_id,
|
||||
)
|
||||
async_add_external_statistics(
|
||||
self.hass, consumption_metadata, consumption_statistics
|
||||
)
|
||||
|
||||
async def _async_get_energy_usage(
|
||||
self, meter: dict[str, Any], start_time: float | None = None
|
||||
) -> dict[datetime, dict[str, float | int]]:
|
||||
"""Get energy usage.
|
||||
|
||||
If start_time is None, get usage since account activation (or as far back as possible),
|
||||
otherwise since start_time - 30 days to allow corrections in data.
|
||||
|
||||
Duke Energy provides hourly data all the way back to ~3 years.
|
||||
"""
|
||||
|
||||
# All of Duke Energy Service Areas are currently in America/New_York timezone
|
||||
# May need to re-think this if that ever changes and determine timezone based
|
||||
# on the service address somehow.
|
||||
tz = await dt_util.async_get_time_zone("America/New_York")
|
||||
lookback = timedelta(days=30)
|
||||
one = timedelta(days=1)
|
||||
if start_time is None:
|
||||
# Max 3 years of data
|
||||
start = dt_util.now(tz) - timedelta(days=3 * 365)
|
||||
else:
|
||||
start = datetime.fromtimestamp(start_time, tz=tz) - lookback
|
||||
agreement_date = dt_util.parse_datetime(meter["agreementActiveDate"])
|
||||
if agreement_date is not None:
|
||||
start = max(agreement_date.replace(tzinfo=tz), start)
|
||||
|
||||
start = start.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end = dt_util.now(tz).replace(hour=0, minute=0, second=0, microsecond=0) - one
|
||||
_LOGGER.debug("Data lookup range: %s - %s", start, end)
|
||||
|
||||
start_step = max(end - lookback, start)
|
||||
end_step = end
|
||||
usage: dict[datetime, dict[str, float | int]] = {}
|
||||
while True:
|
||||
_LOGGER.debug("Getting hourly usage: %s - %s", start_step, end_step)
|
||||
try:
|
||||
# Get data
|
||||
results = await self.api.get_energy_usage(
|
||||
meter["serialNum"], "HOURLY", "DAY", start_step, end_step
|
||||
)
|
||||
usage = {**results["data"], **usage}
|
||||
|
||||
for missing in results["missing"]:
|
||||
_LOGGER.debug("Missing data: %s", missing)
|
||||
|
||||
# Set next range
|
||||
end_step = start_step - one
|
||||
start_step = max(start_step - lookback, start)
|
||||
|
||||
# Make sure we don't go back too far
|
||||
if end_step < start:
|
||||
break
|
||||
except TimeoutError, ClientError:
|
||||
# ClientError is raised when there is no more data for the range
|
||||
break
|
||||
|
||||
_LOGGER.debug("Got %s meter usage reads", len(usage))
|
||||
return usage
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"domain": "duke_energy",
|
||||
"name": "Duke Energy",
|
||||
"codeowners": ["@hunterjm"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["recorder"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/duke_energy",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["aiodukeenergy==0.3.0"]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,12 +78,6 @@ query ($owner: String!, $repository: String!) {
|
||||
number
|
||||
}
|
||||
}
|
||||
merged_pull_request: pullRequests(
|
||||
first:1
|
||||
states: MERGED
|
||||
) {
|
||||
total: totalCount
|
||||
}
|
||||
release: latestRelease {
|
||||
name
|
||||
url
|
||||
|
||||
@@ -28,9 +28,6 @@
|
||||
"latest_tag": {
|
||||
"default": "mdi:tag"
|
||||
},
|
||||
"merged_pulls_count": {
|
||||
"default": "mdi:source-merge"
|
||||
},
|
||||
"pulls_count": {
|
||||
"default": "mdi:source-pull"
|
||||
},
|
||||
|
||||
@@ -75,13 +75,6 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = (
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data["pull_request"]["total"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="merged_pulls_count",
|
||||
translation_key="merged_pulls_count",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda data: data["merged_pull_request"]["total"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="latest_commit",
|
||||
translation_key="latest_commit",
|
||||
|
||||
@@ -48,10 +48,6 @@
|
||||
"latest_tag": {
|
||||
"name": "Latest tag"
|
||||
},
|
||||
"merged_pulls_count": {
|
||||
"name": "Merged pull requests",
|
||||
"unit_of_measurement": "pull requests"
|
||||
},
|
||||
"pulls_count": {
|
||||
"name": "Pull requests",
|
||||
"unit_of_measurement": "pull requests"
|
||||
|
||||
@@ -423,20 +423,6 @@ class MediaPlayerGroup(MediaPlayerEntity):
|
||||
context=self._context,
|
||||
)
|
||||
|
||||
async def async_volume_up(self) -> None:
|
||||
"""Turn volume up for media player(s)."""
|
||||
for entity in self._features[KEY_VOLUME]:
|
||||
volume_level = self.hass.states.get(entity).attributes["volume_level"] # type: ignore[union-attr]
|
||||
if volume_level < 1:
|
||||
await self.async_set_volume_level(min(1, volume_level + 0.1))
|
||||
|
||||
async def async_volume_down(self) -> None:
|
||||
"""Turn volume down for media player(s)."""
|
||||
for entity in self._features[KEY_VOLUME]:
|
||||
volume_level = self.hass.states.get(entity).attributes["volume_level"] # type: ignore[union-attr]
|
||||
if volume_level > 0:
|
||||
await self.async_set_volume_level(max(0, volume_level - 0.1))
|
||||
|
||||
@callback
|
||||
def async_update_group_state(self) -> None:
|
||||
"""Query all members and determine the media group state."""
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"audio_unmute": {
|
||||
"default": "mdi:volume-high"
|
||||
},
|
||||
"earc_unmute": {
|
||||
"default": "mdi:volume-high"
|
||||
},
|
||||
"oled_fade": {
|
||||
"default": "mdi:cellphone-information"
|
||||
},
|
||||
|
||||
@@ -31,6 +31,32 @@ class HDFuryNumberEntityDescription(NumberEntityDescription):
|
||||
|
||||
|
||||
NUMBERS: tuple[HDFuryNumberEntityDescription, ...] = (
|
||||
HDFuryNumberEntityDescription(
|
||||
key="unmutecnt",
|
||||
translation_key="audio_unmute",
|
||||
entity_registry_enabled_default=False,
|
||||
mode=NumberMode.BOX,
|
||||
native_min_value=50,
|
||||
native_max_value=1000,
|
||||
native_step=1,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
set_value_fn=lambda client, value: client.set_audio_unmute(value),
|
||||
),
|
||||
HDFuryNumberEntityDescription(
|
||||
key="earcunmutecnt",
|
||||
translation_key="earc_unmute",
|
||||
entity_registry_enabled_default=False,
|
||||
mode=NumberMode.BOX,
|
||||
native_min_value=0,
|
||||
native_max_value=1000,
|
||||
native_step=1,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.MILLISECONDS,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
set_value_fn=lambda client, value: client.set_earc_unmute(value),
|
||||
),
|
||||
HDFuryNumberEntityDescription(
|
||||
key="oledfade",
|
||||
translation_key="oled_fade",
|
||||
|
||||
@@ -41,6 +41,12 @@
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"audio_unmute": {
|
||||
"name": "Unmute delay"
|
||||
},
|
||||
"earc_unmute": {
|
||||
"name": "eARC unmute delay"
|
||||
},
|
||||
"oled_fade": {
|
||||
"name": "OLED fade timer"
|
||||
},
|
||||
|
||||
@@ -168,8 +168,9 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
|
||||
segments: dict[str, Segment] = {}
|
||||
for area in supported_areas:
|
||||
area_name = None
|
||||
if area.areaInfo and area.areaInfo.locationInfo:
|
||||
area_name = area.areaInfo.locationInfo.locationName
|
||||
location_info = area.areaInfo.locationInfo
|
||||
if location_info not in (None, clusters.NullValue):
|
||||
area_name = location_info.locationName
|
||||
|
||||
if area_name:
|
||||
segment_id = str(area.areaID)
|
||||
|
||||
@@ -124,6 +124,17 @@ class SFTPFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
}
|
||||
)
|
||||
|
||||
if not user_input[CONF_BACKUP_LOCATION].startswith("/"):
|
||||
errors[CONF_BACKUP_LOCATION] = "backup_location_relative"
|
||||
return self.async_show_form(
|
||||
step_id=step_id,
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
DATA_SCHEMA, user_input
|
||||
),
|
||||
description_placeholders=placeholders,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
try:
|
||||
# Validate auth input and save uploaded key file if provided
|
||||
user_input = await self._validate_auth_and_save_keyfile(user_input)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"already_configured": "Integration already configured. Host with same address, port and backup location already exists."
|
||||
},
|
||||
"error": {
|
||||
"backup_location_relative": "The remote path must be an absolute path (starting with `/`).",
|
||||
"invalid_key": "Invalid key uploaded. Please make sure key corresponds to valid SSH key algorithm.",
|
||||
"key_or_password_needed": "Please configure password or private key file location for SFTP Storage.",
|
||||
"os_error": "{error_message}. Please check if host and/or port are correct.",
|
||||
|
||||
@@ -257,6 +257,9 @@ class AbstractTemplateSensor(AbstractTemplateEntity, RestoreSensor):
|
||||
) -> StateType | date | datetime | Decimal | None:
|
||||
"""Validate the state."""
|
||||
if self._numeric_state_expected:
|
||||
if not isinstance(result, bool) and isinstance(result, (int, float)):
|
||||
return result
|
||||
|
||||
return template_validators.number(self, CONF_STATE)(result)
|
||||
|
||||
if result is None or self.device_class not in (
|
||||
|
||||
@@ -274,6 +274,9 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Unload ZHA config entry."""
|
||||
if not await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS):
|
||||
return False
|
||||
|
||||
ha_zha_data = get_zha_data(hass)
|
||||
ha_zha_data.config_entry = None
|
||||
|
||||
@@ -281,6 +284,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
await ha_zha_data.gateway_proxy.shutdown()
|
||||
ha_zha_data.gateway_proxy = None
|
||||
|
||||
ha_zha_data.update_coordinator = None
|
||||
|
||||
# clean up any remaining entity metadata
|
||||
# (entities that have been discovered but not yet added to HA)
|
||||
# suppress KeyError because we don't know what state we may
|
||||
@@ -291,7 +296,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
|
||||
websocket_api.async_unload_api(hass)
|
||||
|
||||
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
|
||||
return True
|
||||
|
||||
|
||||
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
|
||||
1
homeassistant/generated/config_flows.py
generated
1
homeassistant/generated/config_flows.py
generated
@@ -161,7 +161,6 @@ FLOWS = {
|
||||
"dsmr",
|
||||
"dsmr_reader",
|
||||
"duckdns",
|
||||
"duke_energy",
|
||||
"dunehd",
|
||||
"duotecno",
|
||||
"dwd_weather_warnings",
|
||||
|
||||
@@ -1497,12 +1497,6 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"duke_energy": {
|
||||
"name": "Duke Energy",
|
||||
"integration_type": "service",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"dunehd": {
|
||||
"name": "Dune HD",
|
||||
"integration_type": "device",
|
||||
|
||||
3
requirements_all.txt
generated
3
requirements_all.txt
generated
@@ -235,9 +235,6 @@ aiodiscover==2.7.1
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==4.0.0
|
||||
|
||||
# homeassistant.components.duke_energy
|
||||
aiodukeenergy==0.3.0
|
||||
|
||||
# homeassistant.components.eafm
|
||||
aioeafm==0.1.2
|
||||
|
||||
|
||||
3
requirements_test_all.txt
generated
3
requirements_test_all.txt
generated
@@ -226,9 +226,6 @@ aiodiscover==2.7.1
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==4.0.0
|
||||
|
||||
# homeassistant.components.duke_energy
|
||||
aiodukeenergy==0.3.0
|
||||
|
||||
# homeassistant.components.eafm
|
||||
aioeafm==0.1.2
|
||||
|
||||
|
||||
@@ -295,7 +295,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"dsmr",
|
||||
"dsmr_reader",
|
||||
"dublin_bus_transport",
|
||||
"duke_energy",
|
||||
"dunehd",
|
||||
"duotecno",
|
||||
"dwd_weather_warnings",
|
||||
@@ -1273,7 +1272,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"dsmr",
|
||||
"dsmr_reader",
|
||||
"dublin_bus_transport",
|
||||
"duke_energy",
|
||||
"dunehd",
|
||||
"duotecno",
|
||||
"dwd_weather_warnings",
|
||||
|
||||
@@ -1,23 +1 @@
|
||||
"""Tests for the AdGuard Home integration."""
|
||||
|
||||
from collections.abc import AsyncGenerator
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
adguard_mock: AsyncGenerator,
|
||||
) -> None:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.adguard.AdGuardHome",
|
||||
return_value=adguard_mock,
|
||||
):
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Common fixtures for the adguard tests."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from adguardhome import AdGuardHome
|
||||
from adguardhome.filtering import AdGuardHomeFiltering
|
||||
@@ -12,7 +13,7 @@ from adguardhome.stats import AdGuardHomeStats
|
||||
from adguardhome.update import AdGuardHomeAvailableUpdate, AdGuardHomeUpdate
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.adguard import DOMAIN
|
||||
from homeassistant.components.adguard import DOMAIN, PLATFORMS
|
||||
from homeassistant.const import (
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
@@ -20,7 +21,9 @@ from homeassistant.const import (
|
||||
CONF_SSL,
|
||||
CONF_USERNAME,
|
||||
CONF_VERIFY_SSL,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@@ -43,8 +46,8 @@ def mock_config_entry() -> MockConfigEntry:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_adguard() -> AsyncMock:
|
||||
"""Fixture for setting up the component."""
|
||||
def mock_adguard() -> Generator[AsyncMock]:
|
||||
"""Return a mocked AdGuard Home client."""
|
||||
adguard_mock = AsyncMock(spec=AdGuardHome)
|
||||
adguard_mock.filtering = AsyncMock(spec=AdGuardHomeFiltering)
|
||||
adguard_mock.parental = AsyncMock(spec=AdGuardHomeParental)
|
||||
@@ -86,4 +89,31 @@ async def mock_adguard() -> AsyncMock:
|
||||
)
|
||||
)
|
||||
|
||||
return adguard_mock
|
||||
with patch(
|
||||
"homeassistant.components.adguard.AdGuardHome",
|
||||
return_value=adguard_mock,
|
||||
):
|
||||
yield adguard_mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return PLATFORMS
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_adguard: AsyncMock,
|
||||
platforms: list[Platform],
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the AdGuard Home integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", platforms):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_config_entry
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
"""Tests for the AdGuard Home."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from adguardhome import AdGuardHomeConnectionError
|
||||
import pytest
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return []
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_setup(
|
||||
hass: HomeAssistant,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the adguard setup."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
@@ -31,5 +34,8 @@ async def test_setup_failed(
|
||||
"""Test the adguard setup failed."""
|
||||
mock_adguard.version.side_effect = AdGuardHomeConnectionError("Connection error")
|
||||
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Tests for the AdGuard Home sensor entities."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
@@ -9,21 +7,21 @@ from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.SENSOR]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration")
|
||||
async def test_sensors(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the adguard sensor platform."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -14,22 +14,22 @@ from homeassistant.components.adguard.const import (
|
||||
SERVICE_REFRESH,
|
||||
SERVICE_REMOVE_URL,
|
||||
)
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return []
|
||||
|
||||
|
||||
async def test_service_registration(
|
||||
hass: HomeAssistant,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the adguard services be registered."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
services = hass.services.async_services_for_domain(DOMAIN)
|
||||
|
||||
assert len(services) == 5
|
||||
@@ -73,15 +73,11 @@ async def test_service_registration(
|
||||
async def test_service(
|
||||
hass: HomeAssistant,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
service: str,
|
||||
service_call_data: dict,
|
||||
call_assertion: Callable[[AsyncMock], Any],
|
||||
) -> None:
|
||||
"""Test the adguard services be unregistered with unloading last entry."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", []):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
service,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from collections.abc import Callable
|
||||
import logging
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from adguardhome import AdGuardHomeError
|
||||
import pytest
|
||||
@@ -14,23 +14,26 @@ from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.SWITCH]
|
||||
|
||||
|
||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_switch(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the adguard switch platform."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.SWITCH]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@@ -103,15 +106,11 @@ async def test_switch(
|
||||
async def test_switch_actions(
|
||||
hass: HomeAssistant,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
switch_name: str,
|
||||
service: str,
|
||||
call_assertion: Callable[[AsyncMock], Any],
|
||||
) -> None:
|
||||
"""Test the adguard switch actions."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.SWITCH]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
service,
|
||||
@@ -138,7 +137,6 @@ async def test_switch_actions(
|
||||
async def test_switch_action_failed(
|
||||
hass: HomeAssistant,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
service: str,
|
||||
expected_message: str,
|
||||
@@ -146,9 +144,6 @@ async def test_switch_action_failed(
|
||||
"""Test the adguard switch actions."""
|
||||
caplog.set_level(logging.ERROR)
|
||||
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.SWITCH]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
mock_adguard.enable_protection.side_effect = AdGuardHomeError("Boom")
|
||||
mock_adguard.disable_protection.side_effect = AdGuardHomeError("Boom")
|
||||
|
||||
|
||||
@@ -12,22 +12,23 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.UPDATE]
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_update(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the adguard update platform."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@@ -41,41 +42,38 @@ async def test_update_disabled(
|
||||
disabled=True,
|
||||
)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert not hass.states.async_all()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_update_install(
|
||||
hass: HomeAssistant,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the adguard update installation."""
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
await hass.services.async_call(
|
||||
"update",
|
||||
"install",
|
||||
{"entity_id": "update.adguard_home"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_adguard.update.begin_update.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_update_install_failed(
|
||||
hass: HomeAssistant,
|
||||
mock_adguard: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the adguard update install failed."""
|
||||
mock_adguard.update.begin_update.side_effect = AdGuardHomeError("boom")
|
||||
|
||||
with patch("homeassistant.components.adguard.PLATFORMS", [Platform.UPDATE]):
|
||||
await setup_integration(hass, mock_config_entry, mock_adguard)
|
||||
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await hass.services.async_call(
|
||||
"update",
|
||||
|
||||
@@ -2385,3 +2385,42 @@ async def test_ha_cast(hass: HomeAssistant, ha_controller_mock) -> None:
|
||||
chromecast.unregister_handler.reset_mock()
|
||||
unregister_cb()
|
||||
chromecast.unregister_handler.assert_not_called()
|
||||
|
||||
|
||||
async def test_entity_media_states_active_app_reported_idle(
|
||||
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
||||
) -> None:
|
||||
"""Test entity state when app is active but device reports idle (fixes #160814)."""
|
||||
entity_id = "media_player.speaker"
|
||||
info = get_fake_chromecast_info()
|
||||
chromecast, _ = await async_setup_media_player_cast(hass, info)
|
||||
cast_status_cb, conn_status_cb, _ = get_status_callbacks(chromecast)
|
||||
|
||||
# Connect the device
|
||||
connection_status = MagicMock()
|
||||
connection_status.status = "CONNECTED"
|
||||
conn_status_cb(connection_status)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Scenario: Custom App is running (e.g. DashCast), but device reports is_idle=True
|
||||
chromecast.app_id = "84912283" # Example Custom App ID
|
||||
chromecast.is_idle = True # Device thinks it's idle/standby
|
||||
|
||||
# Trigger a status update
|
||||
cast_status = MagicMock()
|
||||
cast_status_cb(cast_status)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "idle"
|
||||
|
||||
# Scenario: Backdrop (Screensaver) is running. Should still be OFF.
|
||||
chromecast.app_id = pychromecast.config.APP_BACKDROP
|
||||
chromecast.is_idle = True
|
||||
|
||||
cast_status_cb(cast_status)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "off"
|
||||
|
||||
@@ -30,7 +30,7 @@ def recorder_url_mock():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_bluetooth", "mock_zeroconf")
|
||||
@pytest.mark.usefixtures("mock_bluetooth", "mock_zeroconf", "socket_enabled")
|
||||
async def test_setup(hass: HomeAssistant) -> None:
|
||||
"""Test setup."""
|
||||
recorder_helper.async_initialize_recorder(hass)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""Tests for the Duke Energy integration."""
|
||||
@@ -1,90 +0,0 @@
|
||||
"""Common fixtures for the Duke Energy tests."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.duke_energy.const import DOMAIN
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import RecorderInstanceContextManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_recorder_before_hass(
|
||||
async_test_recorder: RecorderInstanceContextManager,
|
||||
) -> None:
|
||||
"""Set up recorder."""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.duke_energy.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry(hass: HomeAssistant) -> Generator[AsyncMock]:
|
||||
"""Return the default mocked config entry."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_EMAIL: "test@example.com",
|
||||
CONF_USERNAME: "test-username",
|
||||
CONF_PASSWORD: "test-password",
|
||||
},
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
return config_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_api() -> Generator[AsyncMock]:
|
||||
"""Mock a successful Duke Energy API."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.duke_energy.config_flow.DukeEnergy",
|
||||
autospec=True,
|
||||
) as mock_api,
|
||||
patch(
|
||||
"homeassistant.components.duke_energy.coordinator.DukeEnergy",
|
||||
new=mock_api,
|
||||
),
|
||||
):
|
||||
api = mock_api.return_value
|
||||
api.authenticate.return_value = {
|
||||
"loginEmailAddress": "TEST@EXAMPLE.COM",
|
||||
"internalUserID": "test-username",
|
||||
}
|
||||
api.get_meters.return_value = {}
|
||||
yield api
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_api_with_meters(mock_api: AsyncMock) -> AsyncMock:
|
||||
"""Mock a successful Duke Energy API with meters."""
|
||||
mock_api.get_meters.return_value = {
|
||||
"123": {
|
||||
"serialNum": "123",
|
||||
"serviceType": "ELECTRIC",
|
||||
"agreementActiveDate": "2000-01-01",
|
||||
},
|
||||
}
|
||||
mock_api.get_energy_usage.return_value = {
|
||||
"data": {
|
||||
dt_util.now(): {
|
||||
"energy": 1.3,
|
||||
"temperature": 70,
|
||||
}
|
||||
},
|
||||
"missing": [],
|
||||
}
|
||||
return mock_api
|
||||
@@ -1,118 +0,0 @@
|
||||
"""Test the Duke Energy config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.duke_energy.const import DOMAIN
|
||||
from homeassistant.components.recorder import Recorder
|
||||
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
|
||||
async def test_user(
|
||||
hass: HomeAssistant,
|
||||
recorder_mock: Recorder,
|
||||
mock_api: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test user config."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("step_id") == "user"
|
||||
|
||||
# test with all provided
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
|
||||
)
|
||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
||||
assert result.get("title") == "test@example.com"
|
||||
|
||||
data = result.get("data")
|
||||
assert data
|
||||
assert data[CONF_USERNAME] == "test-username"
|
||||
assert data[CONF_PASSWORD] == "test-password"
|
||||
assert data[CONF_EMAIL] == "test@example.com"
|
||||
|
||||
|
||||
async def test_abort_if_already_setup(
|
||||
hass: HomeAssistant,
|
||||
recorder_mock: Recorder,
|
||||
mock_api: AsyncMock,
|
||||
mock_config_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test we abort if the email is already setup."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={
|
||||
CONF_USERNAME: "test-username",
|
||||
CONF_PASSWORD: "test-password",
|
||||
},
|
||||
)
|
||||
assert result
|
||||
assert result.get("type") is FlowResultType.ABORT
|
||||
assert result.get("reason") == "already_configured"
|
||||
|
||||
|
||||
async def test_abort_if_already_setup_alternate_username(
|
||||
hass: HomeAssistant,
|
||||
recorder_mock: Recorder,
|
||||
mock_api: AsyncMock,
|
||||
mock_config_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test we abort if the email is already setup."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={
|
||||
CONF_USERNAME: "test@example.com",
|
||||
CONF_PASSWORD: "test-password",
|
||||
},
|
||||
)
|
||||
assert result
|
||||
assert result.get("type") is FlowResultType.ABORT
|
||||
assert result.get("reason") == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect", "expected_error"),
|
||||
[
|
||||
(ClientResponseError(None, None, status=404), "invalid_auth"),
|
||||
(ClientResponseError(None, None, status=500), "cannot_connect"),
|
||||
(TimeoutError(), "cannot_connect"),
|
||||
(ClientError(), "cannot_connect"),
|
||||
(Exception(), "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_api_errors(
|
||||
hass: HomeAssistant,
|
||||
recorder_mock: Recorder,
|
||||
mock_api: Mock,
|
||||
side_effect,
|
||||
expected_error,
|
||||
) -> None:
|
||||
"""Test the failure scenarios."""
|
||||
mock_api.authenticate.side_effect = side_effect
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
|
||||
)
|
||||
assert result.get("type") is FlowResultType.FORM
|
||||
assert result.get("errors") == {"base": expected_error}
|
||||
|
||||
mock_api.authenticate.side_effect = None
|
||||
|
||||
# test with all provided
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
|
||||
)
|
||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
||||
@@ -1,44 +0,0 @@
|
||||
"""Tests for the SolarEdge coordinator services."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
|
||||
from homeassistant.components.recorder import Recorder
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
async def test_update(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_api_with_meters: Mock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
recorder_mock: Recorder,
|
||||
) -> None:
|
||||
"""Test Coordinator."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
assert mock_api_with_meters.get_meters.call_count == 1
|
||||
# 3 years of data
|
||||
assert mock_api_with_meters.get_energy_usage.call_count == 37
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.duke_energy.coordinator.get_last_statistics",
|
||||
return_value={
|
||||
"duke_energy:electric_123_energy_consumption": [
|
||||
{"start": dt_util.now().timestamp()}
|
||||
]
|
||||
},
|
||||
):
|
||||
freezer.tick(timedelta(hours=12))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert mock_api_with_meters.get_meters.call_count == 2
|
||||
# Now have stats, so only one call
|
||||
assert mock_api_with_meters.get_energy_usage.call_count == 38
|
||||
@@ -19,9 +19,11 @@ from .conftest import MockESPHomeDeviceType
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration", "mock_dashboard")
|
||||
@pytest.mark.usefixtures("mock_dashboard")
|
||||
async def test_dashboard_storage(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
init_integration: MockConfigEntry,
|
||||
hass_storage: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test dashboard storage."""
|
||||
@@ -129,6 +131,7 @@ async def test_setup_dashboard_fails(
|
||||
|
||||
async def test_setup_dashboard_fails_when_already_setup(
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
hass_storage: dict[str, Any],
|
||||
) -> None:
|
||||
@@ -168,7 +171,9 @@ async def test_setup_dashboard_fails_when_already_setup(
|
||||
|
||||
@pytest.mark.usefixtures("mock_dashboard")
|
||||
async def test_new_info_reload_config_entries(
|
||||
hass: HomeAssistant, init_integration: MockConfigEntry
|
||||
hass: HomeAssistant,
|
||||
mock_client: APIClient,
|
||||
init_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test config entries are reloaded when new info is set."""
|
||||
assert init_integration.state is ConfigEntryState.LOADED
|
||||
|
||||
@@ -49,9 +49,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"merged_pull_request": {
|
||||
"total": 42
|
||||
},
|
||||
"release": {
|
||||
"name": "v1.0.0",
|
||||
"url": "https://github.com/octocat/Hello-World/releases/v1.0.0",
|
||||
|
||||
@@ -104,6 +104,8 @@ def mock_hdfury_client() -> Generator[AsyncMock]:
|
||||
"relay": "0",
|
||||
"macaddr": "c7:1c:df:9d:f6:40",
|
||||
"reboottimer": "0",
|
||||
"unmutecnt": "500",
|
||||
"earcunmutecnt": "0",
|
||||
"oled": "1",
|
||||
"oledfade": "30",
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
'cec1en': '1',
|
||||
'cec2en': '1',
|
||||
'cec3en': '1',
|
||||
'earcunmutecnt': '0',
|
||||
'htpcmode0': '0',
|
||||
'htpcmode1': '0',
|
||||
'htpcmode2': '0',
|
||||
@@ -29,6 +30,7 @@
|
||||
'relay': '0',
|
||||
'tx0plus5': '1',
|
||||
'tx1plus5': '1',
|
||||
'unmutecnt': '500',
|
||||
}),
|
||||
'info': dict({
|
||||
'AUD0': 'bitstream 48kHz',
|
||||
|
||||
@@ -1,4 +1,64 @@
|
||||
# serializer version: 1
|
||||
# name: test_number_entities[number.hdfury_vrroom_02_earc_unmute_delay-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 1000,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.hdfury_vrroom_02_earc_unmute_delay',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'eARC unmute delay',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'eARC unmute delay',
|
||||
'platform': 'hdfury',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'earc_unmute',
|
||||
'unique_id': '000123456789_earcunmutecnt',
|
||||
'unit_of_measurement': <UnitOfTime.MILLISECONDS: 'ms'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_number_entities[number.hdfury_vrroom_02_earc_unmute_delay-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
'friendly_name': 'HDFury VRROOM-02 eARC unmute delay',
|
||||
'max': 1000,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfTime.MILLISECONDS: 'ms'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.hdfury_vrroom_02_earc_unmute_delay',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_number_entities[number.hdfury_vrroom_02_oled_fade_timer-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -119,3 +179,63 @@
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_number_entities[number.hdfury_vrroom_02_unmute_delay-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 1000,
|
||||
'min': 50,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.hdfury_vrroom_02_unmute_delay',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Unmute delay',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.DURATION: 'duration'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Unmute delay',
|
||||
'platform': 'hdfury',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'audio_unmute',
|
||||
'unique_id': '000123456789_unmutecnt',
|
||||
'unit_of_measurement': <UnitOfTime.MILLISECONDS: 'ms'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_number_entities[number.hdfury_vrroom_02_unmute_delay-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'duration',
|
||||
'friendly_name': 'HDFury VRROOM-02 Unmute delay',
|
||||
'max': 1000,
|
||||
'min': 50,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfTime.MILLISECONDS: 'ms'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.hdfury_vrroom_02_unmute_delay',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '500.0',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -23,6 +23,7 @@ from . import setup_integration
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_number_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
@@ -40,8 +41,11 @@ async def test_number_entities(
|
||||
[
|
||||
("number.hdfury_vrroom_02_oled_fade_timer", "set_oled_fade"),
|
||||
("number.hdfury_vrroom_02_restart_timer", "set_reboot_timer"),
|
||||
("number.hdfury_vrroom_02_unmute_delay", "set_audio_unmute"),
|
||||
("number.hdfury_vrroom_02_earc_unmute_delay", "set_earc_unmute"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_number_set_value(
|
||||
hass: HomeAssistant,
|
||||
mock_hdfury_client: AsyncMock,
|
||||
@@ -68,8 +72,11 @@ async def test_number_set_value(
|
||||
[
|
||||
("number.hdfury_vrroom_02_oled_fade_timer", "set_oled_fade"),
|
||||
("number.hdfury_vrroom_02_restart_timer", "set_reboot_timer"),
|
||||
("number.hdfury_vrroom_02_unmute_delay", "set_audio_unmute"),
|
||||
("number.hdfury_vrroom_02_earc_unmute_delay", "set_earc_unmute"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_number_error(
|
||||
hass: HomeAssistant,
|
||||
mock_hdfury_client: AsyncMock,
|
||||
@@ -100,8 +107,11 @@ async def test_number_error(
|
||||
[
|
||||
("number.hdfury_vrroom_02_oled_fade_timer"),
|
||||
("number.hdfury_vrroom_02_restart_timer"),
|
||||
("number.hdfury_vrroom_02_unmute_delay"),
|
||||
("number.hdfury_vrroom_02_earc_unmute_delay"),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_number_entities_unavailable_on_error(
|
||||
hass: HomeAssistant,
|
||||
mock_hdfury_client: AsyncMock,
|
||||
|
||||
@@ -131,10 +131,15 @@ def simple_mock_home_fixture():
|
||||
get_current_state_async=AsyncMock(),
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.homematicip_cloud.hap.AsyncHome",
|
||||
autospec=True,
|
||||
return_value=mock_home,
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.homematicip_cloud.hap.AsyncHome",
|
||||
autospec=True,
|
||||
return_value=mock_home,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.homematicip_cloud.hap.ConnectionContextBuilder.build_context_async",
|
||||
),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from homematicip.connection.connection_context import ConnectionContext
|
||||
from homematicip.exceptions.connection_exceptions import HmipConnectionError
|
||||
|
||||
from homeassistant.components.homematicip_cloud.const import (
|
||||
@@ -107,7 +106,6 @@ async def test_load_entry_fails_due_to_connection_error(
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.homematicip_cloud.hap.ConnectionContextBuilder.build_context_async",
|
||||
return_value=ConnectionContext(),
|
||||
),
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
@@ -127,6 +125,9 @@ async def test_load_entry_fails_due_to_generic_exception(
|
||||
"homeassistant.components.homematicip_cloud.hap.AsyncHome.get_current_state_async",
|
||||
side_effect=Exception,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.homematicip_cloud.hap.ConnectionContextBuilder.build_context_async",
|
||||
),
|
||||
):
|
||||
assert await async_setup_component(hass, DOMAIN, {})
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def disable_http_server() -> None:
|
||||
def disable_http_server(socket_enabled: None) -> None:
|
||||
"""Override the global disable_http_server fixture with an empty fixture.
|
||||
|
||||
This allows the HTTP server to start in tests that need it.
|
||||
|
||||
@@ -92,6 +92,7 @@ FIXTURES = [
|
||||
"mock_window_covering_tilt",
|
||||
"onoff_light_with_levelcontrol_present",
|
||||
"resideo_x2s_thermostat",
|
||||
"roborock_saros_10",
|
||||
"secuyou_smart_lock",
|
||||
"silabs_dishwasher",
|
||||
"silabs_evse_charging",
|
||||
|
||||
540
tests/components/matter/fixtures/nodes/roborock_saros_10.json
Normal file
540
tests/components/matter/fixtures/nodes/roborock_saros_10.json
Normal file
@@ -0,0 +1,540 @@
|
||||
{
|
||||
"node_id": 202,
|
||||
"date_commissioned": "2025-01-01T00:00:00",
|
||||
"last_interview": "2026-01-01T00:00:00",
|
||||
"interview_version": 6,
|
||||
"available": true,
|
||||
"is_bridge": false,
|
||||
"attributes": {
|
||||
"0/29/0": [
|
||||
{
|
||||
"0": 22,
|
||||
"1": 1
|
||||
}
|
||||
],
|
||||
"0/29/1": [29, 31, 40, 48, 49, 50, 51, 60, 62, 63],
|
||||
"0/29/2": [],
|
||||
"0/29/3": [1],
|
||||
"0/29/65533": 2,
|
||||
"0/29/65532": 0,
|
||||
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/29/65529": [],
|
||||
"0/29/65528": [],
|
||||
"0/31/0": [
|
||||
{
|
||||
"1": 5,
|
||||
"2": 2,
|
||||
"3": [112233],
|
||||
"4": null,
|
||||
"254": 2
|
||||
}
|
||||
],
|
||||
"0/31/2": 4,
|
||||
"0/31/3": 3,
|
||||
"0/31/4": 4,
|
||||
"0/31/65533": 2,
|
||||
"0/31/65532": 0,
|
||||
"0/31/65531": [0, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/31/65529": [],
|
||||
"0/31/65528": [],
|
||||
"0/40/0": 18,
|
||||
"0/40/1": "Roborock",
|
||||
"0/40/2": 5248,
|
||||
"0/40/3": "Robotic Vacuum Cleaner",
|
||||
"0/40/4": 5,
|
||||
"0/40/5": "",
|
||||
"0/40/6": "**REDACTED**",
|
||||
"0/40/7": 2,
|
||||
"0/40/8": "1.4",
|
||||
"0/40/9": 2,
|
||||
"0/40/10": "1.4",
|
||||
"0/40/13": "https://www.roborock.com",
|
||||
"0/40/14": "Robotic Vacuum Cleaner",
|
||||
"0/40/15": "RAPEED12345678",
|
||||
"0/40/18": "12AB12AB12AB12AB",
|
||||
"0/40/19": {
|
||||
"0": 3,
|
||||
"1": 65535
|
||||
},
|
||||
"0/40/21": 17039360,
|
||||
"0/40/22": 1,
|
||||
"0/40/24": 1,
|
||||
"0/40/65533": 4,
|
||||
"0/40/65532": 0,
|
||||
"0/40/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 15, 18, 19, 21, 22, 65528,
|
||||
65529, 65531, 65532, 65533
|
||||
],
|
||||
"0/40/65529": [],
|
||||
"0/40/65528": [],
|
||||
"0/48/0": 0,
|
||||
"0/48/1": {
|
||||
"0": 60,
|
||||
"1": 900
|
||||
},
|
||||
"0/48/2": 0,
|
||||
"0/48/3": 2,
|
||||
"0/48/4": true,
|
||||
"0/48/65533": 2,
|
||||
"0/48/65532": 0,
|
||||
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/48/65529": [0, 2, 4],
|
||||
"0/48/65528": [1, 3, 5],
|
||||
"0/49/0": 1,
|
||||
"0/49/1": [],
|
||||
"0/49/4": true,
|
||||
"0/49/5": 0,
|
||||
"0/49/6": null,
|
||||
"0/49/7": null,
|
||||
"0/49/2": 30,
|
||||
"0/49/3": 60,
|
||||
"0/49/8": [0],
|
||||
"0/49/65533": 2,
|
||||
"0/49/65532": 1,
|
||||
"0/49/65531": [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"0/49/65529": [0, 2, 4, 6, 8],
|
||||
"0/49/65528": [1, 5, 7],
|
||||
"0/50/65533": 1,
|
||||
"0/50/65532": 0,
|
||||
"0/50/65531": [65528, 65529, 65531, 65532, 65533],
|
||||
"0/50/65529": [0],
|
||||
"0/50/65528": [1],
|
||||
"0/51/0": [
|
||||
{
|
||||
"0": "ap0",
|
||||
"1": false,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "sko58laD",
|
||||
"5": [],
|
||||
"6": [],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "wlan0",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "sEo58laD",
|
||||
"5": ["wKhQuQ=="],
|
||||
"6": [
|
||||
"/XqKrJXsABCySjn//vJWgw==",
|
||||
"KgIBaTwJABCySjn//vJWgw==",
|
||||
"/oAAAAAAAACySjn//vJWgw=="
|
||||
],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "sit0",
|
||||
"1": false,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "AAAAAAAA",
|
||||
"5": [],
|
||||
"6": [],
|
||||
"7": 0
|
||||
},
|
||||
{
|
||||
"0": "lo",
|
||||
"1": true,
|
||||
"2": null,
|
||||
"3": null,
|
||||
"4": "AAAAAAAA",
|
||||
"5": ["fwAAAQ=="],
|
||||
"6": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
|
||||
"7": 0
|
||||
}
|
||||
],
|
||||
"0/51/1": 296,
|
||||
"0/51/2": 8,
|
||||
"0/51/3": 6328,
|
||||
"0/51/8": false,
|
||||
"0/51/65533": 2,
|
||||
"0/51/65532": 0,
|
||||
"0/51/65531": [0, 1, 2, 3, 8, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/51/65529": [0, 1],
|
||||
"0/51/65528": [2],
|
||||
"0/60/0": 0,
|
||||
"0/60/1": null,
|
||||
"0/60/2": null,
|
||||
"0/60/65533": 1,
|
||||
"0/60/65532": 0,
|
||||
"0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/60/65529": [0, 2],
|
||||
"0/60/65528": [],
|
||||
"0/62/0": [
|
||||
{
|
||||
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRyhgkBwEkCAEwCUEEFn0vNfCOD0dTxJ+/vIAsLHsPottGgAzLEYjD0IZda+wcLI6otwL3l70MZK44UQact9g+kLna4RHtR2DtJjzi3DcKNQEoARgkAgE2AwQCBAEYMAQUfe7BMayXJA5FAhU93iHoPeGaicwwBRS9bdraaL8JLSNzrDNJcbicl5ghHRgwC0DAfR8r1sKukiqQw8dPHxQBsDVYjQ2jyerfvkYRSMQGIr9Pr594PCSUazATbDgxf9kvIT7cpAnWVjA1YaYLXSlVGA==",
|
||||
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEYKwzNQoI9xg/J/BXjm//XmufngPSiphrXcf/ZbJxf7K3k8Xo7I77pwece9Uj8QnKrMMUdloy0sNyxbIPkTGpyjcKNQEpARgkAmAwBBS9bdraaL8JLSNzrDNJcbicl5ghHTAFFBfVqc98NGU0Xt+pmyNVXJvnhDlkGDALQKoRuyZfkC/AbH9qIIxjOhkfJB2ZS8sovhbN1fo+cvSfZXdBw255Ytf9nag0yY2maE5thqhIE4MgGV9jwQ2EPysY",
|
||||
"254": 2
|
||||
}
|
||||
],
|
||||
"0/62/1": [
|
||||
{
|
||||
"1": "BFhpm8fVgw4hzcuwFGwSe59XhvdUHtMntaUUbgCX0jqoaA1fjjcRYrZCA0PDImdLtZSkrUdug3S/euAVf4gvaKo=",
|
||||
"2": 4939,
|
||||
"3": 2,
|
||||
"4": 202,
|
||||
"5": "Home Assistant",
|
||||
"254": 2
|
||||
}
|
||||
],
|
||||
"0/62/2": 5,
|
||||
"0/62/3": 2,
|
||||
"0/62/4": [
|
||||
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEZGswP7Cx5r/rggyFyL5F/W2s7jQv9jdnF/BtORJ5CJLHyNrJouomrpNPkewkATT25URTzakxfZ/BC2RRof3LQjcKNQEpARgkAmAwBBSwDB1/C2jgnr2LPAd9KH/07G7HSjAFFLAMHX8LaOCevYs8B30of/TsbsdKGDALQGEJod+l+O0QOa/rnbYaghE4QgquJyT9pviD3sP2+MbUXJj1br+dZLQ7CfeCKfbM8EO9iPAe1ULLveIFfHakCpAY",
|
||||
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEDheXhz87ejqXrjJrfcRfXbv1Co84yVLcfxYr3Q4VM5Fx0JCbQDNTmqeZ/BC67MDnaqXhrPHz6tPXjC7kar6RLDcKNQEpARgkAmAwBBQX1anPfDRlNF7fqZsjVVyb54Q5ZDAFFBfVqc98NGU0Xt+pmyNVXJvnhDlkGDALQFQj3btpuzZU/TNTTTh2Q/bUE8TTOP7U4kV4J8VNyl/phUUHSfnTAnaTR/YcUehZcgPJqnW6433HWTjsa8lopVMY"
|
||||
],
|
||||
"0/62/5": 2,
|
||||
"0/62/65533": 1,
|
||||
"0/62/65532": 0,
|
||||
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
|
||||
"0/62/65528": [1, 3, 5, 8],
|
||||
"0/63/0": [],
|
||||
"0/63/1": [],
|
||||
"0/63/2": 4,
|
||||
"0/63/3": 3,
|
||||
"0/63/65533": 2,
|
||||
"0/63/65532": 0,
|
||||
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"0/63/65529": [0, 1, 3, 4],
|
||||
"0/63/65528": [2, 5],
|
||||
"1/29/0": [
|
||||
{
|
||||
"0": 17,
|
||||
"1": 1
|
||||
},
|
||||
{
|
||||
"0": 116,
|
||||
"1": 1
|
||||
}
|
||||
],
|
||||
"1/29/1": [3, 29, 47, 84, 85, 97, 336],
|
||||
"1/29/2": [],
|
||||
"1/29/3": [],
|
||||
"1/29/65533": 2,
|
||||
"1/29/65532": 0,
|
||||
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/29/65529": [],
|
||||
"1/29/65528": [],
|
||||
"1/3/0": 0,
|
||||
"1/3/1": 3,
|
||||
"1/3/65533": 5,
|
||||
"1/3/65532": 0,
|
||||
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/3/65529": [0],
|
||||
"1/3/65528": [],
|
||||
"1/336/0": [
|
||||
{
|
||||
"0": 1,
|
||||
"1": 0,
|
||||
"2": {
|
||||
"0": {
|
||||
"0": "Living room",
|
||||
"1": null,
|
||||
"2": 52
|
||||
},
|
||||
"1": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"0": 2,
|
||||
"1": 0,
|
||||
"2": {
|
||||
"0": {
|
||||
"0": "Bathroom",
|
||||
"1": null,
|
||||
"2": 6
|
||||
},
|
||||
"1": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"0": 3,
|
||||
"1": 0,
|
||||
"2": {
|
||||
"0": {
|
||||
"0": "Bedroom",
|
||||
"1": null,
|
||||
"2": 7
|
||||
},
|
||||
"1": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"0": 4,
|
||||
"1": 0,
|
||||
"2": {
|
||||
"0": {
|
||||
"0": "Office",
|
||||
"1": null,
|
||||
"2": 88
|
||||
},
|
||||
"1": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"0": 5,
|
||||
"1": 0,
|
||||
"2": {
|
||||
"0": {
|
||||
"0": "Corridor",
|
||||
"1": null,
|
||||
"2": 16
|
||||
},
|
||||
"1": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"0": 6,
|
||||
"1": 0,
|
||||
"2": {
|
||||
"0": null,
|
||||
"1": {
|
||||
"0": 17,
|
||||
"1": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"0": 7,
|
||||
"1": 0,
|
||||
"2": {
|
||||
"0": null,
|
||||
"1": {
|
||||
"0": 43,
|
||||
"1": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"1/336/2": [],
|
||||
"1/336/65533": 1,
|
||||
"1/336/65532": 4,
|
||||
"1/336/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/336/65529": [0],
|
||||
"1/336/65528": [1],
|
||||
"1/336/1": [
|
||||
{
|
||||
"0": 0,
|
||||
"1": "Map-0"
|
||||
}
|
||||
],
|
||||
"1/47/0": 1,
|
||||
"1/47/1": 0,
|
||||
"1/47/2": "Primary Battery",
|
||||
"1/47/31": [],
|
||||
"1/47/12": 200,
|
||||
"1/47/14": 0,
|
||||
"1/47/15": false,
|
||||
"1/47/16": 3,
|
||||
"1/47/17": true,
|
||||
"1/47/26": 2,
|
||||
"1/47/28": true,
|
||||
"1/47/65533": 3,
|
||||
"1/47/65532": 6,
|
||||
"1/47/65531": [
|
||||
0, 1, 2, 12, 14, 15, 16, 17, 26, 28, 31, 65528, 65529, 65531, 65532, 65533
|
||||
],
|
||||
"1/47/65529": [],
|
||||
"1/47/65528": [],
|
||||
"1/84/0": [
|
||||
{
|
||||
"label": "Idle",
|
||||
"mode": 0,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 16384
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Cleaning",
|
||||
"mode": 1,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 16385
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Mapping",
|
||||
"mode": 2,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 16386
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"1/84/1": 0,
|
||||
"1/84/65533": 3,
|
||||
"1/84/65532": 0,
|
||||
"1/84/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/84/65529": [0],
|
||||
"1/84/65528": [1],
|
||||
"1/85/0": [
|
||||
{
|
||||
"label": "Quiet, Vacuum Only",
|
||||
"mode": 1,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"value": 16385
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Auto, Vacuum Only",
|
||||
"mode": 2,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"value": 16385
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Deep Clean, Vacuum Only",
|
||||
"mode": 3,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 16384
|
||||
},
|
||||
{
|
||||
"value": 16385
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Quiet, Mop Only",
|
||||
"mode": 4,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"value": 16386
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Auto, Mop Only",
|
||||
"mode": 5,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"value": 16386
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Deep Clean, Mop Only",
|
||||
"mode": 6,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 16384
|
||||
},
|
||||
{
|
||||
"value": 16386
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Quiet, Vacuum and Mop",
|
||||
"mode": 7,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"value": 16385
|
||||
},
|
||||
{
|
||||
"value": 16386
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Auto, Vacuum and Mop",
|
||||
"mode": 8,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"value": 16385
|
||||
},
|
||||
{
|
||||
"value": 16386
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Deep Clean, Vacuum and Mop",
|
||||
"mode": 9,
|
||||
"modeTags": [
|
||||
{
|
||||
"value": 16384
|
||||
},
|
||||
{
|
||||
"value": 16385
|
||||
},
|
||||
{
|
||||
"value": 16386
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"1/85/1": 8,
|
||||
"1/85/65533": 3,
|
||||
"1/85/65532": 0,
|
||||
"1/85/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/85/65529": [0],
|
||||
"1/85/65528": [1],
|
||||
"1/97/0": null,
|
||||
"1/97/1": null,
|
||||
"1/97/3": [
|
||||
{
|
||||
"0": 0
|
||||
},
|
||||
{
|
||||
"0": 1
|
||||
},
|
||||
{
|
||||
"0": 2
|
||||
},
|
||||
{
|
||||
"0": 3
|
||||
},
|
||||
{
|
||||
"0": 64
|
||||
},
|
||||
{
|
||||
"0": 65
|
||||
},
|
||||
{
|
||||
"0": 66
|
||||
}
|
||||
],
|
||||
"1/97/4": 66,
|
||||
"1/97/5": {
|
||||
"0": 0
|
||||
},
|
||||
"1/97/65533": 2,
|
||||
"1/97/65532": 0,
|
||||
"1/97/65531": [0, 1, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/97/65529": [0, 3, 128],
|
||||
"1/97/65528": [4]
|
||||
},
|
||||
"attribute_subscriptions": []
|
||||
}
|
||||
@@ -3485,6 +3485,56 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[roborock_saros_10][button.robotic_vacuum_cleaner_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.robotic_vacuum_cleaner_identify',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[roborock_saros_10][button.robotic_vacuum_cleaner_identify-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Robotic Vacuum Cleaner Identify',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.robotic_vacuum_cleaner_identify',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[secuyou_smart_lock][button.secuyou_smart_lock_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -3995,6 +3995,78 @@
|
||||
'state': 'previous',
|
||||
})
|
||||
# ---
|
||||
# name: test_selects[roborock_saros_10][select.robotic_vacuum_cleaner_clean_mode-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'Quiet, Vacuum Only',
|
||||
'Auto, Vacuum Only',
|
||||
'Deep Clean, Vacuum Only',
|
||||
'Quiet, Mop Only',
|
||||
'Auto, Mop Only',
|
||||
'Deep Clean, Mop Only',
|
||||
'Quiet, Vacuum and Mop',
|
||||
'Auto, Vacuum and Mop',
|
||||
'Deep Clean, Vacuum and Mop',
|
||||
]),
|
||||
}),
|
||||
'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.robotic_vacuum_cleaner_clean_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Clean mode',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Clean mode',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'clean_mode',
|
||||
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-MatterRvcCleanMode-85-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_selects[roborock_saros_10][select.robotic_vacuum_cleaner_clean_mode-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robotic Vacuum Cleaner Clean mode',
|
||||
'options': list([
|
||||
'Quiet, Vacuum Only',
|
||||
'Auto, Vacuum Only',
|
||||
'Deep Clean, Vacuum Only',
|
||||
'Quiet, Mop Only',
|
||||
'Auto, Mop Only',
|
||||
'Deep Clean, Mop Only',
|
||||
'Quiet, Vacuum and Mop',
|
||||
'Auto, Vacuum and Mop',
|
||||
'Deep Clean, Vacuum and Mop',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.robotic_vacuum_cleaner_clean_mode',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'Auto, Vacuum and Mop',
|
||||
})
|
||||
# ---
|
||||
# name: test_selects[secuyou_smart_lock][select.secuyou_smart_lock_operating_mode-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -11473,6 +11473,283 @@
|
||||
'state': '20.55',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_battery-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.robotic_vacuum_cleaner_battery',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Battery',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Battery',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-PowerSource-47-12',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_battery-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'Robotic Vacuum Cleaner Battery',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.robotic_vacuum_cleaner_battery',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '100',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_battery_charge_state-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'not_charging',
|
||||
'charging',
|
||||
'full_charge',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.robotic_vacuum_cleaner_battery_charge_state',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Battery charge state',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Battery charge state',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'battery_charge_state',
|
||||
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-PowerSourceBatChargeState-47-26',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_battery_charge_state-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Robotic Vacuum Cleaner Battery charge state',
|
||||
'options': list([
|
||||
'not_charging',
|
||||
'charging',
|
||||
'full_charge',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.robotic_vacuum_cleaner_battery_charge_state',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'full_charge',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_operational_error-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'no_error',
|
||||
'unable_to_start_or_resume',
|
||||
'unable_to_complete_operation',
|
||||
'command_invalid_in_state',
|
||||
'failed_to_find_charging_dock',
|
||||
'stuck',
|
||||
'dust_bin_missing',
|
||||
'dust_bin_full',
|
||||
'water_tank_empty',
|
||||
'water_tank_missing',
|
||||
'water_tank_lid_open',
|
||||
'mop_cleaning_pad_missing',
|
||||
'low_battery',
|
||||
'cannot_reach_target_area',
|
||||
'dirty_water_tank_full',
|
||||
'dirty_water_tank_missing',
|
||||
'wheels_jammed',
|
||||
'brush_jammed',
|
||||
'navigation_sensor_obscured',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.robotic_vacuum_cleaner_operational_error',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Operational error',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Operational error',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'operational_error',
|
||||
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-RvcOperationalStateOperationalError-97-5',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_operational_error-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Robotic Vacuum Cleaner Operational error',
|
||||
'options': list([
|
||||
'no_error',
|
||||
'unable_to_start_or_resume',
|
||||
'unable_to_complete_operation',
|
||||
'command_invalid_in_state',
|
||||
'failed_to_find_charging_dock',
|
||||
'stuck',
|
||||
'dust_bin_missing',
|
||||
'dust_bin_full',
|
||||
'water_tank_empty',
|
||||
'water_tank_missing',
|
||||
'water_tank_lid_open',
|
||||
'mop_cleaning_pad_missing',
|
||||
'low_battery',
|
||||
'cannot_reach_target_area',
|
||||
'dirty_water_tank_full',
|
||||
'dirty_water_tank_missing',
|
||||
'wheels_jammed',
|
||||
'brush_jammed',
|
||||
'navigation_sensor_obscured',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.robotic_vacuum_cleaner_operational_error',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'no_error',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_operational_state-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'stopped',
|
||||
'running',
|
||||
'paused',
|
||||
'error',
|
||||
'seeking_charger',
|
||||
'charging',
|
||||
'docked',
|
||||
]),
|
||||
}),
|
||||
'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.robotic_vacuum_cleaner_operational_state',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Operational state',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Operational state',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'operational_state',
|
||||
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-RvcOperationalState-97-4',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[roborock_saros_10][sensor.robotic_vacuum_cleaner_operational_state-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Robotic Vacuum Cleaner Operational state',
|
||||
'options': list([
|
||||
'stopped',
|
||||
'running',
|
||||
'paused',
|
||||
'error',
|
||||
'seeking_charger',
|
||||
'charging',
|
||||
'docked',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.robotic_vacuum_cleaner_operational_state',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'docked',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[silabs_dishwasher][sensor.dishwasher_effective_current-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -149,6 +149,56 @@
|
||||
'state': 'idle',
|
||||
})
|
||||
# ---
|
||||
# name: test_vacuum[roborock_saros_10][vacuum.robotic_vacuum_cleaner-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': 'vacuum',
|
||||
'entity_category': None,
|
||||
'entity_id': 'vacuum.robotic_vacuum_cleaner',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <VacuumEntityFeature: 29212>,
|
||||
'translation_key': 'vacuum',
|
||||
'unique_id': '00000000000004D2-00000000000000CA-MatterNodeDevice-1-MatterVacuumCleaner-84-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_vacuum[roborock_saros_10][vacuum.robotic_vacuum_cleaner-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robotic Vacuum Cleaner',
|
||||
'supported_features': <VacuumEntityFeature: 29212>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'vacuum.robotic_vacuum_cleaner',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'docked',
|
||||
})
|
||||
# ---
|
||||
# name: test_vacuum[switchbot_k11_plus][vacuum.k11-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -338,6 +338,38 @@ async def test_vacuum_get_segments(
|
||||
assert segments[2] == {"id": "2290649224", "name": "My Location C", "group": None}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["roborock_saros_10"])
|
||||
async def test_vacuum_get_segments_nullable_location_info(
|
||||
hass: HomeAssistant,
|
||||
matter_node: MatterNode,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test vacuum get_segments handles nullable ServiceArea location info."""
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
assert matter_node
|
||||
|
||||
entity_ids = [state.entity_id for state in hass.states.async_all("vacuum")]
|
||||
assert len(entity_ids) == 1
|
||||
entity_id = entity_ids[0]
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await client.send_json_auto_id(
|
||||
{"type": "vacuum/get_segments", "entity_id": entity_id}
|
||||
)
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["success"]
|
||||
assert msg["result"]["segments"] == [
|
||||
{"id": "1", "name": "Living room", "group": None},
|
||||
{"id": "2", "name": "Bathroom", "group": None},
|
||||
{"id": "3", "name": "Bedroom", "group": None},
|
||||
{"id": "4", "name": "Office", "group": None},
|
||||
{"id": "5", "name": "Corridor", "group": None},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_vacuum_cleaner"])
|
||||
async def test_vacuum_clean_area(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -31,7 +31,7 @@ from tests.common import MockConfigEntry
|
||||
type ComponentSetup = Callable[[], Awaitable[None]]
|
||||
|
||||
BACKUP_METADATA = {
|
||||
"file_path": "backup_location/backup.tar",
|
||||
"file_path": "/backup_location/backup.tar",
|
||||
"metadata": {
|
||||
"addons": [{"name": "Test", "slug": "test", "version": "1.0.0"}],
|
||||
"backup_id": "test-backup",
|
||||
@@ -60,7 +60,7 @@ USER_INPUT = {
|
||||
CONF_USERNAME: "username",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_PRIVATE_KEY_FILE: PRIVATE_KEY_FILE_UUID,
|
||||
CONF_BACKUP_LOCATION: "backup_location",
|
||||
CONF_BACKUP_LOCATION: "/backup_location",
|
||||
}
|
||||
TEST_AGENT_ID = ulid()
|
||||
|
||||
@@ -118,7 +118,7 @@ def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
|
||||
CONF_USERNAME: "username",
|
||||
CONF_PASSWORD: "password",
|
||||
CONF_PRIVATE_KEY_FILE: str(private_key),
|
||||
CONF_BACKUP_LOCATION: "backup_location",
|
||||
CONF_BACKUP_LOCATION: "/backup_location",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ async def test_agents_list_backups_include_bad_metadata(
|
||||
# Called two times, one for bad backup metadata and once for good
|
||||
assert mock_ssh_connection._sftp._mock_open._mock_read.call_count == 2
|
||||
assert (
|
||||
"Failed to load backup metadata from file: backup_location/invalid.metadata.json. Expecting value: line 1 column 1 (char 0)"
|
||||
"Failed to load backup metadata from file: /backup_location/invalid.metadata.json. Expecting value: line 1 column 1 (char 0)"
|
||||
in caplog.messages
|
||||
)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from homeassistant.components.sftp_storage.config_flow import (
|
||||
SFTPStorageMissingPasswordOrPkey,
|
||||
)
|
||||
from homeassistant.components.sftp_storage.const import (
|
||||
CONF_BACKUP_LOCATION,
|
||||
CONF_HOST,
|
||||
CONF_PASSWORD,
|
||||
CONF_PRIVATE_KEY_FILE,
|
||||
@@ -194,3 +195,35 @@ async def test_config_entry_error(hass: HomeAssistant) -> None:
|
||||
result["flow_id"], user_input
|
||||
)
|
||||
assert "errors" in result and result["errors"]["base"] == "key_or_password_needed"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
@pytest.mark.usefixtures("mock_process_uploaded_file")
|
||||
@pytest.mark.usefixtures("mock_ssh_connection")
|
||||
async def test_relative_backup_location_rejected(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test that a relative backup location path is rejected."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
user_input = USER_INPUT.copy()
|
||||
user_input[CONF_BACKUP_LOCATION] = "backups"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {CONF_BACKUP_LOCATION: "backup_location_relative"}
|
||||
|
||||
# Fix the path and verify the flow succeeds
|
||||
user_input[CONF_BACKUP_LOCATION] = "/backups"
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], user_input
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "05accb39-2017-c98b-a5ab-04a81f4d3d9a",
|
||||
"deviceId": "01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
"name": "[robot vacuum] Samsung",
|
||||
"label": "Robot vacuum",
|
||||
"label": "Robot Vacuum",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"presentationId": "DA-RVC-MAP-01011",
|
||||
"deviceManufacturerCode": "Samsung Electronics",
|
||||
"locationId": "d31d0982-9bf9-4f0c-afd4-ad3d78842541",
|
||||
"ownerId": "85532262-6537-54d9-179a-333db98dbcc0",
|
||||
"roomId": "572f5713-53a9-4fb8-85fd-60515e44f1ed",
|
||||
"locationId": "4647a408-2d4f-44a8-8ee6-f64328a0e480",
|
||||
"ownerId": "8157695b-6c2f-4de5-98cb-bacaf51b8b2d",
|
||||
"roomId": "9b0f3cf5-56b5-45fa-9bb8-81014bd63715",
|
||||
"deviceTypeName": "Samsung OCF Robot Vacuum",
|
||||
"components": [
|
||||
{
|
||||
@@ -132,10 +132,22 @@
|
||||
"id": "samsungce.microphoneSettings",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.notification",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.objectDetection",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.softwareUpdate",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.sabbathMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.musicPlaylist",
|
||||
"version": 1
|
||||
@@ -320,24 +332,24 @@
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"createTime": "2025-06-20T14:12:56.260Z",
|
||||
"createTime": "2026-02-27T10:49:02.683Z",
|
||||
"profile": {
|
||||
"id": "5d345d41-a497-3fc7-84fe-eaaee50f0509"
|
||||
"id": "0b3bf610-5ec4-3eeb-9e50-1038099f6904"
|
||||
},
|
||||
"ocf": {
|
||||
"ocfDeviceType": "oic.d.robotcleaner",
|
||||
"name": "[robot vacuum] Samsung",
|
||||
"specVersion": "core.1.1.0",
|
||||
"verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0",
|
||||
"verticalDomainSpecVersion": "1.2.1",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"modelNumber": "JETBOT_COMBO_9X00_24K|50029141|80010b0002d8411f0100000000000000",
|
||||
"modelNumber": "JETBOT_COMBOT_9X00_24K|50029141|80010a0002d8411f0100000000000000",
|
||||
"platformVersion": "1.0",
|
||||
"platformOS": "Tizen",
|
||||
"hwVersion": "",
|
||||
"firmwareVersion": "20250123.105306",
|
||||
"firmwareVersion": "20260120.215157",
|
||||
"vendorId": "DA-RVC-MAP-01011",
|
||||
"vendorResourceClientServerVersion": "4.0.38",
|
||||
"lastSignupTime": "2025-06-20T14:12:56.202953160Z",
|
||||
"vendorResourceClientServerVersion": "4.0.40",
|
||||
"lastSignupTime": "2026-02-27T12:08:52.022059763Z",
|
||||
"transferCandidate": false,
|
||||
"additionalAuthCodeRequired": false,
|
||||
"modelCode": "NONE"
|
||||
|
||||
@@ -947,19 +947,19 @@
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'05accb39-2017-c98b-a5ab-04a81f4d3d9a',
|
||||
'01b28624-5907-c8bc-0325-8ad23f03a637',
|
||||
),
|
||||
}),
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Samsung Electronics',
|
||||
'model': 'JETBOT_COMBO_9X00_24K',
|
||||
'model': 'JETBOT_COMBOT_9X00_24K',
|
||||
'model_id': None,
|
||||
'name': 'Robot vacuum',
|
||||
'name': 'Robot Vacuum',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'sw_version': '20250123.105306',
|
||||
'sw_version': '20260120.215157',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -1,4 +1,58 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[da_rvc_map_01011][media_player.robot_vacuum-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'media_player',
|
||||
'entity_category': None,
|
||||
'entity_id': 'media_player.robot_vacuum',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 284045>,
|
||||
'translation_key': None,
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][media_player.robot_vacuum-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum',
|
||||
'is_volume_muted': False,
|
||||
'repeat': <RepeatMode.ALL: 'all'>,
|
||||
'supported_features': <MediaPlayerEntityFeature: 284045>,
|
||||
'volume_level': 0.2,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'media_player.robot_vacuum',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[hw_q80r_soundbar][media_player.soundbar-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -452,14 +452,14 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'lamp',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_samsungce.lamp_brightnessLevel_brightnessLevel',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_samsungce.lamp_brightnessLevel_brightnessLevel',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_lamp-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot vacuum Lamp',
|
||||
'friendly_name': 'Robot Vacuum Lamp',
|
||||
'options': list([
|
||||
'on',
|
||||
'off',
|
||||
|
||||
@@ -10506,7 +10506,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_battery_battery_battery',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_battery_battery_battery',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
@@ -10514,7 +10514,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'Robot vacuum Battery',
|
||||
'friendly_name': 'Robot Vacuum Battery',
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -10522,7 +10522,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '59',
|
||||
'state': '99',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_cleaning_mode-entry]
|
||||
@@ -10566,7 +10566,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'robot_cleaner_cleaning_mode',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_robotCleanerCleaningMode_robotCleanerCleaningMode_robotCleanerCleaningMode',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_robotCleanerCleaningMode_robotCleanerCleaningMode_robotCleanerCleaningMode',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
@@ -10574,7 +10574,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Robot vacuum Cleaning mode',
|
||||
'friendly_name': 'Robot Vacuum Cleaning mode',
|
||||
'options': list([
|
||||
'auto',
|
||||
'part',
|
||||
@@ -10629,7 +10629,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_energy_meter',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_powerConsumptionReport_powerConsumption_energy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
@@ -10637,7 +10637,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Robot vacuum Energy',
|
||||
'friendly_name': 'Robot Vacuum Energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
@@ -10646,7 +10646,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.981',
|
||||
'state': '0.335',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_difference-entry]
|
||||
@@ -10686,7 +10686,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_difference',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
@@ -10694,7 +10694,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Robot vacuum Energy difference',
|
||||
'friendly_name': 'Robot Vacuum Energy difference',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
@@ -10703,7 +10703,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.021',
|
||||
'state': '0.003',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_energy_saved-entry]
|
||||
@@ -10743,7 +10743,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_saved',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_energySaved_meter',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_powerConsumptionReport_powerConsumption_energySaved_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
@@ -10751,7 +10751,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Robot vacuum Energy saved',
|
||||
'friendly_name': 'Robot Vacuum Energy saved',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
@@ -10809,7 +10809,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'robot_cleaner_movement',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_robotCleanerMovement_robotCleanerMovement_robotCleanerMovement',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_robotCleanerMovement_robotCleanerMovement_robotCleanerMovement',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
@@ -10817,7 +10817,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Robot vacuum Movement',
|
||||
'friendly_name': 'Robot Vacuum Movement',
|
||||
'options': list([
|
||||
'homing',
|
||||
'idle',
|
||||
@@ -10837,7 +10837,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'cleaning',
|
||||
'state': 'charging',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][sensor.robot_vacuum_power-entry]
|
||||
@@ -10877,7 +10877,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_power_meter',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_powerConsumptionReport_powerConsumption_power_meter',
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
})
|
||||
# ---
|
||||
@@ -10885,9 +10885,9 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'Robot vacuum Power',
|
||||
'power_consumption_end': '2025-07-10T11:20:22Z',
|
||||
'power_consumption_start': '2025-07-10T11:11:22Z',
|
||||
'friendly_name': 'Robot Vacuum Power',
|
||||
'power_consumption_end': '2026-02-27T17:02:44Z',
|
||||
'power_consumption_start': '2026-02-27T16:52:44Z',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
@@ -10936,7 +10936,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'power_energy',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_powerConsumptionReport_powerConsumption_powerEnergy_meter',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_powerConsumptionReport_powerConsumption_powerEnergy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
@@ -10944,7 +10944,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Robot vacuum Power energy',
|
||||
'friendly_name': 'Robot Vacuum Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
@@ -10995,7 +10995,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'robot_cleaner_turbo_mode',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_robotCleanerTurboMode_robotCleanerTurboMode_robotCleanerTurboMode',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_robotCleanerTurboMode_robotCleanerTurboMode_robotCleanerTurboMode',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
@@ -11003,7 +11003,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Robot vacuum Turbo mode',
|
||||
'friendly_name': 'Robot Vacuum Turbo mode',
|
||||
'options': list([
|
||||
'on',
|
||||
'off',
|
||||
@@ -11016,7 +11016,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'extra_silence',
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_battery-entry]
|
||||
|
||||
@@ -979,55 +979,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum-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': 'switch',
|
||||
'entity_category': None,
|
||||
'entity_id': 'switch.robot_vacuum',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_switch_switch_switch',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot vacuum',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.robot_vacuum',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum_do_not_disturb-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1060,21 +1011,21 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'do_not_disturb',
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main_custom.doNotDisturbMode_doNotDisturb_doNotDisturb',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_custom.doNotDisturbMode_doNotDisturb_doNotDisturb',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum_do_not_disturb-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot vacuum Do not disturb',
|
||||
'friendly_name': 'Robot Vacuum Do not disturb',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.robot_vacuum_do_not_disturb',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_normal_000001][switch.robot_vacuum-entry]
|
||||
|
||||
@@ -31,14 +31,14 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <VacuumEntityFeature: 12308>,
|
||||
'translation_key': None,
|
||||
'unique_id': '05accb39-2017-c98b-a5ab-04a81f4d3d9a_main',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][vacuum.robot_vacuum-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot vacuum',
|
||||
'friendly_name': 'Robot Vacuum',
|
||||
'supported_features': <VacuumEntityFeature: 12308>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
||||
@@ -68,7 +68,7 @@ async def test_vacuum_actions(
|
||||
blocking=True,
|
||||
)
|
||||
devices.execute_device_command.assert_called_once_with(
|
||||
"05accb39-2017-c98b-a5ab-04a81f4d3d9a",
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_OPERATING_STATE,
|
||||
command,
|
||||
MAIN,
|
||||
@@ -89,7 +89,7 @@ async def test_state_update(
|
||||
await trigger_update(
|
||||
hass,
|
||||
devices,
|
||||
"05accb39-2017-c98b-a5ab-04a81f4d3d9a",
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_OPERATING_STATE,
|
||||
Attribute.OPERATING_STATE,
|
||||
"error",
|
||||
@@ -110,13 +110,13 @@ async def test_availability(
|
||||
assert hass.states.get("vacuum.robot_vacuum").state == VacuumActivity.DOCKED
|
||||
|
||||
await trigger_health_update(
|
||||
hass, devices, "05accb39-2017-c98b-a5ab-04a81f4d3d9a", HealthStatus.OFFLINE
|
||||
hass, devices, "01b28624-5907-c8bc-0325-8ad23f03a637", HealthStatus.OFFLINE
|
||||
)
|
||||
|
||||
assert hass.states.get("vacuum.robot_vacuum").state == STATE_UNAVAILABLE
|
||||
|
||||
await trigger_health_update(
|
||||
hass, devices, "05accb39-2017-c98b-a5ab-04a81f4d3d9a", HealthStatus.ONLINE
|
||||
hass, devices, "01b28624-5907-c8bc-0325-8ad23f03a637", HealthStatus.ONLINE
|
||||
)
|
||||
|
||||
assert hass.states.get("vacuum.robot_vacuum").state == VacuumActivity.DOCKED
|
||||
|
||||
@@ -41,7 +41,11 @@ from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
return_value={"mock-domain": [{"st": "mock-st"}]},
|
||||
)
|
||||
async def test_ssdp_flow_dispatched_on_st(
|
||||
mock_get_ssdp, hass: HomeAssistant, caplog: pytest.LogCaptureFixture, mock_flow_init
|
||||
mock_get_ssdp,
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
mock_flow_init,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Test matching based on ST."""
|
||||
mock_ssdp_search_response = _ssdp_headers(
|
||||
@@ -84,7 +88,11 @@ async def test_ssdp_flow_dispatched_on_st(
|
||||
return_value={"mock-domain": [{"manufacturerURL": "mock-url"}]},
|
||||
)
|
||||
async def test_ssdp_flow_dispatched_on_manufacturer_url(
|
||||
mock_get_ssdp, hass: HomeAssistant, caplog: pytest.LogCaptureFixture, mock_flow_init
|
||||
mock_get_ssdp,
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
mock_flow_init,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
) -> None:
|
||||
"""Test matching based on manufacturerURL."""
|
||||
mock_ssdp_search_response = _ssdp_headers(
|
||||
@@ -1038,6 +1046,7 @@ async def test_ssdp_rediscover(
|
||||
async def test_ssdp_rediscover_no_match(
|
||||
mock_get_ssdp,
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
mock_flow_init,
|
||||
entry_domain: str,
|
||||
entry_discovery_keys: dict[str, tuple[DiscoveryKey, ...]],
|
||||
|
||||
@@ -119,8 +119,10 @@ async def test_webhook_platform_init(hass: HomeAssistant, webhook_bot) -> None:
|
||||
assert hass.services.has_service(DOMAIN, SERVICE_SEND_MESSAGE) is True
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_external_calls", "mock_polling_calls")
|
||||
async def test_polling_platform_init(
|
||||
hass: HomeAssistant, mock_polling_config_entry: MockConfigEntry
|
||||
hass: HomeAssistant,
|
||||
mock_polling_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test initialization of the polling platform."""
|
||||
mock_polling_config_entry.add_to_hass(hass)
|
||||
|
||||
@@ -684,7 +684,7 @@ async def test_sun_renders_once_per_sensor(hass: HomeAssistant) -> None:
|
||||
def _record_async_render(self, *args, **kwargs):
|
||||
"""Catch async_render."""
|
||||
async_render_calls.append(self.template)
|
||||
return "75"
|
||||
return 75
|
||||
|
||||
later = dt_util.utcnow()
|
||||
|
||||
@@ -692,7 +692,7 @@ async def test_sun_renders_once_per_sensor(hass: HomeAssistant) -> None:
|
||||
hass.states.async_set("sun.sun", {"elevation": 50, "next_rising": later})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.solar_angle").state == "75.0"
|
||||
assert hass.states.get("sensor.solar_angle").state == "75"
|
||||
assert hass.states.get("sensor.sunrise").state == "75"
|
||||
|
||||
assert len(async_render_calls) == 2
|
||||
@@ -1524,7 +1524,7 @@ async def test_last_reset(hass: HomeAssistant, expected: str) -> None:
|
||||
|
||||
state = hass.states.get(TEST_SENSOR.entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "0.0"
|
||||
assert state.state == "0"
|
||||
assert state.attributes["state_class"] == "total"
|
||||
assert state.attributes["last_reset"] == expected
|
||||
|
||||
@@ -1553,7 +1553,7 @@ async def test_invalid_last_reset(
|
||||
|
||||
state = hass.states.get(TEST_SENSOR.entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "0.0"
|
||||
assert state.state == "0"
|
||||
assert state.attributes.get("last_reset") is None
|
||||
|
||||
err = "Received invalid sensor last_reset: not a datetime for entity"
|
||||
@@ -1993,3 +1993,47 @@ async def test_numeric_sensor_recovers_from_exception(hass: HomeAssistant) -> No
|
||||
):
|
||||
await async_trigger(hass, TEST_STATE_SENSOR, set_state)
|
||||
assert hass.states.get(TEST_SENSOR.entity_id).state == expected_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("count", "config"),
|
||||
[
|
||||
(
|
||||
1,
|
||||
{
|
||||
"device_class": "temperature",
|
||||
"state_class": "measurement",
|
||||
"unit_of_measurement": "°C",
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"style", [ConfigurationStyle.MODERN, ConfigurationStyle.TRIGGER]
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("state_template", "expected_state"),
|
||||
[
|
||||
("{{ '1.0' }}", "1.0"),
|
||||
("{{ '1' }}", "1"),
|
||||
("{{ 1.0 }}", "1.0"),
|
||||
("{{ 1 }}", "1"),
|
||||
("{{ '0.0' }}", "0.0"),
|
||||
("{{ '0' }}", "0"),
|
||||
("{{ 0.0 }}", "0.0"),
|
||||
("{{ 0 }}", "0"),
|
||||
("{{ '10021452' }}", "10021452"),
|
||||
("{{ 10021452 }}", "10021452"),
|
||||
("{{ '1002.1452' }}", "1002.1452"),
|
||||
("{{ 1002.1452 }}", "1002.1452"),
|
||||
("{{ True }}", STATE_UNKNOWN),
|
||||
("{{ False }}", STATE_UNKNOWN),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("setup_state_sensor")
|
||||
async def test_numeric_sensor_int_float(
|
||||
hass: HomeAssistant, expected_state: str
|
||||
) -> None:
|
||||
"""Test sensor properly stores int or float for state."""
|
||||
await async_trigger(hass, TEST_STATE_SENSOR, "anything")
|
||||
assert hass.states.get(TEST_SENSOR.entity_id).state == expected_state
|
||||
|
||||
@@ -545,7 +545,12 @@ async def test_unlink_devices(
|
||||
}
|
||||
assert device_entries[0].identifiers == set(test_identifiers)
|
||||
|
||||
with patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 3):
|
||||
with (
|
||||
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 3),
|
||||
_patch_discovery(),
|
||||
_patch_single_discovery(),
|
||||
_patch_connect(),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -596,6 +601,8 @@ async def test_move_credentials_hash(
|
||||
patch("homeassistant.components.tplink.Device.connect", new=_connect),
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 4),
|
||||
_patch_discovery(),
|
||||
_patch_single_discovery(),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
@@ -640,6 +647,8 @@ async def test_move_credentials_hash_auth_error(
|
||||
),
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 4),
|
||||
_patch_discovery(),
|
||||
_patch_single_discovery(),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
@@ -682,6 +691,8 @@ async def test_move_credentials_hash_other_error(
|
||||
),
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
patch("homeassistant.components.tplink.CONF_CONFIG_ENTRY_MINOR_VERSION", 4),
|
||||
_patch_discovery(),
|
||||
_patch_single_discovery(),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
@@ -717,6 +728,8 @@ async def test_credentials_hash(
|
||||
with (
|
||||
patch("homeassistant.components.tplink.PLATFORMS", []),
|
||||
patch("homeassistant.components.tplink.Device.connect", new=_connect),
|
||||
_patch_discovery(),
|
||||
_patch_single_discovery(),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
@@ -753,6 +766,8 @@ async def test_credentials_hash_auth_error(
|
||||
"homeassistant.components.tplink.Device.connect",
|
||||
side_effect=AuthenticationError,
|
||||
) as connect_mock,
|
||||
_patch_discovery(),
|
||||
_patch_single_discovery(),
|
||||
):
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
@@ -782,6 +797,7 @@ async def test_credentials_hash_auth_error(
|
||||
async def test_migrate_remove_device_config(
|
||||
hass: HomeAssistant,
|
||||
mock_connect: AsyncMock,
|
||||
mock_discovery: AsyncMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
device_config: DeviceConfig,
|
||||
expected_entry_data: dict[str, Any],
|
||||
|
||||
@@ -33,6 +33,7 @@ from homeassistant.components.update import (
|
||||
from homeassistant.components.zha.helpers import (
|
||||
ZHADeviceProxy,
|
||||
ZHAGatewayProxy,
|
||||
get_zha_data,
|
||||
get_zha_gateway,
|
||||
get_zha_gateway_proxy,
|
||||
)
|
||||
@@ -55,6 +56,7 @@ from homeassistant.setup import async_setup_component
|
||||
from .common import find_entity_id, update_attribute_cache
|
||||
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.typing import WebSocketGenerator
|
||||
|
||||
|
||||
@@ -267,6 +269,43 @@ async def test_firmware_update_notification_from_service_call(
|
||||
)
|
||||
|
||||
|
||||
async def test_firmware_update_poll_after_reload(
|
||||
hass: HomeAssistant,
|
||||
setup_zha: Callable[..., Coroutine[None]],
|
||||
config_entry: MockConfigEntry,
|
||||
zigpy_device_mock: Callable[..., Device],
|
||||
) -> None:
|
||||
"""Test polling a ZHA update entity still works after reloading ZHA."""
|
||||
await setup_zha()
|
||||
await async_setup_component(hass, HA_DOMAIN, {})
|
||||
|
||||
zha_data = get_zha_data(hass)
|
||||
coordinator_before = zha_data.update_coordinator
|
||||
assert coordinator_before is not None
|
||||
|
||||
assert await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
coordinator_after = get_zha_data(hass).update_coordinator
|
||||
assert coordinator_after is not None
|
||||
assert coordinator_after is not coordinator_before
|
||||
|
||||
zha_device, _, _, _ = await setup_test_data(hass, zigpy_device_mock)
|
||||
entity_id = find_entity_id(Platform.UPDATE, zha_device, hass)
|
||||
assert entity_id is not None
|
||||
|
||||
with patch("zigpy.ota.OTA.broadcast_notify") as mock_broadcast_notify:
|
||||
await hass.services.async_call(
|
||||
HA_DOMAIN,
|
||||
SERVICE_UPDATE_ENTITY,
|
||||
service_data={ATTR_ENTITY_ID: entity_id},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_broadcast_notify.await_count == 1
|
||||
assert mock_broadcast_notify.call_args_list[0] == call(jitter=100)
|
||||
|
||||
|
||||
def make_packet(zigpy_device, cluster, cmd_name: str, **kwargs):
|
||||
"""Make a zigpy packet."""
|
||||
req_hdr, req_cmd = cluster._create_request(
|
||||
|
||||
Reference in New Issue
Block a user