mirror of
https://github.com/home-assistant/core.git
synced 2026-03-01 21:36:53 +01:00
Compare commits
20 Commits
lg_infrare
...
ubisys_vir
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ae3e101d5 | ||
|
|
0aa66ed6cb | ||
|
|
6903463f14 | ||
|
|
a473010fee | ||
|
|
ddf7a783a8 | ||
|
|
513e4d52fe | ||
|
|
17bb14e260 | ||
|
|
cd1258464b | ||
|
|
d3f5e0e6d7 | ||
|
|
e124829364 | ||
|
|
87b83dcc1b | ||
|
|
be9b47539d | ||
|
|
be6ddc314c | ||
|
|
c6f8a7b7e4 | ||
|
|
53da5612e9 | ||
|
|
6cc56b76f9 | ||
|
|
03cb65d555 | ||
|
|
73dd024933 | ||
|
|
1c8c92bf8f | ||
|
|
9823e31206 |
5
homeassistant/brands/ubisys.json
Normal file
5
homeassistant/brands/ubisys.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"domain": "ubisys",
|
||||
"name": "Ubisys",
|
||||
"iot_standards": ["zigbee"]
|
||||
}
|
||||
@@ -191,7 +191,7 @@ class AccuWeatherEntity(
|
||||
{
|
||||
ATTR_FORECAST_TIME: utc_from_timestamp(item["EpochDate"]).isoformat(),
|
||||
ATTR_FORECAST_CLOUD_COVERAGE: item["CloudCoverDay"],
|
||||
ATTR_FORECAST_HUMIDITY: item["RelativeHumidityDay"]["Average"],
|
||||
ATTR_FORECAST_HUMIDITY: item["RelativeHumidityDay"].get("Average"),
|
||||
ATTR_FORECAST_NATIVE_TEMP: item["TemperatureMax"][ATTR_VALUE],
|
||||
ATTR_FORECAST_NATIVE_TEMP_LOW: item["TemperatureMin"][ATTR_VALUE],
|
||||
ATTR_FORECAST_NATIVE_APPARENT_TEMP: item["RealFeelTemperatureMax"][
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["env_canada"],
|
||||
"requirements": ["env-canada==0.12.4"]
|
||||
"requirements": ["env-canada==0.13.2"]
|
||||
}
|
||||
|
||||
@@ -151,8 +151,6 @@ class AFSAPIDevice(MediaPlayerEntity):
|
||||
# If call to get_volume fails set to 0 and try again next time.
|
||||
if not self._max_volume:
|
||||
self._max_volume = int(await afsapi.get_volume_steps() or 1) - 1
|
||||
if self._max_volume:
|
||||
self._attr_volume_step = 1 / self._max_volume
|
||||
|
||||
if self._attr_state != MediaPlayerState.OFF:
|
||||
info_name = await afsapi.get_play_name()
|
||||
@@ -241,6 +239,18 @@ class AFSAPIDevice(MediaPlayerEntity):
|
||||
await self.fs_device.set_mute(mute)
|
||||
|
||||
# volume
|
||||
async def async_volume_up(self) -> None:
|
||||
"""Send volume up command."""
|
||||
volume = await self.fs_device.get_volume()
|
||||
volume = int(volume or 0) + 1
|
||||
await self.fs_device.set_volume(min(volume, self._max_volume or 1))
|
||||
|
||||
async def async_volume_down(self) -> None:
|
||||
"""Send volume down command."""
|
||||
volume = await self.fs_device.get_volume()
|
||||
volume = int(volume or 0) - 1
|
||||
await self.fs_device.set_volume(max(volume, 0))
|
||||
|
||||
async def async_set_volume_level(self, volume: float) -> None:
|
||||
"""Set volume command."""
|
||||
if self._max_volume: # Can't do anything sensible if not set
|
||||
|
||||
@@ -78,6 +78,12 @@ query ($owner: String!, $repository: String!) {
|
||||
number
|
||||
}
|
||||
}
|
||||
merged_pull_request: pullRequests(
|
||||
first:1
|
||||
states: MERGED
|
||||
) {
|
||||
total: totalCount
|
||||
}
|
||||
release: latestRelease {
|
||||
name
|
||||
url
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
"latest_tag": {
|
||||
"default": "mdi:tag"
|
||||
},
|
||||
"merged_pulls_count": {
|
||||
"default": "mdi:source-merge"
|
||||
},
|
||||
"pulls_count": {
|
||||
"default": "mdi:source-pull"
|
||||
},
|
||||
|
||||
@@ -75,6 +75,13 @@ SENSOR_DESCRIPTIONS: tuple[GitHubSensorEntityDescription, ...] = (
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data["pull_request"]["total"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="merged_pulls_count",
|
||||
translation_key="merged_pulls_count",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
value_fn=lambda data: data["merged_pull_request"]["total"],
|
||||
),
|
||||
GitHubSensorEntityDescription(
|
||||
key="latest_commit",
|
||||
translation_key="latest_commit",
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
"latest_tag": {
|
||||
"name": "Latest tag"
|
||||
},
|
||||
"merged_pulls_count": {
|
||||
"name": "Merged pull requests",
|
||||
"unit_of_measurement": "pull requests"
|
||||
},
|
||||
"pulls_count": {
|
||||
"name": "Pull requests",
|
||||
"unit_of_measurement": "pull requests"
|
||||
|
||||
@@ -107,7 +107,6 @@ ABBREVIATIONS = {
|
||||
"modes": "modes",
|
||||
"name": "name",
|
||||
"o": "origin",
|
||||
"obj_id": "object_id",
|
||||
"off_dly": "off_delay",
|
||||
"on_cmd_type": "on_command_type",
|
||||
"ops": "options",
|
||||
|
||||
@@ -268,7 +268,6 @@ CONF_VIA_DEVICE = "via_device"
|
||||
CONF_DEPRECATED_VIA_HUB = "via_hub"
|
||||
CONF_SUGGESTED_AREA = "suggested_area"
|
||||
CONF_CONFIGURATION_URL = "configuration_url"
|
||||
CONF_OBJECT_ID = "object_id"
|
||||
CONF_SUPPORT_URL = "support_url"
|
||||
|
||||
DEFAULT_ALARM_CONTROL_PANEL_COMMAND_TEMPLATE = "{{action}}"
|
||||
|
||||
@@ -29,7 +29,6 @@ from homeassistant.const import (
|
||||
CONF_MODEL_ID,
|
||||
CONF_NAME,
|
||||
CONF_UNIQUE_ID,
|
||||
CONF_URL,
|
||||
CONF_VALUE_TEMPLATE,
|
||||
)
|
||||
from homeassistant.core import Event, HassJobType, HomeAssistant, callback
|
||||
@@ -84,8 +83,6 @@ from .const import (
|
||||
CONF_JSON_ATTRS_TEMPLATE,
|
||||
CONF_JSON_ATTRS_TOPIC,
|
||||
CONF_MANUFACTURER,
|
||||
CONF_OBJECT_ID,
|
||||
CONF_ORIGIN,
|
||||
CONF_PAYLOAD_AVAILABLE,
|
||||
CONF_PAYLOAD_NOT_AVAILABLE,
|
||||
CONF_QOS,
|
||||
@@ -1412,58 +1409,12 @@ class MqttEntity(
|
||||
"""Set entity_id from default_entity_id if defined in config."""
|
||||
object_id: str
|
||||
default_entity_id: str | None
|
||||
# Setting the default entity_id through the CONF_OBJECT_ID is deprecated
|
||||
# Support will be removed with HA Core 2026.4
|
||||
if (
|
||||
CONF_DEFAULT_ENTITY_ID not in self._config
|
||||
and CONF_OBJECT_ID not in self._config
|
||||
):
|
||||
return
|
||||
if (default_entity_id := self._config.get(CONF_DEFAULT_ENTITY_ID)) is None:
|
||||
object_id = self._config[CONF_OBJECT_ID]
|
||||
else:
|
||||
_, _, object_id = default_entity_id.partition(".")
|
||||
return
|
||||
_, _, object_id = default_entity_id.partition(".")
|
||||
self.entity_id = async_generate_entity_id(
|
||||
self._entity_id_format, object_id, None, self.hass
|
||||
)
|
||||
if CONF_OBJECT_ID in self._config:
|
||||
domain = self.entity_id.split(".")[0]
|
||||
if not self._discovery:
|
||||
async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
self.entity_id,
|
||||
issue_domain=DOMAIN,
|
||||
is_fixable=False,
|
||||
breaks_in_ha_version="2026.4",
|
||||
severity=IssueSeverity.WARNING,
|
||||
learn_more_url=f"{learn_more_url(domain)}#default_enity_id",
|
||||
translation_placeholders={
|
||||
"entity_id": self.entity_id,
|
||||
"object_id": self._config[CONF_OBJECT_ID],
|
||||
"domain": domain,
|
||||
},
|
||||
translation_key="deprecated_object_id",
|
||||
)
|
||||
elif CONF_DEFAULT_ENTITY_ID not in self._config:
|
||||
if CONF_ORIGIN in self._config:
|
||||
origin_name = self._config[CONF_ORIGIN][CONF_NAME]
|
||||
url = self._config[CONF_ORIGIN].get(CONF_URL)
|
||||
origin = f"[{origin_name}]({url})" if url else origin_name
|
||||
else:
|
||||
origin = "the integration"
|
||||
_LOGGER.warning(
|
||||
"The configuration for entity %s uses the deprecated option "
|
||||
"`object_id` to set the default entity id. Replace the "
|
||||
'`"object_id": "%s"` option with `"default_entity_id": '
|
||||
'"%s"` in your published discovery configuration to fix this '
|
||||
"issue, or contact the maintainer of %s that published this config "
|
||||
"to fix this. This will stop working in Home Assistant Core 2026.4",
|
||||
self.entity_id,
|
||||
self._config[CONF_OBJECT_ID],
|
||||
f"{domain}.{self._config[CONF_OBJECT_ID]}",
|
||||
origin,
|
||||
)
|
||||
|
||||
if self.unique_id is None:
|
||||
return
|
||||
@@ -1475,7 +1426,8 @@ class MqttEntity(
|
||||
(entity_platform, DOMAIN, self.unique_id)
|
||||
)
|
||||
) and deleted_entry.entity_id != self.entity_id:
|
||||
# Plan to update the entity_id basis on `object_id` if a deleted entity was found
|
||||
# Plan to update the entity_id based on `default_entity_id`
|
||||
# if a deleted entity was found
|
||||
self._update_registry_entity_id = self.entity_id
|
||||
|
||||
@final
|
||||
|
||||
@@ -42,7 +42,6 @@ from .const import (
|
||||
CONF_JSON_ATTRS_TEMPLATE,
|
||||
CONF_JSON_ATTRS_TOPIC,
|
||||
CONF_MANUFACTURER,
|
||||
CONF_OBJECT_ID,
|
||||
CONF_ORIGIN,
|
||||
CONF_PAYLOAD_AVAILABLE,
|
||||
CONF_PAYLOAD_NOT_AVAILABLE,
|
||||
@@ -173,7 +172,6 @@ MQTT_ENTITY_COMMON_SCHEMA = _MQTT_AVAILABILITY_SCHEMA.extend(
|
||||
vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic,
|
||||
vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template,
|
||||
vol.Optional(CONF_DEFAULT_ENTITY_ID): cv.string,
|
||||
vol.Optional(CONF_OBJECT_ID): cv.string,
|
||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1116,10 +1116,6 @@
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_object_id": {
|
||||
"description": "Entity {entity_id} uses the `object_id` option which is deprecated. To fix the issue, replace the `object_id: {object_id}` option with `default_entity_id: {domain}.{object_id}` in your \"configuration.yaml\", and restart Home Assistant.",
|
||||
"title": "Deprecated option object_id used"
|
||||
},
|
||||
"invalid_platform_config": {
|
||||
"description": "Home Assistant detected an invalid config for a manually configured item.\n\nPlatform domain: **{domain}**\nConfiguration file: **{config_file}**\nNear line: **{line}**\nConfiguration found:\n```yaml\n{config}\n```\nError: **{error}**.\n\nMake sure the configuration is valid and [reload](/config/developer-tools/yaml) the manually configured MQTT items or restart Home Assistant to fix this issue.",
|
||||
"title": "Invalid config found for MQTT {domain} item"
|
||||
|
||||
@@ -7,7 +7,7 @@ import asyncio
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError, web
|
||||
from aiohttp import ClientError, web
|
||||
from google_nest_sdm.camera_traits import CameraClipPreviewTrait
|
||||
from google_nest_sdm.device import Device
|
||||
from google_nest_sdm.device_manager import DeviceManager
|
||||
@@ -43,6 +43,8 @@ from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
HomeAssistantError,
|
||||
OAuth2TokenRequestError,
|
||||
OAuth2TokenRequestReauthError,
|
||||
Unauthorized,
|
||||
)
|
||||
from homeassistant.helpers import (
|
||||
@@ -253,11 +255,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: NestConfigEntry) -> bool
|
||||
auth = await api.new_auth(hass, entry)
|
||||
try:
|
||||
await auth.async_get_access_token()
|
||||
except ClientResponseError as err:
|
||||
if 400 <= err.status < 500:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN, translation_key="reauth_required"
|
||||
) from err
|
||||
except OAuth2TokenRequestReauthError as err:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN, translation_key="reauth_required"
|
||||
) from err
|
||||
except OAuth2TokenRequestError as err:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN, translation_key="auth_server_error"
|
||||
) from err
|
||||
|
||||
@@ -69,7 +69,7 @@ class OverseerrConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
if self.source == SOURCE_USER:
|
||||
return self.async_create_entry(
|
||||
title="Overseerr",
|
||||
title="Seerr",
|
||||
data={
|
||||
CONF_HOST: host,
|
||||
CONF_PORT: port,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"domain": "overseerr",
|
||||
"name": "Overseerr",
|
||||
"name": "Seerr",
|
||||
"after_dependencies": ["cloud"],
|
||||
"codeowners": ["@joostlek", "@AmGarera"],
|
||||
"config_flow": true,
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
"url": "[%key:common::config_flow::data::url%]"
|
||||
},
|
||||
"data_description": {
|
||||
"api_key": "The API key of the Overseerr instance.",
|
||||
"url": "The URL of the Overseerr instance."
|
||||
"api_key": "The API key of the Seerr instance.",
|
||||
"url": "The URL of the Seerr instance."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,11 +137,11 @@
|
||||
},
|
||||
"services": {
|
||||
"get_requests": {
|
||||
"description": "Retrieves a list of media requests from Overseerr.",
|
||||
"description": "Retrieves a list of media requests from Seerr.",
|
||||
"fields": {
|
||||
"config_entry_id": {
|
||||
"description": "The Overseerr instance to get requests from.",
|
||||
"name": "Overseerr instance"
|
||||
"description": "The Seerr instance to get requests from.",
|
||||
"name": "Seerr instance"
|
||||
},
|
||||
"requested_by": {
|
||||
"description": "Filter the requests by the user ID that requested them.",
|
||||
|
||||
@@ -49,12 +49,12 @@ class PowerfoxLocalDataUpdateCoordinator(DataUpdateCoordinator[LocalResponse]):
|
||||
except PowerfoxAuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
translation_placeholders={"error": str(err)},
|
||||
translation_key="auth_failed",
|
||||
translation_placeholders={"host": self.config_entry.data[CONF_HOST]},
|
||||
) from err
|
||||
except PowerfoxConnectionError as err:
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_failed",
|
||||
translation_placeholders={"error": str(err)},
|
||||
translation_key="connection_error",
|
||||
translation_placeholders={"host": self.config_entry.data[CONF_HOST]},
|
||||
) from err
|
||||
|
||||
@@ -56,11 +56,11 @@
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"invalid_auth": {
|
||||
"message": "Error while authenticating with the device: {error}"
|
||||
"auth_failed": {
|
||||
"message": "Authentication with the Poweropti device at {host} failed. Please check your API key."
|
||||
},
|
||||
"update_failed": {
|
||||
"message": "Error while updating the device: {error}"
|
||||
"connection_error": {
|
||||
"message": "Could not connect to the Poweropti device at {host}. Please check if the device is online and reachable."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"missing_output_access_code": {
|
||||
"message": "Cannot control switchable outputs because no user code is configured for this Satel Integra entry. Configure a code in the integration options to enable output control."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_yaml_import_issue_cannot_connect": {
|
||||
"description": "Configuring {integration_title} using YAML is being removed but there was an connection error importing your existing configuration.\n\nEnsure connection to {integration_title} works and restart Home Assistant to try again or remove the `{domain}` YAML configuration from your configuration.yaml file and add the {integration_title} integration manually.",
|
||||
|
||||
@@ -8,9 +8,14 @@ from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.config_entries import ConfigSubentry
|
||||
from homeassistant.const import CONF_CODE
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import CONF_SWITCHABLE_OUTPUT_NUMBER, SUBENTRY_TYPE_SWITCHABLE_OUTPUT
|
||||
from .const import (
|
||||
CONF_SWITCHABLE_OUTPUT_NUMBER,
|
||||
DOMAIN,
|
||||
SUBENTRY_TYPE_SWITCHABLE_OUTPUT,
|
||||
)
|
||||
from .coordinator import SatelConfigEntry, SatelIntegraOutputsCoordinator
|
||||
from .entity import SatelIntegraEntity
|
||||
|
||||
@@ -83,12 +88,24 @@ class SatelIntegraSwitch(
|
||||
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Turn the device on."""
|
||||
if self._code is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="missing_output_access_code",
|
||||
)
|
||||
|
||||
await self._controller.set_output(self._code, self._device_number, True)
|
||||
self._attr_is_on = True
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Turn the device off."""
|
||||
if self._code is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="missing_output_access_code",
|
||||
)
|
||||
|
||||
await self._controller.set_output(self._code, self._device_number, False)
|
||||
self._attr_is_on = False
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmarlaapi", "pysignalr"],
|
||||
"quality_scale": "bronze",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["pysmarlaapi==1.0.1"]
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class SmartThingsButtonDescription(ButtonEntityDescription):
|
||||
|
||||
key: Capability
|
||||
command: Command
|
||||
component: str = MAIN
|
||||
|
||||
|
||||
CAPABILITIES_TO_BUTTONS: dict[Capability | str, SmartThingsButtonDescription] = {
|
||||
@@ -42,6 +43,13 @@ CAPABILITIES_TO_BUTTONS: dict[Capability | str, SmartThingsButtonDescription] =
|
||||
command=Command.RESET_HOOD_FILTER,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
Capability.CUSTOM_HEPA_FILTER: SmartThingsButtonDescription(
|
||||
key=Capability.CUSTOM_HEPA_FILTER,
|
||||
translation_key="reset_hepa_filter",
|
||||
command=Command.RESET_HEPA_FILTER,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
component="station",
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -53,12 +61,11 @@ async def async_setup_entry(
|
||||
"""Add button entities for a config entry."""
|
||||
entry_data = entry.runtime_data
|
||||
async_add_entities(
|
||||
SmartThingsButtonEntity(
|
||||
entry_data.client, device, CAPABILITIES_TO_BUTTONS[capability]
|
||||
)
|
||||
SmartThingsButtonEntity(entry_data.client, device, description)
|
||||
for capability, description in CAPABILITIES_TO_BUTTONS.items()
|
||||
for device in entry_data.devices.values()
|
||||
for capability in device.status[MAIN]
|
||||
if capability in CAPABILITIES_TO_BUTTONS
|
||||
if description.component in device.status
|
||||
and capability in device.status[description.component]
|
||||
)
|
||||
|
||||
|
||||
@@ -74,9 +81,9 @@ class SmartThingsButtonEntity(SmartThingsEntity, ButtonEntity):
|
||||
entity_description: SmartThingsButtonDescription,
|
||||
) -> None:
|
||||
"""Initialize the instance."""
|
||||
super().__init__(client, device, set())
|
||||
super().__init__(client, device, set(), component=entity_description.component)
|
||||
self.entity_description = entity_description
|
||||
self._attr_unique_id = f"{device.device.device_id}_{MAIN}_{entity_description.key}_{entity_description.command}"
|
||||
self._attr_unique_id = f"{device.device.device_id}_{entity_description.component}_{entity_description.key}_{entity_description.command}"
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"reset_hepa_filter": {
|
||||
"default": "mdi:air-filter"
|
||||
},
|
||||
"reset_water_filter": {
|
||||
"default": "mdi:reload"
|
||||
},
|
||||
@@ -93,6 +96,9 @@
|
||||
"stop": "mdi:stop"
|
||||
}
|
||||
},
|
||||
"robot_cleaner_driving_mode": {
|
||||
"default": "mdi:car-cog"
|
||||
},
|
||||
"selected_zone": {
|
||||
"state": {
|
||||
"all": "mdi:card",
|
||||
@@ -219,6 +225,9 @@
|
||||
"sanitizing_wash": {
|
||||
"default": "mdi:lotion"
|
||||
},
|
||||
"sound_detection": {
|
||||
"default": "mdi:home-sound-in"
|
||||
},
|
||||
"sound_effect": {
|
||||
"default": "mdi:volume-high",
|
||||
"state": {
|
||||
|
||||
@@ -26,6 +26,12 @@ LAMP_TO_HA = {
|
||||
"off": "off",
|
||||
}
|
||||
|
||||
DRIVING_MODE_TO_HA = {
|
||||
"areaThenWalls": "area_then_walls",
|
||||
"wallFirst": "walls_first",
|
||||
"quickCleaningZigzagPattern": "quick_clean_zigzag_pattern",
|
||||
}
|
||||
|
||||
WASHER_SOIL_LEVEL_TO_HA = {
|
||||
"none": "none",
|
||||
"heavy": "heavy",
|
||||
@@ -187,6 +193,15 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
options_map=WASHER_WATER_TEMPERATURE_TO_HA,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_DRIVING_MODE: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_ROBOT_CLEANER_DRIVING_MODE,
|
||||
translation_key="robot_cleaner_driving_mode",
|
||||
options_attribute=Attribute.SUPPORTED_DRIVING_MODES,
|
||||
status_attribute=Attribute.DRIVING_MODE,
|
||||
command=Command.SET_DRIVING_MODE,
|
||||
options_map=DRIVING_MODE_TO_HA,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
Capability.SAMSUNG_CE_DUST_FILTER_ALARM: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_DUST_FILTER_ALARM,
|
||||
translation_key="dust_filter_alarm",
|
||||
|
||||
@@ -84,6 +84,9 @@
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"reset_hepa_filter": {
|
||||
"name": "Reset HEPA filter"
|
||||
},
|
||||
"reset_hood_filter": {
|
||||
"name": "Reset filter"
|
||||
},
|
||||
@@ -223,6 +226,14 @@
|
||||
"stop": "[%key:common::state::stopped%]"
|
||||
}
|
||||
},
|
||||
"robot_cleaner_driving_mode": {
|
||||
"name": "Driving mode",
|
||||
"state": {
|
||||
"area_then_walls": "Area then walls",
|
||||
"quick_clean_zigzag_pattern": "Quick clean in a zigzag pattern",
|
||||
"walls_first": "Walls first"
|
||||
}
|
||||
},
|
||||
"selected_zone": {
|
||||
"name": "Selected zone",
|
||||
"state": {
|
||||
@@ -904,6 +915,9 @@
|
||||
"sanitizing_wash": {
|
||||
"name": "Sanitizing wash"
|
||||
},
|
||||
"sound_detection": {
|
||||
"name": "Sound detection"
|
||||
},
|
||||
"sound_effect": {
|
||||
"name": "Sound effect"
|
||||
},
|
||||
@@ -919,6 +933,20 @@
|
||||
"wrinkle_prevent": {
|
||||
"name": "Wrinkle prevent"
|
||||
}
|
||||
},
|
||||
"vacuum": {
|
||||
"vacuum": {
|
||||
"state_attributes": {
|
||||
"fan_speed": {
|
||||
"state": {
|
||||
"maximum": "Maximum",
|
||||
"normal": "Normal",
|
||||
"quiet": "Quiet",
|
||||
"smart": "Smart"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -170,6 +170,15 @@ CAPABILITY_TO_SWITCHES: dict[Capability | str, SmartThingsSwitchEntityDescriptio
|
||||
on_command=Command.DO_NOT_DISTURB_ON,
|
||||
off_command=Command.DO_NOT_DISTURB_OFF,
|
||||
),
|
||||
Capability.SOUND_DETECTION: SmartThingsSwitchEntityDescription(
|
||||
key=Capability.SOUND_DETECTION,
|
||||
translation_key="sound_detection",
|
||||
status_attribute=Attribute.SOUND_DETECTION_STATE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
on_key="enabled",
|
||||
on_command=Command.ENABLE_SOUND_DETECTION,
|
||||
off_command=Command.DISABLE_SOUND_DETECTION,
|
||||
),
|
||||
}
|
||||
DISHWASHER_WASHING_OPTIONS_TO_SWITCHES: dict[
|
||||
Attribute | str, SmartThingsDishwasherWashingOptionSwitchEntityDescription
|
||||
|
||||
@@ -22,6 +22,15 @@ from .entity import SmartThingsEntity
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TURBO_MODE_TO_FAN_SPEED = {
|
||||
"silence": "normal",
|
||||
"on": "maximum",
|
||||
"off": "smart",
|
||||
"extraSilence": "quiet",
|
||||
}
|
||||
|
||||
FAN_SPEED_TO_TURBO_MODE = {v: k for k, v in TURBO_MODE_TO_FAN_SPEED.items()}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -41,20 +50,26 @@ class SamsungJetBotVacuum(SmartThingsEntity, StateVacuumEntity):
|
||||
"""Representation of a Vacuum."""
|
||||
|
||||
_attr_name = None
|
||||
_attr_supported_features = (
|
||||
VacuumEntityFeature.START
|
||||
| VacuumEntityFeature.RETURN_HOME
|
||||
| VacuumEntityFeature.PAUSE
|
||||
| VacuumEntityFeature.STATE
|
||||
)
|
||||
_attr_translation_key = "vacuum"
|
||||
|
||||
def __init__(self, client: SmartThings, device: FullDevice) -> None:
|
||||
"""Initialize the Samsung robot cleaner vacuum entity."""
|
||||
super().__init__(
|
||||
client,
|
||||
device,
|
||||
{Capability.SAMSUNG_CE_ROBOT_CLEANER_OPERATING_STATE},
|
||||
{
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_OPERATING_STATE,
|
||||
Capability.ROBOT_CLEANER_TURBO_MODE,
|
||||
},
|
||||
)
|
||||
self._attr_supported_features = (
|
||||
VacuumEntityFeature.START
|
||||
| VacuumEntityFeature.RETURN_HOME
|
||||
| VacuumEntityFeature.PAUSE
|
||||
| VacuumEntityFeature.STATE
|
||||
)
|
||||
if self.supports_capability(Capability.ROBOT_CLEANER_TURBO_MODE):
|
||||
self._attr_supported_features |= VacuumEntityFeature.FAN_SPEED
|
||||
|
||||
@property
|
||||
def activity(self) -> VacuumActivity | None:
|
||||
@@ -74,6 +89,23 @@ class SamsungJetBotVacuum(SmartThingsEntity, StateVacuumEntity):
|
||||
"charging": VacuumActivity.DOCKED,
|
||||
}.get(status)
|
||||
|
||||
@property
|
||||
def fan_speed_list(self) -> list[str]:
|
||||
"""Return the list of available fan speeds."""
|
||||
if not self.supports_capability(Capability.ROBOT_CLEANER_TURBO_MODE):
|
||||
return []
|
||||
return list(TURBO_MODE_TO_FAN_SPEED.values())
|
||||
|
||||
@property
|
||||
def fan_speed(self) -> str | None:
|
||||
"""Return the current fan speed."""
|
||||
if not self.supports_capability(Capability.ROBOT_CLEANER_TURBO_MODE):
|
||||
return None
|
||||
turbo_mode = self.get_attribute_value(
|
||||
Capability.ROBOT_CLEANER_TURBO_MODE, Attribute.ROBOT_CLEANER_TURBO_MODE
|
||||
)
|
||||
return TURBO_MODE_TO_FAN_SPEED.get(turbo_mode)
|
||||
|
||||
async def async_start(self) -> None:
|
||||
"""Start the vacuum's operation."""
|
||||
await self.execute_device_command(
|
||||
@@ -93,3 +125,12 @@ class SamsungJetBotVacuum(SmartThingsEntity, StateVacuumEntity):
|
||||
Capability.SAMSUNG_CE_ROBOT_CLEANER_OPERATING_STATE,
|
||||
Command.RETURN_TO_HOME,
|
||||
)
|
||||
|
||||
async def async_set_fan_speed(self, fan_speed: str, **kwargs: Any) -> None:
|
||||
"""Set the fan speed."""
|
||||
turbo_mode = FAN_SPEED_TO_TURBO_MODE[fan_speed]
|
||||
await self.execute_device_command(
|
||||
Capability.ROBOT_CLEANER_TURBO_MODE,
|
||||
Command.SET_ROBOT_CLEANER_TURBO_MODE,
|
||||
turbo_mode,
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from functools import partial
|
||||
from typing import Any, Final
|
||||
from typing import Any, Final, cast
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError
|
||||
from tesla_fleet_api.const import Scope
|
||||
@@ -106,7 +106,7 @@ async def _get_access_token(oauth_session: OAuth2Session) -> str:
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="not_ready_connection_error",
|
||||
) from err
|
||||
return str(oauth_session.token[CONF_ACCESS_TOKEN])
|
||||
return cast(str, oauth_session.token[CONF_ACCESS_TOKEN])
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -> bool:
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["tessie", "tesla-fleet-api"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["tessie-api==0.1.1", "tesla-fleet-api==1.4.3"]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,10 @@ rules:
|
||||
comment: |
|
||||
No custom actions are defined. Only entity-based actions exist.
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters: todo
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: |
|
||||
No options flow and no configurable options after initial setup.
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/weheat",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["weheat==2026.1.25"]
|
||||
"requirements": ["weheat==2026.2.28"]
|
||||
}
|
||||
|
||||
@@ -5036,7 +5036,7 @@
|
||||
"iot_class": "local_polling"
|
||||
},
|
||||
"overseerr": {
|
||||
"name": "Overseerr",
|
||||
"name": "Seerr",
|
||||
"integration_type": "service",
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
@@ -7338,6 +7338,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ubisys": {
|
||||
"name": "Ubisys",
|
||||
"iot_standards": [
|
||||
"zigbee"
|
||||
]
|
||||
},
|
||||
"ubiwizz": {
|
||||
"name": "Ubiwizz",
|
||||
"integration_type": "virtual",
|
||||
|
||||
4
requirements_all.txt
generated
4
requirements_all.txt
generated
@@ -906,7 +906,7 @@ enocean==0.50
|
||||
enturclient==0.2.4
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.12.4
|
||||
env-canada==0.13.2
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.6
|
||||
@@ -3256,7 +3256,7 @@ webio-api==0.1.12
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2026.1.25
|
||||
weheat==2026.2.28
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==1.0.3
|
||||
|
||||
4
requirements_test_all.txt
generated
4
requirements_test_all.txt
generated
@@ -797,7 +797,7 @@ energyzero==4.0.1
|
||||
enocean==0.50
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env-canada==0.12.4
|
||||
env-canada==0.13.2
|
||||
|
||||
# homeassistant.components.season
|
||||
ephem==4.1.6
|
||||
@@ -2741,7 +2741,7 @@ webio-api==0.1.12
|
||||
webmin-xmlrpc==0.0.2
|
||||
|
||||
# homeassistant.components.weheat
|
||||
weheat==2026.1.25
|
||||
weheat==2026.2.28
|
||||
|
||||
# homeassistant.components.whirlpool
|
||||
whirlpool-sixth-sense==1.0.3
|
||||
|
||||
@@ -1945,7 +1945,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"template",
|
||||
"tesla_fleet",
|
||||
"tesla_wall_connector",
|
||||
"tessie",
|
||||
"tfiac",
|
||||
"thermobeacon",
|
||||
"thermopro",
|
||||
|
||||
@@ -136,6 +136,31 @@ async def test_forecast_service(
|
||||
assert response == snapshot
|
||||
|
||||
|
||||
async def test_forecast_daily_missing_average_humidity(
|
||||
hass: HomeAssistant,
|
||||
mock_accuweather_client: AsyncMock,
|
||||
) -> None:
|
||||
"""Test daily forecast does not crash when average humidity is missing."""
|
||||
mock_accuweather_client.async_get_daily_forecast.return_value[0][
|
||||
"RelativeHumidityDay"
|
||||
] = {}
|
||||
|
||||
await init_integration(hass)
|
||||
|
||||
response = await hass.services.async_call(
|
||||
WEATHER_DOMAIN,
|
||||
SERVICE_GET_FORECASTS,
|
||||
{
|
||||
"entity_id": "weather.home",
|
||||
"type": "daily",
|
||||
},
|
||||
blocking=True,
|
||||
return_response=True,
|
||||
)
|
||||
|
||||
assert response["weather.home"]["forecast"][0].get("humidity") is None
|
||||
|
||||
|
||||
async def test_forecast_subscription(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
|
||||
@@ -194,7 +194,7 @@ async def test_zone_services_with_ctl_id(
|
||||
) -> None:
|
||||
"""Test calling zone-only services with a non-zone entity_id fail."""
|
||||
|
||||
with pytest.raises(ServiceValidationError) as excinfo:
|
||||
with pytest.raises(ServiceValidationError) as exc_info:
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
service,
|
||||
@@ -203,4 +203,5 @@ async def test_zone_services_with_ctl_id(
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert excinfo.value.translation_key == "zone_only_service"
|
||||
assert exc_info.value.translation_key == "zone_only_service"
|
||||
assert exc_info.value.translation_placeholders == {"service": service}
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"merged_pull_request": {
|
||||
"total": 42
|
||||
},
|
||||
"release": {
|
||||
"name": "v1.0.0",
|
||||
"url": "https://github.com/octocat/Hello-World/releases/v1.0.0",
|
||||
|
||||
@@ -1330,257 +1330,6 @@ async def test_discover_alarm_control_panel(
|
||||
].discovery_already_discovered
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("topic", "config", "entity_id", "name", "domain", "deprecation_warning"),
|
||||
[
|
||||
(
|
||||
"homeassistant/alarm_control_panel/object/bla/config",
|
||||
'{ "name": "Hello World 1", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"alarm_control_panel.hello_id",
|
||||
"Hello World 1",
|
||||
"alarm_control_panel",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/binary_sensor/object/bla/config",
|
||||
'{ "name": "Hello World 2", "obj_id": "hello_id", "state_topic": "test-topic" }',
|
||||
"binary_sensor.hello_id",
|
||||
"Hello World 2",
|
||||
"binary_sensor",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/button/object/bla/config",
|
||||
'{ "name": "Hello World button", "obj_id": "hello_id", "command_topic": "test-topic" }',
|
||||
"button.hello_id",
|
||||
"Hello World button",
|
||||
"button",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/camera/object/bla/config",
|
||||
'{ "name": "Hello World 3", "obj_id": "hello_id", "state_topic": "test-topic", "topic": "test-topic" }',
|
||||
"camera.hello_id",
|
||||
"Hello World 3",
|
||||
"camera",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/climate/object/bla/config",
|
||||
'{ "name": "Hello World 4", "obj_id": "hello_id", "state_topic": "test-topic" }',
|
||||
"climate.hello_id",
|
||||
"Hello World 4",
|
||||
"climate",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/cover/object/bla/config",
|
||||
'{ "name": "Hello World 5", "obj_id": "hello_id", "state_topic": "test-topic" }',
|
||||
"cover.hello_id",
|
||||
"Hello World 5",
|
||||
"cover",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/fan/object/bla/config",
|
||||
'{ "name": "Hello World 6", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"fan.hello_id",
|
||||
"Hello World 6",
|
||||
"fan",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/humidifier/object/bla/config",
|
||||
'{ "name": "Hello World 7", "obj_id": "hello_id", "state_topic": "test-topic", "target_humidity_command_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"humidifier.hello_id",
|
||||
"Hello World 7",
|
||||
"humidifier",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/number/object/bla/config",
|
||||
'{ "name": "Hello World 8", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"number.hello_id",
|
||||
"Hello World 8",
|
||||
"number",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/scene/object/bla/config",
|
||||
'{ "name": "Hello World 9", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"scene.hello_id",
|
||||
"Hello World 9",
|
||||
"scene",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/select/object/bla/config",
|
||||
'{ "name": "Hello World 10", "obj_id": "hello_id", "state_topic": "test-topic", "options": [ "opt1", "opt2" ], "command_topic": "test-topic" }',
|
||||
"select.hello_id",
|
||||
"Hello World 10",
|
||||
"select",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/sensor/object/bla/config",
|
||||
'{ "name": "Hello World 11", "obj_id": "hello_id", "state_topic": "test-topic" }',
|
||||
"sensor.hello_id",
|
||||
"Hello World 11",
|
||||
"sensor",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/switch/object/bla/config",
|
||||
'{ "name": "Hello World 12", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"switch.hello_id",
|
||||
"Hello World 12",
|
||||
"switch",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/light/object/bla/config",
|
||||
'{ "name": "Hello World 13", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"light.hello_id",
|
||||
"Hello World 13",
|
||||
"light",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/light/object/bla/config",
|
||||
'{ "name": "Hello World 14", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic", "schema": "json" }',
|
||||
"light.hello_id",
|
||||
"Hello World 14",
|
||||
"light",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/light/object/bla/config",
|
||||
'{ "name": "Hello World 15", "obj_id": "hello_id", "state_topic": "test-topic", "command_off_template": "template", "command_on_template": "template", "command_topic": "test-topic", "schema": "template" }',
|
||||
"light.hello_id",
|
||||
"Hello World 15",
|
||||
"light",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/vacuum/object/bla/config",
|
||||
'{ "name": "Hello World 16", "obj_id": "hello_id", "state_topic": "test-topic", "schema": "state" }',
|
||||
"vacuum.hello_id",
|
||||
"Hello World 16",
|
||||
"vacuum",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/valve/object/bla/config",
|
||||
'{ "name": "Hello World 17", "obj_id": "hello_id", "state_topic": "test-topic" }',
|
||||
"valve.hello_id",
|
||||
"Hello World 17",
|
||||
"valve",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/lock/object/bla/config",
|
||||
'{ "name": "Hello World 18", "obj_id": "hello_id", "state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"lock.hello_id",
|
||||
"Hello World 18",
|
||||
"lock",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/device_tracker/object/bla/config",
|
||||
'{ "name": "Hello World 19", "obj_id": "hello_id", "state_topic": "test-topic" }',
|
||||
"device_tracker.hello_id",
|
||||
"Hello World 19",
|
||||
"device_tracker",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/binary_sensor/object/bla/config",
|
||||
'{ "name": "Hello World 2", "obj_id": "hello_id", '
|
||||
'"o": {"name": "X2mqtt"}, "state_topic": "test-topic" }',
|
||||
"binary_sensor.hello_id",
|
||||
"Hello World 2",
|
||||
"binary_sensor",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/button/object/bla/config",
|
||||
'{ "name": "Hello World button", "obj_id": "hello_id", '
|
||||
'"o": {"name": "X2mqtt", "url": "https://example.com/x2mqtt"}, '
|
||||
'"command_topic": "test-topic" }',
|
||||
"button.hello_id",
|
||||
"Hello World button",
|
||||
"button",
|
||||
True,
|
||||
),
|
||||
(
|
||||
"homeassistant/alarm_control_panel/object/bla/config",
|
||||
'{ "name": "Hello World 1", "def_ent_id": "alarm_control_panel.hello_id", '
|
||||
'"state_topic": "test-topic", "command_topic": "test-topic" }',
|
||||
"alarm_control_panel.hello_id",
|
||||
"Hello World 1",
|
||||
"alarm_control_panel",
|
||||
False,
|
||||
),
|
||||
(
|
||||
"homeassistant/binary_sensor/object/bla/config",
|
||||
'{ "name": "Hello World 2", "def_ent_id": "binary_sensor.hello_id", '
|
||||
'"o": {"name": "X2mqtt"}, "state_topic": "test-topic" }',
|
||||
"binary_sensor.hello_id",
|
||||
"Hello World 2",
|
||||
"binary_sensor",
|
||||
False,
|
||||
),
|
||||
(
|
||||
"homeassistant/button/object/bla/config",
|
||||
'{ "name": "Hello World button", "def_ent_id": "button.hello_id", '
|
||||
'"o": {"name": "X2mqtt", "url": "https://example.com/x2mqtt"}, '
|
||||
'"command_topic": "test-topic" }',
|
||||
"button.hello_id",
|
||||
"Hello World button",
|
||||
"button",
|
||||
False,
|
||||
),
|
||||
(
|
||||
"homeassistant/button/object/bla/config",
|
||||
'{ "name": "Hello World button", "def_ent_id": "button.hello_id", '
|
||||
'"obj_id": "hello_id_old", '
|
||||
'"o": {"name": "X2mqtt", "url": "https://example.com/x2mqtt"}, '
|
||||
'"command_topic": "test-topic" }',
|
||||
"button.hello_id",
|
||||
"Hello World button",
|
||||
"button",
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_discovery_with_object_id(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
topic: str,
|
||||
config: str,
|
||||
entity_id: str,
|
||||
name: str,
|
||||
domain: str,
|
||||
deprecation_warning: bool,
|
||||
) -> None:
|
||||
"""Test discovering an MQTT entity with object_id."""
|
||||
await mqtt_mock_entry()
|
||||
async_fire_mqtt_message(hass, topic, config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
assert state is not None
|
||||
assert state.name == name
|
||||
assert (domain, "object bla") in hass.data["mqtt"].discovery_already_discovered
|
||||
|
||||
assert (
|
||||
f"The configuration for entity {domain}.hello_id uses the deprecated option `object_id`"
|
||||
in caplog.text
|
||||
) is deprecation_warning
|
||||
|
||||
|
||||
async def test_discovery_with_default_entity_id_for_previous_deleted_entity(
|
||||
hass: HomeAssistant,
|
||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||
|
||||
@@ -468,40 +468,6 @@ async def test_value_template_fails(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"hass_config",
|
||||
[
|
||||
{
|
||||
mqtt.DOMAIN: {
|
||||
sensor.DOMAIN: {
|
||||
"name": "test",
|
||||
"state_topic": "test-topic",
|
||||
"object_id": "test",
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
)
|
||||
async def test_deprecated_option_object_id_is_used_in_yaml(
|
||||
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
||||
) -> None:
|
||||
"""Test issue registry in case the deprecated option object_id was used in YAML."""
|
||||
await mqtt_mock_entry()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.test")
|
||||
assert state is not None
|
||||
|
||||
issue_registry = ir.async_get(hass)
|
||||
issue = issue_registry.async_get_issue(mqtt.DOMAIN, "sensor.test")
|
||||
assert issue is not None
|
||||
assert issue.translation_placeholders == {
|
||||
"entity_id": "sensor.test",
|
||||
"object_id": "test",
|
||||
"domain": "sensor",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mqtt_config_subentries_data",
|
||||
[
|
||||
|
||||
@@ -54,7 +54,7 @@ async def test_full_flow(
|
||||
{CONF_URL: "http://overseerr.test", CONF_API_KEY: "test-key"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Overseerr"
|
||||
assert result["title"] == "Seerr"
|
||||
assert result["data"] == {
|
||||
CONF_HOST: "overseerr.test",
|
||||
CONF_PORT: 80,
|
||||
|
||||
@@ -15,12 +15,14 @@ from homeassistant.components.switch import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_CODE,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.device_registry import DeviceRegistry
|
||||
from homeassistant.helpers.entity_registry import EntityRegistry
|
||||
|
||||
@@ -176,3 +178,35 @@ async def test_switch_last_reported(
|
||||
|
||||
assert first_reported != hass.states.get("switch.switchable_output").last_reported
|
||||
assert len(events) == 1 # last_reported shall not fire state_changed
|
||||
|
||||
|
||||
async def test_switch_actions_require_code(
|
||||
hass: HomeAssistant,
|
||||
mock_satel: AsyncMock,
|
||||
mock_config_entry_with_subentries: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test switch actions fail when access code is missing."""
|
||||
|
||||
await setup_integration(hass, mock_config_entry_with_subentries)
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
mock_config_entry_with_subentries, options={CONF_CODE: None}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Turning the device on or off should raise ServiceValidationError.
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "switch.switchable_output"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "switch.switchable_output"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
},
|
||||
"robotCleanerTurboMode": {
|
||||
"robotCleanerTurboMode": {
|
||||
"value": "off",
|
||||
"value": "on",
|
||||
"timestamp": "2026-02-27T10:49:04.309Z"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -440,3 +440,52 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][button.robot_vacuum_reset_hepa_filter-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.robot_vacuum_reset_hepa_filter',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset HEPA filter',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset HEPA filter',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_hepa_filter',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_station_custom.hepaFilter_resetHepaFilter',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][button.robot_vacuum_reset_hepa_filter-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Reset HEPA filter',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.robot_vacuum_reset_hepa_filter',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -415,6 +415,66 @@
|
||||
'state': 'high',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_driving_mode-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'area_then_walls',
|
||||
'walls_first',
|
||||
'quick_clean_zigzag_pattern',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.robot_vacuum_driving_mode',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Driving mode',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Driving mode',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'robot_cleaner_driving_mode',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_samsungce.robotCleanerDrivingMode_drivingMode_drivingMode',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_driving_mode-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Driving mode',
|
||||
'options': list([
|
||||
'area_then_walls',
|
||||
'walls_first',
|
||||
'quick_clean_zigzag_pattern',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.robot_vacuum_driving_mode',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'area_then_walls',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][select.robot_vacuum_lamp-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -11016,7 +11016,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_normal_000001][sensor.robot_vacuum_battery-entry]
|
||||
|
||||
@@ -1028,6 +1028,55 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum_sound_detection-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'switch',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'switch.robot_vacuum_sound_detection',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sound detection',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sound detection',
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sound_detection',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main_soundDetection_soundDetectionState_soundDetectionState',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_map_01011][switch.robot_vacuum_sound_detection-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Robot Vacuum Sound detection',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'switch.robot_vacuum_sound_detection',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[da_rvc_normal_000001][switch.robot_vacuum-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -4,7 +4,14 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'fan_speed_list': list([
|
||||
'normal',
|
||||
'maximum',
|
||||
'smart',
|
||||
'quiet',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -29,8 +36,8 @@
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <VacuumEntityFeature: 12308>,
|
||||
'translation_key': None,
|
||||
'supported_features': <VacuumEntityFeature: 12340>,
|
||||
'translation_key': 'vacuum',
|
||||
'unique_id': '01b28624-5907-c8bc-0325-8ad23f03a637_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -38,8 +45,15 @@
|
||||
# name: test_all_entities[da_rvc_map_01011][vacuum.robot_vacuum-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'fan_speed': 'maximum',
|
||||
'fan_speed_list': list([
|
||||
'normal',
|
||||
'maximum',
|
||||
'smart',
|
||||
'quiet',
|
||||
]),
|
||||
'friendly_name': 'Robot Vacuum',
|
||||
'supported_features': <VacuumEntityFeature: 12308>,
|
||||
'supported_features': <VacuumEntityFeature: 12340>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'vacuum.robot_vacuum',
|
||||
@@ -54,7 +68,14 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'capabilities': dict({
|
||||
'fan_speed_list': list([
|
||||
'normal',
|
||||
'maximum',
|
||||
'smart',
|
||||
'quiet',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@@ -79,8 +100,8 @@
|
||||
'platform': 'smartthings',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <VacuumEntityFeature: 12308>,
|
||||
'translation_key': None,
|
||||
'supported_features': <VacuumEntityFeature: 12340>,
|
||||
'translation_key': 'vacuum',
|
||||
'unique_id': '3442dfc6-17c0-a65f-dae0-4c6e01786f44_main',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
@@ -88,8 +109,15 @@
|
||||
# name: test_all_entities[da_rvc_normal_000001][vacuum.robot_vacuum-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'fan_speed': 'smart',
|
||||
'fan_speed_list': list([
|
||||
'normal',
|
||||
'maximum',
|
||||
'smart',
|
||||
'quiet',
|
||||
]),
|
||||
'friendly_name': 'Robot vacuum',
|
||||
'supported_features': <VacuumEntityFeature: 12308>,
|
||||
'supported_features': <VacuumEntityFeature: 12340>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'vacuum.robot_vacuum',
|
||||
|
||||
@@ -9,9 +9,11 @@ from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.smartthings import MAIN
|
||||
from homeassistant.components.vacuum import (
|
||||
ATTR_FAN_SPEED,
|
||||
DOMAIN as VACUUM_DOMAIN,
|
||||
SERVICE_PAUSE,
|
||||
SERVICE_RETURN_TO_BASE,
|
||||
SERVICE_SET_FAN_SPEED,
|
||||
SERVICE_START,
|
||||
VacuumActivity,
|
||||
)
|
||||
@@ -131,3 +133,52 @@ async def test_availability_at_start(
|
||||
"""Test unavailable at boot."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert hass.states.get("vacuum.robot_vacuum").state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
|
||||
async def test_fan_speed_update(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test fan speed state update."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert (
|
||||
hass.states.get("vacuum.robot_vacuum").attributes[ATTR_FAN_SPEED] == "maximum"
|
||||
)
|
||||
|
||||
await trigger_update(
|
||||
hass,
|
||||
devices,
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.ROBOT_CLEANER_TURBO_MODE,
|
||||
Attribute.ROBOT_CLEANER_TURBO_MODE,
|
||||
"extraSilence",
|
||||
)
|
||||
|
||||
assert hass.states.get("vacuum.robot_vacuum").attributes[ATTR_FAN_SPEED] == "quiet"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["da_rvc_map_01011"])
|
||||
async def test_vacuum_set_fan_speed(
|
||||
hass: HomeAssistant,
|
||||
devices: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting fan speed."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await hass.services.async_call(
|
||||
VACUUM_DOMAIN,
|
||||
SERVICE_SET_FAN_SPEED,
|
||||
{ATTR_ENTITY_ID: "vacuum.robot_vacuum", ATTR_FAN_SPEED: "normal"},
|
||||
blocking=True,
|
||||
)
|
||||
devices.execute_device_command.assert_called_once_with(
|
||||
"01b28624-5907-c8bc-0325-8ad23f03a637",
|
||||
Capability.ROBOT_CLEANER_TURBO_MODE,
|
||||
Command.SET_ROBOT_CLEANER_TURBO_MODE,
|
||||
MAIN,
|
||||
"silence",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user