mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
2025.4.2 (#142755)
This commit is contained in:
commit
f7794ea6b5
@ -11,5 +11,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aioairzone"],
|
"loggers": ["aioairzone"],
|
||||||
"requirements": ["aioairzone==0.9.9"]
|
"requirements": ["aioairzone==1.0.0"]
|
||||||
}
|
}
|
||||||
|
@ -266,7 +266,7 @@ async def _transform_stream(
|
|||||||
raise ValueError("Unexpected stop event without a current block")
|
raise ValueError("Unexpected stop event without a current block")
|
||||||
if current_block["type"] == "tool_use":
|
if current_block["type"] == "tool_use":
|
||||||
tool_block = cast(ToolUseBlockParam, current_block)
|
tool_block = cast(ToolUseBlockParam, current_block)
|
||||||
tool_args = json.loads(current_tool_args)
|
tool_args = json.loads(current_tool_args) if current_tool_args else {}
|
||||||
tool_block["input"] = tool_args
|
tool_block["input"] = tool_args
|
||||||
yield {
|
yield {
|
||||||
"tool_calls": [
|
"tool_calls": [
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from asyncio.exceptions import TimeoutError
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -53,10 +54,18 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await api.login()
|
await api.login()
|
||||||
except aiocomelit_exceptions.CannotConnect as err:
|
except (aiocomelit_exceptions.CannotConnect, TimeoutError) as err:
|
||||||
raise CannotConnect from err
|
raise CannotConnect(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="cannot_connect",
|
||||||
|
translation_placeholders={"error": repr(err)},
|
||||||
|
) from err
|
||||||
except aiocomelit_exceptions.CannotAuthenticate as err:
|
except aiocomelit_exceptions.CannotAuthenticate as err:
|
||||||
raise InvalidAuth from err
|
raise InvalidAuth(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="cannot_authenticate",
|
||||||
|
translation_placeholders={"error": repr(err)},
|
||||||
|
) from err
|
||||||
finally:
|
finally:
|
||||||
await api.logout()
|
await api.logout()
|
||||||
await api.close()
|
await api.close()
|
||||||
|
@ -69,6 +69,12 @@
|
|||||||
},
|
},
|
||||||
"invalid_clima_data": {
|
"invalid_clima_data": {
|
||||||
"message": "Invalid 'clima' data"
|
"message": "Invalid 'clima' data"
|
||||||
|
},
|
||||||
|
"cannot_connect": {
|
||||||
|
"message": "Error connecting: {error}"
|
||||||
|
},
|
||||||
|
"cannot_authenticate": {
|
||||||
|
"message": "Error authenticating: {error}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
|||||||
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_UUID
|
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PASSWORD, CONF_UUID
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
|
||||||
|
from homeassistant.util.ssl import client_context_no_verify
|
||||||
|
|
||||||
from .const import DOMAIN, KEY_MAC, TIMEOUT
|
from .const import DOMAIN, KEY_MAC, TIMEOUT
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
key=key,
|
key=key,
|
||||||
uuid=uuid,
|
uuid=uuid,
|
||||||
password=password,
|
password=password,
|
||||||
|
ssl_context=client_context_no_verify(),
|
||||||
)
|
)
|
||||||
except (TimeoutError, ClientError):
|
except (TimeoutError, ClientError):
|
||||||
self.host = None
|
self.host = None
|
||||||
|
@ -13,7 +13,7 @@ from aioesphomeapi import (
|
|||||||
APIConnectionError,
|
APIConnectionError,
|
||||||
APIVersion,
|
APIVersion,
|
||||||
DeviceInfo as EsphomeDeviceInfo,
|
DeviceInfo as EsphomeDeviceInfo,
|
||||||
EncryptionHelloAPIError,
|
EncryptionPlaintextAPIError,
|
||||||
EntityInfo,
|
EntityInfo,
|
||||||
HomeassistantServiceCall,
|
HomeassistantServiceCall,
|
||||||
InvalidAuthAPIError,
|
InvalidAuthAPIError,
|
||||||
@ -571,7 +571,7 @@ class ESPHomeManager:
|
|||||||
if isinstance(
|
if isinstance(
|
||||||
err,
|
err,
|
||||||
(
|
(
|
||||||
EncryptionHelloAPIError,
|
EncryptionPlaintextAPIError,
|
||||||
RequiresEncryptionAPIError,
|
RequiresEncryptionAPIError,
|
||||||
InvalidEncryptionKeyAPIError,
|
InvalidEncryptionKeyAPIError,
|
||||||
InvalidAuthAPIError,
|
InvalidAuthAPIError,
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
|
||||||
"mqtt": ["esphome/discover/#"],
|
"mqtt": ["esphome/discover/#"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aioesphomeapi==29.8.0",
|
"aioesphomeapi==29.9.0",
|
||||||
"esphome-dashboard-api==1.2.3",
|
"esphome-dashboard-api==1.2.3",
|
||||||
"bleak-esphome==2.12.0"
|
"bleak-esphome==2.12.0"
|
||||||
],
|
],
|
||||||
|
@ -53,5 +53,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["flux_led"],
|
"loggers": ["flux_led"],
|
||||||
"requirements": ["flux-led==1.1.3"]
|
"requirements": ["flux-led==1.2.0"]
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, Device
|
|||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import BUTTON_TYPE_WOL, CONNECTION_TYPE_LAN, DOMAIN, MeshRoles
|
from .const import BUTTON_TYPE_WOL, CONNECTION_TYPE_LAN, MeshRoles
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
FRITZ_DATA_KEY,
|
FRITZ_DATA_KEY,
|
||||||
AvmWrapper,
|
AvmWrapper,
|
||||||
@ -175,16 +175,6 @@ class FritzBoxWOLButton(FritzDeviceBase, ButtonEntity):
|
|||||||
self._name = f"{self.hostname} Wake on LAN"
|
self._name = f"{self.hostname} Wake on LAN"
|
||||||
self._attr_unique_id = f"{self._mac}_wake_on_lan"
|
self._attr_unique_id = f"{self._mac}_wake_on_lan"
|
||||||
self._is_available = True
|
self._is_available = True
|
||||||
self._attr_device_info = DeviceInfo(
|
|
||||||
connections={(CONNECTION_NETWORK_MAC, self._mac)},
|
|
||||||
default_manufacturer="AVM",
|
|
||||||
default_model="FRITZ!Box Tracked device",
|
|
||||||
default_name=device.hostname,
|
|
||||||
via_device=(
|
|
||||||
DOMAIN,
|
|
||||||
avm_wrapper.unique_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_press(self) -> None:
|
async def async_press(self) -> None:
|
||||||
"""Press the button."""
|
"""Press the button."""
|
||||||
|
@ -526,7 +526,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
|||||||
def manage_device_info(
|
def manage_device_info(
|
||||||
self, dev_info: Device, dev_mac: str, consider_home: bool
|
self, dev_info: Device, dev_mac: str, consider_home: bool
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Update device lists."""
|
"""Update device lists and return if device is new."""
|
||||||
_LOGGER.debug("Client dev_info: %s", dev_info)
|
_LOGGER.debug("Client dev_info: %s", dev_info)
|
||||||
|
|
||||||
if dev_mac in self._devices:
|
if dev_mac in self._devices:
|
||||||
@ -536,6 +536,16 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
|||||||
device = FritzDevice(dev_mac, dev_info.name)
|
device = FritzDevice(dev_mac, dev_info.name)
|
||||||
device.update(dev_info, consider_home)
|
device.update(dev_info, consider_home)
|
||||||
self._devices[dev_mac] = device
|
self._devices[dev_mac] = device
|
||||||
|
|
||||||
|
# manually register device entry for new connected device
|
||||||
|
dr.async_get(self.hass).async_get_or_create(
|
||||||
|
config_entry_id=self.config_entry.entry_id,
|
||||||
|
connections={(CONNECTION_NETWORK_MAC, dev_mac)},
|
||||||
|
default_manufacturer="AVM",
|
||||||
|
default_model="FRITZ!Box Tracked device",
|
||||||
|
default_name=device.hostname,
|
||||||
|
via_device=(DOMAIN, self.unique_id),
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def async_send_signal_device_update(self, new_device: bool) -> None:
|
async def async_send_signal_device_update(self, new_device: bool) -> None:
|
||||||
|
@ -26,6 +26,9 @@ class FritzDeviceBase(CoordinatorEntity[AvmWrapper]):
|
|||||||
self._avm_wrapper = avm_wrapper
|
self._avm_wrapper = avm_wrapper
|
||||||
self._mac: str = device.mac_address
|
self._mac: str = device.mac_address
|
||||||
self._name: str = device.hostname or DEFAULT_DEVICE_NAME
|
self._name: str = device.hostname or DEFAULT_DEVICE_NAME
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
connections={(dr.CONNECTION_NETWORK_MAC, device.mac_address)}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
|
@ -7,9 +7,7 @@ rules:
|
|||||||
config-flow-test-coverage:
|
config-flow-test-coverage:
|
||||||
status: todo
|
status: todo
|
||||||
comment: one coverage miss in line 110
|
comment: one coverage miss in line 110
|
||||||
config-flow:
|
config-flow: done
|
||||||
status: todo
|
|
||||||
comment: data_description are missing
|
|
||||||
dependency-transparency: done
|
dependency-transparency: done
|
||||||
docs-actions: done
|
docs-actions: done
|
||||||
docs-high-level-description: done
|
docs-high-level-description: done
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"data_description_host": "The hostname or IP address of your FRITZ!Box router.",
|
||||||
|
"data_description_port": "Leave empty to use the default port.",
|
||||||
|
"data_description_username": "Username for the FRITZ!Box.",
|
||||||
|
"data_description_password": "Password for the FRITZ!Box.",
|
||||||
|
"data_description_ssl": "Use SSL to connect to the FRITZ!Box."
|
||||||
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"flow_title": "{name}",
|
"flow_title": "{name}",
|
||||||
"step": {
|
"step": {
|
||||||
@ -9,6 +16,11 @@
|
|||||||
"username": "[%key:common::config_flow::data::username%]",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]",
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
"ssl": "[%key:common::config_flow::data::ssl%]"
|
"ssl": "[%key:common::config_flow::data::ssl%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"username": "[%key:component::fritz::common::data_description_username%]",
|
||||||
|
"password": "[%key:component::fritz::common::data_description_password%]",
|
||||||
|
"ssl": "[%key:component::fritz::common::data_description_ssl%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reauth_confirm": {
|
"reauth_confirm": {
|
||||||
@ -17,6 +29,10 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"username": "[%key:component::fritz::common::data_description_username%]",
|
||||||
|
"password": "[%key:component::fritz::common::data_description_password%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"reconfigure": {
|
"reconfigure": {
|
||||||
@ -28,8 +44,9 @@
|
|||||||
"ssl": "[%key:common::config_flow::data::ssl%]"
|
"ssl": "[%key:common::config_flow::data::ssl%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"host": "The hostname or IP address of your FRITZ!Box router.",
|
"host": "[%key:component::fritz::common::data_description_host%]",
|
||||||
"port": "Leave it empty to use the default port."
|
"port": "[%key:component::fritz::common::data_description_port%]",
|
||||||
|
"ssl": "[%key:component::fritz::common::data_description_ssl%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
@ -43,8 +60,11 @@
|
|||||||
"ssl": "[%key:common::config_flow::data::ssl%]"
|
"ssl": "[%key:common::config_flow::data::ssl%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"host": "The hostname or IP address of your FRITZ!Box router.",
|
"host": "[%key:component::fritz::common::data_description_host%]",
|
||||||
"port": "Leave it empty to use the default port."
|
"port": "[%key:component::fritz::common::data_description_port%]",
|
||||||
|
"username": "[%key:component::fritz::common::data_description_username%]",
|
||||||
|
"password": "[%key:component::fritz::common::data_description_password%]",
|
||||||
|
"ssl": "[%key:component::fritz::common::data_description_ssl%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -70,6 +90,10 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"consider_home": "Seconds to consider a device at 'home'",
|
"consider_home": "Seconds to consider a device at 'home'",
|
||||||
"old_discovery": "Enable old discovery method"
|
"old_discovery": "Enable old discovery method"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"consider_home": "Time in seconds to consider a device at home. Default is 180 seconds.",
|
||||||
|
"old_discovery": "Enable old discovery method. This is needed for some scenarios."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,8 +193,12 @@
|
|||||||
"config_entry_not_found": {
|
"config_entry_not_found": {
|
||||||
"message": "Failed to perform action \"{service}\". Config entry for target not found"
|
"message": "Failed to perform action \"{service}\". Config entry for target not found"
|
||||||
},
|
},
|
||||||
"service_parameter_unknown": { "message": "Action or parameter unknown" },
|
"service_parameter_unknown": {
|
||||||
"service_not_supported": { "message": "Action not supported" },
|
"message": "Action or parameter unknown"
|
||||||
|
},
|
||||||
|
"service_not_supported": {
|
||||||
|
"message": "Action not supported"
|
||||||
|
},
|
||||||
"error_refresh_hosts_info": {
|
"error_refresh_hosts_info": {
|
||||||
"message": "Error refreshing hosts info"
|
"message": "Error refreshing hosts info"
|
||||||
},
|
},
|
||||||
|
@ -511,16 +511,6 @@ class FritzBoxProfileSwitch(FritzDeviceBase, SwitchEntity):
|
|||||||
self._name = f"{device.hostname} Internet Access"
|
self._name = f"{device.hostname} Internet Access"
|
||||||
self._attr_unique_id = f"{self._mac}_internet_access"
|
self._attr_unique_id = f"{self._mac}_internet_access"
|
||||||
self._attr_entity_category = EntityCategory.CONFIG
|
self._attr_entity_category = EntityCategory.CONFIG
|
||||||
self._attr_device_info = DeviceInfo(
|
|
||||||
connections={(CONNECTION_NETWORK_MAC, self._mac)},
|
|
||||||
default_manufacturer="AVM",
|
|
||||||
default_model="FRITZ!Box Tracked device",
|
|
||||||
default_name=device.hostname,
|
|
||||||
via_device=(
|
|
||||||
DOMAIN,
|
|
||||||
avm_wrapper.unique_id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20250404.0"]
|
"requirements": ["home-assistant-frontend==20250411.0"]
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/google",
|
"documentation": "https://www.home-assistant.io/integrations/google",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["googleapiclient"],
|
"loggers": ["googleapiclient"],
|
||||||
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.0.3"]
|
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.1.0"]
|
||||||
}
|
}
|
||||||
|
@ -303,7 +303,7 @@ async def google_generative_ai_config_option_schema(
|
|||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
description={"suggested_value": options.get(CONF_TEMPERATURE)},
|
description={"suggested_value": options.get(CONF_TEMPERATURE)},
|
||||||
default=RECOMMENDED_TEMPERATURE,
|
default=RECOMMENDED_TEMPERATURE,
|
||||||
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
|
): NumberSelector(NumberSelectorConfig(min=0, max=2, step=0.05)),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_TOP_P,
|
CONF_TOP_P,
|
||||||
description={"suggested_value": options.get(CONF_TOP_P)},
|
description={"suggested_value": options.get(CONF_TOP_P)},
|
||||||
|
@ -55,6 +55,10 @@ from .const import (
|
|||||||
# Max number of back and forth with the LLM to generate a response
|
# Max number of back and forth with the LLM to generate a response
|
||||||
MAX_TOOL_ITERATIONS = 10
|
MAX_TOOL_ITERATIONS = 10
|
||||||
|
|
||||||
|
ERROR_GETTING_RESPONSE = (
|
||||||
|
"Sorry, I had a problem getting a response from Google Generative AI."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -429,6 +433,12 @@ class GoogleGenerativeAIConversationEntity(
|
|||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"The message got blocked due to content violations, reason: {chat_response.prompt_feedback.block_reason_message}"
|
f"The message got blocked due to content violations, reason: {chat_response.prompt_feedback.block_reason_message}"
|
||||||
)
|
)
|
||||||
|
if not chat_response.candidates:
|
||||||
|
LOGGER.error(
|
||||||
|
"No candidates found in the response: %s",
|
||||||
|
chat_response,
|
||||||
|
)
|
||||||
|
raise HomeAssistantError(ERROR_GETTING_RESPONSE)
|
||||||
|
|
||||||
except (
|
except (
|
||||||
APIError,
|
APIError,
|
||||||
@ -452,9 +462,7 @@ class GoogleGenerativeAIConversationEntity(
|
|||||||
|
|
||||||
response_parts = chat_response.candidates[0].content.parts
|
response_parts = chat_response.candidates[0].content.parts
|
||||||
if not response_parts:
|
if not response_parts:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(ERROR_GETTING_RESPONSE)
|
||||||
"Sorry, I had a problem getting a response from Google Generative AI."
|
|
||||||
)
|
|
||||||
content = " ".join(
|
content = " ".join(
|
||||||
[part.text.strip() for part in response_parts if part.text]
|
[part.text.strip() for part in response_parts if part.text]
|
||||||
)
|
)
|
||||||
|
@ -40,7 +40,8 @@
|
|||||||
"enable_google_search_tool": "Enable Google Search tool"
|
"enable_google_search_tool": "Enable Google Search tool"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"prompt": "Instruct how the LLM should respond. This can be a template."
|
"prompt": "Instruct how the LLM should respond. This can be a template.",
|
||||||
|
"enable_google_search_tool": "Only works with \"No control\" in the \"Control Home Assistant\" setting. See docs for a workaround using it with \"Assist\"."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/growatt_server",
|
"documentation": "https://www.home-assistant.io/integrations/growatt_server",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["growattServer"],
|
"loggers": ["growattServer"],
|
||||||
"requirements": ["growattServer==1.5.0"]
|
"requirements": ["growattServer==1.6.0"]
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["pyheos"],
|
"loggers": ["pyheos"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pyheos==1.0.4"],
|
"requirements": ["pyheos==1.0.5"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"st": "urn:schemas-denon-com:device:ACT-Denon:1"
|
"st": "urn:schemas-denon-com:device:ACT-Denon:1"
|
||||||
|
@ -87,6 +87,7 @@ BASE_SUPPORTED_FEATURES = (
|
|||||||
|
|
||||||
PLAY_STATE_TO_STATE = {
|
PLAY_STATE_TO_STATE = {
|
||||||
None: MediaPlayerState.IDLE,
|
None: MediaPlayerState.IDLE,
|
||||||
|
PlayState.UNKNOWN: MediaPlayerState.IDLE,
|
||||||
PlayState.PLAY: MediaPlayerState.PLAYING,
|
PlayState.PLAY: MediaPlayerState.PLAYING,
|
||||||
PlayState.STOP: MediaPlayerState.IDLE,
|
PlayState.STOP: MediaPlayerState.IDLE,
|
||||||
PlayState.PAUSE: MediaPlayerState.PAUSED,
|
PlayState.PAUSE: MediaPlayerState.PAUSED,
|
||||||
|
@ -659,13 +659,7 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity):
|
|||||||
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
|
# e.g. a thermostat is "heating" a room to 75 degrees Fahrenheit.
|
||||||
# Can be 0 - 2 (Off, Heat, Cool)
|
# Can be 0 - 2 (Off, Heat, Cool)
|
||||||
|
|
||||||
# If the HVAC is switched off, it must be idle
|
|
||||||
# This works around a bug in some devices (like Eve radiator valves) that
|
|
||||||
# return they are heating when they are not.
|
|
||||||
target = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
|
target = self.service.value(CharacteristicsTypes.HEATING_COOLING_TARGET)
|
||||||
if target == HeatingCoolingTargetValues.OFF:
|
|
||||||
return HVACAction.IDLE
|
|
||||||
|
|
||||||
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_CURRENT)
|
value = self.service.value(CharacteristicsTypes.HEATING_COOLING_CURRENT)
|
||||||
current_hass_value = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
|
current_hass_value = CURRENT_MODE_HOMEKIT_TO_HASS.get(value)
|
||||||
|
|
||||||
@ -679,6 +673,12 @@ class HomeKitClimateEntity(HomeKitBaseClimateEntity):
|
|||||||
):
|
):
|
||||||
return HVACAction.FAN
|
return HVACAction.FAN
|
||||||
|
|
||||||
|
# If the HVAC is switched off, it must be idle
|
||||||
|
# This works around a bug in some devices (like Eve radiator valves) that
|
||||||
|
# return they are heating when they are not.
|
||||||
|
if target == HeatingCoolingTargetValues.OFF:
|
||||||
|
return HVACAction.IDLE
|
||||||
|
|
||||||
return current_hass_value
|
return current_hass_value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -136,6 +136,7 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[MowerDictionary]):
|
|||||||
# Process new device
|
# Process new device
|
||||||
new_devices = current_devices - self._devices_last_update
|
new_devices = current_devices - self._devices_last_update
|
||||||
if new_devices:
|
if new_devices:
|
||||||
|
self.data = data
|
||||||
_LOGGER.debug("New devices found: %s", ", ".join(map(str, new_devices)))
|
_LOGGER.debug("New devices found: %s", ", ".join(map(str, new_devices)))
|
||||||
self._add_new_devices(new_devices)
|
self._add_new_devices(new_devices)
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ from homeassistant.components.sensor import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
CONF_DOMAIN,
|
CONF_DOMAIN,
|
||||||
CONF_ENTITIES,
|
CONF_ENTITIES,
|
||||||
CONF_SOURCE,
|
CONF_SOURCE,
|
||||||
@ -49,6 +50,7 @@ DEVICE_CLASS_MAPPING = {
|
|||||||
pypck.lcn_defs.VarUnit.METERPERSECOND: SensorDeviceClass.SPEED,
|
pypck.lcn_defs.VarUnit.METERPERSECOND: SensorDeviceClass.SPEED,
|
||||||
pypck.lcn_defs.VarUnit.VOLT: SensorDeviceClass.VOLTAGE,
|
pypck.lcn_defs.VarUnit.VOLT: SensorDeviceClass.VOLTAGE,
|
||||||
pypck.lcn_defs.VarUnit.AMPERE: SensorDeviceClass.CURRENT,
|
pypck.lcn_defs.VarUnit.AMPERE: SensorDeviceClass.CURRENT,
|
||||||
|
pypck.lcn_defs.VarUnit.PPM: SensorDeviceClass.CO2,
|
||||||
}
|
}
|
||||||
|
|
||||||
UNIT_OF_MEASUREMENT_MAPPING = {
|
UNIT_OF_MEASUREMENT_MAPPING = {
|
||||||
@ -60,6 +62,7 @@ UNIT_OF_MEASUREMENT_MAPPING = {
|
|||||||
pypck.lcn_defs.VarUnit.METERPERSECOND: UnitOfSpeed.METERS_PER_SECOND,
|
pypck.lcn_defs.VarUnit.METERPERSECOND: UnitOfSpeed.METERS_PER_SECOND,
|
||||||
pypck.lcn_defs.VarUnit.VOLT: UnitOfElectricPotential.VOLT,
|
pypck.lcn_defs.VarUnit.VOLT: UnitOfElectricPotential.VOLT,
|
||||||
pypck.lcn_defs.VarUnit.AMPERE: UnitOfElectricCurrent.AMPERE,
|
pypck.lcn_defs.VarUnit.AMPERE: UnitOfElectricCurrent.AMPERE,
|
||||||
|
pypck.lcn_defs.VarUnit.PPM: CONCENTRATION_PARTS_PER_MILLION,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,5 +35,5 @@
|
|||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
"documentation": "https://www.home-assistant.io/integrations/led_ble",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["bluetooth-data-tools==1.26.5", "led-ble==1.1.6"]
|
"requirements": ["bluetooth-data-tools==1.26.5", "led-ble==1.1.7"]
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ turn_on:
|
|||||||
example: "[255, 100, 100]"
|
example: "[255, 100, 100]"
|
||||||
selector:
|
selector:
|
||||||
color_rgb:
|
color_rgb:
|
||||||
kelvin: &kelvin
|
color_temp_kelvin: &color_temp_kelvin
|
||||||
filter: *color_temp_support
|
filter: *color_temp_support
|
||||||
selector:
|
selector:
|
||||||
color_temp:
|
color_temp:
|
||||||
@ -317,7 +317,7 @@ toggle:
|
|||||||
fields:
|
fields:
|
||||||
transition: *transition
|
transition: *transition
|
||||||
rgb_color: *rgb_color
|
rgb_color: *rgb_color
|
||||||
kelvin: *kelvin
|
color_temp_kelvin: *color_temp_kelvin
|
||||||
brightness_pct: *brightness_pct
|
brightness_pct: *brightness_pct
|
||||||
effect: *effect
|
effect: *effect
|
||||||
advanced_fields:
|
advanced_fields:
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
"field_flash_name": "Flash",
|
"field_flash_name": "Flash",
|
||||||
"field_hs_color_description": "Color in hue/sat format. A list of two integers. Hue is 0-360 and Sat is 0-100.",
|
"field_hs_color_description": "Color in hue/sat format. A list of two integers. Hue is 0-360 and Sat is 0-100.",
|
||||||
"field_hs_color_name": "Hue/Sat color",
|
"field_hs_color_name": "Hue/Sat color",
|
||||||
"field_kelvin_description": "Color temperature in Kelvin.",
|
"field_color_temp_kelvin_description": "Color temperature in Kelvin.",
|
||||||
"field_kelvin_name": "Color temperature",
|
"field_color_temp_kelvin_name": "Color temperature",
|
||||||
"field_profile_description": "Name of a light profile to use.",
|
"field_profile_description": "Name of a light profile to use.",
|
||||||
"field_profile_name": "Profile",
|
"field_profile_name": "Profile",
|
||||||
"field_rgb_color_description": "The color in RGB format. A list of three integers between 0 and 255 representing the values of red, green, and blue.",
|
"field_rgb_color_description": "The color in RGB format. A list of three integers between 0 and 255 representing the values of red, green, and blue.",
|
||||||
@ -322,9 +322,9 @@
|
|||||||
"name": "[%key:component::light::common::field_color_temp_name%]",
|
"name": "[%key:component::light::common::field_color_temp_name%]",
|
||||||
"description": "[%key:component::light::common::field_color_temp_description%]"
|
"description": "[%key:component::light::common::field_color_temp_description%]"
|
||||||
},
|
},
|
||||||
"kelvin": {
|
"color_temp_kelvin": {
|
||||||
"name": "[%key:component::light::common::field_kelvin_name%]",
|
"name": "[%key:component::light::common::field_color_temp_kelvin_name%]",
|
||||||
"description": "[%key:component::light::common::field_kelvin_description%]"
|
"description": "[%key:component::light::common::field_color_temp_kelvin_description%]"
|
||||||
},
|
},
|
||||||
"brightness": {
|
"brightness": {
|
||||||
"name": "[%key:component::light::common::field_brightness_name%]",
|
"name": "[%key:component::light::common::field_brightness_name%]",
|
||||||
@ -420,9 +420,9 @@
|
|||||||
"name": "[%key:component::light::common::field_color_temp_name%]",
|
"name": "[%key:component::light::common::field_color_temp_name%]",
|
||||||
"description": "[%key:component::light::common::field_color_temp_description%]"
|
"description": "[%key:component::light::common::field_color_temp_description%]"
|
||||||
},
|
},
|
||||||
"kelvin": {
|
"color_temp_kelvin": {
|
||||||
"name": "[%key:component::light::common::field_kelvin_name%]",
|
"name": "[%key:component::light::common::field_color_temp_kelvin_name%]",
|
||||||
"description": "[%key:component::light::common::field_kelvin_description%]"
|
"description": "[%key:component::light::common::field_color_temp_kelvin_description%]"
|
||||||
},
|
},
|
||||||
"brightness": {
|
"brightness": {
|
||||||
"name": "[%key:component::light::common::field_brightness_name%]",
|
"name": "[%key:component::light::common::field_brightness_name%]",
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/livisi",
|
"documentation": "https://www.home-assistant.io/integrations/livisi",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["livisi==0.0.24"]
|
"requirements": ["livisi==0.0.25"]
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["ical"],
|
"loggers": ["ical"],
|
||||||
"requirements": ["ical==9.0.3"]
|
"requirements": ["ical==9.1.0"]
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["ical==9.0.3"]
|
"requirements": ["ical==9.1.0"]
|
||||||
}
|
}
|
||||||
|
@ -1611,6 +1611,7 @@ def async_is_pem_data(data: bytes) -> bool:
|
|||||||
return (
|
return (
|
||||||
b"-----BEGIN CERTIFICATE-----" in data
|
b"-----BEGIN CERTIFICATE-----" in data
|
||||||
or b"-----BEGIN PRIVATE KEY-----" in data
|
or b"-----BEGIN PRIVATE KEY-----" in data
|
||||||
|
or b"-----BEGIN EC PRIVATE KEY-----" in data
|
||||||
or b"-----BEGIN RSA PRIVATE KEY-----" in data
|
or b"-----BEGIN RSA PRIVATE KEY-----" in data
|
||||||
or b"-----BEGIN ENCRYPTED PRIVATE KEY-----" in data
|
or b"-----BEGIN ENCRYPTED PRIVATE KEY-----" in data
|
||||||
)
|
)
|
||||||
|
@ -154,18 +154,14 @@ def get_origin_support_url(discovery_payload: MQTTDiscoveryPayload) -> str | Non
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_log_discovery_origin_info(
|
def async_log_discovery_origin_info(
|
||||||
message: str, discovery_payload: MQTTDiscoveryPayload, level: int = logging.INFO
|
message: str, discovery_payload: MQTTDiscoveryPayload
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Log information about the discovery and origin."""
|
"""Log information about the discovery and origin."""
|
||||||
# We only log origin info once per device discovery
|
if not _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
if not _LOGGER.isEnabledFor(level):
|
# bail out early if debug logging is disabled
|
||||||
# bail out early if logging is disabled
|
|
||||||
return
|
return
|
||||||
_LOGGER.log(
|
_LOGGER.debug(
|
||||||
level,
|
"%s%s", message, get_origin_log_string(discovery_payload, include_url=True)
|
||||||
"%s%s",
|
|
||||||
message,
|
|
||||||
get_origin_log_string(discovery_payload, include_url=True),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -562,7 +558,7 @@ async def async_start( # noqa: C901
|
|||||||
elif already_discovered:
|
elif already_discovered:
|
||||||
# Dispatch update
|
# Dispatch update
|
||||||
message = f"Component has already been discovered: {component} {discovery_id}, sending update"
|
message = f"Component has already been discovered: {component} {discovery_id}, sending update"
|
||||||
async_log_discovery_origin_info(message, payload, logging.DEBUG)
|
async_log_discovery_origin_info(message, payload)
|
||||||
async_dispatcher_send(
|
async_dispatcher_send(
|
||||||
hass, MQTT_DISCOVERY_UPDATED.format(*discovery_hash), payload
|
hass, MQTT_DISCOVERY_UPDATED.format(*discovery_hash), payload
|
||||||
)
|
)
|
||||||
|
@ -70,8 +70,8 @@ MQTT_NUMBER_ATTRIBUTES_BLOCKED = frozenset(
|
|||||||
|
|
||||||
def validate_config(config: ConfigType) -> ConfigType:
|
def validate_config(config: ConfigType) -> ConfigType:
|
||||||
"""Validate that the configuration is valid, throws if it isn't."""
|
"""Validate that the configuration is valid, throws if it isn't."""
|
||||||
if config[CONF_MIN] >= config[CONF_MAX]:
|
if config[CONF_MIN] > config[CONF_MAX]:
|
||||||
raise vol.Invalid(f"'{CONF_MAX}' must be > '{CONF_MIN}'")
|
raise vol.Invalid(f"{CONF_MAX} must be >= {CONF_MIN}")
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -151,6 +151,9 @@ async def async_setup_entry(
|
|||||||
assert event.object_id is not None
|
assert event.object_id is not None
|
||||||
if event.object_id in added_ids:
|
if event.object_id in added_ids:
|
||||||
return
|
return
|
||||||
|
player = mass.players.get(event.object_id)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert player is not None
|
||||||
if not player.expose_to_ha:
|
if not player.expose_to_ha:
|
||||||
return
|
return
|
||||||
added_ids.add(event.object_id)
|
added_ids.add(event.object_id)
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["opower"],
|
"loggers": ["opower"],
|
||||||
"requirements": ["opower==0.9.0"]
|
"requirements": ["opower==0.11.1"]
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,10 @@ class RemoteCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
except CalendarParseError as err:
|
except CalendarParseError as err:
|
||||||
errors["base"] = "invalid_ics_file"
|
errors["base"] = "invalid_ics_file"
|
||||||
_LOGGER.debug("Invalid .ics file: %s", err)
|
_LOGGER.error("Error reading the calendar information: %s", err.message)
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Additional calendar error detail: %s", str(err.detailed_error)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=user_input[CONF_CALENDAR_NAME], data=user_input
|
title=user_input[CONF_CALENDAR_NAME], data=user_input
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["ical"],
|
"loggers": ["ical"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["ical==9.0.3"]
|
"requirements": ["ical==9.1.0"]
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"forbidden": "The server understood the request but refuses to authorize it.",
|
"forbidden": "The server understood the request but refuses to authorize it.",
|
||||||
"invalid_ics_file": "[%key:component::local_calendar::config::error::invalid_ics_file%]"
|
"invalid_ics_file": "There was a problem reading the calendar information. See the error log for additional details."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
},
|
},
|
||||||
"sensor": {
|
"sensor": {
|
||||||
"charge_state": {
|
"charge_state": {
|
||||||
"default": "mdi:mdi:flash-off",
|
"default": "mdi:flash-off",
|
||||||
"state": {
|
"state": {
|
||||||
"charge_in_progress": "mdi:flash"
|
"charge_in_progress": "mdi:flash"
|
||||||
}
|
}
|
||||||
|
@ -420,6 +420,14 @@ def migrate_entity_ids(
|
|||||||
if entity.device_id in ch_device_ids:
|
if entity.device_id in ch_device_ids:
|
||||||
ch = ch_device_ids[entity.device_id]
|
ch = ch_device_ids[entity.device_id]
|
||||||
id_parts = entity.unique_id.split("_", 2)
|
id_parts = entity.unique_id.split("_", 2)
|
||||||
|
if len(id_parts) < 3:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Reolink channel %s entity has unexpected unique_id format %s, with device id %s",
|
||||||
|
ch,
|
||||||
|
entity.unique_id,
|
||||||
|
entity.device_id,
|
||||||
|
)
|
||||||
|
continue
|
||||||
if host.api.supported(ch, "UID") and id_parts[1] != host.api.camera_uid(ch):
|
if host.api.supported(ch, "UID") and id_parts[1] != host.api.camera_uid(ch):
|
||||||
new_id = f"{host.unique_id}_{host.api.camera_uid(ch)}_{id_parts[2]}"
|
new_id = f"{host.unique_id}_{host.api.camera_uid(ch)}_{id_parts[2]}"
|
||||||
existing_entity = entity_reg.async_get_entity_id(
|
existing_entity = entity_reg.async_get_entity_id(
|
||||||
|
@ -301,7 +301,7 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
for entity_description in BINARY_SMART_AI_SENSORS
|
for entity_description in BINARY_SMART_AI_SENSORS
|
||||||
for location in api.baichuan.smart_location_list(
|
for location in api.baichuan.smart_location_list(
|
||||||
channel, entity_description.key
|
channel, entity_description.smart_type
|
||||||
)
|
)
|
||||||
if entity_description.supported(api, channel, location)
|
if entity_description.supported(api, channel, location)
|
||||||
)
|
)
|
||||||
|
@ -19,5 +19,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["reolink_aio"],
|
"loggers": ["reolink_aio"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["reolink-aio==0.13.0"]
|
"requirements": ["reolink-aio==0.13.1"]
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
ImageConfig(scale=MAP_SCALE),
|
ImageConfig(scale=MAP_SCALE),
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
|
self.last_update_state: str | None = None
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def dock_device_info(self) -> DeviceInfo:
|
def dock_device_info(self) -> DeviceInfo:
|
||||||
@ -225,7 +226,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
"""Update the currently selected map."""
|
"""Update the currently selected map."""
|
||||||
# The current map was set in the props update, so these can be done without
|
# The current map was set in the props update, so these can be done without
|
||||||
# worry of applying them to the wrong map.
|
# worry of applying them to the wrong map.
|
||||||
if self.current_map is None:
|
if self.current_map is None or self.current_map not in self.maps:
|
||||||
# This exists as a safeguard/ to keep mypy happy.
|
# This exists as a safeguard/ to keep mypy happy.
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@ -291,7 +292,6 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> DeviceProp:
|
async def _async_update_data(self) -> DeviceProp:
|
||||||
"""Update data via library."""
|
"""Update data via library."""
|
||||||
previous_state = self.roborock_device_info.props.status.state_name
|
|
||||||
try:
|
try:
|
||||||
# Update device props and standard api information
|
# Update device props and standard api information
|
||||||
await self._update_device_prop()
|
await self._update_device_prop()
|
||||||
@ -302,13 +302,17 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
# If the vacuum is currently cleaning and it has been IMAGE_CACHE_INTERVAL
|
# If the vacuum is currently cleaning and it has been IMAGE_CACHE_INTERVAL
|
||||||
# since the last map update, you can update the map.
|
# since the last map update, you can update the map.
|
||||||
new_status = self.roborock_device_info.props.status
|
new_status = self.roborock_device_info.props.status
|
||||||
if self.current_map is not None and (
|
if (
|
||||||
(
|
self.current_map is not None
|
||||||
new_status.in_cleaning
|
and (current_map := self.maps.get(self.current_map))
|
||||||
and (dt_util.utcnow() - self.maps[self.current_map].last_updated)
|
and (
|
||||||
> IMAGE_CACHE_INTERVAL
|
(
|
||||||
|
new_status.in_cleaning
|
||||||
|
and (dt_util.utcnow() - current_map.last_updated)
|
||||||
|
> IMAGE_CACHE_INTERVAL
|
||||||
|
)
|
||||||
|
or self.last_update_state != new_status.state_name
|
||||||
)
|
)
|
||||||
or previous_state != new_status.state_name
|
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
await self.update_map()
|
await self.update_map()
|
||||||
@ -330,6 +334,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|||||||
self.update_interval = V1_CLOUD_NOT_CLEANING_INTERVAL
|
self.update_interval = V1_CLOUD_NOT_CLEANING_INTERVAL
|
||||||
else:
|
else:
|
||||||
self.update_interval = V1_LOCAL_NOT_CLEANING_INTERVAL
|
self.update_interval = V1_LOCAL_NOT_CLEANING_INTERVAL
|
||||||
|
self.last_update_state = self.roborock_device_info.props.status.state_name
|
||||||
return self.roborock_device_info.props
|
return self.roborock_device_info.props
|
||||||
|
|
||||||
def _set_current_map(self) -> None:
|
def _set_current_map(self) -> None:
|
||||||
|
@ -381,7 +381,10 @@ class RoborockCurrentRoom(RoborockCoordinatedEntityV1, SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def options(self) -> list[str]:
|
def options(self) -> list[str]:
|
||||||
"""Return the currently valid rooms."""
|
"""Return the currently valid rooms."""
|
||||||
if self.coordinator.current_map is not None:
|
if (
|
||||||
|
self.coordinator.current_map is not None
|
||||||
|
and self.coordinator.current_map in self.coordinator.maps
|
||||||
|
):
|
||||||
return list(
|
return list(
|
||||||
self.coordinator.maps[self.coordinator.current_map].rooms.values()
|
self.coordinator.maps[self.coordinator.current_map].rooms.values()
|
||||||
)
|
)
|
||||||
@ -390,7 +393,10 @@ class RoborockCurrentRoom(RoborockCoordinatedEntityV1, SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def native_value(self) -> str | None:
|
def native_value(self) -> str | None:
|
||||||
"""Return the value reported by the sensor."""
|
"""Return the value reported by the sensor."""
|
||||||
if self.coordinator.current_map is not None:
|
if (
|
||||||
|
self.coordinator.current_map is not None
|
||||||
|
and self.coordinator.current_map in self.coordinator.maps
|
||||||
|
):
|
||||||
return self.coordinator.maps[self.coordinator.current_map].current_room
|
return self.coordinator.maps[self.coordinator.current_map].current_room
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
|||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from . import trigger
|
from . import trigger
|
||||||
|
from .const import DOMAIN
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
async_get_client_by_device_entry,
|
async_get_client_by_device_entry,
|
||||||
async_get_device_entry_by_device_id,
|
async_get_device_entry_by_device_id,
|
||||||
@ -75,4 +76,8 @@ async def async_attach_trigger(
|
|||||||
hass, trigger_config, action, trigger_info
|
hass, trigger_config, action, trigger_info
|
||||||
)
|
)
|
||||||
|
|
||||||
raise HomeAssistantError(f"Unhandled trigger type {trigger_type}")
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="unhandled_trigger_type",
|
||||||
|
translation_placeholders={"trigger_type": trigger_type},
|
||||||
|
)
|
||||||
|
@ -106,5 +106,7 @@ class SamsungTVEntity(CoordinatorEntity[SamsungTVDataUpdateCoordinator], Entity)
|
|||||||
self.entity_id,
|
self.entity_id,
|
||||||
)
|
)
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"Entity {self.entity_id} does not support this service."
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_unsupported",
|
||||||
|
translation_placeholders={"entity": self.entity_id},
|
||||||
)
|
)
|
||||||
|
@ -47,5 +47,13 @@
|
|||||||
"trigger_type": {
|
"trigger_type": {
|
||||||
"samsungtv.turn_on": "Device is requested to turn on"
|
"samsungtv.turn_on": "Device is requested to turn on"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"unhandled_trigger_type": {
|
||||||
|
"message": "Unhandled trigger type {trigger_type}."
|
||||||
|
},
|
||||||
|
"service_unsupported": {
|
||||||
|
"message": "Entity {entity} does not support this action."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/sharkiq",
|
"documentation": "https://www.home-assistant.io/integrations/sharkiq",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["sharkiq"],
|
"loggers": ["sharkiq"],
|
||||||
"requirements": ["sharkiq==1.0.2"]
|
"requirements": ["sharkiq==1.1.0"]
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ from aioshelly.exceptions import (
|
|||||||
CustomPortNotSupported,
|
CustomPortNotSupported,
|
||||||
DeviceConnectionError,
|
DeviceConnectionError,
|
||||||
InvalidAuthError,
|
InvalidAuthError,
|
||||||
|
InvalidHostError,
|
||||||
MacAddressMismatchError,
|
MacAddressMismatchError,
|
||||||
)
|
)
|
||||||
from aioshelly.rpc_device import RpcDevice
|
from aioshelly.rpc_device import RpcDevice
|
||||||
@ -157,6 +158,8 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self.info = await self._async_get_info(host, port)
|
self.info = await self._async_get_info(host, port)
|
||||||
except DeviceConnectionError:
|
except DeviceConnectionError:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
|
except InvalidHostError:
|
||||||
|
errors["base"] = "invalid_host"
|
||||||
except Exception: # noqa: BLE001
|
except Exception: # noqa: BLE001
|
||||||
LOGGER.exception("Unexpected exception")
|
LOGGER.exception("Unexpected exception")
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
|
@ -277,3 +277,7 @@ ROLE_TO_DEVICE_CLASS_MAP = {
|
|||||||
"current_humidity": SensorDeviceClass.HUMIDITY,
|
"current_humidity": SensorDeviceClass.HUMIDITY,
|
||||||
"current_temperature": SensorDeviceClass.TEMPERATURE,
|
"current_temperature": SensorDeviceClass.TEMPERATURE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# We want to check only the first 5 KB of the script if it contains emitEvent()
|
||||||
|
# so that the integration startup remains fast.
|
||||||
|
MAX_SCRIPT_SIZE = 5120
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aioshelly"],
|
"loggers": ["aioshelly"],
|
||||||
"requirements": ["aioshelly==13.4.0"],
|
"requirements": ["aioshelly==13.4.1"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_http._tcp.local.",
|
"type": "_http._tcp.local.",
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"invalid_host": "[%key:common::config_flow::error::invalid_host%]",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||||
"firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support",
|
"firmware_not_fully_provisioned": "Device not fully provisioned. Please contact Shelly support",
|
||||||
"custom_port_not_supported": "Gen1 device does not support custom port.",
|
"custom_port_not_supported": "Gen1 device does not support custom port.",
|
||||||
|
@ -58,6 +58,7 @@ from .const import (
|
|||||||
GEN2_BETA_RELEASE_URL,
|
GEN2_BETA_RELEASE_URL,
|
||||||
GEN2_RELEASE_URL,
|
GEN2_RELEASE_URL,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
|
MAX_SCRIPT_SIZE,
|
||||||
RPC_INPUTS_EVENTS_TYPES,
|
RPC_INPUTS_EVENTS_TYPES,
|
||||||
SHAIR_MAX_WORK_HOURS,
|
SHAIR_MAX_WORK_HOURS,
|
||||||
SHBTN_INPUTS_EVENTS_TYPES,
|
SHBTN_INPUTS_EVENTS_TYPES,
|
||||||
@ -642,7 +643,7 @@ def get_rpc_ws_url(hass: HomeAssistant) -> str | None:
|
|||||||
|
|
||||||
async def get_rpc_script_event_types(device: RpcDevice, id: int) -> list[str]:
|
async def get_rpc_script_event_types(device: RpcDevice, id: int) -> list[str]:
|
||||||
"""Return a list of event types for a specific script."""
|
"""Return a list of event types for a specific script."""
|
||||||
code_response = await device.script_getcode(id)
|
code_response = await device.script_getcode(id, bytes_to_read=MAX_SCRIPT_SIZE)
|
||||||
matches = SHELLY_EMIT_EVENT_PATTERN.finditer(code_response["data"])
|
matches = SHELLY_EMIT_EVENT_PATTERN.finditer(code_response["data"])
|
||||||
return sorted([*{str(event_type.group(1)) for event_type in matches}])
|
return sorted([*{str(event_type.group(1)) for event_type in matches}])
|
||||||
|
|
||||||
|
@ -30,5 +30,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pysmartthings"],
|
"loggers": ["pysmartthings"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["pysmartthings==3.0.2"]
|
"requirements": ["pysmartthings==3.0.4"]
|
||||||
}
|
}
|
||||||
|
@ -413,7 +413,6 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
# Haven't seen at devices yet
|
|
||||||
Capability.GAS_METER: {
|
Capability.GAS_METER: {
|
||||||
Attribute.GAS_METER: [
|
Attribute.GAS_METER: [
|
||||||
SmartThingsSensorEntityDescription(
|
SmartThingsSensorEntityDescription(
|
||||||
@ -421,7 +420,7 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
translation_key="gas_meter",
|
translation_key="gas_meter",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.TOTAL,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
Attribute.GAS_METER_CALORIFIC: [
|
Attribute.GAS_METER_CALORIFIC: [
|
||||||
@ -443,7 +442,7 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
key=Attribute.GAS_METER_VOLUME,
|
key=Attribute.GAS_METER_VOLUME,
|
||||||
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
|
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
|
||||||
device_class=SensorDeviceClass.GAS,
|
device_class=SensorDeviceClass.GAS,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.TOTAL,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -1003,6 +1002,7 @@ CAPABILITY_TO_SENSORS: dict[
|
|||||||
UNITS = {
|
UNITS = {
|
||||||
"C": UnitOfTemperature.CELSIUS,
|
"C": UnitOfTemperature.CELSIUS,
|
||||||
"F": UnitOfTemperature.FAHRENHEIT,
|
"F": UnitOfTemperature.FAHRENHEIT,
|
||||||
|
"ccf": UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||||
"lux": LIGHT_LUX,
|
"lux": LIGHT_LUX,
|
||||||
"mG": None,
|
"mG": None,
|
||||||
"μg/m^3": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
"μg/m^3": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
@ -127,6 +127,7 @@ class ViCareFan(ViCareEntity, FanEntity):
|
|||||||
|
|
||||||
_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
|
_attr_speed_count = len(ORDERED_NAMED_FAN_SPEEDS)
|
||||||
_attr_translation_key = "ventilation"
|
_attr_translation_key = "ventilation"
|
||||||
|
_attributes: dict[str, Any] = {}
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -155,7 +156,7 @@ class ViCareFan(ViCareEntity, FanEntity):
|
|||||||
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
self._attr_supported_features |= FanEntityFeature.SET_SPEED
|
||||||
|
|
||||||
# evaluate quickmodes
|
# evaluate quickmodes
|
||||||
quickmodes: list[str] = (
|
self._attributes["vicare_quickmodes"] = quickmodes = list[str](
|
||||||
device.getVentilationQuickmodes()
|
device.getVentilationQuickmodes()
|
||||||
if is_supported(
|
if is_supported(
|
||||||
"getVentilationQuickmodes",
|
"getVentilationQuickmodes",
|
||||||
@ -196,26 +197,23 @@ class ViCareFan(ViCareEntity, FanEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool | None:
|
def is_on(self) -> bool | None:
|
||||||
"""Return true if the entity is on."""
|
"""Return true if the entity is on."""
|
||||||
if (
|
if VentilationQuickmode.STANDBY in self._attributes[
|
||||||
self._attr_supported_features & FanEntityFeature.TURN_OFF
|
"vicare_quickmodes"
|
||||||
and self._api.getVentilationQuickmode(VentilationQuickmode.STANDBY)
|
] and self._api.getVentilationQuickmode(VentilationQuickmode.STANDBY):
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.percentage is not None and self.percentage > 0
|
return self.percentage is not None and self.percentage > 0
|
||||||
|
|
||||||
def turn_off(self, **kwargs: Any) -> None:
|
def turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the entity off."""
|
"""Turn the entity off."""
|
||||||
|
|
||||||
self._api.activateVentilationQuickmode(str(VentilationQuickmode.STANDBY))
|
self._api.activateVentilationQuickmode(str(VentilationQuickmode.STANDBY))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self) -> str | None:
|
def icon(self) -> str | None:
|
||||||
"""Return the icon to use in the frontend."""
|
"""Return the icon to use in the frontend."""
|
||||||
if (
|
if VentilationQuickmode.STANDBY in self._attributes[
|
||||||
self._attr_supported_features & FanEntityFeature.TURN_OFF
|
"vicare_quickmodes"
|
||||||
and self._api.getVentilationQuickmode(VentilationQuickmode.STANDBY)
|
] and self._api.getVentilationQuickmode(VentilationQuickmode.STANDBY):
|
||||||
):
|
|
||||||
return "mdi:fan-off"
|
return "mdi:fan-off"
|
||||||
if hasattr(self, "_attr_preset_mode"):
|
if hasattr(self, "_attr_preset_mode"):
|
||||||
if self._attr_preset_mode == VentilationMode.VENTILATION:
|
if self._attr_preset_mode == VentilationMode.VENTILATION:
|
||||||
@ -242,7 +240,9 @@ class ViCareFan(ViCareEntity, FanEntity):
|
|||||||
"""Set the speed of the fan, as a percentage."""
|
"""Set the speed of the fan, as a percentage."""
|
||||||
if self._attr_preset_mode != str(VentilationMode.PERMANENT):
|
if self._attr_preset_mode != str(VentilationMode.PERMANENT):
|
||||||
self.set_preset_mode(VentilationMode.PERMANENT)
|
self.set_preset_mode(VentilationMode.PERMANENT)
|
||||||
elif self._api.getVentilationQuickmode(VentilationQuickmode.STANDBY):
|
elif VentilationQuickmode.STANDBY in self._attributes[
|
||||||
|
"vicare_quickmodes"
|
||||||
|
] and self._api.getVentilationQuickmode(VentilationQuickmode.STANDBY):
|
||||||
self._api.deactivateVentilationQuickmode(str(VentilationQuickmode.STANDBY))
|
self._api.deactivateVentilationQuickmode(str(VentilationQuickmode.STANDBY))
|
||||||
|
|
||||||
level = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage)
|
level = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, percentage)
|
||||||
@ -254,3 +254,8 @@ class ViCareFan(ViCareEntity, FanEntity):
|
|||||||
target_mode = VentilationMode.to_vicare_mode(preset_mode)
|
target_mode = VentilationMode.to_vicare_mode(preset_mode)
|
||||||
_LOGGER.debug("changing ventilation mode to %s", target_mode)
|
_LOGGER.debug("changing ventilation mode to %s", target_mode)
|
||||||
self._api.activateVentilationMode(target_mode)
|
self._api.activateVentilationMode(target_mode)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
"""Show Device Attributes."""
|
||||||
|
return self._attributes
|
||||||
|
@ -11,5 +11,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/vicare",
|
"documentation": "https://www.home-assistant.io/integrations/vicare",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["PyViCare"],
|
"loggers": ["PyViCare"],
|
||||||
"requirements": ["PyViCare==2.43.1"]
|
"requirements": ["PyViCare==2.44.0"]
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,7 @@ class WebDavBackupAgent(BackupAgent):
|
|||||||
return {
|
return {
|
||||||
metadata_content.backup_id: metadata_content
|
metadata_content.backup_id: metadata_content
|
||||||
for file_name in files
|
for file_name in files
|
||||||
if file_name.endswith(".json")
|
if file_name.endswith(".metadata.json")
|
||||||
if (metadata_content := await _download_metadata(file_name))
|
if (metadata_content := await _download_metadata(file_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -514,6 +514,7 @@ class ZHAGatewayProxy(EventBase):
|
|||||||
self._log_queue_handler.listener = logging.handlers.QueueListener(
|
self._log_queue_handler.listener = logging.handlers.QueueListener(
|
||||||
log_simple_queue, log_relay_handler
|
log_simple_queue, log_relay_handler
|
||||||
)
|
)
|
||||||
|
self._log_queue_handler_count: int = 0
|
||||||
|
|
||||||
self._unsubs: list[Callable[[], None]] = []
|
self._unsubs: list[Callable[[], None]] = []
|
||||||
self._unsubs.append(self.gateway.on_all_events(self._handle_event_protocol))
|
self._unsubs.append(self.gateway.on_all_events(self._handle_event_protocol))
|
||||||
@ -747,7 +748,10 @@ class ZHAGatewayProxy(EventBase):
|
|||||||
if filterer:
|
if filterer:
|
||||||
self._log_queue_handler.addFilter(filterer)
|
self._log_queue_handler.addFilter(filterer)
|
||||||
|
|
||||||
if self._log_queue_handler.listener:
|
# Only start a new log queue handler if the old one is no longer running
|
||||||
|
self._log_queue_handler_count += 1
|
||||||
|
|
||||||
|
if self._log_queue_handler.listener and self._log_queue_handler_count == 1:
|
||||||
self._log_queue_handler.listener.start()
|
self._log_queue_handler.listener.start()
|
||||||
|
|
||||||
for logger_name in DEBUG_RELAY_LOGGERS:
|
for logger_name in DEBUG_RELAY_LOGGERS:
|
||||||
@ -763,7 +767,10 @@ class ZHAGatewayProxy(EventBase):
|
|||||||
for logger_name in DEBUG_RELAY_LOGGERS:
|
for logger_name in DEBUG_RELAY_LOGGERS:
|
||||||
logging.getLogger(logger_name).removeHandler(self._log_queue_handler)
|
logging.getLogger(logger_name).removeHandler(self._log_queue_handler)
|
||||||
|
|
||||||
if self._log_queue_handler.listener:
|
# Only stop the log queue handler if nothing else is using it
|
||||||
|
self._log_queue_handler_count -= 1
|
||||||
|
|
||||||
|
if self._log_queue_handler.listener and self._log_queue_handler_count == 0:
|
||||||
self._log_queue_handler.listener.stop()
|
self._log_queue_handler.listener.stop()
|
||||||
|
|
||||||
if filterer:
|
if filterer:
|
||||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2025
|
MAJOR_VERSION: Final = 2025
|
||||||
MINOR_VERSION: Final = 4
|
MINOR_VERSION: Final = 4
|
||||||
PATCH_VERSION: Final = "1"
|
PATCH_VERSION: Final = "2"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 0)
|
||||||
|
@ -38,7 +38,7 @@ habluetooth==3.37.0
|
|||||||
hass-nabucasa==0.94.0
|
hass-nabucasa==0.94.0
|
||||||
hassil==2.2.3
|
hassil==2.2.3
|
||||||
home-assistant-bluetooth==1.13.1
|
home-assistant-bluetooth==1.13.1
|
||||||
home-assistant-frontend==20250404.0
|
home-assistant-frontend==20250411.0
|
||||||
home-assistant-intents==2025.3.28
|
home-assistant-intents==2025.3.28
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
@ -212,3 +212,8 @@ async-timeout==4.0.3
|
|||||||
# https://github.com/home-assistant/core/issues/122508
|
# https://github.com/home-assistant/core/issues/122508
|
||||||
# https://github.com/home-assistant/core/issues/118004
|
# https://github.com/home-assistant/core/issues/118004
|
||||||
aiofiles>=24.1.0
|
aiofiles>=24.1.0
|
||||||
|
|
||||||
|
# multidict < 6.4.0 has memory leaks
|
||||||
|
# https://github.com/aio-libs/multidict/issues/1134
|
||||||
|
# https://github.com/aio-libs/multidict/issues/1131
|
||||||
|
multidict>=6.4.2
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2025.4.1"
|
version = "2025.4.2"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
|
30
requirements_all.txt
generated
30
requirements_all.txt
generated
@ -100,7 +100,7 @@ PyTransportNSW==0.1.1
|
|||||||
PyTurboJPEG==1.7.5
|
PyTurboJPEG==1.7.5
|
||||||
|
|
||||||
# homeassistant.components.vicare
|
# homeassistant.components.vicare
|
||||||
PyViCare==2.43.1
|
PyViCare==2.44.0
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_aqara
|
# homeassistant.components.xiaomi_aqara
|
||||||
PyXiaomiGateway==0.14.3
|
PyXiaomiGateway==0.14.3
|
||||||
@ -182,7 +182,7 @@ aioairq==0.4.4
|
|||||||
aioairzone-cloud==0.6.11
|
aioairzone-cloud==0.6.11
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.9.9
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@ -243,7 +243,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==29.8.0
|
aioesphomeapi==29.9.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -371,7 +371,7 @@ aioruuvigateway==0.1.0
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==13.4.0
|
aioshelly==13.4.1
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
@ -944,7 +944,7 @@ flexit_bacnet==2.2.3
|
|||||||
flipr-api==1.6.1
|
flipr-api==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux-led==1.1.3
|
flux-led==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
@ -1084,7 +1084,7 @@ greenwavereality==0.5.1
|
|||||||
gridnet==5.0.1
|
gridnet==5.0.1
|
||||||
|
|
||||||
# homeassistant.components.growatt_server
|
# homeassistant.components.growatt_server
|
||||||
growattServer==1.5.0
|
growattServer==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.google_sheets
|
# homeassistant.components.google_sheets
|
||||||
gspread==5.5.0
|
gspread==5.5.0
|
||||||
@ -1157,7 +1157,7 @@ hole==0.8.0
|
|||||||
holidays==0.69
|
holidays==0.69
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250404.0
|
home-assistant-frontend==20250411.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.3.28
|
home-assistant-intents==2025.3.28
|
||||||
@ -1196,7 +1196,7 @@ ibmiotf==0.3.4
|
|||||||
# homeassistant.components.local_calendar
|
# homeassistant.components.local_calendar
|
||||||
# homeassistant.components.local_todo
|
# homeassistant.components.local_todo
|
||||||
# homeassistant.components.remote_calendar
|
# homeassistant.components.remote_calendar
|
||||||
ical==9.0.3
|
ical==9.1.0
|
||||||
|
|
||||||
# homeassistant.components.caldav
|
# homeassistant.components.caldav
|
||||||
icalendar==6.1.0
|
icalendar==6.1.0
|
||||||
@ -1314,7 +1314,7 @@ ld2410-ble==0.1.1
|
|||||||
leaone-ble==0.1.0
|
leaone-ble==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.led_ble
|
# homeassistant.components.led_ble
|
||||||
led-ble==1.1.6
|
led-ble==1.1.7
|
||||||
|
|
||||||
# homeassistant.components.lektrico
|
# homeassistant.components.lektrico
|
||||||
lektricowifi==0.0.43
|
lektricowifi==0.0.43
|
||||||
@ -1350,7 +1350,7 @@ linear-garage-door==0.2.9
|
|||||||
linode-api==4.1.9b1
|
linode-api==4.1.9b1
|
||||||
|
|
||||||
# homeassistant.components.livisi
|
# homeassistant.components.livisi
|
||||||
livisi==0.0.24
|
livisi==0.0.25
|
||||||
|
|
||||||
# homeassistant.components.google_maps
|
# homeassistant.components.google_maps
|
||||||
locationsharinglib==5.0.1
|
locationsharinglib==5.0.1
|
||||||
@ -1607,7 +1607,7 @@ openwrt-luci-rpc==1.1.17
|
|||||||
openwrt-ubus-rpc==0.0.2
|
openwrt-ubus-rpc==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.9.0
|
opower==0.11.1
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -2005,7 +2005,7 @@ pygti==0.9.4
|
|||||||
pyhaversion==22.8.0
|
pyhaversion==22.8.0
|
||||||
|
|
||||||
# homeassistant.components.heos
|
# homeassistant.components.heos
|
||||||
pyheos==1.0.4
|
pyheos==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.hive
|
# homeassistant.components.hive
|
||||||
pyhive-integration==1.0.2
|
pyhive-integration==1.0.2
|
||||||
@ -2319,7 +2319,7 @@ pysma==0.7.5
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.0.2
|
pysmartthings==3.0.4
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@ -2627,7 +2627,7 @@ renault-api==0.2.9
|
|||||||
renson-endura-delta==1.7.2
|
renson-endura-delta==1.7.2
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.13.0
|
reolink-aio==0.13.1
|
||||||
|
|
||||||
# homeassistant.components.idteck_prox
|
# homeassistant.components.idteck_prox
|
||||||
rfk101py==0.0.1
|
rfk101py==0.0.1
|
||||||
@ -2730,7 +2730,7 @@ sentry-sdk==1.45.1
|
|||||||
sfrbox-api==0.0.11
|
sfrbox-api==0.0.11
|
||||||
|
|
||||||
# homeassistant.components.sharkiq
|
# homeassistant.components.sharkiq
|
||||||
sharkiq==1.0.2
|
sharkiq==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.aquostv
|
# homeassistant.components.aquostv
|
||||||
sharp_aquos_rc==0.3.2
|
sharp_aquos_rc==0.3.2
|
||||||
|
30
requirements_test_all.txt
generated
30
requirements_test_all.txt
generated
@ -94,7 +94,7 @@ PyTransportNSW==0.1.1
|
|||||||
PyTurboJPEG==1.7.5
|
PyTurboJPEG==1.7.5
|
||||||
|
|
||||||
# homeassistant.components.vicare
|
# homeassistant.components.vicare
|
||||||
PyViCare==2.43.1
|
PyViCare==2.44.0
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_aqara
|
# homeassistant.components.xiaomi_aqara
|
||||||
PyXiaomiGateway==0.14.3
|
PyXiaomiGateway==0.14.3
|
||||||
@ -170,7 +170,7 @@ aioairq==0.4.4
|
|||||||
aioairzone-cloud==0.6.11
|
aioairzone-cloud==0.6.11
|
||||||
|
|
||||||
# homeassistant.components.airzone
|
# homeassistant.components.airzone
|
||||||
aioairzone==0.9.9
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@ -231,7 +231,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==29.8.0
|
aioesphomeapi==29.9.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -353,7 +353,7 @@ aioruuvigateway==0.1.0
|
|||||||
aiosenz==1.0.0
|
aiosenz==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.shelly
|
# homeassistant.components.shelly
|
||||||
aioshelly==13.4.0
|
aioshelly==13.4.1
|
||||||
|
|
||||||
# homeassistant.components.skybell
|
# homeassistant.components.skybell
|
||||||
aioskybell==22.7.0
|
aioskybell==22.7.0
|
||||||
@ -804,7 +804,7 @@ flexit_bacnet==2.2.3
|
|||||||
flipr-api==1.6.1
|
flipr-api==1.6.1
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux-led==1.1.3
|
flux-led==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
# homeassistant.components.recorder
|
# homeassistant.components.recorder
|
||||||
@ -929,7 +929,7 @@ greeneye_monitor==3.0.3
|
|||||||
gridnet==5.0.1
|
gridnet==5.0.1
|
||||||
|
|
||||||
# homeassistant.components.growatt_server
|
# homeassistant.components.growatt_server
|
||||||
growattServer==1.5.0
|
growattServer==1.6.0
|
||||||
|
|
||||||
# homeassistant.components.google_sheets
|
# homeassistant.components.google_sheets
|
||||||
gspread==5.5.0
|
gspread==5.5.0
|
||||||
@ -984,7 +984,7 @@ hole==0.8.0
|
|||||||
holidays==0.69
|
holidays==0.69
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250404.0
|
home-assistant-frontend==20250411.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.3.28
|
home-assistant-intents==2025.3.28
|
||||||
@ -1014,7 +1014,7 @@ ibeacon-ble==1.2.0
|
|||||||
# homeassistant.components.local_calendar
|
# homeassistant.components.local_calendar
|
||||||
# homeassistant.components.local_todo
|
# homeassistant.components.local_todo
|
||||||
# homeassistant.components.remote_calendar
|
# homeassistant.components.remote_calendar
|
||||||
ical==9.0.3
|
ical==9.1.0
|
||||||
|
|
||||||
# homeassistant.components.caldav
|
# homeassistant.components.caldav
|
||||||
icalendar==6.1.0
|
icalendar==6.1.0
|
||||||
@ -1111,7 +1111,7 @@ ld2410-ble==0.1.1
|
|||||||
leaone-ble==0.1.0
|
leaone-ble==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.led_ble
|
# homeassistant.components.led_ble
|
||||||
led-ble==1.1.6
|
led-ble==1.1.7
|
||||||
|
|
||||||
# homeassistant.components.lektrico
|
# homeassistant.components.lektrico
|
||||||
lektricowifi==0.0.43
|
lektricowifi==0.0.43
|
||||||
@ -1132,7 +1132,7 @@ libsoundtouch==0.8
|
|||||||
linear-garage-door==0.2.9
|
linear-garage-door==0.2.9
|
||||||
|
|
||||||
# homeassistant.components.livisi
|
# homeassistant.components.livisi
|
||||||
livisi==0.0.24
|
livisi==0.0.25
|
||||||
|
|
||||||
# homeassistant.components.london_underground
|
# homeassistant.components.london_underground
|
||||||
london-tube-status==0.5
|
london-tube-status==0.5
|
||||||
@ -1341,7 +1341,7 @@ openhomedevice==2.2.0
|
|||||||
openwebifpy==4.3.1
|
openwebifpy==4.3.1
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.9.0
|
opower==0.11.1
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@ -1632,7 +1632,7 @@ pygti==0.9.4
|
|||||||
pyhaversion==22.8.0
|
pyhaversion==22.8.0
|
||||||
|
|
||||||
# homeassistant.components.heos
|
# homeassistant.components.heos
|
||||||
pyheos==1.0.4
|
pyheos==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.hive
|
# homeassistant.components.hive
|
||||||
pyhive-integration==1.0.2
|
pyhive-integration==1.0.2
|
||||||
@ -1889,7 +1889,7 @@ pysma==0.7.5
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.0.2
|
pysmartthings==3.0.4
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@ -2128,7 +2128,7 @@ renault-api==0.2.9
|
|||||||
renson-endura-delta==1.7.2
|
renson-endura-delta==1.7.2
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.13.0
|
reolink-aio==0.13.1
|
||||||
|
|
||||||
# homeassistant.components.rflink
|
# homeassistant.components.rflink
|
||||||
rflink==0.0.66
|
rflink==0.0.66
|
||||||
@ -2207,7 +2207,7 @@ sentry-sdk==1.45.1
|
|||||||
sfrbox-api==0.0.11
|
sfrbox-api==0.0.11
|
||||||
|
|
||||||
# homeassistant.components.sharkiq
|
# homeassistant.components.sharkiq
|
||||||
sharkiq==1.0.2
|
sharkiq==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.simplefin
|
# homeassistant.components.simplefin
|
||||||
simplefin4py==0.0.18
|
simplefin4py==0.0.18
|
||||||
|
@ -241,6 +241,11 @@ async-timeout==4.0.3
|
|||||||
# https://github.com/home-assistant/core/issues/122508
|
# https://github.com/home-assistant/core/issues/122508
|
||||||
# https://github.com/home-assistant/core/issues/118004
|
# https://github.com/home-assistant/core/issues/118004
|
||||||
aiofiles>=24.1.0
|
aiofiles>=24.1.0
|
||||||
|
|
||||||
|
# multidict < 6.4.0 has memory leaks
|
||||||
|
# https://github.com/aio-libs/multidict/issues/1134
|
||||||
|
# https://github.com/aio-libs/multidict/issues/1131
|
||||||
|
multidict>=6.4.2
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GENERATED_MESSAGE = (
|
GENERATED_MESSAGE = (
|
||||||
|
@ -190,6 +190,7 @@ EXCEPTIONS = {
|
|||||||
"enocean", # https://github.com/kipe/enocean/pull/142
|
"enocean", # https://github.com/kipe/enocean/pull/142
|
||||||
"imutils", # https://github.com/PyImageSearch/imutils/pull/292
|
"imutils", # https://github.com/PyImageSearch/imutils/pull/292
|
||||||
"iso4217", # Public domain
|
"iso4217", # Public domain
|
||||||
|
"jaraco.itertools", # MIT - https://github.com/jaraco/jaraco.itertools/issues/21
|
||||||
"kiwiki_client", # https://github.com/c7h/kiwiki_client/pull/6
|
"kiwiki_client", # https://github.com/c7h/kiwiki_client/pull/6
|
||||||
"ld2410-ble", # https://github.com/930913/ld2410-ble/pull/7
|
"ld2410-ble", # https://github.com/930913/ld2410-ble/pull/7
|
||||||
"maxcube-api", # https://github.com/uebelack/python-maxcube-api/pull/48
|
"maxcube-api", # https://github.com/uebelack/python-maxcube-api/pull/48
|
||||||
|
@ -44,9 +44,11 @@
|
|||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'air_demand': 1,
|
'air_demand': 1,
|
||||||
|
'battery': 99,
|
||||||
'coldStage': 1,
|
'coldStage': 1,
|
||||||
'coldStages': 1,
|
'coldStages': 1,
|
||||||
'coldangle': 2,
|
'coldangle': 2,
|
||||||
|
'coverage': 72,
|
||||||
'errors': list([
|
'errors': list([
|
||||||
]),
|
]),
|
||||||
'floor_demand': 1,
|
'floor_demand': 1,
|
||||||
@ -73,9 +75,11 @@
|
|||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'air_demand': 0,
|
'air_demand': 0,
|
||||||
|
'battery': 35,
|
||||||
'coldStage': 1,
|
'coldStage': 1,
|
||||||
'coldStages': 1,
|
'coldStages': 1,
|
||||||
'coldangle': 0,
|
'coldangle': 0,
|
||||||
|
'coverage': 60,
|
||||||
'errors': list([
|
'errors': list([
|
||||||
]),
|
]),
|
||||||
'floor_demand': 0,
|
'floor_demand': 0,
|
||||||
@ -100,9 +104,11 @@
|
|||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'air_demand': 0,
|
'air_demand': 0,
|
||||||
|
'battery': 25,
|
||||||
'coldStage': 1,
|
'coldStage': 1,
|
||||||
'coldStages': 1,
|
'coldStages': 1,
|
||||||
'coldangle': 0,
|
'coldangle': 0,
|
||||||
|
'coverage': 88,
|
||||||
'errors': list([
|
'errors': list([
|
||||||
dict({
|
dict({
|
||||||
'Zone': 'Low battery',
|
'Zone': 'Low battery',
|
||||||
@ -130,9 +136,11 @@
|
|||||||
}),
|
}),
|
||||||
dict({
|
dict({
|
||||||
'air_demand': 0,
|
'air_demand': 0,
|
||||||
|
'battery': 80,
|
||||||
'coldStage': 1,
|
'coldStage': 1,
|
||||||
'coldStages': 1,
|
'coldStages': 1,
|
||||||
'coldangle': 0,
|
'coldangle': 0,
|
||||||
|
'coverage': 66,
|
||||||
'errors': list([
|
'errors': list([
|
||||||
]),
|
]),
|
||||||
'floor_demand': 0,
|
'floor_demand': 0,
|
||||||
@ -497,9 +505,11 @@
|
|||||||
'temp-set': 19.2,
|
'temp-set': 19.2,
|
||||||
'temp-step': 0.5,
|
'temp-step': 0.5,
|
||||||
'temp-unit': 0,
|
'temp-unit': 0,
|
||||||
|
'thermostat-battery': 99,
|
||||||
'thermostat-fw': '3.33',
|
'thermostat-fw': '3.33',
|
||||||
'thermostat-model': 'Think (Radio)',
|
'thermostat-model': 'Think (Radio)',
|
||||||
'thermostat-radio': True,
|
'thermostat-radio': True,
|
||||||
|
'thermostat-signal': 72,
|
||||||
}),
|
}),
|
||||||
'1:3': dict({
|
'1:3': dict({
|
||||||
'absolute-temp-max': 30.0,
|
'absolute-temp-max': 30.0,
|
||||||
@ -546,9 +556,11 @@
|
|||||||
'temp-set': 19.3,
|
'temp-set': 19.3,
|
||||||
'temp-step': 0.5,
|
'temp-step': 0.5,
|
||||||
'temp-unit': 0,
|
'temp-unit': 0,
|
||||||
|
'thermostat-battery': 35,
|
||||||
'thermostat-fw': '3.33',
|
'thermostat-fw': '3.33',
|
||||||
'thermostat-model': 'Think (Radio)',
|
'thermostat-model': 'Think (Radio)',
|
||||||
'thermostat-radio': True,
|
'thermostat-radio': True,
|
||||||
|
'thermostat-signal': 60,
|
||||||
}),
|
}),
|
||||||
'1:4': dict({
|
'1:4': dict({
|
||||||
'absolute-temp-max': 86.0,
|
'absolute-temp-max': 86.0,
|
||||||
@ -597,9 +609,11 @@
|
|||||||
'temp-set': 66.9,
|
'temp-set': 66.9,
|
||||||
'temp-step': 1.0,
|
'temp-step': 1.0,
|
||||||
'temp-unit': 1,
|
'temp-unit': 1,
|
||||||
|
'thermostat-battery': 25,
|
||||||
'thermostat-fw': '3.33',
|
'thermostat-fw': '3.33',
|
||||||
'thermostat-model': 'Think (Radio)',
|
'thermostat-model': 'Think (Radio)',
|
||||||
'thermostat-radio': True,
|
'thermostat-radio': True,
|
||||||
|
'thermostat-signal': 88,
|
||||||
}),
|
}),
|
||||||
'1:5': dict({
|
'1:5': dict({
|
||||||
'absolute-temp-max': 30.0,
|
'absolute-temp-max': 30.0,
|
||||||
@ -645,9 +659,11 @@
|
|||||||
'temp-set': 19.5,
|
'temp-set': 19.5,
|
||||||
'temp-step': 0.5,
|
'temp-step': 0.5,
|
||||||
'temp-unit': 0,
|
'temp-unit': 0,
|
||||||
|
'thermostat-battery': 80,
|
||||||
'thermostat-fw': '3.33',
|
'thermostat-fw': '3.33',
|
||||||
'thermostat-model': 'Think (Radio)',
|
'thermostat-model': 'Think (Radio)',
|
||||||
'thermostat-radio': True,
|
'thermostat-radio': True,
|
||||||
|
'thermostat-signal': 66,
|
||||||
}),
|
}),
|
||||||
'2:1': dict({
|
'2:1': dict({
|
||||||
'absolute-temp-max': 30.0,
|
'absolute-temp-max': 30.0,
|
||||||
|
@ -11,12 +11,14 @@ from aioairzone.const import (
|
|||||||
API_ACS_SET_POINT,
|
API_ACS_SET_POINT,
|
||||||
API_ACS_TEMP,
|
API_ACS_TEMP,
|
||||||
API_AIR_DEMAND,
|
API_AIR_DEMAND,
|
||||||
|
API_BATTERY,
|
||||||
API_COLD_ANGLE,
|
API_COLD_ANGLE,
|
||||||
API_COLD_STAGE,
|
API_COLD_STAGE,
|
||||||
API_COLD_STAGES,
|
API_COLD_STAGES,
|
||||||
API_COOL_MAX_TEMP,
|
API_COOL_MAX_TEMP,
|
||||||
API_COOL_MIN_TEMP,
|
API_COOL_MIN_TEMP,
|
||||||
API_COOL_SET_POINT,
|
API_COOL_SET_POINT,
|
||||||
|
API_COVERAGE,
|
||||||
API_DATA,
|
API_DATA,
|
||||||
API_ERRORS,
|
API_ERRORS,
|
||||||
API_FLOOR_DEMAND,
|
API_FLOOR_DEMAND,
|
||||||
@ -119,6 +121,8 @@ HVAC_MOCK = {
|
|||||||
API_THERMOS_TYPE: 4,
|
API_THERMOS_TYPE: 4,
|
||||||
API_THERMOS_FIRMWARE: "3.33",
|
API_THERMOS_FIRMWARE: "3.33",
|
||||||
API_THERMOS_RADIO: 1,
|
API_THERMOS_RADIO: 1,
|
||||||
|
API_BATTERY: 99,
|
||||||
|
API_COVERAGE: 72,
|
||||||
API_ON: 1,
|
API_ON: 1,
|
||||||
API_MAX_TEMP: 30,
|
API_MAX_TEMP: 30,
|
||||||
API_MIN_TEMP: 15,
|
API_MIN_TEMP: 15,
|
||||||
@ -147,6 +151,8 @@ HVAC_MOCK = {
|
|||||||
API_THERMOS_TYPE: 4,
|
API_THERMOS_TYPE: 4,
|
||||||
API_THERMOS_FIRMWARE: "3.33",
|
API_THERMOS_FIRMWARE: "3.33",
|
||||||
API_THERMOS_RADIO: 1,
|
API_THERMOS_RADIO: 1,
|
||||||
|
API_BATTERY: 35,
|
||||||
|
API_COVERAGE: 60,
|
||||||
API_ON: 1,
|
API_ON: 1,
|
||||||
API_MAX_TEMP: 30,
|
API_MAX_TEMP: 30,
|
||||||
API_MIN_TEMP: 15,
|
API_MIN_TEMP: 15,
|
||||||
@ -173,6 +179,8 @@ HVAC_MOCK = {
|
|||||||
API_THERMOS_TYPE: 4,
|
API_THERMOS_TYPE: 4,
|
||||||
API_THERMOS_FIRMWARE: "3.33",
|
API_THERMOS_FIRMWARE: "3.33",
|
||||||
API_THERMOS_RADIO: 1,
|
API_THERMOS_RADIO: 1,
|
||||||
|
API_BATTERY: 25,
|
||||||
|
API_COVERAGE: 88,
|
||||||
API_ON: 0,
|
API_ON: 0,
|
||||||
API_MAX_TEMP: 86,
|
API_MAX_TEMP: 86,
|
||||||
API_MIN_TEMP: 59,
|
API_MIN_TEMP: 59,
|
||||||
@ -203,6 +211,8 @@ HVAC_MOCK = {
|
|||||||
API_THERMOS_TYPE: 4,
|
API_THERMOS_TYPE: 4,
|
||||||
API_THERMOS_FIRMWARE: "3.33",
|
API_THERMOS_FIRMWARE: "3.33",
|
||||||
API_THERMOS_RADIO: 1,
|
API_THERMOS_RADIO: 1,
|
||||||
|
API_BATTERY: 80,
|
||||||
|
API_COVERAGE: 66,
|
||||||
API_ON: 0,
|
API_ON: 0,
|
||||||
API_MAX_TEMP: 30,
|
API_MAX_TEMP: 30,
|
||||||
API_MIN_TEMP: 15,
|
API_MIN_TEMP: 15,
|
||||||
|
@ -303,11 +303,27 @@ async def test_conversation_agent(
|
|||||||
|
|
||||||
|
|
||||||
@patch("homeassistant.components.anthropic.conversation.llm.AssistAPI._async_get_tools")
|
@patch("homeassistant.components.anthropic.conversation.llm.AssistAPI._async_get_tools")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("tool_call_json_parts", "expected_call_tool_args"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
['{"param1": "test_value"}'],
|
||||||
|
{"param1": "test_value"},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
['{"para', 'm1": "test_valu', 'e"}'],
|
||||||
|
{"param1": "test_value"},
|
||||||
|
),
|
||||||
|
([""], {}),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_function_call(
|
async def test_function_call(
|
||||||
mock_get_tools,
|
mock_get_tools,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry_with_assist: MockConfigEntry,
|
mock_config_entry_with_assist: MockConfigEntry,
|
||||||
mock_init_component,
|
mock_init_component,
|
||||||
|
tool_call_json_parts: list[str],
|
||||||
|
expected_call_tool_args: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test function call from the assistant."""
|
"""Test function call from the assistant."""
|
||||||
agent_id = "conversation.claude"
|
agent_id = "conversation.claude"
|
||||||
@ -343,7 +359,7 @@ async def test_function_call(
|
|||||||
1,
|
1,
|
||||||
"toolu_0123456789AbCdEfGhIjKlM",
|
"toolu_0123456789AbCdEfGhIjKlM",
|
||||||
"test_tool",
|
"test_tool",
|
||||||
['{"para', 'm1": "test_valu', 'e"}'],
|
tool_call_json_parts,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -387,7 +403,7 @@ async def test_function_call(
|
|||||||
llm.ToolInput(
|
llm.ToolInput(
|
||||||
id="toolu_0123456789AbCdEfGhIjKlM",
|
id="toolu_0123456789AbCdEfGhIjKlM",
|
||||||
tool_name="test_tool",
|
tool_name="test_tool",
|
||||||
tool_args={"param1": "test_value"},
|
tool_args=expected_call_tool_args,
|
||||||
),
|
),
|
||||||
llm.LLMContext(
|
llm.LLMContext(
|
||||||
platform="anthropic",
|
platform="anthropic",
|
||||||
|
@ -12,6 +12,7 @@ import voluptuous as vol
|
|||||||
from homeassistant.components import conversation
|
from homeassistant.components import conversation
|
||||||
from homeassistant.components.conversation import UserContent, async_get_chat_log, trace
|
from homeassistant.components.conversation import UserContent, async_get_chat_log, trace
|
||||||
from homeassistant.components.google_generative_ai_conversation.conversation import (
|
from homeassistant.components.google_generative_ai_conversation.conversation import (
|
||||||
|
ERROR_GETTING_RESPONSE,
|
||||||
_escape_decode,
|
_escape_decode,
|
||||||
_format_schema,
|
_format_schema,
|
||||||
)
|
)
|
||||||
@ -492,7 +493,33 @@ async def test_empty_response(
|
|||||||
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
||||||
assert result.response.error_code == "unknown", result
|
assert result.response.error_code == "unknown", result
|
||||||
assert result.response.as_dict()["speech"]["plain"]["speech"] == (
|
assert result.response.as_dict()["speech"]["plain"]["speech"] == (
|
||||||
"Sorry, I had a problem getting a response from Google Generative AI."
|
ERROR_GETTING_RESPONSE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("mock_init_component")
|
||||||
|
async def test_none_response(
|
||||||
|
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||||
|
) -> None:
|
||||||
|
"""Test empty response."""
|
||||||
|
with patch("google.genai.chats.AsyncChats.create") as mock_create:
|
||||||
|
mock_chat = AsyncMock()
|
||||||
|
mock_create.return_value.send_message = mock_chat
|
||||||
|
chat_response = Mock(prompt_feedback=None)
|
||||||
|
mock_chat.return_value = chat_response
|
||||||
|
chat_response.candidates = None
|
||||||
|
result = await conversation.async_converse(
|
||||||
|
hass,
|
||||||
|
"hello",
|
||||||
|
None,
|
||||||
|
Context(),
|
||||||
|
agent_id="conversation.google_generative_ai_conversation",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.response.response_type == intent.IntentResponseType.ERROR, result
|
||||||
|
assert result.response.error_code == "unknown", result
|
||||||
|
assert result.response.as_dict()["speech"]["plain"]["speech"] == (
|
||||||
|
ERROR_GETTING_RESPONSE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
3436
tests/components/homekit_controller/fixtures/ecobee3_lite.json
Normal file
3436
tests/components/homekit_controller/fixtures/ecobee3_lite.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,8 @@ from aiohomekit.model.characteristics import (
|
|||||||
CharacteristicsTypes,
|
CharacteristicsTypes,
|
||||||
CurrentFanStateValues,
|
CurrentFanStateValues,
|
||||||
CurrentHeaterCoolerStateValues,
|
CurrentHeaterCoolerStateValues,
|
||||||
|
HeatingCoolingCurrentValues,
|
||||||
|
HeatingCoolingTargetValues,
|
||||||
SwingModeValues,
|
SwingModeValues,
|
||||||
TargetHeaterCoolerStateValues,
|
TargetHeaterCoolerStateValues,
|
||||||
)
|
)
|
||||||
@ -20,6 +22,7 @@ from homeassistant.components.climate import (
|
|||||||
SERVICE_SET_HVAC_MODE,
|
SERVICE_SET_HVAC_MODE,
|
||||||
SERVICE_SET_SWING_MODE,
|
SERVICE_SET_SWING_MODE,
|
||||||
SERVICE_SET_TEMPERATURE,
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
HVACAction,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -662,7 +665,7 @@ async def test_hvac_mode_vs_hvac_action(
|
|||||||
|
|
||||||
state = await helper.poll_and_get_state()
|
state = await helper.poll_and_get_state()
|
||||||
assert state.state == "heat"
|
assert state.state == "heat"
|
||||||
assert state.attributes["hvac_action"] == "fan"
|
assert state.attributes["hvac_action"] == HVACAction.FAN
|
||||||
|
|
||||||
# Simulate that current temperature is below target temp
|
# Simulate that current temperature is below target temp
|
||||||
# Heating might be on and hvac_action currently 'heat'
|
# Heating might be on and hvac_action currently 'heat'
|
||||||
@ -676,7 +679,23 @@ async def test_hvac_mode_vs_hvac_action(
|
|||||||
|
|
||||||
state = await helper.poll_and_get_state()
|
state = await helper.poll_and_get_state()
|
||||||
assert state.state == "heat"
|
assert state.state == "heat"
|
||||||
assert state.attributes["hvac_action"] == "heating"
|
assert state.attributes["hvac_action"] == HVACAction.HEATING
|
||||||
|
|
||||||
|
# If the fan is active, and the heating is off, the hvac_action should be 'fan'
|
||||||
|
# and not 'idle' or 'heating'
|
||||||
|
await helper.async_update(
|
||||||
|
ServicesTypes.THERMOSTAT,
|
||||||
|
{
|
||||||
|
CharacteristicsTypes.FAN_STATE_CURRENT: CurrentFanStateValues.ACTIVE,
|
||||||
|
CharacteristicsTypes.HEATING_COOLING_CURRENT: HeatingCoolingCurrentValues.IDLE,
|
||||||
|
CharacteristicsTypes.HEATING_COOLING_TARGET: HeatingCoolingTargetValues.OFF,
|
||||||
|
CharacteristicsTypes.FAN_STATE_CURRENT: CurrentFanStateValues.ACTIVE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
state = await helper.poll_and_get_state()
|
||||||
|
assert state.state == HVACMode.OFF
|
||||||
|
assert state.attributes["hvac_action"] == HVACAction.FAN
|
||||||
|
|
||||||
|
|
||||||
async def test_hvac_mode_vs_hvac_action_current_mode_wrong(
|
async def test_hvac_mode_vs_hvac_action_current_mode_wrong(
|
||||||
|
@ -835,32 +835,57 @@ async def test_entity_debug_info_message(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"hass_config",
|
("hass_config", "min_number", "max_number", "step"),
|
||||||
[
|
[
|
||||||
{
|
(
|
||||||
mqtt.DOMAIN: {
|
{
|
||||||
number.DOMAIN: {
|
mqtt.DOMAIN: {
|
||||||
"state_topic": "test/state_number",
|
number.DOMAIN: {
|
||||||
"command_topic": "test/cmd_number",
|
"state_topic": "test/state_number",
|
||||||
"name": "Test Number",
|
"command_topic": "test/cmd_number",
|
||||||
"min": 5,
|
"name": "Test Number",
|
||||||
"max": 110,
|
"min": 5,
|
||||||
"step": 20,
|
"max": 110,
|
||||||
|
"step": 20,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
5,
|
||||||
|
110,
|
||||||
|
20,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
mqtt.DOMAIN: {
|
||||||
|
number.DOMAIN: {
|
||||||
|
"state_topic": "test/state_number",
|
||||||
|
"command_topic": "test/cmd_number",
|
||||||
|
"name": "Test Number",
|
||||||
|
"min": 100,
|
||||||
|
"max": 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
100,
|
||||||
|
100,
|
||||||
|
1,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_min_max_step_attributes(
|
async def test_min_max_step_attributes(
|
||||||
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
min_number: float,
|
||||||
|
max_number: float,
|
||||||
|
step: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test min/max/step attributes."""
|
"""Test min/max/step attributes."""
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
|
|
||||||
state = hass.states.get("number.test_number")
|
state = hass.states.get("number.test_number")
|
||||||
assert state.attributes.get(ATTR_MIN) == 5
|
assert state.attributes.get(ATTR_MIN) == min_number
|
||||||
assert state.attributes.get(ATTR_MAX) == 110
|
assert state.attributes.get(ATTR_MAX) == max_number
|
||||||
assert state.attributes.get(ATTR_STEP) == 20
|
assert state.attributes.get(ATTR_STEP) == step
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@ -885,7 +910,7 @@ async def test_invalid_min_max_attributes(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test invalid min/max attributes."""
|
"""Test invalid min/max attributes."""
|
||||||
assert await mqtt_mock_entry()
|
assert await mqtt_mock_entry()
|
||||||
assert f"'{CONF_MAX}' must be > '{CONF_MIN}'" in caplog.text
|
assert f"{CONF_MAX} must be >= {CONF_MIN}" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -424,6 +424,15 @@ async def test_removing_chime(
|
|||||||
True,
|
True,
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
f"{TEST_UID}_unexpected",
|
||||||
|
f"{TEST_UID}_unexpected",
|
||||||
|
f"{TEST_UID}_{TEST_UID_CAM}",
|
||||||
|
f"{TEST_UID}_{TEST_UID_CAM}",
|
||||||
|
Platform.SWITCH,
|
||||||
|
True,
|
||||||
|
True,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_migrate_entity_ids(
|
async def test_migrate_entity_ids(
|
||||||
@ -469,7 +478,8 @@ async def test_migrate_entity_ids(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id)
|
assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id)
|
||||||
assert entity_registry.async_get_entity_id(domain, DOMAIN, new_id) is None
|
if original_id != new_id:
|
||||||
|
assert entity_registry.async_get_entity_id(domain, DOMAIN, new_id) is None
|
||||||
|
|
||||||
assert device_registry.async_get_device(identifiers={(DOMAIN, original_dev_id)})
|
assert device_registry.async_get_device(identifiers={(DOMAIN, original_dev_id)})
|
||||||
if new_dev_id != original_dev_id:
|
if new_dev_id != original_dev_id:
|
||||||
@ -482,7 +492,8 @@ async def test_migrate_entity_ids(
|
|||||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id) is None
|
if original_id != new_id:
|
||||||
|
assert entity_registry.async_get_entity_id(domain, DOMAIN, original_id) is None
|
||||||
assert entity_registry.async_get_entity_id(domain, DOMAIN, new_id)
|
assert entity_registry.async_get_entity_id(domain, DOMAIN, new_id)
|
||||||
|
|
||||||
if new_dev_id != original_dev_id:
|
if new_dev_id != original_dev_id:
|
||||||
|
@ -122,9 +122,14 @@ async def test_turn_on_wol(hass: HomeAssistant) -> None:
|
|||||||
async def test_turn_on_without_turnon(hass: HomeAssistant, remote: Mock) -> None:
|
async def test_turn_on_without_turnon(hass: HomeAssistant, remote: Mock) -> None:
|
||||||
"""Test turn on."""
|
"""Test turn on."""
|
||||||
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
await setup_samsungtv_entry(hass, MOCK_CONFIG)
|
||||||
with pytest.raises(HomeAssistantError, match="does not support this service"):
|
with pytest.raises(HomeAssistantError) as exc_info:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
REMOTE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
REMOTE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_ID}, True
|
||||||
)
|
)
|
||||||
# nothing called as not supported feature
|
# nothing called as not supported feature
|
||||||
assert remote.control.call_count == 0
|
assert remote.control.call_count == 0
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "service_unsupported"
|
||||||
|
assert exc_info.value.translation_placeholders == {
|
||||||
|
"entity": ENTITY_ID,
|
||||||
|
}
|
||||||
|
@ -492,7 +492,9 @@ def _mock_rpc_device(version: str | None = None):
|
|||||||
initialized=True,
|
initialized=True,
|
||||||
connected=True,
|
connected=True,
|
||||||
script_getcode=AsyncMock(
|
script_getcode=AsyncMock(
|
||||||
side_effect=lambda script_id: {"data": MOCK_SCRIPTS[script_id - 1]}
|
side_effect=lambda script_id, bytes_to_read: {
|
||||||
|
"data": MOCK_SCRIPTS[script_id - 1]
|
||||||
|
}
|
||||||
),
|
),
|
||||||
xmod_info={},
|
xmod_info={},
|
||||||
)
|
)
|
||||||
@ -514,7 +516,9 @@ def _mock_blu_rtv_device(version: str | None = None):
|
|||||||
initialized=True,
|
initialized=True,
|
||||||
connected=True,
|
connected=True,
|
||||||
script_getcode=AsyncMock(
|
script_getcode=AsyncMock(
|
||||||
side_effect=lambda script_id: {"data": MOCK_SCRIPTS[script_id - 1]}
|
side_effect=lambda script_id, bytes_to_read: {
|
||||||
|
"data": MOCK_SCRIPTS[script_id - 1]
|
||||||
|
}
|
||||||
),
|
),
|
||||||
xmod_info={},
|
xmod_info={},
|
||||||
)
|
)
|
||||||
|
@ -11,6 +11,7 @@ from aioshelly.exceptions import (
|
|||||||
CustomPortNotSupported,
|
CustomPortNotSupported,
|
||||||
DeviceConnectionError,
|
DeviceConnectionError,
|
||||||
InvalidAuthError,
|
InvalidAuthError,
|
||||||
|
InvalidHostError,
|
||||||
)
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -308,6 +309,7 @@ async def test_form_auth(
|
|||||||
("exc", "base_error"),
|
("exc", "base_error"),
|
||||||
[
|
[
|
||||||
(DeviceConnectionError, "cannot_connect"),
|
(DeviceConnectionError, "cannot_connect"),
|
||||||
|
(InvalidHostError, "invalid_host"),
|
||||||
(ValueError, "unknown"),
|
(ValueError, "unknown"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -146,6 +146,7 @@ def mock_smartthings() -> Generator[AsyncMock]:
|
|||||||
"ikea_kadrilj",
|
"ikea_kadrilj",
|
||||||
"aux_ac",
|
"aux_ac",
|
||||||
"hw_q80r_soundbar",
|
"hw_q80r_soundbar",
|
||||||
|
"gas_meter",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def device_fixture(
|
def device_fixture(
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"components": {
|
||||||
|
"main": {
|
||||||
|
"healthCheck": {
|
||||||
|
"checkInterval": {
|
||||||
|
"value": 60,
|
||||||
|
"unit": "s",
|
||||||
|
"data": {
|
||||||
|
"deviceScheme": "UNTRACKED",
|
||||||
|
"protocol": "cloud"
|
||||||
|
},
|
||||||
|
"timestamp": "2025-02-27T14:06:11.704Z"
|
||||||
|
},
|
||||||
|
"healthStatus": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"DeviceWatch-Enroll": {
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
"DeviceWatch-DeviceStatus": {
|
||||||
|
"value": "online",
|
||||||
|
"data": {},
|
||||||
|
"timestamp": "2025-04-11T13:00:00.444Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"refresh": {},
|
||||||
|
"gasMeter": {
|
||||||
|
"gasMeterPrecision": {
|
||||||
|
"value": {
|
||||||
|
"volume": 5,
|
||||||
|
"calorific": 1,
|
||||||
|
"conversion": 1
|
||||||
|
},
|
||||||
|
"timestamp": "2025-04-11T13:00:00.444Z"
|
||||||
|
},
|
||||||
|
"gasMeterCalorific": {
|
||||||
|
"value": 40,
|
||||||
|
"timestamp": "2025-04-11T13:00:00.444Z"
|
||||||
|
},
|
||||||
|
"gasMeterTime": {
|
||||||
|
"value": "2025-04-11T13:30:00.028Z",
|
||||||
|
"timestamp": "2025-04-11T13:30:00.532Z"
|
||||||
|
},
|
||||||
|
"gasMeterVolume": {
|
||||||
|
"value": 14,
|
||||||
|
"unit": "ccf",
|
||||||
|
"timestamp": "2025-04-11T13:00:00.444Z"
|
||||||
|
},
|
||||||
|
"gasMeterConversion": {
|
||||||
|
"value": 3.6,
|
||||||
|
"timestamp": "2025-04-11T13:00:00.444Z"
|
||||||
|
},
|
||||||
|
"gasMeter": {
|
||||||
|
"value": 450.5,
|
||||||
|
"unit": "kWh",
|
||||||
|
"timestamp": "2025-04-11T13:00:00.444Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
tests/components/smartthings/fixtures/devices/gas_meter.json
Normal file
56
tests/components/smartthings/fixtures/devices/gas_meter.json
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"deviceId": "3b57dca3-9a90-4f27-ba80-f947b1e60d58",
|
||||||
|
"name": "copper_gas_meter_v04",
|
||||||
|
"label": "Gas Meter",
|
||||||
|
"manufacturerName": "0A6v",
|
||||||
|
"presentationId": "ST_176e9efa-01d2-4d1b-8130-d37a4ef1b413",
|
||||||
|
"deviceManufacturerCode": "CopperLabs",
|
||||||
|
"locationId": "4e88bf74-3bed-4e6d-9fa7-6acb776a4df9",
|
||||||
|
"ownerId": "6fc21de5-123e-2f8c-2cc6-311635aeaaef",
|
||||||
|
"roomId": "fafae9db-a2b5-480f-8ff5-df8f913356df",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"id": "main",
|
||||||
|
"label": "main",
|
||||||
|
"capabilities": [
|
||||||
|
{
|
||||||
|
"id": "healthCheck",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "refresh",
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gasMeter",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"name": "GasMeter",
|
||||||
|
"categoryType": "manufacturer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"createTime": "2025-02-27T14:06:11.522Z",
|
||||||
|
"profile": {
|
||||||
|
"id": "5cca2553-23d6-43c4-81ad-a1c6c43efa00"
|
||||||
|
},
|
||||||
|
"viper": {
|
||||||
|
"manufacturerName": "CopperLabs",
|
||||||
|
"modelName": "Virtual Gas Meter",
|
||||||
|
"endpointAppId": "viper_1d5767a0-af08-11ed-a999-9f1f172a27ff"
|
||||||
|
},
|
||||||
|
"type": "VIPER",
|
||||||
|
"restrictionTier": 0,
|
||||||
|
"allowed": null,
|
||||||
|
"executionContext": "CLOUD",
|
||||||
|
"relationships": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_links": {}
|
||||||
|
}
|
@ -1058,6 +1058,39 @@
|
|||||||
'via_device_id': None,
|
'via_device_id': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_devices[gas_meter]
|
||||||
|
DeviceRegistryEntrySnapshot({
|
||||||
|
'area_id': None,
|
||||||
|
'config_entries': <ANY>,
|
||||||
|
'config_entries_subentries': <ANY>,
|
||||||
|
'configuration_url': 'https://account.smartthings.com',
|
||||||
|
'connections': set({
|
||||||
|
}),
|
||||||
|
'disabled_by': None,
|
||||||
|
'entry_type': None,
|
||||||
|
'hw_version': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'identifiers': set({
|
||||||
|
tuple(
|
||||||
|
'smartthings',
|
||||||
|
'3b57dca3-9a90-4f27-ba80-f947b1e60d58',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
'is_new': False,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'manufacturer': 'CopperLabs',
|
||||||
|
'model': 'Virtual Gas Meter',
|
||||||
|
'model_id': None,
|
||||||
|
'name': 'Gas Meter',
|
||||||
|
'name_by_user': None,
|
||||||
|
'primary_config_entry': <ANY>,
|
||||||
|
'serial_number': None,
|
||||||
|
'suggested_area': None,
|
||||||
|
'sw_version': None,
|
||||||
|
'via_device_id': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_devices[ge_in_wall_smart_dimmer]
|
# name: test_devices[ge_in_wall_smart_dimmer]
|
||||||
DeviceRegistryEntrySnapshot({
|
DeviceRegistryEntrySnapshot({
|
||||||
'area_id': 'theater',
|
'area_id': 'theater',
|
||||||
|
@ -8007,6 +8007,208 @@
|
|||||||
'state': 'unknown',
|
'state': 'unknown',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor.private': dict({
|
||||||
|
'suggested_unit_of_measurement': <UnitOfVolume.CUBIC_METERS: 'm³'>,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.GAS: 'gas'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Gas',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': '3b57dca3-9a90-4f27-ba80-f947b1e60d58_main_gasMeter_gasMeterVolume_gasMeterVolume',
|
||||||
|
'unit_of_measurement': <UnitOfVolume.CUBIC_METERS: 'm³'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'gas',
|
||||||
|
'friendly_name': 'Gas Meter Gas',
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
'unit_of_measurement': <UnitOfVolume.CUBIC_METERS: 'm³'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '40',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas_meter-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas_meter',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Gas meter',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'gas_meter',
|
||||||
|
'unique_id': '3b57dca3-9a90-4f27-ba80-f947b1e60d58_main_gasMeter_gasMeter_gasMeter',
|
||||||
|
'unit_of_measurement': 'kWh',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas_meter-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'energy',
|
||||||
|
'friendly_name': 'Gas Meter Gas meter',
|
||||||
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
|
'unit_of_measurement': 'kWh',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas_meter',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '450.5',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas_meter_calorific-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': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas_meter_calorific',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Gas meter calorific',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'gas_meter_calorific',
|
||||||
|
'unique_id': '3b57dca3-9a90-4f27-ba80-f947b1e60d58_main_gasMeter_gasMeterCalorific_gasMeterCalorific',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas_meter_calorific-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Gas Meter Gas meter calorific',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas_meter_calorific',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '40',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas_meter_time-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': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas_meter_time',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Gas meter time',
|
||||||
|
'platform': 'smartthings',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'gas_meter_time',
|
||||||
|
'unique_id': '3b57dca3-9a90-4f27-ba80-f947b1e60d58_main_gasMeter_gasMeterTime_gasMeterTime',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_all_entities[gas_meter][sensor.gas_meter_gas_meter_time-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'timestamp',
|
||||||
|
'friendly_name': 'Gas Meter Gas meter time',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.gas_meter_gas_meter_time',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '2025-04-11T13:30:00+00:00',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_all_entities[generic_ef00_v1][sensor.thermostat_kuche_link_quality-entry]
|
# name: test_all_entities[generic_ef00_v1][sensor.thermostat_kuche_link_quality-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@ -55,6 +55,11 @@
|
|||||||
<VentilationMode.SENSOR_OVERRIDE: 'sensor_override'>,
|
<VentilationMode.SENSOR_OVERRIDE: 'sensor_override'>,
|
||||||
]),
|
]),
|
||||||
'supported_features': <FanEntityFeature: 9>,
|
'supported_features': <FanEntityFeature: 9>,
|
||||||
|
'vicare_quickmodes': list([
|
||||||
|
'comfort',
|
||||||
|
'eco',
|
||||||
|
'holiday',
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
'entity_id': 'fan.model0_ventilation',
|
'entity_id': 'fan.model0_ventilation',
|
||||||
@ -94,7 +99,7 @@
|
|||||||
'options': dict({
|
'options': dict({
|
||||||
}),
|
}),
|
||||||
'original_device_class': None,
|
'original_device_class': None,
|
||||||
'original_icon': 'mdi:fan-off',
|
'original_icon': 'mdi:fan',
|
||||||
'original_name': 'Ventilation',
|
'original_name': 'Ventilation',
|
||||||
'platform': 'vicare',
|
'platform': 'vicare',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
@ -108,7 +113,7 @@
|
|||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'model1 Ventilation',
|
'friendly_name': 'model1 Ventilation',
|
||||||
'icon': 'mdi:fan-off',
|
'icon': 'mdi:fan',
|
||||||
'percentage': 0,
|
'percentage': 0,
|
||||||
'percentage_step': 25.0,
|
'percentage_step': 25.0,
|
||||||
'preset_mode': None,
|
'preset_mode': None,
|
||||||
@ -118,6 +123,11 @@
|
|||||||
<VentilationMode.SENSOR_DRIVEN: 'sensor_driven'>,
|
<VentilationMode.SENSOR_DRIVEN: 'sensor_driven'>,
|
||||||
]),
|
]),
|
||||||
'supported_features': <FanEntityFeature: 25>,
|
'supported_features': <FanEntityFeature: 25>,
|
||||||
|
'vicare_quickmodes': list([
|
||||||
|
'comfort',
|
||||||
|
'eco',
|
||||||
|
'holiday',
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
'entity_id': 'fan.model1_ventilation',
|
'entity_id': 'fan.model1_ventilation',
|
||||||
@ -179,6 +189,11 @@
|
|||||||
<VentilationMode.STANDARD: 'standard'>,
|
<VentilationMode.STANDARD: 'standard'>,
|
||||||
]),
|
]),
|
||||||
'supported_features': <FanEntityFeature: 8>,
|
'supported_features': <FanEntityFeature: 8>,
|
||||||
|
'vicare_quickmodes': list([
|
||||||
|
'comfort',
|
||||||
|
'eco',
|
||||||
|
'holiday',
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
'entity_id': 'fan.model2_ventilation',
|
'entity_id': 'fan.model2_ventilation',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user