mirror of
https://github.com/home-assistant/core.git
synced 2026-02-23 10:41:19 +01:00
Compare commits
12 Commits
fritzbox/i
...
hv_switch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
556440a918 | ||
|
|
18f1a2705e | ||
|
|
2ff115f3a7 | ||
|
|
901220b4a1 | ||
|
|
085127ac58 | ||
|
|
498791253b | ||
|
|
f0059b2ffb | ||
|
|
1a9177b097 | ||
|
|
7a07d0c198 | ||
|
|
937cbb9d60 | ||
|
|
c46f73aafb | ||
|
|
9dcb3c6bb0 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -37,7 +37,7 @@ on:
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
CACHE_VERSION: 3
|
||||
CACHE_VERSION: 2
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2026.3"
|
||||
|
||||
4
CODEOWNERS
generated
4
CODEOWNERS
generated
@@ -403,8 +403,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/duke_energy/ @hunterjm
|
||||
/homeassistant/components/duotecno/ @cereal2nd
|
||||
/tests/components/duotecno/ @cereal2nd
|
||||
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192
|
||||
/tests/components/dwd_weather_warnings/ @runningman84 @stephan192
|
||||
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192 @andarotajo
|
||||
/tests/components/dwd_weather_warnings/ @runningman84 @stephan192 @andarotajo
|
||||
/homeassistant/components/dynalite/ @ziv1234
|
||||
/tests/components/dynalite/ @ziv1234
|
||||
/homeassistant/components/eafm/ @Jc2k
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
"name": "Go to preset"
|
||||
},
|
||||
"ptz_control": {
|
||||
"description": "Moves (pan/tilt) and/or zooms a PTZ camera.",
|
||||
"description": "Moves (pan/tilt) and/or zoom a PTZ camera.",
|
||||
"fields": {
|
||||
"entity_id": {
|
||||
"description": "[%key:component::amcrest::services::enable_recording::fields::entity_id::description%]",
|
||||
|
||||
@@ -38,6 +38,7 @@ def get_app_entity_description(
|
||||
translation_key="apps",
|
||||
name=name_slug,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.apps.get(name_slug),
|
||||
)
|
||||
|
||||
@@ -51,6 +52,7 @@ def get_core_integration_entity_description(
|
||||
translation_key="core_integrations",
|
||||
name=name,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.core_integrations.get(domain),
|
||||
)
|
||||
|
||||
@@ -64,6 +66,7 @@ def get_custom_integration_entity_description(
|
||||
translation_key="custom_integrations",
|
||||
translation_placeholders={"custom_integration_domain": domain},
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.custom_integrations.get(domain),
|
||||
)
|
||||
|
||||
@@ -74,6 +77,7 @@ GENERAL_SENSORS = [
|
||||
translation_key="total_active_installations",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.active_installations,
|
||||
),
|
||||
AnalyticsSensorEntityDescription(
|
||||
@@ -81,6 +85,7 @@ GENERAL_SENSORS = [
|
||||
translation_key="total_reports_integrations",
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
native_unit_of_measurement="active installations",
|
||||
value_fn=lambda data: data.reports_integrations,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -24,23 +24,14 @@
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"apps": {
|
||||
"unit_of_measurement": "active installations"
|
||||
},
|
||||
"core_integrations": {
|
||||
"unit_of_measurement": "[%key:component::analytics_insights::entity::sensor::apps::unit_of_measurement%]"
|
||||
},
|
||||
"custom_integrations": {
|
||||
"name": "{custom_integration_domain} (custom)",
|
||||
"unit_of_measurement": "[%key:component::analytics_insights::entity::sensor::apps::unit_of_measurement%]"
|
||||
"name": "{custom_integration_domain} (custom)"
|
||||
},
|
||||
"total_active_installations": {
|
||||
"name": "Total active installations",
|
||||
"unit_of_measurement": "[%key:component::analytics_insights::entity::sensor::apps::unit_of_measurement%]"
|
||||
"name": "Total active installations"
|
||||
},
|
||||
"total_reports_integrations": {
|
||||
"name": "Total reported integrations",
|
||||
"unit_of_measurement": "[%key:component::analytics_insights::entity::sensor::apps::unit_of_measurement%]"
|
||||
"name": "Total reported integrations"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -74,7 +74,7 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
|
||||
return self._feature.is_on
|
||||
|
||||
@property
|
||||
def brightness(self) -> int | None:
|
||||
def brightness(self):
|
||||
"""Return the name."""
|
||||
return self._feature.brightness
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
def _convert_image_for_editing(data: bytes) -> tuple[bytes, str]:
|
||||
"""Ensure the image data is in a format accepted by OpenAI image edits."""
|
||||
img: Image.Image
|
||||
stream = io.BytesIO(data)
|
||||
with Image.open(stream) as img:
|
||||
mode = img.mode
|
||||
|
||||
@@ -199,7 +199,7 @@ class Control4Light(Control4Entity, LightEntity):
|
||||
return self.coordinator.data[self._idx][CONTROL4_NON_DIMMER_VAR] > 0
|
||||
|
||||
@property
|
||||
def brightness(self) -> int | None:
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
if self._is_dimmer:
|
||||
for var in CONTROL4_DIMMER_VARS:
|
||||
|
||||
@@ -132,7 +132,7 @@ class DecoraWifiLight(LightEntity):
|
||||
return self._switch.serial
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self):
|
||||
"""Return the brightness of the dimmer switch."""
|
||||
return int(self._switch.brightness * 255 / 100)
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pydoods"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["pydoods==1.0.2", "Pillow==12.1.1"]
|
||||
"requirements": ["pydoods==1.0.2", "Pillow==12.0.0"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "dwd_weather_warnings",
|
||||
"name": "Deutscher Wetterdienst (DWD) Weather Warnings",
|
||||
"codeowners": ["@runningman84", "@stephan192"],
|
||||
"codeowners": ["@runningman84", "@stephan192", "@andarotajo"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings",
|
||||
"integration_type": "service",
|
||||
|
||||
@@ -75,7 +75,7 @@ class EufyHomeLight(LightEntity):
|
||||
self._attr_is_on = self._bulb.power
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return int(self._brightness * 255 / 100)
|
||||
|
||||
@@ -88,7 +88,7 @@ class EufyHomeLight(LightEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple[float, float] | None:
|
||||
def hs_color(self):
|
||||
"""Return the color of this light."""
|
||||
return self._hs
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class FitbitApi(ABC):
|
||||
configuration = Configuration()
|
||||
configuration.pool_manager = async_get_clientsession(self._hass)
|
||||
configuration.access_token = token[CONF_ACCESS_TOKEN]
|
||||
return await self._hass.async_add_executor_job(ApiClient, configuration)
|
||||
return ApiClient(configuration)
|
||||
|
||||
async def async_get_user_profile(self) -> FitbitProfile:
|
||||
"""Return the user profile from the API."""
|
||||
|
||||
@@ -63,7 +63,6 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat
|
||||
host=self.config_entry.data[CONF_HOST],
|
||||
user=self.config_entry.data[CONF_USERNAME],
|
||||
password=self.config_entry.data[CONF_PASSWORD],
|
||||
timeout=20,
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyfritzhome"],
|
||||
"requirements": ["pyfritzhome==0.6.20"],
|
||||
"requirements": ["pyfritzhome==0.6.19"],
|
||||
"ssdp": [
|
||||
{
|
||||
"st": "urn:schemas-upnp-org:device:fritzbox:1"
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/generic",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["av==16.0.1", "Pillow==12.1.1"]
|
||||
"requirements": ["av==16.0.1", "Pillow==12.0.0"]
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DISCOVERY_TIMEOUT, DOMAIN
|
||||
from .const import DISCOVERY_TIMEOUT
|
||||
from .coordinator import GoveeLocalApiCoordinator, GoveeLocalConfigEntry
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.LIGHT]
|
||||
@@ -52,11 +52,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoveeLocalConfigEntry) -
|
||||
_LOGGER.error("Start failed, errno: %d", ex.errno)
|
||||
return False
|
||||
_LOGGER.error("Port %s already in use", LISTENING_PORT)
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="port_in_use",
|
||||
translation_placeholders={"port": LISTENING_PORT},
|
||||
) from ex
|
||||
raise ConfigEntryNotReady from ex
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
@@ -65,9 +61,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoveeLocalConfigEntry) -
|
||||
while not coordinator.devices:
|
||||
await asyncio.sleep(delay=1)
|
||||
except TimeoutError as ex:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN, translation_key="no_devices_found"
|
||||
) from ex
|
||||
raise ConfigEntryNotReady from ex
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -33,13 +33,5 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"no_devices_found": {
|
||||
"message": "[%key:common::config_flow::abort::no_devices_found%]"
|
||||
},
|
||||
"port_in_use": {
|
||||
"message": "Port {port} is already in use"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
"abort": {
|
||||
"fw_download_failed": "{firmware_name} firmware for your {model} failed to download. Make sure Home Assistant has internet access and try again.",
|
||||
"fw_install_failed": "{firmware_name} firmware failed to install, check Home Assistant logs for more information.",
|
||||
"not_hassio_thread": "The OpenThread Border Router app can only be installed with Home Assistant OS. If you would like to use the {model} as a Thread border router, please manually set up OpenThread Border Router to communicate with it.",
|
||||
"otbr_addon_already_running": "The OpenThread Border Router app is already running, it cannot be installed again.",
|
||||
"otbr_still_using_stick": "This {model} is in use by the OpenThread Border Router app. If you use the Thread network, make sure you have alternative border routers. Uninstall the app and try again.",
|
||||
"unsupported_firmware": "The radio firmware on your {model} could not be determined. Make sure that no other integration or app is currently trying to communicate with the device. If you are running Home Assistant OS in a virtual machine or in Docker, please make sure that permissions are set correctly for the device.",
|
||||
"not_hassio_thread": "The OpenThread Border Router add-on can only be installed with Home Assistant OS. If you would like to use the {model} as a Thread border router, please manually set up OpenThread Border Router to communicate with it.",
|
||||
"otbr_addon_already_running": "The OpenThread Border Router add-on is already running, it cannot be installed again.",
|
||||
"otbr_still_using_stick": "This {model} is in use by the OpenThread Border Router add-on. If you use the Thread network, make sure you have alternative border routers. Uninstall the add-on and try again.",
|
||||
"unsupported_firmware": "The radio firmware on your {model} could not be determined. Make sure that no other integration or add-on is currently trying to communicate with the device. If you are running Home Assistant OS in a virtual machine or in Docker, please make sure that permissions are set correctly for the device.",
|
||||
"zha_still_using_stick": "This {model} is in use by the Zigbee Home Automation integration. Please migrate your Zigbee network to another adapter or delete the integration and try again."
|
||||
},
|
||||
"progress": {
|
||||
"install_firmware": "Installing {firmware_name} firmware.\n\nDo not make any changes to your hardware or software until this finishes.",
|
||||
"install_otbr_addon": "Installing app",
|
||||
"start_otbr_addon": "Starting app"
|
||||
"install_otbr_addon": "Installing add-on",
|
||||
"start_otbr_addon": "Starting add-on"
|
||||
},
|
||||
"step": {
|
||||
"confirm_otbr": {
|
||||
@@ -34,7 +34,7 @@
|
||||
"title": "Updating adapter"
|
||||
},
|
||||
"otbr_failed": {
|
||||
"description": "The OpenThread Border Router app installation was unsuccessful. Ensure no other software is trying to communicate with the {model}, you have access to the Internet and can install other apps, and try again. Check the Supervisor logs if the problem persists.",
|
||||
"description": "The OpenThread Border Router add-on installation was unsuccessful. Ensure no other software is trying to communicate with the {model}, you have access to the Internet and can install other add-ons, and try again. Check the Supervisor logs if the problem persists.",
|
||||
"title": "Failed to set up OpenThread Border Router"
|
||||
},
|
||||
"pick_firmware": {
|
||||
@@ -89,11 +89,11 @@
|
||||
"silabs_multiprotocol_hardware": {
|
||||
"options": {
|
||||
"abort": {
|
||||
"addon_already_running": "Failed to start the {addon_name} app because it is already running.",
|
||||
"addon_info_failed": "Failed to get {addon_name} app info.",
|
||||
"addon_install_failed": "Failed to install the {addon_name} app.",
|
||||
"addon_already_running": "Failed to start the {addon_name} add-on because it is already running.",
|
||||
"addon_info_failed": "Failed to get {addon_name} add-on info.",
|
||||
"addon_install_failed": "Failed to install the {addon_name} add-on.",
|
||||
"addon_set_config_failed": "Failed to set {addon_name} configuration.",
|
||||
"addon_start_failed": "Failed to start the {addon_name} app.",
|
||||
"addon_start_failed": "Failed to start the {addon_name} add-on.",
|
||||
"not_hassio": "The hardware options can only be configured on Home Assistant OS installations.",
|
||||
"zha_migration_failed": "The ZHA migration did not succeed."
|
||||
},
|
||||
@@ -101,8 +101,8 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"progress": {
|
||||
"install_addon": "Please wait while the {addon_name} app installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the {addon_name} app start completes. This may take some seconds."
|
||||
"install_addon": "Please wait while the {addon_name} add-on installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the {addon_name} add-on start completes. This may take some seconds."
|
||||
},
|
||||
"step": {
|
||||
"addon_installed_other_device": {
|
||||
@@ -129,7 +129,7 @@
|
||||
"title": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::step::reconfigure_addon::title%]"
|
||||
},
|
||||
"install_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol app installation has started"
|
||||
"title": "The Silicon Labs Multiprotocol add-on installation has started"
|
||||
},
|
||||
"notify_channel_change": {
|
||||
"description": "A Zigbee and Thread channel change has been initiated and will finish in {delay_minutes} minutes.",
|
||||
@@ -143,7 +143,7 @@
|
||||
"title": "Reconfigure IEEE 802.15.4 radio multiprotocol support"
|
||||
},
|
||||
"start_addon": {
|
||||
"title": "The Silicon Labs Multiprotocol app is starting."
|
||||
"title": "The Silicon Labs Multiprotocol add-on is starting."
|
||||
},
|
||||
"uninstall_addon": {
|
||||
"data": {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"otbr_addon_already_running": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_addon_already_running%]",
|
||||
"otbr_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::otbr_still_using_stick%]",
|
||||
"read_hw_settings_error": "Failed to read hardware settings",
|
||||
"unsupported_firmware": "The radio firmware on your {model} could not be determined. Make sure that no other integration or app is currently trying to communicate with the device.",
|
||||
"unsupported_firmware": "The radio firmware on your {model} could not be determined. Make sure that no other integration or add-on is currently trying to communicate with the device.",
|
||||
"write_hw_settings_error": "Failed to write hardware settings",
|
||||
"zha_migration_failed": "[%key:component::homeassistant_hardware::silabs_multiprotocol_hardware::options::abort::zha_migration_failed%]",
|
||||
"zha_still_using_stick": "[%key:component::homeassistant_hardware::firmware_picker::options::abort::zha_still_using_stick%]"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pyhomematic import HMConnection
|
||||
import voluptuous as vol
|
||||
@@ -216,11 +215,8 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
hass.data[DATA_CONF] = remotes = {}
|
||||
hass.data[DATA_STORE] = set()
|
||||
|
||||
interfaces: dict[str, dict[str, Any]] = conf[CONF_INTERFACES]
|
||||
hosts: dict[str, dict[str, Any]] = conf[CONF_HOSTS]
|
||||
|
||||
# Create hosts-dictionary for pyhomematic
|
||||
for rname, rconfig in interfaces.items():
|
||||
for rname, rconfig in conf[CONF_INTERFACES].items():
|
||||
remotes[rname] = {
|
||||
"ip": rconfig.get(CONF_HOST),
|
||||
"port": rconfig.get(CONF_PORT),
|
||||
@@ -236,7 +232,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"connect": True,
|
||||
}
|
||||
|
||||
for sname, sconfig in hosts.items():
|
||||
for sname, sconfig in conf[CONF_HOSTS].items():
|
||||
remotes[sname] = {
|
||||
"ip": sconfig.get(CONF_HOST),
|
||||
"port": sconfig[CONF_PORT],
|
||||
@@ -262,7 +258,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, hass.data[DATA_HOMEMATIC].stop)
|
||||
|
||||
# Init homematic hubs
|
||||
entity_hubs = [HMHub(hass, homematic, hub_name) for hub_name in hosts]
|
||||
entity_hubs = [HMHub(hass, homematic, hub_name) for hub_name in conf[CONF_HOSTS]]
|
||||
|
||||
def _hm_service_virtualkey(service: ServiceCall) -> None:
|
||||
"""Service to handle virtualkey servicecalls."""
|
||||
@@ -298,7 +294,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
def _service_handle_value(service: ServiceCall) -> None:
|
||||
"""Service to call setValue method for HomeMatic system variable."""
|
||||
entity_ids: list[str] | None = service.data.get(ATTR_ENTITY_ID)
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
name = service.data[ATTR_NAME]
|
||||
value = service.data[ATTR_VALUE]
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ from pyhomematic import HMConnection
|
||||
from pyhomematic.devicetypes.generic import HMGeneric
|
||||
|
||||
from homeassistant.const import ATTR_NAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
from homeassistant.helpers.event import track_time_interval
|
||||
@@ -200,14 +199,14 @@ class HMHub(Entity):
|
||||
|
||||
_attr_should_poll = False
|
||||
|
||||
def __init__(self, hass: HomeAssistant, homematic: HMConnection, name: str) -> None:
|
||||
def __init__(self, hass, homematic, name):
|
||||
"""Initialize HomeMatic hub."""
|
||||
self.hass = hass
|
||||
self.entity_id = f"{DOMAIN}.{name.lower()}"
|
||||
self._homematic = homematic
|
||||
self._variables: dict[str, Any] = {}
|
||||
self._variables = {}
|
||||
self._name = name
|
||||
self._state: int | None = None
|
||||
self._state = None
|
||||
|
||||
# Load data
|
||||
track_time_interval(self.hass, self._update_hub, SCAN_INTERVAL_HUB)
|
||||
@@ -217,12 +216,12 @@ class HMHub(Entity):
|
||||
self.hass.add_job(self._update_variables, None)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def state(self) -> int | None:
|
||||
def state(self):
|
||||
"""Return the state of the entity."""
|
||||
return self._state
|
||||
|
||||
@@ -232,7 +231,7 @@ class HMHub(Entity):
|
||||
return self._variables.copy()
|
||||
|
||||
@property
|
||||
def icon(self) -> str:
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend, if any."""
|
||||
return "mdi:gradient-vertical"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .coordinator import HomevoltConfigEntry, HomevoltDataUpdateCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: HomevoltConfigEntry) -> bool:
|
||||
|
||||
67
homeassistant/components/homevolt/entity.py
Normal file
67
homeassistant/components/homevolt/entity.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Shared entity helpers for Homevolt."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from typing import Any, Concatenate
|
||||
|
||||
from homevolt import HomevoltAuthenticationError, HomevoltConnectionError, HomevoltError
|
||||
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
from .coordinator import HomevoltDataUpdateCoordinator
|
||||
|
||||
|
||||
class HomevoltEntity(CoordinatorEntity[HomevoltDataUpdateCoordinator]):
|
||||
"""Base Homevolt entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, coordinator: HomevoltDataUpdateCoordinator, device_identifier: str
|
||||
) -> None:
|
||||
"""Initialize the Homevolt entity."""
|
||||
super().__init__(coordinator)
|
||||
device_id = coordinator.data.unique_id
|
||||
device_metadata = coordinator.data.device_metadata.get(device_identifier)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{device_id}_{device_identifier}")},
|
||||
configuration_url=coordinator.client.base_url,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=device_metadata.model if device_metadata else None,
|
||||
name=device_metadata.name if device_metadata else None,
|
||||
)
|
||||
|
||||
|
||||
def homevolt_exception_handler[_HomevoltEntityT: HomevoltEntity, **_P](
|
||||
func: Callable[Concatenate[_HomevoltEntityT, _P], Coroutine[Any, Any, Any]],
|
||||
) -> Callable[Concatenate[_HomevoltEntityT, _P], Coroutine[Any, Any, None]]:
|
||||
"""Decorate Homevolt calls to handle exceptions."""
|
||||
|
||||
async def handler(
|
||||
self: _HomevoltEntityT, *args: _P.args, **kwargs: _P.kwargs
|
||||
) -> None:
|
||||
try:
|
||||
await func(self, *args, **kwargs)
|
||||
except HomevoltAuthenticationError as error:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="auth_failed",
|
||||
) from error
|
||||
except HomevoltConnectionError as error:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="communication_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
except HomevoltError as error:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unknown_error",
|
||||
translation_placeholders={"error": str(error)},
|
||||
) from error
|
||||
|
||||
return handler
|
||||
@@ -7,7 +7,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["homevolt==0.4.4"],
|
||||
"requirements": ["homevolt==0.5.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"name": "homevolt*",
|
||||
|
||||
@@ -22,13 +22,11 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN, MANUFACTURER
|
||||
from .coordinator import HomevoltConfigEntry, HomevoltDataUpdateCoordinator
|
||||
from .entity import HomevoltEntity
|
||||
|
||||
PARALLEL_UPDATES = 0 # Coordinator-based updates
|
||||
|
||||
@@ -309,11 +307,10 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class HomevoltSensor(CoordinatorEntity[HomevoltDataUpdateCoordinator], SensorEntity):
|
||||
class HomevoltSensor(HomevoltEntity, SensorEntity):
|
||||
"""Representation of a Homevolt sensor."""
|
||||
|
||||
entity_description: SensorEntityDescription
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -322,24 +319,12 @@ class HomevoltSensor(CoordinatorEntity[HomevoltDataUpdateCoordinator], SensorEnt
|
||||
sensor_key: str,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
unique_id = coordinator.data.unique_id
|
||||
self._attr_unique_id = f"{unique_id}_{sensor_key}"
|
||||
sensor_data = coordinator.data.sensors[sensor_key]
|
||||
super().__init__(coordinator, sensor_data.device_identifier)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.data.unique_id}_{sensor_key}"
|
||||
self._sensor_key = sensor_key
|
||||
|
||||
device_metadata = coordinator.data.device_metadata.get(
|
||||
sensor_data.device_identifier
|
||||
)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, f"{unique_id}_{sensor_data.device_identifier}")},
|
||||
configuration_url=coordinator.client.base_url,
|
||||
manufacturer=MANUFACTURER,
|
||||
model=device_metadata.model if device_metadata else None,
|
||||
name=device_metadata.name if device_metadata else None,
|
||||
)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
|
||||
@@ -160,6 +160,22 @@
|
||||
"tmin": {
|
||||
"name": "Minimum temperature"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"local_mode": {
|
||||
"name": "Local mode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"auth_failed": {
|
||||
"message": "[%key:common::config_flow::error::invalid_auth%]"
|
||||
},
|
||||
"communication_error": {
|
||||
"message": "[%key:common::config_flow::error::cannot_connect%]"
|
||||
},
|
||||
"unknown_error": {
|
||||
"message": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
55
homeassistant/components/homevolt/switch.py
Normal file
55
homeassistant/components/homevolt/switch.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Support for Homevolt switch entities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import HomevoltConfigEntry, HomevoltDataUpdateCoordinator
|
||||
from .entity import HomevoltEntity, homevolt_exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 0 # Coordinator-based updates
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: HomevoltConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Homevolt switch entities."""
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities([HomevoltLocalModeSwitch(coordinator)])
|
||||
|
||||
|
||||
class HomevoltLocalModeSwitch(HomevoltEntity, SwitchEntity):
|
||||
"""Switch entity for Homevolt local mode."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_translation_key = "local_mode"
|
||||
|
||||
def __init__(self, coordinator: HomevoltDataUpdateCoordinator) -> None:
|
||||
"""Initialize the switch entity."""
|
||||
self._attr_unique_id = f"{coordinator.data.unique_id}_local_mode"
|
||||
device_id = coordinator.data.unique_id
|
||||
super().__init__(coordinator, f"ems_{device_id}")
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if local mode is enabled."""
|
||||
return self.coordinator.client.local_mode_enabled
|
||||
|
||||
@homevolt_exception_handler
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Enable local mode."""
|
||||
await self.coordinator.client.enable_local_mode()
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
@homevolt_exception_handler
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Disable local mode."""
|
||||
await self.coordinator.client.disable_local_mode()
|
||||
await self.coordinator.async_request_refresh()
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["keyrings.alt", "pyicloud"],
|
||||
"requirements": ["pyicloud==2.4.1"]
|
||||
"requirements": ["pyicloud==2.3.0"]
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class IGloLamp(LightEntity):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return int((self._lamp.state()["brightness"] / 200.0) * 255)
|
||||
|
||||
@@ -97,17 +97,17 @@ class IGloLamp(LightEntity):
|
||||
return self._lamp.min_kelvin
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple[float, float]:
|
||||
def hs_color(self):
|
||||
"""Return the hs value."""
|
||||
return color_util.color_RGB_to_hs(*self._lamp.state()["rgb"])
|
||||
|
||||
@property
|
||||
def effect(self) -> str:
|
||||
def effect(self):
|
||||
"""Return the current effect."""
|
||||
return self._lamp.state()["effect"]
|
||||
|
||||
@property
|
||||
def effect_list(self) -> list[str]:
|
||||
def effect_list(self):
|
||||
"""Return the list of supported effects."""
|
||||
return self._lamp.effect_list()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Implementation of the lock platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError
|
||||
from igloohome_api import (
|
||||
@@ -64,7 +63,7 @@ class IgloohomeLockEntity(IgloohomeBaseEntity, LockEntity):
|
||||
)
|
||||
self.bridge_id = bridge_id
|
||||
|
||||
async def async_lock(self, **kwargs: Any) -> None:
|
||||
async def async_lock(self, **kwargs):
|
||||
"""Lock this lock."""
|
||||
try:
|
||||
await self.api.create_bridge_proxied_job(
|
||||
@@ -73,7 +72,7 @@ class IgloohomeLockEntity(IgloohomeBaseEntity, LockEntity):
|
||||
except (ApiException, ClientError) as err:
|
||||
raise HomeAssistantError from err
|
||||
|
||||
async def async_unlock(self, **kwargs: Any) -> None:
|
||||
async def async_unlock(self, **kwargs):
|
||||
"""Unlock this lock."""
|
||||
try:
|
||||
await self.api.create_bridge_proxied_job(
|
||||
@@ -82,7 +81,7 @@ class IgloohomeLockEntity(IgloohomeBaseEntity, LockEntity):
|
||||
except (ApiException, ClientError) as err:
|
||||
raise HomeAssistantError from err
|
||||
|
||||
async def async_open(self, **kwargs: Any) -> None:
|
||||
async def async_open(self, **kwargs):
|
||||
"""Open (unlatch) this lock."""
|
||||
try:
|
||||
await self.api.create_bridge_proxied_job(
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/image_upload",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["Pillow==12.1.1"]
|
||||
"requirements": ["Pillow==12.0.0"]
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class InsteonDimmerEntity(InsteonEntity, LightEntity):
|
||||
self._attr_supported_color_modes = {ColorMode.ONOFF}
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._insteon_device_group.value
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Generic
|
||||
|
||||
from pylitterbot import FeederRobot, LitterRobot3, LitterRobot4, LitterRobot5, Robot
|
||||
from pylitterbot import FeederRobot, LitterRobot3, LitterRobot4, Robot
|
||||
|
||||
from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
@@ -24,24 +24,20 @@ class RobotButtonEntityDescription(ButtonEntityDescription, Generic[_WhiskerEnti
|
||||
press_fn: Callable[[_WhiskerEntityT], Coroutine[Any, Any, bool]]
|
||||
|
||||
|
||||
ROBOT_BUTTON_MAP: dict[tuple[type[Robot], ...], RobotButtonEntityDescription] = {
|
||||
(LitterRobot3, LitterRobot5): RobotButtonEntityDescription[
|
||||
LitterRobot3 | LitterRobot5
|
||||
](
|
||||
ROBOT_BUTTON_MAP: dict[type[Robot], RobotButtonEntityDescription] = {
|
||||
LitterRobot3: RobotButtonEntityDescription[LitterRobot3](
|
||||
key="reset_waste_drawer",
|
||||
translation_key="reset_waste_drawer",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_fn=lambda robot: robot.reset_waste_drawer(),
|
||||
),
|
||||
(LitterRobot4, LitterRobot5): RobotButtonEntityDescription[
|
||||
LitterRobot4 | LitterRobot5
|
||||
](
|
||||
LitterRobot4: RobotButtonEntityDescription[LitterRobot4](
|
||||
key="reset",
|
||||
translation_key="reset",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
press_fn=lambda robot: robot.reset(),
|
||||
),
|
||||
(FeederRobot,): RobotButtonEntityDescription[FeederRobot](
|
||||
FeederRobot: RobotButtonEntityDescription[FeederRobot](
|
||||
key="give_snack",
|
||||
translation_key="give_snack",
|
||||
press_fn=lambda robot: robot.give_snack(),
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylitterbot"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pylitterbot==2025.1.0"]
|
||||
"requirements": ["pylitterbot==2025.0.0"]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Generic, TypeVar
|
||||
|
||||
from pylitterbot import FeederRobot, LitterRobot, LitterRobot4, LitterRobot5, Robot
|
||||
from pylitterbot import FeederRobot, LitterRobot, LitterRobot4, Robot
|
||||
from pylitterbot.robot.litterrobot4 import BrightnessLevel, NightLightMode
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
@@ -32,11 +32,9 @@ class RobotSelectEntityDescription(
|
||||
select_fn: Callable[[_WhiskerEntityT, str], Coroutine[Any, Any, bool]]
|
||||
|
||||
|
||||
ROBOT_SELECT_MAP: dict[
|
||||
type[Robot] | tuple[type[Robot], ...], tuple[RobotSelectEntityDescription, ...]
|
||||
] = {
|
||||
ROBOT_SELECT_MAP: dict[type[Robot], tuple[RobotSelectEntityDescription, ...]] = {
|
||||
LitterRobot: (
|
||||
RobotSelectEntityDescription[LitterRobot, int](
|
||||
RobotSelectEntityDescription[LitterRobot, int]( # type: ignore[type-abstract] # only used for isinstance check
|
||||
key="cycle_delay",
|
||||
translation_key="cycle_delay",
|
||||
unit_of_measurement=UnitOfTime.MINUTES,
|
||||
@@ -45,8 +43,8 @@ ROBOT_SELECT_MAP: dict[
|
||||
select_fn=lambda robot, opt: robot.set_wait_time(int(opt)),
|
||||
),
|
||||
),
|
||||
(LitterRobot4, LitterRobot5): (
|
||||
RobotSelectEntityDescription[LitterRobot4 | LitterRobot5, str](
|
||||
LitterRobot4: (
|
||||
RobotSelectEntityDescription[LitterRobot4, str](
|
||||
key="globe_brightness",
|
||||
translation_key="globe_brightness",
|
||||
current_fn=(
|
||||
@@ -63,7 +61,7 @@ ROBOT_SELECT_MAP: dict[
|
||||
)
|
||||
),
|
||||
),
|
||||
RobotSelectEntityDescription[LitterRobot4 | LitterRobot5, str](
|
||||
RobotSelectEntityDescription[LitterRobot4, str](
|
||||
key="globe_light",
|
||||
translation_key="globe_light",
|
||||
current_fn=(
|
||||
@@ -80,7 +78,7 @@ ROBOT_SELECT_MAP: dict[
|
||||
)
|
||||
),
|
||||
),
|
||||
RobotSelectEntityDescription[LitterRobot4 | LitterRobot5, str](
|
||||
RobotSelectEntityDescription[LitterRobot4, str](
|
||||
key="panel_brightness",
|
||||
translation_key="brightness_level",
|
||||
current_fn=(
|
||||
|
||||
@@ -7,7 +7,7 @@ from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, Generic
|
||||
|
||||
from pylitterbot import FeederRobot, LitterRobot, LitterRobot4, LitterRobot5, Pet, Robot
|
||||
from pylitterbot import FeederRobot, LitterRobot, LitterRobot4, Pet, Robot
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@@ -44,10 +44,8 @@ class RobotSensorEntityDescription(SensorEntityDescription, Generic[_WhiskerEnti
|
||||
value_fn: Callable[[_WhiskerEntityT], float | datetime | str | None]
|
||||
|
||||
|
||||
ROBOT_SENSOR_MAP: dict[
|
||||
type[Robot] | tuple[type[Robot], ...], list[RobotSensorEntityDescription]
|
||||
] = {
|
||||
LitterRobot: [
|
||||
ROBOT_SENSOR_MAP: dict[type[Robot], list[RobotSensorEntityDescription]] = {
|
||||
LitterRobot: [ # type: ignore[type-abstract] # only used for isinstance check
|
||||
RobotSensorEntityDescription[LitterRobot](
|
||||
key="waste_drawer_level",
|
||||
translation_key="waste_drawer",
|
||||
@@ -147,9 +145,7 @@ ROBOT_SENSOR_MAP: dict[
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
(LitterRobot4, LitterRobot5): [
|
||||
RobotSensorEntityDescription[LitterRobot4 | LitterRobot5](
|
||||
RobotSensorEntityDescription[LitterRobot4](
|
||||
key="litter_level",
|
||||
translation_key="litter_level",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
@@ -157,7 +153,7 @@ ROBOT_SENSOR_MAP: dict[
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda robot: robot.litter_level,
|
||||
),
|
||||
RobotSensorEntityDescription[LitterRobot4 | LitterRobot5](
|
||||
RobotSensorEntityDescription[LitterRobot4](
|
||||
key="pet_weight",
|
||||
translation_key="pet_weight",
|
||||
native_unit_of_measurement=UnitOfMass.POUNDS,
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["matrix_client"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["matrix-nio==0.25.2", "Pillow==12.1.1", "aiofiles==24.1.0"]
|
||||
"requirements": ["matrix-nio==0.25.2", "Pillow==12.0.0", "aiofiles==24.1.0"]
|
||||
}
|
||||
|
||||
@@ -498,7 +498,6 @@ DISCOVERY_SCHEMAS = [
|
||||
required_attributes=(
|
||||
custom_clusters.InovelliCluster.Attributes.LEDIndicatorIntensityOff,
|
||||
),
|
||||
product_id=(2, 16),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.NUMBER,
|
||||
@@ -515,7 +514,6 @@ DISCOVERY_SCHEMAS = [
|
||||
required_attributes=(
|
||||
custom_clusters.InovelliCluster.Attributes.LEDIndicatorIntensityOn,
|
||||
),
|
||||
product_id=(2, 16),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.NUMBER,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"addon_get_discovery_info_failed": "Failed to get Matter Server app discovery info.",
|
||||
"addon_info_failed": "Failed to get Matter Server app info.",
|
||||
"addon_install_failed": "Failed to install the Matter Server app.",
|
||||
"addon_start_failed": "Failed to start the Matter Server app.",
|
||||
"addon_get_discovery_info_failed": "Failed to get Matter Server add-on discovery info.",
|
||||
"addon_info_failed": "Failed to get Matter Server add-on info.",
|
||||
"addon_install_failed": "Failed to install the Matter Server add-on.",
|
||||
"addon_start_failed": "Failed to start the Matter Server add-on.",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"not_matter_addon": "Discovered app is not the official Matter Server app.",
|
||||
"not_matter_addon": "Discovered add-on is not the official Matter Server add-on.",
|
||||
"reconfiguration_successful": "Successfully reconfigured the Matter integration."
|
||||
},
|
||||
"error": {
|
||||
@@ -18,15 +18,15 @@
|
||||
},
|
||||
"flow_title": "{name}",
|
||||
"progress": {
|
||||
"install_addon": "Please wait while the Matter Server app installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the Matter Server app starts. This app is what powers Matter in Home Assistant. This may take some seconds."
|
||||
"install_addon": "Please wait while the Matter Server add-on installation finishes. This can take several minutes.",
|
||||
"start_addon": "Please wait while the Matter Server add-on starts. This add-on is what powers Matter in Home Assistant. This may take some seconds."
|
||||
},
|
||||
"step": {
|
||||
"hassio_confirm": {
|
||||
"title": "Set up the Matter integration with the Matter Server app"
|
||||
"title": "Set up the Matter integration with the Matter Server add-on"
|
||||
},
|
||||
"install_addon": {
|
||||
"title": "The app installation has started"
|
||||
"title": "The add-on installation has started"
|
||||
},
|
||||
"manual": {
|
||||
"data": {
|
||||
@@ -35,13 +35,13 @@
|
||||
},
|
||||
"on_supervisor": {
|
||||
"data": {
|
||||
"use_addon": "Use the official Matter Server Supervisor app"
|
||||
"use_addon": "Use the official Matter Server Supervisor add-on"
|
||||
},
|
||||
"description": "Do you want to use the official Matter Server Supervisor app?\n\nIf you are already running the Matter Server in another app, in a custom container, natively etc., then do not select this option.",
|
||||
"description": "Do you want to use the official Matter Server Supervisor add-on?\n\nIf you are already running the Matter Server in another add-on, in a custom container, natively etc., then do not select this option.",
|
||||
"title": "Select connection method"
|
||||
},
|
||||
"start_addon": {
|
||||
"title": "Starting app."
|
||||
"title": "Starting add-on."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
@@ -120,31 +119,6 @@ class NRGkickConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._discovered_name: str | None = None
|
||||
self._pending_host: str | None = None
|
||||
|
||||
async def _async_validate_credentials(
|
||||
self,
|
||||
host: str,
|
||||
errors: dict[str, str],
|
||||
username: str | None = None,
|
||||
password: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Validate credentials and populate errors dict on failure."""
|
||||
try:
|
||||
return await validate_input(
|
||||
self.hass, host, username=username, password=password
|
||||
)
|
||||
except NRGkickApiClientApiDisabledError:
|
||||
errors["base"] = "json_api_disabled"
|
||||
except NRGkickApiClientAuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except NRGkickApiClientInvalidResponseError:
|
||||
errors["base"] = "invalid_response"
|
||||
except NRGkickApiClientCommunicationError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except NRGkickApiClientError:
|
||||
_LOGGER.exception("Unexpected error")
|
||||
errors["base"] = "unknown"
|
||||
return None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
@@ -195,20 +169,36 @@ class NRGkickConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
assert self._pending_host is not None
|
||||
|
||||
if user_input is not None:
|
||||
if info := await self._async_validate_credentials(
|
||||
self._pending_host,
|
||||
errors,
|
||||
username=user_input[CONF_USERNAME],
|
||||
password=user_input[CONF_PASSWORD],
|
||||
):
|
||||
username = user_input.get(CONF_USERNAME)
|
||||
password = user_input.get(CONF_PASSWORD)
|
||||
|
||||
try:
|
||||
info = await validate_input(
|
||||
self.hass,
|
||||
self._pending_host,
|
||||
username=username,
|
||||
password=password,
|
||||
)
|
||||
except NRGkickApiClientApiDisabledError:
|
||||
errors["base"] = "json_api_disabled"
|
||||
except NRGkickApiClientAuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except NRGkickApiClientInvalidResponseError:
|
||||
errors["base"] = "invalid_response"
|
||||
except NRGkickApiClientCommunicationError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except NRGkickApiClientError:
|
||||
_LOGGER.exception("Unexpected error")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
await self.async_set_unique_id(info["serial"], raise_on_progress=False)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(
|
||||
title=info["title"],
|
||||
data={
|
||||
CONF_HOST: self._pending_host,
|
||||
CONF_USERNAME: user_input[CONF_USERNAME],
|
||||
CONF_PASSWORD: user_input[CONF_PASSWORD],
|
||||
CONF_USERNAME: username,
|
||||
CONF_PASSWORD: password,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -221,42 +211,6 @@ class NRGkickConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
},
|
||||
)
|
||||
|
||||
async def async_step_reauth(
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle initiation of reauthentication."""
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle reauthentication."""
|
||||
errors: dict[str, str] = {}
|
||||
|
||||
if user_input is not None:
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
if info := await self._async_validate_credentials(
|
||||
reauth_entry.data[CONF_HOST],
|
||||
errors,
|
||||
username=user_input[CONF_USERNAME],
|
||||
password=user_input[CONF_PASSWORD],
|
||||
):
|
||||
await self.async_set_unique_id(info["serial"], raise_on_progress=False)
|
||||
self._abort_if_unique_id_mismatch()
|
||||
return self.async_update_reload_and_abort(
|
||||
reauth_entry,
|
||||
data_updates=user_input,
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="reauth_confirm",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
STEP_AUTH_DATA_SCHEMA,
|
||||
self._get_reauth_entry().data,
|
||||
),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_zeroconf(
|
||||
self, discovery_info: ZeroconfServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
@@ -281,9 +235,8 @@ class NRGkickConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
# Store discovery info for the confirmation step.
|
||||
self._discovered_host = discovery_info.host
|
||||
# Fallback: device_name -> model_type -> "NRGkick".
|
||||
discovered_name = device_name or model_type or "NRGkick"
|
||||
self._discovered_name = discovered_name
|
||||
self.context["title_placeholders"] = {"name": discovered_name}
|
||||
self._discovered_name = device_name or model_type or "NRGkick"
|
||||
self.context["title_placeholders"] = {"name": self._discovered_name}
|
||||
|
||||
# If JSON API is disabled, guide the user through enabling it.
|
||||
if json_api_enabled != "1":
|
||||
|
||||
@@ -18,7 +18,7 @@ from nrgkick_api import (
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryError
|
||||
from homeassistant.exceptions import ConfigEntryError
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DEFAULT_SCAN_INTERVAL, DOMAIN
|
||||
@@ -65,7 +65,7 @@ class NRGkickDataUpdateCoordinator(DataUpdateCoordinator[NRGkickData]):
|
||||
control = await self.api.get_control()
|
||||
values = await self.api.get_values(raw=True)
|
||||
except NRGkickAuthenticationError as error:
|
||||
raise ConfigEntryAuthFailed(
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="authentication_error",
|
||||
) from error
|
||||
|
||||
@@ -43,7 +43,7 @@ rules:
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
reauthentication-flow: todo
|
||||
test-coverage: done
|
||||
|
||||
# Gold
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||
"json_api_disabled": "JSON API is disabled on the device. Enable it in the NRGkick mobile app under Extended \u2192 Local API \u2192 API Variants.",
|
||||
"no_serial_number": "Device does not provide a serial number",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"unique_id_mismatch": "The device does not match the previous device"
|
||||
"no_serial_number": "Device does not provide a serial number"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
@@ -17,17 +15,6 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"username": "[%key:common::config_flow::data::username%]"
|
||||
},
|
||||
"data_description": {
|
||||
"password": "[%key:component::nrgkick::config::step::user_auth::data_description::password%]",
|
||||
"username": "[%key:component::nrgkick::config::step::user_auth::data_description::username%]"
|
||||
},
|
||||
"description": "Reauthenticate with your NRGkick device.\n\nGet your username and password in the NRGkick mobile app:\n1. Open the NRGkick mobile app \u2192 Extended \u2192 Local API\n2. Under Authentication (JSON), check or set your username and password"
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "[%key:common::config_flow::data::host%]"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/ntfy",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aiontfy"],
|
||||
"loggers": ["aionfty"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["aiontfy==0.8.0"]
|
||||
"requirements": ["aiontfy==0.7.0"]
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ class Luminary(LightEntity):
|
||||
return self._luminary.name()
|
||||
|
||||
@property
|
||||
def hs_color(self) -> tuple[float, float]:
|
||||
def hs_color(self):
|
||||
"""Return last hs color value set."""
|
||||
return color_util.color_RGB_to_hs(*self._rgb_color)
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class PilightLight(PilightBaseDevice, LightEntity):
|
||||
self._dimlevel_max = config.get(CONF_DIMLEVEL_MAX)
|
||||
|
||||
@property
|
||||
def brightness(self) -> int | None:
|
||||
def brightness(self):
|
||||
"""Return the brightness."""
|
||||
return self._brightness
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pyportainer==1.0.28"]
|
||||
"requirements": ["pyportainer==1.0.23"]
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ class ProxmoxCoordinator(DataUpdateCoordinator[dict[str, ProxmoxNodeData]]):
|
||||
list[dict[str, Any]], list[tuple[list[dict[str, Any]], list[dict[str, Any]]]]
|
||||
]:
|
||||
"""Fetch all nodes, and then proceed to the VMs and containers."""
|
||||
nodes = self.proxmox.nodes.get() or []
|
||||
nodes = self.proxmox.nodes.get()
|
||||
vms_containers = [self._get_vms_containers(node) for node in nodes]
|
||||
return nodes, vms_containers
|
||||
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/proxy",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.1.1"]
|
||||
"requirements": ["Pillow==12.0.0"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "calculated",
|
||||
"loggers": ["pyzbar"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.1.1", "pyzbar==0.1.7"]
|
||||
"requirements": ["Pillow==12.0.0", "pyzbar==0.1.7"]
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class QSLight(QSToggleEntity, LightEntity):
|
||||
"""Light based on a Qwikswitch relay/dimmer module."""
|
||||
|
||||
@property
|
||||
def brightness(self) -> int | None:
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light (0-255)."""
|
||||
return self.device.value if self.device.is_dimmer else None
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"title": "Database backup failed due to lack of resources"
|
||||
},
|
||||
"maria_db_range_index_regression": {
|
||||
"description": "Older versions of MariaDB suffer from a significant performance regression when retrieving history data or purging the database. Update to MariaDB version {min_version} or later and restart Home Assistant. If you are using the MariaDB Core app, make sure to update it to the latest version.",
|
||||
"description": "Older versions of MariaDB suffer from a significant performance regression when retrieving history data or purging the database. Update to MariaDB version {min_version} or later and restart Home Assistant. If you are using the MariaDB core add-on, make sure to update it to the latest version.",
|
||||
"title": "Update MariaDB to {min_version} or later resolve a significant performance issue"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["renault_api"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["renault-api==0.5.5"]
|
||||
"requirements": ["renault-api==0.5.3"]
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ class DimmableRflinkLight(SwitchableRflinkDevice, LightEntity):
|
||||
self._state = True
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._brightness
|
||||
|
||||
|
||||
@@ -144,18 +144,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
||||
for coord in coordinators
|
||||
if isinstance(coord, RoborockDataUpdateCoordinatorA01)
|
||||
]
|
||||
b01_q7_coords = [
|
||||
b01_coords = [
|
||||
coord
|
||||
for coord in coordinators
|
||||
if isinstance(coord, RoborockB01Q7UpdateCoordinator)
|
||||
if isinstance(coord, RoborockDataUpdateCoordinatorB01)
|
||||
]
|
||||
if len(v1_coords) + len(a01_coords) + len(b01_q7_coords) == 0:
|
||||
if len(v1_coords) + len(a01_coords) + len(b01_coords) == 0:
|
||||
raise ConfigEntryNotReady(
|
||||
"No devices were able to successfully setup",
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_coordinators",
|
||||
)
|
||||
entry.runtime_data = RoborockCoordinators(v1_coords, a01_coords, b01_q7_coords)
|
||||
entry.runtime_data = RoborockCoordinators(v1_coords, a01_coords, b01_coords)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
|
||||
@@ -64,17 +64,17 @@ class RoborockCoordinators:
|
||||
|
||||
v1: list[RoborockDataUpdateCoordinator]
|
||||
a01: list[RoborockDataUpdateCoordinatorA01]
|
||||
b01_q7: list[RoborockB01Q7UpdateCoordinator]
|
||||
b01: list[RoborockDataUpdateCoordinatorB01]
|
||||
|
||||
def values(
|
||||
self,
|
||||
) -> list[
|
||||
RoborockDataUpdateCoordinator
|
||||
| RoborockDataUpdateCoordinatorA01
|
||||
| RoborockB01Q7UpdateCoordinator
|
||||
| RoborockDataUpdateCoordinatorB01
|
||||
]:
|
||||
"""Return all coordinators."""
|
||||
return self.v1 + self.a01 + self.b01_q7
|
||||
return self.v1 + self.a01 + self.b01
|
||||
|
||||
|
||||
type RoborockConfigEntry = ConfigEntry[RoborockCoordinators]
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from roborock.data import Status
|
||||
from roborock.devices.traits.v1.command import CommandTrait
|
||||
from roborock.devices.traits.v1.status import StatusTrait
|
||||
from roborock.exceptions import RoborockException
|
||||
from roborock.roborock_typing import RoborockCommand
|
||||
|
||||
@@ -14,9 +14,9 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import (
|
||||
RoborockB01Q7UpdateCoordinator,
|
||||
RoborockDataUpdateCoordinator,
|
||||
RoborockDataUpdateCoordinatorA01,
|
||||
RoborockDataUpdateCoordinatorB01,
|
||||
)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class RoborockCoordinatedEntityV1(
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@property
|
||||
def _device_status(self) -> StatusTrait:
|
||||
def _device_status(self) -> Status:
|
||||
"""Return the status of the device."""
|
||||
data = self.coordinator.data
|
||||
return data.status
|
||||
@@ -130,21 +130,21 @@ class RoborockCoordinatedEntityA01(
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
|
||||
class RoborockCoordinatedEntityB01Q7(
|
||||
RoborockEntity, CoordinatorEntity[RoborockB01Q7UpdateCoordinator]
|
||||
class RoborockCoordinatedEntityB01(
|
||||
RoborockEntity, CoordinatorEntity[RoborockDataUpdateCoordinatorB01]
|
||||
):
|
||||
"""Representation of coordinated Roborock Entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
unique_id: str,
|
||||
coordinator: RoborockB01Q7UpdateCoordinator,
|
||||
coordinator: RoborockDataUpdateCoordinatorB01,
|
||||
) -> None:
|
||||
"""Initialize the coordinated Roborock Device."""
|
||||
CoordinatorEntity.__init__(self, coordinator=coordinator)
|
||||
RoborockEntity.__init__(
|
||||
self,
|
||||
unique_id=unique_id,
|
||||
device_info=coordinator.device_info,
|
||||
)
|
||||
CoordinatorEntity.__init__(self, coordinator=coordinator)
|
||||
self._attr_unique_id = unique_id
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"loggers": ["roborock"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": [
|
||||
"python-roborock==4.17.1",
|
||||
"python-roborock==4.14.0",
|
||||
"vacuum-map-parser-roborock==0.1.4"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ from roborock.data import (
|
||||
HomeDataDevice,
|
||||
HomeDataProduct,
|
||||
NetworkInfo,
|
||||
Status,
|
||||
)
|
||||
from roborock.devices.traits.v1.status import StatusTrait
|
||||
from vacuum_map_parser_base.map_data import MapData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -23,7 +23,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class DeviceState:
|
||||
"""Data about the current state of a device."""
|
||||
|
||||
status: StatusTrait
|
||||
status: Status
|
||||
dnd_timer: DnDTimer
|
||||
consumable: Consumable
|
||||
clean_summary: CleanSummaryWithDetail
|
||||
|
||||
@@ -26,7 +26,7 @@ from .coordinator import (
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
)
|
||||
from .entity import RoborockCoordinatedEntityB01Q7, RoborockCoordinatedEntityV1
|
||||
from .entity import RoborockCoordinatedEntityB01, RoborockCoordinatedEntityV1
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -92,31 +92,25 @@ SELECT_DESCRIPTIONS: list[RoborockSelectDescription] = [
|
||||
key="water_box_mode",
|
||||
translation_key="mop_intensity",
|
||||
api_command=RoborockCommand.SET_WATER_BOX_CUSTOM_MODE,
|
||||
value_fn=lambda api: api.status.water_mode_name,
|
||||
value_fn=lambda api: api.status.water_box_mode_name,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
options_lambda=lambda api: (
|
||||
[mode.value for mode in api.status.water_mode_options]
|
||||
if api.status.water_mode_options
|
||||
api.status.water_box_mode.keys()
|
||||
if api.status.water_box_mode is not None
|
||||
else None
|
||||
),
|
||||
parameter_lambda=lambda key, api: [
|
||||
{v: k for k, v in api.status.water_mode_mapping.items()}[key]
|
||||
],
|
||||
parameter_lambda=lambda key, api: [api.status.get_mop_intensity_code(key)],
|
||||
),
|
||||
RoborockSelectDescription(
|
||||
key="mop_mode",
|
||||
translation_key="mop_mode",
|
||||
api_command=RoborockCommand.SET_MOP_MODE,
|
||||
value_fn=lambda api: api.status.mop_route_name,
|
||||
value_fn=lambda api: api.status.mop_mode_name,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
options_lambda=lambda api: (
|
||||
[mode.value for mode in api.status.mop_route_options]
|
||||
if api.status.mop_route_options
|
||||
else None
|
||||
api.status.mop_mode.keys() if api.status.mop_mode is not None else None
|
||||
),
|
||||
parameter_lambda=lambda key, api: [
|
||||
{v: k for k, v in api.status.mop_route_mapping.items()}[key]
|
||||
],
|
||||
parameter_lambda=lambda key, api: [api.status.get_mop_mode_code(key)],
|
||||
),
|
||||
RoborockSelectDescription(
|
||||
key="dust_collection_mode",
|
||||
@@ -165,13 +159,14 @@ async def async_setup_entry(
|
||||
)
|
||||
async_add_entities(
|
||||
RoborockB01SelectEntity(coordinator, description, options)
|
||||
for coordinator in config_entry.runtime_data.b01_q7
|
||||
for coordinator in config_entry.runtime_data.b01
|
||||
for description in B01_SELECT_DESCRIPTIONS
|
||||
if isinstance(coordinator, RoborockB01Q7UpdateCoordinator)
|
||||
if (options := description.options_lambda(coordinator.api)) is not None
|
||||
)
|
||||
|
||||
|
||||
class RoborockB01SelectEntity(RoborockCoordinatedEntityB01Q7, SelectEntity):
|
||||
class RoborockB01SelectEntity(RoborockCoordinatedEntityB01, SelectEntity):
|
||||
"""Select entity for Roborock B01 devices."""
|
||||
|
||||
entity_description: RoborockB01SelectDescription
|
||||
|
||||
@@ -33,14 +33,14 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from .coordinator import (
|
||||
RoborockB01Q7UpdateCoordinator,
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
RoborockDataUpdateCoordinatorA01,
|
||||
RoborockDataUpdateCoordinatorB01,
|
||||
)
|
||||
from .entity import (
|
||||
RoborockCoordinatedEntityA01,
|
||||
RoborockCoordinatedEntityB01Q7,
|
||||
RoborockCoordinatedEntityB01,
|
||||
RoborockCoordinatedEntityV1,
|
||||
RoborockEntity,
|
||||
)
|
||||
@@ -422,8 +422,8 @@ async def async_setup_entry(
|
||||
if description.data_protocol in coordinator.request_protocols
|
||||
)
|
||||
entities.extend(
|
||||
RoborockSensorEntityB01Q7(coordinator, description)
|
||||
for coordinator in coordinators.b01_q7
|
||||
RoborockSensorEntityB01(coordinator, description)
|
||||
for coordinator in coordinators.b01
|
||||
for description in Q7_B01_SENSOR_DESCRIPTIONS
|
||||
if description.value_fn(coordinator.data) is not None
|
||||
)
|
||||
@@ -515,14 +515,14 @@ class RoborockSensorEntityA01(RoborockCoordinatedEntityA01, SensorEntity):
|
||||
return self.coordinator.data[self.entity_description.data_protocol]
|
||||
|
||||
|
||||
class RoborockSensorEntityB01Q7(RoborockCoordinatedEntityB01Q7, SensorEntity):
|
||||
"""Representation of a B01 Q7 Roborock sensor."""
|
||||
class RoborockSensorEntityB01(RoborockCoordinatedEntityB01, SensorEntity):
|
||||
"""Representation of a B01 Roborock sensor."""
|
||||
|
||||
entity_description: RoborockSensorDescriptionB01
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: RoborockB01Q7UpdateCoordinator,
|
||||
coordinator: RoborockDataUpdateCoordinatorB01,
|
||||
description: RoborockSensorDescriptionB01,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
|
||||
@@ -118,12 +118,9 @@
|
||||
"max": "Max",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"mild": "Mild",
|
||||
"min": "Min",
|
||||
"moderate": "Moderate",
|
||||
"off": "[%key:common::state::off%]",
|
||||
"slight": "Slight",
|
||||
"smart_mode": "[%key:component::roborock::entity::select::mop_mode::state::smart_mode%]",
|
||||
"standard": "[%key:component::roborock::entity::select::mop_mode::state::standard%]",
|
||||
"vac_followed_by_mop": "Vacuum followed by mop"
|
||||
}
|
||||
},
|
||||
@@ -451,7 +448,6 @@
|
||||
"max_plus": "Max plus",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"off": "[%key:common::state::off%]",
|
||||
"off_raise_main_brush": "Off (raised brush)",
|
||||
"quiet": "Quiet",
|
||||
"silent": "Silent",
|
||||
"smart_mode": "[%key:component::roborock::entity::select::mop_mode::state::smart_mode%]",
|
||||
@@ -482,9 +478,6 @@
|
||||
"mqtt_unauthorized": {
|
||||
"message": "Roborock MQTT servers rejected the connection due to rate limiting or invalid credentials. You may either attempt to reauthenticate or wait and reload the integration."
|
||||
},
|
||||
"multiple_maps_in_clean": {
|
||||
"message": "All segments must belong to the same map. Got segments from maps: {map_flags}"
|
||||
},
|
||||
"no_coordinators": {
|
||||
"message": "No devices were able to successfully setup"
|
||||
},
|
||||
@@ -494,9 +487,6 @@
|
||||
"position_not_found": {
|
||||
"message": "Robot position not found"
|
||||
},
|
||||
"segment_id_parse_error": {
|
||||
"message": "Invalid segment ID format: {segment_id}"
|
||||
},
|
||||
"update_data_fail": {
|
||||
"message": "Failed to update data"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Support for Roborock vacuum class."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
@@ -9,22 +8,21 @@ from roborock.exceptions import RoborockException
|
||||
from roborock.roborock_typing import RoborockCommand
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
Segment,
|
||||
StateVacuumEntity,
|
||||
VacuumActivity,
|
||||
VacuumEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceResponse
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, MAP_SLEEP
|
||||
from .const import DOMAIN
|
||||
from .coordinator import (
|
||||
RoborockB01Q7UpdateCoordinator,
|
||||
RoborockConfigEntry,
|
||||
RoborockDataUpdateCoordinator,
|
||||
)
|
||||
from .entity import RoborockCoordinatedEntityB01Q7, RoborockCoordinatedEntityV1
|
||||
from .entity import RoborockCoordinatedEntityB01, RoborockCoordinatedEntityV1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -84,7 +82,8 @@ async def async_setup_entry(
|
||||
)
|
||||
async_add_entities(
|
||||
RoborockQ7Vacuum(coordinator)
|
||||
for coordinator in config_entry.runtime_data.b01_q7
|
||||
for coordinator in config_entry.runtime_data.b01
|
||||
if isinstance(coordinator, RoborockB01Q7UpdateCoordinator)
|
||||
)
|
||||
|
||||
|
||||
@@ -102,7 +101,6 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity):
|
||||
| VacuumEntityFeature.CLEAN_SPOT
|
||||
| VacuumEntityFeature.STATE
|
||||
| VacuumEntityFeature.START
|
||||
| VacuumEntityFeature.CLEAN_AREA
|
||||
)
|
||||
_attr_translation_key = DOMAIN
|
||||
_attr_name = None
|
||||
@@ -118,13 +116,11 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity):
|
||||
coordinator.duid_slug,
|
||||
coordinator,
|
||||
)
|
||||
self._home_trait = coordinator.properties_api.home
|
||||
self._maps_trait = coordinator.properties_api.maps
|
||||
|
||||
@property
|
||||
def fan_speed_list(self) -> list[str]:
|
||||
"""Get the list of available fan speeds."""
|
||||
return [mode.value for mode in self._device_status.fan_speed_options]
|
||||
return self._device_status.fan_power_options
|
||||
|
||||
@property
|
||||
def activity(self) -> VacuumActivity | None:
|
||||
@@ -135,7 +131,7 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity):
|
||||
@property
|
||||
def fan_speed(self) -> str | None:
|
||||
"""Return the fan speed of the vacuum cleaner."""
|
||||
return self._device_status.fan_speed_name
|
||||
return self._device_status.fan_power_name
|
||||
|
||||
async def async_start(self) -> None:
|
||||
"""Start the vacuum."""
|
||||
@@ -174,83 +170,13 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity):
|
||||
"""Set vacuum fan speed."""
|
||||
await self.send(
|
||||
RoborockCommand.SET_CUSTOM_MODE,
|
||||
[
|
||||
{v: k for k, v in self._device_status.fan_speed_mapping.items()}[
|
||||
fan_speed
|
||||
]
|
||||
],
|
||||
[self._device_status.get_fan_speed_code(fan_speed)],
|
||||
)
|
||||
|
||||
async def async_set_vacuum_goto_position(self, x: int, y: int) -> None:
|
||||
"""Send vacuum to a specific target point."""
|
||||
await self.send(RoborockCommand.APP_GOTO_TARGET, [x, y])
|
||||
|
||||
async def async_get_segments(self) -> list[Segment]:
|
||||
"""Get the segments that can be cleaned."""
|
||||
home_map_info = self._home_trait.home_map_info
|
||||
if not home_map_info:
|
||||
return []
|
||||
return [
|
||||
Segment(
|
||||
id=f"{map_flag}:{room.segment_id}",
|
||||
name=room.name,
|
||||
group=map_info.name,
|
||||
)
|
||||
for map_flag, map_info in home_map_info.items()
|
||||
for room in map_info.rooms
|
||||
]
|
||||
|
||||
async def async_clean_segments(self, segment_ids: list[str], **kwargs: Any) -> None:
|
||||
"""Clean the specified segments."""
|
||||
parsed: list[tuple[int, int]] = []
|
||||
for seg_id in segment_ids:
|
||||
# Segment id is mapflag:segment_id
|
||||
parts = seg_id.split(":")
|
||||
if len(parts) != 2:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="segment_id_parse_error",
|
||||
translation_placeholders={"segment_id": seg_id},
|
||||
)
|
||||
try:
|
||||
# We need to make sure both parts are ints.
|
||||
parsed.append((int(parts[0]), int(parts[1])))
|
||||
except ValueError as err:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="segment_id_parse_error",
|
||||
translation_placeholders={"segment_id": seg_id},
|
||||
) from err
|
||||
|
||||
# Because segment_ids can overlap for each map,
|
||||
# we need to make sure that only one map is passed in.
|
||||
unique_map_flags = {map_flag for map_flag, _ in parsed}
|
||||
if len(unique_map_flags) > 1:
|
||||
map_flags_str = ", ".join(str(flag) for flag in sorted(unique_map_flags))
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="multiple_maps_in_clean",
|
||||
translation_placeholders={"map_flags": map_flags_str},
|
||||
)
|
||||
target_map_flag = next(iter(unique_map_flags))
|
||||
if self._maps_trait.current_map != target_map_flag:
|
||||
# If the user is attempting to clean an area on a map that is not selected, we should try to change.
|
||||
try:
|
||||
await self._maps_trait.set_current_map(target_map_flag)
|
||||
except RoborockException as err:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="command_failed",
|
||||
translation_placeholders={"command": "load_multi_map"},
|
||||
) from err
|
||||
await asyncio.sleep(MAP_SLEEP)
|
||||
|
||||
# We can now confirm all segments are on our current map, so clean them all.
|
||||
await self.send(
|
||||
RoborockCommand.APP_SEGMENT_CLEAN,
|
||||
[{"segments": [seg_id for _, seg_id in parsed]}],
|
||||
)
|
||||
|
||||
async def async_send_command(
|
||||
self,
|
||||
command: str,
|
||||
@@ -306,7 +232,7 @@ class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity):
|
||||
}
|
||||
|
||||
|
||||
class RoborockQ7Vacuum(RoborockCoordinatedEntityB01Q7, StateVacuumEntity):
|
||||
class RoborockQ7Vacuum(RoborockCoordinatedEntityB01, StateVacuumEntity):
|
||||
"""General Representation of a Roborock vacuum."""
|
||||
|
||||
_attr_icon = "mdi:robot-vacuum"
|
||||
@@ -330,7 +256,7 @@ class RoborockQ7Vacuum(RoborockCoordinatedEntityB01Q7, StateVacuumEntity):
|
||||
) -> None:
|
||||
"""Initialize a vacuum."""
|
||||
StateVacuumEntity.__init__(self)
|
||||
RoborockCoordinatedEntityB01Q7.__init__(
|
||||
RoborockCoordinatedEntityB01.__init__(
|
||||
self,
|
||||
coordinator.duid_slug,
|
||||
coordinator,
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/seven_segments",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.1.1"]
|
||||
"requirements": ["Pillow==12.0.0"]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["simplehound"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["Pillow==12.1.1", "simplehound==0.3"]
|
||||
"requirements": ["Pillow==12.0.0", "simplehound==0.3"]
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class SisyphusLight(LightEntity):
|
||||
return not self._table.is_sleeping
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self):
|
||||
"""Return the current brightness of the table's ring light."""
|
||||
return self._table.brightness * 255
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class SmartTubLight(SmartTubEntity, LightEntity):
|
||||
return self.coordinator.data[self.spa.id][ATTR_LIGHTS][self.light_zone]
|
||||
|
||||
@property
|
||||
def brightness(self) -> int:
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
|
||||
# SmartTub intensity is 0..100
|
||||
@@ -87,7 +87,7 @@ class SmartTubLight(SmartTubEntity, LightEntity):
|
||||
return self.light.mode != SpaLight.LightMode.OFF
|
||||
|
||||
@property
|
||||
def effect(self) -> str | None:
|
||||
def effect(self):
|
||||
"""Return the current effect."""
|
||||
mode = self.light.mode.name.lower()
|
||||
if mode in self.effect_list:
|
||||
@@ -95,7 +95,7 @@ class SmartTubLight(SmartTubEntity, LightEntity):
|
||||
return None
|
||||
|
||||
@property
|
||||
def effect_list(self) -> list[str]:
|
||||
def effect_list(self):
|
||||
"""Return the list of supported effects."""
|
||||
return [
|
||||
effect
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@Lash-L"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/snoo",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["snoo"],
|
||||
"quality_scale": "bronze",
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"config_flow": true,
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/snooz",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["pysnooz==0.8.6"]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@squishykid", "@Darsstar"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/solax",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["solax"],
|
||||
"requirements": ["solax==3.2.3"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@ratsept"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/soma",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["api"],
|
||||
"requirements": ["pysoma==0.0.12"]
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/somfy_mylink",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "assumed_state",
|
||||
"loggers": ["somfy_mylink_synergy"],
|
||||
"requirements": ["somfy-mylink-synergy==1.0.6"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@ctalkington"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/sonarr",
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiopyarr"],
|
||||
"requirements": ["aiopyarr==23.4.0"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@rytilahti", "@shenxn"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/songpal",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["songpal"],
|
||||
"requirements": ["python-songpal==0.16.2"],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@kroimon"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/soundtouch",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["libsoundtouch"],
|
||||
"requirements": ["libsoundtouch==0.8"],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@Bre77"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/splunk",
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["hass_splunk"],
|
||||
"quality_scale": "legacy",
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/sql",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["SQLAlchemy==2.0.41", "sqlparse==0.5.5"]
|
||||
"requirements": ["SQLAlchemy==2.0.41", "sqlparse==0.5.0"]
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ def check_and_render_sql_query(hass: HomeAssistant, query: Template | str) -> st
|
||||
raise MultipleQueryError("Multiple SQL statements are not allowed")
|
||||
if (
|
||||
len(rendered_queries) == 0
|
||||
or (query_type := rendered_queries[0].get_type()) == "UNKNOWN" # type: ignore[no-untyped-call]
|
||||
or (query_type := rendered_queries[0].get_type()) == "UNKNOWN"
|
||||
):
|
||||
raise UnknownQueryTypeError("SQL query is empty or unknown type")
|
||||
if query_type != "SELECT":
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@briglx"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/srp_energy",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["srpenergy"],
|
||||
"requirements": ["srpenergy==1.3.6"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@anonym-tsk"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/starline",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["starline"],
|
||||
"requirements": ["starline==0.1.5"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@boswelja"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/starlink",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["starlink-grpc-core==1.2.3"]
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/steamist",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiosteamist", "discovery30303"],
|
||||
"requirements": ["aiosteamist==1.0.1", "discovery30303==0.3.3"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@fucm", "@ThyMYthOS"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/stiebel_eltron",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pymodbus", "pystiebeleltron"],
|
||||
"requirements": ["pystiebeleltron==0.2.5"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": [],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/streamlabswater",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["streamlabswater"],
|
||||
"requirements": ["streamlabswater==1.0.1"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@G-Two"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/subaru",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["stdiomask", "subarulink"],
|
||||
"requirements": ["subarulink==0.7.15"]
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"codeowners": ["@ooii", "@jb101010-2"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/suez_water",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pysuez", "regex"],
|
||||
"quality_scale": "bronze",
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/sunricher_dali",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["PySrDaliGateway==0.19.3"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@benleb", "@danielhiversen"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/surepetcare",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["rich", "surepy"],
|
||||
"requirements": ["surepy==0.9.0"]
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import Any
|
||||
|
||||
from swisshydrodata import SwissHydroData
|
||||
import voluptuous as vol
|
||||
@@ -67,8 +67,8 @@ def setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Swiss hydrological sensor."""
|
||||
station: int = config[CONF_STATION]
|
||||
monitored_conditions: list[str] = config[CONF_MONITORED_CONDITIONS]
|
||||
station = config[CONF_STATION]
|
||||
monitored_conditions = config[CONF_MONITORED_CONDITIONS]
|
||||
|
||||
hydro_data = HydrologicalData(station)
|
||||
hydro_data.update()
|
||||
@@ -93,24 +93,38 @@ class SwissHydrologicalDataSensor(SensorEntity):
|
||||
"Data provided by the Swiss Federal Office for the Environment FOEN"
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self, hydro_data: HydrologicalData, station: int, condition: str
|
||||
) -> None:
|
||||
def __init__(self, hydro_data, station, condition):
|
||||
"""Initialize the Swiss hydrological sensor."""
|
||||
self.hydro_data = hydro_data
|
||||
data = hydro_data.data
|
||||
if TYPE_CHECKING:
|
||||
# Setup will fail in setup_platform if the data is None.
|
||||
assert data is not None
|
||||
|
||||
self._condition = condition
|
||||
self._data: dict[str, Any] | None = data
|
||||
self._attr_icon = CONDITIONS[condition]
|
||||
self._attr_name = f"{data['water-body-name']} {condition}"
|
||||
self._attr_native_unit_of_measurement = data["parameters"][condition]["unit"]
|
||||
self._attr_unique_id = f"{station}_{condition}"
|
||||
self._data = self._state = self._unit_of_measurement = None
|
||||
self._icon = CONDITIONS[condition]
|
||||
self._station = station
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return f"{self._data['water-body-name']} {self._condition}"
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique, friendly identifier for this entity."""
|
||||
return f"{self._station}_{self._condition}"
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity, if any."""
|
||||
if self._state is not None:
|
||||
return self.hydro_data.data["parameters"][self._condition]["unit"]
|
||||
return None
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
if isinstance(self._state, (int, float)):
|
||||
return round(self._state, 2)
|
||||
return None
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the device state attributes."""
|
||||
@@ -132,28 +146,32 @@ class SwissHydrologicalDataSensor(SensorEntity):
|
||||
|
||||
return attrs
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Icon to use in the frontend."""
|
||||
return self._icon
|
||||
|
||||
def update(self) -> None:
|
||||
"""Get the latest data and update the state."""
|
||||
self.hydro_data.update()
|
||||
self._data = self.hydro_data.data
|
||||
|
||||
self._attr_native_value = None
|
||||
if self._data is not None:
|
||||
state = self._data["parameters"][self._condition]["value"]
|
||||
if isinstance(state, (int, float)):
|
||||
self._attr_native_value = round(state, 2)
|
||||
if self._data is None:
|
||||
self._state = None
|
||||
else:
|
||||
self._state = self._data["parameters"][self._condition]["value"]
|
||||
|
||||
|
||||
class HydrologicalData:
|
||||
"""The Class for handling the data retrieval."""
|
||||
|
||||
def __init__(self, station: int) -> None:
|
||||
def __init__(self, station):
|
||||
"""Initialize the data object."""
|
||||
self.station = station
|
||||
self.data: dict[str, Any] | None = None
|
||||
self.data = None
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self) -> None:
|
||||
def update(self):
|
||||
"""Get the latest data."""
|
||||
|
||||
shd = SwissHydroData()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@fabaff", "@miaucl"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/swiss_public_transport",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["opendata_transport"],
|
||||
"requirements": ["python-opendata-transport==0.5.0"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@jafar-atili"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/switchbee",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["pyswitchbee==1.8.3"]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@thecode", "@YogevBokobza"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/switcher_kis",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aioswitcher"],
|
||||
"quality_scale": "silver",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@zhulik"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/syncthing",
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiosyncthing"],
|
||||
"requirements": ["aiosyncthing==0.7.1"]
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@nielstron"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/syncthru",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pysyncthru"],
|
||||
"requirements": ["PySyncThru==0.8.0", "url-normalize==2.2.1"],
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["systembridgeconnector"],
|
||||
"requirements": ["systembridgeconnector==5.4.3"],
|
||||
"requirements": ["systembridgeconnector==5.3.1"],
|
||||
"zeroconf": ["_system-bridge._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"codeowners": ["@Guy293"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/tami4",
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["Tami4EdgeAPI==3.0"]
|
||||
}
|
||||
|
||||
@@ -71,9 +71,9 @@ from .const import (
|
||||
ATTR_KEYBOARD_INLINE,
|
||||
ATTR_MEDIA_TYPE,
|
||||
ATTR_MESSAGE,
|
||||
ATTR_MESSAGE_ID,
|
||||
ATTR_MESSAGE_TAG,
|
||||
ATTR_MESSAGE_THREAD_ID,
|
||||
ATTR_MESSAGEID,
|
||||
ATTR_ONE_TIME_KEYBOARD,
|
||||
ATTR_OPEN_PERIOD,
|
||||
ATTR_OPTIONS,
|
||||
@@ -264,7 +264,7 @@ SERVICE_SCHEMA_EDIT_MESSAGE = vol.All(
|
||||
vol.Optional(CONF_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Optional(ATTR_TITLE): cv.string,
|
||||
vol.Required(ATTR_MESSAGE): cv.string,
|
||||
vol.Required(ATTR_MESSAGE_ID): vol.Any(
|
||||
vol.Required(ATTR_MESSAGEID): vol.Any(
|
||||
cv.positive_int, vol.All(cv.string, "last")
|
||||
),
|
||||
vol.Optional(ATTR_CHAT_ID): vol.Coerce(int),
|
||||
@@ -281,7 +281,7 @@ SERVICE_SCHEMA_EDIT_MESSAGE_MEDIA = vol.All(
|
||||
{
|
||||
vol.Optional(ATTR_ENTITY_ID): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Required(ATTR_MESSAGE_ID): vol.Any(
|
||||
vol.Required(ATTR_MESSAGEID): vol.Any(
|
||||
cv.positive_int, vol.All(cv.string, "last")
|
||||
),
|
||||
vol.Optional(ATTR_CHAT_ID): vol.Coerce(int),
|
||||
@@ -311,7 +311,7 @@ SERVICE_SCHEMA_EDIT_CAPTION = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_ENTITY_ID): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Required(ATTR_MESSAGE_ID): vol.Any(
|
||||
vol.Required(ATTR_MESSAGEID): vol.Any(
|
||||
cv.positive_int, vol.All(cv.string, "last")
|
||||
),
|
||||
vol.Optional(ATTR_CHAT_ID): vol.Coerce(int),
|
||||
@@ -325,7 +325,7 @@ SERVICE_SCHEMA_EDIT_REPLYMARKUP = vol.Schema(
|
||||
{
|
||||
vol.Optional(ATTR_ENTITY_ID): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Required(ATTR_MESSAGE_ID): vol.Any(
|
||||
vol.Required(ATTR_MESSAGEID): vol.Any(
|
||||
cv.positive_int, vol.All(cv.string, "last")
|
||||
),
|
||||
vol.Optional(ATTR_CHAT_ID): vol.Coerce(int),
|
||||
@@ -347,7 +347,7 @@ SERVICE_SCHEMA_DELETE_MESSAGE = vol.Schema(
|
||||
vol.Optional(ATTR_ENTITY_ID): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Optional(ATTR_CHAT_ID): vol.Coerce(int),
|
||||
vol.Required(ATTR_MESSAGE_ID): vol.Any(
|
||||
vol.Required(ATTR_MESSAGEID): vol.Any(
|
||||
cv.positive_int, vol.All(cv.string, "last")
|
||||
),
|
||||
}
|
||||
@@ -364,7 +364,7 @@ SERVICE_SCHEMA_LEAVE_CHAT = vol.Schema(
|
||||
SERVICE_SCHEMA_SET_MESSAGE_REACTION = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_CONFIG_ENTRY_ID): cv.string,
|
||||
vol.Required(ATTR_MESSAGE_ID): vol.Any(
|
||||
vol.Required(ATTR_MESSAGEID): vol.Any(
|
||||
cv.positive_int, vol.All(cv.string, "last")
|
||||
),
|
||||
vol.Optional(ATTR_CHAT_ID): vol.Coerce(int),
|
||||
@@ -468,7 +468,7 @@ async def _async_send_telegram_message(service: ServiceCall) -> ServiceResponse:
|
||||
targets = _build_targets(service)
|
||||
|
||||
service_responses: JsonValueType = []
|
||||
errors: list[tuple[Exception, str]] = []
|
||||
errors: list[tuple[HomeAssistantError, str]] = []
|
||||
|
||||
# invoke the service for each target
|
||||
for target_config_entry, target_chat_id, target_notify_entity_id in targets:
|
||||
@@ -485,7 +485,7 @@ async def _async_send_telegram_message(service: ServiceCall) -> ServiceResponse:
|
||||
for chat_id, message_id in service_response.items():
|
||||
formatted_response = {
|
||||
ATTR_CHAT_ID: int(chat_id),
|
||||
ATTR_MESSAGE_ID: message_id,
|
||||
ATTR_MESSAGEID: message_id,
|
||||
}
|
||||
|
||||
if target_notify_entity_id:
|
||||
@@ -495,7 +495,7 @@ async def _async_send_telegram_message(service: ServiceCall) -> ServiceResponse:
|
||||
|
||||
assert isinstance(service_responses, list)
|
||||
service_responses.extend(formatted_responses)
|
||||
except (HomeAssistantError, TelegramError) as ex:
|
||||
except HomeAssistantError as ex:
|
||||
target = target_notify_entity_id or str(target_chat_id)
|
||||
errors.append((ex, target))
|
||||
|
||||
|
||||
@@ -73,9 +73,9 @@ from .const import (
|
||||
ATTR_KEYBOARD,
|
||||
ATTR_KEYBOARD_INLINE,
|
||||
ATTR_MESSAGE,
|
||||
ATTR_MESSAGE_ID,
|
||||
ATTR_MESSAGE_TAG,
|
||||
ATTR_MESSAGE_THREAD_ID,
|
||||
ATTR_MESSAGEID,
|
||||
ATTR_MSG,
|
||||
ATTR_MSGID,
|
||||
ATTR_ONE_TIME_KEYBOARD,
|
||||
@@ -319,8 +319,8 @@ class TelegramNotificationService:
|
||||
"""
|
||||
message_id: Any | None = None
|
||||
inline_message_id: int | None = None
|
||||
if ATTR_MESSAGE_ID in msg_data:
|
||||
message_id = msg_data[ATTR_MESSAGE_ID]
|
||||
if ATTR_MESSAGEID in msg_data:
|
||||
message_id = msg_data[ATTR_MESSAGEID]
|
||||
if (
|
||||
isinstance(message_id, str)
|
||||
and (message_id == "last")
|
||||
@@ -433,6 +433,7 @@ class TelegramNotificationService:
|
||||
async def _send_msgs(
|
||||
self,
|
||||
func_send: Callable,
|
||||
msg_error: str,
|
||||
message_tag: str | None,
|
||||
*args_msg: Any,
|
||||
context: Context | None = None,
|
||||
@@ -458,10 +459,12 @@ class TelegramNotificationService:
|
||||
|
||||
response: Message = await self._send_msg(
|
||||
func_send,
|
||||
msg_error,
|
||||
message_tag,
|
||||
chat_id,
|
||||
*args_msg,
|
||||
context=context,
|
||||
suppress_error=len(chat_ids) > 1,
|
||||
**kwargs_msg,
|
||||
)
|
||||
if response:
|
||||
@@ -472,39 +475,58 @@ class TelegramNotificationService:
|
||||
async def _send_msg(
|
||||
self,
|
||||
func_send: Callable,
|
||||
msg_error: str,
|
||||
message_tag: str | None,
|
||||
*args_msg: Any,
|
||||
context: Context | None = None,
|
||||
suppress_error: bool = False,
|
||||
**kwargs_msg: Any,
|
||||
) -> Any:
|
||||
"""Send one message."""
|
||||
out = await func_send(*args_msg, **kwargs_msg)
|
||||
if isinstance(out, Message):
|
||||
chat_id = out.chat_id
|
||||
message_id = out.message_id
|
||||
self._last_message_id[chat_id] = message_id
|
||||
_LOGGER.debug(
|
||||
"Last message ID: %s (from chat_id %s)",
|
||||
self._last_message_id,
|
||||
chat_id,
|
||||
)
|
||||
|
||||
event_data: dict[str, Any] = {
|
||||
ATTR_CHAT_ID: chat_id,
|
||||
ATTR_MESSAGE_ID: message_id,
|
||||
}
|
||||
if message_tag is not None:
|
||||
event_data[ATTR_MESSAGE_TAG] = message_tag
|
||||
if kwargs_msg.get(ATTR_MESSAGE_THREAD_ID) is not None:
|
||||
event_data[ATTR_MESSAGE_THREAD_ID] = kwargs_msg[ATTR_MESSAGE_THREAD_ID]
|
||||
|
||||
event_data["bot"] = _get_bot_info(self.bot, self.config)
|
||||
|
||||
self.hass.bus.async_fire(EVENT_TELEGRAM_SENT, event_data, context=context)
|
||||
async_dispatcher_send(
|
||||
self.hass, signal(self.bot), EVENT_TELEGRAM_SENT, event_data
|
||||
try:
|
||||
out = await func_send(*args_msg, **kwargs_msg)
|
||||
if isinstance(out, Message):
|
||||
chat_id = out.chat_id
|
||||
message_id = out.message_id
|
||||
self._last_message_id[chat_id] = message_id
|
||||
_LOGGER.debug(
|
||||
"Last message ID: %s (from chat_id %s)",
|
||||
self._last_message_id,
|
||||
chat_id,
|
||||
)
|
||||
|
||||
event_data: dict[str, Any] = {
|
||||
ATTR_CHAT_ID: chat_id,
|
||||
ATTR_MESSAGEID: message_id,
|
||||
}
|
||||
if message_tag is not None:
|
||||
event_data[ATTR_MESSAGE_TAG] = message_tag
|
||||
if kwargs_msg.get(ATTR_MESSAGE_THREAD_ID) is not None:
|
||||
event_data[ATTR_MESSAGE_THREAD_ID] = kwargs_msg[
|
||||
ATTR_MESSAGE_THREAD_ID
|
||||
]
|
||||
|
||||
event_data["bot"] = _get_bot_info(self.bot, self.config)
|
||||
|
||||
self.hass.bus.async_fire(
|
||||
EVENT_TELEGRAM_SENT, event_data, context=context
|
||||
)
|
||||
async_dispatcher_send(
|
||||
self.hass, signal(self.bot), EVENT_TELEGRAM_SENT, event_data
|
||||
)
|
||||
except TelegramError as exc:
|
||||
if not suppress_error:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="action_failed",
|
||||
translation_placeholders={"error": str(exc)},
|
||||
) from exc
|
||||
|
||||
_LOGGER.error(
|
||||
"%s: %s. Args: %s, kwargs: %s", msg_error, exc, args_msg, kwargs_msg
|
||||
)
|
||||
|
||||
return None
|
||||
return out
|
||||
|
||||
async def send_message(
|
||||
@@ -520,6 +542,7 @@ class TelegramNotificationService:
|
||||
params = self._get_msg_kwargs(kwargs)
|
||||
return await self._send_msgs(
|
||||
self.bot.send_message,
|
||||
"Error sending message",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
text,
|
||||
chat_id=chat_id,
|
||||
@@ -544,6 +567,7 @@ class TelegramNotificationService:
|
||||
_LOGGER.debug("Delete message %s in chat ID %s", message_id, chat_id)
|
||||
deleted: bool = await self._send_msg(
|
||||
self.bot.delete_message,
|
||||
"Error deleting message",
|
||||
None,
|
||||
chat_id,
|
||||
message_id,
|
||||
@@ -620,6 +644,7 @@ class TelegramNotificationService:
|
||||
|
||||
return await self._send_msg(
|
||||
self.bot.edit_message_media,
|
||||
"Error editing message media",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
media=media,
|
||||
chat_id=chat_id,
|
||||
@@ -653,6 +678,7 @@ class TelegramNotificationService:
|
||||
_LOGGER.debug("Editing message with ID %s", message_id or inline_message_id)
|
||||
return await self._send_msg(
|
||||
self.bot.edit_message_text,
|
||||
"Error editing text message",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
text,
|
||||
chat_id=chat_id,
|
||||
@@ -667,6 +693,7 @@ class TelegramNotificationService:
|
||||
if type_edit == SERVICE_EDIT_CAPTION:
|
||||
return await self._send_msg(
|
||||
self.bot.edit_message_caption,
|
||||
"Error editing message attributes",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
@@ -680,6 +707,7 @@ class TelegramNotificationService:
|
||||
|
||||
return await self._send_msg(
|
||||
self.bot.edit_message_reply_markup,
|
||||
"Error editing message attributes",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
@@ -707,6 +735,7 @@ class TelegramNotificationService:
|
||||
)
|
||||
await self._send_msg(
|
||||
self.bot.answer_callback_query,
|
||||
"Error sending answer callback query",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
callback_query_id,
|
||||
text=message,
|
||||
@@ -727,6 +756,7 @@ class TelegramNotificationService:
|
||||
_LOGGER.debug("Send action %s in chat ID %s", chat_action, chat_id)
|
||||
is_successful = await self._send_msg(
|
||||
self.bot.send_chat_action,
|
||||
"Error sending action",
|
||||
None,
|
||||
chat_id=chat_id,
|
||||
action=chat_action,
|
||||
@@ -761,6 +791,7 @@ class TelegramNotificationService:
|
||||
if file_type == SERVICE_SEND_PHOTO:
|
||||
return await self._send_msgs(
|
||||
self.bot.send_photo,
|
||||
"Error sending photo",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
photo=file_content,
|
||||
@@ -777,6 +808,7 @@ class TelegramNotificationService:
|
||||
if file_type == SERVICE_SEND_STICKER:
|
||||
return await self._send_msgs(
|
||||
self.bot.send_sticker,
|
||||
"Error sending sticker",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
sticker=file_content,
|
||||
@@ -791,6 +823,7 @@ class TelegramNotificationService:
|
||||
if file_type == SERVICE_SEND_VIDEO:
|
||||
return await self._send_msgs(
|
||||
self.bot.send_video,
|
||||
"Error sending video",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
video=file_content,
|
||||
@@ -807,6 +840,7 @@ class TelegramNotificationService:
|
||||
if file_type == SERVICE_SEND_DOCUMENT:
|
||||
return await self._send_msgs(
|
||||
self.bot.send_document,
|
||||
"Error sending document",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
document=file_content,
|
||||
@@ -823,6 +857,7 @@ class TelegramNotificationService:
|
||||
if file_type == SERVICE_SEND_VOICE:
|
||||
return await self._send_msgs(
|
||||
self.bot.send_voice,
|
||||
"Error sending voice",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
voice=file_content,
|
||||
@@ -838,6 +873,7 @@ class TelegramNotificationService:
|
||||
# SERVICE_SEND_ANIMATION
|
||||
return await self._send_msgs(
|
||||
self.bot.send_animation,
|
||||
"Error sending animation",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
animation=file_content,
|
||||
@@ -863,6 +899,7 @@ class TelegramNotificationService:
|
||||
if stickerid:
|
||||
return await self._send_msgs(
|
||||
self.bot.send_sticker,
|
||||
"Error sending sticker",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
sticker=stickerid,
|
||||
@@ -888,6 +925,7 @@ class TelegramNotificationService:
|
||||
params = self._get_msg_kwargs(kwargs)
|
||||
return await self._send_msgs(
|
||||
self.bot.send_location,
|
||||
"Error sending location",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
latitude=latitude,
|
||||
@@ -913,6 +951,7 @@ class TelegramNotificationService:
|
||||
openperiod = kwargs.get(ATTR_OPEN_PERIOD)
|
||||
return await self._send_msgs(
|
||||
self.bot.send_poll,
|
||||
"Error sending poll",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id=kwargs[ATTR_CHAT_ID],
|
||||
question=question,
|
||||
@@ -935,7 +974,9 @@ class TelegramNotificationService:
|
||||
) -> Any:
|
||||
"""Remove bot from chat."""
|
||||
_LOGGER.debug("Leave from chat ID %s", chat_id)
|
||||
return await self._send_msg(self.bot.leave_chat, None, chat_id, context=context)
|
||||
return await self._send_msg(
|
||||
self.bot.leave_chat, "Error leaving chat", None, chat_id, context=context
|
||||
)
|
||||
|
||||
async def set_message_reaction(
|
||||
self,
|
||||
@@ -959,6 +1000,7 @@ class TelegramNotificationService:
|
||||
|
||||
await self._send_msg(
|
||||
self.bot.set_message_reaction,
|
||||
"Error setting message reaction",
|
||||
params[ATTR_MESSAGE_TAG],
|
||||
chat_id,
|
||||
message_id,
|
||||
@@ -981,6 +1023,7 @@ class TelegramNotificationService:
|
||||
directory_path = self.hass.config.path(DOMAIN)
|
||||
file: File = await self._send_msg(
|
||||
self.bot.get_file,
|
||||
"Error getting file",
|
||||
None,
|
||||
file_id=file_id,
|
||||
context=context,
|
||||
|
||||
@@ -102,7 +102,7 @@ ATTR_KEYBOARD = "keyboard"
|
||||
ATTR_RESIZE_KEYBOARD = "resize_keyboard"
|
||||
ATTR_ONE_TIME_KEYBOARD = "one_time_keyboard"
|
||||
ATTR_KEYBOARD_INLINE = "inline_keyboard"
|
||||
ATTR_MESSAGE_ID = "message_id"
|
||||
ATTR_MESSAGEID = "message_id"
|
||||
ATTR_INLINE_MESSAGE_ID = "inline_message_id"
|
||||
ATTR_MEDIA_TYPE = "media_type"
|
||||
ATTR_MSG = "message"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user