mirror of
https://github.com/home-assistant/core.git
synced 2026-04-16 06:36:14 +02:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b981ece163 | ||
|
|
7ea931fdc8 | ||
|
|
f3038a20af | ||
|
|
de234c7190 | ||
|
|
399681984f | ||
|
|
5ca14ca7d7 | ||
|
|
ac53cfa85a | ||
|
|
02f1a9c3a9 | ||
|
|
f93fdceac9 | ||
|
|
711a89f7b8 | ||
|
|
19e58c554e | ||
|
|
feb6c2bfe6 | ||
|
|
6bb91422ff | ||
|
|
3bd699285b | ||
|
|
6d10305197 | ||
|
|
42a9c8488d | ||
|
|
c6c273559e | ||
|
|
f7394ce302 | ||
|
|
175dec6f1a | ||
|
|
d137761cb5 | ||
|
|
8055cbc58d | ||
|
|
c9dff27590 | ||
|
|
c913a858b6 | ||
|
|
4ed33a804e | ||
|
|
8bf5674826 | ||
|
|
b8a0b0083b | ||
|
|
a57c101b5e | ||
|
|
957b8c1c52 | ||
|
|
bb002d051b | ||
|
|
2b2fd4ac92 | ||
|
|
f4c270629b |
2
.github/workflows/builder.yml
vendored
2
.github/workflows/builder.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Get information
|
||||
id: info
|
||||
uses: home-assistant/actions/helpers/info@master # zizmor: ignore[unpinned-uses]
|
||||
uses: home-assistant/actions/helpers/info@5f5b077d63a1e4c53019231409a0c4d791fb74e5 # zizmor: ignore[unpinned-uses]
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["arcam"],
|
||||
"requirements": ["arcam-fmj==1.8.2"],
|
||||
"requirements": ["arcam-fmj==1.8.3"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
|
||||
@@ -91,6 +91,7 @@ SENSORS: tuple[ArcamFmjSensorEntityDescription, ...] = (
|
||||
value_fn=lambda state: (
|
||||
vp.colorspace.name.lower()
|
||||
if (vp := state.get_incoming_video_parameters()) is not None
|
||||
and vp.colorspace is not None
|
||||
else None
|
||||
),
|
||||
),
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
"services": {
|
||||
"announce": {
|
||||
"description": "Lets a satellite announce a message.",
|
||||
"description": "Lets an Assist satellite announce a message.",
|
||||
"fields": {
|
||||
"media_id": {
|
||||
"description": "The media ID to announce instead of using text-to-speech.",
|
||||
@@ -94,10 +94,10 @@
|
||||
"name": "Preannounce media ID"
|
||||
}
|
||||
},
|
||||
"name": "Announce"
|
||||
"name": "Announce on satellite"
|
||||
},
|
||||
"ask_question": {
|
||||
"description": "Asks a question and gets the user's response.",
|
||||
"description": "Lets an Assist satellite ask a question and get the user's response.",
|
||||
"fields": {
|
||||
"answers": {
|
||||
"description": "Possible answers to the question.",
|
||||
@@ -124,10 +124,10 @@
|
||||
"name": "Question media ID"
|
||||
}
|
||||
},
|
||||
"name": "Ask question"
|
||||
"name": "Ask question on satellite"
|
||||
},
|
||||
"start_conversation": {
|
||||
"description": "Starts a conversation from a satellite.",
|
||||
"description": "Starts a conversation from an Assist satellite.",
|
||||
"fields": {
|
||||
"extra_system_prompt": {
|
||||
"description": "Provide background information to the AI about the request.",
|
||||
@@ -150,13 +150,13 @@
|
||||
"name": "Message"
|
||||
}
|
||||
},
|
||||
"name": "Start conversation"
|
||||
"name": "Start conversation on satellite"
|
||||
}
|
||||
},
|
||||
"title": "Assist satellite",
|
||||
"triggers": {
|
||||
"idle": {
|
||||
"description": "Triggers after one or more voice assistant satellites become idle after having processed a command.",
|
||||
"description": "Triggers after one or more Assist satellites become idle after having processed a command.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
@@ -165,7 +165,7 @@
|
||||
"name": "Satellite became idle"
|
||||
},
|
||||
"listening": {
|
||||
"description": "Triggers after one or more voice assistant satellites start listening for a command from someone.",
|
||||
"description": "Triggers after one or more Assist satellites start listening for a command from someone.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
@@ -174,7 +174,7 @@
|
||||
"name": "Satellite started listening"
|
||||
},
|
||||
"processing": {
|
||||
"description": "Triggers after one or more voice assistant satellites start processing a command after having heard it.",
|
||||
"description": "Triggers after one or more Assist satellites start processing a command after having heard it.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
@@ -183,7 +183,7 @@
|
||||
"name": "Satellite started processing"
|
||||
},
|
||||
"responding": {
|
||||
"description": "Triggers after one or more voice assistant satellites start responding to a command after having processed it, or start announcing something.",
|
||||
"description": "Triggers after one or more Assist satellites start responding to a command after having processed it, or start announcing something.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::assist_satellite::common::trigger_behavior_name%]"
|
||||
|
||||
@@ -10,9 +10,11 @@ from requests.exceptions import RequestException
|
||||
from homeassistant.components.image import ImageEntity
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util, slugify
|
||||
|
||||
from .const import DOMAIN, Platform
|
||||
from .coordinator import AvmWrapper, FritzConfigEntry
|
||||
from .entity import FritzBoxBaseEntity
|
||||
|
||||
@@ -22,6 +24,32 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def _migrate_to_new_unique_id(
|
||||
hass: HomeAssistant, avm_wrapper: AvmWrapper, ssid: str
|
||||
) -> None:
|
||||
"""Migrate old unique id to new unique id."""
|
||||
|
||||
old_unique_id = slugify(f"{avm_wrapper.unique_id}-{ssid}-qr-code")
|
||||
new_unique_id = f"{avm_wrapper.unique_id}-guest_wifi_qr_code"
|
||||
|
||||
entity_registry = er.async_get(hass)
|
||||
entity_id = entity_registry.async_get_entity_id(
|
||||
Platform.IMAGE,
|
||||
DOMAIN,
|
||||
old_unique_id,
|
||||
)
|
||||
|
||||
if entity_id is None:
|
||||
return
|
||||
|
||||
entity_registry.async_update_entity(entity_id, new_unique_id=new_unique_id)
|
||||
_LOGGER.debug(
|
||||
"Migrating guest Wi-Fi image unique_id from [%s] to [%s]",
|
||||
old_unique_id,
|
||||
new_unique_id,
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: FritzConfigEntry,
|
||||
@@ -34,6 +62,8 @@ async def async_setup_entry(
|
||||
avm_wrapper.fritz_guest_wifi.get_info
|
||||
)
|
||||
|
||||
await _migrate_to_new_unique_id(hass, avm_wrapper, guest_wifi_info["NewSSID"])
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
FritzGuestWifiQRImage(
|
||||
@@ -60,7 +90,7 @@ class FritzGuestWifiQRImage(FritzBoxBaseEntity, ImageEntity):
|
||||
) -> None:
|
||||
"""Initialize the image entity."""
|
||||
self._attr_name = ssid
|
||||
self._attr_unique_id = slugify(f"{avm_wrapper.unique_id}-{ssid}-qr-code")
|
||||
self._attr_unique_id = f"{avm_wrapper.unique_id}-guest_wifi_qr_code"
|
||||
self._current_qr_bytes: bytes | None = None
|
||||
super().__init__(avm_wrapper, device_friendly_name)
|
||||
ImageEntity.__init__(self, hass)
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"integration_type": "system",
|
||||
"preview_features": { "winter_mode": {} },
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20260325.5"]
|
||||
"requirements": ["home-assistant-frontend==20260325.6"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.84", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.93", "babel==2.15.0"]
|
||||
}
|
||||
|
||||
@@ -323,7 +323,11 @@ DISCOVERY_SCHEMAS = [
|
||||
required_attributes=(
|
||||
clusters.FanControl.Attributes.FanMode,
|
||||
clusters.FanControl.Attributes.PercentCurrent,
|
||||
clusters.FanControl.Attributes.PercentSetting,
|
||||
),
|
||||
# PercentSetting SHALL be null when FanMode is Auto (spec 4.4.6.3),
|
||||
# so allow null values to not block discovery in that state.
|
||||
allow_none_value=True,
|
||||
optional_attributes=(
|
||||
clusters.FanControl.Attributes.SpeedSetting,
|
||||
clusters.FanControl.Attributes.RockSetting,
|
||||
|
||||
@@ -168,10 +168,15 @@ class MatterWaterHeater(MatterEntity, WaterHeaterEntity):
|
||||
self._attr_target_temperature = self._get_temperature_in_degrees(
|
||||
clusters.Thermostat.Attributes.OccupiedHeatingSetpoint
|
||||
)
|
||||
system_mode = self.get_matter_attribute_value(
|
||||
clusters.Thermostat.Attributes.SystemMode
|
||||
)
|
||||
boost_state = self.get_matter_attribute_value(
|
||||
clusters.WaterHeaterManagement.Attributes.BoostState
|
||||
)
|
||||
if boost_state == clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive:
|
||||
if system_mode == clusters.Thermostat.Enums.SystemModeEnum.kOff:
|
||||
self._attr_current_operation = STATE_OFF
|
||||
elif boost_state == clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive:
|
||||
self._attr_current_operation = STATE_HIGH_DEMAND
|
||||
else:
|
||||
self._attr_current_operation = STATE_ECO
|
||||
@@ -218,6 +223,7 @@ DISCOVERY_SCHEMAS = [
|
||||
clusters.Thermostat.Attributes.AbsMinHeatSetpointLimit,
|
||||
clusters.Thermostat.Attributes.AbsMaxHeatSetpointLimit,
|
||||
clusters.Thermostat.Attributes.LocalTemperature,
|
||||
clusters.Thermostat.Attributes.SystemMode,
|
||||
clusters.WaterHeaterManagement.Attributes.FeatureMap,
|
||||
),
|
||||
optional_attributes=(
|
||||
|
||||
@@ -260,7 +260,7 @@
|
||||
},
|
||||
"clear_playlist": {
|
||||
"description": "Removes all items from a media player's playlist.",
|
||||
"name": "Clear playlist"
|
||||
"name": "Clear media player playlist"
|
||||
},
|
||||
"join": {
|
||||
"description": "Groups media players together for synchronous playback. Only works on supported multiroom audio systems.",
|
||||
@@ -270,44 +270,44 @@
|
||||
"name": "Group members"
|
||||
}
|
||||
},
|
||||
"name": "Join"
|
||||
"name": "Join media players"
|
||||
},
|
||||
"media_next_track": {
|
||||
"description": "Selects the next track.",
|
||||
"name": "Next"
|
||||
"description": "Selects the next track on a media player.",
|
||||
"name": "Next track"
|
||||
},
|
||||
"media_pause": {
|
||||
"description": "Pauses playback on a media player.",
|
||||
"name": "[%key:common::action::pause%]"
|
||||
"name": "Pause media"
|
||||
},
|
||||
"media_play": {
|
||||
"description": "Starts playback on a media player.",
|
||||
"name": "Play"
|
||||
"name": "Play media"
|
||||
},
|
||||
"media_play_pause": {
|
||||
"description": "Toggles play/pause on a media player.",
|
||||
"name": "Play/Pause"
|
||||
"name": "Play/Pause media"
|
||||
},
|
||||
"media_previous_track": {
|
||||
"description": "Selects the previous track.",
|
||||
"name": "Previous"
|
||||
"description": "Selects the previous track on a media player.",
|
||||
"name": "Previous track"
|
||||
},
|
||||
"media_seek": {
|
||||
"description": "Allows you to go to a different part of the media that is currently playing.",
|
||||
"description": "Allows you to go to a different part of the media that is currently playing on a media player.",
|
||||
"fields": {
|
||||
"seek_position": {
|
||||
"description": "Target position in the currently playing media. The format is platform dependent.",
|
||||
"name": "Position"
|
||||
}
|
||||
},
|
||||
"name": "Seek"
|
||||
"name": "Seek media"
|
||||
},
|
||||
"media_stop": {
|
||||
"description": "Stops playback on a media player.",
|
||||
"name": "[%key:common::action::stop%]"
|
||||
"name": "Stop media"
|
||||
},
|
||||
"play_media": {
|
||||
"description": "Starts playing specified media.",
|
||||
"description": "Starts playing specified media on a media player.",
|
||||
"fields": {
|
||||
"announce": {
|
||||
"description": "If the media should be played as an announcement.",
|
||||
@@ -325,14 +325,14 @@
|
||||
"name": "Play media"
|
||||
},
|
||||
"repeat_set": {
|
||||
"description": "Sets the repeat mode.",
|
||||
"description": "Sets the repeat mode of a media player.",
|
||||
"fields": {
|
||||
"repeat": {
|
||||
"description": "Whether the media (one or all) should be played in a loop or not.",
|
||||
"name": "Repeat mode"
|
||||
}
|
||||
},
|
||||
"name": "Set repeat"
|
||||
"name": "Set media player repeat"
|
||||
},
|
||||
"search_media": {
|
||||
"description": "Searches the available media.",
|
||||
@@ -357,14 +357,14 @@
|
||||
"name": "Search media"
|
||||
},
|
||||
"select_sound_mode": {
|
||||
"description": "Selects a specific sound mode.",
|
||||
"description": "Selects a specific sound mode of a media player.",
|
||||
"fields": {
|
||||
"sound_mode": {
|
||||
"description": "Name of the sound mode to switch to.",
|
||||
"name": "Sound mode"
|
||||
}
|
||||
},
|
||||
"name": "Select sound mode"
|
||||
"name": "Select media player sound mode"
|
||||
},
|
||||
"select_source": {
|
||||
"description": "Sends a media player the command to change the input source.",
|
||||
@@ -374,37 +374,37 @@
|
||||
"name": "Source"
|
||||
}
|
||||
},
|
||||
"name": "Select source"
|
||||
"name": "Select media player source"
|
||||
},
|
||||
"shuffle_set": {
|
||||
"description": "Enables or disables the shuffle mode.",
|
||||
"description": "Enables or disables the shuffle mode of a media player.",
|
||||
"fields": {
|
||||
"shuffle": {
|
||||
"description": "Whether the media should be played in randomized order or not.",
|
||||
"name": "Shuffle mode"
|
||||
}
|
||||
},
|
||||
"name": "Set shuffle"
|
||||
"name": "Set media player shuffle"
|
||||
},
|
||||
"toggle": {
|
||||
"description": "Toggles a media player on/off.",
|
||||
"name": "[%key:common::action::toggle%]"
|
||||
"name": "Toggle media player"
|
||||
},
|
||||
"turn_off": {
|
||||
"description": "Turns off the power of a media player.",
|
||||
"name": "[%key:common::action::turn_off%]"
|
||||
"name": "Turn off media player"
|
||||
},
|
||||
"turn_on": {
|
||||
"description": "Turns on the power of a media player.",
|
||||
"name": "[%key:common::action::turn_on%]"
|
||||
"name": "Turn on media player"
|
||||
},
|
||||
"unjoin": {
|
||||
"description": "Removes a media player from a group. Only works on platforms which support player groups.",
|
||||
"name": "Unjoin"
|
||||
"name": "Unjoin media player"
|
||||
},
|
||||
"volume_down": {
|
||||
"description": "Turns down the volume of a media player.",
|
||||
"name": "Turn down volume"
|
||||
"name": "Turn down media player volume"
|
||||
},
|
||||
"volume_mute": {
|
||||
"description": "Mutes or unmutes a media player.",
|
||||
@@ -414,7 +414,7 @@
|
||||
"name": "Muted"
|
||||
}
|
||||
},
|
||||
"name": "Mute/unmute volume"
|
||||
"name": "Mute/unmute media player"
|
||||
},
|
||||
"volume_set": {
|
||||
"description": "Sets the volume level of a media player.",
|
||||
@@ -424,11 +424,11 @@
|
||||
"name": "Level"
|
||||
}
|
||||
},
|
||||
"name": "Set volume"
|
||||
"name": "Set media player volume"
|
||||
},
|
||||
"volume_up": {
|
||||
"description": "Turns up the volume of a media player.",
|
||||
"name": "Turn up volume"
|
||||
"name": "Turn up media player volume"
|
||||
}
|
||||
},
|
||||
"title": "Media player",
|
||||
|
||||
@@ -21,7 +21,7 @@ VM_CONTAINER_RUNNING = "running"
|
||||
STORAGE_ACTIVE = 1
|
||||
STORAGE_SHARED = 1
|
||||
STORAGE_ENABLED = 1
|
||||
STATUS_OK = "ok"
|
||||
STATUS_OK = "OK"
|
||||
|
||||
AUTH_PAM = "pam"
|
||||
AUTH_PVE = "pve"
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
"services": {
|
||||
"disable": {
|
||||
"description": "Stops the recording of events and state changes.",
|
||||
"name": "[%key:common::action::disable%]"
|
||||
"name": "Disable Recorder"
|
||||
},
|
||||
"enable": {
|
||||
"description": "Starts the recording of events and state changes.",
|
||||
"name": "[%key:common::action::enable%]"
|
||||
"name": "Enable Recorder"
|
||||
},
|
||||
"get_statistics": {
|
||||
"description": "Retrieves statistics data for entities within a specific time period.",
|
||||
@@ -46,7 +46,7 @@
|
||||
"name": "Units"
|
||||
}
|
||||
},
|
||||
"name": "Get statistics"
|
||||
"name": "Get Recorder statistics"
|
||||
},
|
||||
"purge": {
|
||||
"description": "Starts purge task - to clean up old data from your database.",
|
||||
@@ -64,7 +64,7 @@
|
||||
"name": "Repack"
|
||||
}
|
||||
},
|
||||
"name": "Purge"
|
||||
"name": "Purge Recorder database"
|
||||
},
|
||||
"purge_entities": {
|
||||
"description": "Starts a purge task to remove the data related to specific entities from your database.",
|
||||
@@ -86,7 +86,7 @@
|
||||
"name": "[%key:component::recorder::services::purge::fields::keep_days::name%]"
|
||||
}
|
||||
},
|
||||
"name": "Purge entities"
|
||||
"name": "Purge Recorder entities"
|
||||
}
|
||||
},
|
||||
"system_health": {
|
||||
|
||||
@@ -128,9 +128,8 @@ class RingCam(RingEntity[RingDoorBell], Camera):
|
||||
self._device = self._get_coordinator_data().get_video_device(
|
||||
self._device.device_api_id
|
||||
)
|
||||
|
||||
history_data = self._device.last_history
|
||||
if history_data and self._device.has_subscription:
|
||||
if history_data:
|
||||
self._last_event = history_data[0]
|
||||
# will call async_update to update the attributes and get the
|
||||
# video url from the api
|
||||
@@ -155,16 +154,13 @@ class RingCam(RingEntity[RingDoorBell], Camera):
|
||||
self, width: int | None = None, height: int | None = None
|
||||
) -> bytes | None:
|
||||
"""Return a still image response from the camera."""
|
||||
if self._video_url is None:
|
||||
if not self._device.has_subscription:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_subscription",
|
||||
)
|
||||
return None
|
||||
# For live_view cameras, get a fresh snapshot
|
||||
if self.entity_description.key == "live_view":
|
||||
return await self._async_get_fresh_snapshot()
|
||||
|
||||
# For last_recording cameras, use the cached video frame
|
||||
key = (width, height)
|
||||
if not (image := self._images.get(key)):
|
||||
if not (image := self._images.get(key)) and self._video_url is not None:
|
||||
image = await ffmpeg.async_get_image(
|
||||
self.hass,
|
||||
self._video_url,
|
||||
@@ -177,6 +173,11 @@ class RingCam(RingEntity[RingDoorBell], Camera):
|
||||
|
||||
return image
|
||||
|
||||
@exception_wrap
|
||||
async def _async_get_fresh_snapshot(self) -> bytes | None:
|
||||
"""Get a fresh snapshot from the camera."""
|
||||
return await self._device.async_get_snapshot()
|
||||
|
||||
async def handle_async_mjpeg_stream(
|
||||
self, request: web.Request
|
||||
) -> web.StreamResponse | None:
|
||||
|
||||
@@ -151,9 +151,6 @@
|
||||
"api_timeout": {
|
||||
"message": "Timeout communicating with Ring API"
|
||||
},
|
||||
"no_subscription": {
|
||||
"message": "Ring Protect subscription required for snapshots"
|
||||
},
|
||||
"sdp_m_line_index_required": {
|
||||
"message": "Error negotiating stream for {device}"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ is_option_selected:
|
||||
required: true
|
||||
selector:
|
||||
state:
|
||||
attribute: options
|
||||
hide_states:
|
||||
- unavailable
|
||||
- unknown
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pysmartthings==3.7.2"]
|
||||
"requirements": ["pysmartthings==3.7.3"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pysmhi"],
|
||||
"requirements": ["pysmhi==1.1.0"]
|
||||
"requirements": ["pysmhi==2.0.0"]
|
||||
}
|
||||
|
||||
@@ -56,6 +56,21 @@ FORESTDRY_MAP = {
|
||||
"5": "very_dry",
|
||||
"6": "extremely_dry",
|
||||
}
|
||||
PRECIPITATION_CATEGORY_MAP = {
|
||||
0: "no_precipitation",
|
||||
1: "rain",
|
||||
2: "thunderstorm",
|
||||
3: "freezing_rain",
|
||||
4: "mixed_ice",
|
||||
5: "snow",
|
||||
6: "wet_snow",
|
||||
7: "rain_snow_mixed",
|
||||
8: "ice_pellets",
|
||||
9: "graupel",
|
||||
10: "hail",
|
||||
11: "drizzle",
|
||||
12: "freezing_drizzle",
|
||||
}
|
||||
|
||||
|
||||
def get_percentage_values(entity: SMHIWeatherSensor, key: str) -> int | None:
|
||||
@@ -68,6 +83,14 @@ def get_percentage_values(entity: SMHIWeatherSensor, key: str) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def get_precipitation_category(entity: SMHIWeatherSensor) -> str | None:
|
||||
"""Return the precipitation category."""
|
||||
value: int | None = entity.coordinator.current.get("precipitation_category")
|
||||
if value in PRECIPITATION_CATEGORY_MAP:
|
||||
return PRECIPITATION_CATEGORY_MAP[value]
|
||||
return None
|
||||
|
||||
|
||||
def get_fire_index_value(entity: SMHIFireSensor, key: str) -> str:
|
||||
"""Return index value as string."""
|
||||
value: int | None = entity.coordinator.fire_current.get(key) # type: ignore[assignment]
|
||||
@@ -128,11 +151,9 @@ WEATHER_SENSOR_DESCRIPTIONS: tuple[SMHIWeatherEntityDescription, ...] = (
|
||||
SMHIWeatherEntityDescription(
|
||||
key="precipitation_category",
|
||||
translation_key="precipitation_category",
|
||||
value_fn=lambda entity: str(
|
||||
get_percentage_values(entity, "precipitation_category")
|
||||
),
|
||||
value_fn=get_precipitation_category,
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["0", "1", "2", "3", "4", "5", "6"],
|
||||
options=[*PRECIPITATION_CATEGORY_MAP.values()],
|
||||
),
|
||||
SMHIWeatherEntityDescription(
|
||||
key="frozen_precipitation",
|
||||
|
||||
@@ -95,13 +95,19 @@
|
||||
"precipitation_category": {
|
||||
"name": "Precipitation category",
|
||||
"state": {
|
||||
"0": "No precipitation",
|
||||
"1": "Snow",
|
||||
"2": "Snow and rain",
|
||||
"3": "Rain",
|
||||
"4": "Drizzle",
|
||||
"5": "Freezing rain",
|
||||
"6": "Freezing drizzle"
|
||||
"drizzle": "Drizzle",
|
||||
"freezing_drizzle": "Freezing drizzle",
|
||||
"freezing_rain": "Freezing rain",
|
||||
"graupel": "Graupel",
|
||||
"hail": "Hail",
|
||||
"ice_pellets": "Ice pellets",
|
||||
"mixed_ice": "Mixed/ice",
|
||||
"no_precipitation": "No precipitation",
|
||||
"rain": "Rain",
|
||||
"rain_snow_mixed": "Mixture of rain and snow",
|
||||
"snow": "Snow",
|
||||
"thunderstorm": "Thunderstorm",
|
||||
"wet_snow": "Wet snow"
|
||||
}
|
||||
},
|
||||
"rate_of_spread": {
|
||||
|
||||
@@ -417,6 +417,7 @@ class SonosDiscoveryManager:
|
||||
)
|
||||
new_coordinator.setup(soco)
|
||||
c_dict[soco.household_id] = new_coordinator
|
||||
c_dict[soco.household_id].add_speaker(soco)
|
||||
speaker.setup(self.entry)
|
||||
except (OSError, SoCoException, Timeout) as ex:
|
||||
_LOGGER.warning("Failed to add SonosSpeaker using %s: %s", soco, ex)
|
||||
|
||||
@@ -99,3 +99,7 @@ class SonosAlarms(SonosHouseholdCoordinator):
|
||||
)
|
||||
self.last_processed_event_id = self.alarms.last_id
|
||||
return True
|
||||
|
||||
def add_speaker(self, soco: SoCo) -> None:
|
||||
"""Update any skipped alarms when speaker is added."""
|
||||
self.alarms.update_skipped(soco)
|
||||
|
||||
@@ -85,3 +85,6 @@ class SonosHouseholdCoordinator:
|
||||
def update_cache(self, soco: SoCo, update_id: int | None = None) -> bool:
|
||||
"""Update the cache of the household-level feature and return if cache has changed."""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_speaker(self, soco: SoCo) -> None:
|
||||
"""Additional processing when a speaker is added if needed."""
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"quality_scale": "bronze",
|
||||
"requirements": [
|
||||
"defusedxml==0.7.1",
|
||||
"soco==0.30.14",
|
||||
"soco==0.30.15",
|
||||
"sonos-websocket==0.1.3"
|
||||
],
|
||||
"ssdp": [
|
||||
|
||||
@@ -132,7 +132,8 @@ class SonosMedia:
|
||||
|
||||
self.artist = track_info.get("artist")
|
||||
self.album_name = track_info.get("album")
|
||||
self.title = track_info.get("title")
|
||||
title = track_info.get("title") or ""
|
||||
self.title = title.strip() or None
|
||||
self.image_url = track_info.get("album_art")
|
||||
|
||||
playlist_position = int(track_info.get("playlist_position", -1))
|
||||
|
||||
@@ -9,7 +9,7 @@ import os
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple
|
||||
|
||||
from psutil import Process
|
||||
from psutil._common import sbattery, sdiskusage, shwtemp, snetio, snicaddr, sswap
|
||||
from psutil._ntuples import sbattery, sdiskusage, shwtemp, snetio, snicaddr, sswap
|
||||
import psutil_home_assistant as ha_psutil
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/systemmonitor",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["psutil"],
|
||||
"requirements": ["psutil-home-assistant==0.0.1", "psutil==7.1.2"],
|
||||
"requirements": ["psutil-home-assistant==0.0.1", "psutil==7.2.2"],
|
||||
"single_config_entry": true
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import os
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from psutil._common import sfan, shwtemp
|
||||
from psutil._ntuples import sfan, shwtemp
|
||||
import psutil_home_assistant as ha_psutil
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -52,7 +52,7 @@ VEHICLE_DESCRIPTIONS: tuple[TeslaFleetNumberVehicleEntityDescription, ...] = (
|
||||
mode=NumberMode.AUTO,
|
||||
max_key="charge_state_charge_current_request_max",
|
||||
func=lambda api, value: api.set_charging_amps(value),
|
||||
scopes=[Scope.VEHICLE_CHARGING_CMDS],
|
||||
scopes=[Scope.VEHICLE_CHARGING_CMDS, Scope.VEHICLE_CMDS],
|
||||
),
|
||||
TeslaFleetNumberVehicleEntityDescription(
|
||||
key="charge_state_charge_limit_soc",
|
||||
|
||||
@@ -30,4 +30,8 @@ class TeslaUserImplementation(AuthImplementation):
|
||||
@property
|
||||
def extra_authorize_data(self) -> dict[str, Any]:
|
||||
"""Extra data that needs to be appended to the authorize url."""
|
||||
return {"prompt": "login", "scope": " ".join(SCOPES)}
|
||||
return {
|
||||
"prompt": "login",
|
||||
"prompt_missing_scopes": "true",
|
||||
"scope": " ".join(SCOPES),
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"description": "The {name} integration needs to re-authenticate your account",
|
||||
"description": "The {name} integration needs to re-authenticate your account. Reauthentication refreshes the Tesla API permissions granted to Home Assistant, including any newly enabled scopes.",
|
||||
"title": "[%key:common::config_flow::title::reauth%]"
|
||||
},
|
||||
"registration_complete": {
|
||||
@@ -60,7 +60,7 @@
|
||||
"data_description": {
|
||||
"qr_code": "Scan this QR code with your phone to set up the virtual key."
|
||||
},
|
||||
"description": "To enable command signing, you must open the Tesla app, select your vehicle, and then visit the following URL to set up a virtual key. You must repeat this process for each vehicle.\n\n{virtual_key_url}",
|
||||
"description": "To enable command signing, you must open the Tesla app, select your vehicle, and then visit the following URL to set up a virtual key. You must repeat this process for each vehicle.\n\n{virtual_key_url}\n\nIf you later enable additional Tesla API permissions, reauthenticate the integration to refresh the granted scopes.",
|
||||
"title": "Command signing"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ from .services import async_setup_services
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
|
||||
PLATFORMS = [Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
MIGRATION_NAME_TO_KEY = {
|
||||
# Sensors
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
"""Binary sensor platform for Transmission integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import TransmissionConfigEntry, TransmissionDataUpdateCoordinator
|
||||
from .entity import TransmissionEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class TransmissionBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Describe a Transmission binary sensor entity."""
|
||||
|
||||
is_on_fn: Callable[[TransmissionDataUpdateCoordinator], bool | None]
|
||||
|
||||
|
||||
BINARY_SENSOR_TYPES: tuple[TransmissionBinarySensorEntityDescription, ...] = (
|
||||
TransmissionBinarySensorEntityDescription(
|
||||
key="port_forwarding",
|
||||
translation_key="port_forwarding",
|
||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
is_on_fn=lambda coordinator: coordinator.port_forwarding,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: TransmissionConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Transmission binary sensors from a config entry."""
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
TransmissionBinarySensor(coordinator, description)
|
||||
for description in BINARY_SENSOR_TYPES
|
||||
)
|
||||
|
||||
|
||||
class TransmissionBinarySensor(TransmissionEntity, BinarySensorEntity):
|
||||
"""Representation of a Transmission binary sensor."""
|
||||
|
||||
entity_description: TransmissionBinarySensorEntityDescription
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return True if the port is open."""
|
||||
return self.entity_description.is_on_fn(self.coordinator)
|
||||
@@ -45,7 +45,6 @@ class TransmissionDataUpdateCoordinator(DataUpdateCoordinator[SessionStats]):
|
||||
self.api = api
|
||||
self.host = entry.data[CONF_HOST]
|
||||
self._session: transmission_rpc.Session | None = None
|
||||
self.port_forwarding: bool | None = None
|
||||
self._all_torrents: list[transmission_rpc.Torrent] = []
|
||||
self._completed_torrents: list[transmission_rpc.Torrent] = []
|
||||
self._started_torrents: list[transmission_rpc.Torrent] = []
|
||||
@@ -78,7 +77,6 @@ class TransmissionDataUpdateCoordinator(DataUpdateCoordinator[SessionStats]):
|
||||
data = self.api.session_stats()
|
||||
self.torrents = self.api.get_torrents()
|
||||
self._session = self.api.get_session()
|
||||
self.port_forwarding = self.api.port_test()
|
||||
except transmission_rpc.TransmissionError as err:
|
||||
raise UpdateFailed("Unable to connect to Transmission client") from err
|
||||
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
{
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"port_forwarding": {
|
||||
"default": "mdi:shield-check",
|
||||
"state": {
|
||||
"off": "mdi:shield-off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"active_torrents": {
|
||||
"default": "mdi:counter"
|
||||
|
||||
@@ -41,15 +41,6 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"port_forwarding": {
|
||||
"name": "Port forwarding",
|
||||
"state": {
|
||||
"off": "Closed",
|
||||
"on": "Open"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"active_torrents": {
|
||||
"name": "Active torrents",
|
||||
|
||||
@@ -35,6 +35,7 @@ from homeassistant.const import (
|
||||
EntityCategory,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfPower,
|
||||
UnitOfTime,
|
||||
)
|
||||
@@ -656,6 +657,8 @@ SENSORS: dict[DeviceCategory, tuple[TuyaSensorEntityDescription, ...]] = {
|
||||
translation_key="total_energy",
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
suggested_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
),
|
||||
TuyaSensorEntityDescription(
|
||||
key=DPCode.PRO_ADD_ELE,
|
||||
|
||||
@@ -531,7 +531,13 @@ async def websocket_release_notes(
|
||||
"Entity does not support release notes",
|
||||
)
|
||||
return
|
||||
|
||||
if entity.available is False:
|
||||
connection.send_error(
|
||||
msg["id"],
|
||||
websocket_api.ERR_HOME_ASSISTANT_ERROR,
|
||||
"Entity is not available",
|
||||
)
|
||||
return
|
||||
connection.send_result(
|
||||
msg["id"],
|
||||
await entity.async_release_notes(),
|
||||
|
||||
@@ -66,7 +66,6 @@ SUPPORT_WIIM_BASE = (
|
||||
| MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.GROUPING
|
||||
| MediaPlayerEntityFeature.SEEK
|
||||
)
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.84"]
|
||||
"requirements": ["holidays==0.93"]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import ZinvoltConfigEntry, ZinvoltData, ZinvoltDeviceCoordinator
|
||||
from .entity import ZinvoltEntity
|
||||
from .entity import ZinvoltEntity, ZinvoltUnitEntity
|
||||
|
||||
POINT_ENTITIES = {
|
||||
"communication": BinarySensorDeviceClass.PROBLEM,
|
||||
@@ -57,9 +57,10 @@ async def async_setup_entry(
|
||||
for coordinator in entry.runtime_data.values()
|
||||
]
|
||||
entities.extend(
|
||||
ZinvoltPointBinarySensor(coordinator, point)
|
||||
ZinvoltPointBinarySensor(coordinator, battery.serial_number, point)
|
||||
for coordinator in entry.runtime_data.values()
|
||||
for point in coordinator.data.points
|
||||
for battery in coordinator.battery_units.values()
|
||||
for point in coordinator.data.batteries[battery.serial_number].points
|
||||
if point in POINT_ENTITIES
|
||||
)
|
||||
async_add_entities(entities)
|
||||
@@ -88,25 +89,27 @@ class ZinvoltBatteryStateBinarySensor(ZinvoltEntity, BinarySensorEntity):
|
||||
return self.entity_description.is_on_fn(self.coordinator.data)
|
||||
|
||||
|
||||
class ZinvoltPointBinarySensor(ZinvoltEntity, BinarySensorEntity):
|
||||
class ZinvoltPointBinarySensor(ZinvoltUnitEntity, BinarySensorEntity):
|
||||
"""Zinvolt battery state binary sensor."""
|
||||
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
|
||||
def __init__(self, coordinator: ZinvoltDeviceCoordinator, point: str) -> None:
|
||||
def __init__(
|
||||
self, coordinator: ZinvoltDeviceCoordinator, unit_serial_number: str, point: str
|
||||
) -> None:
|
||||
"""Initialize the binary sensor."""
|
||||
super().__init__(coordinator)
|
||||
super().__init__(coordinator, unit_serial_number)
|
||||
self.point = point
|
||||
self._attr_translation_key = point
|
||||
self._attr_device_class = POINT_ENTITIES[point]
|
||||
self._attr_unique_id = f"{coordinator.data.battery.serial_number}.{point}"
|
||||
self._attr_unique_id = f"{self.serial_number}.{point}"
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return the availability of the binary sensor."""
|
||||
return super().available and self.point in self.coordinator.data.points
|
||||
return super().available and self.point in self.battery.points
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the binary sensor."""
|
||||
return not self.coordinator.data.points[self.point]
|
||||
return not self.battery.points[self.point]
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
|
||||
from zinvolt import ZinvoltClient
|
||||
from zinvolt.exceptions import ZinvoltError
|
||||
from zinvolt.models import Battery, BatteryState
|
||||
from zinvolt.models import Battery, BatteryState, Unit, UnitType
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -24,6 +24,13 @@ class ZinvoltData:
|
||||
"""Data for the Zinvolt integration."""
|
||||
|
||||
battery: BatteryState
|
||||
batteries: dict[str, BatteryData]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BatteryData:
|
||||
"""Data per battery unit."""
|
||||
|
||||
sw_version: str
|
||||
model: str
|
||||
points: dict[str, bool]
|
||||
@@ -32,6 +39,8 @@ class ZinvoltData:
|
||||
class ZinvoltDeviceCoordinator(DataUpdateCoordinator[ZinvoltData]):
|
||||
"""Class for Zinvolt devices."""
|
||||
|
||||
battery_units: dict[str, Unit]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
@@ -50,15 +59,30 @@ class ZinvoltDeviceCoordinator(DataUpdateCoordinator[ZinvoltData]):
|
||||
self.battery = battery
|
||||
self.client = client
|
||||
|
||||
async def _async_setup(self) -> None:
|
||||
"""Set up the Zinvolt integration."""
|
||||
try:
|
||||
units = await self.client.get_units(self.battery.identifier)
|
||||
except ZinvoltError as err:
|
||||
raise UpdateFailed(
|
||||
translation_key="update_failed", translation_domain=DOMAIN
|
||||
) from err
|
||||
self.battery_units = {
|
||||
unit.serial_number: unit for unit in units if unit.type is UnitType.BATTERY
|
||||
}
|
||||
|
||||
async def _async_update_data(self) -> ZinvoltData:
|
||||
"""Update data from Zinvolt."""
|
||||
try:
|
||||
battery_state = await self.client.get_battery_status(
|
||||
self.battery.identifier
|
||||
)
|
||||
battery_unit = await self.client.get_battery_unit(
|
||||
self.battery.identifier, self.battery.serial_number
|
||||
)
|
||||
battery_units = {
|
||||
unit_serial_number: await self.client.get_battery_unit(
|
||||
self.battery.identifier, unit_serial_number
|
||||
)
|
||||
for unit_serial_number in self.battery_units
|
||||
}
|
||||
except ZinvoltError as err:
|
||||
raise UpdateFailed(
|
||||
translation_key="update_failed",
|
||||
@@ -66,7 +90,15 @@ class ZinvoltDeviceCoordinator(DataUpdateCoordinator[ZinvoltData]):
|
||||
) from err
|
||||
return ZinvoltData(
|
||||
battery_state,
|
||||
battery_unit.version.current_version,
|
||||
battery_unit.battery_model,
|
||||
{point.point.lower(): point.normal for point in battery_unit.points},
|
||||
{
|
||||
serial_number: BatteryData(
|
||||
battery_unit.version.current_version,
|
||||
battery_unit.battery_model,
|
||||
{
|
||||
point.point.lower(): point.normal
|
||||
for point in battery_unit.points
|
||||
},
|
||||
)
|
||||
for serial_number, battery_unit in battery_units.items()
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"""Base entity for Zinvolt integration."""
|
||||
|
||||
from zinvolt.models import Unit
|
||||
|
||||
from homeassistant.const import ATTR_VIA_DEVICE
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ZinvoltDeviceCoordinator
|
||||
from .coordinator import BatteryData, ZinvoltDeviceCoordinator
|
||||
|
||||
|
||||
class ZinvoltEntity(CoordinatorEntity[ZinvoltDeviceCoordinator]):
|
||||
@@ -20,6 +23,55 @@ class ZinvoltEntity(CoordinatorEntity[ZinvoltDeviceCoordinator]):
|
||||
manufacturer="Zinvolt",
|
||||
name=coordinator.battery.name,
|
||||
serial_number=coordinator.data.battery.serial_number,
|
||||
model_id=coordinator.data.model,
|
||||
sw_version=coordinator.data.sw_version,
|
||||
)
|
||||
|
||||
|
||||
class ZinvoltUnitEntity(ZinvoltEntity):
|
||||
"""Base entity for Zinvolt units."""
|
||||
|
||||
def __init__(
|
||||
self, coordinator: ZinvoltDeviceCoordinator, unit_serial_number: str
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self.unit_serial_number = unit_serial_number
|
||||
is_main_device = (
|
||||
list(coordinator.battery_units).index(self.unit_serial_number) == 0
|
||||
)
|
||||
self.serial_number = (
|
||||
coordinator.data.battery.serial_number
|
||||
if is_main_device
|
||||
else self.battery_unit.serial_number
|
||||
)
|
||||
name = coordinator.battery.name if is_main_device else self.battery_unit.name
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self.serial_number)},
|
||||
manufacturer="Zinvolt",
|
||||
name=name,
|
||||
serial_number=self.serial_number,
|
||||
sw_version=self.battery_unit.version.current_version,
|
||||
model_id=self.battery.model,
|
||||
)
|
||||
if not is_main_device:
|
||||
self._attr_device_info[ATTR_VIA_DEVICE] = (
|
||||
DOMAIN,
|
||||
coordinator.data.battery.serial_number,
|
||||
)
|
||||
|
||||
@property
|
||||
def battery(self) -> BatteryData:
|
||||
"""Return the battery data."""
|
||||
return self.coordinator.data.batteries[self.unit_serial_number]
|
||||
|
||||
@property
|
||||
def battery_unit(self) -> Unit:
|
||||
"""Return the battery unit."""
|
||||
return self.coordinator.battery_units[self.unit_serial_number]
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if the entity is available."""
|
||||
return (
|
||||
super().available
|
||||
and self.unit_serial_number in self.coordinator.data.batteries
|
||||
)
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["zinvolt"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["zinvolt==0.3.0"]
|
||||
"requirements": ["zinvolt==0.4.1"]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2026
|
||||
MINOR_VERSION: Final = 4
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 14, 2)
|
||||
|
||||
@@ -6,7 +6,7 @@ from functools import cache
|
||||
from getpass import getuser
|
||||
import logging
|
||||
import platform
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.const import __version__ as current_version
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -15,7 +15,6 @@ from homeassistant.util.package import is_docker_env, is_virtual_env
|
||||
from homeassistant.util.system_info import is_official_image
|
||||
|
||||
from .hassio import is_hassio
|
||||
from .importlib import async_import_module
|
||||
from .singleton import singleton
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -54,15 +53,6 @@ cached_get_user = cache(getuser)
|
||||
@bind_hass
|
||||
async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
"""Return info about the system."""
|
||||
# Local import to avoid circular dependencies
|
||||
# We use the import helper because hassio
|
||||
# may not be loaded yet and we don't want to
|
||||
# do blocking I/O in the event loop to import it.
|
||||
if TYPE_CHECKING:
|
||||
from homeassistant.components import hassio # noqa: PLC0415
|
||||
else:
|
||||
hassio = await async_import_module(hass, "homeassistant.components.hassio")
|
||||
|
||||
is_hassio_ = is_hassio(hass)
|
||||
|
||||
info_object = {
|
||||
@@ -105,6 +95,9 @@ async def async_get_system_info(hass: HomeAssistant) -> dict[str, Any]:
|
||||
|
||||
# Enrich with Supervisor information
|
||||
if is_hassio_:
|
||||
# Local import to avoid circular dependencies
|
||||
from homeassistant.components import hassio # noqa: PLC0415
|
||||
|
||||
if not (info := hassio.get_info(hass)):
|
||||
_LOGGER.warning("No Home Assistant Supervisor info available")
|
||||
info = {}
|
||||
|
||||
@@ -39,7 +39,7 @@ habluetooth==5.11.1
|
||||
hass-nabucasa==2.2.0
|
||||
hassil==3.5.0
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20260325.5
|
||||
home-assistant-frontend==20260325.6
|
||||
home-assistant-intents==2026.3.24
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2026.4.0"
|
||||
version = "2026.4.1"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
|
||||
16
requirements_all.txt
generated
16
requirements_all.txt
generated
@@ -533,7 +533,7 @@ aqualogic==2.6
|
||||
aranet4==2.6.0
|
||||
|
||||
# homeassistant.components.arcam_fmj
|
||||
arcam-fmj==1.8.2
|
||||
arcam-fmj==1.8.3
|
||||
|
||||
# homeassistant.components.arris_tg2492lg
|
||||
arris-tg2492lg==2.2.0
|
||||
@@ -1226,10 +1226,10 @@ hole==0.9.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.84
|
||||
holidays==0.93
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20260325.5
|
||||
home-assistant-frontend==20260325.6
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2026.3.24
|
||||
@@ -1832,7 +1832,7 @@ proxmoxer==2.3.0
|
||||
psutil-home-assistant==0.0.1
|
||||
|
||||
# homeassistant.components.systemmonitor
|
||||
psutil==7.1.2
|
||||
psutil==7.2.2
|
||||
|
||||
# homeassistant.components.pulseaudio_loopback
|
||||
pulsectl==23.5.2
|
||||
@@ -2500,13 +2500,13 @@ pysmappee==0.2.29
|
||||
pysmarlaapi==1.0.2
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.7.2
|
||||
pysmartthings==3.7.3
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.3
|
||||
|
||||
# homeassistant.components.smhi
|
||||
pysmhi==1.1.0
|
||||
pysmhi==2.0.0
|
||||
|
||||
# homeassistant.components.edl21
|
||||
pysml==0.1.5
|
||||
@@ -2972,7 +2972,7 @@ smart-meter-texas==0.5.5
|
||||
snapcast==2.3.7
|
||||
|
||||
# homeassistant.components.sonos
|
||||
soco==0.30.14
|
||||
soco==0.30.15
|
||||
|
||||
# homeassistant.components.solaredge_local
|
||||
solaredge-local==0.2.3
|
||||
@@ -3392,7 +3392,7 @@ zhong-hong-hvac==1.0.13
|
||||
ziggo-mediabox-xl==1.1.0
|
||||
|
||||
# homeassistant.components.zinvolt
|
||||
zinvolt==0.3.0
|
||||
zinvolt==0.4.1
|
||||
|
||||
# homeassistant.components.zoneminder
|
||||
zm-py==0.5.4
|
||||
|
||||
@@ -44,7 +44,7 @@ types-chardet==0.1.5
|
||||
types-decorator==5.2.0.20251101
|
||||
types-pexpect==4.9.0.20250916
|
||||
types-protobuf==6.30.2.20250914
|
||||
types-psutil==7.1.1.20251122
|
||||
types-psutil==7.2.2.20260402
|
||||
types-pyserial==3.5.0.20251001
|
||||
types-python-dateutil==2.9.0.20260124
|
||||
types-python-slugify==8.0.2.20240310
|
||||
|
||||
16
requirements_test_all.txt
generated
16
requirements_test_all.txt
generated
@@ -506,7 +506,7 @@ apsystems-ez1==2.7.0
|
||||
aranet4==2.6.0
|
||||
|
||||
# homeassistant.components.arcam_fmj
|
||||
arcam-fmj==1.8.2
|
||||
arcam-fmj==1.8.3
|
||||
|
||||
# homeassistant.components.asuswrt
|
||||
asusrouter==1.21.3
|
||||
@@ -1090,10 +1090,10 @@ hole==0.9.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.84
|
||||
holidays==0.93
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20260325.5
|
||||
home-assistant-frontend==20260325.6
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2026.3.24
|
||||
@@ -1590,7 +1590,7 @@ proxmoxer==2.3.0
|
||||
psutil-home-assistant==0.0.1
|
||||
|
||||
# homeassistant.components.systemmonitor
|
||||
psutil==7.1.2
|
||||
psutil==7.2.2
|
||||
|
||||
# homeassistant.components.pushbullet
|
||||
pushbullet.py==0.11.0
|
||||
@@ -2135,13 +2135,13 @@ pysmappee==0.2.29
|
||||
pysmarlaapi==1.0.2
|
||||
|
||||
# homeassistant.components.smartthings
|
||||
pysmartthings==3.7.2
|
||||
pysmartthings==3.7.3
|
||||
|
||||
# homeassistant.components.smarty
|
||||
pysmarty2==0.10.3
|
||||
|
||||
# homeassistant.components.smhi
|
||||
pysmhi==1.1.0
|
||||
pysmhi==2.0.0
|
||||
|
||||
# homeassistant.components.edl21
|
||||
pysml==0.1.5
|
||||
@@ -2517,7 +2517,7 @@ smart-meter-texas==0.5.5
|
||||
snapcast==2.3.7
|
||||
|
||||
# homeassistant.components.sonos
|
||||
soco==0.30.14
|
||||
soco==0.30.15
|
||||
|
||||
# homeassistant.components.solaredge
|
||||
solaredge-web==0.0.1
|
||||
@@ -2868,7 +2868,7 @@ zeversolar==0.3.2
|
||||
zha==1.1.1
|
||||
|
||||
# homeassistant.components.zinvolt
|
||||
zinvolt==0.3.0
|
||||
zinvolt==0.4.1
|
||||
|
||||
# homeassistant.components.zoneminder
|
||||
zm-py==0.5.4
|
||||
|
||||
@@ -13,9 +13,11 @@ from homeassistant.components.image import DOMAIN as IMAGE_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import MOCK_FB_SERVICES, MOCK_USER_DATA
|
||||
from .const import MOCK_FB_SERVICES, MOCK_MESH_MASTER_MAC, MOCK_USER_DATA
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
from tests.typing import ClientSessionGenerator
|
||||
@@ -126,7 +128,7 @@ async def test_image_entity(
|
||||
}
|
||||
|
||||
assert (state := entity_registry.async_get("image.mock_title_guestwifi"))
|
||||
assert state.unique_id == "1c_ed_6f_12_34_11_guestwifi_qr_code"
|
||||
assert state.unique_id == "1C:ED:6F:12:34:11-guest_wifi_qr_code"
|
||||
|
||||
# test image download
|
||||
client = await hass_client()
|
||||
@@ -222,3 +224,53 @@ async def test_image_update_unavailable(
|
||||
|
||||
assert (state := hass.states.get(entity_id))
|
||||
assert state.state != STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_migrate_to_new_unique_id(
|
||||
hass: HomeAssistant,
|
||||
fc_class_mock,
|
||||
fh_class_mock,
|
||||
entity_registry: EntityRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test migrate from old unique id to new unique id."""
|
||||
|
||||
mock_unique_id = "1234567890"
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data=MOCK_USER_DATA,
|
||||
unique_id=mock_unique_id,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
old_unique_id = slugify(f"{MOCK_MESH_MASTER_MAC}-MyWifi-qr-code")
|
||||
new_unique_id = f"{MOCK_MESH_MASTER_MAC}-guest_wifi_qr_code"
|
||||
|
||||
entity_registry.async_get_or_create(
|
||||
suggested_object_id="mock_title_mywifi",
|
||||
disabled_by=None,
|
||||
domain=IMAGE_DOMAIN,
|
||||
platform=DOMAIN,
|
||||
unique_id=old_unique_id,
|
||||
config_entry=entry,
|
||||
)
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(DOMAIN, mock_unique_id)},
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, MOCK_MESH_MASTER_MAC)},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_entry = entity_registry.async_get("image.mock_title_mywifi")
|
||||
assert entity_entry
|
||||
assert entity_entry.unique_id == old_unique_id
|
||||
|
||||
with patch("homeassistant.components.fritz.PLATFORMS", [Platform.IMAGE]):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_entry = entity_registry.async_get("image.mock_title_mywifi")
|
||||
assert entity_entry
|
||||
assert entity_entry.unique_id == new_unique_id
|
||||
|
||||
@@ -66,15 +66,15 @@ async def test_form_no_subdivision(hass: HomeAssistant) -> None:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_COUNTRY: "SE",
|
||||
CONF_COUNTRY: "AL",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == "Sweden"
|
||||
assert result2["title"] == "Albania"
|
||||
assert result2["data"] == {
|
||||
"country": "SE",
|
||||
"country": "AL",
|
||||
}
|
||||
|
||||
|
||||
@@ -90,12 +90,12 @@ async def test_form_translated_title(hass: HomeAssistant) -> None:
|
||||
result2 = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_COUNTRY: "SE",
|
||||
CONF_COUNTRY: "AL",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["title"] == "Schweden"
|
||||
assert result2["title"] == "Albanien"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
@@ -105,20 +105,20 @@ async def test_single_combination_country_province(hass: HomeAssistant) -> None:
|
||||
CONF_COUNTRY: "DE",
|
||||
CONF_PROVINCE: "BW",
|
||||
}
|
||||
data_se = {
|
||||
CONF_COUNTRY: "SE",
|
||||
data_al = {
|
||||
CONF_COUNTRY: "AL",
|
||||
}
|
||||
MockConfigEntry(domain=DOMAIN, data=data_de).add_to_hass(hass)
|
||||
MockConfigEntry(domain=DOMAIN, data=data_se).add_to_hass(hass)
|
||||
MockConfigEntry(domain=DOMAIN, data=data_al).add_to_hass(hass)
|
||||
|
||||
# Test for country without subdivisions
|
||||
result_se = await hass.config_entries.flow.async_init(
|
||||
result_al = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": config_entries.SOURCE_USER},
|
||||
data=data_se,
|
||||
data=data_al,
|
||||
)
|
||||
assert result_se["type"] is FlowResultType.ABORT
|
||||
assert result_se["reason"] == "already_configured"
|
||||
assert result_al["type"] is FlowResultType.ABORT
|
||||
assert result_al["reason"] == "already_configured"
|
||||
|
||||
# Test for country with subdivisions
|
||||
result_de_step1 = await hass.config_entries.flow.async_init(
|
||||
@@ -150,12 +150,12 @@ async def test_form_babel_unresolved_language(hass: HomeAssistant) -> None:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_COUNTRY: "SE",
|
||||
CONF_COUNTRY: "AL",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["title"] == "Sweden"
|
||||
assert result["title"] == "Albania"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -197,12 +197,12 @@ async def test_form_babel_replace_dash_with_underscore(hass: HomeAssistant) -> N
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_COUNTRY: "SE",
|
||||
CONF_COUNTRY: "AL",
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["title"] == "Sweden"
|
||||
assert result["title"] == "Albania"
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -430,8 +430,8 @@ async def test_options_abort_no_categories(hass: HomeAssistant) -> None:
|
||||
"""Test the options flow abort if no categories to select."""
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_COUNTRY: "SE"},
|
||||
title="Sweden",
|
||||
data={CONF_COUNTRY: "AL"},
|
||||
title="Albania",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
|
||||
@@ -226,6 +226,20 @@ async def test_update_from_water_heater(
|
||||
assert state
|
||||
assert state.state == STATE_ECO
|
||||
|
||||
# confirm water heater state is 'off' when SystemMode is set to 0 (kOff)
|
||||
set_node_attribute(matter_node, 2, 513, 28, 0)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# confirm water heater state returns to 'eco' when SystemMode is set back to 4 (kHeat)
|
||||
set_node_attribute(matter_node, 2, 513, 28, 4)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == STATE_ECO
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["silabs_water_heater"])
|
||||
async def test_water_heater_turn_on_off(
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.pve1_status-entry]
|
||||
|
||||
@@ -294,10 +294,32 @@ async def test_camera_image(
|
||||
await setup_platform(hass, Platform.CAMERA)
|
||||
|
||||
front_camera_mock = mock_ring_devices.get_device(765432)
|
||||
front_camera_mock.async_get_snapshot.return_value = SMALLEST_VALID_JPEG_BYTES
|
||||
|
||||
state = hass.states.get("camera.front_live_view")
|
||||
assert state is not None
|
||||
|
||||
# For live_view camera, snapshot should use async_get_snapshot
|
||||
image = await async_get_image(hass, "camera.front_live_view")
|
||||
assert image.content == SMALLEST_VALID_JPEG_BYTES
|
||||
front_camera_mock.async_get_snapshot.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_camera_last_recording_image(
|
||||
hass: HomeAssistant,
|
||||
mock_ring_client,
|
||||
mock_ring_devices,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test last recording camera will return still image from video when available."""
|
||||
await setup_platform(hass, Platform.CAMERA)
|
||||
|
||||
front_camera_mock = mock_ring_devices.get_device(765432)
|
||||
|
||||
state = hass.states.get("camera.front_last_recording")
|
||||
assert state is not None
|
||||
|
||||
# history not updated yet
|
||||
front_camera_mock.async_history.assert_not_called()
|
||||
front_camera_mock.async_recording_url.assert_not_called()
|
||||
@@ -308,55 +330,23 @@ async def test_camera_image(
|
||||
),
|
||||
pytest.raises(HomeAssistantError),
|
||||
):
|
||||
image = await async_get_image(hass, "camera.front_live_view")
|
||||
await async_get_image(hass, "camera.front_last_recording")
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
# history updated so image available
|
||||
front_camera_mock.async_history.assert_called_once()
|
||||
front_camera_mock.async_recording_url.assert_called_once()
|
||||
assert front_camera_mock.async_recording_url.call_count == 2
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.ring.camera.ffmpeg.async_get_image",
|
||||
return_value=SMALLEST_VALID_JPEG_BYTES,
|
||||
):
|
||||
image = await async_get_image(hass, "camera.front_live_view")
|
||||
image = await async_get_image(hass, "camera.front_last_recording")
|
||||
assert image.content == SMALLEST_VALID_JPEG_BYTES
|
||||
|
||||
|
||||
async def test_camera_live_view_no_subscription(
|
||||
hass: HomeAssistant,
|
||||
mock_ring_client,
|
||||
mock_ring_devices,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test live view camera skips recording URL when no subscription."""
|
||||
await setup_platform(hass, Platform.CAMERA)
|
||||
|
||||
front_camera_mock = mock_ring_devices.get_device(765432)
|
||||
# Set device to not have subscription
|
||||
front_camera_mock.has_subscription = False
|
||||
|
||||
state = hass.states.get("camera.front_live_view")
|
||||
assert state is not None
|
||||
|
||||
# Reset mock call counts
|
||||
front_camera_mock.async_recording_url.reset_mock()
|
||||
|
||||
# Trigger coordinator update
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
# For cameras without subscription, recording URL should NOT be fetched
|
||||
front_camera_mock.async_recording_url.assert_not_called()
|
||||
|
||||
# Requesting an image without subscription should raise an error
|
||||
with pytest.raises(HomeAssistantError):
|
||||
await async_get_image(hass, "camera.front_live_view")
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_camera_stream_attributes(
|
||||
hass: HomeAssistant,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,148 +1,37 @@
|
||||
{
|
||||
"approvedTime": "2023-08-07T07:07:34Z",
|
||||
"referenceTime": "2023-08-07T07:00:00Z",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [[15.990068, 57.997072]]
|
||||
},
|
||||
"createdTime": "2026-04-02T11:01:32Z",
|
||||
"referenceTime": "2026-04-02T10:45:00Z",
|
||||
"geometry": { "type": "Point", "coordinates": [16.158549, 58.577821] },
|
||||
"timeSeries": [
|
||||
{
|
||||
"validTime": "2023-08-07T08:00:00Z",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "spp",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "percent",
|
||||
"values": [-9]
|
||||
},
|
||||
{
|
||||
"name": "pcat",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "category",
|
||||
"values": [0]
|
||||
},
|
||||
{
|
||||
"name": "pmin",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "kg/m2/h",
|
||||
"values": [0.0]
|
||||
},
|
||||
{
|
||||
"name": "pmean",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "kg/m2/h",
|
||||
"values": [0.0]
|
||||
},
|
||||
{
|
||||
"name": "pmax",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "kg/m2/h",
|
||||
"values": [0.0]
|
||||
},
|
||||
{
|
||||
"name": "pmedian",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "kg/m2/h",
|
||||
"values": [0.0]
|
||||
},
|
||||
{
|
||||
"name": "tcc_mean",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "octas",
|
||||
"values": [8]
|
||||
},
|
||||
{
|
||||
"name": "lcc_mean",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "octas",
|
||||
"values": [8]
|
||||
},
|
||||
{
|
||||
"name": "mcc_mean",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "octas",
|
||||
"values": [7]
|
||||
},
|
||||
{
|
||||
"name": "hcc_mean",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "octas",
|
||||
"values": [7]
|
||||
},
|
||||
{
|
||||
"name": "t",
|
||||
"levelType": "hl",
|
||||
"level": 2,
|
||||
"unit": "Cel",
|
||||
"values": [18.4]
|
||||
},
|
||||
{
|
||||
"name": "msl",
|
||||
"levelType": "hmsl",
|
||||
"level": 0,
|
||||
"unit": "hPa",
|
||||
"values": [992.4]
|
||||
},
|
||||
{
|
||||
"name": "vis",
|
||||
"levelType": "hl",
|
||||
"level": 2,
|
||||
"unit": "km",
|
||||
"values": [0.4]
|
||||
},
|
||||
{
|
||||
"name": "wd",
|
||||
"levelType": "hl",
|
||||
"level": 10,
|
||||
"unit": "degree",
|
||||
"values": [93]
|
||||
},
|
||||
{
|
||||
"name": "ws",
|
||||
"levelType": "hl",
|
||||
"level": 10,
|
||||
"unit": "m/s",
|
||||
"values": [2.5]
|
||||
},
|
||||
{
|
||||
"name": "r",
|
||||
"levelType": "hl",
|
||||
"level": 2,
|
||||
"unit": "percent",
|
||||
"values": [100]
|
||||
},
|
||||
{
|
||||
"name": "tstm",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "percent",
|
||||
"values": [37]
|
||||
},
|
||||
{
|
||||
"name": "gust",
|
||||
"levelType": "hl",
|
||||
"level": 10,
|
||||
"unit": "m/s",
|
||||
"values": [6.2]
|
||||
},
|
||||
{
|
||||
"name": "Wsymb2",
|
||||
"levelType": "hl",
|
||||
"level": 0,
|
||||
"unit": "category",
|
||||
"values": [7]
|
||||
}
|
||||
]
|
||||
"time": "2026-04-02T11:00:00Z",
|
||||
"intervalParametersStartTime": "2026-04-02T10:00:00Z",
|
||||
"data": {
|
||||
"air_temperature": 10.4,
|
||||
"wind_from_direction": 255,
|
||||
"wind_speed": 3.4,
|
||||
"wind_speed_of_gust": 6.8,
|
||||
"relative_humidity": 80,
|
||||
"air_pressure_at_mean_sea_level": 1011.5,
|
||||
"visibility_in_air": 17.9,
|
||||
"thunderstorm_probability": 0,
|
||||
"probability_of_frozen_precipitation": 0.0,
|
||||
"cloud_area_fraction": 8,
|
||||
"low_type_cloud_area_fraction": 8,
|
||||
"medium_type_cloud_area_fraction": 8,
|
||||
"high_type_cloud_area_fraction": 2,
|
||||
"cloud_base_altitude": 528,
|
||||
"cloud_top_altitude": 2250,
|
||||
"precipitation_amount_mean_deterministic": 0.0,
|
||||
"precipitation_amount_mean": 0.0,
|
||||
"precipitation_amount_min": 0.1,
|
||||
"precipitation_amount_max": 0.3,
|
||||
"precipitation_amount_median": 0.0,
|
||||
"probability_of_precipitation": 10,
|
||||
"precipitation_frozen_part": 0,
|
||||
"predominant_precipitation_type_at_surface": 1,
|
||||
"symbol_code": 6
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -508,7 +508,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '88',
|
||||
'state': '25',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_setup[load_platforms0][sensor.test_highest_grass_fire_risk-entry]
|
||||
@@ -735,7 +735,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '88',
|
||||
'state': '100',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_setup[load_platforms0][sensor.test_potential_rate_of_spread-entry]
|
||||
@@ -805,13 +805,19 @@
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'no_precipitation',
|
||||
'rain',
|
||||
'thunderstorm',
|
||||
'freezing_rain',
|
||||
'mixed_ice',
|
||||
'snow',
|
||||
'wet_snow',
|
||||
'rain_snow_mixed',
|
||||
'ice_pellets',
|
||||
'graupel',
|
||||
'hail',
|
||||
'drizzle',
|
||||
'freezing_drizzle',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -851,13 +857,19 @@
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Test Precipitation category',
|
||||
'options': list([
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'no_precipitation',
|
||||
'rain',
|
||||
'thunderstorm',
|
||||
'freezing_rain',
|
||||
'mixed_ice',
|
||||
'snow',
|
||||
'wet_snow',
|
||||
'rain_snow_mixed',
|
||||
'ice_pellets',
|
||||
'graupel',
|
||||
'hail',
|
||||
'drizzle',
|
||||
'freezing_drizzle',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -865,7 +877,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
'state': 'rain',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_setup[load_platforms0][sensor.test_thunder_probability-entry]
|
||||
@@ -917,7 +929,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '37',
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_setup[load_platforms0][sensor.test_total_cloud_coverage-entry]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@ async def test_setup_hass(
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
|
||||
assert state
|
||||
assert state.state == "fog"
|
||||
assert state.state == "cloudy"
|
||||
assert state.attributes == snapshot
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ async def test_setup_hass(
|
||||
"to_load",
|
||||
[1],
|
||||
)
|
||||
@pytest.mark.freeze_time(datetime(2023, 8, 7, 1, tzinfo=dt_util.UTC))
|
||||
@pytest.mark.freeze_time(datetime(2026, 4, 3, 1, tzinfo=dt_util.UTC))
|
||||
async def test_clear_night(
|
||||
hass: HomeAssistant,
|
||||
mock_client: SMHIPointForecast,
|
||||
@@ -134,7 +134,7 @@ async def test_properties_no_data(
|
||||
|
||||
assert state
|
||||
assert state.name == "Test"
|
||||
assert state.state == "fog"
|
||||
assert state.state == "cloudy"
|
||||
assert ATTR_SMHI_THUNDER_PROBABILITY not in state.attributes
|
||||
assert state.attributes[ATTR_ATTRIBUTION] == "Swedish weather institute (SMHI)"
|
||||
|
||||
@@ -356,7 +356,7 @@ async def test_custom_speed_unit(
|
||||
|
||||
assert state
|
||||
assert state.name == "Test"
|
||||
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 22.32
|
||||
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 24.48
|
||||
|
||||
entity_registry.async_update_entity_options(
|
||||
state.entity_id,
|
||||
@@ -367,7 +367,7 @@ async def test_custom_speed_unit(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 6.2
|
||||
assert state.attributes[ATTR_WEATHER_WIND_GUST_SPEED] == 6.8
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -400,7 +400,7 @@ async def test_forecast_services(
|
||||
assert msg["type"] == "event"
|
||||
forecast1 = msg["event"]["forecast"]
|
||||
|
||||
assert len(forecast1) == 10
|
||||
assert len(forecast1) == 11
|
||||
assert forecast1[0] == snapshot
|
||||
assert forecast1[6] == snapshot
|
||||
|
||||
@@ -421,7 +421,7 @@ async def test_forecast_services(
|
||||
assert msg["type"] == "event"
|
||||
forecast1 = msg["event"]["forecast"]
|
||||
|
||||
assert len(forecast1) == 52
|
||||
assert len(forecast1) == 59
|
||||
assert forecast1[0] == snapshot
|
||||
assert forecast1[6] == snapshot
|
||||
|
||||
|
||||
@@ -1175,6 +1175,46 @@ async def test_media_transport(
|
||||
assert getattr(soco, client_call).call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("transport_state", "title", "expected_state"),
|
||||
[
|
||||
("PAUSED_PLAYBACK", "Something", "paused"),
|
||||
("PAUSED_PLAYBACK", None, "idle"),
|
||||
("PAUSED_PLAYBACK", " ", "idle"),
|
||||
("STOPPED", "Something", "paused"),
|
||||
("STOPPED", None, "idle"),
|
||||
("STOPPED", " ", "idle"),
|
||||
],
|
||||
)
|
||||
async def test_state_paused_idle(
|
||||
hass: HomeAssistant,
|
||||
soco: MockSoCo,
|
||||
async_autosetup_sonos,
|
||||
no_media_event: SonosMockEvent,
|
||||
transport_state: str,
|
||||
title: str | None,
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test that idle is returned when title is None or whitespace, paused otherwise."""
|
||||
soco.get_current_track_info.return_value = {
|
||||
"title": title,
|
||||
"artist": "",
|
||||
"album": "",
|
||||
"album_art": "",
|
||||
"position": "NOT_IMPLEMENTED",
|
||||
"playlist_position": "1",
|
||||
"duration": "NOT_IMPLEMENTED",
|
||||
"uri": "x-file-cifs://192.168.42.10/music/track.mp3",
|
||||
"metadata": "NOT_IMPLEMENTED",
|
||||
}
|
||||
no_media_event.variables["transport_state"] = transport_state
|
||||
soco.avTransport.subscribe.return_value.callback(no_media_event)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
state = hass.states.get("media_player.zone_a")
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
async def test_play_media_announce(
|
||||
hass: HomeAssistant,
|
||||
soco: MockSoCo,
|
||||
|
||||
@@ -21,6 +21,7 @@ from homeassistant.components.sonos.switch import (
|
||||
ATTR_SPEECH_ENHANCEMENT_ENABLED,
|
||||
ATTR_VOLUME,
|
||||
)
|
||||
from homeassistant.components.ssdp import SsdpChange
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
||||
from homeassistant.const import (
|
||||
@@ -33,6 +34,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.service_info.ssdp import ATTR_UPNP_UDN, SsdpServiceInfo
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
@@ -337,3 +339,39 @@ async def test_alarm_change_device(
|
||||
alarm_14 = entity_registry.async_get(entity_id)
|
||||
device = device_registry.async_get(alarm_14.device_id)
|
||||
assert device.name == soco_br.get_speaker_info()["zone_name"]
|
||||
|
||||
|
||||
async def test_alarm_setup_for_undiscovered_speaker(
|
||||
hass: HomeAssistant,
|
||||
async_setup_sonos,
|
||||
alarm_clock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
soco_factory: SoCoMockFactory,
|
||||
discover,
|
||||
) -> None:
|
||||
"""Test for creation of alarm on a speaker that is discovered after the integration is setup."""
|
||||
|
||||
soco_bedroom = soco_factory.cache_mock(MockSoCo(), "10.10.10.2", "Bedroom")
|
||||
one_alarm = copy(alarm_clock.ListAlarms.return_value)
|
||||
one_alarm["CurrentAlarmList"] = one_alarm["CurrentAlarmList"].replace(
|
||||
"RINCON_test", soco_bedroom.uid
|
||||
)
|
||||
alarm_clock.ListAlarms.return_value = one_alarm
|
||||
await async_setup_sonos()
|
||||
|
||||
# Switch should not be created since the speaker isn't discovered yet
|
||||
assert "switch.sonos_alarm_14" not in entity_registry.entities
|
||||
|
||||
# Simulate discovery of the bedroom speaker
|
||||
discover.call_args.args[1](
|
||||
SsdpServiceInfo(
|
||||
ssdp_location=f"http://{soco_bedroom.ip_address}/",
|
||||
ssdp_st="urn:schemas-upnp-org:device:ZonePlayer:1",
|
||||
ssdp_usn=f"uuid:{soco_bedroom.uid}_MR::urn:schemas-upnp-org:service:GroupRenderingControl:1",
|
||||
upnp={ATTR_UPNP_UDN: f"uuid:{soco_bedroom.uid}"},
|
||||
),
|
||||
SsdpChange.ALIVE,
|
||||
)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert "switch.sonos_alarm_14" in entity_registry.entities
|
||||
|
||||
@@ -7,7 +7,7 @@ import socket
|
||||
from unittest.mock import AsyncMock, Mock, NonCallableMock, patch
|
||||
|
||||
from psutil import NoSuchProcess, Process
|
||||
from psutil._common import (
|
||||
from psutil._ntuples import (
|
||||
sbattery,
|
||||
sdiskpart,
|
||||
sdiskusage,
|
||||
|
||||
@@ -5,7 +5,7 @@ import socket
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from psutil._common import sdiskpart, sdiskusage, shwtemp, snetio, snicaddr
|
||||
from psutil._ntuples import sdiskpart, sdiskusage, shwtemp, snetio, snicaddr
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
from psutil._common import sdiskpart
|
||||
from psutil._ntuples import sdiskpart
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -233,6 +233,7 @@ async def test_full_flow_with_domain_registration(
|
||||
assert parsed_query["client_id"][0] == "user_client_id"
|
||||
assert parsed_query["redirect_uri"][0] == REDIRECT
|
||||
assert parsed_query["state"][0] == state
|
||||
assert parsed_query["prompt_missing_scopes"][0] == "true"
|
||||
assert parsed_query["scope"][0] == " ".join(SCOPES)
|
||||
assert "code_challenge" not in parsed_query
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# serializer version: 1
|
||||
# name: test_binary_sensors[binary_sensor.transmission_port_forwarding-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.transmission_port_forwarding',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Port forwarding',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.CONNECTIVITY: 'connectivity'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Port forwarding',
|
||||
'platform': 'transmission',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'port_forwarding',
|
||||
'unique_id': '01J0BC4QM2YBRP6H5G933AETT7-port_forwarding',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[binary_sensor.transmission_port_forwarding-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'connectivity',
|
||||
'friendly_name': 'Transmission Port forwarding',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.transmission_port_forwarding',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
@@ -1,31 +0,0 @@
|
||||
"""Tests for the Transmission binary sensor platform."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_binary_sensors(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_transmission_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the binary sensor entities."""
|
||||
with patch(
|
||||
"homeassistant.components.transmission.PLATFORMS", [Platform.BINARY_SENSOR]
|
||||
):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
@@ -144,8 +144,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -154,14 +160,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.pykascx9yfqrxtbgzcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.3dprinter_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': '3DPrinter Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.3dprinter_total_energy',
|
||||
@@ -380,6 +388,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -795,8 +806,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -805,15 +822,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.ZDldMHS0tjmQgGxEzcadd_ele',
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.ak1_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'AK1 Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.ak1_total_energy',
|
||||
@@ -1640,8 +1658,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -1650,14 +1674,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.cju47ovcbeuapei2zcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.aubess_cooker_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Aubess Cooker Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.aubess_cooker_total_energy',
|
||||
@@ -1873,8 +1899,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -1883,14 +1915,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.zjh9xhtm3gibs9kizcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.aubess_washing_machine_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Aubess Washing Machine Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.aubess_washing_machine_total_energy',
|
||||
@@ -2445,6 +2479,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -5000,6 +5037,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -5345,8 +5385,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -5355,14 +5401,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.jgsopsvzh2ec3itjzcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.dehumidifier_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Dehumidifier Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.dehumidifier_total_energy',
|
||||
@@ -5851,8 +5899,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -5861,15 +5915,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.l8uxezzkc7c5a0jhzcadd_ele',
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.droger_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'droger Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.droger_total_energy',
|
||||
@@ -7213,8 +7268,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -7223,14 +7284,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.cq4hzlrnqn4qi0mqzcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.elivco_kitchen_socket_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Elivco Kitchen Socket Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.elivco_kitchen_socket_total_energy',
|
||||
@@ -7446,8 +7509,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -7456,14 +7525,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.pz2xuth8hczv6zrwzcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.elivco_tv_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Elivco TV Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.elivco_tv_total_energy',
|
||||
@@ -7734,8 +7805,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -7744,14 +7821,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.51tdkcsamisw9ukycpadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.framboisier_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Framboisier Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.framboisier_total_energy',
|
||||
@@ -8246,6 +8325,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -9158,8 +9240,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -9168,14 +9256,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.sxa4ealyi9cotiugzcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.ha_socket_delta_test_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'HA Socket Delta Test Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.ha_socket_delta_test_total_energy',
|
||||
@@ -10221,6 +10311,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -10619,8 +10712,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -10629,14 +10728,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.vx2owjsg86g2ys93zcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.ineox_sp2_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Ineox SP2 Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.ineox_sp2_total_energy',
|
||||
@@ -10964,6 +11065,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -11738,8 +11842,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -11748,15 +11858,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.g7af6lrt4miugbstcpadd_ele',
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.keller_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Keller Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.keller_total_energy',
|
||||
@@ -12079,6 +12190,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -12314,8 +12428,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -12324,15 +12444,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.uvh6oeqrfliovfiwzcadd_ele',
|
||||
'unit_of_measurement': '度',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.licht_drucker_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Licht drucker Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': '度',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.licht_drucker_total_energy',
|
||||
@@ -15153,8 +15274,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -15163,15 +15290,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.uc9fL2NpR79iCzGIzcadd_ele',
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.n4_auto_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'N4-Auto Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.n4_auto_total_energy',
|
||||
@@ -15555,8 +15683,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -15565,14 +15699,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.2x473nefusdo7af6zcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.office_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Office Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.office_total_energy',
|
||||
@@ -17882,6 +18018,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -19716,8 +19855,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -19726,14 +19871,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.4q5c2am8n1bwb6bszcadd_ele',
|
||||
'unit_of_measurement': None,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.socket4_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Socket4 Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.socket4_total_energy',
|
||||
@@ -20393,6 +20540,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -22851,6 +23001,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
@@ -23651,8 +23804,14 @@
|
||||
'name': None,
|
||||
'object_id_base': 'Total energy',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Total energy',
|
||||
'platform': 'tuya',
|
||||
@@ -23661,15 +23820,16 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_energy',
|
||||
'unique_id': 'tuya.dNBnmtjLU8eRWHf0zcadd_ele',
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_platform_setup_and_discovery[sensor.weihnachten3_total_energy-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'energy',
|
||||
'friendly_name': 'Weihnachten3 Total energy',
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
'unit_of_measurement': '',
|
||||
'unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.weihnachten3_total_energy',
|
||||
@@ -23888,6 +24048,9 @@
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': <UnitOfEnergy.KILO_WATT_HOUR: 'kWh'>,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||
'original_icon': None,
|
||||
|
||||
@@ -48,6 +48,8 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from .common import MockUpdateEntity
|
||||
|
||||
from tests.common import (
|
||||
MockConfigEntry,
|
||||
MockEntityPlatform,
|
||||
@@ -64,13 +66,9 @@ from tests.typing import WebSocketGenerator
|
||||
TEST_DOMAIN = "test"
|
||||
|
||||
|
||||
class MockUpdateEntity(UpdateEntity):
|
||||
"""Mock UpdateEntity to use in tests."""
|
||||
|
||||
|
||||
async def test_update(hass: HomeAssistant) -> None:
|
||||
"""Test getting data from the mocked update entity."""
|
||||
update = MockUpdateEntity()
|
||||
update = UpdateEntity()
|
||||
update.hass = hass
|
||||
update.platform = MockEntityPlatform(hass)
|
||||
|
||||
@@ -797,6 +795,41 @@ async def test_release_notes_entity_does_not_support_release_notes(
|
||||
assert result["error"]["message"] == "Entity does not support release notes"
|
||||
|
||||
|
||||
async def test_release_notes_entity_unavailable(
|
||||
hass: HomeAssistant,
|
||||
mock_update_entities: list[MockUpdateEntity],
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test getting the release notes for entity that is unavailable."""
|
||||
entity = MockUpdateEntity(
|
||||
name="Update unavailable",
|
||||
unique_id="unavailable",
|
||||
installed_version="1.0.0",
|
||||
latest_version="1.0.1",
|
||||
available=False,
|
||||
supported_features=UpdateEntityFeature.RELEASE_NOTES,
|
||||
)
|
||||
|
||||
setup_test_component_platform(hass, DOMAIN, [entity])
|
||||
|
||||
assert await async_setup_component(hass, DOMAIN, {DOMAIN: {CONF_PLATFORM: "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "update/release_notes",
|
||||
"entity_id": "update.update_unavailable",
|
||||
}
|
||||
)
|
||||
result = await client.receive_json()
|
||||
assert result["error"]["code"] == "home_assistant_error"
|
||||
assert result["error"]["message"] == "Entity is not available"
|
||||
|
||||
|
||||
class MockFlow(ConfigFlow):
|
||||
"""Test flow."""
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ async def test_state_machine_updates_from_device_callbacks(
|
||||
| MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.GROUPING
|
||||
| MediaPlayerEntityFeature.SEEK
|
||||
)
|
||||
|
||||
@@ -130,7 +129,6 @@ async def test_state_machine_updates_from_device_callbacks(
|
||||
| MediaPlayerEntityFeature.BROWSE_MEDIA
|
||||
| MediaPlayerEntityFeature.PLAY_MEDIA
|
||||
| MediaPlayerEntityFeature.SELECT_SOURCE
|
||||
| MediaPlayerEntityFeature.GROUPING
|
||||
| MediaPlayerEntityFeature.SEEK
|
||||
| MediaPlayerEntityFeature.NEXT_TRACK
|
||||
| MediaPlayerEntityFeature.REPEAT_SET
|
||||
|
||||
@@ -4,7 +4,7 @@ from collections.abc import Generator
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from zinvolt.models import BatteryListResponse, BatteryState, BatteryUnit
|
||||
from zinvolt.models import BatteryListResponse, BatteryState, BatteryUnit, UnitsResponse
|
||||
|
||||
from homeassistant.components.zinvolt.const import DOMAIN
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN
|
||||
@@ -57,4 +57,7 @@ def mock_zinvolt_client() -> Generator[AsyncMock]:
|
||||
client.get_battery_unit.return_value = BatteryUnit.from_json(
|
||||
load_fixture("battery_unit.json", DOMAIN)
|
||||
)
|
||||
client.get_units.return_value = UnitsResponse.from_json(
|
||||
load_fixture("units.json", DOMAIN)
|
||||
).units
|
||||
yield client
|
||||
|
||||
48
tests/components/zinvolt/fixtures/units.json
Normal file
48
tests/components/zinvolt/fixtures/units.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"units": [
|
||||
{
|
||||
"usn": "INV001",
|
||||
"name": "Inverter",
|
||||
"type": "INVERTER",
|
||||
"abnormalAmount": 0,
|
||||
"resettable": false,
|
||||
"version": {
|
||||
"currentVersion": "V1.032",
|
||||
"status": "NO_UPDATE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"usn": "ems",
|
||||
"name": "EMS",
|
||||
"type": "EMS",
|
||||
"abnormalAmount": 0,
|
||||
"resettable": false,
|
||||
"version": {
|
||||
"currentVersion": "V1.01.45E",
|
||||
"status": "NO_UPDATE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"usn": "BAT001",
|
||||
"name": "Battery - 1",
|
||||
"type": "BATTERY",
|
||||
"abnormalAmount": 0,
|
||||
"resettable": false,
|
||||
"version": {
|
||||
"currentVersion": "V1.20",
|
||||
"status": "NO_UPDATE"
|
||||
}
|
||||
},
|
||||
{
|
||||
"usn": "BAT002",
|
||||
"name": "Battery - 2",
|
||||
"type": "BATTERY",
|
||||
"abnormalAmount": 0,
|
||||
"resettable": false,
|
||||
"version": {
|
||||
"currentVersion": "V1.20",
|
||||
"status": "NO_UPDATE"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,361 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[binary_sensor.battery_2_charge-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.battery_2_charge',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Charge',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Charge',
|
||||
'platform': 'zinvolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'charge',
|
||||
'unique_id': 'BAT002.charge',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_charge-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Battery - 2 Charge',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.battery_2_charge',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_communication-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.battery_2_communication',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Communication',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Communication',
|
||||
'platform': 'zinvolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'communication',
|
||||
'unique_id': 'BAT002.communication',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_communication-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Battery - 2 Communication',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.battery_2_communication',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_current-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.battery_2_current',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Current',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Current',
|
||||
'platform': 'zinvolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'current',
|
||||
'unique_id': 'BAT002.current',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_current-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Battery - 2 Current',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.battery_2_current',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_discharge-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.battery_2_discharge',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Discharge',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Discharge',
|
||||
'platform': 'zinvolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'discharge',
|
||||
'unique_id': 'BAT002.discharge',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_discharge-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Battery - 2 Discharge',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.battery_2_discharge',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_heat-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.battery_2_heat',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Heat',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.HEAT: 'heat'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Heat',
|
||||
'platform': 'zinvolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'temperature',
|
||||
'unique_id': 'BAT002.temperature',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_heat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'heat',
|
||||
'friendly_name': 'Battery - 2 Heat',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.battery_2_heat',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_other_problems-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.battery_2_other_problems',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Other problems',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Other problems',
|
||||
'platform': 'zinvolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'other',
|
||||
'unique_id': 'BAT002.other',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_other_problems-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Battery - 2 Other problems',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.battery_2_other_problems',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_voltage-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.battery_2_voltage',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Voltage',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Voltage',
|
||||
'platform': 'zinvolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'voltage',
|
||||
'unique_id': 'BAT002.voltage',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.battery_2_voltage-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'Battery - 2 Voltage',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.battery_2_voltage',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[binary_sensor.zinvolt_batterij_charge-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -4,6 +4,34 @@
|
||||
'coordinators': list([
|
||||
dict({
|
||||
'a125ef17-6bdf-45ad-b106-ce54e95e4634': dict({
|
||||
'batteries': dict({
|
||||
'BAT001': dict({
|
||||
'model': 'ZVS4000',
|
||||
'points': dict({
|
||||
'charge': True,
|
||||
'communication': True,
|
||||
'current': True,
|
||||
'discharge': True,
|
||||
'other': True,
|
||||
'temperature': True,
|
||||
'voltage': True,
|
||||
}),
|
||||
'sw_version': 'V1.02-V0.00.000',
|
||||
}),
|
||||
'BAT002': dict({
|
||||
'model': 'ZVS4000',
|
||||
'points': dict({
|
||||
'charge': True,
|
||||
'communication': True,
|
||||
'current': True,
|
||||
'discharge': True,
|
||||
'other': True,
|
||||
'temperature': True,
|
||||
'voltage': True,
|
||||
}),
|
||||
'sw_version': 'V1.02-V0.00.000',
|
||||
}),
|
||||
}),
|
||||
'battery': dict({
|
||||
'current_power': dict({
|
||||
'is_dormant': False,
|
||||
@@ -29,17 +57,6 @@
|
||||
'serial_number': 'ZVG011025120088',
|
||||
'smart_mode': 'CHARGED',
|
||||
}),
|
||||
'model': 'ZVS4000',
|
||||
'points': dict({
|
||||
'charge': True,
|
||||
'communication': True,
|
||||
'current': True,
|
||||
'discharge': True,
|
||||
'other': True,
|
||||
'temperature': True,
|
||||
'voltage': True,
|
||||
}),
|
||||
'sw_version': 'V1.02-V0.00.000',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# serializer version: 1
|
||||
# name: test_device
|
||||
# name: test_device[BAT002]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'config_entries_subentries': <ANY>,
|
||||
'configuration_url': None,
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'zinvolt',
|
||||
'BAT002',
|
||||
),
|
||||
}),
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'Zinvolt',
|
||||
'model': None,
|
||||
'model_id': 'ZVS4000',
|
||||
'name': 'Battery - 2',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': 'BAT002',
|
||||
'sw_version': 'V1.20',
|
||||
'via_device_id': <ANY>,
|
||||
})
|
||||
# ---
|
||||
# name: test_device[ZVG011025120088]
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
@@ -26,7 +57,7 @@
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': 'ZVG011025120088',
|
||||
'sw_version': 'V1.02-V0.00.000',
|
||||
'sw_version': 'V1.20',
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -4,7 +4,6 @@ from unittest.mock import AsyncMock
|
||||
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.zinvolt.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
@@ -22,6 +21,6 @@ async def test_device(
|
||||
) -> None:
|
||||
"""Test the Zinvolt device."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
device = device_registry.async_get_device({(DOMAIN, "ZVG011025120088")})
|
||||
assert device
|
||||
assert device == snapshot
|
||||
devices = device_registry.devices
|
||||
for device in devices.values():
|
||||
assert device == snapshot(name=list(device.identifiers)[0][1])
|
||||
|
||||
Reference in New Issue
Block a user