mirror of
https://github.com/home-assistant/core.git
synced 2026-06-15 05:32:40 +02:00
Compare commits
73 Commits
yoto_sensors
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| c576d5267a | |||
| 1241a11c9d | |||
| 8f7447de58 | |||
| 1e54dba835 | |||
| 20583d6d1b | |||
| b94370ee51 | |||
| d068a2aa11 | |||
| 5d53a1f204 | |||
| 2908f37130 | |||
| a88a795ad3 | |||
| 73a36a2c47 | |||
| 054494181e | |||
| e4e8f901ab | |||
| b7a29bfa2f | |||
| 26b0079945 | |||
| 7454f40dd8 | |||
| 26b7d1e32c | |||
| f7342ea9b0 | |||
| 825d99ddaf | |||
| 401fae6bdd | |||
| 5433beeec1 | |||
| af60e248d3 | |||
| 8c452c280f | |||
| 3aec970321 | |||
| 687c91d5f4 | |||
| 377fdceb6c | |||
| 11a4533ccc | |||
| 52b2738b2a | |||
| 3fda722dbb | |||
| e01215da0e | |||
| 6c116cf3e4 | |||
| 8017e802dd | |||
| 501d956b1b | |||
| 8aca342a78 | |||
| bd68e9fbe3 | |||
| b75c839868 | |||
| 742bfb00ff | |||
| 987c19d991 | |||
| b4319c4d0c | |||
| 0fdb3ebed7 | |||
| efa3334616 | |||
| 9ec0f2fe4f | |||
| 9bc5e2b06b | |||
| 46a38cc481 | |||
| a63f2f1d20 | |||
| 744bb6a068 | |||
| d449e3e97b | |||
| 0df379704f | |||
| 4ab7ce04a8 | |||
| 210b08b637 | |||
| f0b448dc6e | |||
| b5a314bf60 | |||
| 741c342749 | |||
| f4d4df9c35 | |||
| bcbdf7b2bb | |||
| b3309ef169 | |||
| caaf5f9715 | |||
| 7ce7de3650 | |||
| 2c14c6be75 | |||
| e020f338ab | |||
| c85c2c4cd3 | |||
| c4e618e990 | |||
| 5efde60d21 | |||
| d9dc10ed81 | |||
| cb6ae03d21 | |||
| 915b78473c | |||
| 559006ba19 | |||
| bad2eed9fe | |||
| 9f1a079688 | |||
| 965a96b957 | |||
| d5791ae8b4 | |||
| 7b561934ea | |||
| cf60690fb7 |
@@ -26,5 +26,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioacaia"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioacaia==0.1.17"]
|
||||
"requirements": ["aioacaia==0.1.18"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/acmeda",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiopulse"],
|
||||
"requirements": ["aiopulse==0.4.6"]
|
||||
"requirements": ["aiopulse==0.4.7"]
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aioamazondevices"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aioamazondevices==14.0.3"]
|
||||
"requirements": ["aioamazondevices==14.0.4"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["anova_wifi"],
|
||||
"requirements": ["anova-wifi==0.17.0"]
|
||||
"requirements": ["anova-wifi==0.17.1"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["anthemav"],
|
||||
"requirements": ["anthemav==1.4.1"]
|
||||
"requirements": ["anthemav==1.4.2"]
|
||||
}
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==9.2.7", "yalexs-ble==3.3.0"]
|
||||
"requirements": ["yalexs==9.2.7", "yalexs-ble==3.3.1"]
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["blinkpy"],
|
||||
"requirements": ["blinkpy==0.25.2"]
|
||||
"requirements": ["blinkpy==0.25.6"]
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
"description": "The credentials for {username} need to be updated",
|
||||
"title": "Re-authenticate Blink"
|
||||
},
|
||||
"reconfigure": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["bluecurrent_api"],
|
||||
"requirements": ["bluecurrent-api==1.3.2"]
|
||||
"requirements": ["bluecurrent-api==1.3.3"]
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ from homeassistant.components.websocket_api import (
|
||||
ActiveConnection,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.const import CONF_EVENT, STATE_OFF, STATE_ON
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
HomeAssistant,
|
||||
@@ -45,7 +45,6 @@ from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.json import JsonValueType
|
||||
|
||||
from .const import (
|
||||
CONF_EVENT,
|
||||
DATA_COMPONENT,
|
||||
DOMAIN,
|
||||
EVENT_DESCRIPTION,
|
||||
|
||||
@@ -13,9 +13,6 @@ if TYPE_CHECKING:
|
||||
DOMAIN = "calendar"
|
||||
DATA_COMPONENT: HassKey[EntityComponent[CalendarEntity]] = HassKey(DOMAIN)
|
||||
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
CONF_EVENT = "event"
|
||||
|
||||
|
||||
class CalendarEntityFeature(IntFlag):
|
||||
"""Supported features of the calendar entity."""
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiocomelit"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiocomelit==2.0.3"]
|
||||
"requirements": ["aiocomelit==2.0.5"]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from hassil.recognize import RecognizeResult
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import MATCH_ALL
|
||||
from homeassistant.const import MATCH_ALL, SERVICE_RELOAD
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
@@ -53,7 +53,6 @@ from .const import (
|
||||
METADATA_CUSTOM_FILE,
|
||||
METADATA_CUSTOM_SENTENCE,
|
||||
SERVICE_PROCESS,
|
||||
SERVICE_RELOAD,
|
||||
ConversationEntityFeature,
|
||||
)
|
||||
from .default_agent import async_setup_default_agent
|
||||
|
||||
@@ -19,8 +19,6 @@ ATTR_AGENT_ID = "agent_id"
|
||||
ATTR_CONVERSATION_ID = "conversation_id"
|
||||
|
||||
SERVICE_PROCESS = "process"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
SERVICE_RELOAD = "reload"
|
||||
|
||||
DATA_COMPONENT: HassKey[EntityComponent[ConversationEntity]] = HassKey(DOMAIN)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["denonavr"],
|
||||
"requirements": ["denonavr==1.3.2"],
|
||||
"requirements": ["denonavr==1.3.3"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["HomeControl", "Mydevolo", "MprmRest", "MprmWebsocket", "Mprm"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["devolo-home-control-api==0.19.0"],
|
||||
"requirements": ["devolo-home-control-api==0.19.1"],
|
||||
"zeroconf": ["_dvl-deviceapi._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"quality_scale": "internal",
|
||||
"requirements": [
|
||||
"aiodhcpwatcher==1.2.7",
|
||||
"aiodiscover==3.3.1",
|
||||
"aiodiscover==3.3.2",
|
||||
"cached-ipaddress==1.1.2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["eheimdigital"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["eheimdigital==1.6.0"],
|
||||
"requirements": ["eheimdigital==1.7.0"],
|
||||
"zeroconf": [
|
||||
{ "name": "eheimdigital._http._tcp.local.", "type": "_http._tcp.local." }
|
||||
]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyenphase==2.4.8"],
|
||||
"requirements": ["pyenphase==2.4.9"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"requirements": [
|
||||
"aioesphomeapi==45.3.1",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==3.9.1"
|
||||
"bleak-esphome==3.9.4"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
"discovery_confirm": {
|
||||
"description": "Do you want to set up {model} {id} ({ipaddr})?"
|
||||
},
|
||||
"pick_device": {
|
||||
"data": {
|
||||
"device": "[%key:common::config_flow::data::device%]"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"data_description_password": "Password for the FRITZ!Box.",
|
||||
"data_description_port": "Leave empty to use the default port.",
|
||||
"data_description_ssl": "Use SSL to connect to the FRITZ!Box.",
|
||||
"data_description_username": "Username for the FRITZ!Box.",
|
||||
"data_description_username": "Username for the FRITZ!Box. FRITZ!Powerline devices ignore this information and accept any value.",
|
||||
"data_feature_device_tracking": "Enable network device tracking"
|
||||
},
|
||||
"config": {
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/geniushub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["geniushubclient"],
|
||||
"requirements": ["geniushub-client==0.7.1"]
|
||||
"requirements": ["geniushub-client==0.7.4"]
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"device": "[%key:common::config_flow::data::device%]",
|
||||
"ip_address": "[%key:common::config_flow::data::ip%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
|
||||
@@ -151,6 +151,13 @@ class HolidayCalendarEntity(CalendarEntity):
|
||||
"""Set up first update."""
|
||||
self._update_state_and_setup_listener()
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Cancel listener when removing."""
|
||||
await super().async_will_remove_from_hass()
|
||||
if self.unsub:
|
||||
self.unsub()
|
||||
self.unsub = None
|
||||
|
||||
def update_event(self, now: datetime) -> CalendarEvent | None:
|
||||
"""Return the next upcoming event."""
|
||||
next_holiday = None
|
||||
|
||||
@@ -27,6 +27,7 @@ from homematicip.device import (
|
||||
PassageDetector,
|
||||
PresenceDetectorIndoor,
|
||||
RoomControlDeviceAnalog,
|
||||
RotaryHandleSensor,
|
||||
SmokeDetector,
|
||||
SoilMoistureSensorInterface,
|
||||
SwitchMeasuring,
|
||||
@@ -166,6 +167,7 @@ ILLUMINATION_DEVICE_ATTRIBUTES = {
|
||||
}
|
||||
|
||||
TILT_STATE_VALUES = ["neutral", "tilted", "non_neutral"]
|
||||
WINDOW_STATE_VALUES = ["open", "closed", "tilted"]
|
||||
|
||||
|
||||
def get_device_handlers(hap: HomematicipHAP) -> dict[type, Callable]:
|
||||
@@ -204,6 +206,9 @@ def get_device_handlers(hap: HomematicipHAP) -> dict[type, Callable]:
|
||||
RoomControlDeviceAnalog: lambda device: [
|
||||
HomematicipTemperatureSensor(hap, device),
|
||||
],
|
||||
RotaryHandleSensor: lambda device: [
|
||||
HomematicipWindowStateSensor(hap, device),
|
||||
],
|
||||
LightSensor: lambda device: [
|
||||
HomematicipIlluminanceSensor(hap, device),
|
||||
],
|
||||
@@ -498,6 +503,24 @@ class HomematicipTiltStateSensor(HomematicipGenericEntity, SensorEntity):
|
||||
return state_attr
|
||||
|
||||
|
||||
class HomematicipWindowStateSensor(HomematicipGenericEntity, SensorEntity):
|
||||
"""Representation of the HomematicIP rotary handle window state sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.ENUM
|
||||
_attr_options = WINDOW_STATE_VALUES
|
||||
_attr_translation_key = "window_state"
|
||||
|
||||
def __init__(self, hap: HomematicipHAP, device: RotaryHandleSensor) -> None:
|
||||
"""Initialize the window state sensor."""
|
||||
super().__init__(hap, device, feature_id="window_state")
|
||||
|
||||
@property
|
||||
def native_value(self) -> str | None:
|
||||
"""Return the state."""
|
||||
window_state = getattr(self._device, "windowState", None)
|
||||
return window_state.lower() if window_state is not None else None
|
||||
|
||||
|
||||
class HomematicipFloorTerminalBlockMechanicChannelValve(
|
||||
HomematicipGenericEntity, SensorEntity
|
||||
):
|
||||
|
||||
@@ -98,6 +98,14 @@
|
||||
"non_neutral": "Non-neutral",
|
||||
"tilted": "Tilted"
|
||||
}
|
||||
},
|
||||
"window_state": {
|
||||
"name": "Window state",
|
||||
"state": {
|
||||
"closed": "[%key:common::state::closed%]",
|
||||
"open": "[%key:common::state::open%]",
|
||||
"tilted": "Tilted"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -50,14 +50,12 @@ def homevolt_exception_handler[_HomevoltEntityT: HomevoltEntity, **_P](
|
||||
translation_key="auth_failed",
|
||||
) from error
|
||||
except HomevoltConnectionError as error:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
except HomevoltError as error:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_error",
|
||||
|
||||
@@ -172,10 +172,10 @@
|
||||
"message": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
},
|
||||
"communication_error": {
|
||||
"message": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
"message": "Error communicating with the Homevolt battery: {error}"
|
||||
},
|
||||
"unknown_error": {
|
||||
"message": "[%key:common::config_flow::error::unknown%]"
|
||||
"message": "Unknown error from the Homevolt battery: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
"host": "[%key:common::config_flow::data::host%]",
|
||||
"id": "Hue bridge"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "The hostname or IP address of your Hue bridge."
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
|
||||
from huum.const import SaunaStatus
|
||||
|
||||
from homeassistant.components.number import NumberEntity
|
||||
from homeassistant.components.number import NumberDeviceClass, NumberEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -34,7 +34,9 @@ class HuumSteamer(HuumBaseEntity, NumberEntity):
|
||||
"""Representation of a steamer."""
|
||||
|
||||
_attr_translation_key = "humidity"
|
||||
_attr_native_max_value = 10
|
||||
_attr_device_class = NumberDeviceClass.HUMIDITY
|
||||
_attr_native_unit_of_measurement = "%"
|
||||
_attr_native_max_value = 40
|
||||
_attr_native_min_value = 0
|
||||
_attr_native_step = 1
|
||||
|
||||
@@ -47,7 +49,7 @@ class HuumSteamer(HuumBaseEntity, NumberEntity):
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the current value."""
|
||||
return self.coordinator.data.target_humidity
|
||||
return self.coordinator.data.humidity
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the current value."""
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyimouapi==1.2.7"]
|
||||
"requirements": ["pyimouapi==1.2.8"]
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ from homeassistant.const import (
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_NAME,
|
||||
CONF_OPTIONS,
|
||||
SERVICE_RELOAD,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
@@ -37,8 +38,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DOMAIN = "input_select"
|
||||
|
||||
CONF_INITIAL = "initial"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
CONF_OPTIONS = "options"
|
||||
|
||||
SERVICE_SET_OPTIONS = "set_options"
|
||||
STORAGE_KEY = DOMAIN
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyiskra"],
|
||||
"requirements": ["pyiskra==0.1.27"]
|
||||
"requirements": ["pyiskra==0.1.29"]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"location": "[%key:common::config_flow::data::location%]",
|
||||
"name": "[%key:common::config_flow::data::name%]"
|
||||
},
|
||||
"description": "Do you want to set up Islamic Prayer Times?",
|
||||
"title": "Set up Islamic Prayer Times"
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: IsraelRailConfigEntry) -
|
||||
try:
|
||||
await hass.async_add_executor_job(train_schedule.query, start, destination)
|
||||
except Exception as e:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="request_timeout",
|
||||
|
||||
@@ -65,5 +65,10 @@
|
||||
"name": "Trains +2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"request_timeout": {
|
||||
"message": "Timeout connecting to the Israel Rail API for {config_title}: {error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import logging
|
||||
|
||||
from jvcprojector import Command, JvcProjector
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER, NAME
|
||||
@@ -27,8 +27,12 @@ class JvcProjectorEntity(CoordinatorEntity[JvcProjectorDataUpdateCoordinator]):
|
||||
super().__init__(coordinator, command)
|
||||
|
||||
self._attr_unique_id = coordinator.unique_id
|
||||
# The config entry unique id is the device's formatted MAC address (set
|
||||
# from the projector's MAC in the config flow), so it doubles as the
|
||||
# network MAC connection.
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
connections={(CONNECTION_NETWORK_MAC, self._attr_unique_id)},
|
||||
name=NAME,
|
||||
model=self.device.model,
|
||||
manufacturer=MANUFACTURER,
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
"location": {
|
||||
"data": {
|
||||
"location": "[%key:common::config_flow::data::location%]"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["linkplay"],
|
||||
"requirements": ["python-linkplay==0.2.12"],
|
||||
"requirements": ["python-linkplay==0.2.14"],
|
||||
"zeroconf": ["_linkplay._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"ip_address": "[%key:common::config_flow::data::ip%]"
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ SUPPORT_DRY_MODE_DEVICES: set[tuple[int, int]] = {
|
||||
# support dry mode.
|
||||
(0x0001, 0x0108),
|
||||
(0x0001, 0x010A),
|
||||
(0x0001, 0x013F),
|
||||
(0x1209, 0x8000),
|
||||
(0x1209, 0x8001),
|
||||
(0x1209, 0x8002),
|
||||
@@ -138,6 +139,7 @@ SUPPORT_FAN_MODE_DEVICES: set[tuple[int, int]] = {
|
||||
# support fan-only mode.
|
||||
(0x0001, 0x0108),
|
||||
(0x0001, 0x010A),
|
||||
(0x0001, 0x013F),
|
||||
(0x118C, 0x2022),
|
||||
(0x1209, 0x8000),
|
||||
(0x1209, 0x8001),
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"bluetooth_confirm": {
|
||||
"description": "Do you want to add the Melnor Bluetooth valve `{name}` to Home Assistant?",
|
||||
"title": "Discovered Melnor Bluetooth valve"
|
||||
},
|
||||
"pick_device": {
|
||||
"data": {
|
||||
"address": "[%key:common::config_flow::data::device%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"code": "Station code"
|
||||
"station_code": "Station code"
|
||||
},
|
||||
"data_description": {
|
||||
"code": "Looks like ESCAT4300000043206B"
|
||||
"station_code": "Looks like ESCAT4300000043206B"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,8 @@ class MieleFan(MieleEntity, FanEntity):
|
||||
_LOGGER.debug("Calc ventilation_step: %s", ventilation_step)
|
||||
if ventilation_step == 0:
|
||||
await self.async_turn_off()
|
||||
elif ventilation_step == self.device.state_ventilation_step:
|
||||
return
|
||||
else:
|
||||
try:
|
||||
await self.api.send_action(
|
||||
@@ -165,7 +167,6 @@ class MieleFan(MieleEntity, FanEntity):
|
||||
try:
|
||||
await self.api.send_action(self._device_id, {POWER_ON: True})
|
||||
except ClientResponseError as ex:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="set_state_error",
|
||||
@@ -183,7 +184,6 @@ class MieleFan(MieleEntity, FanEntity):
|
||||
try:
|
||||
await self.api.send_action(self._device_id, {POWER_OFF: True})
|
||||
except ClientResponseError as ex:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="set_state_error",
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"data": {
|
||||
"blind_type": "Blind type"
|
||||
},
|
||||
"description": "What kind of blind is {display_name}?"
|
||||
},
|
||||
"user": {
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.components.light import (
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
@@ -57,6 +57,7 @@ class MyStromLight(LightEntity):
|
||||
self._attr_hs_color = 0, 0
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, mac)},
|
||||
connections={(CONNECTION_NETWORK_MAC, mac)},
|
||||
name=name,
|
||||
manufacturer=MANUFACTURER,
|
||||
sw_version=self._bulb.firmware,
|
||||
|
||||
@@ -50,16 +50,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
)
|
||||
if not await webio_api.refresh_device_info():
|
||||
_LOGGER.error("[%s] Refresh device info failed", entry.data[CONF_HOST])
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_error_internal_error",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
)
|
||||
webio_serial = webio_api.get_serial_number()
|
||||
if webio_serial is None:
|
||||
_LOGGER.error("[%s] Serial number not available", entry.data[CONF_HOST])
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_error_internal_error",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
)
|
||||
@@ -67,8 +67,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
_LOGGER.error(
|
||||
"[%s] Serial number doesn't match config entry", entry.data[CONF_HOST]
|
||||
)
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(translation_key="config_entry_error_serial_mismatch")
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_error_serial_mismatch",
|
||||
)
|
||||
|
||||
coordinator = NASwebCoordinator(
|
||||
hass, webio_api, name=f"NASweb[{webio_api.get_name()}]"
|
||||
@@ -79,15 +81,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
webhook_url = nasweb_data.get_webhook_url(hass)
|
||||
if not await webio_api.status_subscription(webhook_url, True):
|
||||
_LOGGER.error("Failed to subscribe for status updates from webio")
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_error_internal_error",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
)
|
||||
if not await nasweb_data.notify_coordinator.check_connection(webio_serial):
|
||||
_LOGGER.error("Did not receive status from device")
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_error_no_status_update",
|
||||
translation_placeholders={"support_email": SUPPORT_EMAIL},
|
||||
)
|
||||
@@ -96,14 +98,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: NASwebConfigEntry) -> bo
|
||||
f"[{entry.data[CONF_HOST]}] Check connection reached timeout"
|
||||
) from error
|
||||
except AuthError as error:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_invalid_authentication"
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_error_invalid_authentication",
|
||||
) from error
|
||||
except NoURLAvailableError as error:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-domain-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_key="config_entry_error_missing_internal_url"
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="config_entry_error_missing_internal_url",
|
||||
) from error
|
||||
|
||||
device_registry = dr.async_get(hass)
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"config_entry_error_no_status_update": {
|
||||
"message": "Did not receive any status updates within the expected time window. Make sure the Home Assistant internal URL is reachable from the NASweb device. If the issue persists contact support at {support_email}"
|
||||
},
|
||||
"serial_mismatch": {
|
||||
"config_entry_error_serial_mismatch": {
|
||||
"message": "Connected to different NASweb device (serial number mismatch)."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ from homeassistant.helpers.dispatcher import (
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.setup import async_when_setup
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from .config_flow import CONF_SECRET
|
||||
@@ -311,6 +312,6 @@ class OwnTracksContext:
|
||||
# kwargs location is the beacon's configured lat/lon
|
||||
kwargs.pop("battery", None)
|
||||
for beacon in self.mobile_beacons_active[dev_id]:
|
||||
kwargs["dev_id"] = f"{BEACON_DEV_ID}_{beacon}"
|
||||
kwargs["dev_id"] = slugify(f"{BEACON_DEV_ID}_{beacon}")
|
||||
kwargs["host_name"] = beacon
|
||||
self.async_see(**kwargs)
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"update_interval": "Update interval (minutes)"
|
||||
"scan_interval": "Update interval (minutes)"
|
||||
},
|
||||
"description": "Set the update interval (minutes)",
|
||||
"title": "Options for Plaato"
|
||||
|
||||
@@ -90,5 +90,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["PSNAWP==3.0.3", "pyrate-limiter==4.2.0"]
|
||||
"requirements": ["PSNAWP==3.0.3", "pyrate-limiter==4.4.0"]
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
"cloudapp/QBUSMQTTGW/+/state"
|
||||
],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["qbusmqttapi==1.5.0"]
|
||||
"requirements": ["qbusmqttapi==1.5.1"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyrainbird"],
|
||||
"requirements": ["pyrainbird==6.3.0"]
|
||||
"requirements": ["pyrainbird==6.3.1"]
|
||||
}
|
||||
|
||||
@@ -34,11 +34,7 @@ rules:
|
||||
docs-removal-instructions: todo
|
||||
test-before-setup: done
|
||||
docs-high-level-description: done
|
||||
config-flow-test-coverage:
|
||||
status: todo
|
||||
comment: |
|
||||
All config flow tests should finish with CREATE_ENTRY and ABORT to
|
||||
test they are able to recover from errors
|
||||
config-flow-test-coverage: done
|
||||
docs-actions: done
|
||||
runtime-data: done
|
||||
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["reolink_aio"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["reolink-aio==0.21.0"]
|
||||
"requirements": ["reolink-aio==0.21.1"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/scrape",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["beautifulsoup4==4.13.3", "lxml==6.0.1"]
|
||||
"requirements": ["beautifulsoup4==4.13.3", "lxml==6.1.1"]
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ from propcache.api import cached_property
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_OPTION, SERVICE_SELECT_OPTION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -18,13 +19,11 @@ from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import (
|
||||
ATTR_CYCLE,
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
DOMAIN,
|
||||
SERVICE_SELECT_FIRST,
|
||||
SERVICE_SELECT_LAST,
|
||||
SERVICE_SELECT_NEXT,
|
||||
SERVICE_SELECT_OPTION,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
)
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ DOMAIN = "select"
|
||||
|
||||
ATTR_CYCLE = "cycle"
|
||||
ATTR_OPTIONS = "options"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_OPTION = "option"
|
||||
|
||||
CONF_CYCLE = "cycle"
|
||||
CONF_OPTION = "option"
|
||||
@@ -13,6 +11,4 @@ CONF_OPTION = "option"
|
||||
SERVICE_SELECT_FIRST = "select_first"
|
||||
SERVICE_SELECT_LAST = "select_last"
|
||||
SERVICE_SELECT_NEXT = "select_next"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
SERVICE_SELECT_OPTION = "select_option"
|
||||
SERVICE_SELECT_PREVIOUS = "select_previous"
|
||||
|
||||
@@ -10,10 +10,12 @@ from homeassistant.components.device_automation import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_OPTION,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_DOMAIN,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_TYPE,
|
||||
SERVICE_SELECT_OPTION,
|
||||
)
|
||||
from homeassistant.core import Context, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
@@ -23,7 +25,6 @@ from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
|
||||
from .const import (
|
||||
ATTR_CYCLE,
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
CONF_CYCLE,
|
||||
CONF_OPTION,
|
||||
@@ -31,7 +32,6 @@ from .const import (
|
||||
SERVICE_SELECT_FIRST,
|
||||
SERVICE_SELECT_LAST,
|
||||
SERVICE_SELECT_NEXT,
|
||||
SERVICE_SELECT_OPTION,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
)
|
||||
|
||||
|
||||
@@ -5,10 +5,10 @@ from collections.abc import Iterable
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.const import ATTR_ENTITY_ID
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, SERVICE_SELECT_OPTION
|
||||
from homeassistant.core import Context, HomeAssistant, State
|
||||
|
||||
from .const import ATTR_OPTION, ATTR_OPTIONS, DOMAIN, SERVICE_SELECT_OPTION
|
||||
from .const import ATTR_OPTIONS, DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["construct", "snapcast"],
|
||||
"requirements": ["snapcast==2.3.7"]
|
||||
"requirements": ["snapcast==2.3.8"]
|
||||
}
|
||||
|
||||
@@ -80,11 +80,10 @@ class SnooSwitch(SnooDescriptionEntity, SwitchEntity):
|
||||
True,
|
||||
)
|
||||
except SnooCommandException as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="switch_on_failed",
|
||||
translation_placeholders={"name": str(self.name), "status": "on"},
|
||||
translation_placeholders={"name": str(self.name)},
|
||||
) from err
|
||||
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
@@ -97,9 +96,8 @@ class SnooSwitch(SnooDescriptionEntity, SwitchEntity):
|
||||
False,
|
||||
)
|
||||
except SnooCommandException as err:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="switch_off_failed",
|
||||
translation_placeholders={"name": str(self.name), "status": "off"},
|
||||
translation_placeholders={"name": str(self.name)},
|
||||
) from err
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"address": "[%key:common::config_flow::data::device%]"
|
||||
"address": "[%key:common::config_flow::data::device%]",
|
||||
"name": "[%key:common::config_flow::data::name%]"
|
||||
},
|
||||
"description": "[%key:component::bluetooth::config::step::user::description%]"
|
||||
}
|
||||
|
||||
@@ -86,12 +86,11 @@ async def async_setup_entry(
|
||||
},
|
||||
) from e
|
||||
except OpendataTransportError as e:
|
||||
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_data",
|
||||
translation_placeholders={
|
||||
**PLACEHOLDERS,
|
||||
"stationboard_url": PLACEHOLDERS["stationboard_url"],
|
||||
"config_title": entry.title,
|
||||
"error": str(e),
|
||||
},
|
||||
|
||||
@@ -96,6 +96,7 @@ PLATFORMS_BY_TYPE = {
|
||||
],
|
||||
SupportedModels.HUBMINI_MATTER.value: [Platform.SENSOR],
|
||||
SupportedModels.CIRCULATOR_FAN.value: [Platform.FAN, Platform.SENSOR],
|
||||
SupportedModels.STANDING_FAN.value: [Platform.SENSOR],
|
||||
SupportedModels.S10_VACUUM.value: [Platform.VACUUM, Platform.SENSOR],
|
||||
SupportedModels.S20_VACUUM.value: [Platform.VACUUM, Platform.SENSOR],
|
||||
SupportedModels.K10_VACUUM.value: [Platform.VACUUM, Platform.SENSOR],
|
||||
@@ -207,6 +208,7 @@ CLASS_BY_DEVICE = {
|
||||
SupportedModels.RELAY_SWITCH_1.value: switchbot.SwitchbotRelaySwitch,
|
||||
SupportedModels.ROLLER_SHADE.value: switchbot.SwitchbotRollerShade,
|
||||
SupportedModels.CIRCULATOR_FAN.value: switchbot.SwitchbotFan,
|
||||
SupportedModels.STANDING_FAN.value: switchbot.SwitchbotStandingFan,
|
||||
SupportedModels.S10_VACUUM.value: switchbot.SwitchbotVacuum,
|
||||
SupportedModels.S20_VACUUM.value: switchbot.SwitchbotVacuum,
|
||||
SupportedModels.K10_VACUUM.value: switchbot.SwitchbotVacuum,
|
||||
|
||||
@@ -71,6 +71,7 @@ class SupportedModels(StrEnum):
|
||||
LOCK_VISION = "lock_vision"
|
||||
LOCK_PRO_WIFI = "lock_pro_wifi"
|
||||
WEATHER_STATION = "weather_station"
|
||||
STANDING_FAN = "standing_fan"
|
||||
|
||||
|
||||
CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
||||
@@ -120,6 +121,7 @@ CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
||||
SwitchbotModel.LOCK_VISION_PRO: SupportedModels.LOCK_VISION_PRO,
|
||||
SwitchbotModel.LOCK_VISION: SupportedModels.LOCK_VISION,
|
||||
SwitchbotModel.LOCK_PRO_WIFI: SupportedModels.LOCK_PRO_WIFI,
|
||||
SwitchbotModel.STANDING_FAN: SupportedModels.STANDING_FAN,
|
||||
}
|
||||
|
||||
NON_CONNECTABLE_SUPPORTED_MODEL_TYPES = {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["synology_dsm"],
|
||||
"requirements": ["py-synologydsm-api==2.9.0"],
|
||||
"requirements": ["py-synologydsm-api==2.10.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:Basic:1",
|
||||
|
||||
@@ -6,7 +6,15 @@ import voluptuous as vol
|
||||
from voluptuous import All, Range
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.const import (
|
||||
ATTR_ID,
|
||||
ATTR_LOCATION,
|
||||
ATTR_NAME,
|
||||
ATTR_TIME,
|
||||
CONF_DEVICE_ID,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
@@ -18,20 +26,14 @@ from .models import TeslemetryEnergyData, TeslemetryVehicleData
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Attributes
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_ID = "id"
|
||||
ATTR_GPS = "gps"
|
||||
ATTR_TYPE = "type"
|
||||
ATTR_VALUE = "value"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_LOCATION = "location"
|
||||
ATTR_LOCALE = "locale"
|
||||
ATTR_ORDER = "order"
|
||||
ATTR_TIMESTAMP = "timestamp"
|
||||
ATTR_FIELDS = "fields"
|
||||
ATTR_ENABLE = "enable"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_TIME = "time"
|
||||
ATTR_PIN = "pin"
|
||||
ATTR_TOU_SETTINGS = "tou_settings"
|
||||
ATTR_PRECONDITIONING_ENABLED = "preconditioning_enabled"
|
||||
@@ -44,8 +46,6 @@ ATTR_DAYS_OF_WEEK = "days_of_week"
|
||||
ATTR_START_TIME = "start_time"
|
||||
ATTR_END_TIME = "end_time"
|
||||
ATTR_ONE_TIME = "one_time"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_NAME = "name"
|
||||
ATTR_PRECONDITION_TIME = "precondition_time"
|
||||
|
||||
# Services
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiohttp import ClientError
|
||||
from tesla_fleet_api.const import Scope
|
||||
from tesla_fleet_api.exceptions import (
|
||||
Forbidden,
|
||||
@@ -81,6 +82,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_connect",
|
||||
) from e
|
||||
except ClientError as e:
|
||||
raise ConfigEntryNotReady from e
|
||||
|
||||
vehicles: list[TessieVehicleData] = []
|
||||
for vehicle in state_of_all_vehicles["results"]:
|
||||
@@ -124,13 +127,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
|
||||
|
||||
try:
|
||||
scopes = await tessie.scopes()
|
||||
except TeslaFleetError as e:
|
||||
except (TeslaFleetError, ClientError) as e:
|
||||
raise ConfigEntryNotReady from e
|
||||
|
||||
if Scope.ENERGY_DEVICE_DATA in scopes:
|
||||
try:
|
||||
products = (await tessie.products())["response"]
|
||||
except TeslaFleetError as e:
|
||||
except (TeslaFleetError, ClientError) as e:
|
||||
raise ConfigEntryNotReady from e
|
||||
|
||||
for product in products:
|
||||
@@ -154,7 +157,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: TessieConfigEntry) -> bo
|
||||
except (InvalidToken, Forbidden, SubscriptionRequired) as e:
|
||||
raise ConfigEntryAuthFailed from e
|
||||
except TeslaFleetError as e:
|
||||
raise ConfigEntryNotReady(e.message) from e
|
||||
raise ConfigEntryNotReady(getattr(e, "message", str(e))) from e
|
||||
except ClientError as e:
|
||||
raise ConfigEntryNotReady from e
|
||||
|
||||
powerwall = (
|
||||
product["components"]["battery"] or product["components"]["solar"]
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["steamloop"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["steamloop==1.2.0"]
|
||||
"requirements": ["steamloop==1.2.1"]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
"description": "The Tuya integration now uses an improved login method. To reauthenticate with your Smart Life or Tuya Smart account, you need to enter your user code.\n\nYou can find this code in the Smart Life app or Tuya Smart app in **Settings** > **Account and Security** screen, and enter the code shown on the **User Code** field. The user code is case-sensitive, please be sure to enter it exactly as shown in the app."
|
||||
},
|
||||
"scan": {
|
||||
"data": {
|
||||
"QR": "QR code"
|
||||
},
|
||||
"description": "Use the Smart Life app or Tuya Smart app to scan the following QR code to complete the login.\n\nContinue to the next step once you have completed this step in the app."
|
||||
},
|
||||
"user": {
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["uiprotect"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["uiprotect==13.1.1"]
|
||||
"requirements": ["uiprotect==13.1.2"]
|
||||
}
|
||||
|
||||
@@ -64,17 +64,14 @@ async def async_setup_entry(
|
||||
try:
|
||||
await manager.login()
|
||||
except VeSyncLoginError as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN, translation_key="invalid_auth"
|
||||
) from err
|
||||
except VeSyncServerError as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN, translation_key="server_error"
|
||||
) from err
|
||||
except VeSyncAPIResponseError as err:
|
||||
# pylint: disable-next=home-assistant-exception-translation-key-missing
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN, translation_key="api_response_error"
|
||||
) from err
|
||||
|
||||
@@ -154,6 +154,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"api_response_error": {
|
||||
"message": "Invalid response from the VeSync API"
|
||||
},
|
||||
"invalid_auth": {
|
||||
"message": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
},
|
||||
"server_error": {
|
||||
"message": "Server error occurred while connecting to VeSync"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"update_devices": {
|
||||
"description": "Adds new VeSync devices to Home Assistant.",
|
||||
|
||||
@@ -67,7 +67,4 @@ class VistapoolLEDPulseButton(VistapoolEntity, ButtonEntity):
|
||||
translation_key="set_failed",
|
||||
translation_placeholders={"entity": self.entity_id},
|
||||
) from err
|
||||
# Optimistically reflect the just-written value so a rapid second press
|
||||
# doesn't read the stale off-state before the Firestore push round-trips.
|
||||
self.coordinator.data.setdefault("light", {})["status"] = 1
|
||||
self.coordinator.async_set_updated_data(self.coordinator.data)
|
||||
self.coordinator.apply_optimistic(_LIGHT_STATUS_PATH, 1)
|
||||
|
||||
@@ -81,3 +81,22 @@ class VistapoolDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
def get_value(self, path: str, default: Any = None) -> Any:
|
||||
"""Get nested data using dot-notation path."""
|
||||
return AquariteClient.get_value(self.data, path, default)
|
||||
|
||||
def apply_optimistic(self, value_path: str, value: Any) -> None:
|
||||
"""Reflect a just-written value before the Firestore push round-trips.
|
||||
|
||||
Hayward's cloud takes several seconds to acknowledge a write back
|
||||
through Firestore, which would make the UI feel laggy. Writing into
|
||||
coordinator.data after a successful REST call gives entities instant
|
||||
feedback; the next snapshot from Firestore overwrites it harmlessly.
|
||||
"""
|
||||
keys = value_path.split(".")
|
||||
target: dict[str, Any] = self.data
|
||||
for key in keys[:-1]:
|
||||
child = target.get(key)
|
||||
if not isinstance(child, dict):
|
||||
child = {}
|
||||
target[key] = child
|
||||
target = child
|
||||
target[keys[-1]] = value
|
||||
self.async_set_updated_data(self.data)
|
||||
|
||||
@@ -71,3 +71,4 @@ class VistapoolLight(VistapoolEntity, LightEntity):
|
||||
translation_key="set_failed",
|
||||
translation_placeholders={"entity": self.entity_id},
|
||||
) from err
|
||||
self.coordinator.apply_optimistic(_VALUE_PATH, value)
|
||||
|
||||
@@ -233,3 +233,4 @@ class VistapoolNumber(VistapoolEntity, NumberEntity):
|
||||
translation_key="set_failed",
|
||||
translation_placeholders={"entity": self.entity_id},
|
||||
) from err
|
||||
self.coordinator.apply_optimistic(self.entity_description.value_path, raw)
|
||||
|
||||
@@ -9,7 +9,6 @@ from pythonxbox.api.provider.people.models import Person
|
||||
from pythonxbox.api.provider.titlehub.models import Title
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
@@ -17,12 +16,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import XboxConfigEntry
|
||||
from .entity import (
|
||||
XboxBaseEntity,
|
||||
XboxBaseEntityDescription,
|
||||
check_deprecated_entity,
|
||||
profile_pic,
|
||||
)
|
||||
from .entity import XboxBaseEntity, XboxBaseEntityDescription, profile_pic
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -31,9 +25,7 @@ class XboxBinarySensor(StrEnum):
|
||||
"""Xbox binary sensor."""
|
||||
|
||||
ONLINE = "online"
|
||||
IN_PARTY = "in_party"
|
||||
IN_GAME = "in_game"
|
||||
IN_MULTIPLAYER = "in_multiplayer"
|
||||
HAS_GAME_PASS = "has_game_pass"
|
||||
|
||||
|
||||
@@ -81,21 +73,11 @@ SENSOR_DESCRIPTIONS: tuple[XboxBinarySensorEntityDescription, ...] = (
|
||||
entity_picture_fn=profile_pic,
|
||||
attributes_fn=profile_attributes,
|
||||
),
|
||||
XboxBinarySensorEntityDescription(
|
||||
key=XboxBinarySensor.IN_PARTY,
|
||||
is_on_fn=lambda _: None,
|
||||
deprecated=True,
|
||||
),
|
||||
XboxBinarySensorEntityDescription(
|
||||
key=XboxBinarySensor.IN_GAME,
|
||||
translation_key=XboxBinarySensor.IN_GAME,
|
||||
is_on_fn=in_game,
|
||||
),
|
||||
XboxBinarySensorEntityDescription(
|
||||
key=XboxBinarySensor.IN_MULTIPLAYER,
|
||||
is_on_fn=lambda _: None,
|
||||
deprecated=True,
|
||||
),
|
||||
XboxBinarySensorEntityDescription(
|
||||
key=XboxBinarySensor.HAS_GAME_PASS,
|
||||
translation_key=XboxBinarySensor.HAS_GAME_PASS,
|
||||
@@ -118,9 +100,6 @@ async def async_setup_entry(
|
||||
[
|
||||
XboxBinarySensorEntity(coordinator, entry.unique_id, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if check_deprecated_entity(
|
||||
hass, entry.unique_id, description, BINARY_SENSOR_DOMAIN
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -130,9 +109,6 @@ async def async_setup_entry(
|
||||
XboxBinarySensorEntity(coordinator, subentry.unique_id, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if subentry.unique_id
|
||||
and check_deprecated_entity(
|
||||
hass, subentry.unique_id, description, BINARY_SENSOR_DOMAIN
|
||||
)
|
||||
and subentry.unique_id in coordinator.data.presence
|
||||
and subentry.subentry_type == "friend"
|
||||
],
|
||||
|
||||
@@ -9,8 +9,6 @@ from pythonxbox.api.provider.smartglass.models import ConsoleType, SmartglassCon
|
||||
from pythonxbox.api.provider.titlehub.models import Title
|
||||
from yarl import URL
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity import EntityDescription
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -40,7 +38,6 @@ class XboxBaseEntityDescription(EntityDescription):
|
||||
attributes_fn: Callable[[Person, Title | None], Mapping[str, Any] | None] | None = (
|
||||
None
|
||||
)
|
||||
deprecated: bool | None = None
|
||||
|
||||
|
||||
class XboxBaseEntity(CoordinatorEntity[XboxPresenceCoordinator]):
|
||||
@@ -145,26 +142,6 @@ class XboxConsoleBaseEntity(CoordinatorEntity[XboxConsoleStatusCoordinator]):
|
||||
return self.coordinator.data.get(self._console.id) is not None
|
||||
|
||||
|
||||
def check_deprecated_entity(
|
||||
hass: HomeAssistant,
|
||||
xuid: str,
|
||||
entity_description: XboxBaseEntityDescription,
|
||||
entity_domain: str,
|
||||
) -> bool:
|
||||
"""Check for deprecated entity and remove it."""
|
||||
if not entity_description.deprecated:
|
||||
return True
|
||||
ent_reg = er.async_get(hass)
|
||||
if entity_id := ent_reg.async_get_entity_id(
|
||||
entity_domain,
|
||||
DOMAIN,
|
||||
f"{xuid}_{entity_description.key}",
|
||||
):
|
||||
ent_reg.async_remove(entity_id)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def to_https(image_url: str) -> str:
|
||||
"""Convert image URLs to secure URLs."""
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from pythonxbox.api.provider.smartglass.models import SmartglassConsole, Storage
|
||||
from pythonxbox.api.provider.titlehub.models import Title
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
DOMAIN as SENSOR_DOMAIN,
|
||||
EntityCategory,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
@@ -27,13 +26,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import XboxConfigEntry, XboxConsolesCoordinator
|
||||
from .entity import (
|
||||
MAP_MODEL,
|
||||
XboxBaseEntity,
|
||||
XboxBaseEntityDescription,
|
||||
check_deprecated_entity,
|
||||
to_https,
|
||||
)
|
||||
from .entity import MAP_MODEL, XboxBaseEntity, XboxBaseEntityDescription, to_https
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -58,8 +51,6 @@ class XboxSensor(StrEnum):
|
||||
|
||||
STATUS = "status"
|
||||
GAMER_SCORE = "gamer_score"
|
||||
ACCOUNT_TIER = "account_tier"
|
||||
GOLD_TENURE = "gold_tenure"
|
||||
LAST_ONLINE = "last_online"
|
||||
FOLLOWING = "following"
|
||||
FOLLOWER = "follower"
|
||||
@@ -200,16 +191,6 @@ SENSOR_DESCRIPTIONS: tuple[XboxSensorEntityDescription, ...] = (
|
||||
value_fn=lambda x, _: x.gamer_score,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
XboxSensorEntityDescription(
|
||||
key=XboxSensor.ACCOUNT_TIER,
|
||||
value_fn=lambda _, __: None,
|
||||
deprecated=True,
|
||||
),
|
||||
XboxSensorEntityDescription(
|
||||
key=XboxSensor.GOLD_TENURE,
|
||||
value_fn=lambda _, __: None,
|
||||
deprecated=True,
|
||||
),
|
||||
XboxSensorEntityDescription(
|
||||
key=XboxSensor.LAST_ONLINE,
|
||||
translation_key=XboxSensor.LAST_ONLINE,
|
||||
@@ -304,9 +285,6 @@ async def async_setup_entry(
|
||||
[
|
||||
XboxSensorEntity(presence, config_entry.unique_id, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if check_deprecated_entity(
|
||||
hass, config_entry.unique_id, description, SENSOR_DOMAIN
|
||||
)
|
||||
]
|
||||
)
|
||||
for subentry_id, subentry in config_entry.subentries.items():
|
||||
@@ -315,9 +293,6 @@ async def async_setup_entry(
|
||||
XboxSensorEntity(presence, subentry.unique_id, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if subentry.unique_id
|
||||
and check_deprecated_entity(
|
||||
hass, subentry.unique_id, description, SENSOR_DOMAIN
|
||||
)
|
||||
and subentry.unique_id in presence.data.presence
|
||||
and subentry.subentry_type == "friend"
|
||||
],
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["socketio", "engineio", "yalexs"],
|
||||
"requirements": ["yalexs==9.2.7", "yalexs-ble==3.3.0"]
|
||||
"requirements": ["yalexs==9.2.7", "yalexs-ble==3.3.1"]
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/yalexs_ble",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["yalexs-ble==3.3.0"]
|
||||
"requirements": ["yalexs-ble==3.3.1"]
|
||||
}
|
||||
|
||||
@@ -19,11 +19,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
from .const import DOMAIN
|
||||
from .coordinator import YotoConfigEntry, YotoDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.SENSOR,
|
||||
Platform.TIME,
|
||||
]
|
||||
PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.TIME]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: YotoConfigEntry) -> bool:
|
||||
|
||||
@@ -148,6 +148,8 @@ class YotoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, YotoPlayer]]):
|
||||
"""Ask each player to push a fresh status snapshot over MQTT."""
|
||||
if not self.client.is_mqtt_connected:
|
||||
return
|
||||
# Fire-and-forget: the data/status response lands via the on_update
|
||||
# callback later, which already triggers async_set_updated_data.
|
||||
for device_id in list(self.client.players):
|
||||
await self.client.request_player_status(device_id)
|
||||
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"card_insertion_state": {
|
||||
"default": "mdi:card-bulleted-outline",
|
||||
"state": {
|
||||
"none": "mdi:card-bulleted-off-outline",
|
||||
"physical": "mdi:card-bulleted",
|
||||
"remote": "mdi:cast-audio",
|
||||
"streaming": "mdi:radio-tower"
|
||||
}
|
||||
},
|
||||
"day_mode": {
|
||||
"default": "mdi:theme-light-dark",
|
||||
"state": {
|
||||
"day": "mdi:weather-sunny",
|
||||
"night": "mdi:weather-night"
|
||||
}
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"day_mode_start": {
|
||||
"default": "mdi:weather-sunny"
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
"""Sensor platform for the Yoto integration."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from yoto_api import CardInsertionState, DayMode, YotoPlayer
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .coordinator import YotoConfigEntry, YotoDataUpdateCoordinator
|
||||
from .entity import YotoPlayerEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def _enum_state(value: CardInsertionState | None) -> str | None:
|
||||
"""Return an enum member as a lowercase string, or None if unset."""
|
||||
return value.name.lower() if value is not None else None
|
||||
|
||||
|
||||
def _day_mode_state(value: DayMode | None) -> str | None:
|
||||
"""Return day/night, treating the firmware's UNKNOWN as unset."""
|
||||
if value is None or value is DayMode.UNKNOWN:
|
||||
return None
|
||||
return value.name.lower()
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class YotoSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes a Yoto sensor entity."""
|
||||
|
||||
value_fn: Callable[[YotoPlayer], StateType]
|
||||
|
||||
|
||||
SENSORS: tuple[YotoSensorEntityDescription, ...] = (
|
||||
YotoSensorEntityDescription(
|
||||
key="battery_level",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
value_fn=lambda player: player.status.battery_level_percentage,
|
||||
),
|
||||
YotoSensorEntityDescription(
|
||||
key="card_insertion_state",
|
||||
translation_key="card_insertion_state",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[state.name.lower() for state in CardInsertionState],
|
||||
value_fn=lambda player: _enum_state(player.status.card_insertion_state),
|
||||
),
|
||||
YotoSensorEntityDescription(
|
||||
key="day_mode",
|
||||
translation_key="day_mode",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=["day", "night"],
|
||||
value_fn=lambda player: _day_mode_state(player.status.day_mode),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: YotoConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Yoto sensor platform."""
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
YotoSensor(coordinator, player, description)
|
||||
for player in coordinator.client.players.values()
|
||||
for description in SENSORS
|
||||
)
|
||||
|
||||
|
||||
class YotoSensor(YotoPlayerEntity, SensorEntity):
|
||||
"""Representation of a Yoto player sensor."""
|
||||
|
||||
entity_description: YotoSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: YotoDataUpdateCoordinator,
|
||||
player: YotoPlayer,
|
||||
description: YotoSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator, player)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{player.id}_{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the sensor value."""
|
||||
return self.entity_description.value_fn(self.player)
|
||||
@@ -37,24 +37,6 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"card_insertion_state": {
|
||||
"name": "Card slot",
|
||||
"state": {
|
||||
"none": "Empty",
|
||||
"physical": "Physical card",
|
||||
"remote": "Remote",
|
||||
"streaming": "Streaming"
|
||||
}
|
||||
},
|
||||
"day_mode": {
|
||||
"name": "Day mode",
|
||||
"state": {
|
||||
"day": "Day",
|
||||
"night": "Night"
|
||||
}
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"day_mode_start": {
|
||||
"name": "Day mode start"
|
||||
|
||||
@@ -214,6 +214,10 @@ class ZHAEntity(LogMixin, RestoreEntity, Entity):
|
||||
|
||||
def log(self, level: int, msg: str, *args, **kwargs):
|
||||
"""Log a message."""
|
||||
if not _LOGGER.isEnabledFor(level):
|
||||
# Avoid building the prefixed message and args tuple for disabled
|
||||
# levels; this runs for every entity event via _handle_entity_events.
|
||||
return
|
||||
msg = f"%s: {msg}"
|
||||
args = (self.entity_id, *args)
|
||||
_LOGGER.log(level, msg, *args, **kwargs)
|
||||
|
||||
@@ -432,15 +432,6 @@ async def async_set_credential(
|
||||
translation_key="no_available_credential_slots",
|
||||
translation_placeholders={"credential_type": cred_type_str},
|
||||
)
|
||||
elif not 1 <= credential_slot <= type_cap.number_of_credential_slots:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="credential_slot_out_of_range",
|
||||
translation_placeholders={
|
||||
"credential_type": cred_type_str,
|
||||
"max_slot": str(type_cap.number_of_credential_slots),
|
||||
},
|
||||
)
|
||||
|
||||
status = await node.access_control.set_credential(
|
||||
user_id, credential_type, credential_slot, credential_data
|
||||
|
||||
@@ -322,9 +322,6 @@
|
||||
"credential_rejected_wrong_uuid": {
|
||||
"message": "The device rejected the credential because the user unique identifier does not match."
|
||||
},
|
||||
"credential_slot_out_of_range": {
|
||||
"message": "Credential slot for {credential_type} must be between 1 and {max_slot}."
|
||||
},
|
||||
"credential_type_not_supported": {
|
||||
"message": "Credential type {credential_type} is not supported on this device"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Automatically generated by gen_requirements_all.py, do not edit
|
||||
|
||||
aiodhcpwatcher==1.2.7
|
||||
aiodiscover==3.3.1
|
||||
aiodiscover==3.3.2
|
||||
aiodns==4.0.4
|
||||
aiogithubapi==26.0.0
|
||||
aiohttp-asyncmdnsresolver==0.2.0
|
||||
@@ -70,7 +70,7 @@ standard-telnetlib==3.13.0
|
||||
typing-extensions>=4.15.0,<5.0
|
||||
ulid-transform==2.2.9
|
||||
urllib3>=2.0
|
||||
uv==0.11.19
|
||||
uv==0.11.21
|
||||
voluptuous-openapi==0.3.0
|
||||
voluptuous-serialize==2.7.0
|
||||
voluptuous==0.15.2
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
"""Plugin to encourage correct use of DOMAIN constants in tests."""
|
||||
"""Plugin to prevent incorrect use of Platform enum in tests.
|
||||
|
||||
This plugin checks for common test helper functions and methods
|
||||
where a domain is expected, and ensures the argument is not a Platform.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -30,9 +34,6 @@ _METHOD_CHECKS: list[tuple[str, str, ArgumentCheckInfo]] = [
|
||||
("hass.states", "async_entity_ids", ArgumentCheckInfo(0, "domain_filter", True)),
|
||||
]
|
||||
|
||||
_DOMAIN_CONSTANTS: set[str] = {"DOMAIN", "domain"}
|
||||
_DOMAIN_SUFFIXES: tuple[str, ...] = ("_DOMAIN", "_domain")
|
||||
|
||||
|
||||
def _check_call_node_domain_arguments(node: nodes.Call) -> nodes.NodeNG | None:
|
||||
"""Ensure the call node arguments are valid domain constant or variable.
|
||||
@@ -81,10 +82,14 @@ def _check_call_node_domain_argument(
|
||||
return None
|
||||
|
||||
|
||||
def _check_domain_argument(arg_node: nodes.NodeNG, allow_iterable: bool) -> bool:
|
||||
def _check_domain_argument(arg_node: nodes.NodeNG, allow_iterable: bool = True) -> bool:
|
||||
"""Ensure the argument node is a domain constant or variable.
|
||||
|
||||
We allow:
|
||||
We currently only disallow `Platform.Xyz`
|
||||
|
||||
The plugin can be extended in the future (see #173374) to improve
|
||||
consistency and only allow certain patterns for domain arguments,
|
||||
such as:
|
||||
- x.DOMAIN/x.domain attribute (including *_DOMAIN/*_domain)
|
||||
- DOMAIN/domain name (including *_DOMAIN/*_domain)
|
||||
- string literals
|
||||
@@ -94,21 +99,8 @@ def _check_domain_argument(arg_node: nodes.NodeNG, allow_iterable: bool) -> bool
|
||||
"""
|
||||
match arg_node:
|
||||
case nodes.Attribute():
|
||||
if (
|
||||
attrname := arg_node.attrname
|
||||
) in _DOMAIN_CONSTANTS or attrname.endswith(_DOMAIN_SUFFIXES):
|
||||
return True
|
||||
case nodes.Const():
|
||||
if isinstance(arg_node.value, str):
|
||||
return True
|
||||
case nodes.Name():
|
||||
if (node_name := arg_node.name) in _DOMAIN_CONSTANTS or node_name.endswith(
|
||||
_DOMAIN_SUFFIXES
|
||||
):
|
||||
return True
|
||||
case nodes.Subscript():
|
||||
# Ignore subscripts like dict["key"]
|
||||
return True
|
||||
if arg_node.expr.as_string() == "Platform":
|
||||
return False
|
||||
case nodes.Tuple():
|
||||
if allow_iterable:
|
||||
return all(
|
||||
@@ -116,7 +108,7 @@ def _check_domain_argument(arg_node: nodes.NodeNG, allow_iterable: bool) -> bool
|
||||
for element in arg_node.elts
|
||||
)
|
||||
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class DomainConstantChecker(BaseChecker):
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ dependencies = [
|
||||
"typing-extensions>=4.15.0,<5.0",
|
||||
"ulid-transform==2.2.9",
|
||||
"urllib3>=2.0",
|
||||
"uv==0.11.19",
|
||||
"uv==0.11.21",
|
||||
"voluptuous==0.15.2",
|
||||
"voluptuous-serialize==2.7.0",
|
||||
"voluptuous-openapi==0.3.0",
|
||||
|
||||
Generated
+1
-1
@@ -55,7 +55,7 @@ standard-telnetlib==3.13.0
|
||||
typing-extensions>=4.15.0,<5.0
|
||||
ulid-transform==2.2.9
|
||||
urllib3>=2.0
|
||||
uv==0.11.19
|
||||
uv==0.11.21
|
||||
voluptuous-openapi==0.3.0
|
||||
voluptuous-serialize==2.7.0
|
||||
voluptuous==0.15.2
|
||||
|
||||
Generated
+28
-28
@@ -178,7 +178,7 @@ aio-georss-gdacs==0.10
|
||||
aio-ownet==0.0.5
|
||||
|
||||
# homeassistant.components.acaia
|
||||
aioacaia==0.1.17
|
||||
aioacaia==0.1.18
|
||||
|
||||
# homeassistant.components.airq
|
||||
aioairq==0.4.7
|
||||
@@ -190,7 +190,7 @@ aioairzone-cloud==0.7.2
|
||||
aioairzone==1.0.5
|
||||
|
||||
# homeassistant.components.alexa_devices
|
||||
aioamazondevices==14.0.3
|
||||
aioamazondevices==14.0.4
|
||||
|
||||
# homeassistant.components.ambient_network
|
||||
# homeassistant.components.ambient_station
|
||||
@@ -230,13 +230,13 @@ aiobotocore==2.21.1
|
||||
aiocentriconnect==0.2.3
|
||||
|
||||
# homeassistant.components.comelit
|
||||
aiocomelit==2.0.3
|
||||
aiocomelit==2.0.5
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodhcpwatcher==1.2.7
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodiscover==3.3.1
|
||||
aiodiscover==3.3.2
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==4.0.4
|
||||
@@ -372,7 +372,7 @@ aiopnsense==1.0.8
|
||||
aioptdevices==2026.03.2
|
||||
|
||||
# homeassistant.components.acmeda
|
||||
aiopulse==0.4.6
|
||||
aiopulse==0.4.7
|
||||
|
||||
# homeassistant.components.purpleair
|
||||
aiopurpleair==2025.08.1
|
||||
@@ -516,10 +516,10 @@ androidtvremote2==0.3.1
|
||||
anel-pwrctrl-homeassistant==0.0.1.dev2
|
||||
|
||||
# homeassistant.components.anova
|
||||
anova-wifi==0.17.0
|
||||
anova-wifi==0.17.1
|
||||
|
||||
# homeassistant.components.anthemav
|
||||
anthemav==1.4.1
|
||||
anthemav==1.4.2
|
||||
|
||||
# homeassistant.components.anthropic
|
||||
anthropic==0.108.0
|
||||
@@ -657,7 +657,7 @@ beautifulsoup4==4.13.3
|
||||
bizkaibus==0.1.1
|
||||
|
||||
# homeassistant.components.esphome
|
||||
bleak-esphome==3.9.1
|
||||
bleak-esphome==3.9.4
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==4.6.1
|
||||
@@ -669,13 +669,13 @@ bleak==3.0.2
|
||||
blebox-uniapi==2.5.5
|
||||
|
||||
# homeassistant.components.blink
|
||||
blinkpy==0.25.2
|
||||
blinkpy==0.25.6
|
||||
|
||||
# homeassistant.components.bitcoin
|
||||
blockchain==1.4.4
|
||||
|
||||
# homeassistant.components.blue_current
|
||||
bluecurrent-api==1.3.2
|
||||
bluecurrent-api==1.3.3
|
||||
|
||||
# homeassistant.components.bluemaestro
|
||||
bluemaestro-ble==0.4.1
|
||||
@@ -829,13 +829,13 @@ demetriek==1.3.0
|
||||
denon-rs232==4.1.0
|
||||
|
||||
# homeassistant.components.denonavr
|
||||
denonavr==1.3.2
|
||||
denonavr==1.3.3
|
||||
|
||||
# homeassistant.components.devialet
|
||||
devialet==1.5.7
|
||||
|
||||
# homeassistant.components.devolo_home_control
|
||||
devolo-home-control-api==0.19.0
|
||||
devolo-home-control-api==0.19.1
|
||||
|
||||
# homeassistant.components.devolo_home_network
|
||||
devolo-plc-api==1.5.1
|
||||
@@ -889,7 +889,7 @@ ecoaliface==0.4.0
|
||||
egauge-async==0.4.0
|
||||
|
||||
# homeassistant.components.eheimdigital
|
||||
eheimdigital==1.6.0
|
||||
eheimdigital==1.7.0
|
||||
|
||||
# homeassistant.components.ekeybionyx
|
||||
ekey-bionyxpy==1.0.1
|
||||
@@ -1081,7 +1081,7 @@ gcal-sync==8.0.0
|
||||
genie-partner-sdk==1.0.11
|
||||
|
||||
# homeassistant.components.geniushub
|
||||
geniushub-client==0.7.1
|
||||
geniushub-client==0.7.4
|
||||
|
||||
# homeassistant.components.geocaching
|
||||
geocachingapi==0.3.0
|
||||
@@ -1522,7 +1522,7 @@ lupupy==0.3.2
|
||||
lw12==0.9.2
|
||||
|
||||
# homeassistant.components.scrape
|
||||
lxml==6.0.1
|
||||
lxml==6.1.1
|
||||
|
||||
# homeassistant.components.matrix
|
||||
matrix-nio==0.25.2
|
||||
@@ -1948,7 +1948,7 @@ py-schluter==0.1.7
|
||||
py-sucks==0.9.11
|
||||
|
||||
# homeassistant.components.synology_dsm
|
||||
py-synologydsm-api==2.9.0
|
||||
py-synologydsm-api==2.10.0
|
||||
|
||||
# homeassistant.components.unifi_access
|
||||
py-unifi-access==1.3.0
|
||||
@@ -2151,7 +2151,7 @@ pyegps==0.2.5
|
||||
pyemoncms==0.1.3
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==2.4.8
|
||||
pyenphase==2.4.9
|
||||
|
||||
# homeassistant.components.envertech_evt800
|
||||
pyenvertechevt800==0.2.4
|
||||
@@ -2238,7 +2238,7 @@ pyialarm==2.2.0
|
||||
pyicloud==2.4.1
|
||||
|
||||
# homeassistant.components.imou
|
||||
pyimouapi==1.2.7
|
||||
pyimouapi==1.2.8
|
||||
|
||||
# homeassistant.components.insteon
|
||||
pyinsteon==1.6.4
|
||||
@@ -2262,7 +2262,7 @@ pyiqvia==2022.04.0
|
||||
pyirishrail==0.0.2
|
||||
|
||||
# homeassistant.components.iskra
|
||||
pyiskra==0.1.27
|
||||
pyiskra==0.1.29
|
||||
|
||||
# homeassistant.components.iss
|
||||
pyiss==1.0.1
|
||||
@@ -2495,10 +2495,10 @@ pyqwikswitch==0.93
|
||||
pyrail==0.4.1
|
||||
|
||||
# homeassistant.components.rainbird
|
||||
pyrainbird==6.3.0
|
||||
pyrainbird==6.3.1
|
||||
|
||||
# homeassistant.components.playstation_network
|
||||
pyrate-limiter==4.2.0
|
||||
pyrate-limiter==4.4.0
|
||||
|
||||
# homeassistant.components.recswitch
|
||||
pyrecswitch==1.0.2
|
||||
@@ -2684,7 +2684,7 @@ python-join-api==0.1.1
|
||||
python-kasa[speedups]==0.10.2
|
||||
|
||||
# homeassistant.components.linkplay
|
||||
python-linkplay==0.2.12
|
||||
python-linkplay==0.2.14
|
||||
|
||||
# homeassistant.components.melcloud
|
||||
python-melcloud==0.1.3
|
||||
@@ -2860,7 +2860,7 @@ pyzerproc==0.4.8
|
||||
qbittorrent-api==2026.5.1
|
||||
|
||||
# homeassistant.components.qbus
|
||||
qbusmqttapi==1.5.0
|
||||
qbusmqttapi==1.5.1
|
||||
|
||||
# homeassistant.components.qingping
|
||||
qingping-ble==1.1.5
|
||||
@@ -2902,7 +2902,7 @@ renault-api==0.5.12
|
||||
renson-endura-delta==1.7.2
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.21.0
|
||||
reolink-aio==0.21.1
|
||||
|
||||
# homeassistant.components.radio_frequency
|
||||
rf-protocols==4.2.0
|
||||
@@ -3046,7 +3046,7 @@ slixmpp==1.13.2
|
||||
smart-meter-texas==0.5.5
|
||||
|
||||
# homeassistant.components.snapcast
|
||||
snapcast==2.3.7
|
||||
snapcast==2.3.8
|
||||
|
||||
# homeassistant.components.sonos
|
||||
soco==0.31.1
|
||||
@@ -3100,7 +3100,7 @@ starlink-grpc-core==1.2.5
|
||||
statsd==3.2.1
|
||||
|
||||
# homeassistant.components.trane
|
||||
steamloop==1.2.0
|
||||
steamloop==1.2.1
|
||||
|
||||
# homeassistant.components.steam_online
|
||||
steamodd==4.21
|
||||
@@ -3255,7 +3255,7 @@ uasiren==0.0.1
|
||||
uhooapi==1.2.8
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==13.1.1
|
||||
uiprotect==13.1.2
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.6.1
|
||||
@@ -3423,7 +3423,7 @@ yalesmartalarmclient==0.4.3
|
||||
# homeassistant.components.august
|
||||
# homeassistant.components.yale
|
||||
# homeassistant.components.yalexs_ble
|
||||
yalexs-ble==3.3.0
|
||||
yalexs-ble==3.3.1
|
||||
|
||||
# homeassistant.components.august
|
||||
# homeassistant.components.yale
|
||||
|
||||
@@ -19,7 +19,7 @@ mock-open==1.4.0
|
||||
mypy==2.1.0
|
||||
prek==0.2.28
|
||||
pydantic==2.13.4
|
||||
pylint==4.0.5
|
||||
pylint==4.0.6
|
||||
pylint-per-file-ignores==3.2.1
|
||||
pipdeptree==2.26.1
|
||||
pytest-asyncio==1.4.0
|
||||
@@ -37,7 +37,7 @@ pytest==9.0.3
|
||||
requests==2.34.2
|
||||
requests-mock==1.12.1
|
||||
respx==0.23.1
|
||||
syrupy==5.3.1
|
||||
syrupy==5.3.2
|
||||
tqdm==4.67.1
|
||||
types-aiofiles==24.1.0.20250822
|
||||
types-atomicwrites==1.4.5.1
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for calendar platform of Holiday integration."""
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from holidays import CATHOLIC
|
||||
@@ -17,6 +18,7 @@ from homeassistant.components.holiday.const import (
|
||||
)
|
||||
from homeassistant.const import CONF_COUNTRY
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
@@ -431,3 +433,45 @@ async def test_categories(
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_no_update_when_disabled(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
entity_registry: er.EntityRegistry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test that a disabled calendar entity does not trigger updates."""
|
||||
zone = await dt_util.async_get_time_zone("US/Hawaii")
|
||||
freezer.move_to(datetime(2023, 1, 1, 0, 1, 1, tzinfo=zone))
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={CONF_COUNTRY: "US", CONF_PROVINCE: "AK"},
|
||||
title="United States, AK",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await async_setup_component(hass, "calendar", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "calendar.united_states_ak"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
|
||||
entity_registry.async_update_entity(
|
||||
entity_id, disabled_by=er.RegistryEntryDisabler.USER
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with caplog.at_level(logging.WARNING, logger="homeassistant.helpers.entity"):
|
||||
freezer.move_to(datetime(2023, 1, 2, 0, 1, 1, tzinfo=zone))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
"incorrectly being triggered for updates while it is disabled"
|
||||
not in caplog.text
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ async def test_hmip_load_all_supported_devices(
|
||||
test_devices=None, test_groups=None
|
||||
)
|
||||
|
||||
assert len(mock_hap.hmip_device_by_entity_id) == 384
|
||||
assert len(mock_hap.hmip_device_by_entity_id) == 385
|
||||
|
||||
|
||||
async def test_hmip_remove_device(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user