mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
2025.4.3 (#143253)
This commit is contained in:
commit
6f0a9910ea
@ -120,6 +120,7 @@ class AppleTvMediaPlayer(
|
||||
"""Initialize the Apple TV media player."""
|
||||
super().__init__(name, identifier, manager)
|
||||
self._playing: Playing | None = None
|
||||
self._playing_last_updated: datetime | None = None
|
||||
self._app_list: dict[str, str] = {}
|
||||
|
||||
@callback
|
||||
@ -209,6 +210,7 @@ class AppleTvMediaPlayer(
|
||||
This is a callback function from pyatv.interface.PushListener.
|
||||
"""
|
||||
self._playing = playstatus
|
||||
self._playing_last_updated = dt_util.utcnow()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@callback
|
||||
@ -316,7 +318,7 @@ class AppleTvMediaPlayer(
|
||||
def media_position_updated_at(self) -> datetime | None:
|
||||
"""Last valid time of media position."""
|
||||
if self.state in {MediaPlayerState.PLAYING, MediaPlayerState.PAUSED}:
|
||||
return dt_util.utcnow()
|
||||
return self._playing_last_updated
|
||||
return None
|
||||
|
||||
async def async_play_media(
|
||||
|
@ -81,4 +81,7 @@ class ComelitSwitchEntity(CoordinatorEntity[ComelitSerialBridge], SwitchEntity):
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return True if switch is on."""
|
||||
return self.coordinator.data[OTHER][self._device.index].status == STATE_ON
|
||||
return (
|
||||
self.coordinator.data[self._device.type][self._device.index].status
|
||||
== STATE_ON
|
||||
)
|
||||
|
@ -8,7 +8,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["devolo_plc_api"],
|
||||
"requirements": ["devolo-plc-api==1.4.1"],
|
||||
"requirements": ["devolo-plc-api==1.5.1"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_dvl-deviceapi._tcp.local.",
|
||||
|
@ -179,22 +179,18 @@ class DukeEnergyCoordinator(DataUpdateCoordinator[None]):
|
||||
one = timedelta(days=1)
|
||||
if start_time is None:
|
||||
# Max 3 years of data
|
||||
agreement_date = dt_util.parse_datetime(meter["agreementActiveDate"])
|
||||
if agreement_date is None:
|
||||
start = dt_util.now(tz) - timedelta(days=3 * 365)
|
||||
else:
|
||||
start = max(
|
||||
agreement_date.replace(tzinfo=tz),
|
||||
dt_util.now(tz) - timedelta(days=3 * 365),
|
||||
)
|
||||
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 = end - lookback
|
||||
start_step = max(end - lookback, start)
|
||||
end_step = end
|
||||
usage: dict[datetime, dict[str, float | int]] = {}
|
||||
while True:
|
||||
|
@ -35,7 +35,7 @@ async def validate_input(data):
|
||||
lon = weather_data.lon
|
||||
|
||||
return {
|
||||
CONF_TITLE: weather_data.metadata.get("location"),
|
||||
CONF_TITLE: weather_data.metadata.location,
|
||||
CONF_STATION: weather_data.station_id,
|
||||
CONF_LATITUDE: lat,
|
||||
CONF_LONGITUDE: lon,
|
||||
|
@ -7,7 +7,7 @@ from datetime import timedelta
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from env_canada import ECAirQuality, ECRadar, ECWeather, ec_exc
|
||||
from env_canada import ECAirQuality, ECRadar, ECWeather, ECWeatherUpdateFailed, ec_exc
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@ -65,6 +65,6 @@ class ECDataUpdateCoordinator[DataT: ECDataType](DataUpdateCoordinator[DataT]):
|
||||
"""Fetch data from EC."""
|
||||
try:
|
||||
await self.ec_data.update()
|
||||
except (ET.ParseError, ec_exc.UnknownStationId) as ex:
|
||||
except (ET.ParseError, ECWeatherUpdateFailed, ec_exc.UnknownStationId) as ex:
|
||||
raise UpdateFailed(f"Error fetching {self.name} data: {ex}") from ex
|
||||
return self.ec_data
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["env_canada"],
|
||||
"requirements": ["env-canada==0.8.0"]
|
||||
"requirements": ["env-canada==0.10.1"]
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ SENSOR_TYPES: tuple[ECSensorEntityDescription, ...] = (
|
||||
key="timestamp",
|
||||
translation_key="timestamp",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda data: data.metadata.get("timestamp"),
|
||||
value_fn=lambda data: data.metadata.timestamp,
|
||||
),
|
||||
ECSensorEntityDescription(
|
||||
key="uv_index",
|
||||
@ -289,7 +289,7 @@ class ECBaseSensorEntity[DataT: ECDataType](
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._ec_data = coordinator.ec_data
|
||||
self._attr_attribution = self._ec_data.metadata["attribution"]
|
||||
self._attr_attribution = self._ec_data.metadata.attribution
|
||||
self._attr_unique_id = f"{coordinator.config_entry.title}-{description.key}"
|
||||
self._attr_device_info = coordinator.device_info
|
||||
|
||||
@ -313,8 +313,8 @@ class ECSensorEntity[DataT: ECDataType](ECBaseSensorEntity[DataT]):
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator, description)
|
||||
self._attr_extra_state_attributes = {
|
||||
ATTR_LOCATION: self._ec_data.metadata.get("location"),
|
||||
ATTR_STATION: self._ec_data.metadata.get("station"),
|
||||
ATTR_LOCATION: self._ec_data.metadata.location,
|
||||
ATTR_STATION: self._ec_data.metadata.station,
|
||||
}
|
||||
|
||||
|
||||
@ -329,8 +329,8 @@ class ECAlertSensorEntity(ECBaseSensorEntity[ECWeather]):
|
||||
return None
|
||||
|
||||
extra_state_attrs = {
|
||||
ATTR_LOCATION: self._ec_data.metadata.get("location"),
|
||||
ATTR_STATION: self._ec_data.metadata.get("station"),
|
||||
ATTR_LOCATION: self._ec_data.metadata.location,
|
||||
ATTR_STATION: self._ec_data.metadata.station,
|
||||
}
|
||||
for index, alert in enumerate(value, start=1):
|
||||
extra_state_attrs[f"alert_{index}"] = alert.get("title")
|
||||
|
@ -115,7 +115,7 @@ class ECWeatherEntity(
|
||||
"""Initialize Environment Canada weather."""
|
||||
super().__init__(coordinator)
|
||||
self.ec_data = coordinator.ec_data
|
||||
self._attr_attribution = self.ec_data.metadata["attribution"]
|
||||
self._attr_attribution = self.ec_data.metadata.attribution
|
||||
self._attr_translation_key = "forecast"
|
||||
self._attr_unique_id = _calculate_unique_id(
|
||||
coordinator.config_entry.unique_id, False
|
||||
|
@ -74,7 +74,7 @@ def build_rrule(task: TaskData) -> rrule:
|
||||
|
||||
bysetpos = None
|
||||
if rrule_frequency == MONTHLY and task.weeksOfMonth:
|
||||
bysetpos = task.weeksOfMonth
|
||||
bysetpos = [i + 1 for i in task.weeksOfMonth]
|
||||
weekdays = weekdays if weekdays else [MO]
|
||||
|
||||
return rrule(
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.69", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.70", "babel==2.15.0"]
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ class HomeConnectCoordinator(
|
||||
events = self.data[event_message_ha_id].events
|
||||
for event in event_message.data.items:
|
||||
event_key = event.key
|
||||
if event_key in SettingKey:
|
||||
if event_key in SettingKey.__members__.values(): # type: ignore[comparison-overlap]
|
||||
setting_key = SettingKey(event_key)
|
||||
if setting_key in settings:
|
||||
settings[setting_key].value = event.value
|
||||
|
@ -61,11 +61,14 @@ OPTIONS_SCHEMA = vol.Schema(
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_data_schema(hass: HomeAssistant) -> vol.Schema:
|
||||
async def _get_data_schema(hass: HomeAssistant) -> vol.Schema:
|
||||
default_location = {
|
||||
CONF_LATITUDE: hass.config.latitude,
|
||||
CONF_LONGITUDE: hass.config.longitude,
|
||||
}
|
||||
get_timezones: list[str] = list(
|
||||
await hass.async_add_executor_job(zoneinfo.available_timezones)
|
||||
)
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_DIASPORA, default=DEFAULT_DIASPORA): BooleanSelector(),
|
||||
@ -75,9 +78,7 @@ def _get_data_schema(hass: HomeAssistant) -> vol.Schema:
|
||||
vol.Optional(CONF_LOCATION, default=default_location): LocationSelector(),
|
||||
vol.Optional(CONF_ELEVATION, default=hass.config.elevation): int,
|
||||
vol.Optional(CONF_TIME_ZONE, default=hass.config.time_zone): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=sorted(zoneinfo.available_timezones()),
|
||||
)
|
||||
SelectSelectorConfig(options=get_timezones, sort=True)
|
||||
),
|
||||
}
|
||||
)
|
||||
@ -109,7 +110,7 @@ class JewishCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
_get_data_schema(self.hass), user_input
|
||||
await _get_data_schema(self.hass), user_input
|
||||
),
|
||||
)
|
||||
|
||||
@ -121,7 +122,7 @@ class JewishCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if not user_input:
|
||||
return self.async_show_form(
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
_get_data_schema(self.hass),
|
||||
await _get_data_schema(self.hass),
|
||||
reconfigure_entry.data,
|
||||
),
|
||||
step_id="reconfigure",
|
||||
|
@ -145,7 +145,10 @@ class KrakenData:
|
||||
await asyncio.sleep(CALL_RATE_LIMIT_SLEEP)
|
||||
|
||||
def _get_websocket_name_asset_pairs(self) -> str:
|
||||
return ",".join(wsname for wsname in self.tradable_asset_pairs.values())
|
||||
return ",".join(
|
||||
self.tradable_asset_pairs[tracked_pair]
|
||||
for tracked_pair in self._config_entry.options[CONF_TRACKED_ASSET_PAIRS]
|
||||
)
|
||||
|
||||
def set_update_interval(self, update_interval: int) -> None:
|
||||
"""Set the coordinator update_interval to the supplied update_interval."""
|
||||
|
@ -254,7 +254,7 @@ def _generate_device_config(
|
||||
comp_config = config[CONF_COMPONENTS]
|
||||
for platform, discover_id in mqtt_data.discovery_already_discovered:
|
||||
ids = discover_id.split(" ")
|
||||
component_node_id = ids.pop(0)
|
||||
component_node_id = f"{ids.pop(1)} {ids.pop(0)}" if len(ids) > 2 else ids.pop(0)
|
||||
component_object_id = " ".join(ids)
|
||||
if not ids:
|
||||
continue
|
||||
|
@ -371,6 +371,9 @@ def migrate_entity_ids(
|
||||
new_device_id = f"{host.unique_id}"
|
||||
else:
|
||||
new_device_id = f"{host.unique_id}_{device_uid[1]}"
|
||||
_LOGGER.debug(
|
||||
"Updating Reolink device UID from %s to %s", device_uid, new_device_id
|
||||
)
|
||||
new_identifiers = {(DOMAIN, new_device_id)}
|
||||
device_reg.async_update_device(device.id, new_identifiers=new_identifiers)
|
||||
|
||||
@ -383,6 +386,9 @@ def migrate_entity_ids(
|
||||
new_device_id = f"{host.unique_id}_{host.api.camera_uid(ch)}"
|
||||
else:
|
||||
new_device_id = f"{device_uid[0]}_{host.api.camera_uid(ch)}"
|
||||
_LOGGER.debug(
|
||||
"Updating Reolink device UID from %s to %s", device_uid, new_device_id
|
||||
)
|
||||
new_identifiers = {(DOMAIN, new_device_id)}
|
||||
existing_device = device_reg.async_get_device(identifiers=new_identifiers)
|
||||
if existing_device is None:
|
||||
@ -415,6 +421,11 @@ def migrate_entity_ids(
|
||||
host.unique_id
|
||||
):
|
||||
new_id = f"{host.unique_id}_{entity.unique_id.split('_', 1)[1]}"
|
||||
_LOGGER.debug(
|
||||
"Updating Reolink entity unique_id from %s to %s",
|
||||
entity.unique_id,
|
||||
new_id,
|
||||
)
|
||||
entity_reg.async_update_entity(entity.entity_id, new_unique_id=new_id)
|
||||
|
||||
if entity.device_id in ch_device_ids:
|
||||
@ -430,6 +441,11 @@ def migrate_entity_ids(
|
||||
continue
|
||||
if host.api.supported(ch, "UID") and id_parts[1] != host.api.camera_uid(ch):
|
||||
new_id = f"{host.unique_id}_{host.api.camera_uid(ch)}_{id_parts[2]}"
|
||||
_LOGGER.debug(
|
||||
"Updating Reolink entity unique_id from %s to %s",
|
||||
entity.unique_id,
|
||||
new_id,
|
||||
)
|
||||
existing_entity = entity_reg.async_get_entity_id(
|
||||
entity.domain, entity.platform, new_id
|
||||
)
|
||||
|
@ -19,5 +19,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["reolink_aio"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["reolink-aio==0.13.1"]
|
||||
"requirements": ["reolink-aio==0.13.2"]
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class ReolinkVODMediaSource(MediaSource):
|
||||
host = get_host(self.hass, config_entry_id)
|
||||
|
||||
def get_vod_type() -> VodRequestType:
|
||||
if filename.endswith((".mp4", ".vref")):
|
||||
if filename.endswith((".mp4", ".vref")) or host.api.is_hub:
|
||||
if host.api.is_nvr:
|
||||
return VodRequestType.DOWNLOAD
|
||||
return VodRequestType.PLAYBACK
|
||||
|
@ -79,11 +79,15 @@ def get_device_uid_and_ch(
|
||||
device: dr.DeviceEntry, host: ReolinkHost
|
||||
) -> tuple[list[str], int | None, bool]:
|
||||
"""Get the channel and the split device_uid from a reolink DeviceEntry."""
|
||||
device_uid = [
|
||||
dev_id[1].split("_") for dev_id in device.identifiers if dev_id[0] == DOMAIN
|
||||
][0]
|
||||
|
||||
device_uid = []
|
||||
is_chime = False
|
||||
|
||||
for dev_id in device.identifiers:
|
||||
if dev_id[0] == DOMAIN:
|
||||
device_uid = dev_id[1].split("_")
|
||||
if device_uid[0] == host.unique_id:
|
||||
break
|
||||
|
||||
if len(device_uid) < 2:
|
||||
# NVR itself
|
||||
ch = None
|
||||
|
@ -209,7 +209,7 @@ KELVIN_MIN_VALUE_COLOR: Final = 3000
|
||||
BLOCK_WRONG_SLEEP_PERIOD = 21600
|
||||
BLOCK_EXPECTED_SLEEP_PERIOD = 43200
|
||||
|
||||
UPTIME_DEVIATION: Final = 5
|
||||
UPTIME_DEVIATION: Final = 60
|
||||
|
||||
# Time to wait before reloading entry upon device config change
|
||||
ENTRY_RELOAD_COOLDOWN = 60
|
||||
|
@ -200,8 +200,18 @@ def get_device_uptime(uptime: float, last_uptime: datetime | None) -> datetime:
|
||||
|
||||
if (
|
||||
not last_uptime
|
||||
or abs((delta_uptime - last_uptime).total_seconds()) > UPTIME_DEVIATION
|
||||
or (diff := abs((delta_uptime - last_uptime).total_seconds()))
|
||||
> UPTIME_DEVIATION
|
||||
):
|
||||
if last_uptime:
|
||||
LOGGER.debug(
|
||||
"Time deviation %s > %s: uptime=%s, last_uptime=%s, delta_uptime=%s",
|
||||
diff,
|
||||
UPTIME_DEVIATION,
|
||||
uptime,
|
||||
last_uptime,
|
||||
delta_uptime,
|
||||
)
|
||||
return delta_uptime
|
||||
|
||||
return last_uptime
|
||||
|
@ -59,10 +59,11 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
Category.DOOR: BinarySensorDeviceClass.DOOR,
|
||||
Category.WINDOW: BinarySensorDeviceClass.WINDOW,
|
||||
},
|
||||
exists_fn=lambda key: key in {"freezer", "cooler"},
|
||||
exists_fn=lambda key: key in {"freezer", "cooler", "cvroom"},
|
||||
component_translation_key={
|
||||
"freezer": "freezer_door",
|
||||
"cooler": "cooler_door",
|
||||
"cvroom": "cool_select_plus_door",
|
||||
},
|
||||
deprecated_fn=(
|
||||
lambda status: "fridge_door"
|
||||
|
@ -23,7 +23,6 @@ from .entity import SmartThingsEntity
|
||||
MEDIA_PLAYER_CAPABILITIES = (
|
||||
Capability.AUDIO_MUTE,
|
||||
Capability.AUDIO_VOLUME,
|
||||
Capability.MEDIA_PLAYBACK,
|
||||
)
|
||||
|
||||
CONTROLLABLE_SOURCES = ["bluetooth", "wifi"]
|
||||
@ -100,27 +99,25 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
)
|
||||
|
||||
def _determine_features(self) -> MediaPlayerEntityFeature:
|
||||
flags = MediaPlayerEntityFeature(0)
|
||||
playback_commands = self.get_attribute_value(
|
||||
Capability.MEDIA_PLAYBACK, Attribute.SUPPORTED_PLAYBACK_COMMANDS
|
||||
flags = (
|
||||
MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.VOLUME_STEP
|
||||
| MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
)
|
||||
if "play" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.PLAY
|
||||
if "pause" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.PAUSE
|
||||
if "stop" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.STOP
|
||||
if "rewind" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.PREVIOUS_TRACK
|
||||
if "fastForward" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.NEXT_TRACK
|
||||
if self.supports_capability(Capability.AUDIO_VOLUME):
|
||||
flags |= (
|
||||
MediaPlayerEntityFeature.VOLUME_SET
|
||||
| MediaPlayerEntityFeature.VOLUME_STEP
|
||||
if self.supports_capability(Capability.MEDIA_PLAYBACK):
|
||||
playback_commands = self.get_attribute_value(
|
||||
Capability.MEDIA_PLAYBACK, Attribute.SUPPORTED_PLAYBACK_COMMANDS
|
||||
)
|
||||
if self.supports_capability(Capability.AUDIO_MUTE):
|
||||
flags |= MediaPlayerEntityFeature.VOLUME_MUTE
|
||||
if "play" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.PLAY
|
||||
if "pause" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.PAUSE
|
||||
if "stop" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.STOP
|
||||
if "rewind" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.PREVIOUS_TRACK
|
||||
if "fastForward" in playback_commands:
|
||||
flags |= MediaPlayerEntityFeature.NEXT_TRACK
|
||||
if self.supports_capability(Capability.SWITCH):
|
||||
flags |= (
|
||||
MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF
|
||||
@ -270,6 +267,13 @@ class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
||||
def state(self) -> MediaPlayerState | None:
|
||||
"""State of the media player."""
|
||||
if self.supports_capability(Capability.SWITCH):
|
||||
if not self.supports_capability(Capability.MEDIA_PLAYBACK):
|
||||
if (
|
||||
self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH)
|
||||
== "on"
|
||||
):
|
||||
return MediaPlayerState.ON
|
||||
return MediaPlayerState.OFF
|
||||
if self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH) == "on":
|
||||
if (
|
||||
self.source is not None
|
||||
|
@ -194,13 +194,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
deprecated=(
|
||||
lambda status: "media_player"
|
||||
if all(
|
||||
capability in status
|
||||
for capability in (
|
||||
Capability.AUDIO_MUTE,
|
||||
Capability.MEDIA_PLAYBACK,
|
||||
)
|
||||
)
|
||||
if Capability.AUDIO_MUTE in status
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
@ -48,6 +48,9 @@
|
||||
"cooler_door": {
|
||||
"name": "Cooler door"
|
||||
},
|
||||
"cool_select_plus_door": {
|
||||
"name": "CoolSelect+ door"
|
||||
},
|
||||
"remote_control": {
|
||||
"name": "Remote control"
|
||||
},
|
||||
|
@ -38,7 +38,6 @@ AC_CAPABILITIES = (
|
||||
MEDIA_PLAYER_CAPABILITIES = (
|
||||
Capability.AUDIO_MUTE,
|
||||
Capability.AUDIO_VOLUME,
|
||||
Capability.MEDIA_PLAYBACK,
|
||||
)
|
||||
|
||||
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/smhi",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pysmhi"],
|
||||
"requirements": ["pysmhi==1.0.1"]
|
||||
"requirements": ["pysmhi==1.0.2"]
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ def _build_response_apps_radios_category(
|
||||
) -> BrowseMedia:
|
||||
"""Build item for App or radio category."""
|
||||
return BrowseMedia(
|
||||
media_content_id=item.get("id", ""),
|
||||
media_content_id=item["id"],
|
||||
title=item["title"],
|
||||
media_content_type=cmd,
|
||||
media_class=browse_data.content_type_media_class[cmd]["item"],
|
||||
@ -163,7 +163,7 @@ def _build_response_known_app(
|
||||
"""Build item for app or radio."""
|
||||
|
||||
return BrowseMedia(
|
||||
media_content_id=item.get("id", ""),
|
||||
media_content_id=item["id"],
|
||||
title=item["title"],
|
||||
media_content_type=search_type,
|
||||
media_class=browse_data.content_type_media_class[search_type]["item"],
|
||||
@ -185,7 +185,7 @@ def _build_response_favorites(item: dict[str, Any]) -> BrowseMedia:
|
||||
)
|
||||
if item["hasitems"] and not item["isaudio"]:
|
||||
return BrowseMedia(
|
||||
media_content_id=item.get("id", ""),
|
||||
media_content_id=item["id"],
|
||||
title=item["title"],
|
||||
media_content_type="Favorites",
|
||||
media_class=CONTENT_TYPE_MEDIA_CLASS["Favorites"]["item"],
|
||||
@ -193,7 +193,7 @@ def _build_response_favorites(item: dict[str, Any]) -> BrowseMedia:
|
||||
can_play=False,
|
||||
)
|
||||
return BrowseMedia(
|
||||
media_content_id=item.get("id", ""),
|
||||
media_content_id=item["id"],
|
||||
title=item["title"],
|
||||
media_content_type="Favorites",
|
||||
media_class=CONTENT_TYPE_MEDIA_CLASS[MediaType.TRACK]["item"],
|
||||
@ -217,7 +217,7 @@ def _get_item_thumbnail(
|
||||
item_thumbnail = player.generate_image_url_from_track_id(artwork_track_id)
|
||||
elif item_type is not None:
|
||||
item_thumbnail = entity.get_browse_image_url(
|
||||
item_type, item.get("id", ""), artwork_track_id
|
||||
item_type, item["id"], artwork_track_id
|
||||
)
|
||||
|
||||
elif search_type in ["Apps", "Radios"]:
|
||||
@ -263,6 +263,8 @@ async def build_item_response(
|
||||
|
||||
children = []
|
||||
for item in result["items"]:
|
||||
# Force the item id to a string in case it's numeric from some lms
|
||||
item["id"] = str(item.get("id", ""))
|
||||
if search_type == "Favorites":
|
||||
child_media = _build_response_favorites(item)
|
||||
|
||||
@ -294,7 +296,7 @@ async def build_item_response(
|
||||
|
||||
elif item_type:
|
||||
child_media = BrowseMedia(
|
||||
media_content_id=str(item.get("id", "")),
|
||||
media_content_id=item["id"],
|
||||
title=item["title"],
|
||||
media_content_type=item_type,
|
||||
media_class=CONTENT_TYPE_MEDIA_CLASS[item_type]["item"],
|
||||
|
@ -113,7 +113,9 @@ SENSORS: tuple[StarlinkSensorEntityDescription, ...] = (
|
||||
translation_key="last_boot_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda data: now() - timedelta(seconds=data.status["uptime"]),
|
||||
value_fn=lambda data: (
|
||||
now() - timedelta(seconds=data.status["uptime"])
|
||||
).replace(microsecond=0),
|
||||
),
|
||||
StarlinkSensorEntityDescription(
|
||||
key="ping_drop_rate",
|
||||
|
@ -6,7 +6,12 @@ import logging
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
from tesla_fleet_api.const import Scope
|
||||
from tesla_fleet_api.exceptions import TeslaFleetError
|
||||
from tesla_fleet_api.exceptions import (
|
||||
Forbidden,
|
||||
InvalidToken,
|
||||
SubscriptionRequired,
|
||||
TeslaFleetError,
|
||||
)
|
||||
from tesla_fleet_api.tessie import Tessie
|
||||
from tessie_api import get_state_of_all_vehicles
|
||||
|
||||
@ -124,12 +129,24 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
|
||||
continue
|
||||
|
||||
api = tessie.energySites.create(site_id)
|
||||
|
||||
try:
|
||||
live_status = (await api.live_status())["response"]
|
||||
except (InvalidToken, Forbidden, SubscriptionRequired) as e:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
except TeslaFleetError as e:
|
||||
raise ConfigEntryNotReady(e.message) from e
|
||||
|
||||
energysites.append(
|
||||
TessieEnergyData(
|
||||
api=api,
|
||||
id=site_id,
|
||||
live_coordinator=TessieEnergySiteLiveCoordinator(
|
||||
hass, entry, api
|
||||
live_coordinator=(
|
||||
TessieEnergySiteLiveCoordinator(
|
||||
hass, entry, api, live_status
|
||||
)
|
||||
if isinstance(live_status, dict)
|
||||
else None
|
||||
),
|
||||
info_coordinator=TessieEnergySiteInfoCoordinator(
|
||||
hass, entry, api
|
||||
@ -147,6 +164,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
|
||||
*(
|
||||
energysite.live_coordinator.async_config_entry_first_refresh()
|
||||
for energysite in energysites
|
||||
if energysite.live_coordinator is not None
|
||||
),
|
||||
*(
|
||||
energysite.info_coordinator.async_config_entry_first_refresh()
|
||||
|
@ -191,6 +191,7 @@ async def async_setup_entry(
|
||||
TessieEnergyLiveBinarySensorEntity(energy, description)
|
||||
for energy in entry.runtime_data.energysites
|
||||
for description in ENERGY_LIVE_DESCRIPTIONS
|
||||
if energy.live_coordinator is not None
|
||||
),
|
||||
(
|
||||
TessieEnergyInfoBinarySensorEntity(vehicle, description)
|
||||
@ -233,6 +234,7 @@ class TessieEnergyLiveBinarySensorEntity(TessieEnergyEntity, BinarySensorEntity)
|
||||
) -> None:
|
||||
"""Initialize the binary sensor."""
|
||||
self.entity_description = description
|
||||
assert data.live_coordinator is not None
|
||||
super().__init__(data, data.live_coordinator, description.key)
|
||||
|
||||
def _async_update_attrs(self) -> None:
|
||||
|
@ -102,7 +102,11 @@ class TessieEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
config_entry: TessieConfigEntry
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: TessieConfigEntry, api: EnergySite
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
config_entry: TessieConfigEntry,
|
||||
api: EnergySite,
|
||||
data: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize Tessie Energy Site Live coordinator."""
|
||||
super().__init__(
|
||||
@ -114,6 +118,12 @@ class TessieEnergySiteLiveCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
)
|
||||
self.api = api
|
||||
|
||||
# Convert Wall Connectors from array to dict
|
||||
data["wall_connectors"] = {
|
||||
wc["din"]: wc for wc in (data.get("wall_connectors") or [])
|
||||
}
|
||||
self.data = data
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Update energy site data using Tessie API."""
|
||||
|
||||
|
@ -41,7 +41,9 @@ async def async_get_config_entry_diagnostics(
|
||||
]
|
||||
energysites = [
|
||||
{
|
||||
"live": async_redact_data(x.live_coordinator.data, ENERGY_LIVE_REDACT),
|
||||
"live": async_redact_data(x.live_coordinator.data, ENERGY_LIVE_REDACT)
|
||||
if x.live_coordinator
|
||||
else None,
|
||||
"info": async_redact_data(x.info_coordinator.data, ENERGY_INFO_REDACT),
|
||||
}
|
||||
for x in entry.runtime_data.energysites
|
||||
|
@ -155,7 +155,7 @@ class TessieWallConnectorEntity(TessieBaseEntity):
|
||||
via_device=(DOMAIN, str(data.id)),
|
||||
serial_number=din.split("-")[-1],
|
||||
)
|
||||
|
||||
assert data.live_coordinator
|
||||
super().__init__(data.live_coordinator, key)
|
||||
|
||||
@property
|
||||
|
@ -28,7 +28,7 @@ class TessieEnergyData:
|
||||
"""Data for a Energy Site in the Tessie integration."""
|
||||
|
||||
api: EnergySite
|
||||
live_coordinator: TessieEnergySiteLiveCoordinator
|
||||
live_coordinator: TessieEnergySiteLiveCoordinator | None
|
||||
info_coordinator: TessieEnergySiteInfoCoordinator
|
||||
id: int
|
||||
device: DeviceInfo
|
||||
|
@ -396,12 +396,16 @@ async def async_setup_entry(
|
||||
TessieEnergyLiveSensorEntity(energysite, description)
|
||||
for energysite in entry.runtime_data.energysites
|
||||
for description in ENERGY_LIVE_DESCRIPTIONS
|
||||
if description.key in energysite.live_coordinator.data
|
||||
or description.key == "percentage_charged"
|
||||
if energysite.live_coordinator is not None
|
||||
and (
|
||||
description.key in energysite.live_coordinator.data
|
||||
or description.key == "percentage_charged"
|
||||
)
|
||||
),
|
||||
( # Add wall connectors
|
||||
TessieWallConnectorSensorEntity(energysite, din, description)
|
||||
for energysite in entry.runtime_data.energysites
|
||||
if energysite.live_coordinator is not None
|
||||
for din in energysite.live_coordinator.data.get("wall_connectors", {})
|
||||
for description in WALL_CONNECTOR_DESCRIPTIONS
|
||||
),
|
||||
@ -446,6 +450,7 @@ class TessieEnergyLiveSensorEntity(TessieEnergyEntity, SensorEntity):
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self.entity_description = description
|
||||
assert data.live_coordinator is not None
|
||||
super().__init__(data, data.live_coordinator, description.key)
|
||||
|
||||
def _async_update_attrs(self) -> None:
|
||||
|
@ -163,7 +163,7 @@ class UkTransportLiveBusTimeSensor(UkTransportSensor):
|
||||
self._destination_re = re.compile(f"{bus_direction}", re.IGNORECASE)
|
||||
|
||||
sensor_name = f"Next bus to {bus_direction}"
|
||||
stop_url = f"bus/stop/{stop_atcocode}/live.json"
|
||||
stop_url = f"bus/stop/{stop_atcocode}.json"
|
||||
|
||||
UkTransportSensor.__init__(self, sensor_name, api_app_id, api_app_key, stop_url)
|
||||
self.update = Throttle(interval)(self._update)
|
||||
@ -226,7 +226,7 @@ class UkTransportLiveTrainTimeSensor(UkTransportSensor):
|
||||
self._next_trains = []
|
||||
|
||||
sensor_name = f"Next train to {calling_at}"
|
||||
query_url = f"train/station/{station_code}/live.json"
|
||||
query_url = f"train/station/{station_code}.json"
|
||||
|
||||
UkTransportSensor.__init__(
|
||||
self, sensor_name, api_app_id, api_app_key, query_url
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.69"]
|
||||
"requirements": ["holidays==0.70"]
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
"zha",
|
||||
"universal_silabs_flasher"
|
||||
],
|
||||
"requirements": ["zha==0.0.55"],
|
||||
"requirements": ["zha==0.0.56"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10C4",
|
||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 4
|
||||
PATCH_VERSION: Final = "2"
|
||||
PATCH_VERSION: Final = "3"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 0)
|
||||
|
@ -71,6 +71,19 @@ NO_ENTITIES_PROMPT = (
|
||||
"to their voice assistant in Home Assistant."
|
||||
)
|
||||
|
||||
DYNAMIC_CONTEXT_PROMPT = """You ARE equipped to answer questions about the current state of
|
||||
the home using the `GetLiveContext` tool. This is a primary function. Do not state you lack the
|
||||
functionality if the question requires live data.
|
||||
If the user asks about device existence/type (e.g., "Do I have lights in the bedroom?"): Answer
|
||||
from the static context below.
|
||||
If the user asks about the CURRENT state, value, or mode (e.g., "Is the lock locked?",
|
||||
"Is the fan on?", "What mode is the thermostat in?", "What is the temperature outside?"):
|
||||
1. Recognize this requires live data.
|
||||
2. You MUST call `GetLiveContext`. This tool will provide the needed real-time information (like temperature from the local weather, lock status, etc.).
|
||||
3. Use the tool's response** to answer the user accurately (e.g., "The temperature outside is [value from tool].").
|
||||
For general knowledge questions not about the home: Answer truthfully from internal knowledge.
|
||||
"""
|
||||
|
||||
|
||||
@callback
|
||||
def async_render_no_api_prompt(hass: HomeAssistant) -> str:
|
||||
@ -384,6 +397,8 @@ class AssistAPI(API):
|
||||
):
|
||||
prompt.append("This device is not able to start timers.")
|
||||
|
||||
prompt.append(DYNAMIC_CONTEXT_PROMPT)
|
||||
|
||||
return prompt
|
||||
|
||||
@callback
|
||||
@ -395,7 +410,7 @@ class AssistAPI(API):
|
||||
|
||||
if exposed_entities and exposed_entities["entities"]:
|
||||
prompt.append(
|
||||
"An overview of the areas and the devices in this smart home:"
|
||||
"Static Context: An overview of the areas and the devices in this smart home:"
|
||||
)
|
||||
prompt.append(yaml_util.dump(list(exposed_entities["entities"].values())))
|
||||
|
||||
@ -457,7 +472,7 @@ class AssistAPI(API):
|
||||
)
|
||||
|
||||
if exposed_domains:
|
||||
tools.append(GetHomeStateTool())
|
||||
tools.append(GetLiveContextTool())
|
||||
|
||||
return tools
|
||||
|
||||
@ -898,7 +913,7 @@ class CalendarGetEventsTool(Tool):
|
||||
return {"success": True, "result": events}
|
||||
|
||||
|
||||
class GetHomeStateTool(Tool):
|
||||
class GetLiveContextTool(Tool):
|
||||
"""Tool for getting the current state of exposed entities.
|
||||
|
||||
This returns state for all entities that have been exposed to
|
||||
@ -906,8 +921,13 @@ class GetHomeStateTool(Tool):
|
||||
returns state for entities based on intent parameters.
|
||||
"""
|
||||
|
||||
name = "get_home_state"
|
||||
description = "Get the current state of all devices in the home. "
|
||||
name = "GetLiveContext"
|
||||
description = (
|
||||
"Use this tool when the user asks a question about the CURRENT state, "
|
||||
"value, or mode of a specific device, sensor, entity, or area in the "
|
||||
"smart home, and the answer can be improved with real-time data not "
|
||||
"available in the static device overview list. "
|
||||
)
|
||||
|
||||
async def async_call(
|
||||
self,
|
||||
@ -925,7 +945,7 @@ class GetHomeStateTool(Tool):
|
||||
if not exposed_entities["entities"]:
|
||||
return {"success": False, "error": NO_ENTITIES_PROMPT}
|
||||
prompt = [
|
||||
"An overview of the areas and the devices in this smart home:",
|
||||
"Live Context: An overview of the areas and the devices in this smart home:",
|
||||
yaml_util.dump(list(exposed_entities["entities"].values())),
|
||||
]
|
||||
return {
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.4.2"
|
||||
version = "2025.4.3"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
|
12
requirements_all.txt
generated
12
requirements_all.txt
generated
@ -781,7 +781,7 @@ devialet==1.5.7
|
||||
devolo-home-control-api==0.18.3
|
||||
|
||||
# homeassistant.components.devolo_home_network
|
||||
devolo-plc-api==1.4.1
|
||||
devolo-plc-api==1.5.1
|
||||
|
||||
# homeassistant.components.chacon_dio
|
||||
dio-chacon-wifi-api==1.2.1
|
||||
@ -871,7 +871,7 @@ enocean==0.50
|
||||
enturclient==0.2.4
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.8.0
|
||||
env-canada==0.10.1
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.6
|
||||
@ -1154,7 +1154,7 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.69
|
||||
holidays==0.70
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250411.0
|
||||
@ -2325,7 +2325,7 @@ pysmartthings==3.0.4
|
||||
pysmarty2==0.10.2
|
||||
|
||||
# homeassistant.components.smhi
|
||||
pysmhi==1.0.1
|
||||
pysmhi==1.0.2
|
||||
|
||||
# homeassistant.components.edl21
|
||||
pysml==0.0.12
|
||||
@ -2627,7 +2627,7 @@ renault-api==0.2.9
|
||||
renson-endura-delta==1.7.2
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.13.1
|
||||
reolink-aio==0.13.2
|
||||
|
||||
# homeassistant.components.idteck_prox
|
||||
rfk101py==0.0.1
|
||||
@ -3152,7 +3152,7 @@ zeroconf==0.146.0
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.55
|
||||
zha==0.0.56
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.13
|
||||
|
12
requirements_test_all.txt
generated
12
requirements_test_all.txt
generated
@ -672,7 +672,7 @@ devialet==1.5.7
|
||||
devolo-home-control-api==0.18.3
|
||||
|
||||
# homeassistant.components.devolo_home_network
|
||||
devolo-plc-api==1.4.1
|
||||
devolo-plc-api==1.5.1
|
||||
|
||||
# homeassistant.components.chacon_dio
|
||||
dio-chacon-wifi-api==1.2.1
|
||||
@ -741,7 +741,7 @@ energyzero==2.1.1
|
||||
enocean==0.50
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.8.0
|
||||
env-canada==0.10.1
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.6
|
||||
@ -981,7 +981,7 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.69
|
||||
holidays==0.70
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250411.0
|
||||
@ -1895,7 +1895,7 @@ pysmartthings==3.0.4
|
||||
pysmarty2==0.10.2
|
||||
|
||||
# homeassistant.components.smhi
|
||||
pysmhi==1.0.1
|
||||
pysmhi==1.0.2
|
||||
|
||||
# homeassistant.components.edl21
|
||||
pysml==0.0.12
|
||||
@ -2128,7 +2128,7 @@ renault-api==0.2.9
|
||||
renson-endura-delta==1.7.2
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.13.1
|
||||
reolink-aio==0.13.2
|
||||
|
||||
# homeassistant.components.rflink
|
||||
rflink==0.0.66
|
||||
@ -2542,7 +2542,7 @@ zeroconf==0.146.0
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.55
|
||||
zha==0.0.56
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.62.0
|
||||
|
@ -88,6 +88,7 @@ OSI_APPROVED_LICENSES_SPDX = {
|
||||
"MPL-1.1",
|
||||
"MPL-2.0",
|
||||
"PSF-2.0",
|
||||
"Python-2.0",
|
||||
"Unlicense",
|
||||
"Zlib",
|
||||
"ZPL-2.1",
|
||||
|
@ -33,7 +33,7 @@ async def init_integration(hass: HomeAssistant, ec_data) -> MockConfigEntry:
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
weather_mock = mock_ec()
|
||||
ec_data["metadata"]["timestamp"] = datetime(2022, 10, 4, tzinfo=UTC)
|
||||
ec_data["metadata"].timestamp = datetime(2022, 10, 4, tzinfo=UTC)
|
||||
weather_mock.conditions = ec_data["conditions"]
|
||||
weather_mock.alerts = ec_data["alerts"]
|
||||
weather_mock.daily_forecasts = ec_data["daily_forecasts"]
|
||||
|
@ -4,6 +4,7 @@ import contextlib
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
from env_canada.ec_weather import MetaData
|
||||
import pytest
|
||||
|
||||
from tests.common import load_fixture
|
||||
@ -13,7 +14,7 @@ from tests.common import load_fixture
|
||||
def ec_data():
|
||||
"""Load Environment Canada data."""
|
||||
|
||||
def date_hook(weather):
|
||||
def data_hook(weather):
|
||||
"""Convert timestamp string to datetime."""
|
||||
|
||||
if t := weather.get("timestamp"):
|
||||
@ -22,9 +23,11 @@ def ec_data():
|
||||
elif t := weather.get("period"):
|
||||
with contextlib.suppress(ValueError):
|
||||
weather["period"] = datetime.fromisoformat(t)
|
||||
if t := weather.get("metadata"):
|
||||
weather["metadata"] = MetaData(**t)
|
||||
return weather
|
||||
|
||||
return json.loads(
|
||||
load_fixture("environment_canada/current_conditions_data.json"),
|
||||
object_hook=date_hook,
|
||||
object_hook=data_hook,
|
||||
)
|
||||
|
@ -30,7 +30,7 @@ def mocked_ec():
|
||||
ec_mock.lat = FAKE_CONFIG[CONF_LATITUDE]
|
||||
ec_mock.lon = FAKE_CONFIG[CONF_LONGITUDE]
|
||||
ec_mock.language = FAKE_CONFIG[CONF_LANGUAGE]
|
||||
ec_mock.metadata = {"location": FAKE_TITLE}
|
||||
ec_mock.metadata.location = FAKE_TITLE
|
||||
|
||||
ec_mock.update = AsyncMock()
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
"""Test Environment Canada diagnostics."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
@ -11,7 +10,6 @@ from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import init_integration
|
||||
|
||||
from tests.common import load_fixture
|
||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
@ -31,10 +29,6 @@ async def test_entry_diagnostics(
|
||||
) -> None:
|
||||
"""Test config entry diagnostics."""
|
||||
|
||||
ec_data = json.loads(
|
||||
load_fixture("environment_canada/current_conditions_data.json")
|
||||
)
|
||||
|
||||
config_entry = await init_integration(hass, ec_data)
|
||||
diagnostics = await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, config_entry
|
||||
|
@ -624,6 +624,49 @@
|
||||
"isDue": false,
|
||||
"id": "6e53f1f5-a315-4edd-984d-8d762e4a08ef"
|
||||
},
|
||||
{
|
||||
"repeat": {
|
||||
"m": false,
|
||||
"t": false,
|
||||
"w": false,
|
||||
"th": false,
|
||||
"f": false,
|
||||
"s": false,
|
||||
"su": true
|
||||
},
|
||||
"challenge": {},
|
||||
"group": {
|
||||
"completedBy": {},
|
||||
"assignedUsers": []
|
||||
},
|
||||
"_id": "369afeed-61e3-4bf7-9747-66e05807134c",
|
||||
"frequency": "monthly",
|
||||
"everyX": 1,
|
||||
"streak": 1,
|
||||
"nextDue": ["2024-12-14T23:00:00.000Z", "2025-01-18T23:00:00.000Z"],
|
||||
"yesterDaily": true,
|
||||
"history": [],
|
||||
"completed": false,
|
||||
"collapseChecklist": false,
|
||||
"type": "daily",
|
||||
"text": "Monatliche Finanzübersicht erstellen",
|
||||
"notes": "Setze dich einmal im Monat hin, um deine Einnahmen und Ausgaben zu überprüfen und dein Budget zu planen.",
|
||||
"tags": [],
|
||||
"value": -0.9215181434950852,
|
||||
"priority": 1,
|
||||
"attribute": "str",
|
||||
"byHabitica": false,
|
||||
"startDate": "2024-04-04T22:00:00.000Z",
|
||||
"daysOfMonth": [],
|
||||
"weeksOfMonth": [0],
|
||||
"checklist": [],
|
||||
"reminders": [],
|
||||
"createdAt": "2024-04-04T22:00:00.000Z",
|
||||
"updatedAt": "2024-04-04T22:00:00.000Z",
|
||||
"userId": "5f359083-ef78-4af0-985a-0b2c6d05797c",
|
||||
"isDue": false,
|
||||
"id": "369afeed-61e3-4bf7-9747-66e05807134c"
|
||||
},
|
||||
{
|
||||
"repeat": {
|
||||
"m": false,
|
||||
|
@ -66,7 +66,8 @@
|
||||
"564b9ac9-c53d-4638-9e7f-1cd96fe19baa",
|
||||
"f2c85972-1a19-4426-bc6d-ce3337b9d99f",
|
||||
"2c6d136c-a1c3-4bef-b7c4-fa980784b1e1",
|
||||
"6e53f1f5-a315-4edd-984d-8d762e4a08ef"
|
||||
"6e53f1f5-a315-4edd-984d-8d762e4a08ef",
|
||||
"369afeed-61e3-4bf7-9747-66e05807134c"
|
||||
],
|
||||
"habits": ["1d147de6-5c02-4740-8e2f-71d3015a37f4"]
|
||||
},
|
||||
|
@ -87,6 +87,20 @@
|
||||
'summary': 'Fitnessstudio besuchen',
|
||||
'uid': '2c6d136c-a1c3-4bef-b7c4-fa980784b1e1',
|
||||
}),
|
||||
dict({
|
||||
'description': 'Klicke um den Namen Deines aktuellen Projekts anzugeben & setze einen Terminplan!',
|
||||
'end': dict({
|
||||
'date': '2024-09-23',
|
||||
}),
|
||||
'location': None,
|
||||
'recurrence_id': None,
|
||||
'rrule': 'FREQ=MONTHLY;BYSETPOS=4;BYDAY=SU',
|
||||
'start': dict({
|
||||
'date': '2024-09-22',
|
||||
}),
|
||||
'summary': 'Arbeite an einem kreativen Projekt',
|
||||
'uid': '6e53f1f5-a315-4edd-984d-8d762e4a08ef',
|
||||
}),
|
||||
dict({
|
||||
'description': 'Klicke um Änderungen zu machen!',
|
||||
'end': dict({
|
||||
@ -563,6 +577,20 @@
|
||||
'summary': 'Fitnessstudio besuchen',
|
||||
'uid': '2c6d136c-a1c3-4bef-b7c4-fa980784b1e1',
|
||||
}),
|
||||
dict({
|
||||
'description': 'Setze dich einmal im Monat hin, um deine Einnahmen und Ausgaben zu überprüfen und dein Budget zu planen.',
|
||||
'end': dict({
|
||||
'date': '2024-10-07',
|
||||
}),
|
||||
'location': None,
|
||||
'recurrence_id': None,
|
||||
'rrule': 'FREQ=MONTHLY;BYSETPOS=1;BYDAY=SU',
|
||||
'start': dict({
|
||||
'date': '2024-10-06',
|
||||
}),
|
||||
'summary': 'Monatliche Finanzübersicht erstellen',
|
||||
'uid': '369afeed-61e3-4bf7-9747-66e05807134c',
|
||||
}),
|
||||
dict({
|
||||
'description': 'Klicke um Änderungen zu machen!',
|
||||
'end': dict({
|
||||
|
@ -1193,6 +1193,81 @@
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
'byHabitica': False,
|
||||
'challenge': dict({
|
||||
'broken': None,
|
||||
'id': None,
|
||||
'shortName': None,
|
||||
'taskId': None,
|
||||
'winner': None,
|
||||
}),
|
||||
'checklist': list([
|
||||
]),
|
||||
'collapseChecklist': False,
|
||||
'completed': False,
|
||||
'counterDown': None,
|
||||
'counterUp': None,
|
||||
'createdAt': '2024-04-04T22:00:00+00:00',
|
||||
'date': None,
|
||||
'daysOfMonth': list([
|
||||
]),
|
||||
'down': None,
|
||||
'everyX': 1,
|
||||
'frequency': 'monthly',
|
||||
'group': dict({
|
||||
'assignedDate': None,
|
||||
'assignedUsers': list([
|
||||
]),
|
||||
'assignedUsersDetail': dict({
|
||||
}),
|
||||
'assigningUsername': None,
|
||||
'completedBy': dict({
|
||||
'date': None,
|
||||
'userId': None,
|
||||
}),
|
||||
'id': None,
|
||||
'managerNotes': None,
|
||||
'taskId': None,
|
||||
}),
|
||||
'history': list([
|
||||
]),
|
||||
'id': '369afeed-61e3-4bf7-9747-66e05807134c',
|
||||
'isDue': False,
|
||||
'nextDue': list([
|
||||
'2024-12-14T23:00:00+00:00',
|
||||
'2025-01-18T23:00:00+00:00',
|
||||
]),
|
||||
'notes': 'Setze dich einmal im Monat hin, um deine Einnahmen und Ausgaben zu überprüfen und dein Budget zu planen.',
|
||||
'priority': 1,
|
||||
'reminders': list([
|
||||
]),
|
||||
'repeat': dict({
|
||||
'f': False,
|
||||
'm': False,
|
||||
's': False,
|
||||
'su': True,
|
||||
't': False,
|
||||
'th': False,
|
||||
'w': False,
|
||||
}),
|
||||
'startDate': '2024-04-04T22:00:00+00:00',
|
||||
'streak': 1,
|
||||
'tags': list([
|
||||
]),
|
||||
'text': 'Monatliche Finanzübersicht erstellen',
|
||||
'type': 'daily',
|
||||
'up': None,
|
||||
'updatedAt': '2024-04-04T22:00:00+00:00',
|
||||
'userId': '5f359083-ef78-4af0-985a-0b2c6d05797c',
|
||||
'value': -0.9215181434950852,
|
||||
'weeksOfMonth': list([
|
||||
0,
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
@ -3465,6 +3540,81 @@
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
'byHabitica': False,
|
||||
'challenge': dict({
|
||||
'broken': None,
|
||||
'id': None,
|
||||
'shortName': None,
|
||||
'taskId': None,
|
||||
'winner': None,
|
||||
}),
|
||||
'checklist': list([
|
||||
]),
|
||||
'collapseChecklist': False,
|
||||
'completed': False,
|
||||
'counterDown': None,
|
||||
'counterUp': None,
|
||||
'createdAt': '2024-04-04T22:00:00+00:00',
|
||||
'date': None,
|
||||
'daysOfMonth': list([
|
||||
]),
|
||||
'down': None,
|
||||
'everyX': 1,
|
||||
'frequency': 'monthly',
|
||||
'group': dict({
|
||||
'assignedDate': None,
|
||||
'assignedUsers': list([
|
||||
]),
|
||||
'assignedUsersDetail': dict({
|
||||
}),
|
||||
'assigningUsername': None,
|
||||
'completedBy': dict({
|
||||
'date': None,
|
||||
'userId': None,
|
||||
}),
|
||||
'id': None,
|
||||
'managerNotes': None,
|
||||
'taskId': None,
|
||||
}),
|
||||
'history': list([
|
||||
]),
|
||||
'id': '369afeed-61e3-4bf7-9747-66e05807134c',
|
||||
'isDue': False,
|
||||
'nextDue': list([
|
||||
'2024-12-14T23:00:00+00:00',
|
||||
'2025-01-18T23:00:00+00:00',
|
||||
]),
|
||||
'notes': 'Setze dich einmal im Monat hin, um deine Einnahmen und Ausgaben zu überprüfen und dein Budget zu planen.',
|
||||
'priority': 1,
|
||||
'reminders': list([
|
||||
]),
|
||||
'repeat': dict({
|
||||
'f': False,
|
||||
'm': False,
|
||||
's': False,
|
||||
'su': True,
|
||||
't': False,
|
||||
'th': False,
|
||||
'w': False,
|
||||
}),
|
||||
'startDate': '2024-04-04T22:00:00+00:00',
|
||||
'streak': 1,
|
||||
'tags': list([
|
||||
]),
|
||||
'text': 'Monatliche Finanzübersicht erstellen',
|
||||
'type': 'daily',
|
||||
'up': None,
|
||||
'updatedAt': '2024-04-04T22:00:00+00:00',
|
||||
'userId': '5f359083-ef78-4af0-985a-0b2c6d05797c',
|
||||
'value': -0.9215181434950852,
|
||||
'weeksOfMonth': list([
|
||||
0,
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
@ -4608,6 +4758,81 @@
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
'byHabitica': False,
|
||||
'challenge': dict({
|
||||
'broken': None,
|
||||
'id': None,
|
||||
'shortName': None,
|
||||
'taskId': None,
|
||||
'winner': None,
|
||||
}),
|
||||
'checklist': list([
|
||||
]),
|
||||
'collapseChecklist': False,
|
||||
'completed': False,
|
||||
'counterDown': None,
|
||||
'counterUp': None,
|
||||
'createdAt': '2024-04-04T22:00:00+00:00',
|
||||
'date': None,
|
||||
'daysOfMonth': list([
|
||||
]),
|
||||
'down': None,
|
||||
'everyX': 1,
|
||||
'frequency': 'monthly',
|
||||
'group': dict({
|
||||
'assignedDate': None,
|
||||
'assignedUsers': list([
|
||||
]),
|
||||
'assignedUsersDetail': dict({
|
||||
}),
|
||||
'assigningUsername': None,
|
||||
'completedBy': dict({
|
||||
'date': None,
|
||||
'userId': None,
|
||||
}),
|
||||
'id': None,
|
||||
'managerNotes': None,
|
||||
'taskId': None,
|
||||
}),
|
||||
'history': list([
|
||||
]),
|
||||
'id': '369afeed-61e3-4bf7-9747-66e05807134c',
|
||||
'isDue': False,
|
||||
'nextDue': list([
|
||||
'2024-12-14T23:00:00+00:00',
|
||||
'2025-01-18T23:00:00+00:00',
|
||||
]),
|
||||
'notes': 'Setze dich einmal im Monat hin, um deine Einnahmen und Ausgaben zu überprüfen und dein Budget zu planen.',
|
||||
'priority': 1,
|
||||
'reminders': list([
|
||||
]),
|
||||
'repeat': dict({
|
||||
'f': False,
|
||||
'm': False,
|
||||
's': False,
|
||||
'su': True,
|
||||
't': False,
|
||||
'th': False,
|
||||
'w': False,
|
||||
}),
|
||||
'startDate': '2024-04-04T22:00:00+00:00',
|
||||
'streak': 1,
|
||||
'tags': list([
|
||||
]),
|
||||
'text': 'Monatliche Finanzübersicht erstellen',
|
||||
'type': 'daily',
|
||||
'up': None,
|
||||
'updatedAt': '2024-04-04T22:00:00+00:00',
|
||||
'userId': '5f359083-ef78-4af0-985a-0b2c6d05797c',
|
||||
'value': -0.9215181434950852,
|
||||
'weeksOfMonth': list([
|
||||
0,
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
@ -5199,6 +5424,81 @@
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
'byHabitica': False,
|
||||
'challenge': dict({
|
||||
'broken': None,
|
||||
'id': None,
|
||||
'shortName': None,
|
||||
'taskId': None,
|
||||
'winner': None,
|
||||
}),
|
||||
'checklist': list([
|
||||
]),
|
||||
'collapseChecklist': False,
|
||||
'completed': False,
|
||||
'counterDown': None,
|
||||
'counterUp': None,
|
||||
'createdAt': '2024-04-04T22:00:00+00:00',
|
||||
'date': None,
|
||||
'daysOfMonth': list([
|
||||
]),
|
||||
'down': None,
|
||||
'everyX': 1,
|
||||
'frequency': 'monthly',
|
||||
'group': dict({
|
||||
'assignedDate': None,
|
||||
'assignedUsers': list([
|
||||
]),
|
||||
'assignedUsersDetail': dict({
|
||||
}),
|
||||
'assigningUsername': None,
|
||||
'completedBy': dict({
|
||||
'date': None,
|
||||
'userId': None,
|
||||
}),
|
||||
'id': None,
|
||||
'managerNotes': None,
|
||||
'taskId': None,
|
||||
}),
|
||||
'history': list([
|
||||
]),
|
||||
'id': '369afeed-61e3-4bf7-9747-66e05807134c',
|
||||
'isDue': False,
|
||||
'nextDue': list([
|
||||
'2024-12-14T23:00:00+00:00',
|
||||
'2025-01-18T23:00:00+00:00',
|
||||
]),
|
||||
'notes': 'Setze dich einmal im Monat hin, um deine Einnahmen und Ausgaben zu überprüfen und dein Budget zu planen.',
|
||||
'priority': 1,
|
||||
'reminders': list([
|
||||
]),
|
||||
'repeat': dict({
|
||||
'f': False,
|
||||
'm': False,
|
||||
's': False,
|
||||
'su': True,
|
||||
't': False,
|
||||
'th': False,
|
||||
'w': False,
|
||||
}),
|
||||
'startDate': '2024-04-04T22:00:00+00:00',
|
||||
'streak': 1,
|
||||
'tags': list([
|
||||
]),
|
||||
'text': 'Monatliche Finanzübersicht erstellen',
|
||||
'type': 'daily',
|
||||
'up': None,
|
||||
'updatedAt': '2024-04-04T22:00:00+00:00',
|
||||
'userId': '5f359083-ef78-4af0-985a-0b2c6d05797c',
|
||||
'value': -0.9215181434950852,
|
||||
'weeksOfMonth': list([
|
||||
0,
|
||||
]),
|
||||
'yesterDaily': True,
|
||||
}),
|
||||
dict({
|
||||
'alias': None,
|
||||
'attribute': 'str',
|
||||
|
@ -49,6 +49,13 @@
|
||||
'summary': 'Arbeite an einem kreativen Projekt',
|
||||
'uid': '6e53f1f5-a315-4edd-984d-8d762e4a08ef',
|
||||
}),
|
||||
dict({
|
||||
'description': 'Setze dich einmal im Monat hin, um deine Einnahmen und Ausgaben zu überprüfen und dein Budget zu planen.',
|
||||
'due': '2024-12-14',
|
||||
'status': 'needs_action',
|
||||
'summary': 'Monatliche Finanzübersicht erstellen',
|
||||
'uid': '369afeed-61e3-4bf7-9747-66e05807134c',
|
||||
}),
|
||||
dict({
|
||||
'description': 'Wähle eine Programmiersprache aus, die du noch nicht kennst, und lerne die Grundlagen.',
|
||||
'status': 'needs_action',
|
||||
@ -151,7 +158,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '4',
|
||||
'state': '5',
|
||||
})
|
||||
# ---
|
||||
# name: test_todos[todo.test_user_to_do_s-entry]
|
||||
|
@ -388,23 +388,181 @@ async def test_only_valid_components(
|
||||
assert not mock_dispatcher_send.called
|
||||
|
||||
|
||||
async def test_correct_config_discovery(
|
||||
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||
@pytest.mark.parametrize(
|
||||
("discovery_topic", "discovery_hash"),
|
||||
[
|
||||
("homeassistant/binary_sensor/bla/config", ("binary_sensor", "bla")),
|
||||
("homeassistant/binary_sensor/node/bla/config", ("binary_sensor", "node bla")),
|
||||
],
|
||||
ids=["without_node", "with_node"],
|
||||
)
|
||||
async def test_correct_config_discovery_component(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
discovery_topic: str,
|
||||
discovery_hash: tuple[str, str],
|
||||
) -> None:
|
||||
"""Test sending in correct JSON."""
|
||||
await mqtt_mock_entry()
|
||||
config_init = {
|
||||
"name": "Beer",
|
||||
"state_topic": "test-topic",
|
||||
"unique_id": "bla001",
|
||||
"device": {"identifiers": "0AFFD2", "name": "test_device1"},
|
||||
"o": {"name": "foobar"},
|
||||
}
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
"homeassistant/binary_sensor/bla/config",
|
||||
'{ "name": "Beer", "state_topic": "test-topic" }',
|
||||
discovery_topic,
|
||||
json.dumps(config_init),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.beer")
|
||||
state = hass.states.get("binary_sensor.test_device1_beer")
|
||||
|
||||
assert state is not None
|
||||
assert state.name == "Beer"
|
||||
assert ("binary_sensor", "bla") in hass.data["mqtt"].discovery_already_discovered
|
||||
assert state.name == "test_device1 Beer"
|
||||
assert discovery_hash in hass.data["mqtt"].discovery_already_discovered
|
||||
|
||||
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
|
||||
assert device_entry is not None
|
||||
assert device_entry.name == "test_device1"
|
||||
|
||||
# Update the device and component
|
||||
config_update = {
|
||||
"name": "Milk",
|
||||
"state_topic": "test-topic",
|
||||
"unique_id": "bla001",
|
||||
"device": {"identifiers": "0AFFD2", "name": "test_device2"},
|
||||
"o": {"name": "foobar"},
|
||||
}
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
discovery_topic,
|
||||
json.dumps(config_update),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.test_device1_beer")
|
||||
|
||||
assert state is not None
|
||||
assert state.name == "test_device2 Milk"
|
||||
assert discovery_hash in hass.data["mqtt"].discovery_already_discovered
|
||||
|
||||
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
|
||||
assert device_entry is not None
|
||||
assert device_entry.name == "test_device2"
|
||||
|
||||
# Remove the device and component
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
discovery_topic,
|
||||
"",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.test_device1_beer")
|
||||
|
||||
assert state is None
|
||||
|
||||
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
|
||||
assert device_entry is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("discovery_topic", "discovery_hash"),
|
||||
[
|
||||
("homeassistant/device/some_id/config", ("binary_sensor", "some_id bla")),
|
||||
(
|
||||
"homeassistant/device/node_id/some_id/config",
|
||||
("binary_sensor", "some_id node_id bla"),
|
||||
),
|
||||
],
|
||||
ids=["without_node", "with_node"],
|
||||
)
|
||||
async def test_correct_config_discovery_device(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
discovery_topic: str,
|
||||
discovery_hash: tuple[str, str],
|
||||
) -> None:
|
||||
"""Test sending in correct JSON."""
|
||||
await mqtt_mock_entry()
|
||||
config_init = {
|
||||
"cmps": {
|
||||
"bla": {
|
||||
"platform": "binary_sensor",
|
||||
"name": "Beer",
|
||||
"state_topic": "test-topic",
|
||||
"unique_id": "bla001",
|
||||
},
|
||||
},
|
||||
"device": {"identifiers": "0AFFD2", "name": "test_device1"},
|
||||
"o": {"name": "foobar"},
|
||||
}
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
discovery_topic,
|
||||
json.dumps(config_init),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.test_device1_beer")
|
||||
|
||||
assert state is not None
|
||||
assert state.name == "test_device1 Beer"
|
||||
assert discovery_hash in hass.data["mqtt"].discovery_already_discovered
|
||||
|
||||
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
|
||||
assert device_entry is not None
|
||||
assert device_entry.name == "test_device1"
|
||||
|
||||
# Update the device and component
|
||||
config_update = {
|
||||
"cmps": {
|
||||
"bla": {
|
||||
"platform": "binary_sensor",
|
||||
"name": "Milk",
|
||||
"state_topic": "test-topic",
|
||||
"unique_id": "bla001",
|
||||
},
|
||||
},
|
||||
"device": {"identifiers": "0AFFD2", "name": "test_device2"},
|
||||
"o": {"name": "foobar"},
|
||||
}
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
discovery_topic,
|
||||
json.dumps(config_update),
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.test_device1_beer")
|
||||
|
||||
assert state is not None
|
||||
assert state.name == "test_device2 Milk"
|
||||
assert discovery_hash in hass.data["mqtt"].discovery_already_discovered
|
||||
|
||||
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
|
||||
assert device_entry is not None
|
||||
assert device_entry.name == "test_device2"
|
||||
|
||||
# Remove the device and component
|
||||
async_fire_mqtt_message(
|
||||
hass,
|
||||
discovery_topic,
|
||||
"",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.test_device1_beer")
|
||||
|
||||
assert state is None
|
||||
|
||||
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
|
||||
assert device_entry is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -77,6 +77,7 @@ def reolink_connect_class() -> Generator[MagicMock]:
|
||||
host_mock.check_new_firmware.return_value = False
|
||||
host_mock.unsubscribe.return_value = True
|
||||
host_mock.logout.return_value = True
|
||||
host_mock.is_nvr = True
|
||||
host_mock.is_hub = False
|
||||
host_mock.mac_address = TEST_MAC
|
||||
host_mock.uid = TEST_UID
|
||||
|
@ -23,15 +23,21 @@ from homeassistant.components.number import (
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.components.reolink.const import DOMAIN
|
||||
from homeassistant.components.reolink.util import get_device_uid_and_ch
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .conftest import TEST_NVR_NAME
|
||||
from .conftest import TEST_NVR_NAME, TEST_UID, TEST_UID_CAM
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
DEV_ID_NVR = f"{TEST_UID}_{TEST_UID_CAM}"
|
||||
DEV_ID_STANDALONE_CAM = f"{TEST_UID_CAM}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect", "expected"),
|
||||
@ -123,3 +129,36 @@ async def test_try_function(
|
||||
assert err.value.translation_key == expected.translation_key
|
||||
|
||||
reolink_connect.set_volume.reset_mock(side_effect=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("identifiers"),
|
||||
[
|
||||
({(DOMAIN, DEV_ID_NVR), (DOMAIN, DEV_ID_STANDALONE_CAM)}),
|
||||
({(DOMAIN, DEV_ID_STANDALONE_CAM), (DOMAIN, DEV_ID_NVR)}),
|
||||
],
|
||||
)
|
||||
async def test_get_device_uid_and_ch(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
reolink_connect: MagicMock,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
identifiers: set[tuple[str, str]],
|
||||
) -> None:
|
||||
"""Test get_device_uid_and_ch with multiple identifiers."""
|
||||
reolink_connect.channels = [0]
|
||||
|
||||
dev_entry = device_registry.async_get_or_create(
|
||||
identifiers=identifiers,
|
||||
config_entry_id=config_entry.entry_id,
|
||||
disabled_by=None,
|
||||
)
|
||||
|
||||
# setup CH 0 and host entities/device
|
||||
with patch("homeassistant.components.reolink.PLATFORMS", [Platform.SWITCH]):
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = get_device_uid_and_ch(dev_entry, config_entry.runtime_data.host)
|
||||
# always get the uid and channel form the DEV_ID_NVR since is_nvr = True
|
||||
assert result == ([TEST_UID, TEST_UID_CAM], 0, False)
|
||||
|
@ -21,6 +21,7 @@ from homeassistant.components.shelly.const import (
|
||||
GEN1_RELEASE_URL,
|
||||
GEN2_BETA_RELEASE_URL,
|
||||
GEN2_RELEASE_URL,
|
||||
UPTIME_DEVIATION,
|
||||
)
|
||||
from homeassistant.components.shelly.utils import (
|
||||
get_block_channel_name,
|
||||
@ -188,8 +189,9 @@ async def test_get_device_uptime() -> None:
|
||||
) == dt_util.as_utc(dt_util.parse_datetime("2019-01-10 18:42:00+00:00"))
|
||||
|
||||
assert get_device_uptime(
|
||||
50, dt_util.as_utc(dt_util.parse_datetime("2019-01-10 18:42:00+00:00"))
|
||||
) == dt_util.as_utc(dt_util.parse_datetime("2019-01-10 18:42:10+00:00"))
|
||||
55 - UPTIME_DEVIATION,
|
||||
dt_util.as_utc(dt_util.parse_datetime("2019-01-10 18:42:00+00:00")),
|
||||
) == dt_util.as_utc(dt_util.parse_datetime("2019-01-10 18:43:05+00:00"))
|
||||
|
||||
|
||||
async def test_get_block_input_triggers(
|
||||
|
@ -107,7 +107,9 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
||||
"centralite",
|
||||
"da_ref_normal_000001",
|
||||
"da_ref_normal_01011",
|
||||
"da_ref_normal_01001",
|
||||
"vd_network_audio_002s",
|
||||
"vd_network_audio_003s",
|
||||
"vd_sensor_light_2023",
|
||||
"iphone",
|
||||
"da_sac_ehs_000001_sub",
|
||||
|
@ -0,0 +1,929 @@
|
||||
{
|
||||
"components": {
|
||||
"pantry-01": {
|
||||
"samsungce.foodDefrost": {
|
||||
"supportedOptions": {
|
||||
"value": null
|
||||
},
|
||||
"foodType": {
|
||||
"value": null
|
||||
},
|
||||
"weight": {
|
||||
"value": null
|
||||
},
|
||||
"operationTime": {
|
||||
"value": null
|
||||
},
|
||||
"remainingTime": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.fridgePantryInfo": {
|
||||
"name": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": ["samsungce.meatAging", "samsungce.foodDefrost"],
|
||||
"timestamp": "2022-02-07T10:54:05.580Z"
|
||||
}
|
||||
},
|
||||
"samsungce.meatAging": {
|
||||
"zoneInfo": {
|
||||
"value": null
|
||||
},
|
||||
"supportedMeatTypes": {
|
||||
"value": null
|
||||
},
|
||||
"supportedAgingMethods": {
|
||||
"value": null
|
||||
},
|
||||
"status": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.fridgePantryMode": {
|
||||
"mode": {
|
||||
"value": null
|
||||
},
|
||||
"supportedModes": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"icemaker": {
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [],
|
||||
"timestamp": "2022-02-07T10:54:05.580Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": "on",
|
||||
"timestamp": "2025-02-07T12:01:52.528Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scale-10": {
|
||||
"samsungce.connectionState": {
|
||||
"connectionState": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [],
|
||||
"timestamp": "2022-02-07T10:54:05.580Z"
|
||||
}
|
||||
},
|
||||
"samsungce.weightMeasurement": {
|
||||
"weight": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.scaleSettings": {
|
||||
"enabled": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.weightMeasurementCalibration": {}
|
||||
},
|
||||
"scale-11": {
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [],
|
||||
"timestamp": "2022-02-07T10:54:05.580Z"
|
||||
}
|
||||
},
|
||||
"samsungce.weightMeasurement": {
|
||||
"weight": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"camera-01": {
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": ["switch"],
|
||||
"timestamp": "2023-12-17T11:19:18.845Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"cooler": {
|
||||
"contactSensor": {
|
||||
"contact": {
|
||||
"value": "closed",
|
||||
"timestamp": "2025-02-09T00:23:41.655Z"
|
||||
}
|
||||
},
|
||||
"samsungce.unavailableCapabilities": {
|
||||
"unavailableCommands": {
|
||||
"value": [],
|
||||
"timestamp": "2024-11-06T12:35:50.411Z"
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [],
|
||||
"timestamp": "2024-06-17T06:16:33.918Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 37,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
}
|
||||
},
|
||||
"custom.thermostatSetpointControl": {
|
||||
"minimumSetpoint": {
|
||||
"value": 34,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
},
|
||||
"maximumSetpoint": {
|
||||
"value": 44,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
}
|
||||
},
|
||||
"thermostatCoolingSetpoint": {
|
||||
"coolingSetpointRange": {
|
||||
"value": {
|
||||
"minimum": 34,
|
||||
"maximum": 44,
|
||||
"step": 1
|
||||
},
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
},
|
||||
"coolingSetpoint": {
|
||||
"value": 37,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"freezer": {
|
||||
"contactSensor": {
|
||||
"contact": {
|
||||
"value": "closed",
|
||||
"timestamp": "2025-02-09T00:00:44.267Z"
|
||||
}
|
||||
},
|
||||
"samsungce.unavailableCapabilities": {
|
||||
"unavailableCommands": {
|
||||
"value": [],
|
||||
"timestamp": "2024-11-06T12:35:50.411Z"
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": ["samsungce.freezerConvertMode"],
|
||||
"timestamp": "2024-11-06T09:00:29.743Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": 0,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
}
|
||||
},
|
||||
"custom.thermostatSetpointControl": {
|
||||
"minimumSetpoint": {
|
||||
"value": -8,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
},
|
||||
"maximumSetpoint": {
|
||||
"value": 5,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
}
|
||||
},
|
||||
"samsungce.freezerConvertMode": {
|
||||
"supportedFreezerConvertModes": {
|
||||
"value": [],
|
||||
"timestamp": "2025-02-01T19:39:00.448Z"
|
||||
},
|
||||
"freezerConvertMode": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"thermostatCoolingSetpoint": {
|
||||
"coolingSetpointRange": {
|
||||
"value": {
|
||||
"minimum": -8,
|
||||
"maximum": 5,
|
||||
"step": 1
|
||||
},
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
},
|
||||
"coolingSetpoint": {
|
||||
"value": 0,
|
||||
"unit": "F",
|
||||
"timestamp": "2025-02-01T19:39:00.493Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": {
|
||||
"contactSensor": {
|
||||
"contact": {
|
||||
"value": "closed",
|
||||
"timestamp": "2025-02-09T00:23:41.655Z"
|
||||
}
|
||||
},
|
||||
"samsungce.viewInside": {
|
||||
"supportedFocusAreas": {
|
||||
"value": ["mainShelves"],
|
||||
"timestamp": "2025-02-01T19:39:00.946Z"
|
||||
},
|
||||
"contents": {
|
||||
"value": [
|
||||
{
|
||||
"fileId": "d3e1f875-f8b3-a031-737b-366eaa227773",
|
||||
"mimeType": "image/jpeg",
|
||||
"expiredTime": "2025-01-20T16:17:04Z",
|
||||
"focusArea": "mainShelves"
|
||||
},
|
||||
{
|
||||
"fileId": "9fccb6b4-e71f-6c7f-9935-f6082bb6ccfe",
|
||||
"mimeType": "image/jpeg",
|
||||
"expiredTime": "2025-01-20T16:17:04Z",
|
||||
"focusArea": "mainShelves"
|
||||
},
|
||||
{
|
||||
"fileId": "20b57a4d-b7fc-17fc-3a03-0fb84fb4efab",
|
||||
"mimeType": "image/jpeg",
|
||||
"expiredTime": "2025-01-20T16:17:05Z",
|
||||
"focusArea": "mainShelves"
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-01-20T16:07:05.423Z"
|
||||
},
|
||||
"lastUpdatedTime": {
|
||||
"value": "2025-02-07T12:01:52Z",
|
||||
"timestamp": "2025-02-07T12:01:52.585Z"
|
||||
}
|
||||
},
|
||||
"samsungce.fridgeFoodList": {
|
||||
"outOfSyncChanges": {
|
||||
"value": null
|
||||
},
|
||||
"refreshResult": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.deviceIdentification": {
|
||||
"micomAssayCode": {
|
||||
"value": null
|
||||
},
|
||||
"modelName": {
|
||||
"value": null
|
||||
},
|
||||
"serialNumber": {
|
||||
"value": null
|
||||
},
|
||||
"serialNumberExtra": {
|
||||
"value": null
|
||||
},
|
||||
"modelClassificationCode": {
|
||||
"value": null
|
||||
},
|
||||
"description": {
|
||||
"value": null
|
||||
},
|
||||
"releaseYear": {
|
||||
"value": 19,
|
||||
"timestamp": "2024-11-06T09:00:29.743Z"
|
||||
},
|
||||
"binaryId": {
|
||||
"value": "24K_REF_LCD_FHUB9.0",
|
||||
"timestamp": "2025-02-07T12:01:53.067Z"
|
||||
}
|
||||
},
|
||||
"samsungce.quickControl": {
|
||||
"version": {
|
||||
"value": "1.0",
|
||||
"timestamp": "2025-02-01T19:39:01.848Z"
|
||||
}
|
||||
},
|
||||
"custom.fridgeMode": {
|
||||
"fridgeModeValue": {
|
||||
"value": null
|
||||
},
|
||||
"fridgeMode": {
|
||||
"value": null
|
||||
},
|
||||
"supportedFridgeModes": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"ocf": {
|
||||
"st": {
|
||||
"value": "2024-11-08T11:56:59Z",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mndt": {
|
||||
"value": "",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnfv": {
|
||||
"value": "20240616.213423",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnhw": {
|
||||
"value": "",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"di": {
|
||||
"value": "7d3feb98-8a36-4351-c362-5e21ad3a78dd",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnsl": {
|
||||
"value": "",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"dmv": {
|
||||
"value": "res.1.1.0,sh.1.1.0",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"n": {
|
||||
"value": "Family Hub",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnmo": {
|
||||
"value": "24K_REF_LCD_FHUB9.0|00113141|0002034e051324200103000000000000",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"vid": {
|
||||
"value": "DA-REF-NORMAL-01001",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnmn": {
|
||||
"value": "Samsung Electronics",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnml": {
|
||||
"value": "",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnpv": {
|
||||
"value": "7.0",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"mnos": {
|
||||
"value": "Tizen",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"pi": {
|
||||
"value": "7d3feb98-8a36-4351-c362-5e21ad3a78dd",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
},
|
||||
"icv": {
|
||||
"value": "core.1.1.0",
|
||||
"timestamp": "2025-01-02T12:37:43.756Z"
|
||||
}
|
||||
},
|
||||
"samsungce.fridgeVacationMode": {
|
||||
"vacationMode": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [
|
||||
"thermostatCoolingSetpoint",
|
||||
"temperatureMeasurement",
|
||||
"custom.fridgeMode",
|
||||
"custom.deviceReportStateConfiguration",
|
||||
"samsungce.fridgeFoodList",
|
||||
"samsungce.runestoneHomeContext",
|
||||
"demandResponseLoadControl",
|
||||
"samsungce.fridgeVacationMode",
|
||||
"samsungce.sabbathMode"
|
||||
],
|
||||
"timestamp": "2025-02-08T23:57:45.739Z"
|
||||
}
|
||||
},
|
||||
"samsungce.driverVersion": {
|
||||
"versionNumber": {
|
||||
"value": 24090102,
|
||||
"timestamp": "2024-11-06T09:00:29.743Z"
|
||||
}
|
||||
},
|
||||
"sec.diagnosticsInformation": {
|
||||
"logType": {
|
||||
"value": ["errCode", "dump"],
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"endpoint": {
|
||||
"value": "SSM",
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"minVersion": {
|
||||
"value": "1.0",
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"signinPermission": {
|
||||
"value": null
|
||||
},
|
||||
"setupId": {
|
||||
"value": "500",
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"protocolType": {
|
||||
"value": "wifi_https",
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"tsId": {
|
||||
"value": null
|
||||
},
|
||||
"mnId": {
|
||||
"value": "0AJT",
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"dumpType": {
|
||||
"value": "file",
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
}
|
||||
},
|
||||
"temperatureMeasurement": {
|
||||
"temperatureRange": {
|
||||
"value": null
|
||||
},
|
||||
"temperature": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"custom.deviceReportStateConfiguration": {
|
||||
"reportStateRealtimePeriod": {
|
||||
"value": null
|
||||
},
|
||||
"reportStateRealtime": {
|
||||
"value": {
|
||||
"state": "disabled"
|
||||
},
|
||||
"timestamp": "2025-02-01T19:39:00.345Z"
|
||||
},
|
||||
"reportStatePeriod": {
|
||||
"value": "enabled",
|
||||
"timestamp": "2025-02-01T19:39:00.345Z"
|
||||
}
|
||||
},
|
||||
"thermostatCoolingSetpoint": {
|
||||
"coolingSetpointRange": {
|
||||
"value": null
|
||||
},
|
||||
"coolingSetpoint": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"custom.disabledComponents": {
|
||||
"disabledComponents": {
|
||||
"value": [
|
||||
"icemaker-02",
|
||||
"icemaker-03",
|
||||
"pantry-01",
|
||||
"camera-01",
|
||||
"scale-10",
|
||||
"scale-11"
|
||||
],
|
||||
"timestamp": "2025-02-07T12:01:52.638Z"
|
||||
}
|
||||
},
|
||||
"demandResponseLoadControl": {
|
||||
"drlcStatus": {
|
||||
"value": {
|
||||
"drlcType": 1,
|
||||
"drlcLevel": 0,
|
||||
"duration": 0,
|
||||
"override": false
|
||||
},
|
||||
"timestamp": "2025-02-01T19:38:59.899Z"
|
||||
}
|
||||
},
|
||||
"samsungce.sabbathMode": {
|
||||
"supportedActions": {
|
||||
"value": null
|
||||
},
|
||||
"status": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"powerConsumptionReport": {
|
||||
"powerConsumption": {
|
||||
"value": {
|
||||
"energy": 4381422,
|
||||
"deltaEnergy": 27,
|
||||
"power": 144,
|
||||
"powerEnergy": 27.01890500307083,
|
||||
"persistedEnergy": 0,
|
||||
"energySaved": 0,
|
||||
"start": "2025-02-09T00:13:39Z",
|
||||
"end": "2025-02-09T00:25:23Z"
|
||||
},
|
||||
"timestamp": "2025-02-09T00:25:23.843Z"
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"samsungce.runestoneHomeContext": {
|
||||
"supportedContexts": {
|
||||
"value": [
|
||||
{
|
||||
"context": "HOME_IN",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "ASLEEP",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "AWAKE",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "COOKING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_COOKING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "EATING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_EATING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "DOING_LAUNDRY",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_DOING_LAUNDRY",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "CLEANING_HOUSE",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_CLEANING_HOUSE",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "MUSIC_LISTENING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_MUSIC_LISTENING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "AIR_CONDITIONING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_AIR_CONDITIONING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "WASHING_DISHES",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_WASHING_DISHES",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "CARING_CLOTHING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_CARING_CLOTHING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "WATCHING_TV",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_WATCHING_TV",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "BEFORE_BEDTIME",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "BEFORE_COOKING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "BEFORE_HOME_OUT",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "ORDERING_DELIVERY_FOOD",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_ORDERING_DELIVERY_FOOD",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "ONLINE_GROCERY_SHOPPING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
},
|
||||
{
|
||||
"context": "FINISH_ONLINE_GROCERY_SHOPPING",
|
||||
"place": "HOME",
|
||||
"startTime": "99:99",
|
||||
"endTime": "99:99"
|
||||
}
|
||||
],
|
||||
"timestamp": "2025-02-01T19:39:02.150Z"
|
||||
}
|
||||
},
|
||||
"execute": {
|
||||
"data": {
|
||||
"value": {
|
||||
"payload": {
|
||||
"rt": ["x.com.samsung.da.fridge"],
|
||||
"if": ["oic.if.a"],
|
||||
"x.com.samsung.da.rapidFridge": "Off",
|
||||
"x.com.samsung.da.rapidFreezing": "Off"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"href": "/refrigeration/vs/0"
|
||||
},
|
||||
"timestamp": "2024-03-26T09:06:17.169Z"
|
||||
}
|
||||
},
|
||||
"sec.wifiConfiguration": {
|
||||
"autoReconnection": {
|
||||
"value": true,
|
||||
"timestamp": "2025-02-01T19:39:01.951Z"
|
||||
},
|
||||
"minVersion": {
|
||||
"value": "1.0",
|
||||
"timestamp": "2025-02-01T19:39:01.951Z"
|
||||
},
|
||||
"supportedWiFiFreq": {
|
||||
"value": ["2.4G", "5G"],
|
||||
"timestamp": "2025-02-01T19:39:01.951Z"
|
||||
},
|
||||
"supportedAuthType": {
|
||||
"value": ["OPEN", "WEP", "WPA-PSK", "WPA2-PSK"],
|
||||
"timestamp": "2025-02-01T19:39:01.951Z"
|
||||
},
|
||||
"protocolType": {
|
||||
"value": ["helper_hotspot"],
|
||||
"timestamp": "2025-02-01T19:39:01.951Z"
|
||||
}
|
||||
},
|
||||
"refrigeration": {
|
||||
"defrost": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-02-01T19:38:59.276Z"
|
||||
},
|
||||
"rapidCooling": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-02-01T19:39:00.497Z"
|
||||
},
|
||||
"rapidFreezing": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-02-01T19:39:00.497Z"
|
||||
}
|
||||
},
|
||||
"samsungce.powerCool": {
|
||||
"activated": {
|
||||
"value": false,
|
||||
"timestamp": "2025-02-01T19:39:00.497Z"
|
||||
}
|
||||
},
|
||||
"custom.energyType": {
|
||||
"energyType": {
|
||||
"value": "2.0",
|
||||
"timestamp": "2022-02-07T10:54:05.580Z"
|
||||
},
|
||||
"energySavingSupport": {
|
||||
"value": false,
|
||||
"timestamp": "2022-02-07T10:57:35.490Z"
|
||||
},
|
||||
"drMaxDuration": {
|
||||
"value": 1440,
|
||||
"unit": "min",
|
||||
"timestamp": "2022-02-07T11:50:40.228Z"
|
||||
},
|
||||
"energySavingLevel": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingInfo": {
|
||||
"value": null
|
||||
},
|
||||
"supportedEnergySavingLevels": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingOperation": {
|
||||
"value": null
|
||||
},
|
||||
"notificationTemplateID": {
|
||||
"value": null
|
||||
},
|
||||
"energySavingOperationSupport": {
|
||||
"value": false,
|
||||
"timestamp": "2022-02-07T11:50:40.228Z"
|
||||
}
|
||||
},
|
||||
"samsungce.softwareUpdate": {
|
||||
"targetModule": {
|
||||
"value": {},
|
||||
"timestamp": "2025-02-01T19:39:00.200Z"
|
||||
},
|
||||
"otnDUID": {
|
||||
"value": "2DCEZFTFQZPMO",
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"lastUpdatedDate": {
|
||||
"value": null
|
||||
},
|
||||
"availableModules": {
|
||||
"value": [],
|
||||
"timestamp": "2025-02-01T19:39:00.523Z"
|
||||
},
|
||||
"newVersionAvailable": {
|
||||
"value": false,
|
||||
"timestamp": "2025-02-01T19:39:00.200Z"
|
||||
},
|
||||
"operatingState": {
|
||||
"value": null
|
||||
},
|
||||
"progress": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungce.powerFreeze": {
|
||||
"activated": {
|
||||
"value": false,
|
||||
"timestamp": "2025-02-01T19:39:00.497Z"
|
||||
}
|
||||
},
|
||||
"custom.waterFilter": {
|
||||
"waterFilterUsageStep": {
|
||||
"value": 1,
|
||||
"timestamp": "2025-02-01T19:38:59.973Z"
|
||||
},
|
||||
"waterFilterResetType": {
|
||||
"value": ["replaceable"],
|
||||
"timestamp": "2025-02-01T19:38:59.973Z"
|
||||
},
|
||||
"waterFilterCapacity": {
|
||||
"value": null
|
||||
},
|
||||
"waterFilterLastResetDate": {
|
||||
"value": null
|
||||
},
|
||||
"waterFilterUsage": {
|
||||
"value": 52,
|
||||
"timestamp": "2025-02-08T05:06:45.769Z"
|
||||
},
|
||||
"waterFilterStatus": {
|
||||
"value": "normal",
|
||||
"timestamp": "2025-02-01T19:38:59.973Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"cvroom": {
|
||||
"custom.fridgeMode": {
|
||||
"fridgeModeValue": {
|
||||
"value": null
|
||||
},
|
||||
"fridgeMode": {
|
||||
"value": "CV_FDR_DELI",
|
||||
"timestamp": "2025-02-01T19:39:00.448Z"
|
||||
},
|
||||
"supportedFridgeModes": {
|
||||
"value": [
|
||||
"CV_FDR_WINE",
|
||||
"CV_FDR_DELI",
|
||||
"CV_FDR_BEVERAGE",
|
||||
"CV_FDR_MEAT"
|
||||
],
|
||||
"timestamp": "2025-02-01T19:39:00.448Z"
|
||||
}
|
||||
},
|
||||
"contactSensor": {
|
||||
"contact": {
|
||||
"value": "closed",
|
||||
"timestamp": "2025-02-08T23:22:04.631Z"
|
||||
}
|
||||
},
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [],
|
||||
"timestamp": "2021-07-27T01:19:43.145Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icemaker-02": {
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [],
|
||||
"timestamp": "2022-07-28T18:47:07.039Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"icemaker-03": {
|
||||
"custom.disabledCapabilities": {
|
||||
"disabledCapabilities": {
|
||||
"value": [],
|
||||
"timestamp": "2023-12-15T01:05:09.803Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,231 @@
|
||||
{
|
||||
"components": {
|
||||
"main": {
|
||||
"samsungvd.soundFrom": {
|
||||
"mode": {
|
||||
"value": 29,
|
||||
"timestamp": "2025-04-05T13:51:47.865Z"
|
||||
},
|
||||
"detailName": {
|
||||
"value": "None",
|
||||
"timestamp": "2025-04-05T13:51:50.230Z"
|
||||
}
|
||||
},
|
||||
"audioVolume": {
|
||||
"volume": {
|
||||
"value": 6,
|
||||
"unit": "%",
|
||||
"timestamp": "2025-04-17T11:17:25.272Z"
|
||||
}
|
||||
},
|
||||
"samsungvd.audioGroupInfo": {
|
||||
"role": {
|
||||
"value": null
|
||||
},
|
||||
"channel": {
|
||||
"value": null
|
||||
},
|
||||
"status": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"refresh": {},
|
||||
"audioNotification": {},
|
||||
"execute": {
|
||||
"data": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"samsungvd.audioInputSource": {
|
||||
"supportedInputSources": {
|
||||
"value": ["D.IN", "BT", "WIFI"],
|
||||
"timestamp": "2025-03-18T19:11:54.071Z"
|
||||
},
|
||||
"inputSource": {
|
||||
"value": "D.IN",
|
||||
"timestamp": "2025-04-17T11:18:02.048Z"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"switch": {
|
||||
"value": "off",
|
||||
"timestamp": "2025-04-17T14:42:04.704Z"
|
||||
}
|
||||
},
|
||||
"sec.wifiConfiguration": {
|
||||
"autoReconnection": {
|
||||
"value": true,
|
||||
"timestamp": "2025-03-18T19:11:54.484Z"
|
||||
},
|
||||
"minVersion": {
|
||||
"value": "1.0",
|
||||
"timestamp": "2025-03-18T19:11:54.484Z"
|
||||
},
|
||||
"supportedWiFiFreq": {
|
||||
"value": ["2.4G", "5G"],
|
||||
"timestamp": "2025-03-18T19:11:54.484Z"
|
||||
},
|
||||
"supportedAuthType": {
|
||||
"value": [
|
||||
"OPEN",
|
||||
"WEP",
|
||||
"WPA-PSK",
|
||||
"WPA2-PSK",
|
||||
"EAP",
|
||||
"SAE",
|
||||
"OWE",
|
||||
"FT-PSK"
|
||||
],
|
||||
"timestamp": "2025-03-18T19:11:54.484Z"
|
||||
},
|
||||
"protocolType": {
|
||||
"value": ["ble_ocf"],
|
||||
"timestamp": "2025-03-18T19:11:54.484Z"
|
||||
}
|
||||
},
|
||||
"ocf": {
|
||||
"st": {
|
||||
"value": "1970-01-01T00:00:47Z",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"mndt": {
|
||||
"value": "2024-01-01",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"mnfv": {
|
||||
"value": "SAT-MT8532D24WWC-1016.0",
|
||||
"timestamp": "2025-02-21T16:47:38.134Z"
|
||||
},
|
||||
"mnhw": {
|
||||
"value": "",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"di": {
|
||||
"value": "a75cb1e1-03fd-3c77-ca9f-d4e56c4096c6",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"mnsl": {
|
||||
"value": "",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"dmv": {
|
||||
"value": "res.1.1.0,sh.1.1.0",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"n": {
|
||||
"value": "Soundbar",
|
||||
"timestamp": "2025-02-21T16:47:38.134Z"
|
||||
},
|
||||
"mnmo": {
|
||||
"value": "HW-S60D",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"vid": {
|
||||
"value": "VD-NetworkAudio-003S",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"mnmn": {
|
||||
"value": "Samsung Electronics",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"mnml": {
|
||||
"value": "",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"mnpv": {
|
||||
"value": "8.0",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"mnos": {
|
||||
"value": "Tizen",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"pi": {
|
||||
"value": "a75cb1e1-03fd-3c77-ca9f-d4e56c4096c6",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
},
|
||||
"icv": {
|
||||
"value": "core.1.1.0",
|
||||
"timestamp": "2025-02-21T15:09:52.348Z"
|
||||
}
|
||||
},
|
||||
"samsungvd.supportsFeatures": {
|
||||
"mediaOutputSupported": {
|
||||
"value": null
|
||||
},
|
||||
"imeAdvSupported": {
|
||||
"value": null
|
||||
},
|
||||
"wifiUpdateSupport": {
|
||||
"value": true,
|
||||
"timestamp": "2025-03-18T19:11:53.853Z"
|
||||
},
|
||||
"executableServiceList": {
|
||||
"value": null
|
||||
},
|
||||
"remotelessSupported": {
|
||||
"value": null
|
||||
},
|
||||
"artSupported": {
|
||||
"value": null
|
||||
},
|
||||
"mobileCamSupported": {
|
||||
"value": null
|
||||
}
|
||||
},
|
||||
"sec.diagnosticsInformation": {
|
||||
"logType": {
|
||||
"value": ["errCode", "dump"],
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
},
|
||||
"endpoint": {
|
||||
"value": "PIPER",
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
},
|
||||
"minVersion": {
|
||||
"value": "3.0",
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
},
|
||||
"signinPermission": {
|
||||
"value": null
|
||||
},
|
||||
"setupId": {
|
||||
"value": "301",
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
},
|
||||
"protocolType": {
|
||||
"value": "ble_ocf",
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
},
|
||||
"tsId": {
|
||||
"value": "VD02",
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
},
|
||||
"mnId": {
|
||||
"value": "0AJK",
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
},
|
||||
"dumpType": {
|
||||
"value": "file",
|
||||
"timestamp": "2025-03-18T19:11:54.336Z"
|
||||
}
|
||||
},
|
||||
"audioMute": {
|
||||
"mute": {
|
||||
"value": "muted",
|
||||
"timestamp": "2025-04-17T11:36:04.814Z"
|
||||
}
|
||||
},
|
||||
"samsungvd.thingStatus": {
|
||||
"updatedTime": {
|
||||
"value": 1744900925,
|
||||
"timestamp": "2025-04-17T14:42:04.770Z"
|
||||
},
|
||||
"status": {
|
||||
"value": "Idle",
|
||||
"timestamp": "2025-03-18T19:11:54.101Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,433 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "7d3feb98-8a36-4351-c362-5e21ad3a78dd",
|
||||
"name": "Family Hub",
|
||||
"label": "Refrigerator",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"presentationId": "DA-REF-NORMAL-01001",
|
||||
"deviceManufacturerCode": "Samsung Electronics",
|
||||
"locationId": "2487472a-06c4-4bce-8f4c-700c5f8644f8",
|
||||
"ownerId": "b603d7e8-6066-4e10-8102-afa752a63816",
|
||||
"roomId": "acaa060a-7c19-4579-8a4a-5ad891a2f0c1",
|
||||
"deviceTypeName": "Samsung OCF Refrigerator",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "contactSensor",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "execute",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "ocf",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "powerConsumptionReport",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "demandResponseLoadControl",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refrigeration",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "temperatureMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatCoolingSetpoint",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.deviceReportStateConfiguration",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.energyType",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.fridgeMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledComponents",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.waterFilter",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.fridgeFoodList",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.softwareUpdate",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.deviceIdentification",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.driverVersion",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.fridgeVacationMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.powerCool",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.powerFreeze",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.sabbathMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.viewInside",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.runestoneHomeContext",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.quickControl",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "sec.diagnosticsInformation",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "sec.wifiConfiguration",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Refrigerator",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "freezer",
|
||||
"label": "freezer",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "contactSensor",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "temperatureMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatCoolingSetpoint",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.thermostatSetpointControl",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.freezerConvertMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.unavailableCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cooler",
|
||||
"label": "cooler",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "contactSensor",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "temperatureMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "thermostatCoolingSetpoint",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.thermostatSetpointControl",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.unavailableCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cvroom",
|
||||
"label": "cvroom",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "contactSensor",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.fridgeMode",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "icemaker",
|
||||
"label": "icemaker",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "icemaker-02",
|
||||
"label": "icemaker-02",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "icemaker-03",
|
||||
"label": "icemaker-03",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "scale-10",
|
||||
"label": "scale-10",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "samsungce.weightMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.weightMeasurementCalibration",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.connectionState",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.scaleSettings",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "scale-11",
|
||||
"label": "scale-11",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "samsungce.weightMeasurement",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pantry-01",
|
||||
"label": "pantry-01",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "samsungce.fridgePantryInfo",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.fridgePantryMode",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.meatAging",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungce.foodDefrost",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "camera-01",
|
||||
"label": "camera-01",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "custom.disabledCapabilities",
|
||||
"version": 1
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "Other",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"createTime": "2021-07-27T01:19:42.051Z",
|
||||
"profile": {
|
||||
"id": "4c654f1b-8ef4-35b0-920e-c12568554213"
|
||||
},
|
||||
"ocf": {
|
||||
"ocfDeviceType": "oic.d.refrigerator",
|
||||
"name": "Family Hub",
|
||||
"specVersion": "core.1.1.0",
|
||||
"verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"modelNumber": "24K_REF_LCD_FHUB9.0|00113141|0002034e051324200103000000000000",
|
||||
"platformVersion": "7.0",
|
||||
"platformOS": "Tizen",
|
||||
"hwVersion": "",
|
||||
"firmwareVersion": "20240616.213423",
|
||||
"vendorId": "DA-REF-NORMAL-01001",
|
||||
"vendorResourceClientServerVersion": "4.0.22",
|
||||
"locale": "",
|
||||
"lastSignupTime": "2021-07-27T01:19:40.244392Z",
|
||||
"transferCandidate": false,
|
||||
"additionalAuthCodeRequired": false
|
||||
},
|
||||
"type": "OCF",
|
||||
"restrictionTier": 0,
|
||||
"allowed": [],
|
||||
"executionContext": "CLOUD"
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"deviceId": "a75cb1e1-03fd-3c77-ca9f-d4e56c4096c6",
|
||||
"name": "Soundbar",
|
||||
"label": "Soundbar",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"presentationId": "VD-NetworkAudio-003S",
|
||||
"deviceManufacturerCode": "Samsung Electronics",
|
||||
"locationId": "6bdf6730-8167-488b-8645-d0c5046ff763",
|
||||
"ownerId": "15f0ae72-da51-14e2-65cf-ef59ae867e7f",
|
||||
"roomId": "3b0fe9a8-51d6-49cf-b64a-8a719013c0a7",
|
||||
"deviceTypeName": "Samsung OCF Network Audio Player",
|
||||
"components": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": "main",
|
||||
"capabilities": [
|
||||
{
|
||||
"id": "ocf",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "execute",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "refresh",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "switch",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioVolume",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioMute",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungvd.audioInputSource",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "audioNotification",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungvd.soundFrom",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "sec.diagnosticsInformation",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungvd.thingStatus",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungvd.supportsFeatures",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "sec.wifiConfiguration",
|
||||
"version": 1
|
||||
},
|
||||
{
|
||||
"id": "samsungvd.audioGroupInfo",
|
||||
"version": 1,
|
||||
"ephemeral": true
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"name": "NetworkAudio",
|
||||
"categoryType": "manufacturer"
|
||||
}
|
||||
],
|
||||
"optional": false
|
||||
}
|
||||
],
|
||||
"createTime": "2025-02-21T14:25:21.843Z",
|
||||
"profile": {
|
||||
"id": "25504ad5-8563-3b07-8770-e52ad29a9c5a"
|
||||
},
|
||||
"ocf": {
|
||||
"ocfDeviceType": "oic.d.networkaudio",
|
||||
"name": "Soundbar",
|
||||
"specVersion": "core.1.1.0",
|
||||
"verticalDomainSpecVersion": "res.1.1.0,sh.1.1.0",
|
||||
"manufacturerName": "Samsung Electronics",
|
||||
"modelNumber": "HW-S60D",
|
||||
"platformVersion": "8.0",
|
||||
"platformOS": "Tizen",
|
||||
"hwVersion": "",
|
||||
"firmwareVersion": "SAT-MT8532D24WWC-1016.0",
|
||||
"vendorId": "VD-NetworkAudio-003S",
|
||||
"vendorResourceClientServerVersion": "4.0.26",
|
||||
"lastSignupTime": "2025-03-18T19:11:51.176292902Z",
|
||||
"transferCandidate": false,
|
||||
"additionalAuthCodeRequired": false
|
||||
},
|
||||
"type": "OCF",
|
||||
"restrictionTier": 0,
|
||||
"allowed": null,
|
||||
"executionContext": "CLOUD",
|
||||
"relationships": []
|
||||
}
|
||||
],
|
||||
"_links": {}
|
||||
}
|
@ -761,6 +761,150 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][binary_sensor.refrigerator_cooler_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.refrigerator_cooler_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Cooler door',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'cooler_door',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_cooler_contactSensor_contact_contact',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][binary_sensor.refrigerator_cooler_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'Refrigerator Cooler door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.refrigerator_cooler_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][binary_sensor.refrigerator_coolselect_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.refrigerator_coolselect_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'CoolSelect+ door',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'cool_select_plus_door',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_cvroom_contactSensor_contact_contact',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][binary_sensor.refrigerator_coolselect_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'Refrigerator CoolSelect+ door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.refrigerator_coolselect_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][binary_sensor.refrigerator_freezer_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.refrigerator_freezer_door',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.DOOR: 'door'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Freezer door',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'freezer_door',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_freezer_contactSensor_contact_contact',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][binary_sensor.refrigerator_freezer_door-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'door',
|
||||
'friendly_name': 'Refrigerator Freezer door',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.refrigerator_freezer_door',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01011][binary_sensor.frigo_cooler_door-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -187,3 +187,50 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][button.refrigerator_reset_water_filter-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.refrigerator_reset_water_filter',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset water filter',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_water_filter',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_custom.waterFilter_resetWaterFilter',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][button.refrigerator_reset_water_filter-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Refrigerator Reset water filter',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.refrigerator_reset_water_filter',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
|
@ -629,6 +629,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[da_ref_normal_01001]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': 'https://account.smartthings.com',
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': '',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'7d3feb98-8a36-4351-c362-5e21ad3a78dd',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Samsung Electronics',
|
||||
'model': '24K_REF_LCD_FHUB9.0',
|
||||
'model_id': None,
|
||||
'name': 'Refrigerator',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '20240616.213423',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[da_ref_normal_01011]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
@ -1652,6 +1685,39 @@
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[vd_network_audio_003s]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': 'https://account.smartthings.com',
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': '',
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'smartthings',
|
||||
'a75cb1e1-03fd-3c77-ca9f-d4e56c4096c6',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Samsung Electronics',
|
||||
'model': 'HW-S60D',
|
||||
'model_id': None,
|
||||
'name': 'Soundbar',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': 'SAT-MT8532D24WWC-1016.0',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_devices[vd_sensor_light_2023]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
|
@ -231,6 +231,56 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[vd_network_audio_003s][media_player.soundbar-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.soundbar',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <MediaPlayerDeviceClass.SPEAKER: 'speaker'>,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 1420>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'a75cb1e1-03fd-3c77-ca9f-d4e56c4096c6_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[vd_network_audio_003s][media_player.soundbar-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'speaker',
|
||||
'friendly_name': 'Soundbar',
|
||||
'supported_features': <MediaPlayerEntityFeature: 1420>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'media_player.soundbar',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[vd_stv_2017_k][media_player.tv_samsung_8_series_49-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -4049,6 +4049,283 @@
|
||||
'state': '0.0135559777781698',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
'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.refrigerator_energy',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_energy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Refrigerator Energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.refrigerator_energy',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '4381.422',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_difference-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
'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.refrigerator_energy_difference',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy difference',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_difference',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_deltaEnergy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_difference-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Refrigerator Energy difference',
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.refrigerator_energy_difference',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.027',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_saved-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
'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.refrigerator_energy_saved',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Energy saved',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_saved',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_energySaved_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_energy_saved-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Refrigerator Energy saved',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.refrigerator_energy_saved',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power-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': None,
|
||||
'entity_id': 'sensor.refrigerator_power',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Power',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_power_meter',
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'power',
|
||||
'friendly_name': 'Refrigerator Power',
|
||||
'power_consumption_end': '2025-02-09T00:25:23Z',
|
||||
'power_consumption_start': '2025-02-09T00:13:39Z',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.refrigerator_power',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '144',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power_energy-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
'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.refrigerator_power_energy',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Power energy',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'power_energy',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_main_powerConsumptionReport_powerConsumption_powerEnergy_meter',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][sensor.refrigerator_power_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Refrigerator Power energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.refrigerator_power_energy',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.0270189050030708',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01011][sensor.frigo_energy-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -93,6 +93,53 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][switch.refrigerator_ice_maker-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.refrigerator_ice_maker',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Ice maker',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'ice_maker',
|
||||
'unique_id': '7d3feb98-8a36-4351-c362-5e21ad3a78dd_icemaker_switch_switch_switch',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_ref_normal_01001][switch.refrigerator_ice_maker-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Refrigerator Ice maker',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.refrigerator_ice_maker',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_normal_000001][switch.robot_vacuum-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
@ -181,13 +181,13 @@ async def test_assist_api(
|
||||
|
||||
assert len(llm.async_get_apis(hass)) == 1
|
||||
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||
assert [tool.name for tool in api.tools] == ["get_home_state"]
|
||||
assert [tool.name for tool in api.tools] == ["GetLiveContext"]
|
||||
|
||||
# Match all
|
||||
intent_handler.platforms = None
|
||||
|
||||
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||
assert [tool.name for tool in api.tools] == ["test_intent", "get_home_state"]
|
||||
assert [tool.name for tool in api.tools] == ["test_intent", "GetLiveContext"]
|
||||
|
||||
# Match specific domain
|
||||
intent_handler.platforms = {"light"}
|
||||
@ -575,7 +575,7 @@ async def test_assist_api_prompt(
|
||||
suggested_area="Test Area 2",
|
||||
)
|
||||
)
|
||||
exposed_entities_prompt = """An overview of the areas and the devices in this smart home:
|
||||
exposed_entities_prompt = """Live Context: An overview of the areas and the devices in this smart home:
|
||||
- names: Kitchen
|
||||
domain: light
|
||||
state: 'on'
|
||||
@ -623,7 +623,7 @@ async def test_assist_api_prompt(
|
||||
state: unavailable
|
||||
areas: Test Area 2
|
||||
"""
|
||||
stateless_exposed_entities_prompt = """An overview of the areas and the devices in this smart home:
|
||||
stateless_exposed_entities_prompt = """Static Context: An overview of the areas and the devices in this smart home:
|
||||
- names: Kitchen
|
||||
domain: light
|
||||
- names: Living Room
|
||||
@ -669,17 +669,30 @@ async def test_assist_api_prompt(
|
||||
"When a user asks to turn on all devices of a specific type, "
|
||||
"ask user to specify an area, unless there is only one device of that type."
|
||||
)
|
||||
dynamic_context_prompt = """You ARE equipped to answer questions about the current state of
|
||||
the home using the `GetLiveContext` tool. This is a primary function. Do not state you lack the
|
||||
functionality if the question requires live data.
|
||||
If the user asks about device existence/type (e.g., "Do I have lights in the bedroom?"): Answer
|
||||
from the static context below.
|
||||
If the user asks about the CURRENT state, value, or mode (e.g., "Is the lock locked?",
|
||||
"Is the fan on?", "What mode is the thermostat in?", "What is the temperature outside?"):
|
||||
1. Recognize this requires live data.
|
||||
2. You MUST call `GetLiveContext`. This tool will provide the needed real-time information (like temperature from the local weather, lock status, etc.).
|
||||
3. Use the tool's response** to answer the user accurately (e.g., "The temperature outside is [value from tool].").
|
||||
For general knowledge questions not about the home: Answer truthfully from internal knowledge.
|
||||
"""
|
||||
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||
assert api.api_prompt == (
|
||||
f"""{first_part_prompt}
|
||||
{area_prompt}
|
||||
{no_timer_prompt}
|
||||
{dynamic_context_prompt}
|
||||
{stateless_exposed_entities_prompt}"""
|
||||
)
|
||||
|
||||
# Verify that the get_home_state tool returns the same results as the exposed_entities_prompt
|
||||
# Verify that the GetLiveContext tool returns the same results as the exposed_entities_prompt
|
||||
result = await api.async_call_tool(
|
||||
llm.ToolInput(tool_name="get_home_state", tool_args={})
|
||||
llm.ToolInput(tool_name="GetLiveContext", tool_args={})
|
||||
)
|
||||
assert result == {
|
||||
"success": True,
|
||||
@ -697,6 +710,7 @@ async def test_assist_api_prompt(
|
||||
f"""{first_part_prompt}
|
||||
{area_prompt}
|
||||
{no_timer_prompt}
|
||||
{dynamic_context_prompt}
|
||||
{stateless_exposed_entities_prompt}"""
|
||||
)
|
||||
|
||||
@ -712,6 +726,7 @@ async def test_assist_api_prompt(
|
||||
f"""{first_part_prompt}
|
||||
{area_prompt}
|
||||
{no_timer_prompt}
|
||||
{dynamic_context_prompt}
|
||||
{stateless_exposed_entities_prompt}"""
|
||||
)
|
||||
|
||||
@ -723,6 +738,7 @@ async def test_assist_api_prompt(
|
||||
assert api.api_prompt == (
|
||||
f"""{first_part_prompt}
|
||||
{area_prompt}
|
||||
{dynamic_context_prompt}
|
||||
{stateless_exposed_entities_prompt}"""
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user