Compare commits

..

113 Commits

Author SHA1 Message Date
Franck Nijhof e6dcaaadd8 Bumped version to 2022.4.0b4 2022-04-04 11:14:39 +02:00
Erik Montnemery 73cd4a3366 Unsubscribe from listeners when removing threshold binary sensor (#69236) 2022-04-04 11:13:53 +02:00
Erik Montnemery 9ec9dc047b Unsubscribe from listeners when removing integration sensor (#69235) 2022-04-04 11:13:50 +02:00
Erik Montnemery a3ad44f7b6 Unsubscribe from listeners when removing derivative sensor (#69234) 2022-04-04 11:13:46 +02:00
Erik Montnemery da3f9fdf7d Drop unsupported SI-prefix peta from integration sensor (#69229) 2022-04-04 11:13:43 +02:00
Simone Chemelli eeeff761e9 Avoid fritz API calls during shutdown (#69225) 2022-04-04 11:13:40 +02:00
J. Nick Koston 3cd9c7d19d Only fire device_registry_updated for suggested_area if the suggestion results in an area change (#69215) 2022-04-04 11:13:36 +02:00
Franck Nijhof 62b44e6903 Exclude more media player attributes from recorder (#69209) 2022-04-04 11:13:33 +02:00
Daniel Hjelseth Høyer 9c6a69775a Tibber, Use a dedicated executor pool for database operations (#69208) 2022-04-04 11:13:30 +02:00
Franck Nijhof 8ce1b104eb Exclude weather forecast attribute from recorder (#69205) 2022-04-04 11:13:26 +02:00
Franck Nijhof 2916d35626 Exclude update entity picture attribute from recorder (#69201) 2022-04-04 11:13:23 +02:00
Franck Nijhof 814f92a75c Set brand icon as entity picture on update entities (#69200)
Co-authored-by: Joakim Sørensen <ludeeus@ludeeus.dev>
2022-04-04 11:13:19 +02:00
J. Nick Koston 1ae45fa7ad Exclude static vacuum attributes from being recorded in the database (#69199) 2022-04-04 11:13:16 +02:00
J. Nick Koston e18d61224d Exclude static siren attributes from being recorded in the database (#69196) 2022-04-04 11:13:11 +02:00
J. Nick Koston 7cfaf46287 Exclude static select attributes from being recorded in the database (#69195) 2022-04-04 11:13:08 +02:00
J. Nick Koston cebf53c340 Exclude static number attributes from being recorded in the database (#69194) 2022-04-04 11:13:03 +02:00
J. Nick Koston 8fa8716387 Exclude static humidifier attributes from being recorded in the database (#69193) 2022-04-04 11:12:59 +02:00
J. Nick Koston 5a96b0ffbc Exclude static fan attributes from being recorded in the database (#69192) 2022-04-04 11:12:56 +02:00
Erik Montnemery 2e13fb3c24 Unsubscribe listeners when entity meter sensor is removed (#69172) 2022-04-04 11:12:52 +02:00
Jesse Hills 90cb9ccde2 ESPHome: Remove disconnect callbacks after they are done (#69169) 2022-04-04 11:12:49 +02:00
J. Nick Koston 9a91a7edf5 Exclude supported features and attribution from being recorded in the database (#69165) 2022-04-04 11:12:46 +02:00
Raman Gupta c769b20256 Make zwave_js config entry unique ID a string (#69163)
* Make zwave_js config entry unique ID a string

* Add test

* Fix tests
2022-04-04 11:12:42 +02:00
J. Nick Koston 47c1e48166 Exclude static water_heater attributes from being recorded in the database (#69159) 2022-04-04 11:12:39 +02:00
J. Nick Koston 0e6550ab70 Exclude static climate attributes from being recorded in the database (#69158) 2022-04-04 11:12:36 +02:00
J. Nick Koston 5fa2dc540b Exclude static and token attributes from being recorded for media_player (#69156) 2022-04-04 11:12:32 +02:00
J. Nick Koston b503be9301 Exclude static light attributes from being recorded in the database (#69155) 2022-04-04 11:12:29 +02:00
Diogo Gomes d99e04e2bc Makes sure YAML defined tariffs are unique (#69151) 2022-04-04 11:12:25 +02:00
Marc Mueller 39a38d9866 Update hangups to 0.4.18 (#69149) 2022-04-04 11:12:22 +02:00
Alex Yao bc106cc430 Bump Yeelight to v0.7.10 (#69147) 2022-04-04 11:12:18 +02:00
Franck Nijhof 2ae390a342 Set brand icon on WLED update entity (#69145) 2022-04-04 11:12:15 +02:00
Glenn Waters 5cc58579bb Environment Canada: allow AQHI to pull from forecast when current not available (#69142)
* Allow AQHI to pull from forecast when current not available.

* Remove redundant check.

* Remove lambda.
2022-04-04 11:12:12 +02:00
Bouwe Westerdijk 61129734f5 Bump plugwise to v0.17.3 (#69139) 2022-04-04 11:12:09 +02:00
Raman Gupta a26e221ebc Fix kodi log spamming again (#69137)
* Fix kodi log spamming again

* use try except else
2022-04-04 11:12:05 +02:00
Michael 026c843545 ignore ip6 link local address on ssdp discovery (#69135) 2022-04-04 11:12:02 +02:00
Mick Vleeshouwer f2d41a0011 Bugfix for overkiz.alarm_control_panel platform exception (#69131)
* Revert changes

* Remove debug statement
2022-04-04 11:11:58 +02:00
Nenad Bogojevic bee853d415 Fix withings race condition for access token (#69107) 2022-04-04 11:11:55 +02:00
Erik Montnemery 3ba8ddb192 Return unsubscribe callback from start.async_at_start (#69083)
* Return unsubscribe callback from start.async_at_start

* Update calling code
2022-04-04 11:11:52 +02:00
Wictor 53107e4f2c Refactor telegram_bot polling/webhooks platforms and add tests (#66433)
Co-authored-by: Pär Berge <paer.berge@gmail.com>
2022-04-04 11:11:48 +02:00
Franck Nijhof b539700e45 Bumped version to 2022.4.0b3 2022-04-02 11:40:11 +02:00
Franck Nijhof dd2c2eb974 Update wled to 0.13.2 (#69116) 2022-04-02 11:39:44 +02:00
Raman Gupta c1c8299511 Fix kodi log spamming (#69113) 2022-04-02 11:39:40 +02:00
Franck Nijhof c72db40082 Bump num of conflicts in pip check (#69112) 2022-04-02 11:39:37 +02:00
Marcel van der Veldt 9327938dcc Allow lowercase none for effect value in Hue lights (#69111) 2022-04-02 11:39:33 +02:00
Marcel van der Veldt c7f807cf33 Adjust check for orphaned Hue device entries for grouped lights (#69110)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2022-04-02 11:39:30 +02:00
Raman Gupta 691eac7a7f Fix unit prefixes for derivative and integration (#69109) 2022-04-02 11:39:27 +02:00
Keilin Bickar e341e55e88 Bump asyncsleepiq to 1.2.3 (#69104) 2022-04-02 11:39:24 +02:00
Glenn Waters 51e8ddbd27 Environment Canada: Fix for when temperature is zero (#69101) 2022-04-02 11:39:20 +02:00
puddly c2c397527e Bump ZHA dependency zigpy-deconz from 0.14.0 to 0.15.0 (#69099) 2022-04-02 11:39:17 +02:00
Bram Kragten 328f479bc4 Update frontend to 20220401.0 (#69095) 2022-04-02 11:39:13 +02:00
Bouwe Westerdijk 5df7882ce4 Remove use of HVAC_MODE_OFF in plugwise climate, it's not implemented (#69094) 2022-04-02 11:39:10 +02:00
Franck Nijhof 7fe6174bd9 Rename current_version to installed_version in Update platform (#69093) 2022-04-02 11:39:06 +02:00
Dave T d4a31b037f Add image test cases to generic (#69040) 2022-04-02 11:39:03 +02:00
Mick Vleeshouwer 5db1c67812 Fix Protexial alarm in Overkiz integration (#68996) 2022-04-02 11:38:59 +02:00
Paulus Schoutsen f40513c95b Bumped version to 2022.4.0b2 2022-04-01 09:30:33 -07:00
Mick Vleeshouwer 2d7cbeb1fc Bump pyoverkiz to 1.3.14 in Overkiz integration (#69084) 2022-04-01 09:30:08 -07:00
Erik Montnemery 718bc734f2 Add bluepy as a dependency for zengge (#69067)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2022-04-01 09:30:07 -07:00
Erik Montnemery 66c131515a Update zengge codeowners (#69068) 2022-04-01 09:28:01 -07:00
Erik Montnemery 200a24d869 Fix utility_meter startup (#69064) 2022-04-01 09:25:55 -07:00
Matthias Alphart c030af0919 coerce number selector values to int (#69059) 2022-04-01 09:25:55 -07:00
Erik Montnemery ad2067381b Bump pychromecast to 11.0.0 (#69057) 2022-04-01 09:25:54 -07:00
Joakim Sørensen cc9e55594f Add auto_update property to supervisor and addon update entities (#69055) 2022-04-01 09:25:53 -07:00
Joakim Sørensen fadb63c43a Add auto_update property to UpdateEntity (#69054) 2022-04-01 09:25:53 -07:00
Raman Gupta 0cf817698d Fix zwave_js device action logic (#69049)
* Fix zwave_js device action logic

* Add test for this behavior
2022-04-01 09:25:52 -07:00
Shay Levy 432768f503 Remove webostv deprecated YAML import (#69043)
* webostv: remove deprecated YAML import

* Remove unused CUSTOMIZE_SCHEMA and WEBOSTV_CONFIG_FILE
2022-04-01 09:25:51 -07:00
J. Nick Koston 65ccb7446f Prevent HomeKit from offering hidden entities (#69042) 2022-04-01 09:25:50 -07:00
Mick Vleeshouwer 90bec9cfcd Enable select platform in Overkiz integration (#68995) 2022-04-01 09:25:50 -07:00
Erik Montnemery ab21ac370c Mend incorrectly imported MQTT config entries (#68987)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-04-01 09:25:49 -07:00
Matthias Alphart 43b9e4febf Update KNX flow strings to use "data_description" and remove invalid options (#68935)
* use `data_description`

* use selectors so `data_description` shows

* remove unused config option

`individual_address` has no effect for tunneling. The address is always assigned by the tunnelling server.

* remove unsupported option for tunneling V1 devices
2022-04-01 09:25:48 -07:00
Chris Talkington 3659bceedb Update rokuecp to 0.16.0 (#68822) 2022-04-01 09:25:47 -07:00
J. Nick Koston e4395dee6c Convert statistics to use history api for database access (#68411) 2022-04-01 09:25:47 -07:00
Paulus Schoutsen 0b8bba0db9 Bumped version to 2022.4.0b1 2022-03-31 15:10:09 -07:00
Dave T 2cb77c4702 Improve image checks for generic camera (#69037) 2022-03-31 15:10:01 -07:00
Franck Nijhof dcbca89d06 Remove deprecated YAML configuration from Yamaha Music Cast (#69033) 2022-03-31 15:10:00 -07:00
Franck Nijhof 9614e8496b Remove deprecated YAML configuration from Fronius (#69032) 2022-03-31 15:10:00 -07:00
Franck Nijhof deed4c5980 Remove deprecated YAML configuration from EZVIZ (#69031) 2022-03-31 15:09:59 -07:00
Franck Nijhof 9c9bacad31 Remove deprecated YAML configuration from Sensibo (#69028) 2022-03-31 15:09:58 -07:00
Franck Nijhof daf2f746ed Remove deprecated YAML configuration from Met.no (#69027) 2022-03-31 15:09:57 -07:00
Franck Nijhof d0bb58c698 Remove deprecated YAML configuration from Yale Smart Alarm (#69025) 2022-03-31 15:09:57 -07:00
Franck Nijhof 652ce897c7 Remove deprecated YAML configuration from Brunt (#69024) 2022-03-31 15:09:56 -07:00
Franck Nijhof 5120a03470 Remove deprecated template support in persistent notifications (#69021) 2022-03-31 15:09:55 -07:00
Joakim Sørensen b349322055 Cleanup Version after removing YAML (#69020) 2022-03-31 15:09:55 -07:00
Paulus Schoutsen 00b363d896 Enforce EntityCategory enum (#69015)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2022-03-31 15:09:54 -07:00
Paulus Schoutsen a1ebea271c Enforce RegistryEntryDisabler enum (#69017)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2022-03-31 15:09:26 -07:00
Paulus Schoutsen c73f423102 Device Automation: enforce passing in device-automation-enum (#69013) 2022-03-31 15:06:57 -07:00
Paulus Schoutsen a630e7dc49 Version: remove deprecated YAML import (#69010) 2022-03-31 15:06:56 -07:00
Paulus Schoutsen b7e3e7a8e6 Launch Library: remove deprecated YAML import (#69008) 2022-03-31 15:06:56 -07:00
Paulus Schoutsen 7eecf48645 DNS IP: Remove deprecated YAML import (#69007)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2022-03-31 15:06:55 -07:00
Paulus Schoutsen bb26ded115 iCloud: remove deprecated YAML import (#69006)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2022-03-31 15:06:54 -07:00
Paulus Schoutsen 8c65e930d5 Update tradfri deprecation message (#69005) 2022-03-31 15:06:53 -07:00
Paulus Schoutsen ff332049e1 Nanoleaf: remove deprecated YAML import (#69004) 2022-03-31 15:06:52 -07:00
Paulus Schoutsen fa12fd3776 Solax: remove deprecated YAML import (#69003) 2022-03-31 15:06:51 -07:00
Paulus Schoutsen 0adeebfe33 Remove deprecated Switchbot import (#69002) 2022-03-31 15:06:50 -07:00
Paulus Schoutsen 2328813e69 Don't log the stack trace (#69000) 2022-03-31 15:06:50 -07:00
starkillerOG 013f376ef3 bump pynetgear to 0.9.2 (#68986) 2022-03-31 15:06:49 -07:00
Erik Montnemery a8e6ad9f3d Deprecate temperature conversion in base entity class (#68978)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2022-03-31 15:06:48 -07:00
Erik Montnemery a8dcecf1ec Make utility_meter tariffs a list (#68967) 2022-03-31 15:06:47 -07:00
Erik Montnemery 76db6acfb2 Improve utility_meter services.yaml (#68960) 2022-03-31 15:06:47 -07:00
Paulus Schoutsen 844d0680f0 Change privacy mode to config (#68954) 2022-03-31 15:06:46 -07:00
Raman Gupta c684603037 Add support for new select selector properties (#68952)
* Add support for new select selector properties

* fix mode option

* Apply suggestions from code review

* Correct validation for empty options, update tests

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2022-03-31 15:06:45 -07:00
Allen Porter e6b88a5214 Fix google calendar blocking call, running outside of executor (#68948) 2022-03-31 15:06:45 -07:00
Robert Hillis a16d86585b Rename google hangouts to google chat (#68941) 2022-03-31 15:06:44 -07:00
J. Nick Koston df308f703f Exclude large and chatty attributes from being recorded for update entities (#68940)
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
2022-03-31 15:06:43 -07:00
Philip Allgaier 40e7055934 Prevent issues with setting up "Timer" integration (unknown "restore" key) (#68936)
* Prevent issues with setting up "Timer" for existing entities

* Use default constant

* Update homeassistant/components/timer/__init__.py

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2022-03-31 15:06:42 -07:00
Dave T 45bd4038f4 Generic camera: Allow gif image type in still image checker (#68933) 2022-03-31 15:06:42 -07:00
Dave T 3aa0294b5d Generic camera: Allow svg detect to accept leading whitespace (#68932) 2022-03-31 15:06:41 -07:00
Erik Montnemery 143e9f4fc3 Improve utility_meter services.yaml (#68930) 2022-03-31 15:06:40 -07:00
Nathan Spencer 0de84d882a Bump pylitterbot to 2022.3.0 (#68929) 2022-03-31 15:06:39 -07:00
Bram Kragten ff786c3be8 Handle config entries of integrations that are removed (#68928) 2022-03-31 15:06:39 -07:00
Michael d617271ca0 Add "station is open" sensor to Tankerkoenig (#68925) 2022-03-31 15:06:38 -07:00
Erik Montnemery d38382fbe3 Rename helper_config_entry_flow to schema_config_entry_flow (#68924) 2022-03-31 15:06:37 -07:00
Paulus Schoutsen 475b9e212d Mark all input integrations as helpers (#68922) 2022-03-31 15:06:37 -07:00
puddly a8ad3292c8 Bump zigpy to 0.44.1 and zha-quirks to 0.0.69 (#68921)
* Make unit tests pass

* Flip response type check to not rely on it being a list
https://github.com/zigpy/zigpy/pull/716#issuecomment-1025236190

* Bump zigpy and quirks versions to ZCLR8 releases

* Fix renamed zigpy cluster attributes

* Handle the default response for ZLL `get_group_identifiers`

* Add more error context to `stage failed` errors

* Fix unit test returning lists as ZCL request responses

* Always load quirks when testing ZHA

* Bump zha-quirks to 0.0.69
2022-03-31 15:06:36 -07:00
rianadon 78d8bc66d1 Calculate temperature precision based on user units (#59560)
* Calculate temperature precision based on user units

* Fix a few more failing tests

* Fix failing test

Co-authored-by: Erik <erik@montnemery.com>
2022-03-31 15:06:35 -07:00
271 changed files with 3475 additions and 3535 deletions
+1
View File
@@ -1197,6 +1197,7 @@ omit =
homeassistant/components/tado/water_heater.py
homeassistant/components/tank_utility/sensor.py
homeassistant/components/tankerkoenig/__init__.py
homeassistant/components/tankerkoenig/binary_sensor.py
homeassistant/components/tankerkoenig/const.py
homeassistant/components/tankerkoenig/sensor.py
homeassistant/components/tapsaff/binary_sensor.py
+1
View File
@@ -1187,6 +1187,7 @@ build.json @home-assistant/supervisor
/homeassistant/components/yi/ @bachya
/homeassistant/components/youless/ @gjong
/tests/components/youless/ @gjong
/homeassistant/components/zengge/ @emontnemery
/homeassistant/components/zeroconf/ @bdraco
/tests/components/zeroconf/ @bdraco
/homeassistant/components/zerproc/ @emlove
@@ -111,9 +111,3 @@ class BruntConfigFlow(ConfigFlow, domain=DOMAIN):
self.hass.config_entries.async_update_entry(self._reauth_entry, data=user_input)
await self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
return self.async_abort(reason="reauth_successful")
async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult:
"""Import config from configuration.yaml."""
await self.async_set_unique_id(import_config[CONF_USERNAME].lower())
self._abort_if_unique_id_configured()
return await self.async_step_user(import_config)
+1 -22
View File
@@ -2,7 +2,6 @@
from __future__ import annotations
from collections.abc import MutableMapping
import logging
from typing import Any
from aiohttp.client_exceptions import ClientResponseError
@@ -16,12 +15,11 @@ from homeassistant.components.cover import (
CoverDeviceClass,
CoverEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
@@ -39,28 +37,9 @@ from .const import (
REGULAR_INTERVAL,
)
_LOGGER = logging.getLogger(__name__)
COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Component setup, run import config flow for each entry in config."""
_LOGGER.warning(
"Loading brunt via platform config is deprecated; The configuration has been migrated to a config entry and can be safely removed from configuration.yaml"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
+1 -1
View File
@@ -3,7 +3,7 @@
"name": "Google Cast",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==10.3.0"],
"requirements": ["pychromecast==11.0.0"],
"after_dependencies": [
"cloud",
"http",
@@ -0,0 +1,32 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from .const import (
ATTR_FAN_MODES,
ATTR_HVAC_MODES,
ATTR_MAX_HUMIDITY,
ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY,
ATTR_MIN_TEMP,
ATTR_PRESET_MODES,
ATTR_SWING_MODES,
ATTR_TARGET_TEMP_STEP,
)
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static attributes from being recorded in the database."""
return {
ATTR_HVAC_MODES,
ATTR_FAN_MODES,
ATTR_SWING_MODES,
ATTR_MIN_TEMP,
ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY,
ATTR_MAX_HUMIDITY,
ATTR_TARGET_TEMP_STEP,
ATTR_PRESET_MODES,
}
@@ -18,7 +18,7 @@ from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView,
FlowManagerResourceView,
)
from homeassistant.loader import async_get_config_flows
from homeassistant.loader import Integration, async_get_config_flows
async def async_setup(hass):
@@ -63,19 +63,33 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
integrations = {}
type_filter = request.query["type"]
async def load_integration(
hass: HomeAssistant, domain: str
) -> Integration | None:
"""Load integration."""
try:
return await loader.async_get_integration(hass, domain)
except loader.IntegrationNotFound:
return None
# Fetch all the integrations so we can check their type
for integration in await asyncio.gather(
*(
loader.async_get_integration(hass, domain)
load_integration(hass, domain)
for domain in {entry.domain for entry in entries}
)
):
integrations[integration.domain] = integration
if integration:
integrations[integration.domain] = integration
entries = [
entry
for entry in entries
if integrations[entry.domain].integration_type == type_filter
if (type_filter != "helper" and entry.domain not in integrations)
or (
entry.domain in integrations
and integrations[entry.domain].integration_type == type_filter
)
]
return self.json([entry_json(entry) for entry in entries])
+8 -8
View File
@@ -31,7 +31,7 @@ async def async_setup_platform(
unique_id="update_no_install",
name="Demo Update No Install",
title="Awesomesoft Inc.",
current_version="1.0.0",
installed_version="1.0.0",
latest_version="1.0.1",
release_summary="Awesome update, fixing everything!",
release_url="https://www.example.com/release/1.0.1",
@@ -41,14 +41,14 @@ async def async_setup_platform(
unique_id="update_2_date",
name="Demo No Update",
title="AdGuard Home",
current_version="1.0.0",
installed_version="1.0.0",
latest_version="1.0.0",
),
DemoUpdate(
unique_id="update_addon",
name="Demo add-on",
title="AdGuard Home",
current_version="1.0.0",
installed_version="1.0.0",
latest_version="1.0.1",
release_summary="Awesome update, fixing everything!",
release_url="https://www.example.com/release/1.0.1",
@@ -57,7 +57,7 @@ async def async_setup_platform(
unique_id="update_light_bulb",
name="Demo Living Room Bulb Update",
title="Philips Lamps Firmware",
current_version="1.93.3",
installed_version="1.93.3",
latest_version="1.94.2",
release_summary="Added support for effects",
release_url="https://www.example.com/release/1.93.3",
@@ -67,7 +67,7 @@ async def async_setup_platform(
unique_id="update_support_progress",
name="Demo Update with Progress",
title="Philips Lamps Firmware",
current_version="1.93.3",
installed_version="1.93.3",
latest_version="1.94.2",
support_progress=True,
release_summary="Added support for effects",
@@ -104,7 +104,7 @@ class DemoUpdate(UpdateEntity):
unique_id: str,
name: str,
title: str | None,
current_version: str | None,
installed_version: str | None,
latest_version: str | None,
release_summary: str | None = None,
release_url: str | None = None,
@@ -114,7 +114,7 @@ class DemoUpdate(UpdateEntity):
device_class: UpdateDeviceClass | None = None,
) -> None:
"""Initialize the Demo select entity."""
self._attr_current_version = current_version
self._attr_installed_version = installed_version
self._attr_device_class = device_class
self._attr_latest_version = latest_version
self._attr_name = name or DEVICE_DEFAULT_NAME
@@ -149,7 +149,7 @@ class DemoUpdate(UpdateEntity):
await _fake_install()
self._attr_in_progress = False
self._attr_current_version = (
self._attr_installed_version = (
version if version is not None else self.latest_version
)
self.async_write_ha_state()
@@ -16,10 +16,10 @@ from homeassistant.const import (
TIME_SECONDS,
)
from homeassistant.helpers import selector
from homeassistant.helpers.helper_config_entry_flow import (
HelperConfigFlowHandler,
HelperFlowFormStep,
HelperFlowMenuStep,
from homeassistant.helpers.schema_config_entry_flow import (
SchemaConfigFlowHandler,
SchemaFlowFormStep,
SchemaFlowMenuStep,
)
from .const import (
@@ -37,8 +37,9 @@ UNIT_PREFIXES = [
{"value": "m", "label": "m (milli)"},
{"value": "k", "label": "k (kilo)"},
{"value": "M", "label": "M (mega)"},
{"value": "G", "label": "T (tera)"},
{"value": "T", "label": "P (peta)"},
{"value": "G", "label": "G (giga)"},
{"value": "T", "label": "T (tera)"},
{"value": "P", "label": "P (peta)"},
]
TIME_UNITS = [
{"value": TIME_SECONDS, "label": "Seconds"},
@@ -78,16 +79,16 @@ CONFIG_SCHEMA = vol.Schema(
}
).extend(OPTIONS_SCHEMA.schema)
CONFIG_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"user": HelperFlowFormStep(CONFIG_SCHEMA)
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"user": SchemaFlowFormStep(CONFIG_SCHEMA)
}
OPTIONS_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"init": HelperFlowFormStep(OPTIONS_SCHEMA)
OPTIONS_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"init": SchemaFlowFormStep(OPTIONS_SCHEMA)
}
class ConfigFlowHandler(HelperConfigFlowHandler, domain=DOMAIN):
class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config or options flow for Derivative."""
config_flow = CONFIG_FLOW
@@ -250,8 +250,10 @@ class DerivativeSensor(RestoreEntity, SensorEntity):
self._state = derivative
self.async_write_ha_state()
async_track_state_change_event(
self.hass, self._sensor_source_id, calc_derivative
self.async_on_remove(
async_track_state_change_event(
self.hass, self._sensor_source_id, calc_derivative
)
)
@property
@@ -20,7 +20,6 @@ from homeassistant.helpers import (
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.frame import report
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import IntegrationNotFound, bind_hass
from homeassistant.requirements import async_get_integration_with_requirements
@@ -88,24 +87,6 @@ TYPES = {
}
@bind_hass
async def async_get_device_automations(
hass: HomeAssistant,
automation_type: DeviceAutomationType | str,
device_ids: Iterable[str] | None = None,
) -> Mapping[str, Any]:
"""Return all the device automations for a type optionally limited to specific device ids."""
if isinstance(automation_type, str):
report(
"uses str for async_get_device_automations automation_type. This is "
"deprecated and will stop working in Home Assistant 2022.4, it should be "
"updated to use DeviceAutomationType instead",
error_if_core=False,
)
automation_type = DeviceAutomationType[automation_type.upper()]
return await _async_get_device_automations(hass, automation_type, device_ids)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up device automation."""
websocket_api.async_register_command(hass, websocket_device_automation_list_actions)
@@ -156,26 +137,18 @@ async def async_get_device_automation_platform( # noqa: D103
@overload
async def async_get_device_automation_platform( # noqa: D103
hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType | str
hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType
) -> "DeviceAutomationPlatformType":
...
async def async_get_device_automation_platform(
hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType | str
hass: HomeAssistant, domain: str, automation_type: DeviceAutomationType
) -> "DeviceAutomationPlatformType":
"""Load device automation platform for integration.
Throws InvalidDeviceAutomationConfig if the integration is not found or does not support device automation.
"""
if isinstance(automation_type, str):
report(
"uses str for async_get_device_automation_platform automation_type. This "
"is deprecated and will stop working in Home Assistant 2022.4, it should "
"be updated to use DeviceAutomationType instead",
error_if_core=False,
)
automation_type = DeviceAutomationType[automation_type.upper()]
platform_name = automation_type.value.section
try:
integration = await async_get_integration_with_requirements(hass, domain)
@@ -215,10 +188,11 @@ async def _async_get_device_automations_from_domain(
)
async def _async_get_device_automations(
@bind_hass
async def async_get_device_automations(
hass: HomeAssistant,
automation_type: DeviceAutomationType,
device_ids: Iterable[str] | None,
device_ids: Iterable[str] | None = None,
) -> Mapping[str, list[dict[str, Any]]]:
"""List device automations."""
device_registry = dr.async_get(hass)
@@ -336,7 +310,7 @@ async def websocket_device_automation_list_actions(hass, connection, msg):
"""Handle request for device actions."""
device_id = msg["device_id"]
actions = (
await _async_get_device_automations(
await async_get_device_automations(
hass, DeviceAutomationType.ACTION, [device_id]
)
).get(device_id)
@@ -355,7 +329,7 @@ async def websocket_device_automation_list_conditions(hass, connection, msg):
"""Handle request for device conditions."""
device_id = msg["device_id"]
conditions = (
await _async_get_device_automations(
await async_get_device_automations(
hass, DeviceAutomationType.CONDITION, [device_id]
)
).get(device_id)
@@ -374,7 +348,7 @@ async def websocket_device_automation_list_triggers(hass, connection, msg):
"""Handle request for device triggers."""
device_id = msg["device_id"]
triggers = (
await _async_get_device_automations(
await async_get_device_automations(
hass, DeviceAutomationType.TRIGGER, [device_id]
)
).get(device_id)
@@ -1,15 +1,11 @@
"""The dnsip component."""
from __future__ import annotations
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import PLATFORMS
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up DNS IP from a config entry."""
@@ -82,14 +82,6 @@ class DnsIPConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Return Option handler."""
return DnsIPOptionsFlowHandler(config_entry)
async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Import a configuration from config.yaml."""
hostname = config.get(CONF_HOSTNAME, DEFAULT_HOSTNAME)
self._async_abort_entries_match({CONF_HOSTNAME: hostname})
config[CONF_HOSTNAME] = hostname
return await self.async_step_user(user_input=config)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
+2 -44
View File
@@ -6,20 +6,14 @@ import logging
import aiodns
from aiodns.error import DNSError
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
SensorEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import (
CONF_HOSTNAME,
@@ -27,10 +21,6 @@ from .const import (
CONF_IPV6,
CONF_RESOLVER,
CONF_RESOLVER_IPV6,
DEFAULT_HOSTNAME,
DEFAULT_IPV6,
DEFAULT_RESOLVER,
DEFAULT_RESOLVER_IPV6,
DOMAIN,
)
@@ -38,38 +28,6 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=120)
PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_HOSTNAME, default=DEFAULT_HOSTNAME): cv.string,
vol.Optional(CONF_RESOLVER, default=DEFAULT_RESOLVER): cv.string,
vol.Optional(CONF_RESOLVER_IPV6, default=DEFAULT_RESOLVER_IPV6): cv.string,
vol.Optional(CONF_IPV6, default=DEFAULT_IPV6): cv.boolean,
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_devices: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the DNS IP sensor."""
_LOGGER.warning(
"Configuration of the DNS IP platform in YAML is deprecated and will be "
"removed in Home Assistant 2022.4; Your existing configuration "
"has been imported into the UI automatically and can be safely removed "
"from your configuration.yaml file"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
@@ -2,7 +2,7 @@
"domain": "environment_canada",
"name": "Environment Canada",
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
"requirements": ["env_canada==0.5.20"],
"requirements": ["env_canada==0.5.21"],
"codeowners": ["@gwww", "@michaeldavie"],
"config_flow": true,
"iot_class": "cloud_polling",
@@ -209,13 +209,23 @@ SENSOR_TYPES: tuple[ECSensorEntityDescription, ...] = (
),
)
def _get_aqhi_value(data):
if (aqhi := data.current) is not None:
return aqhi
if data.forecasts and (hourly := data.forecasts.get("hourly")) is not None:
if values := list(hourly.values()):
return values[0]
return None
AQHI_SENSOR = ECSensorEntityDescription(
key="aqhi",
name="AQHI",
device_class=SensorDeviceClass.AQI,
native_unit_of_measurement="AQI",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.current,
value_fn=_get_aqhi_value,
)
ALERT_TYPES: tuple[ECSensorEntityDescription, ...] = (
@@ -80,12 +80,16 @@ class ECWeather(CoordinatorEntity, WeatherEntity):
@property
def temperature(self):
"""Return the temperature."""
if self.ec_data.conditions.get("temperature", {}).get("value"):
return float(self.ec_data.conditions["temperature"]["value"])
if self.ec_data.hourly_forecasts and self.ec_data.hourly_forecasts[0].get(
"temperature"
if (
temperature := self.ec_data.conditions.get("temperature", {}).get("value")
) is not None:
return float(temperature)
if (
self.ec_data.hourly_forecasts
and (temperature := self.ec_data.hourly_forecasts[0].get("temperature"))
is not None
):
return float(self.ec_data.hourly_forecasts[0]["temperature"])
return float(temperature)
return None
@property
@@ -520,6 +520,7 @@ async def _cleanup_instance(
data = domain_data.pop_entry_data(entry)
for disconnect_cb in data.disconnect_callbacks:
disconnect_cb()
data.disconnect_callbacks = []
for cleanup_callback in data.cleanup_callbacks:
cleanup_callback()
await data.client.disconnect()
+1 -57
View File
@@ -7,18 +7,16 @@ from pyezviz.exceptions import HTTPError, InvalidHost, PyEzvizError
import voluptuous as vol
from homeassistant.components import ffmpeg
from homeassistant.components.camera import PLATFORM_SCHEMA, SUPPORT_STREAM, Camera
from homeassistant.components.camera import SUPPORT_STREAM, Camera
from homeassistant.components.ffmpeg import get_ffmpeg_manager
from homeassistant.config_entries import (
SOURCE_IGNORE,
SOURCE_IMPORT,
SOURCE_INTEGRATION_DISCOVERY,
ConfigEntry,
)
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import (
ATTR_DIRECTION,
@@ -27,7 +25,6 @@ from .const import (
ATTR_SERIAL,
ATTR_SPEED,
ATTR_TYPE,
CONF_CAMERAS,
CONF_FFMPEG_ARGUMENTS,
DATA_COORDINATOR,
DEFAULT_CAMERA_USERNAME,
@@ -47,62 +44,9 @@ from .const import (
from .coordinator import EzvizDataUpdateCoordinator
from .entity import EzvizEntity
CAMERA_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string}
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CAMERAS, default={}): {cv.string: CAMERA_SCHEMA},
}
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: entity_platform.AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up a Ezviz IP Camera from platform config."""
_LOGGER.warning(
"Loading ezviz via platform config is deprecated, it will be automatically imported. Please remove it afterwards"
)
# Check if entry config exists and skips import if it does.
if hass.config_entries.async_entries(DOMAIN):
return
# Check if importing camera account.
if CONF_CAMERAS in config:
cameras_conf = config[CONF_CAMERAS]
for serial, camera in cameras_conf.items():
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
ATTR_SERIAL: serial,
CONF_USERNAME: camera[CONF_USERNAME],
CONF_PASSWORD: camera[CONF_PASSWORD],
},
)
)
# Check if importing main ezviz cloud account.
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
@@ -307,50 +307,6 @@ class EzvizConfigFlow(ConfigFlow, domain=DOMAIN):
},
)
async def async_step_import(self, import_config):
"""Handle config import from yaml."""
_LOGGER.debug("import config: %s", import_config)
# Check importing camera.
if ATTR_SERIAL in import_config:
return await self.async_step_import_camera(import_config)
# Validate and setup of main ezviz cloud account.
try:
return await self._validate_and_create_auth(import_config)
except InvalidURL:
_LOGGER.error("Error importing Ezviz platform config: invalid host")
return self.async_abort(reason="invalid_host")
except InvalidHost:
_LOGGER.error("Error importing Ezviz platform config: cannot connect")
return self.async_abort(reason="cannot_connect")
except (AuthTestResultFailed, PyEzvizError):
_LOGGER.error("Error importing Ezviz platform config: invalid auth")
return self.async_abort(reason="invalid_auth")
except Exception: # pylint: disable=broad-except
_LOGGER.exception(
"Error importing ezviz platform config: unexpected exception"
)
return self.async_abort(reason="unknown")
async def async_step_import_camera(self, data):
"""Create RTSP auth entry per camera in config."""
await self.async_set_unique_id(data[ATTR_SERIAL])
self._abort_if_unique_id_configured()
_LOGGER.debug("Create camera with: %s", data)
cam_serial = data.pop(ATTR_SERIAL)
data[CONF_TYPE] = ATTR_TYPE_CAMERA
return self.async_create_entry(title=cam_serial, data=data)
class EzvizOptionsFlowHandler(OptionsFlow):
"""Handle Ezviz client options."""
-1
View File
@@ -5,7 +5,6 @@ MANUFACTURER = "Ezviz"
# Configuration
ATTR_SERIAL = "serial"
CONF_CAMERAS = "cameras"
CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments"
ATTR_HOME = "HOME_MODE"
ATTR_AWAY = "AWAY_MODE"
+12
View File
@@ -0,0 +1,12 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from . import ATTR_PRESET_MODES
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static attributes from being recorded in the database."""
return {ATTR_PRESET_MODES}
+13
View File
@@ -91,6 +91,11 @@ def _cleanup_entity_filter(device: er.RegistryEntry) -> bool:
)
def _ha_is_stopping(activity: str) -> None:
"""Inform that HA is stopping."""
_LOGGER.info("Cannot execute %s: HomeAssistant is shutting down", activity)
class ClassSetupMissing(Exception):
"""Raised when a Class func is called before setup."""
@@ -351,6 +356,10 @@ class FritzBoxTools(update_coordinator.DataUpdateCoordinator):
def scan_devices(self, now: datetime | None = None) -> None:
"""Scan for new devices and return a list of found device ids."""
if self.hass.is_stopping:
_ha_is_stopping("scan devices")
return
_LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host)
self._update_available, self._latest_firmware = self._update_device_info()
@@ -603,6 +612,10 @@ class AvmWrapper(FritzBoxTools):
) -> dict:
"""Return service details."""
if self.hass.is_stopping:
_ha_is_stopping(f"{service_name}/{action_name}")
return {}
if f"{service_name}{service_suffix}" not in self.connection.services:
return {}
@@ -1,6 +1,7 @@
"""Config flow to configure the FRITZ!Box Tools integration."""
from __future__ import annotations
import ipaddress
import logging
import socket
from typing import Any
@@ -129,6 +130,9 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
)
self.context[CONF_HOST] = self._host
if ipaddress.ip_address(self._host).is_link_local:
return self.async_abort(reason="ignore_ip6_link_local")
if uuid := discovery_info.upnp.get(ssdp.ATTR_UPNP_UDN):
if uuid.startswith("uuid:"):
uuid = uuid[5:]
@@ -32,6 +32,7 @@
"abort": {
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"ignore_ip6_link_local": "IPv6 link local address is not supported.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
@@ -3,13 +3,13 @@
"abort": {
"already_configured": "Device is already configured",
"already_in_progress": "Configuration flow is already in progress",
"ignore_ip6_link_local": "IPv6 link local address is not supported.",
"reauth_successful": "Re-authentication was successful"
},
"error": {
"already_configured": "Device is already configured",
"already_in_progress": "Configuration flow is already in progress",
"cannot_connect": "Failed to connect",
"connection_error": "Failed to connect",
"invalid_auth": "Invalid authentication",
"upnp_not_configured": "Missing UPnP settings on device."
},
@@ -31,16 +31,6 @@
"description": "Update FRITZ!Box Tools credentials for: {host}.\n\nFRITZ!Box Tools is unable to log in to your FRITZ!Box.",
"title": "Updating FRITZ!Box Tools - credentials"
},
"start_config": {
"data": {
"host": "Host",
"password": "Password",
"port": "Port",
"username": "Username"
},
"description": "Setup FRITZ!Box Tools to control your FRITZ!Box.\nMinimum needed: username, password.",
"title": "Setup FRITZ!Box Tools - mandatory"
},
"user": {
"data": {
"host": "Host",
@@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.dhcp import DhcpServiceInfo
from homeassistant.const import CONF_HOST, CONF_RESOURCE
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
@@ -110,10 +110,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_import(self, conf: dict) -> FlowResult:
"""Import a configuration from config.yaml."""
return await self.async_step_user(user_input={CONF_HOST: conf[CONF_RESOURCE]})
async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> FlowResult:
"""Handle a flow initiated by the DHCP client."""
for entry in self._async_current_entries(include_ignore=False):
+1 -39
View File
@@ -1,23 +1,17 @@
"""Support for Fronius devices."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Final
import voluptuous as vol
from homeassistant.components.sensor import (
DOMAIN as SENSOR_DOMAIN,
PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_MONITORED_CONDITIONS,
CONF_RESOURCE,
ELECTRIC_CURRENT_AMPERE,
ELECTRIC_POTENTIAL_VOLT,
ENERGY_WATT_HOUR,
@@ -29,10 +23,8 @@ from homeassistant.const import (
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo, EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
@@ -49,38 +41,8 @@ if TYPE_CHECKING:
FroniusStorageUpdateCoordinator,
)
_LOGGER: Final = logging.getLogger(__name__)
ENERGY_VOLT_AMPERE_REACTIVE_HOUR: Final = "varh"
PLATFORM_SCHEMA = vol.All(
PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_MONITORED_CONDITIONS): object,
}
),
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Import Fronius configuration from yaml."""
_LOGGER.warning(
"Loading Fronius via platform setup is deprecated. Please remove it from your yaml configuration"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)
async def async_setup_entry(
hass: HomeAssistant,
@@ -2,7 +2,7 @@
"domain": "frontend",
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": ["home-assistant-frontend==20220330.0"],
"requirements": ["home-assistant-frontend==20220401.0"],
"dependencies": [
"api",
"auth",
@@ -4,12 +4,13 @@ from __future__ import annotations
import contextlib
from errno import EHOSTUNREACH, EIO
from functools import partial
import imghdr
import io
import logging
from types import MappingProxyType
from typing import Any
from urllib.parse import urlparse, urlunparse
import PIL
from async_timeout import timeout
import av
from httpx import HTTPStatusError, RequestError, TimeoutException
@@ -57,7 +58,7 @@ DEFAULT_DATA = {
CONF_VERIFY_SSL: True,
}
SUPPORTED_IMAGE_TYPES = ["png", "jpeg", "svg+xml"]
SUPPORTED_IMAGE_TYPES = {"png", "jpeg", "gif", "svg+xml"}
def build_schema(
@@ -110,11 +111,16 @@ def build_schema(
def get_image_type(image):
"""Get the format of downloaded bytes that could be an image."""
fmt = imghdr.what(None, h=image)
fmt = None
imagefile = io.BytesIO(image)
with contextlib.suppress(PIL.UnidentifiedImageError):
img = PIL.Image.open(imagefile)
fmt = img.format.lower()
if fmt is None:
# if imghdr can't figure it out, could be svg.
# if PIL can't figure it out, could be svg.
with contextlib.suppress(UnicodeDecodeError):
if image.decode("utf-8").startswith("<svg"):
if image.decode("utf-8").lstrip().startswith("<svg"):
return "svg+xml"
return fmt
@@ -2,7 +2,7 @@
"domain": "generic",
"name": "Generic Camera",
"config_flow": true,
"requirements": ["av==9.0.0"],
"requirements": ["av==9.0.0", "pillow==9.0.1"],
"documentation": "https://www.home-assistant.io/integrations/generic",
"codeowners": ["@davet2001"],
"iot_class": "local_push"
+7 -3
View File
@@ -183,9 +183,13 @@ class GoogleCalendarService:
"""Get the calendar service with valid credetnails."""
await self._session.async_ensure_token_valid()
creds = _async_google_creds(self._hass, self._session.token)
return google_discovery.build(
"calendar", "v3", credentials=creds, cache_discovery=False
)
def _build() -> google_discovery.Resource:
return google_discovery.build(
"calendar", "v3", credentials=creds, cache_discovery=False
)
return await self._hass.async_add_executor_job(_build)
async def async_list_calendars(
self,
+2 -2
View File
@@ -454,7 +454,7 @@ class GroupEntity(Entity):
self.async_update_group_state()
self.async_write_ha_state()
start.async_at_start(self.hass, _update_at_start)
self.async_on_remove(start.async_at_start(self.hass, _update_at_start))
@callback
def async_defer_or_update_ha_state(self) -> None:
@@ -689,7 +689,7 @@ class Group(Entity):
async def async_added_to_hass(self):
"""Handle addition to Home Assistant."""
start.async_at_start(self.hass, self._async_start)
self.async_on_remove(start.async_at_start(self.hass, self._async_start))
async def async_will_remove_from_hass(self):
"""Handle removal from Home Assistant."""
+28 -28
View File
@@ -10,11 +10,11 @@ import voluptuous as vol
from homeassistant.const import CONF_ENTITIES
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er, selector
from homeassistant.helpers.helper_config_entry_flow import (
HelperConfigFlowHandler,
HelperFlowFormStep,
HelperFlowMenuStep,
HelperOptionsFlowHandler,
from homeassistant.helpers.schema_config_entry_flow import (
SchemaConfigFlowHandler,
SchemaFlowFormStep,
SchemaFlowMenuStep,
SchemaOptionsFlowHandler,
entity_selector_without_own_entities,
)
@@ -25,11 +25,11 @@ from .const import CONF_HIDE_MEMBERS
def basic_group_options_schema(
domain: str,
handler: HelperConfigFlowHandler | HelperOptionsFlowHandler,
handler: SchemaConfigFlowHandler | SchemaOptionsFlowHandler,
options: dict[str, Any],
) -> vol.Schema:
"""Generate options schema."""
handler = cast(HelperOptionsFlowHandler, handler)
handler = cast(SchemaOptionsFlowHandler, handler)
return vol.Schema(
{
vol.Required(CONF_ENTITIES): entity_selector_without_own_entities(
@@ -58,7 +58,7 @@ def basic_group_config_schema(domain: str) -> vol.Schema:
def binary_sensor_options_schema(
handler: HelperConfigFlowHandler | HelperOptionsFlowHandler,
handler: SchemaConfigFlowHandler | SchemaOptionsFlowHandler,
options: dict[str, Any],
) -> vol.Schema:
"""Generate options schema."""
@@ -78,7 +78,7 @@ BINARY_SENSOR_CONFIG_SCHEMA = basic_group_config_schema("binary_sensor").extend(
def light_switch_options_schema(
domain: str,
handler: HelperConfigFlowHandler | HelperOptionsFlowHandler,
handler: SchemaConfigFlowHandler | SchemaOptionsFlowHandler,
options: dict[str, Any],
) -> vol.Schema:
"""Generate options schema."""
@@ -119,45 +119,45 @@ def set_group_type(group_type: str) -> Callable[[dict[str, Any]], dict[str, Any]
return _set_group_type
CONFIG_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"user": HelperFlowMenuStep(GROUP_TYPES),
"binary_sensor": HelperFlowFormStep(
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"user": SchemaFlowMenuStep(GROUP_TYPES),
"binary_sensor": SchemaFlowFormStep(
BINARY_SENSOR_CONFIG_SCHEMA, set_group_type("binary_sensor")
),
"cover": HelperFlowFormStep(
"cover": SchemaFlowFormStep(
basic_group_config_schema("cover"), set_group_type("cover")
),
"fan": HelperFlowFormStep(basic_group_config_schema("fan"), set_group_type("fan")),
"light": HelperFlowFormStep(
"fan": SchemaFlowFormStep(basic_group_config_schema("fan"), set_group_type("fan")),
"light": SchemaFlowFormStep(
basic_group_config_schema("light"), set_group_type("light")
),
"lock": HelperFlowFormStep(
"lock": SchemaFlowFormStep(
basic_group_config_schema("lock"), set_group_type("lock")
),
"media_player": HelperFlowFormStep(
"media_player": SchemaFlowFormStep(
basic_group_config_schema("media_player"), set_group_type("media_player")
),
"switch": HelperFlowFormStep(
"switch": SchemaFlowFormStep(
basic_group_config_schema("switch"), set_group_type("switch")
),
}
OPTIONS_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"init": HelperFlowFormStep(None, next_step=choose_options_step),
"binary_sensor": HelperFlowFormStep(binary_sensor_options_schema),
"cover": HelperFlowFormStep(partial(basic_group_options_schema, "cover")),
"fan": HelperFlowFormStep(partial(basic_group_options_schema, "fan")),
"light": HelperFlowFormStep(partial(light_switch_options_schema, "light")),
"lock": HelperFlowFormStep(partial(basic_group_options_schema, "lock")),
"media_player": HelperFlowFormStep(
OPTIONS_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"init": SchemaFlowFormStep(None, next_step=choose_options_step),
"binary_sensor": SchemaFlowFormStep(binary_sensor_options_schema),
"cover": SchemaFlowFormStep(partial(basic_group_options_schema, "cover")),
"fan": SchemaFlowFormStep(partial(basic_group_options_schema, "fan")),
"light": SchemaFlowFormStep(partial(light_switch_options_schema, "light")),
"lock": SchemaFlowFormStep(partial(basic_group_options_schema, "lock")),
"media_player": SchemaFlowFormStep(
partial(basic_group_options_schema, "media_player")
),
"switch": HelperFlowFormStep(partial(light_switch_options_schema, "switch")),
"switch": SchemaFlowFormStep(partial(light_switch_options_schema, "switch")),
}
class GroupConfigFlowHandler(HelperConfigFlowHandler, domain=DOMAIN):
class GroupConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config or options flow for groups."""
config_flow = CONFIG_FLOW
@@ -1,9 +1,9 @@
{
"domain": "hangouts",
"name": "Google Hangouts",
"name": "Google Chat",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hangouts",
"requirements": ["hangups==0.4.17"],
"requirements": ["hangups==0.4.18"],
"codeowners": [],
"iot_class": "cloud_push",
"loggers": ["hangups", "urwid"]
@@ -16,7 +16,7 @@
"password": "[%key:common::config_flow::data::password%]",
"authorization_code": "Authorization Code (required for manual authentication)"
},
"title": "Google Hangouts Login"
"title": "Google Chat Login"
},
"2fa": {
"data": {
@@ -22,7 +22,7 @@
"email": "Email",
"password": "Password"
},
"title": "Google Hangouts Login"
"title": "Google Chat Login"
}
}
}
@@ -51,6 +51,7 @@ from .auth import async_setup_auth_view
from .const import (
ATTR_ADDON,
ATTR_ADDONS,
ATTR_AUTO_UPDATE,
ATTR_CHANGELOG,
ATTR_DISCOVERY,
ATTR_FOLDERS,
@@ -98,6 +99,7 @@ DATA_INFO = "hassio_info"
DATA_OS_INFO = "hassio_os_info"
DATA_SUPERVISOR_INFO = "hassio_supervisor_info"
DATA_ADDONS_CHANGELOGS = "hassio_addons_changelogs"
DATA_ADDONS_INFO = "hassio_addons_info"
DATA_ADDONS_STATS = "hassio_addons_stats"
HASSIO_UPDATE_INTERVAL = timedelta(minutes=5)
@@ -422,6 +424,16 @@ def get_supervisor_info(hass):
return hass.data.get(DATA_SUPERVISOR_INFO)
@callback
@bind_hass
def get_addons_info(hass):
"""Return Addons info.
Async friendly.
"""
return hass.data.get(DATA_ADDONS_INFO)
@callback
@bind_hass
def get_addons_stats(hass):
@@ -607,6 +619,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
changelog = await hassio.get_addon_changelog(slug)
return (slug, changelog)
async def update_addon_info(slug):
"""Return the info for an add-on."""
info = await hassio.get_addon_info(slug)
return (slug, info)
async def update_info_data(now):
"""Update last available supervisor information."""
@@ -641,6 +658,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
*[update_addon_changelog(addon[ATTR_SLUG]) for addon in addons]
)
)
hass.data[DATA_ADDONS_INFO] = dict(
await asyncio.gather(
*[update_addon_info(addon[ATTR_SLUG]) for addon in addons]
)
)
if ADDONS_COORDINATOR in hass.data:
await hass.data[ADDONS_COORDINATOR].async_refresh()
@@ -845,6 +867,7 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
"""Update data via library."""
new_data = {}
supervisor_info = get_supervisor_info(self.hass)
addons_info = get_addons_info(self.hass)
addons_stats = get_addons_stats(self.hass)
addons_changelogs = get_addons_changelogs(self.hass)
store_data = get_store(self.hass)
@@ -858,6 +881,9 @@ class HassioDataUpdateCoordinator(DataUpdateCoordinator):
addon[ATTR_SLUG]: {
**addon,
**((addons_stats or {}).get(addon[ATTR_SLUG], {})),
ATTR_AUTO_UPDATE: addons_info.get(addon[ATTR_SLUG], {}).get(
ATTR_AUTO_UPDATE, False
),
ATTR_CHANGELOG: (addons_changelogs or {}).get(addon[ATTR_SLUG]),
ATTR_REPOSITORY: repositories.get(
addon.get(ATTR_REPOSITORY), addon.get(ATTR_REPOSITORY, "")
+1
View File
@@ -39,6 +39,7 @@ WS_TYPE_SUBSCRIBE = "supervisor/subscribe"
EVENT_SUPERVISOR_EVENT = "supervisor_event"
ATTR_AUTO_UPDATE = "auto_update"
ATTR_VERSION = "version"
ATTR_VERSION_LATEST = "version_latest"
ATTR_UPDATE_AVAILABLE = "update_available"
+17 -7
View File
@@ -24,6 +24,7 @@ from . import (
async_update_supervisor,
)
from .const import (
ATTR_AUTO_UPDATE,
ATTR_CHANGELOG,
ATTR_VERSION,
ATTR_VERSION_LATEST,
@@ -99,6 +100,11 @@ class SupervisorAddonUpdateEntity(HassioAddonEntity, UpdateEntity):
"""Return the add-on data."""
return self.coordinator.data[DATA_KEY_ADDONS][self._addon_slug]
@property
def auto_update(self):
"""Return true if auto-update is enabled for the add-on."""
return self._addon_data[ATTR_AUTO_UPDATE]
@property
def title(self) -> str | None:
"""Return the title of the update."""
@@ -110,8 +116,8 @@ class SupervisorAddonUpdateEntity(HassioAddonEntity, UpdateEntity):
return self._addon_data[ATTR_VERSION_LATEST]
@property
def current_version(self) -> str | None:
"""Version currently in use."""
def installed_version(self) -> str | None:
"""Version installed and in use."""
return self._addon_data[ATTR_VERSION]
@property
@@ -133,9 +139,12 @@ class SupervisorAddonUpdateEntity(HassioAddonEntity, UpdateEntity):
if (notes := self._addon_data[ATTR_CHANGELOG]) is None:
return None
if f"# {self.latest_version}" in notes and f"# {self.current_version}" in notes:
if (
f"# {self.latest_version}" in notes
and f"# {self.installed_version}" in notes
):
# Split the release notes to only what is between the versions if we can
new_notes = notes.split(f"# {self.current_version}")[0]
new_notes = notes.split(f"# {self.installed_version}")[0]
if f"# {self.latest_version}" in new_notes:
# Make sure the latest version is still there.
# This can be False if the order of the release notes are not correct
@@ -176,7 +185,7 @@ class SupervisorOSUpdateEntity(HassioOSEntity, UpdateEntity):
return self.coordinator.data[DATA_KEY_OS][ATTR_VERSION_LATEST]
@property
def current_version(self) -> str:
def installed_version(self) -> str:
"""Return native value of entity."""
return self.coordinator.data[DATA_KEY_OS][ATTR_VERSION]
@@ -210,6 +219,7 @@ class SupervisorOSUpdateEntity(HassioOSEntity, UpdateEntity):
class SupervisorSupervisorUpdateEntity(HassioSupervisorEntity, UpdateEntity):
"""Update entity to handle updates for the Home Assistant Supervisor."""
_attr_auto_update = True
_attr_supported_features = UpdateEntityFeature.INSTALL
_attr_title = "Home Assistant Supervisor"
@@ -219,7 +229,7 @@ class SupervisorSupervisorUpdateEntity(HassioSupervisorEntity, UpdateEntity):
return self.coordinator.data[DATA_KEY_SUPERVISOR][ATTR_VERSION_LATEST]
@property
def current_version(self) -> str:
def installed_version(self) -> str:
"""Return native value of entity."""
return self.coordinator.data[DATA_KEY_SUPERVISOR][ATTR_VERSION]
@@ -264,7 +274,7 @@ class SupervisorCoreUpdateEntity(HassioCoreEntity, UpdateEntity):
return self.coordinator.data[DATA_KEY_CORE][ATTR_VERSION_LATEST]
@property
def current_version(self) -> str:
def installed_version(self) -> str:
"""Return native value of entity."""
return self.coordinator.data[DATA_KEY_CORE][ATTR_VERSION]
@@ -247,7 +247,7 @@ class HERETravelTimeSensor(SensorEntity, CoordinatorEntity):
async def _update_at_start(_):
await self.async_update()
async_at_start(self.hass, _update_at_start)
self.async_on_remove(async_at_start(self.hass, _update_at_start))
@property
def native_value(self) -> str | None:
+29 -18
View File
@@ -466,7 +466,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
entity_filter = self.hk_options.get(CONF_FILTER, {})
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
all_supported_entities = _async_get_matching_entities(self.hass, domains)
all_supported_entities = _async_get_matching_entities(
self.hass, domains, include_entity_category=True
)
# In accessory mode we can only have one
default_value = next(
iter(
@@ -505,7 +507,9 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
entity_filter = self.hk_options.get(CONF_FILTER, {})
entities = entity_filter.get(CONF_INCLUDE_ENTITIES, [])
all_supported_entities = _async_get_matching_entities(self.hass, domains)
all_supported_entities = _async_get_matching_entities(
self.hass, domains, include_entity_category=True
)
if not entities:
entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, [])
# Strip out entities that no longer exist to prevent error in the UI
@@ -559,21 +563,7 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
all_supported_entities = _async_get_matching_entities(self.hass, domains)
if not entities:
entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, [])
ent_reg = entity_registry.async_get(self.hass)
excluded_entities = set()
for entity_id in all_supported_entities:
if ent_reg_ent := ent_reg.async_get(entity_id):
if (
ent_reg_ent.entity_category is not None
or ent_reg_ent.hidden_by is not None
):
excluded_entities.add(entity_id)
# Remove entity category entities since we will exclude them anyways
all_supported_entities = {
k: v
for k, v in all_supported_entities.items()
if k not in excluded_entities
}
# Strip out entities that no longer exist to prevent error in the UI
default_value = [
entity_id for entity_id in entities if entity_id in all_supported_entities
@@ -652,16 +642,37 @@ async def _async_get_supported_devices(hass: HomeAssistant) -> dict[str, str]:
return dict(sorted(unsorted.items(), key=lambda item: item[1]))
def _exclude_by_entity_registry(
ent_reg: entity_registry.EntityRegistry,
entity_id: str,
include_entity_category: bool,
) -> bool:
"""Filter out hidden entities and ones with entity category (unless specified)."""
return bool(
(entry := ent_reg.async_get(entity_id))
and (
entry.hidden_by is not None
or (not include_entity_category or entry.entity_category is not None)
)
)
def _async_get_matching_entities(
hass: HomeAssistant, domains: list[str] | None = None
hass: HomeAssistant,
domains: list[str] | None = None,
include_entity_category: bool = False,
) -> dict[str, str]:
"""Fetch all entities or entities in the given domains."""
ent_reg = entity_registry.async_get(hass)
return {
state.entity_id: f"{state.attributes.get(ATTR_FRIENDLY_NAME, state.entity_id)} ({state.entity_id})"
for state in sorted(
hass.states.async_all(domains and set(domains)),
key=lambda item: item.entity_id,
)
if not _exclude_by_entity_registry(
ent_reg, state.entity_id, include_entity_category
)
}
@@ -81,6 +81,10 @@ async def async_setup_devices(bridge: "HueBridge"):
dev_reg, entry.entry_id
):
if device not in known_devices:
# handle case where a virtual device was created for a Hue group
hue_dev_id = next(x[1] for x in device.identifiers if x[0] == DOMAIN)
if hue_dev_id in api.groups:
continue
dev_reg.async_remove_device(device.id)
# add listener for updates on Hue devices controller
+1 -1
View File
@@ -194,7 +194,7 @@ class HueLight(HueBaseEntity, LightEntity):
brightness = normalize_hue_brightness(kwargs.get(ATTR_BRIGHTNESS))
flash = kwargs.get(ATTR_FLASH)
effect = effect_str = kwargs.get(ATTR_EFFECT)
if effect_str == EFFECT_NONE:
if effect_str in (EFFECT_NONE, EFFECT_NONE.lower()):
effect = EffectStatus.NO_EFFECT
elif effect_str is not None:
# work out if we got a regular effect or timed effect
@@ -0,0 +1,16 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from . import ATTR_AVAILABLE_MODES, ATTR_MAX_HUMIDITY, ATTR_MIN_HUMIDITY
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static attributes from being recorded in the database."""
return {
ATTR_MIN_HUMIDITY,
ATTR_MAX_HUMIDITY,
ATTR_AVAILABLE_MODES,
}
+2 -48
View File
@@ -1,13 +1,10 @@
"""The iCloud component."""
import logging
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant, ServiceCall
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from .account import IcloudAccount
@@ -15,9 +12,6 @@ from .const import (
CONF_GPS_ACCURACY_THRESHOLD,
CONF_MAX_INTERVAL,
CONF_WITH_FAMILY,
DEFAULT_GPS_ACCURACY_THRESHOLD,
DEFAULT_MAX_INTERVAL,
DEFAULT_WITH_FAMILY,
DOMAIN,
PLATFORMS,
STORAGE_KEY,
@@ -69,47 +63,7 @@ SERVICE_SCHEMA_LOST_DEVICE = vol.Schema(
}
)
ACCOUNT_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_WITH_FAMILY, default=DEFAULT_WITH_FAMILY): cv.boolean,
vol.Optional(CONF_MAX_INTERVAL, default=DEFAULT_MAX_INTERVAL): cv.positive_int,
vol.Optional(
CONF_GPS_ACCURACY_THRESHOLD, default=DEFAULT_GPS_ACCURACY_THRESHOLD
): cv.positive_int,
}
)
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema(vol.All(cv.ensure_list, [ACCOUNT_SCHEMA]))},
extra=vol.ALLOW_EXTRA,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up iCloud from legacy config file."""
if (conf := config.get(DOMAIN)) is None:
return True
# Note: need to remember to cleanup device_tracker (remove async_setup_scanner)
_LOGGER.warning(
"Configuration of the iCloud integration in YAML is deprecated and "
"will be removed in Home Assistant 2022.4; Your existing configuration "
"has been imported into the UI automatically and can be safely removed "
"from your configuration.yaml file"
)
for account_conf in conf:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=account_conf
)
)
return True
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -172,10 +172,6 @@ class IcloudFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._validate_and_create_entry(user_input, "user")
async def async_step_import(self, user_input):
"""Import a config entry."""
return await self.async_step_user(user_input)
async def async_step_reauth(self, user_input=None):
"""Update password for a config entry that can't authenticate."""
# Store existing entry data so it can be used later and set unique ID
@@ -1,5 +1,6 @@
{
"domain": "input_boolean",
"integration_type": "helper",
"name": "Input Boolean",
"documentation": "https://www.home-assistant.io/integrations/input_boolean",
"codeowners": ["@home-assistant/core"],
@@ -1,5 +1,6 @@
{
"domain": "input_button",
"integration_type": "helper",
"name": "Input Button",
"documentation": "https://www.home-assistant.io/integrations/input_button",
"codeowners": ["@home-assistant/core"],
@@ -1,5 +1,6 @@
{
"domain": "input_datetime",
"integration_type": "helper",
"name": "Input Datetime",
"documentation": "https://www.home-assistant.io/integrations/input_datetime",
"codeowners": ["@home-assistant/core"],
@@ -1,5 +1,6 @@
{
"domain": "input_number",
"integration_type": "helper",
"name": "Input Number",
"documentation": "https://www.home-assistant.io/integrations/input_number",
"codeowners": ["@home-assistant/core"],
@@ -1,5 +1,6 @@
{
"domain": "input_select",
"integration_type": "helper",
"name": "Input Select",
"documentation": "https://www.home-assistant.io/integrations/input_select",
"codeowners": ["@home-assistant/core"],
@@ -1,5 +1,6 @@
{
"domain": "input_text",
"integration_type": "helper",
"name": "Input Text",
"documentation": "https://www.home-assistant.io/integrations/input_text",
"codeowners": ["@home-assistant/core"],
@@ -16,10 +16,10 @@ from homeassistant.const import (
TIME_SECONDS,
)
from homeassistant.helpers import selector
from homeassistant.helpers.helper_config_entry_flow import (
HelperConfigFlowHandler,
HelperFlowFormStep,
HelperFlowMenuStep,
from homeassistant.helpers.schema_config_entry_flow import (
SchemaConfigFlowHandler,
SchemaFlowFormStep,
SchemaFlowMenuStep,
)
from .const import (
@@ -37,8 +37,8 @@ UNIT_PREFIXES = [
{"value": "none", "label": "none"},
{"value": "k", "label": "k (kilo)"},
{"value": "M", "label": "M (mega)"},
{"value": "G", "label": "T (tera)"},
{"value": "T", "label": "P (peta)"},
{"value": "G", "label": "G (giga)"},
{"value": "T", "label": "T (tera)"},
]
TIME_UNITS = [
{"value": TIME_SECONDS, "label": "s (seconds)"},
@@ -88,16 +88,16 @@ CONFIG_SCHEMA = vol.Schema(
}
)
CONFIG_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"user": HelperFlowFormStep(CONFIG_SCHEMA)
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"user": SchemaFlowFormStep(CONFIG_SCHEMA)
}
OPTIONS_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"init": HelperFlowFormStep(OPTIONS_SCHEMA)
OPTIONS_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"init": SchemaFlowFormStep(OPTIONS_SCHEMA)
}
class ConfigFlowHandler(HelperConfigFlowHandler, domain=DOMAIN):
class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config or options flow for Integration."""
config_flow = CONFIG_FLOW
@@ -253,8 +253,10 @@ class IntegrationSensor(RestoreEntity, SensorEntity):
self._state = integral
self.async_write_ha_state()
async_track_state_change_event(
self.hass, [self._sensor_source_id], calc_integration
self.async_on_remove(
async_track_state_change_event(
self.hass, [self._sensor_source_id], calc_integration
)
)
@property
+49 -25
View File
@@ -15,7 +15,7 @@ from homeassistant.config_entries import ConfigEntry, OptionsFlow
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import selector
from homeassistant.helpers.storage import STORAGE_DIR
from .const import (
@@ -63,6 +63,13 @@ CONF_KNX_LABEL_TUNNELING_TCP_SECURE: Final = "TCP with IP Secure"
CONF_KNX_LABEL_TUNNELING_UDP: Final = "UDP"
CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK: Final = "UDP with route back / NAT mode"
_IA_SELECTOR = selector.selector({"text": {}})
_IP_SELECTOR = selector.selector({"text": {}})
_PORT_SELECTOR = vol.All(
selector.selector({"number": {"min": 1, "max": 65535, "mode": "box"}}),
vol.Coerce(int),
)
class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a KNX config flow."""
@@ -164,7 +171,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
**DEFAULT_ENTRY_DATA, # type: ignore[misc]
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
CONF_KNX_INDIVIDUAL_ADDRESS: user_input[CONF_KNX_INDIVIDUAL_ADDRESS],
CONF_KNX_ROUTE_BACK: (
connection_type == CONF_KNX_LABEL_TUNNELING_UDP_ROUTE_BACK
),
@@ -202,18 +208,16 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
port = self._selected_tunnel.port
if not self._selected_tunnel.supports_tunnelling_tcp:
connection_methods.remove(CONF_KNX_LABEL_TUNNELING_TCP)
connection_methods.remove(CONF_KNX_LABEL_TUNNELING_TCP_SECURE)
fields = {
vol.Required(CONF_KNX_TUNNELING_TYPE): vol.In(connection_methods),
vol.Required(CONF_HOST, default=ip_address): str,
vol.Required(CONF_PORT, default=port): cv.port,
vol.Required(
CONF_KNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS
): str,
vol.Required(CONF_HOST, default=ip_address): _IP_SELECTOR,
vol.Required(CONF_PORT, default=port): _PORT_SELECTOR,
}
if self.show_advanced_options:
fields[vol.Optional(CONF_KNX_LOCAL_IP)] = str
fields[vol.Optional(CONF_KNX_LOCAL_IP)] = _IP_SELECTOR
return self.async_show_form(
step_id="manual_tunnel", data_schema=vol.Schema(fields), errors=errors
@@ -245,9 +249,16 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
)
fields = {
vol.Required(CONF_KNX_SECURE_USER_ID): int,
vol.Required(CONF_KNX_SECURE_USER_PASSWORD): str,
vol.Required(CONF_KNX_SECURE_DEVICE_AUTHENTICATION): str,
vol.Required(CONF_KNX_SECURE_USER_ID, default=2): vol.All(
selector.selector({"number": {"min": 1, "max": 127, "mode": "box"}}),
vol.Coerce(int),
),
vol.Required(CONF_KNX_SECURE_USER_PASSWORD): selector.selector(
{"text": {"type": "password"}}
),
vol.Required(CONF_KNX_SECURE_DEVICE_AUTHENTICATION): selector.selector(
{"text": {"type": "password"}}
),
}
return self.async_show_form(
@@ -290,8 +301,8 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors["base"] = "file_not_found"
fields = {
vol.Required(CONF_KNX_KNXKEY_FILENAME): str,
vol.Required(CONF_KNX_KNXKEY_PASSWORD): str,
vol.Required(CONF_KNX_KNXKEY_FILENAME): selector.selector({"text": {}}),
vol.Required(CONF_KNX_KNXKEY_PASSWORD): selector.selector({"text": {}}),
}
return self.async_show_form(
@@ -319,13 +330,15 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
fields = {
vol.Required(
CONF_KNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS
): str,
vol.Required(CONF_KNX_MCAST_GRP, default=DEFAULT_MCAST_GRP): str,
vol.Required(CONF_KNX_MCAST_PORT, default=DEFAULT_MCAST_PORT): cv.port,
): _IA_SELECTOR,
vol.Required(CONF_KNX_MCAST_GRP, default=DEFAULT_MCAST_GRP): _IP_SELECTOR,
vol.Required(
CONF_KNX_MCAST_PORT, default=DEFAULT_MCAST_PORT
): _PORT_SELECTOR,
}
if self.show_advanced_options:
fields[vol.Optional(CONF_KNX_LOCAL_IP)] = str
fields[vol.Optional(CONF_KNX_LOCAL_IP)] = _IP_SELECTOR
return self.async_show_form(
step_id="routing", data_schema=vol.Schema(fields), errors=errors
@@ -370,17 +383,17 @@ class KNXOptionsFlowHandler(OptionsFlow):
vol.Required(
CONF_KNX_INDIVIDUAL_ADDRESS,
default=self.current_config[CONF_KNX_INDIVIDUAL_ADDRESS],
): str,
): selector.selector({"text": {}}),
vol.Required(
CONF_KNX_MCAST_GRP,
default=self.current_config.get(CONF_KNX_MCAST_GRP, DEFAULT_MCAST_GRP),
): str,
): _IP_SELECTOR,
vol.Required(
CONF_KNX_MCAST_PORT,
default=self.current_config.get(
CONF_KNX_MCAST_PORT, DEFAULT_MCAST_PORT
),
): cv.port,
): _PORT_SELECTOR,
}
if self.show_advanced_options:
@@ -394,7 +407,7 @@ class KNXOptionsFlowHandler(OptionsFlow):
CONF_KNX_LOCAL_IP,
default=local_ip,
)
] = str
] = _IP_SELECTOR
data_schema[
vol.Required(
CONF_KNX_STATE_UPDATER,
@@ -403,7 +416,7 @@ class KNXOptionsFlowHandler(OptionsFlow):
CONF_KNX_DEFAULT_STATE_UPDATER,
),
)
] = bool
] = selector.selector({"boolean": {}})
data_schema[
vol.Required(
CONF_KNX_RATE_LIMIT,
@@ -412,7 +425,18 @@ class KNXOptionsFlowHandler(OptionsFlow):
CONF_KNX_DEFAULT_RATE_LIMIT,
),
)
] = vol.All(vol.Coerce(int), vol.Range(min=1, max=CONF_MAX_RATE_LIMIT))
] = vol.All(
selector.selector(
{
"number": {
"min": 1,
"max": CONF_MAX_RATE_LIMIT,
"mode": "box",
}
}
),
vol.Coerce(int),
)
return self.async_show_form(
step_id="init",
@@ -444,10 +468,10 @@ class KNXOptionsFlowHandler(OptionsFlow):
): vol.In(connection_methods),
vol.Required(
CONF_HOST, default=self.current_config.get(CONF_HOST)
): str,
): _IP_SELECTOR,
vol.Required(
CONF_PORT, default=self.current_config.get(CONF_PORT, 3671)
): cv.port,
): _PORT_SELECTOR,
}
),
last_step=True,
+47 -18
View File
@@ -19,39 +19,56 @@
"tunneling_type": "KNX Tunneling Type",
"port": "[%key:common::config_flow::data::port%]",
"host": "[%key:common::config_flow::data::host%]",
"individual_address": "Individual address for the connection",
"local_ip": "Local IP of Home Assistant (leave empty for automatic detection)"
"local_ip": "Local IP of Home Assistant"
},
"data_description": {
"port": "Port of the KNX/IP tunneling device.",
"host": "IP address of the KNX/IP tunneling device.",
"local_ip": "Leave blank to use auto-discovery."
}
},
"secure_tunneling": {
"description": "Select how you want to configure IP Secure.",
"description": "Select how you want to configure KNX/IP Secure.",
"menu_options": {
"secure_knxkeys": "Configure a knxkeys file containing IP secure information",
"secure_manual": "Configure IP secure manually"
"secure_knxkeys": "Use a `.knxkeys` file containing IP secure keys",
"secure_manual": "Configure IP secure keys manually"
}
},
"secure_knxkeys": {
"description": "Please enter the information for your knxkeys file.",
"description": "Please enter the information for your `.knxkeys` file.",
"data": {
"knxkeys_filename": "The full name of your knxkeys file",
"knxkeys_password": "The password to decrypt the knxkeys file"
"knxkeys_filename": "The filename of your `.knxkeys` file (including extension)",
"knxkeys_password": "The password to decrypt the `.knxkeys` file"
},
"data_description": {
"knxkeys_filename": "The file is expected to be found in your config directory in `.storage/knx/`.\nIn Home Assistant OS this would be `/config/.storage/knx/`\nExample: `my_project.knxkeys`",
"knxkeys_password": "This was set when exporting the file from ETS."
}
},
"secure_manual": {
"description": "Please enter the IP secure information.",
"description": "Please enter your IP secure information.",
"data": {
"user_id": "User ID",
"user_password": "User password",
"device_authentication": "Device authentication password"
},
"data_description": {
"user_id": "This is often tunnel number +1. So 'Tunnel 2' would have User-ID '3'.",
"user_password": "Password for the specific tunnel connection set in the 'Properties' panel of the tunnel in ETS.",
"device_authentication": "This is set in the 'IP' panel of the interface in ETS."
}
},
"routing": {
"description": "Please configure the routing options.",
"data": {
"individual_address": "Individual address for the routing connection",
"multicast_group": "The multicast group used for routing",
"multicast_port": "The multicast port used for routing",
"local_ip": "Local IP of Home Assistant (leave empty for automatic detection)"
"individual_address": "Individual address",
"multicast_group": "Multicast group used for routing",
"multicast_port": "Multicast port used for routing",
"local_ip": "Local IP of Home Assistant"
},
"data_description": {
"individual_address": "KNX address to be used by Home Assistant, e.g. `0.0.4`",
"local_ip": "Leave blank to use auto-discovery."
}
}
},
@@ -71,11 +88,19 @@
"data": {
"connection_type": "KNX Connection Type",
"individual_address": "Default individual address",
"multicast_group": "Multicast group used for routing and discovery",
"multicast_port": "Multicast port used for routing and discovery",
"local_ip": "Local IP of Home Assistant (use 0.0.0.0 for automatic detection)",
"state_updater": "Globally enable reading states from the KNX Bus",
"rate_limit": "Maximum outgoing telegrams per second"
"multicast_group": "Multicast group",
"multicast_port": "Multicast port",
"local_ip": "Local IP of Home Assistant",
"state_updater": "State updater",
"rate_limit": "Rate limit"
},
"data_description": {
"individual_address": "KNX address to be used by Home Assistant, e.g. `0.0.4`",
"multicast_group": "Used for routing and discovery. Default: `224.0.23.12`",
"multicast_port": "Used for routing and discovery. Default: `3671`",
"local_ip": "Use `0.0.0.0` for auto-discovery.",
"state_updater": "Globally enable or disable reading states from the KNX Bus. When disabled, Home Assistant will not actively retrieve states from the KNX Bus, `sync_state` entity options will have no effect.",
"rate_limit": "Maximum outgoing telegrams per second.\nRecommended: 20 to 40"
}
},
"tunnel": {
@@ -83,6 +108,10 @@
"tunneling_type": "KNX Tunneling Type",
"port": "[%key:common::config_flow::data::port%]",
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"port": "Port of the KNX/IP tunneling device.",
"host": "IP address of the KNX/IP tunneling device."
}
}
}
@@ -13,44 +13,61 @@
"manual_tunnel": {
"data": {
"host": "Host",
"individual_address": "Individual address for the connection",
"local_ip": "Local IP of Home Assistant (leave empty for automatic detection)",
"local_ip": "Local IP of Home Assistant",
"port": "Port",
"tunneling_type": "KNX Tunneling Type"
},
"data_description": {
"host": "IP address of the KNX/IP tunneling device.",
"local_ip": "Leave blank to use auto-discovery.",
"port": "Port of the KNX/IP tunneling device."
},
"description": "Please enter the connection information of your tunneling device."
},
"routing": {
"data": {
"individual_address": "Individual address for the routing connection",
"local_ip": "Local IP of Home Assistant (leave empty for automatic detection)",
"multicast_group": "The multicast group used for routing",
"multicast_port": "The multicast port used for routing"
"individual_address": "Individual address",
"local_ip": "Local IP of Home Assistant",
"multicast_group": "Multicast group used for routing",
"multicast_port": "Multicast port used for routing"
},
"data_description": {
"individual_address": "KNX address to be used by Home Assistant, e.g. `0.0.4`",
"local_ip": "Leave blank to use auto-discovery."
},
"description": "Please configure the routing options."
},
"secure_knxkeys": {
"data": {
"knxkeys_filename": "The full name of your knxkeys file",
"knxkeys_password": "The password to decrypt the knxkeys file."
"knxkeys_filename": "The filename of your `.knxkeys` file (including extension)",
"knxkeys_password": "The password to decrypt the `.knxkeys` file"
},
"description": "Please enter the information for your knxkeys file."
},
"secure_tunneling": {
"description": "Select how you want to configure IP Secure.",
"menu_options": {
"secure_knxkeys": "Configure a knxkeys file containing IP secure information",
"secure_manual": "Configure IP secure manually"
}
"data_description": {
"knxkeys_filename": "The file is expected to be found in your config directory in `.storage/knx/`.\nIn Home Assistant OS this would be `/config/.storage/knx/`\nExample: `my_project.knxkeys`",
"knxkeys_password": "This was set when exporting the file from ETS."
},
"description": "Please enter the information for your `.knxkeys` file."
},
"secure_manual": {
"description": "Please enter the IP secure information.",
"data": {
"user_id": "User ID",
"user_password": "User password",
"device_authentication": "Device authentication password"
"device_authentication": "Device authentication password",
"user_id": "User ID",
"user_password": "User password"
},
"data_description": {
"device_authentication": "This is set in the 'IP' panel of the interface in ETS.",
"user_id": "This is often tunnel number +1. So 'Tunnel 2' would have User-ID '3'.",
"user_password": "Password for the specific tunnel connection set in the 'Properties' panel of the tunnel in ETS."
},
"description": "Please enter your IP secure information."
},
"secure_tunneling": {
"description": "Select how you want to configure KNX/IP Secure.",
"menu_options": {
"secure_knxkeys": "Use a `.knxkeys` file containing IP secure keys",
"secure_manual": "Configure IP secure keys manually"
}
},
},
"tunnel": {
"data": {
"gateway": "KNX Tunnel Connection"
@@ -71,11 +88,19 @@
"data": {
"connection_type": "KNX Connection Type",
"individual_address": "Default individual address",
"local_ip": "Local IP of Home Assistant (use 0.0.0.0 for automatic detection)",
"multicast_group": "Multicast group used for routing and discovery",
"multicast_port": "Multicast port used for routing and discovery",
"rate_limit": "Maximum outgoing telegrams per second",
"state_updater": "Globally enable reading states from the KNX Bus"
"local_ip": "Local IP of Home Assistant",
"multicast_group": "Multicast group",
"multicast_port": "Multicast port",
"rate_limit": "Rate limit",
"state_updater": "State updater"
},
"data_description": {
"individual_address": "KNX address to be used by Home Assistant, e.g. `0.0.4`",
"local_ip": "Use `0.0.0.0` for auto-discovery.",
"multicast_group": "Used for routing and discovery. Default: `224.0.23.12`",
"multicast_port": "Used for routing and discovery. Default: `3671`",
"rate_limit": "Maximum outgoing telegrams per second.\nRecommended: 20 to 40",
"state_updater": "Globally enable or disable reading states from the KNX Bus. When disabled, Home Assistant will not actively retrieve states from the KNX Bus, `sync_state` entity options will have no effect."
}
},
"tunnel": {
@@ -83,6 +108,10 @@
"host": "Host",
"port": "Port",
"tunneling_type": "KNX Tunneling Type"
},
"data_description": {
"host": "IP address of the KNX/IP tunneling device.",
"port": "Port of the KNX/IP tunneling device."
}
}
}
@@ -321,7 +321,6 @@ class KodiEntity(MediaPlayerEntity):
self._app_properties = {}
self._media_position_updated_at = None
self._media_position = None
self._connect_error = False
@property
def _kodi_is_off(self):
@@ -447,8 +446,10 @@ class KodiEntity(MediaPlayerEntity):
except (jsonrpc_base.jsonrpc.TransportError, CannotConnectError):
if not self._connect_error:
self._connect_error = True
_LOGGER.error("Unable to connect to Kodi via websocket", exc_info=True)
_LOGGER.warning("Unable to connect to Kodi via websocket")
await self._clear_connection(False)
else:
self._connect_error = False
async def _ping(self):
try:
@@ -456,8 +457,10 @@ class KodiEntity(MediaPlayerEntity):
except (jsonrpc_base.jsonrpc.TransportError, CannotConnectError):
if not self._connect_error:
self._connect_error = True
_LOGGER.error("Unable to ping Kodi via websocket", exc_info=True)
_LOGGER.warning("Unable to ping Kodi via websocket")
await self._clear_connection()
else:
self._connect_error = False
async def _async_connect_websocket_if_disconnected(self, *_):
"""Reconnect the websocket if it fails."""
@@ -4,7 +4,6 @@ from __future__ import annotations
from typing import Any
from homeassistant import config_entries
from homeassistant.const import CONF_NAME
from homeassistant.data_entry_flow import FlowResult
from .const import DOMAIN
@@ -27,7 +26,3 @@ class LaunchLibraryFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title="Launch Library", data=user_input)
return self.async_show_form(step_id="user")
async def async_step_import(self, conf: dict[str, Any]) -> FlowResult:
"""Import a configuration from config.yaml."""
return await self.async_step_user(user_input={CONF_NAME: conf[CONF_NAME]})
@@ -4,25 +4,20 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
import logging
from typing import Any
from pylaunches.objects.event import Event
from pylaunches.objects.launch import Launch
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, PERCENTAGE
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
@@ -34,13 +29,6 @@ from .const import DOMAIN
DEFAULT_NEXT_LAUNCH_NAME = "Next launch"
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{vol.Optional(CONF_NAME, default=DEFAULT_NEXT_LAUNCH_NAME): cv.string}
)
@dataclass
class LaunchLibrarySensorEntityDescriptionMixin:
@@ -137,28 +125,6 @@ SENSOR_DESCRIPTIONS: tuple[LaunchLibrarySensorEntityDescription, ...] = (
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Import Launch Library configuration from yaml."""
_LOGGER.warning(
"Configuration of the launch_library platform in YAML is deprecated and will be "
"removed in Home Assistant 2022.4; Your existing configuration "
"has been imported into the UI automatically and can be safely removed "
"from your configuration.yaml file"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
@@ -0,0 +1,22 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from . import (
ATTR_EFFECT_LIST,
ATTR_MAX_MIREDS,
ATTR_MIN_MIREDS,
ATTR_SUPPORTED_COLOR_MODES,
)
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static attributes from being recorded in the database."""
return {
ATTR_SUPPORTED_COLOR_MODES,
ATTR_EFFECT_LIST,
ATTR_MIN_MIREDS,
ATTR_MAX_MIREDS,
}
@@ -3,7 +3,7 @@
"name": "Litter-Robot",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
"requirements": ["pylitterbot==2021.12.0"],
"requirements": ["pylitterbot==2022.3.0"],
"codeowners": ["@natekspencer"],
"iot_class": "cloud_polling",
"loggers": ["pylitterbot"]
@@ -70,6 +70,7 @@ from .browse_media import BrowseMedia, async_process_play_media_url # noqa: F40
from .const import ( # noqa: F401
ATTR_APP_ID,
ATTR_APP_NAME,
ATTR_ENTITY_PICTURE_LOCAL,
ATTR_GROUP_MEMBERS,
ATTR_INPUT_SOURCE,
ATTR_INPUT_SOURCE_LIST,
@@ -936,7 +937,7 @@ class MediaPlayerEntity(Entity):
state_attr[attr] = value
if self.media_image_remotely_accessible:
state_attr["entity_picture_local"] = self.media_image_local
state_attr[ATTR_ENTITY_PICTURE_LOCAL] = self.media_image_local
return state_attr
@@ -4,6 +4,7 @@ CONTENT_AUTH_EXPIRY_TIME = 3600 * 24
ATTR_APP_ID = "app_id"
ATTR_APP_NAME = "app_name"
ATTR_ENTITY_PICTURE_LOCAL = "entity_picture_local"
ATTR_GROUP_MEMBERS = "group_members"
ATTR_INPUT_SOURCE = "source"
ATTR_INPUT_SOURCE_LIST = "source_list"
@@ -0,0 +1,26 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.const import ATTR_ENTITY_PICTURE
from homeassistant.core import HomeAssistant, callback
from . import (
ATTR_ENTITY_PICTURE_LOCAL,
ATTR_INPUT_SOURCE_LIST,
ATTR_MEDIA_POSITION,
ATTR_MEDIA_POSITION_UPDATED_AT,
ATTR_SOUND_MODE_LIST,
)
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static and token attributes from being recorded in the database."""
return {
ATTR_ENTITY_PICTURE_LOCAL,
ATTR_ENTITY_PICTURE,
ATTR_INPUT_SOURCE_LIST,
ATTR_MEDIA_POSITION_UPDATED_AT,
ATTR_MEDIA_POSITION,
ATTR_SOUND_MODE_LIST,
}
@@ -6,7 +6,6 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_ELEVATION, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
import homeassistant.helpers.config_validation as cv
from .const import (
@@ -79,10 +78,6 @@ class MetFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
errors=self._errors,
)
async def async_step_import(self, user_input: dict | None = None) -> FlowResult:
"""Handle configuration by yaml file."""
return await self.async_step_user(user_input)
async def async_step_onboarding(self, data=None):
"""Handle a flow initialized by onboarding."""
# Don't create entry if latitude or longitude isn't set.
+1 -46
View File
@@ -1,12 +1,9 @@
"""Support for Met.no weather service."""
from __future__ import annotations
import logging
from types import MappingProxyType
from typing import Any
import voluptuous as vol
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION,
ATTR_FORECAST_TEMP,
@@ -16,13 +13,11 @@ from homeassistant.components.weather import (
ATTR_WEATHER_TEMPERATURE,
ATTR_WEATHER_WIND_BEARING,
ATTR_WEATHER_WIND_SPEED,
PLATFORM_SCHEMA,
Forecast,
WeatherEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_ELEVATION,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
@@ -35,11 +30,9 @@ from homeassistant.const import (
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.distance import convert as convert_distance
from homeassistant.util.pressure import convert as convert_pressure
@@ -55,8 +48,6 @@ from .const import (
FORECAST_MAP,
)
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = (
"Weather forecast from met.no, delivered by the Norwegian "
"Meteorological Institute."
@@ -64,42 +55,6 @@ ATTRIBUTION = (
DEFAULT_NAME = "Met.no"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Inclusive(
CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together"
): cv.latitude,
vol.Inclusive(
CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together"
): cv.longitude,
vol.Optional(CONF_ELEVATION): int,
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Met.no weather platform."""
_LOGGER.warning("Loading Met.no via platform config is deprecated")
# Add defaults.
config = {CONF_ELEVATION: hass.config.elevation, **config}
if config.get(CONF_LATITUDE) is None:
config[CONF_TRACK_HOME] = True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config
)
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
@@ -8,10 +8,10 @@ import voluptuous as vol
from homeassistant.const import CONF_TYPE
from homeassistant.helpers import selector
from homeassistant.helpers.helper_config_entry_flow import (
HelperConfigFlowHandler,
HelperFlowFormStep,
HelperFlowMenuStep,
from homeassistant.helpers.schema_config_entry_flow import (
SchemaConfigFlowHandler,
SchemaFlowFormStep,
SchemaFlowMenuStep,
)
from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
@@ -38,16 +38,16 @@ CONFIG_SCHEMA = vol.Schema(
}
).extend(OPTIONS_SCHEMA.schema)
CONFIG_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"user": HelperFlowFormStep(CONFIG_SCHEMA)
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"user": SchemaFlowFormStep(CONFIG_SCHEMA)
}
OPTIONS_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"init": HelperFlowFormStep(OPTIONS_SCHEMA)
OPTIONS_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"init": SchemaFlowFormStep(OPTIONS_SCHEMA)
}
class ConfigFlowHandler(HelperConfigFlowHandler, domain=DOMAIN):
class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config or options flow for Min/Max."""
config_flow = CONFIG_FLOW
+62 -12
View File
@@ -131,12 +131,15 @@ DEFAULT_PROTOCOL = PROTOCOL_311
DEFAULT_TLS_PROTOCOL = "auto"
DEFAULT_VALUES = {
CONF_PORT: DEFAULT_PORT,
CONF_WILL_MESSAGE: DEFAULT_WILL,
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
CONF_DISCOVERY: DEFAULT_DISCOVERY,
CONF_PORT: DEFAULT_PORT,
CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL,
CONF_WILL_MESSAGE: DEFAULT_WILL,
}
MANDATORY_DEFAULT_VALUES = (CONF_PORT,)
ATTR_TOPIC_TEMPLATE = "topic_template"
ATTR_PAYLOAD_TEMPLATE = "payload_template"
@@ -203,9 +206,7 @@ CONFIG_SCHEMA_BASE = vol.Schema(
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
): cv.isfile,
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
vol.Optional(CONF_TLS_VERSION, default=DEFAULT_TLS_PROTOCOL): vol.Any(
"auto", "1.0", "1.1", "1.2"
),
vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"),
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
),
@@ -220,6 +221,17 @@ CONFIG_SCHEMA_BASE = vol.Schema(
}
)
DEPRECATED_CONFIG_KEYS = [
CONF_BIRTH_MESSAGE,
CONF_BROKER,
CONF_DISCOVERY,
CONF_PASSWORD,
CONF_PORT,
CONF_TLS_VERSION,
CONF_USERNAME,
CONF_WILL_MESSAGE,
]
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
@@ -602,6 +614,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.data[DATA_MQTT_CONFIG] = conf
if not bool(hass.config_entries.async_entries(DOMAIN)):
# Create an import flow if the user has yaml configured entities etc.
# but no broker configuration. Note: The intention is not for this to
# import broker configuration from YAML because that has been deprecated.
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
@@ -612,18 +627,53 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
def _merge_config(entry, conf):
"""Merge configuration.yaml config with config entry."""
# Base config on default values
def _merge_basic_config(
hass: HomeAssistant, entry: ConfigEntry, yaml_config: dict[str, Any]
) -> None:
"""Merge basic options in configuration.yaml config with config entry.
This mends incomplete migration from old version of HA Core.
"""
entry_updated = False
entry_config = {**entry.data}
for key in DEPRECATED_CONFIG_KEYS:
if key in yaml_config and key not in entry_config:
entry_config[key] = yaml_config[key]
entry_updated = True
for key in MANDATORY_DEFAULT_VALUES:
if key not in entry_config:
entry_config[key] = DEFAULT_VALUES[key]
entry_updated = True
if entry_updated:
hass.config_entries.async_update_entry(entry, data=entry_config)
def _merge_extended_config(entry, conf):
"""Merge advanced options in configuration.yaml config with config entry."""
# Add default values
conf = {**DEFAULT_VALUES, **conf}
return {**conf, **entry.data}
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Load a config entry."""
# If user didn't have configuration.yaml config, generate defaults
# Merge basic configuration, and add missing defaults for basic options
_merge_basic_config(hass, entry, hass.data.get(DATA_MQTT_CONFIG, {}))
# Bail out if broker setting is missing
if CONF_BROKER not in entry.data:
_LOGGER.error("MQTT broker is not configured, please configure it")
return False
# If user doesn't have configuration.yaml config, generate default values
# for options not in config entry data
if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None:
conf = CONFIG_SCHEMA_BASE(dict(entry.data))
# User has configuration.yaml config, warn about config entry overrides
elif any(key in conf for key in entry.data):
shared_keys = conf.keys() & entry.data.keys()
override = {k: entry.data[k] for k in shared_keys}
@@ -635,8 +685,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
override,
)
# Merge the configuration values from configuration.yaml
conf = _merge_config(entry, conf)
# Merge advanced configuration values from configuration.yaml
conf = _merge_extended_config(entry, conf)
hass.data[DATA_MQTT] = MQTT(
hass,
@@ -870,7 +920,7 @@ class MQTT:
if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None:
conf = CONFIG_SCHEMA_BASE(dict(entry.data))
self.conf = _merge_config(entry, conf)
self.conf = _merge_extended_config(entry, conf)
await self.async_disconnect()
self.init_client()
await self.async_connect()
+1 -1
View File
@@ -814,7 +814,7 @@ class MqttEntity(
return self._config[CONF_ENABLED_BY_DEFAULT]
@property
def entity_category(self) -> EntityCategory | str | None:
def entity_category(self) -> EntityCategory | None:
"""Return the entity category if any."""
return self._config.get(CONF_ENTITY_CATEGORY)
@@ -184,17 +184,6 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return await self.async_setup_finish()
async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Handle Nanoleaf configuration import."""
self._async_abort_entries_match({CONF_HOST: config[CONF_HOST]})
_LOGGER.debug(
"Importing Nanoleaf on %s from your configuration.yaml", config[CONF_HOST]
)
self.nanoleaf = Nanoleaf(
async_get_clientsession(self.hass), config[CONF_HOST], config[CONF_TOKEN]
)
return await self.async_setup_finish()
async def async_setup_finish(
self, discovery_integration_import: bool = False
) -> FlowResult:
+1 -39
View File
@@ -1,12 +1,10 @@
"""Support for Nanoleaf Lights."""
from __future__ import annotations
import logging
import math
from typing import Any
from aionanoleaf import Nanoleaf
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
@@ -14,7 +12,6 @@ from homeassistant.components.light import (
ATTR_EFFECT,
ATTR_HS_COLOR,
ATTR_TRANSITION,
PLATFORM_SCHEMA,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
@@ -22,12 +19,9 @@ from homeassistant.components.light import (
SUPPORT_TRANSITION,
LightEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_TOKEN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util.color import (
color_temperature_kelvin_to_mired as kelvin_to_mired,
@@ -41,38 +35,6 @@ from .entity import NanoleafEntity
RESERVED_EFFECTS = ("*Solid*", "*Static*", "*Dynamic*")
DEFAULT_NAME = "Nanoleaf"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_TOKEN): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Import Nanoleaf light platform."""
_LOGGER.warning(
"Configuration of the Nanoleaf integration in YAML is deprecated and "
"will be removed in Home Assistant 2022.4; Your existing configuration "
"has been imported into the UI automatically and can be safely removed "
"from your configuration.yaml file"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={CONF_HOST: config[CONF_HOST], CONF_TOKEN: config[CONF_TOKEN]},
)
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
+1 -1
View File
@@ -108,7 +108,7 @@ class NeatoConnectedSwitch(SwitchEntity):
)
@property
def entity_category(self) -> str:
def entity_category(self) -> EntityCategory:
"""Device entity category."""
return EntityCategory.CONFIG
@@ -2,7 +2,7 @@
"domain": "netgear",
"name": "NETGEAR",
"documentation": "https://www.home-assistant.io/integrations/netgear",
"requirements": ["pynetgear==0.9.1"],
"requirements": ["pynetgear==0.9.2"],
"codeowners": ["@hacf-fr", "@Quentame", "@starkillerOG"],
"iot_class": "local_polling",
"config_flow": true,
@@ -0,0 +1,17 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from . import ATTR_MAX, ATTR_MIN, ATTR_MODE, ATTR_STEP
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static attributes from being recorded in the database."""
return {
ATTR_MIN,
ATTR_MAX,
ATTR_STEP,
ATTR_MODE,
}
@@ -3,7 +3,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import cast
from typing import Any, cast
from pyoverkiz.enums import OverkizCommand, OverkizCommandParam, OverkizState
from pyoverkiz.enums.ui import UIWidget
@@ -22,6 +22,7 @@ from homeassistant.components.alarm_control_panel.const import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
@@ -54,15 +55,15 @@ class OverkizAlarmDescription(
"""Class to describe an Overkiz alarm control panel."""
alarm_disarm: str | None = None
alarm_disarm_args: OverkizStateType | list[OverkizStateType] | None = None
alarm_disarm_args: OverkizStateType | list[OverkizStateType] = None
alarm_arm_home: str | None = None
alarm_arm_home_args: OverkizStateType | list[OverkizStateType] | None = None
alarm_arm_home_args: OverkizStateType | list[OverkizStateType] = None
alarm_arm_night: str | None = None
alarm_arm_night_args: OverkizStateType | list[OverkizStateType] | None = None
alarm_arm_night_args: OverkizStateType | list[OverkizStateType] = None
alarm_arm_away: str | None = None
alarm_arm_away_args: OverkizStateType | list[OverkizStateType] | None = None
alarm_arm_away_args: OverkizStateType | list[OverkizStateType] = None
alarm_trigger: str | None = None
alarm_trigger_args: OverkizStateType | list[OverkizStateType] | None = None
alarm_trigger_args: OverkizStateType | list[OverkizStateType] = None
MAP_INTERNAL_STATUS_STATE: dict[str, str] = {
@@ -91,23 +92,25 @@ def _state_tsk_alarm_controller(select_state: Callable[[str], OverkizStateType])
]
MAP_CORE_ACTIVE_ZONES: dict[str, str] = {
OverkizCommandParam.A: STATE_ALARM_ARMED_HOME,
f"{OverkizCommandParam.A},{OverkizCommandParam.B}": STATE_ALARM_ARMED_NIGHT,
f"{OverkizCommandParam.A},{OverkizCommandParam.B},{OverkizCommandParam.C}": STATE_ALARM_ARMED_AWAY,
}
def _state_stateful_alarm_controller(
select_state: Callable[[str], OverkizStateType]
) -> str:
"""Return the state of the device."""
if state := cast(list, select_state(OverkizState.CORE_ACTIVE_ZONES)):
if [
OverkizCommandParam.A,
OverkizCommandParam.B,
OverkizCommandParam.C,
] in state:
return STATE_ALARM_ARMED_AWAY
if state := cast(str, select_state(OverkizState.CORE_ACTIVE_ZONES)):
# The Stateful Alarm Controller has 3 zones with the following options:
# (A, B, C, A,B, B,C, A,C, A,B,C). Since it is not possible to map this to AlarmControlPanel entity,
# only the most important zones are mapped, other zones can only be disarmed.
if state in MAP_CORE_ACTIVE_ZONES:
return MAP_CORE_ACTIVE_ZONES[state]
if [OverkizCommandParam.A, OverkizCommandParam.B] in state:
return STATE_ALARM_ARMED_NIGHT
if OverkizCommandParam.A in state:
return STATE_ALARM_ARMED_HOME
return STATE_ALARM_ARMED_CUSTOM_BYPASS
return STATE_ALARM_DISARMED
@@ -181,17 +184,13 @@ ALARM_DESCRIPTIONS: list[OverkizAlarmDescription] = [
SUPPORT_ALARM_ARM_AWAY | SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_NIGHT
),
fn_state=_state_stateful_alarm_controller,
alarm_disarm=OverkizCommand.DISARM,
alarm_disarm=OverkizCommand.ALARM_OFF,
alarm_arm_home=OverkizCommand.ALARM_ZONE_ON,
alarm_arm_home_args=[OverkizCommandParam.A],
alarm_arm_home_args=OverkizCommandParam.A,
alarm_arm_night=OverkizCommand.ALARM_ZONE_ON,
alarm_arm_night_args=[OverkizCommandParam.A, OverkizCommandParam.B],
alarm_arm_night_args=f"{OverkizCommandParam.A}, {OverkizCommandParam.B}",
alarm_arm_away=OverkizCommand.ALARM_ZONE_ON,
alarm_arm_away_args=[
OverkizCommandParam.A,
OverkizCommandParam.B,
OverkizCommandParam.C,
],
alarm_arm_away_args=f"{OverkizCommandParam.A},{OverkizCommandParam.B},{OverkizCommandParam.C}",
),
# MyFoxAlarmController
OverkizAlarmDescription(
@@ -267,7 +266,7 @@ class OverkizAlarmControlPanel(OverkizDescriptiveEntity, AlarmControlPanelEntity
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
assert self.entity_description.alarm_disarm
await self.executor.async_execute_command(
await self.async_execute_command(
self.entity_description.alarm_disarm,
self.entity_description.alarm_disarm_args,
)
@@ -275,7 +274,7 @@ class OverkizAlarmControlPanel(OverkizDescriptiveEntity, AlarmControlPanelEntity
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
assert self.entity_description.alarm_arm_home
await self.executor.async_execute_command(
await self.async_execute_command(
self.entity_description.alarm_arm_home,
self.entity_description.alarm_arm_home_args,
)
@@ -283,7 +282,7 @@ class OverkizAlarmControlPanel(OverkizDescriptiveEntity, AlarmControlPanelEntity
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
assert self.entity_description.alarm_arm_night
await self.executor.async_execute_command(
await self.async_execute_command(
self.entity_description.alarm_arm_night,
self.entity_description.alarm_arm_night_args,
)
@@ -291,7 +290,7 @@ class OverkizAlarmControlPanel(OverkizDescriptiveEntity, AlarmControlPanelEntity
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
assert self.entity_description.alarm_arm_away
await self.executor.async_execute_command(
await self.async_execute_command(
self.entity_description.alarm_arm_away,
self.entity_description.alarm_arm_away_args,
)
@@ -299,7 +298,14 @@ class OverkizAlarmControlPanel(OverkizDescriptiveEntity, AlarmControlPanelEntity
async def async_alarm_trigger(self, code: str | None = None) -> None:
"""Send alarm trigger command."""
assert self.entity_description.alarm_trigger
await self.executor.async_execute_command(
await self.async_execute_command(
self.entity_description.alarm_trigger,
self.entity_description.alarm_trigger_args,
)
async def async_execute_command(self, command_name: str, args: Any) -> None:
"""Execute device command in async context."""
if args:
await self.executor.async_execute_command(command_name, args)
else:
await self.executor.async_execute_command(command_name)
@@ -28,6 +28,7 @@ PLATFORMS: list[Platform] = [
Platform.LOCK,
Platform.NUMBER,
Platform.SCENE,
Platform.SELECT,
Platform.SENSOR,
Platform.SIREN,
Platform.SWITCH,
@@ -3,7 +3,7 @@
"name": "Overkiz (by Somfy)",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/overkiz",
"requirements": ["pyoverkiz==1.3.13"],
"requirements": ["pyoverkiz==1.3.14"],
"zeroconf": [
{
"type": "_kizbox._tcp.local.",
@@ -3,17 +3,15 @@ from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any, cast
from typing import Any
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.core import Context, HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.template import Template, is_template_string
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
from homeassistant.util import slugify
@@ -82,36 +80,11 @@ def async_create(
)
notification_id = entity_id.split(".")[1]
warn = False
attr: dict[str, str] = {}
attr: dict[str, str] = {ATTR_MESSAGE: message}
if title is not None:
if is_template_string(title):
warn = True
try:
title = cast(
str, Template(title, hass).async_render(parse_result=False) # type: ignore[no-untyped-call]
)
except TemplateError as ex:
_LOGGER.error("Error rendering title %s: %s", title, ex)
attr[ATTR_TITLE] = title
attr[ATTR_FRIENDLY_NAME] = title
if is_template_string(message):
warn = True
try:
message = Template(message, hass).async_render(parse_result=False) # type: ignore[no-untyped-call]
except TemplateError as ex:
_LOGGER.error("Error rendering message %s: %s", message, ex)
attr[ATTR_MESSAGE] = message
if warn:
_LOGGER.warning(
"Passing a template string to persistent_notification.async_create function is deprecated"
)
hass.states.async_set(entity_id, STATE, attr, context=context)
# Store notification and fire event
+7 -7
View File
@@ -22,7 +22,7 @@ from .const import DATA_KEY_API, DATA_KEY_COORDINATOR, DOMAIN
class PiHoleUpdateEntityDescription(UpdateEntityDescription):
"""Describes PiHole update entity."""
current_version: Callable[[dict], str | None] = lambda api: None
installed_version: Callable[[dict], str | None] = lambda api: None
latest_version: Callable[[dict], str | None] = lambda api: None
release_base_url: str | None = None
title: str | None = None
@@ -34,7 +34,7 @@ UPDATE_ENTITY_TYPES: tuple[PiHoleUpdateEntityDescription, ...] = (
name="Core Update Available",
title="Pi-hole Core",
entity_category=EntityCategory.DIAGNOSTIC,
current_version=lambda versions: versions.get("core_current"),
installed_version=lambda versions: versions.get("core_current"),
latest_version=lambda versions: versions.get("core_latest"),
release_base_url="https://github.com/pi-hole/pi-hole/releases/tag",
),
@@ -43,7 +43,7 @@ UPDATE_ENTITY_TYPES: tuple[PiHoleUpdateEntityDescription, ...] = (
name="Web Update Available",
title="Pi-hole Web interface",
entity_category=EntityCategory.DIAGNOSTIC,
current_version=lambda versions: versions.get("web_current"),
installed_version=lambda versions: versions.get("web_current"),
latest_version=lambda versions: versions.get("web_latest"),
release_base_url="https://github.com/pi-hole/AdminLTE/releases/tag",
),
@@ -52,7 +52,7 @@ UPDATE_ENTITY_TYPES: tuple[PiHoleUpdateEntityDescription, ...] = (
name="FTL Update Available",
title="Pi-hole FTL DNS",
entity_category=EntityCategory.DIAGNOSTIC,
current_version=lambda versions: versions.get("FTL_current"),
installed_version=lambda versions: versions.get("FTL_current"),
latest_version=lambda versions: versions.get("FTL_latest"),
release_base_url="https://github.com/pi-hole/FTL/releases/tag",
),
@@ -100,10 +100,10 @@ class PiHoleUpdateEntity(PiHoleEntity, UpdateEntity):
self._attr_title = description.title
@property
def current_version(self) -> str | None:
"""Version currently in use."""
def installed_version(self) -> str | None:
"""Version installed and in use."""
if isinstance(self.api.versions, dict):
return self.entity_description.current_version(self.api.versions)
return self.entity_description.installed_version(self.api.versions)
return None
@property
+2 -3
View File
@@ -12,7 +12,6 @@ from homeassistant.components.climate.const import (
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
@@ -64,7 +63,7 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
self._attr_preset_modes = list(presets)
# Determine hvac modes and current hvac mode
self._attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
self._attr_hvac_modes = [HVAC_MODE_HEAT]
if self.coordinator.data.gateway.get("cooling_present"):
self._attr_hvac_modes.append(HVAC_MODE_COOL)
if self.device.get("available_schedules") != ["None"]:
@@ -90,7 +89,7 @@ class PlugwiseClimateEntity(PlugwiseEntity, ClimateEntity):
def hvac_mode(self) -> str:
"""Return HVAC operation ie. heat, cool mode."""
if (mode := self.device.get("mode")) is None or mode not in self.hvac_modes:
return HVAC_MODE_OFF
return HVAC_MODE_HEAT
return mode
@property
@@ -2,7 +2,7 @@
"domain": "plugwise",
"name": "Plugwise",
"documentation": "https://www.home-assistant.io/integrations/plugwise",
"requirements": ["plugwise==0.17.2"],
"requirements": ["plugwise==0.17.3"],
"codeowners": ["@CoMPaTech", "@bouwew", "@brefra", "@frenck"],
"zeroconf": ["_plugwise._tcp.local."],
"config_flow": true,
@@ -4,6 +4,7 @@ from functools import partial
import json
from typing import Final
from homeassistant.const import ATTR_ATTRIBUTION, ATTR_SUPPORTED_FEATURES
from homeassistant.helpers.json import JSONEncoder
DATA_INSTANCE = "recorder_instance"
@@ -25,3 +26,5 @@ MAX_ROWS_TO_PURGE = 998
DB_WORKER_PREFIX = "DbWorker"
JSON_DUMP: Final = partial(json.dumps, cls=JSONEncoder, separators=(",", ":"))
ALL_DOMAIN_EXCLUDE_ATTRS = {ATTR_ATTRIBUTION, ATTR_SUPPORTED_FEATURES}
+7 -6
View File
@@ -38,7 +38,7 @@ from homeassistant.core import Context, Event, EventOrigin, State, split_entity_
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
import homeassistant.util.dt as dt_util
from .const import JSON_DUMP
from .const import ALL_DOMAIN_EXCLUDE_ATTRS, JSON_DUMP
# SQLAlchemy Schema
# pylint: disable=invalid-name
@@ -269,11 +269,12 @@ class StateAttributes(Base): # type: ignore[misc,valid-type]
if state is None:
return "{}"
domain = split_entity_id(state.entity_id)[0]
if exclude_attrs := exclude_attrs_by_domain.get(domain):
return JSON_DUMP(
{k: v for k, v in state.attributes.items() if k not in exclude_attrs}
)
return JSON_DUMP(state.attributes)
exclude_attrs = (
exclude_attrs_by_domain.get(domain, set()) | ALL_DOMAIN_EXCLUDE_ATTRS
)
return JSON_DUMP(
{k: v for k, v in state.attributes.items() if k not in exclude_attrs}
)
@staticmethod
def hash_shared_attrs(shared_attrs: str) -> int:
+1 -1
View File
@@ -2,7 +2,7 @@
"domain": "roku",
"name": "Roku",
"documentation": "https://www.home-assistant.io/integrations/roku",
"requirements": ["rokuecp==0.15.0"],
"requirements": ["rokuecp==0.16.0"],
"homekit": {
"models": ["3810X", "4660X", "7820X", "C105X", "C135X"]
},
@@ -0,0 +1,12 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from . import ATTR_OPTIONS
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static attributes from being recorded in the database."""
return {ATTR_OPTIONS}
+4 -36
View File
@@ -3,10 +3,7 @@ from __future__ import annotations
import voluptuous as vol
from homeassistant.components.climate import (
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
ClimateEntity,
)
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
HVAC_MODE_COOL,
HVAC_MODE_DRY,
@@ -18,36 +15,26 @@ from homeassistant.components.climate.const import (
SUPPORT_SWING_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_STATE,
ATTR_TEMPERATURE,
CONF_API_KEY,
CONF_ID,
PRECISION_TENTHS,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.temperature import convert as convert_temperature
from .const import ALL, DOMAIN, LOGGER
from .const import DOMAIN
from .coordinator import SensiboDataUpdateCoordinator
from .entity import SensiboDeviceBaseEntity
SERVICE_ASSUME_STATE = "assume_state"
PLATFORM_SCHEMA = PARENT_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_ID, default=ALL): vol.All(cv.ensure_list, [cv.string]),
}
)
FIELD_TO_FLAG = {
"fanLevel": SUPPORT_FAN_MODE,
"swing": SUPPORT_SWING_MODE,
@@ -74,25 +61,6 @@ AC_STATE_TO_DATA = {
}
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up Sensibo devices."""
LOGGER.warning(
"Loading Sensibo via platform setup is deprecated; Please remove it from your configuration"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
@@ -75,11 +75,6 @@ class SensiboConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_import(self, config: dict) -> FlowResult:
"""Import a configuration from config.yaml."""
return await self.async_step_user(user_input=config)
async def async_step_user(self, user_input=None) -> FlowResult:
"""Handle the initial step."""
@@ -19,7 +19,6 @@ PLATFORMS = [
Platform.SELECT,
Platform.SENSOR,
]
ALL = ["all"]
DEFAULT_NAME = "Sensibo"
TIMEOUT = 8
@@ -0,0 +1,12 @@
"""Integration platform for recorder."""
from __future__ import annotations
from homeassistant.core import HomeAssistant, callback
from . import ATTR_AVAILABLE_TONES
@callback
def exclude_attributes(hass: HomeAssistant) -> set[str]:
"""Exclude static attributes from being recorded in the database."""
return {ATTR_AVAILABLE_TONES}
@@ -3,7 +3,7 @@
"name": "SleepIQ",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/sleepiq",
"requirements": ["asyncsleepiq==1.2.1"],
"requirements": ["asyncsleepiq==1.2.3"],
"codeowners": ["@mfugate1", "@kbickar"],
"dhcp": [
{
+6 -9
View File
@@ -1,7 +1,7 @@
"""Support for SleepIQ foundation preset selection."""
from __future__ import annotations
from asyncsleepiq import BED_PRESETS, SleepIQBed, SleepIQPreset
from asyncsleepiq import BED_PRESETS, Side, SleepIQBed, SleepIQPreset
from homeassistant.components.select import SelectEntity
from homeassistant.config_entries import ConfigEntry
@@ -39,14 +39,11 @@ class SleepIQSelectEntity(SleepIQBedEntity, SelectEntity):
"""Initialize the select entity."""
self.preset = preset
if preset.side:
self._attr_name = (
f"SleepNumber {bed.name} Foundation Preset {preset.side_full}"
)
self._attr_unique_id = f"{bed.id}_preset_{preset.side}"
else:
self._attr_name = f"SleepNumber {bed.name} Foundation Preset"
self._attr_unique_id = f"{bed.id}_preset"
self._attr_name = f"SleepNumber {bed.name} Foundation Preset"
self._attr_unique_id = f"{bed.id}_preset"
if preset.side != Side.NONE:
self._attr_name += f" {preset.side_full}"
self._attr_unique_id += f"_{preset.side}"
super().__init__(coordinator, bed)
self._async_update_attrs()
@@ -63,14 +63,3 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_import(self, config: dict[str, Any]) -> FlowResult:
"""Handle import of solax config from YAML."""
import_data = {
CONF_IP_ADDRESS: config[CONF_IP_ADDRESS],
CONF_PORT: config[CONF_PORT],
CONF_PASSWORD: DEFAULT_PASSWORD,
}
return await self.async_step_user(user_input=import_data)
+2 -39
View File
@@ -3,38 +3,25 @@ from __future__ import annotations
import asyncio
from datetime import timedelta
import logging
from solax.inverter import InverterError
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TEMP_CELSIUS
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import DOMAIN, MANUFACTURER
_LOGGER = logging.getLogger(__name__)
DEFAULT_PORT = 80
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_IP_ADDRESS): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
},
)
SCAN_INTERVAL = timedelta(seconds=30)
@@ -81,30 +68,6 @@ async def async_setup_entry(
async_add_entities(devices)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Platform setup."""
_LOGGER.warning(
"Configuration of the SolaX Power platform in YAML is deprecated and "
"will be removed in Home Assistant 2022.4; Your existing configuration "
"has been imported into the UI automatically and can be safely removed "
"from your configuration.yaml file"
)
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
)
)
class RealTimeDataEndpoint:
"""Representation of a Sensor."""
+23 -33
View File
@@ -12,9 +12,7 @@ from typing import Any, Literal, cast
import voluptuous as vol
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.models import StateAttributes, States
from homeassistant.components.recorder.util import execute, session_scope
from homeassistant.components.recorder import get_instance, history
from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorDeviceClass,
@@ -304,7 +302,7 @@ class StatisticsSensor(SensorEntity):
if "recorder" in self.hass.config.components:
self.hass.async_create_task(self._initialize_from_database())
async_at_start(self.hass, async_stats_sensor_startup)
self.async_on_remove(async_at_start(self.hass, async_stats_sensor_startup))
def _add_state_to_queue(self, new_state: State) -> None:
"""Add the state to the queue."""
@@ -474,37 +472,29 @@ class StatisticsSensor(SensorEntity):
def _fetch_states_from_database(self) -> list[State]:
"""Fetch the states from the database."""
_LOGGER.debug("%s: initializing values from the database", self.entity_id)
states = []
with session_scope(hass=self.hass) as session:
query = session.query(States, StateAttributes).filter(
States.entity_id == self._source_entity_id.lower()
lower_entity_id = self._source_entity_id.lower()
if self._samples_max_age is not None:
start_date = (
dt_util.utcnow() - self._samples_max_age - timedelta(microseconds=1)
)
if self._samples_max_age is not None:
records_older_then = dt_util.utcnow() - self._samples_max_age
_LOGGER.debug(
"%s: retrieve records not older then %s",
self.entity_id,
records_older_then,
)
query = query.filter(States.last_updated >= records_older_then)
else:
_LOGGER.debug("%s: retrieving all records", self.entity_id)
query = query.outerjoin(
StateAttributes, States.attributes_id == StateAttributes.attributes_id
_LOGGER.debug(
"%s: retrieve records not older then %s",
self.entity_id,
start_date,
)
query = query.order_by(States.last_updated.desc()).limit(
self._samples_max_buffer_size
)
if results := execute(query, to_native=False, validate_entity_ids=False):
for state, attributes in results:
native = state.to_native()
if not native.attributes:
native.attributes = attributes.to_native()
states.append(native)
return states
else:
start_date = datetime.fromtimestamp(0, tz=dt_util.UTC)
_LOGGER.debug("%s: retrieving all records", self.entity_id)
entity_states = history.state_changes_during_period(
self.hass,
start_date,
entity_id=lower_entity_id,
descending=True,
limit=self._samples_max_buffer_size,
include_start_time_state=False,
)
# Need to cast since minimal responses is not passed in
return cast(list[State], entity_states.get(lower_entity_id, []))
async def _initialize_from_database(self) -> None:
"""Initialize the list of states from the database.
@@ -8,17 +8,17 @@ import voluptuous as vol
from homeassistant.const import CONF_ENTITY_ID, Platform
from homeassistant.helpers import entity_registry as er, selector
from homeassistant.helpers.helper_config_entry_flow import (
HelperConfigFlowHandler,
HelperFlowFormStep,
HelperFlowMenuStep,
from homeassistant.helpers.schema_config_entry_flow import (
SchemaConfigFlowHandler,
SchemaFlowFormStep,
SchemaFlowMenuStep,
wrapped_entity_config_entry_title,
)
from .const import CONF_TARGET_DOMAIN, DOMAIN
CONFIG_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
"user": HelperFlowFormStep(
CONFIG_FLOW: dict[str, SchemaFlowFormStep | SchemaFlowMenuStep] = {
"user": SchemaFlowFormStep(
vol.Schema(
{
vol.Required(CONF_ENTITY_ID): selector.selector(
@@ -43,7 +43,7 @@ CONFIG_FLOW: dict[str, HelperFlowFormStep | HelperFlowMenuStep] = {
}
class SwitchAsXConfigFlowHandler(HelperConfigFlowHandler, domain=DOMAIN):
class SwitchAsXConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config flow for Switch as X."""
config_flow = CONFIG_FLOW
@@ -131,19 +131,6 @@ class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
step_id="user", data_schema=data_schema, errors=errors
)
async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult:
"""Handle config import from yaml."""
_LOGGER.debug("import config: %s", import_config)
import_config[CONF_MAC] = import_config[CONF_MAC].replace("-", ":").lower()
await self.async_set_unique_id(import_config[CONF_MAC].replace(":", ""))
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=import_config[CONF_NAME], data=import_config
)
class SwitchbotOptionsFlowHandler(OptionsFlow):
"""Handle Switchbot options."""
+5 -57
View File
@@ -5,27 +5,15 @@ import logging
from typing import Any
from switchbot import Switchbot # pylint: disable=import-error
import voluptuous as vol
from homeassistant.components.switch import (
PLATFORM_SCHEMA,
SwitchDeviceClass,
SwitchEntity,
)
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_MAC,
CONF_NAME,
CONF_PASSWORD,
CONF_SENSOR_TYPE,
STATE_ON,
)
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_MAC, CONF_NAME, CONF_PASSWORD, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers import entity_platform
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import ATTR_BOT, CONF_RETRY_COUNT, DATA_COORDINATOR, DEFAULT_NAME, DOMAIN
from .const import CONF_RETRY_COUNT, DATA_COORDINATOR, DOMAIN
from .coordinator import SwitchbotDataUpdateCoordinator
from .entity import SwitchbotEntity
@@ -33,46 +21,6 @@ from .entity import SwitchbotEntity
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_MAC): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: entity_platform.AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Import yaml config and initiates config flow for Switchbot devices."""
_LOGGER.warning(
"Configuration of the Switchbot switch platform in YAML is deprecated and "
"will be removed in Home Assistant 2022.4; Your existing configuration "
"has been imported into the UI automatically and can be safely removed "
"from your configuration.yaml file"
)
# Check if entry config exists and skips import if it does.
if hass.config_entries.async_entries(DOMAIN):
return
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_NAME: config[CONF_NAME],
CONF_PASSWORD: config.get(CONF_PASSWORD, None),
CONF_MAC: config[CONF_MAC].replace("-", ":").lower(),
CONF_SENSOR_TYPE: ATTR_BOT,
},
)
)
async def async_setup_entry(
hass: HomeAssistant,
@@ -55,15 +55,15 @@ class SynoDSMUpdateEntity(SynologyDSMBaseEntity, UpdateEntity):
_attr_title = "Synology DSM"
@property
def current_version(self) -> str | None:
"""Version currently in use."""
def installed_version(self) -> str | None:
"""Version installed and in use."""
return self._api.information.version_string # type: ignore[no-any-return]
@property
def latest_version(self) -> str | None:
"""Latest version available for install."""
if not self._api.upgrade.update_available:
return self.current_version
return self.installed_version
return self._api.upgrade.available_version # type: ignore[no-any-return]
@property

Some files were not shown because too many files have changed in this diff Show More