Compare commits

...

90 Commits

Author SHA1 Message Date
Paulus Schoutsen b110a5bbfc Merge pull request #57355 from home-assistant/rc 2021-10-08 15:47:54 -07:00
Paulus Schoutsen 3c2f3f2ade Bumped version to 2021.10.2 2021-10-08 14:58:39 -07:00
Paulus Schoutsen 1ea0891602 Guard for bad last reset (#57344) 2021-10-08 14:58:34 -07:00
Steven Looman 54acc5bade Fix multiple upnp/ssdp issues (#57314) 2021-10-08 14:58:19 -07:00
Erik Montnemery 0c886e2990 Improve state of cover groups (#57313)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-10-08 14:51:11 -07:00
Milan Meulemans f10a469d4f Upgrade aionanoleaf to 0.0.3 to fix deadlock (#57312) 2021-10-08 14:51:10 -07:00
J. Nick Koston 0358a13536 Migrate tplink hosts that were previously imported from yaml (#57308) 2021-10-08 14:51:10 -07:00
Milan Meulemans 1a78c461a0 Fix Nanoleaf light turn_off transition (#57305) 2021-10-08 14:51:09 -07:00
starkillerOG c6d506cb4f Netgear fix port and device model beeing overwritten (#57277)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-10-08 14:51:08 -07:00
Paulus Schoutsen 5bb4bc3d13 Merge pull request #57294 from home-assistant/rc 2021-10-07 20:57:08 -07:00
Paulus Schoutsen 387249b593 Bumped version to 2021.10.1 2021-10-07 20:19:53 -07:00
J. Nick Koston 1a376b25af Bump yeelight to 0.7.7 (#57290) 2021-10-07 20:19:46 -07:00
Teemu R ed4b44c126 Stopgap fix for inconsistent upstream API of tplink dimmers (#57285) 2021-10-07 20:19:45 -07:00
J. Nick Koston ef7f1ffddc Bump HAP-python to 4.30 (#57284) 2021-10-07 20:19:44 -07:00
Teemu R e3e64130f1 Fix transition handling for tplink lights (#57272)
* Fix transition handling for tplink light

* Apply suggestions from code review

* Test that all transitions are passed correctly

* Fix linting

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-10-07 20:19:44 -07:00
Bram Kragten 4e49e3a3fb Update frontend to 20211007.0 (#57268) 2021-10-07 20:19:43 -07:00
J. Nick Koston fd371f2887 Fix RGB only (no color temp) devices with tplink (#57267) 2021-10-07 20:19:42 -07:00
Daniel Hjelseth Høyer ec0256e27f Bump Mill library to 0.6.1 (#57261) 2021-10-07 20:19:41 -07:00
Erik Montnemery 425015eb8b Validate initial value for input_datetime (#57256) 2021-10-07 20:19:41 -07:00
Martin Hjelmare 0b26b15749 Fix netgear config flow import (#57253) 2021-10-07 20:19:40 -07:00
Erik Montnemery 06befe906b Correct SQL query generated by get_metadata_with_session (#57225)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2021-10-07 20:19:39 -07:00
J. Nick Koston bf4a3d8d35 Discover tplink devices periodically (#57221)
- These devices sometimes do not respond on the first try
  or may be subject to transient broadcast failures, or
  overloads. We now try discovery periodically once the
  integration has been loaded.

- We used to try this 4x at startup, but that solution
  seemed to aggressive as we want to be sure we pickup
  the devices after startup as well since the network
  will likely be more calm after startup.
2021-10-07 20:19:15 -07:00
Raman Gupta 691d8d6b80 Convert val to str when needed while calling zwave_js.set_value (#57216) 2021-10-07 20:16:20 -07:00
Maciej Bieniek 7f49e02a4d Update led brightness select state only if valid data is available, Xiaomi Miio integration (#57197)
* Update state if there is valid data

* Add comment
2021-10-07 20:16:19 -07:00
J. Nick Koston 7544ec2399 Recreate the powerwall session/object when attempting relogin (#56935) 2021-10-07 20:16:19 -07:00
Franck Nijhof 32889dbfbe Merge pull request #57179 from home-assistant/rc 2021-10-06 16:36:51 +02:00
Franck Nijhof 46394b50d8 Bumped version to 2021.10.0 2021-10-06 15:53:41 +02:00
Erik Montnemery 25fc479cd4 Correct migration to recorder schema 18 (#57165) 2021-10-06 15:52:20 +02:00
Bram Kragten 9636799dfb Update frontend to 20211006.0 (#57164) 2021-10-06 15:52:14 +02:00
Thomas Schamm e01e575092 Skip link local addresses in bosch_shc discovery step (#57074) 2021-10-06 15:52:11 +02:00
Fredrik Erlandsson 2fc9cdbe68 Update Daikin config_flow with better error handling (#57069) 2021-10-06 15:52:07 +02:00
starkillerOG 72b3bc13e4 Remove Netgear tracker link_rate check on Orbi (#57032)
* Netgear tracker: remove link_rate check on Orbi

* fix debug message

* Add orbi models

* check start of model in V2 check

* fix black
2021-10-06 15:52:04 +02:00
Jean-Yves Avenard 9e755fcc49 Change energy state class to STATE_CLASS_TOTAL (#56974) 2021-10-06 15:52:00 +02:00
Paulus Schoutsen a692d4de64 Bumped version to 2021.10.0b9 2021-10-05 21:34:22 -07:00
Paulus Schoutsen bb617d7b89 Bump netdisco to 3.0.0 (#56903) 2021-10-05 21:34:18 -07:00
Paulus Schoutsen 790a61cfae Bumped version to 2021.10.0b8 2021-10-05 21:32:11 -07:00
Paulus Schoutsen 8e02ea1936 Guard upnp create device (#57156) 2021-10-05 21:32:06 -07:00
Simone Chemelli 7502956d2b Fix SamsungTV shutdown race condition (#57149) 2021-10-05 21:32:06 -07:00
Simone Chemelli 8394157c0a Fix Fritz shutdown race condition (#57148) 2021-10-05 21:32:05 -07:00
Lawrence 76a6edb4ed Updated amberelectic attributes to reflect unit change to $/kWh (#57109) 2021-10-05 21:32:04 -07:00
Paulus Schoutsen 73bf538736 Bumped version to 2021.10.0b7 2021-10-05 13:45:52 -07:00
Raman Gupta dbde2f1b92 Bump zwave-js-server-python to 0.31.3 (#57143) 2021-10-05 13:45:42 -07:00
Paulus Schoutsen 78bb2f5b73 Reinstate asking for country in Tuya flow (#57142) 2021-10-05 13:45:41 -07:00
J. Nick Koston 657037553a Fix yeelight connection when bulb stops responding to SSDP (#57138) 2021-10-05 13:45:40 -07:00
Paulus Schoutsen 0e00075628 Bump aiohue to 2.6.3 (#57125) 2021-10-05 13:45:39 -07:00
jrester ecdbb5ff17 Update tesla_powerwall to 0.3.11 (#57112) 2021-10-05 13:45:38 -07:00
Franck Nijhof a13a6bc9c9 Bump tuya-iot-py-sdk to 0.5.0 (#57110)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-10-05 13:45:38 -07:00
Paulus Schoutsen 5b550689e0 Update Tuya code owners (#57078) 2021-10-05 13:45:37 -07:00
Otto Winter c5992e2967 Bump aioesphomeapi from 9.1.4 to 9.1.5 (#57106) 2021-10-05 13:43:48 -07:00
Franck Nijhof b76e19c8c2 Remove Python shebang line from Tuya integration files (#57103) 2021-10-05 13:43:47 -07:00
Franck Nijhof d1f790fab0 Small code styling tweaks for Tuya (#57102) 2021-10-05 13:43:47 -07:00
Franck Nijhof 4d44beb938 Prevent Tuya from accidentally logging credentials in debug mode (#57100) 2021-10-05 13:43:46 -07:00
indykoning 70a6930a8a Fix Growatt login invalid auth response (#57071) 2021-10-05 13:43:45 -07:00
Paulus Schoutsen b289bb2f57 Bumped version to 2021.10.0b6 2021-10-04 21:07:16 -07:00
GitHub Action a457bb7446 [ci skip] Translation update 2021-10-04 21:07:12 -07:00
Paulus Schoutsen f0b22e2f40 Fix energy gas price validation (#57075) 2021-10-04 21:06:47 -07:00
Bram Kragten d42350f986 Update frontend to 20211004.0 (#57073) 2021-10-04 21:06:47 -07:00
Shay Levy 74aa57e764 Fix: Shelly Gen2 - filter unsupported sensors (#57065) 2021-10-04 21:06:46 -07:00
J. Nick Koston fb9a119fc7 Update esphome reconnect logic to use newer RecordUpdateListener logic (#57057) 2021-10-04 21:06:45 -07:00
Erik Montnemery f82fe9d8bb Improve sensor statistics validation (#56892) 2021-10-04 21:06:44 -07:00
Paulus Schoutsen b086266508 [ci skip] Translation update 2021-10-04 08:38:35 -07:00
Paulus Schoutsen 491abf0608 Bumped version to 2021.10.0b5 2021-10-04 08:35:42 -07:00
GitHub Action da61696566 [ci skip] Translation update 2021-10-04 08:35:37 -07:00
GitHub Action 688884ccfe [ci skip] Translation update 2021-10-04 08:35:28 -07:00
Joakim Sørensen 62390b9531 Rewrite tuya config flow (#57043)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2021-10-04 08:30:00 -07:00
Erik Montnemery 7ddb399178 Prevent opening of sockets in watttime tests (#57040) 2021-10-04 08:30:00 -07:00
Otto Winter 1fe4e08003 Bump aioesphomeapi from 9.1.2 to 9.1.4 (#57036) 2021-10-04 08:29:59 -07:00
Otto Winter b106dab916 ESPHome fix zeroconf add_listener issue (#57031) 2021-10-04 08:29:58 -07:00
Chris Browet 36bac936d1 Universal media player: consider unknown as inactive child state (#57029) 2021-10-04 08:29:57 -07:00
Raman Gupta 4ca0c0d3a9 Bump zwave-js-server-python to 0.31.2 (#57007) 2021-10-04 08:29:56 -07:00
Paulus Schoutsen cdaa7b7db7 Mark auth voluptuous schema fields as required (#57003) 2021-10-04 08:29:56 -07:00
Oliver Ou d1f3602732 Fix Tuya v2 login issue (#56973)
* fix login issue

* fix:login error

* update COUNTRY_CODE_CHINA line location

* added one blank line

* feat:added line #L88 was not covered by tests

* ci build errors

Co-authored-by: erchuan <jie.zheng@tuya.com>
2021-10-04 08:29:55 -07:00
Erik Montnemery 8ee8aade86 Evict purged states from recorder's old_state cache (#56877)
Co-authored-by: J. Nick Koston <nick@koston.org>
2021-10-04 08:29:54 -07:00
Paulus Schoutsen 6aad751056 Bumped version to 2021.10.0b4 2021-10-03 22:01:21 -07:00
jjlawren 2d8684283f Shorten album titles when browsing artist (#57027) 2021-10-03 22:01:13 -07:00
J. Nick Koston 779ae6c801 Add DHCP support for TPLink KP400 (#57023) 2021-10-03 22:01:13 -07:00
Tobias Sauerwein 48fecc916a Fix camera tests (#57020) 2021-10-03 22:01:12 -07:00
Oncleben31 0084db3ad2 Meteofrance fix #56975 (#57016) 2021-10-03 22:01:11 -07:00
Tobias Sauerwein ea2113d5d2 Bump pyatmo to v6.1.0 (#57014) 2021-10-03 22:01:10 -07:00
Diogo Gomes c22ec32726 Ignore utility_meter restore state if state is invalid (#57010)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-10-03 22:01:09 -07:00
J. Nick Koston 645cb53284 Bump yeelight to 0.7.6 (#57009)
- Fixes compat with Lamp15 model

- May improvment Monob model drops seen in #56646

Changes: https://gitlab.com/stavros/python-yeelight/-/commit/0b94e5214e3375f20defa386067ef6cb058c872c
2021-10-03 22:01:09 -07:00
J. Nick Koston 5237817109 Round tplink energy sensors to prevent insignificant updates (#56999)
- These sensors wobble quite a bit and the precision did
  not have sensible limits which generated a massive amount
  of data in the database which was not very useful
2021-10-03 22:01:08 -07:00
Phil Cole 45b7922a6a Use pycarwings2.12 for Nissan Leaf integration (#56996) 2021-10-03 22:01:07 -07:00
Steven Looman 6f39632583 Bump async-upnp-client to 0.22.5 (#56989) 2021-10-03 22:01:06 -07:00
Steven Looman 757c5b9201 Fix upnp invalid key in ssdp discovery_info (#56986) 2021-10-03 22:01:06 -07:00
Shay Levy 7203f58b69 Bump aioshelly to 1.0.2 (#56980) 2021-10-03 22:01:05 -07:00
Aaron Bach a527c451c3 Fix incorrect handling of hass.data in WattTime setup (#56971) 2021-10-03 22:01:04 -07:00
J. Nick Koston 201be1a59d Fix yeelight state when controlled outside of Home Assistant (#56964) 2021-10-03 22:01:03 -07:00
Oliver Ou e454c6628a Fix Tuya v2 fan percentage (#56954)
* fix:Some fans do not have a fan_speed_percent key

* fix comment format issue

Co-authored-by: erchuan <jie.zheng@tuya.com>
2021-10-03 22:01:03 -07:00
Michael Chisholm 2dac92df0c Disable discovery for dlna_dmr until it is more selective (#56950) 2021-10-03 22:01:02 -07:00
177 changed files with 3087 additions and 1254 deletions
+1 -1
View File
@@ -544,7 +544,7 @@ homeassistant/components/trafikverket_train/* @endor-force
homeassistant/components/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins
homeassistant/components/tts/* @pvizeli
homeassistant/components/tuya/* @Tuya
homeassistant/components/tuya/* @Tuya @zlinoliver @METISU
homeassistant/components/twentemilieu/* @frenck
homeassistant/components/twinkly/* @dr1rrb
homeassistant/components/ubus/* @noltari
@@ -38,12 +38,12 @@ class InsecureExampleModule(MultiFactorAuthModule):
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({"pin": str})
return vol.Schema({vol.Required("pin"): str})
@property
def setup_schema(self) -> vol.Schema:
"""Validate async_setup_user input data."""
return vol.Schema({"pin": str})
return vol.Schema({vol.Required("pin"): str})
async def async_setup_flow(self, user_id: str) -> SetupFlow:
"""Return a data entry flow handler for setup module.
+1 -1
View File
@@ -110,7 +110,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({INPUT_FIELD_CODE: str})
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
async def _async_load(self) -> None:
"""Load stored data."""
+1 -1
View File
@@ -84,7 +84,7 @@ class TotpAuthModule(MultiFactorAuthModule):
@property
def input_schema(self) -> vol.Schema:
"""Validate login flow input data."""
return vol.Schema({INPUT_FIELD_CODE: str})
return vol.Schema({vol.Required(INPUT_FIELD_CODE): str})
async def _async_load(self) -> None:
"""Load stored data."""
+8 -6
View File
@@ -2,7 +2,6 @@
from __future__ import annotations
import asyncio
import collections
from collections.abc import Mapping
import logging
import os
@@ -148,10 +147,13 @@ class CommandLineLoginFlow(LoginFlow):
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = collections.OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init", data_schema=vol.Schema(schema), errors=errors
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
)
@@ -3,7 +3,6 @@ from __future__ import annotations
import asyncio
import base64
from collections import OrderedDict
from collections.abc import Mapping
import logging
from typing import Any, cast
@@ -335,10 +334,13 @@ class HassLoginFlow(LoginFlow):
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init", data_schema=vol.Schema(schema), errors=errors
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
)
@@ -1,7 +1,6 @@
"""Example auth provider."""
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Mapping
import hmac
from typing import Any, cast
@@ -117,10 +116,13 @@ class ExampleLoginFlow(LoginFlow):
user_input.pop("password")
return await self.async_finish(user_input)
schema: dict[str, type] = OrderedDict()
schema["username"] = str
schema["password"] = str
return self.async_show_form(
step_id="init", data_schema=vol.Schema(schema), errors=errors
step_id="init",
data_schema=vol.Schema(
{
vol.Required("username"): str,
vol.Required("password"): str,
}
),
errors=errors,
)
@@ -102,5 +102,7 @@ class LegacyLoginFlow(LoginFlow):
return await self.async_finish({})
return self.async_show_form(
step_id="init", data_schema=vol.Schema({"password": str}), errors=errors
step_id="init",
data_schema=vol.Schema({vol.Required("password"): str}),
errors=errors,
)
@@ -244,5 +244,7 @@ class TrustedNetworksLoginFlow(LoginFlow):
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({"user": vol.In(self._available_users)}),
data_schema=vol.Schema(
{vol.Required("user"): vol.In(self._available_users)}
),
)
@@ -9,7 +9,7 @@
},
"step": {
"hassio_confirm": {
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistant-ot AdGuard Home-hoz val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot AdGuard Home-hoz val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
"title": "Az AdGuard Home a Home Assistant kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel"
},
"user": {
@@ -8,7 +8,7 @@
},
"step": {
"hassio_confirm": {
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistant-ot az Almondhoz val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot Almondhoz val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
"title": "Almond - Home Assistant kieg\u00e9sz\u00edt\u0151 \u00e1ltal"
},
"pick_implementation": {
@@ -21,7 +21,7 @@
"longitude": "Hossz\u00fas\u00e1g",
"name": "N\u00e9v"
},
"description": "\u00c1ll\u00edtsa be Ambee-t Home Assistant-tal val\u00f3 integr\u00e1ci\u00f3hoz."
"description": "Integr\u00e1lja \u00f6ssze Ambeet Home Assistanttal."
}
}
}
@@ -37,6 +37,11 @@ ICONS = {
UNIT = f"{CURRENCY_DOLLAR}/{ENERGY_KILO_WATT_HOUR}"
def format_cents_to_dollars(cents: float) -> float:
"""Return a formatted conversion from cents to dollars."""
return round(cents / 100, 2)
def friendly_channel_type(channel_type: str) -> str:
"""Return a human readable version of the channel type."""
if channel_type == "controlled_load":
@@ -70,13 +75,13 @@ class AmberPriceSensor(AmberSensor):
"""Amber Price Sensor."""
@property
def native_value(self) -> str | None:
def native_value(self) -> float | None:
"""Return the current price in $/kWh."""
interval = self.coordinator.data[self.entity_description.key][self.channel_type]
if interval.channel_type == ChannelType.FEED_IN:
return round(interval.per_kwh, 0) / 100 * -1
return round(interval.per_kwh, 0) / 100
return format_cents_to_dollars(interval.per_kwh) * -1
return format_cents_to_dollars(interval.per_kwh)
@property
def device_state_attributes(self) -> Mapping[str, Any] | None:
@@ -89,11 +94,11 @@ class AmberPriceSensor(AmberSensor):
data["duration"] = interval.duration
data["date"] = interval.date.isoformat()
data["per_kwh"] = round(interval.per_kwh)
data["per_kwh"] = format_cents_to_dollars(interval.per_kwh)
if interval.channel_type == ChannelType.FEED_IN:
data["per_kwh"] = data["per_kwh"] * -1
data["nem_date"] = interval.nem_time.isoformat()
data["spot_per_kwh"] = round(interval.spot_per_kwh)
data["spot_per_kwh"] = format_cents_to_dollars(interval.spot_per_kwh)
data["start_time"] = interval.start_time.isoformat()
data["end_time"] = interval.end_time.isoformat()
data["renewables"] = round(interval.renewables)
@@ -102,8 +107,8 @@ class AmberPriceSensor(AmberSensor):
data["channel_type"] = interval.channel_type.value
if interval.range is not None:
data["range_min"] = interval.range.min
data["range_max"] = interval.range.max
data["range_min"] = format_cents_to_dollars(interval.range.min)
data["range_max"] = format_cents_to_dollars(interval.range.max)
return data
@@ -112,7 +117,7 @@ class AmberForecastSensor(AmberSensor):
"""Amber Forecast Sensor."""
@property
def native_value(self) -> str | None:
def native_value(self) -> float | None:
"""Return the first forecast price in $/kWh."""
intervals = self.coordinator.data[self.entity_description.key].get(
self.channel_type
@@ -122,8 +127,8 @@ class AmberForecastSensor(AmberSensor):
interval = intervals[0]
if interval.channel_type == ChannelType.FEED_IN:
return round(interval.per_kwh, 0) / 100 * -1
return round(interval.per_kwh, 0) / 100
return format_cents_to_dollars(interval.per_kwh) * -1
return format_cents_to_dollars(interval.per_kwh)
@property
def device_state_attributes(self) -> Mapping[str, Any] | None:
@@ -146,18 +151,18 @@ class AmberForecastSensor(AmberSensor):
datum["duration"] = interval.duration
datum["date"] = interval.date.isoformat()
datum["nem_date"] = interval.nem_time.isoformat()
datum["per_kwh"] = round(interval.per_kwh)
datum["per_kwh"] = format_cents_to_dollars(interval.per_kwh)
if interval.channel_type == ChannelType.FEED_IN:
datum["per_kwh"] = datum["per_kwh"] * -1
datum["spot_per_kwh"] = round(interval.spot_per_kwh)
datum["spot_per_kwh"] = format_cents_to_dollars(interval.spot_per_kwh)
datum["start_time"] = interval.start_time.isoformat()
datum["end_time"] = interval.end_time.isoformat()
datum["renewables"] = round(interval.renewables)
datum["spike_status"] = interval.spike_status.value
if interval.range is not None:
datum["range_min"] = interval.range.min
datum["range_max"] = interval.range.max
datum["range_min"] = format_cents_to_dollars(interval.range.min)
datum["range_max"] = format_cents_to_dollars(interval.range.max)
data["forecasts"].append(datum)
@@ -1,18 +1,46 @@
{
"config": {
"abort": {
"already_configured_device": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e",
"already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d",
"backoff": "\u8bbe\u5907\u76ee\u524d\u6682\u4e0d\u63a5\u53d7\u914d\u5bf9\u8bf7\u6c42\uff08\u53ef\u80fd\u591a\u6b21\u8f93\u5165\u65e0\u6548 PIN \u7801\uff09\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5\u3002",
"invalid_config": "\u6b64\u8bbe\u5907\u7684\u914d\u7f6e\u4fe1\u606f\u4e0d\u5b8c\u6574\u3002\u8bf7\u5c1d\u8bd5\u91cd\u65b0\u6dfb\u52a0\u3002",
"no_devices_found": "\u672a\u5728\u6b64\u7f51\u7edc\u53d1\u73b0\u76f8\u5173\u8bbe\u5907",
"unknown": "\u672a\u77e5\u9519\u8bef"
},
"error": {
"already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e",
"invalid_auth": "\u51ed\u636e\u65e0\u6548",
"no_devices_found": "\u672a\u5728\u6b64\u7f51\u7edc\u53d1\u73b0\u76f8\u5173\u8bbe\u5907",
"no_usable_service": "\u5df2\u76f8\u5173\u627e\u5230\u8bbe\u5907\uff0c\u4f46\u65e0\u6cd5\u8bc6\u522b\u5e76\u4e0e\u5176\u5efa\u7acb\u8fde\u63a5\u3002\u82e5\u60a8\u4e00\u76f4\u6536\u5230\u6b64\u8b66\u544a\u6d88\u606f\uff0c\u8bf7\u5c1d\u8bd5\u4e3a\u5176\u6307\u5b9a\u56fa\u5b9a IP \u5730\u5740\u6216\u91cd\u65b0\u542f\u52a8\u60a8\u7684 Apple TV\u3002",
"unknown": "\u672a\u77e5\u9519\u8bef"
},
"step": {
"confirm": {
"description": "\u60a8\u5373\u5c06\u6dfb\u52a0 Apple TV (\u540d\u79f0\u4e3a\u201c{name}\u201d)\u5230 Home Assistant\u3002 \n\n **\u8981\u5b8c\u6210\u6b64\u8fc7\u7a0b\uff0c\u53ef\u80fd\u9700\u8981\u8f93\u5165\u591a\u4e2a PIN \u7801\u3002** \n\n\u8bf7\u6ce8\u610f\uff0c\u6b64\u96c6\u6210*\u4e0d\u80fd*\u5173\u95ed Apple TV \u7684\u7535\u6e90\uff0c\u53ea\u4f1a\u5173\u95ed Home Assistant \u4e2d\u7684\u5a92\u4f53\u64ad\u653e\u5668\uff01"
"description": "\u60a8\u5373\u5c06\u6dfb\u52a0 Apple TV (\u540d\u79f0\u4e3a\u201c{name}\u201d)\u5230 Home Assistant\u3002 \n\n **\u8981\u5b8c\u6210\u6b64\u8fc7\u7a0b\uff0c\u53ef\u80fd\u9700\u8981\u8f93\u5165\u591a\u4e2a PIN \u7801\u3002** \n\n\u8bf7\u6ce8\u610f\uff0c\u6b64\u96c6\u6210*\u4e0d\u80fd*\u5173\u95ed Apple TV \u7684\u7535\u6e90\uff0c\u53ea\u4f1a\u5173\u95ed Home Assistant \u4e2d\u7684\u5a92\u4f53\u64ad\u653e\u5668\uff01",
"title": "\u786e\u8ba4\u6dfb\u52a0 Apple TV"
},
"pair_no_pin": {
"description": "`{protocol}` \u670d\u52a1\u9700\u8981\u914d\u5bf9\u3002\u8bf7\u5728\u60a8\u7684 Apple TV \u4e0a\u8f93\u5165 PIN {pin}",
"title": "\u914d\u5bf9\u4e2d"
},
"pair_with_pin": {
"data": {
"pin": "PIN\u7801"
}
},
"title": "\u914d\u5bf9\u4e2d"
},
"reconfigure": {
"description": "\u8be5 Apple TV \u9047\u5230\u4e00\u4e9b\u8fde\u63a5\u95ee\u9898\uff0c\u987b\u91cd\u65b0\u914d\u7f6e\u3002",
"title": "\u8bbe\u5907\u91cd\u65b0\u914d\u7f6e"
},
"service_problem": {
"title": "\u6dfb\u52a0\u670d\u52a1\u5931\u8d25"
},
"user": {
"data": {
"device_input": "\u8bbe\u5907\u5730\u5740"
},
"description": "\u8981\u5f00\u59cb\uff0c\u8bf7\u8f93\u5165\u8981\u6dfb\u52a0\u7684 Apple TV \u7684\u8bbe\u5907\u540d\u79f0\u6216 IP \u5730\u5740\u3002\u5728\u7f51\u7edc\u4e0a\u81ea\u52a8\u53d1\u73b0\u7684\u8bbe\u5907\u4f1a\u663e\u793a\u5728\u4e0b\u65b9\u3002 \n\n\u5982\u679c\u6ca1\u6709\u53d1\u73b0\u8bbe\u5907\u6216\u9047\u5230\u4efb\u4f55\u95ee\u9898\uff0c\u8bf7\u5c1d\u8bd5\u6307\u5b9a\u8bbe\u5907 IP \u5730\u5740\u3002 \n\n {devices}",
"title": "\u8bbe\u7f6e\u65b0\u7684 Apple TV"
}
@@ -187,16 +187,26 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="not_bosch_shc")
try:
self.info = info = await self._get_info(discovery_info["host"])
hosts = (
discovery_info["host"]
if isinstance(discovery_info["host"], list)
else [discovery_info["host"]]
)
for host in hosts:
if host.startswith("169."): # skip link local address
continue
self.info = await self._get_info(host)
self.host = host
if self.host is None:
return self.async_abort(reason="cannot_connect")
except SHCConnectionError:
return self.async_abort(reason="cannot_connect")
local_name = discovery_info["hostname"][:-1]
node_name = local_name[: -len(".local")]
await self.async_set_unique_id(info["unique_id"])
self._abort_if_unique_id_configured({CONF_HOST: discovery_info["host"]})
self.host = discovery_info["host"]
await self.async_set_unique_id(self.info["unique_id"])
self._abort_if_unique_id_configured({CONF_HOST: self.host})
self.context["title_placeholders"] = {"name": node_name}
return await self.async_step_confirm_discovery()
@@ -32,7 +32,7 @@
"data": {
"unlock": "Igen, csin\u00e1ld."
},
"description": "{name} ({model} a {host} c\u00edmen) z\u00e1rolva van. Ez hiteles\u00edt\u00e9si probl\u00e9m\u00e1khoz vezethet Home Assistant-ban. Szeretn\u00e9 feloldani?",
"description": "{name} ({model} a {host} c\u00edmen) z\u00e1rolva van. Ez hiteles\u00edt\u00e9si probl\u00e9m\u00e1khoz vezethet Home Assistantban. Szeretn\u00e9 feloldani?",
"title": "Az eszk\u00f6z felold\u00e1sa (opcion\u00e1lis)"
},
"user": {
@@ -22,7 +22,7 @@
"data": {
"type": "A nyomtat\u00f3 t\u00edpusa"
},
"description": "Hozz\u00e1 szeretn\u00e9 adni a {model} Brother nyomtat\u00f3t, amelynek sorsz\u00e1ma: `{serial_number}`, Home Assistant-hoz?",
"description": "Hozz\u00e1 szeretn\u00e9 adni a {model} Brother nyomtat\u00f3t, amelynek sorsz\u00e1ma: `{serial_number}`, Home Assistanthoz?",
"title": "Felfedezett Brother nyomtat\u00f3"
}
}
@@ -29,7 +29,7 @@
"ignore_cec": "A CEC figyelmen k\u00edv\u00fcl hagy\u00e1sa",
"uuid": "Enged\u00e9lyezett UUID-k"
},
"description": "Enged\u00e9lyezett UUID - vessz\u0151vel elv\u00e1lasztott lista a Cast-eszk\u00f6z\u00f6k UUID-j\u00e9b\u0151l, amelyeket hozz\u00e1 lehet adni Home Assistant-hoz. Csak akkor haszn\u00e1lja, ha nem akarja hozz\u00e1adni az \u00f6sszes rendelkez\u00e9sre \u00e1ll\u00f3 cast eszk\u00f6zt.\nCEC figyelmen k\u00edv\u00fcl hagy\u00e1sa - vessz\u0151vel elv\u00e1lasztott Chromecast-lista, amelynek figyelmen k\u00edv\u00fcl kell hagynia a CEC-adatokat az akt\u00edv bemenet meghat\u00e1roz\u00e1s\u00e1hoz. Ezt tov\u00e1bb\u00edtjuk a pychromecast.IGNORE_CEC c\u00edmre.",
"description": "Enged\u00e9lyezett UUID - vessz\u0151vel elv\u00e1lasztott lista a Cast-eszk\u00f6z\u00f6k UUID-j\u00e9b\u0151l, amelyeket hozz\u00e1 lehet adni Home Assistanthoz. Csak akkor haszn\u00e1lja, ha nem akarja hozz\u00e1adni az \u00f6sszes rendelkez\u00e9sre \u00e1ll\u00f3 cast eszk\u00f6zt.\nCEC figyelmen k\u00edv\u00fcl hagy\u00e1sa - vessz\u0151vel elv\u00e1lasztott Chromecast-lista, amelynek figyelmen k\u00edv\u00fcl kell hagynia a CEC-adatokat az akt\u00edv bemenet meghat\u00e1roz\u00e1s\u00e1hoz. Ezt tov\u00e1bb\u00edtjuk a pychromecast.IGNORE_CEC c\u00edmre.",
"title": "Speci\u00e1lis Google Cast-konfigur\u00e1ci\u00f3"
},
"basic_options": {
@@ -7,6 +7,7 @@
"step": {
"user": {
"data": {
"fan_only": "\u05ea\u05de\u05d9\u05db\u05d4 \u05d1\u05de\u05e6\u05d1 \u05de\u05d0\u05d5\u05d5\u05e8\u05e8 \u05d1\u05dc\u05d1\u05d3",
"host": "\u05de\u05d0\u05e8\u05d7"
}
}
@@ -75,7 +75,8 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
uuid=uuid,
password=password,
)
except asyncio.TimeoutError:
except (asyncio.TimeoutError, ClientError):
self.host = None
return self.async_show_form(
step_id="user",
data_schema=self.schema,
@@ -87,13 +88,6 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
data_schema=self.schema,
errors={"base": "invalid_auth"},
)
except ClientError:
_LOGGER.exception("ClientError")
return self.async_show_form(
step_id="user",
data_schema=self.schema,
errors={"base": "unknown"},
)
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected error creating device")
return self.async_show_form(
@@ -109,6 +103,13 @@ class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""User initiated config flow."""
if user_input is None:
return self.async_show_form(step_id="user", data_schema=self.schema)
if user_input.get(CONF_API_KEY) and user_input.get(CONF_PASSWORD):
self.host = user_input.get(CONF_HOST)
return self.async_show_form(
step_id="user",
data_schema=self.schema,
errors={"base": "api_password"},
)
return await self._create_device(
user_input[CONF_HOST],
user_input.get(CONF_API_KEY),
@@ -18,6 +18,7 @@
"error": {
"unknown": "[%key:common::config_flow::error::unknown%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"api_password": "[%key:common::config_flow::error::invalid_auth%], use either API Key or Password.",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}
}
@@ -5,6 +5,7 @@
"cannot_connect": "Failed to connect"
},
"error": {
"api_password": "Invalid authentication, use either API Key or Password.",
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"unknown": "Unexpected error"
@@ -14,11 +14,11 @@
"flow_title": "{host}",
"step": {
"hassio_confirm": {
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistant-ot, hogy csatlakozzon {addon} \u00e1ltal biztos\u00edtott deCONZ \u00e1tj\u00e1r\u00f3hoz?",
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot, hogy csatlakozzon {addon} \u00e1ltal biztos\u00edtott deCONZ \u00e1tj\u00e1r\u00f3hoz?",
"title": "deCONZ Zigbee \u00e1tj\u00e1r\u00f3 Home Assistant kieg\u00e9sz\u00edt\u0151 \u00e1ltal"
},
"link": {
"description": "Enged\u00e9lyezze fel a deCONZ \u00e1tj\u00e1r\u00f3ban a Home Assistant-hoz val\u00f3 regisztr\u00e1l\u00e1st.\n\n1. V\u00e1lassza ki a deCONZ rendszer be\u00e1ll\u00edt\u00e1sait\n2. Nyomja meg az \"Authenticate app\" gombot",
"description": "Enged\u00e9lyezze fel a deCONZ \u00e1tj\u00e1r\u00f3ban a Home Assistanthoz val\u00f3 regisztr\u00e1l\u00e1st.\n\n1. V\u00e1lassza ki a deCONZ rendszer be\u00e1ll\u00edt\u00e1sait\n2. Nyomja meg az \"Authenticate app\" gombot",
"title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz"
},
"manual_input": {
@@ -2,7 +2,7 @@
"domain": "discovery",
"name": "Discovery",
"documentation": "https://www.home-assistant.io/integrations/discovery",
"requirements": ["netdisco==2.9.0"],
"requirements": ["netdisco==3.0.0"],
"after_dependencies": ["zeroconf"],
"codeowners": [],
"quality_scale": "internal"
@@ -3,28 +3,8 @@
"name": "DLNA Digital Media Renderer",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
"requirements": ["async-upnp-client==0.22.4"],
"requirements": ["async-upnp-client==0.22.8"],
"dependencies": ["network", "ssdp"],
"ssdp": [
{
"st": "urn:schemas-upnp-org:device:MediaRenderer:1"
},
{
"st": "urn:schemas-upnp-org:device:MediaRenderer:2"
},
{
"st": "urn:schemas-upnp-org:device:MediaRenderer:3"
},
{
"nt": "urn:schemas-upnp-org:device:MediaRenderer:1"
},
{
"nt": "urn:schemas-upnp-org:device:MediaRenderer:2"
},
{
"nt": "urn:schemas-upnp-org:device:MediaRenderer:3"
}
],
"codeowners": ["@StevenLooman", "@chishm"],
"iot_class": "local_push"
}
@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"flow_title": "{name}",
"step": {
"confirm": {
"description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05ea\u05d7\u05d9\u05dc \u05d1\u05d4\u05d2\u05d3\u05e8\u05d4?"
},
"user": {
"data": {
"url": "\u05db\u05ea\u05d5\u05d1\u05ea \u05d0\u05ea\u05e8"
}
}
}
}
}
@@ -0,0 +1,12 @@
{
"config": {
"error": {
"could_not_connect": "\u65e0\u6cd5\u8fde\u63a5\u5230 DLNA \u8bbe\u5907"
}
},
"options": {
"error": {
"invalid_url": "\u65e0\u6548\u7f51\u5740"
}
}
}
+30 -8
View File
@@ -24,13 +24,21 @@ ENERGY_USAGE_DEVICE_CLASSES = (sensor.DEVICE_CLASS_ENERGY,)
ENERGY_USAGE_UNITS = {
sensor.DEVICE_CLASS_ENERGY: (ENERGY_KILO_WATT_HOUR, ENERGY_WATT_HOUR)
}
ENERGY_PRICE_UNITS = tuple(
f"/{unit}" for units in ENERGY_USAGE_UNITS.values() for unit in units
)
ENERGY_UNIT_ERROR = "entity_unexpected_unit_energy"
ENERGY_PRICE_UNIT_ERROR = "entity_unexpected_unit_energy_price"
GAS_USAGE_DEVICE_CLASSES = (sensor.DEVICE_CLASS_ENERGY, sensor.DEVICE_CLASS_GAS)
GAS_USAGE_UNITS = {
sensor.DEVICE_CLASS_ENERGY: (ENERGY_WATT_HOUR, ENERGY_KILO_WATT_HOUR),
sensor.DEVICE_CLASS_GAS: (VOLUME_CUBIC_METERS, VOLUME_CUBIC_FEET),
}
GAS_PRICE_UNITS = tuple(
f"/{unit}" for units in GAS_USAGE_UNITS.values() for unit in units
)
GAS_UNIT_ERROR = "entity_unexpected_unit_gas"
GAS_PRICE_UNIT_ERROR = "entity_unexpected_unit_gas_price"
@dataclasses.dataclass
@@ -152,7 +160,11 @@ def _async_validate_usage_stat(
@callback
def _async_validate_price_entity(
hass: HomeAssistant, entity_id: str, result: list[ValidationIssue]
hass: HomeAssistant,
entity_id: str,
result: list[ValidationIssue],
allowed_units: tuple[str, ...],
unit_error: str,
) -> None:
"""Validate that the price entity is correct."""
state = hass.states.get(entity_id)
@@ -176,10 +188,8 @@ def _async_validate_price_entity(
unit = state.attributes.get("unit_of_measurement")
if unit is None or not unit.endswith(
(f"/{ENERGY_KILO_WATT_HOUR}", f"/{ENERGY_WATT_HOUR}")
):
result.append(ValidationIssue("entity_unexpected_unit_price", entity_id, unit))
if unit is None or not unit.endswith(allowed_units):
result.append(ValidationIssue(unit_error, entity_id, unit))
@callback
@@ -274,7 +284,11 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_cost_stat(hass, flow["stat_cost"], source_result)
elif flow.get("entity_energy_price") is not None:
_async_validate_price_entity(
hass, flow["entity_energy_price"], source_result
hass,
flow["entity_energy_price"],
source_result,
ENERGY_PRICE_UNITS,
ENERGY_PRICE_UNIT_ERROR,
)
if (
@@ -303,7 +317,11 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
)
elif flow.get("entity_energy_price") is not None:
_async_validate_price_entity(
hass, flow["entity_energy_price"], source_result
hass,
flow["entity_energy_price"],
source_result,
ENERGY_PRICE_UNITS,
ENERGY_PRICE_UNIT_ERROR,
)
if (
@@ -330,7 +348,11 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
_async_validate_cost_stat(hass, source["stat_cost"], source_result)
elif source.get("entity_energy_price") is not None:
_async_validate_price_entity(
hass, source["entity_energy_price"], source_result
hass,
source["entity_energy_price"],
source_result,
GAS_PRICE_UNITS,
GAS_PRICE_UNIT_ERROR,
)
if (
+32 -28
View File
@@ -24,7 +24,7 @@ from aioesphomeapi import (
UserServiceArgType,
)
import voluptuous as vol
from zeroconf import DNSPointer, DNSRecord, RecordUpdateListener, Zeroconf
from zeroconf import DNSPointer, RecordUpdate, RecordUpdateListener, Zeroconf
from homeassistant import const
from homeassistant.components import zeroconf
@@ -503,16 +503,14 @@ class ReconnectLogic(RecordUpdateListener):
"""
async with self._zc_lock:
if not self._zc_listening:
await self._hass.async_add_executor_job(
self._zc.add_listener, self, None
)
self._zc.async_add_listener(self, None)
self._zc_listening = True
async def _stop_zc_listen(self) -> None:
"""Stop listening for zeroconf updates."""
async with self._zc_lock:
if self._zc_listening:
await self._hass.async_add_executor_job(self._zc.remove_listener, self)
self._zc.async_remove_listener(self)
self._zc_listening = False
@callback
@@ -520,34 +518,40 @@ class ReconnectLogic(RecordUpdateListener):
"""Stop as an async callback function."""
self._hass.async_create_task(self.stop())
@callback
def _set_reconnect(self) -> None:
self._reconnect_event.set()
def async_update_records(
self, zc: Zeroconf, now: float, records: list[RecordUpdate]
) -> None:
"""Listen to zeroconf updated mDNS records.
def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
"""Listen to zeroconf updated mDNS records."""
if not isinstance(record, DNSPointer):
# We only consider PTR records and match using the alias name
return
if self._entry_data is None or self._entry_data.device_info is None:
# Either the entry was already teared down or we haven't received device info yet
This is a mDNS record from the device and could mean it just woke up.
"""
# Check if already connected, no lock needed for this access and
# bail if either the entry was already teared down or we haven't received device info yet
if (
self._connected
or self._reconnect_event.is_set()
or self._entry_data is None
or self._entry_data.device_info is None
):
return
filter_alias = f"{self._entry_data.device_info.name}._esphomelib._tcp.local."
if record.alias != filter_alias:
return
# This is a mDNS record from the device and could mean it just woke up
# Check if already connected, no lock needed for this access
if self._connected:
return
for record_update in records:
# We only consider PTR records and match using the alias name
if (
not isinstance(record_update.new, DNSPointer)
or record_update.new.alias != filter_alias
):
continue
# Tell reconnection logic to retry connection attempt now (even before reconnect timer finishes)
_LOGGER.debug(
"%s: Triggering reconnect because of received mDNS record %s",
self._host,
record,
)
self._hass.add_job(self._set_reconnect)
# Tell reconnection logic to retry connection attempt now (even before reconnect timer finishes)
_LOGGER.debug(
"%s: Triggering reconnect because of received mDNS record %s",
self._host,
record_update.new,
)
self._reconnect_event.set()
return
async def _async_setup_device_registry(
@@ -3,7 +3,7 @@
"name": "ESPHome",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/esphome",
"requirements": ["aioesphomeapi==9.1.2"],
"requirements": ["aioesphomeapi==9.1.5"],
"zeroconf": ["_esphomelib._tcp.local."],
"codeowners": ["@OttoWinter", "@jesserockz"],
"after_dependencies": ["zeroconf", "tag"],
@@ -20,7 +20,7 @@
"description": "K\u00e9rem, adja meg a konfigur\u00e1ci\u00f3ban {name} n\u00e9vhez be\u00e1ll\u00edtott jelsz\u00f3t."
},
"discovery_confirm": {
"description": "Szeretn\u00e9 hozz\u00e1adni `{name}` ESPHome csom\u00f3pontot Home Assistant-hoz?",
"description": "Szeretn\u00e9 hozz\u00e1adni `{name}` ESPHome csom\u00f3pontot Home Assistanthoz?",
"title": "Felfedezett ESPHome csom\u00f3pont"
},
"encryption_key": {
@@ -2,11 +2,13 @@
"config": {
"abort": {
"already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86",
"already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d"
"already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d",
"reauth_successful": "\u91cd\u9a8c\u8bc1\u6210\u529f"
},
"error": {
"connection_error": "\u65e0\u6cd5\u8fde\u63a5\u5230 ESP\u3002\u8bf7\u786e\u8ba4\u60a8\u7684 YAML \u6587\u4ef6\u4e2d\u5305\u542b 'api:' \u884c\u3002",
"invalid_auth": "\u8eab\u4efd\u8ba4\u8bc1\u65e0\u6548",
"invalid_psk": "\u4f20\u8f93\u52a0\u5bc6\u5bc6\u94a5\u65e0\u6548\u3002\u8bf7\u786e\u4fdd\u5b83\u4e0e\u60a8\u7684\u914d\u7f6e\u4e00\u81f4\u3002",
"resolve_error": "\u65e0\u6cd5\u89e3\u6790 ESP \u7684\u5730\u5740\u3002\u5982\u679c\u6b64\u9519\u8bef\u6301\u7eed\u5b58\u5728\uff0c\u8bf7\u8bbe\u7f6e\u9759\u6001IP\u5730\u5740\uff1ahttps://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips"
},
"flow_title": "ESPHome: {name}",
@@ -21,9 +23,21 @@
"description": "\u662f\u5426\u8981\u5c06 ESPHome \u8282\u70b9 `{name}` \u6dfb\u52a0\u5230 Home Assistant\uff1f",
"title": "\u53d1\u73b0\u4e86 ESPHome \u8282\u70b9"
},
"encryption_key": {
"data": {
"noise_psk": "\u52a0\u5bc6\u5bc6\u94a5"
},
"description": "\u8bf7\u8f93\u5165\u60a8\u5728\u914d\u7f6e\u4e2d\u8bbe\u5907 {name} \u6240\u8bbe\u7f6e\u7684\u52a0\u5bc6\u5bc6\u94a5\u3002"
},
"reauth_confirm": {
"data": {
"noise_psk": "\u52a0\u5bc6\u5bc6\u94a5"
},
"description": "ESPHome \u8bbe\u5907 {name} \u5df2\u542f\u7528\u6216\u66f4\u6539\u4f20\u8f93\u52a0\u5bc6\u5bc6\u94a5\u3002\u8bf7\u8f93\u5165\u66f4\u65b0\u540e\u7684\u5bc6\u94a5\u4fe1\u606f\u3002"
},
"user": {
"data": {
"host": "\u4e3b\u673a",
"host": "\u4e3b\u673a\u5730\u5740",
"port": "\u7aef\u53e3"
},
"description": "\u8bf7\u8f93\u5165\u60a8\u7684 [ESPHome](https://esphomelib.com/) \u8282\u70b9\u7684\u8fde\u63a5\u8bbe\u7f6e\u3002"
@@ -1,8 +1,16 @@
{
"device_automation": {
"action_type": {
"turn_off": "\u05db\u05d9\u05d1\u05d5\u05d9 {entity_name}",
"turn_on": "\u05d4\u05e4\u05e2\u05dc\u05ea {entity_name}"
},
"condition_type": {
"is_off": "{entity_name} \u05db\u05d1\u05d5\u05d9",
"is_on": "{entity_name} \u05e4\u05d5\u05e2\u05dc"
},
"trigger_type": {
"turned_off": "{entity_name} \u05db\u05d5\u05d1\u05d4",
"turned_on": "{entity_name} \u05d4\u05d5\u05e4\u05e2\u05dc"
}
},
"state": {
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "El dispositiu ja est\u00e0 configurat",
"already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs",
"no_devices_found": "No s'han trobat dispositius a la xarxa"
},
"error": {
"cannot_connect": "Ha fallat la connexi\u00f3"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "Vols configurar {model} {id} ({ipaddr})?"
},
"user": {
"data": {
"host": "Amfitri\u00f3"
},
"description": "Si deixes l'amfitri\u00f3 buit, s'utilitzar\u00e0 el descobriment per cercar dispositius."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Efecte personalitzat: llista d'1 a 16 colors [R,G,B]. Exemple: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "Efecte personalitzat: velocitat en percentatges de l'efecte de canvi de color.",
"custom_effect_transition": "Efecte personalitzat: tipus de transici\u00f3 entre colors.",
"mode": "Mode de brillantor escollit."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "Ger\u00e4t ist bereits konfiguriert",
"already_in_progress": "Der Konfigurationsablauf wird bereits ausgef\u00fchrt",
"no_devices_found": "Keine Ger\u00e4te im Netzwerk gefunden"
},
"error": {
"cannot_connect": "Verbindung fehlgeschlagen"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "M\u00f6chtest du {model} {id} ({ipaddr})?"
},
"user": {
"data": {
"host": "Host"
},
"description": "Wenn du den Host leer l\u00e4sst, wird die Erkennung verwendet, um Ger\u00e4te zu finden."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Benutzerdefinierter Effekt: Liste mit 1 bis 16 [R,G,B]-Farben. Beispiel: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "Benutzerdefinierter Effekt: Geschwindigkeit in Prozent f\u00fcr den Effekt, der die Farbe wechselt.",
"custom_effect_transition": "Benutzerdefinierter Effekt: Art des \u00dcbergangs zwischen den Farben.",
"mode": "Der gew\u00e4hlte Helligkeitsmodus."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured",
"already_in_progress": "Configuration flow is already in progress",
"no_devices_found": "No devices found on the network"
},
"error": {
"cannot_connect": "Failed to connect"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "Do you want to setup {model} {id} ({ipaddr})?"
},
"user": {
"data": {
"host": "Host"
},
"description": "If you leave the host empty, discovery will be used to find devices."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Custom Effect: List of 1 to 16 [R,G,B] colors. Example: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "Custom Effect: Speed in percents for the effect that switch colors.",
"custom_effect_transition": "Custom Effect: Type of transition between the colors.",
"mode": "The chosen brightness mode."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "Seade on juba h\u00e4\u00e4lestatud",
"already_in_progress": "Seadistamine on juba k\u00e4imas",
"no_devices_found": "V\u00f5rgust seadmeid ei leitud"
},
"error": {
"cannot_connect": "\u00dchendamine nurjus"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "Kas seadistada {model} {id} ({ipaddr})?"
},
"user": {
"data": {
"host": "Host"
},
"description": "Kui j\u00e4tad hosti t\u00fchjaks kasutatakse seadmete leidmiseks avastamist."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Kohandatud efekt: Loetelu 1 kuni 16 [R,G,B] v\u00e4rvist. N\u00e4ide: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "Kohandatud efekt: v\u00e4rvide vahetamise efekti kiirus protsentides.",
"custom_effect_transition": "Kohandatud efekt: v\u00e4rvide vahelise \u00fclemineku t\u00fc\u00fcp.",
"mode": "Valitud heleduse re\u017eiim."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van",
"already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van.",
"no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton"
},
"error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani a(z) {model} {id} ({ipaddr}) webhelyet?"
},
"user": {
"data": {
"host": "C\u00edm"
},
"description": "Ha nem ad meg c\u00edmet, akkor az eszk\u00f6z\u00f6k keres\u00e9se a felder\u00edt\u00e9ssel t\u00f6rt\u00e9nik."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Egy\u00e9ni effektus: 1-16 [R,G,B] sz\u00edn list\u00e1ja. P\u00e9lda: [255,0,255], [60,128,0]",
"custom_effect_speed_pct": "Egy\u00e9ni effektus: A sz\u00edneket v\u00e1lt\u00f3 hat\u00e1s sz\u00e1zal\u00e9kos ar\u00e1nya.",
"custom_effect_transition": "Egy\u00e9ni hat\u00e1s: A sz\u00ednek k\u00f6z\u00f6tti \u00e1tmenet t\u00edpusa.",
"mode": "A v\u00e1lasztott f\u00e9nyer\u0151 m\u00f3d."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato",
"already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso",
"no_devices_found": "Nessun dispositivo trovato sulla rete"
},
"error": {
"cannot_connect": "Impossibile connettersi"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "Vuoi configurare {model} {id} ({ipaddr})?"
},
"user": {
"data": {
"host": "Host"
},
"description": "Se lasci vuoto l'host, il rilevamento verr\u00e0 utilizzato per trovare i dispositivi."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Effetto personalizzato: Lista da 1 a 16 colori [R,G,B]. Esempio: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "Effetto personalizzato: Velocit\u00e0 in percentuale per l'effetto che cambia colore.",
"custom_effect_transition": "Effetto personalizzato: Tipo di transizione tra i colori.",
"mode": "La modalit\u00e0 di luminosit\u00e0 scelta."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "Apparaat is al geconfigureerd",
"already_in_progress": "De configuratiestroom is al aan de gang",
"no_devices_found": "Geen apparaten gevonden op het netwerk"
},
"error": {
"cannot_connect": "Kan geen verbinding maken"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "Wilt u {model} {id} ( {ipaddr} ) instellen?"
},
"user": {
"data": {
"host": "Host"
},
"description": "Als u de host leeg laat, wordt detectie gebruikt om apparaten te vinden."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Aangepast effect: Lijst van 1 tot 16 [R,G,B] kleuren. Voorbeeld: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "Aangepast effect: snelheid in procenten voor het effect dat van kleur verandert.",
"custom_effect_transition": "Aangepast effect: Type overgang tussen de kleuren.",
"mode": "De gekozen helderheidsstand."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "Enheten er allerede konfigurert",
"already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede",
"no_devices_found": "Ingen enheter funnet p\u00e5 nettverket"
},
"error": {
"cannot_connect": "Tilkobling mislyktes"
},
"flow_title": "{model} {id} ( {ipaddr} )",
"step": {
"discovery_confirm": {
"description": "Vil du konfigurere {model} {id} ( {ipaddr} )?"
},
"user": {
"data": {
"host": "Vert"
},
"description": "Hvis du lar verten st\u00e5 tom, brukes automatisk oppdagelse til \u00e5 finne enheter"
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "Egendefinert effekt: Liste med farger fra 1 til 16 [R,G,B]. Eksempel: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "Egendefinert effekt: Hastighet i prosent for effekten som bytter farger.",
"custom_effect_transition": "Egendefinert effekt: Overgangstype mellom fargene.",
"mode": "Den valgte lysstyrkemodusen."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.",
"already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.",
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f."
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 {model} {id} ({ipaddr})?"
},
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442"
},
"description": "\u0415\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430, \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u0443\u0434\u0443\u0442 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438."
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u044d\u0444\u0444\u0435\u043a\u0442: \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0442 1 \u0434\u043e 16 [R,G,B] \u0446\u0432\u0435\u0442\u043e\u0432. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: [255,0,255],[60,128,0]",
"custom_effect_speed_pct": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u044d\u0444\u0444\u0435\u043a\u0442: \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0446\u0432\u0435\u0442\u043e\u0432 (\u0432 \u043f\u0440\u043e\u0446\u0435\u043d\u0442\u0430\u0445).",
"custom_effect_transition": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u044d\u0444\u0444\u0435\u043a\u0442: \u0442\u0438\u043f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043c\u0435\u0436\u0434\u0443 \u0446\u0432\u0435\u0442\u0430\u043c\u0438.",
"mode": "\u0412\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c \u044f\u0440\u043a\u043e\u0441\u0442\u0438."
}
}
}
}
}
@@ -0,0 +1,36 @@
{
"config": {
"abort": {
"already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d",
"no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u88dd\u7f6e"
},
"error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557"
},
"flow_title": "{model} {id} ({ipaddr})",
"step": {
"discovery_confirm": {
"description": "\u662f\u5426\u8981\u8a2d\u5b9a {model} {id} ({ipaddr})\uff1f"
},
"user": {
"data": {
"host": "\u4e3b\u6a5f\u7aef"
},
"description": "\u5047\u5982\u4e3b\u6a5f\u7aef\u4f4d\u5740\u6b04\u4f4d\u70ba\u7a7a\u767d\uff0c\u5c07\u6703\u63a2\u7d22\u6240\u6709\u53ef\u7528\u88dd\u7f6e\u3002"
}
}
},
"options": {
"step": {
"init": {
"data": {
"custom_effect_colors": "\u81ea\u8a02\u7279\u6548\uff1a1 \u5230 16 \u7a2e [R,G,B] \u984f\u8272\u3002\u4f8b\u5982\uff1a[255,0,255]\u3001[60,128,0]",
"custom_effect_speed_pct": "\u81ea\u8a02\u7279\u6548\uff1a\u984f\u8272\u5207\u63db\u7684\u901f\u5ea6\u767e\u5206\u6bd4\u3002",
"custom_effect_transition": "\u81ea\u8a02\u7279\u6548\uff1a\u984f\u8272\u9593\u7684\u8f49\u63db\u985e\u578b\u3002",
"mode": "\u9078\u64c7\u4eae\u5ea6\u6a21\u5f0f\u3002"
}
}
}
}
}
+6 -1
View File
@@ -228,7 +228,12 @@ class FritzBoxTools:
def _update_hosts_info(self) -> list[HostInfo]:
"""Retrieve latest hosts information from the FRITZ!Box."""
return self.fritz_hosts.get_hosts_info() # type: ignore [no-any-return]
try:
return self.fritz_hosts.get_hosts_info() # type: ignore [no-any-return]
except Exception as ex: # pylint: disable=[broad-except]
if not self.hass.is_stopping:
raise HomeAssistantError("Error refreshing hosts info") from ex
return []
def _update_device_info(self) -> tuple[bool, str | None]:
"""Retrieve latest device information from the FRITZ!Box."""
@@ -3,7 +3,7 @@
"name": "Home Assistant Frontend",
"documentation": "https://www.home-assistant.io/integrations/frontend",
"requirements": [
"home-assistant-frontend==20211002.0"
"home-assistant-frontend==20211007.0"
],
"dependencies": [
"api",
+8 -2
View File
@@ -259,25 +259,31 @@ class CoverGroup(GroupEntity, CoverEntity):
"""Update state and attributes."""
self._attr_assumed_state = False
self._attr_is_closed = None
self._attr_is_closed = True
self._attr_is_closing = False
self._attr_is_opening = False
has_valid_state = False
for entity_id in self._entities:
state = self.hass.states.get(entity_id)
if not state:
continue
if state.state == STATE_OPEN:
self._attr_is_closed = False
has_valid_state = True
continue
if state.state == STATE_CLOSED:
self._attr_is_closed = True
has_valid_state = True
continue
if state.state == STATE_CLOSING:
self._attr_is_closing = True
has_valid_state = True
continue
if state.state == STATE_OPENING:
self._attr_is_opening = True
has_valid_state = True
continue
if not has_valid_state:
self._attr_is_closed = None
position_covers = self._covers[KEY_POSITION]
all_position_states = [self.hass.states.get(x) for x in position_covers]
@@ -6,7 +6,13 @@ from homeassistant import config_entries
from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME
from homeassistant.core import callback
from .const import CONF_PLANT_ID, DEFAULT_URL, DOMAIN, SERVER_URLS
from .const import (
CONF_PLANT_ID,
DEFAULT_URL,
DOMAIN,
LOGIN_INVALID_AUTH_CODE,
SERVER_URLS,
)
class GrowattServerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@@ -45,7 +51,10 @@ class GrowattServerConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self.api.login, user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
)
if not login_response["success"] and login_response["errCode"] == "102":
if (
not login_response["success"]
and login_response["msg"] == LOGIN_INVALID_AUTH_CODE
):
return self._async_show_user_form({"base": "invalid_auth"})
self.user_id = login_response["user"]["id"]
@@ -16,3 +16,5 @@ DEFAULT_URL = SERVER_URLS[0]
DOMAIN = "growatt_server"
PLATFORMS = ["sensor"]
LOGIN_INVALID_AUTH_CODE = "502"
@@ -37,7 +37,7 @@ from homeassistant.const import (
)
from homeassistant.util import Throttle, dt
from .const import CONF_PLANT_ID, DEFAULT_PLANT_ID, DEFAULT_URL
from .const import CONF_PLANT_ID, DEFAULT_PLANT_ID, DEFAULT_URL, LOGIN_INVALID_AUTH_CODE
_LOGGER = logging.getLogger(__name__)
@@ -876,7 +876,10 @@ def get_device_list(api, config):
# Log in to api and fetch first plant if no plant id is defined.
login_response = api.login(config[CONF_USERNAME], config[CONF_PASSWORD])
if not login_response["success"] and login_response["errCode"] == "102":
if (
not login_response["success"]
and login_response["msg"] == LOGIN_INVALID_AUTH_CODE
):
_LOGGER.error("Username, Password or URL may be incorrect!")
return
user_id = login_response["user"]["id"]
@@ -3,7 +3,7 @@
"name": "HomeKit",
"documentation": "https://www.home-assistant.io/integrations/homekit",
"requirements": [
"HAP-python==4.2.1",
"HAP-python==4.3.0",
"fnvhash==0.1.0",
"PyQRCode==1.2.1",
"base36==0.1.1"
@@ -6,7 +6,7 @@
"already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van.",
"already_paired": "Ez a tartoz\u00e9k m\u00e1r p\u00e1ros\u00edtva van egy m\u00e1sik eszk\u00f6zzel. \u00c1ll\u00edtsa alaphelyzetbe a tartoz\u00e9kot, majd pr\u00f3b\u00e1lkozzon \u00fajra.",
"ignored_model": "A HomeKit t\u00e1mogat\u00e1sa e modelln\u00e9l blokkolva van, mivel a szolg\u00e1ltat\u00e1shoz teljes nat\u00edv integr\u00e1ci\u00f3 \u00e9rhet\u0151 el.",
"invalid_config_entry": "Ez az eszk\u00f6z k\u00e9szen \u00e1ll a p\u00e1ros\u00edt\u00e1sra, de m\u00e1r van egy \u00fctk\u00f6z\u0151 konfigur\u00e1ci\u00f3s bejegyz\u00e9s Home Assistant-ben, amelyet el\u0151sz\u00f6r el kell t\u00e1vol\u00edtani.",
"invalid_config_entry": "Ez az eszk\u00f6z k\u00e9szen \u00e1ll a p\u00e1ros\u00edt\u00e1sra, de m\u00e1r van egy \u00fctk\u00f6z\u0151 konfigur\u00e1ci\u00f3s bejegyz\u00e9s Home Assistantban, amelyet el\u0151sz\u00f6r el kell t\u00e1vol\u00edtani.",
"invalid_properties": "Az eszk\u00f6z \u00e1ltal bejelentett \u00e9rv\u00e9nytelen tulajdons\u00e1gok.",
"no_devices": "Nem tal\u00e1lhat\u00f3 nem p\u00e1ros\u00edtott eszk\u00f6z"
},
@@ -1,8 +1,42 @@
{
"config": {
"abort": {
"already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e",
"already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c",
"not_huawei_lte": "\u8be5\u8bbe\u5907\u4e0d\u662f\u534e\u4e3a LTE \u8bbe\u5907"
},
"error": {
"connection_timeout": "\u8fde\u63a5\u8d85\u65f6",
"incorrect_password": "\u5bc6\u7801\u9519\u8bef",
"incorrect_username": "\u7528\u6237\u540d\u9519\u8bef",
"login_attempts_exceeded": "\u5df2\u8d85\u8fc7\u6700\u5927\u767b\u5f55\u6b21\u6570\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5"
"invalid_auth": "\u51ed\u8bc1\u65e0\u6548",
"invalid_url": "\u65e0\u6548\u7f51\u5740",
"login_attempts_exceeded": "\u5df2\u8d85\u8fc7\u6700\u5927\u767b\u5f55\u6b21\u6570\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5",
"response_error": "\u8bbe\u5907\u51fa\u73b0\u672a\u77e5\u9519\u8bef",
"unknown": "\u672a\u77e5\u9519\u8bef"
},
"step": {
"user": {
"data": {
"url": "\u4e3b\u673a\u5730\u5740",
"username": "\u7528\u6237\u540d"
},
"description": "\u8f93\u5165\u8bbe\u5907\u76f8\u5173\u4fe1\u606f\u4ee5\u4fbf\u8fde\u63a5\u81f3\u8be5\u8bbe\u5907",
"title": "\u914d\u7f6e\u534e\u4e3aLTE"
}
}
},
"options": {
"step": {
"init": {
"data": {
"name": "\u63a8\u9001\u670d\u52a1\u540d\u79f0\uff08\u66f4\u6539\u540e\u9700\u8981\u91cd\u8f7d\uff09",
"recipient": "\u77ed\u4fe1\u901a\u77e5\u6536\u4ef6\u4eba",
"track_new_devices": "\u8ddf\u8e2a\u65b0\u8bbe\u5907",
"track_wired_clients": "\u8ddf\u8e2a\u6709\u7ebf\u7f51\u7edc\u5ba2\u6237\u7aef",
"unauthenticated_mode": "\u672a\u7ecf\u8eab\u4efd\u9a8c\u8bc1\u7684\u6a21\u5f0f\uff08\u66f4\u6539\u540e\u9700\u8981\u91cd\u8f7d\uff09"
}
}
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
"name": "Philips Hue",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/hue",
"requirements": ["aiohue==2.6.2"],
"requirements": ["aiohue==2.6.3"],
"ssdp": [
{
"manufacturer": "Royal Philips Electronics",
@@ -22,7 +22,7 @@
"title": "V\u00e1lasszon Hue bridge-t"
},
"link": {
"description": "Nyomja meg a gombot a bridge-en a Philips Hue Home Assistant-ben val\u00f3 regisztr\u00e1l\u00e1s\u00e1hoz.\n\n![Location of button on bridge](/static/images/config_philips_hue.jpg)",
"description": "Nyomja meg a gombot a bridge-en a Philips Hue Home Assistantban val\u00f3 regisztr\u00e1l\u00e1s\u00e1hoz.\n\n![Gomb helye](/static/images/config_philips_hue.jpg)",
"title": "Kapcsol\u00f3d\u00e1s a hubhoz"
},
"manual": {
@@ -81,6 +81,30 @@ def has_date_or_time(conf):
raise vol.Invalid("Entity needs at least a date or a time")
def valid_initial(conf):
"""Check the initial value is valid."""
initial = conf.get(CONF_INITIAL)
if not initial:
return conf
if conf[CONF_HAS_DATE] and conf[CONF_HAS_TIME]:
parsed_value = dt_util.parse_datetime(initial)
if parsed_value is not None:
return conf
raise vol.Invalid(f"Initial value '{initial}' can't be parsed as a datetime")
if conf[CONF_HAS_DATE]:
parsed_value = dt_util.parse_date(initial)
if parsed_value is not None:
return conf
raise vol.Invalid(f"Initial value '{initial}' can't be parsed as a date")
parsed_value = dt_util.parse_time(initial)
if parsed_value is not None:
return conf
raise vol.Invalid(f"Initial value '{initial}' can't be parsed as a time")
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: cv.schema_with_slug_keys(
@@ -93,6 +117,7 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_INITIAL): cv.string,
},
has_date_or_time,
valid_initial,
)
)
},
+2 -2
View File
@@ -9,7 +9,7 @@ from iotawattpy.sensor import Sensor
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
STATE_CLASS_TOTAL,
SensorEntity,
SensorEntityDescription,
)
@@ -83,6 +83,7 @@ ENTITY_DESCRIPTION_KEY_MAP: dict[str, IotaWattSensorEntityDescription] = {
"WattHours": IotaWattSensorEntityDescription(
"WattHours",
native_unit_of_measurement=ENERGY_WATT_HOUR,
state_class=STATE_CLASS_TOTAL,
device_class=DEVICE_CLASS_ENERGY,
),
"VA": IotaWattSensorEntityDescription(
@@ -242,7 +243,6 @@ class IotaWattAccumulatingSensor(IotaWattSensor, RestoreEntity):
super().__init__(coordinator, key, entity_description)
self._attr_state_class = STATE_CLASS_TOTAL_INCREASING
if self._attr_unique_id is not None:
self._attr_unique_id += ".accumulated"
@@ -22,7 +22,7 @@
"description": "Adja meg a Kodi felhaszn\u00e1l\u00f3nevet \u00e9s jelsz\u00f3t. Ezek megtal\u00e1lhat\u00f3k a Rendszer/Be\u00e1ll\u00edt\u00e1sok/H\u00e1l\u00f3zat/Szolg\u00e1ltat\u00e1sok r\u00e9szben."
},
"discovery_confirm": {
"description": "Szeretn\u00e9 hozz\u00e1adni a Kodi (`{name}`)-t Home Assistant-hoz?",
"description": "Szeretn\u00e9 hozz\u00e1adni a Kodi (`{name}`)-t Home Assistanthoz?",
"title": "Felfedezett Kodi"
},
"user": {
+17 -12
View File
@@ -44,18 +44,23 @@ async def async_setup_entry(
MeteoFranceSensor(coordinator_forecast, description)
for description in SENSOR_TYPES
]
entities.extend(
[
MeteoFranceRainSensor(coordinator_rain, description)
for description in SENSOR_TYPES_RAIN
]
)
entities.extend(
[
MeteoFranceAlertSensor(coordinator_alert, description)
for description in SENSOR_TYPES_ALERT
]
)
# Add rain forecast entity only if location support this feature
if coordinator_rain:
entities.extend(
[
MeteoFranceRainSensor(coordinator_rain, description)
for description in SENSOR_TYPES_RAIN
]
)
# Add weather alert entity only if location support this feature
if coordinator_alert:
entities.extend(
[
MeteoFranceAlertSensor(coordinator_alert, description)
for description in SENSOR_TYPES_ALERT
]
)
# Add weather probability entities only if location support this feature
if coordinator_forecast.data.probability_forecast:
entities.extend(
[
+1 -1
View File
@@ -2,7 +2,7 @@
"domain": "mill",
"name": "Mill",
"documentation": "https://www.home-assistant.io/integrations/mill",
"requirements": ["millheater==0.6.0"],
"requirements": ["millheater==0.6.1"],
"codeowners": ["@danielhiversen"],
"config_flow": true,
"iot_class": "cloud_polling"
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
"install_app": "Nyissa meg a mobil alkalmaz\u00e1st Home Assistant-tal val\u00f3 integr\u00e1ci\u00f3hoz. A kompatibilis alkalmaz\u00e1sok list\u00e1j\u00e1nak megtekint\u00e9s\u00e9hez ellen\u0151rizze [a le\u00edr\u00e1st]({apps_url})."
"install_app": "Nyissa meg a mobil alkalmaz\u00e1st Home Assistanttal val\u00f3 integr\u00e1ci\u00f3hoz. A kompatibilis alkalmaz\u00e1sok list\u00e1j\u00e1nak megtekint\u00e9s\u00e9hez ellen\u0151rizze [a le\u00edr\u00e1st]({apps_url})."
},
"step": {
"confirm": {
@@ -16,7 +16,7 @@
"data": {
"host": "C\u00edm"
},
"description": "\u00c1ll\u00edtsa be Modern Forms-t, hogy integr\u00e1l\u00f3djon Home Assistant-ba."
"description": "Integr\u00e1lja \u00f6ssze Modern Formst Home Assistanttal."
},
"zeroconf_confirm": {
"description": "Hozz\u00e1 szeretn\u00e9 adni `{name}`nev\u0171 Modern Forms rajong\u00f3t Home Assistanthoz?",
@@ -12,7 +12,7 @@
},
"step": {
"hassio_confirm": {
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistant-ot, hogy csatlakozzon {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal biztos\u00edtott motionEye szolg\u00e1ltat\u00e1shoz?",
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot motionEyehez val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
"title": "motionEye a Home Assistant kieg\u00e9sz\u00edt\u0151 seg\u00edts\u00e9g\u00e9vel"
},
"user": {
@@ -22,7 +22,7 @@
"data": {
"discovery": "Felfedez\u00e9s enged\u00e9lyez\u00e9se"
},
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistant-ot MQTT br\u00f3kerhez val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani Home Assistantot MQTT br\u00f3kerhez val\u00f3 csatlakoz\u00e1shoz, {addon} kieg\u00e9sz\u00edt\u0151 \u00e1ltal?",
"title": "MQTT Br\u00f3ker - Home Assistant kieg\u00e9sz\u00edt\u0151 \u00e1ltal"
}
}
+2 -2
View File
@@ -168,8 +168,8 @@ class NanoleafLight(LightEntity):
async def async_turn_off(self, **kwargs):
"""Instruct the light to turn off."""
transition = kwargs.get(ATTR_TRANSITION)
await self._nanoleaf.turn_off(transition)
transition: float | None = kwargs.get(ATTR_TRANSITION)
await self._nanoleaf.turn_off(None if transition is None else int(transition))
async def async_update(self) -> None:
"""Fetch new state data for this light."""
@@ -3,7 +3,7 @@
"name": "Nanoleaf",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/nanoleaf",
"requirements": ["aionanoleaf==0.0.2"],
"requirements": ["aionanoleaf==0.0.3"],
"zeroconf": ["_nanoleafms._tcp.local.", "_nanoleafapi._tcp.local."],
"homekit" : {
"models": [
@@ -3,7 +3,7 @@
"name": "Netatmo",
"documentation": "https://www.home-assistant.io/integrations/netatmo",
"requirements": [
"pyatmo==6.0.0"
"pyatmo==6.1.0"
],
"after_dependencies": [
"cloud",
@@ -1,4 +1,5 @@
"""Config flow to configure the Netgear integration."""
import logging
from urllib.parse import urlparse
from pynetgear import DEFAULT_HOST, DEFAULT_PORT, DEFAULT_USER
@@ -20,6 +21,8 @@ from .const import CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME, DEFAULT_NAME, DOMA
from .errors import CannotLoginException
from .router import get_api
_LOGGER = logging.getLogger(__name__)
def _discovery_schema_with_defaults(discovery_info):
return vol.Schema(_ordered_shared_schema(discovery_info))
@@ -120,15 +123,19 @@ class NetgearFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
device_url = urlparse(discovery_info[ssdp.ATTR_SSDP_LOCATION])
if device_url.hostname:
updated_data[CONF_HOST] = device_url.hostname
if device_url.port:
updated_data[CONF_PORT] = device_url.port
if device_url.scheme == "https":
updated_data[CONF_SSL] = True
else:
updated_data[CONF_SSL] = False
_LOGGER.debug("Netgear ssdp discovery info: %s", discovery_info)
await self.async_set_unique_id(discovery_info[ssdp.ATTR_UPNP_SERIAL])
self._abort_if_unique_id_configured(updates=updated_data)
if device_url.port:
updated_data[CONF_PORT] = device_url.port
self.placeholders.update(updated_data)
self.discovered = True
+18 -1
View File
@@ -11,7 +11,24 @@ DEFAULT_CONSIDER_HOME = timedelta(seconds=180)
DEFAULT_NAME = "Netgear router"
# update method V2 models
MODELS_V2 = ["Orbi"]
MODELS_V2 = [
"Orbi",
"RBK",
"RBR",
"RBS",
"RBW",
"LBK",
"LBR",
"CBK",
"CBR",
"SRC",
"SRK",
"SRR",
"SRS",
"SXK",
"SXR",
"SXS",
]
# Icons
DEVICE_ICONS = {
@@ -4,6 +4,7 @@ import logging
import voluptuous as vol
from homeassistant.components.device_tracker import (
DOMAIN as DEVICE_TRACKER_DOMAIN,
PLATFORM_SCHEMA as PARENT_PLATFORM_SCHEMA,
SOURCE_TYPE_ROUTER,
)
@@ -50,7 +51,7 @@ async def async_get_scanner(hass, config):
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=config,
data=config[DEVICE_TRACKER_DOMAIN],
)
)
+8 -7
View File
@@ -146,8 +146,9 @@ class NetgearRouter:
self.model = self._info.get("ModelName")
self.firmware_version = self._info.get("Firmwareversion")
if self.model in MODELS_V2:
self._method_version = 2
for model in MODELS_V2:
if self.model.startswith(model):
self._method_version = 2
async def async_setup(self) -> None:
"""Set up a Netgear router."""
@@ -198,12 +199,12 @@ class NetgearRouter:
ntg_devices = await self.async_get_attached_devices()
now = dt_util.utcnow()
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Netgear scan result: \n%s", ntg_devices)
for ntg_device in ntg_devices:
device_mac = format_mac(ntg_device.mac)
if self._method_version == 2 and not ntg_device.link_rate:
continue
if not self.devices.get(device_mac):
new_device = True
@@ -273,8 +274,8 @@ class NetgearDeviceEntity(Entity):
"""Return the device information."""
return {
"connections": {(CONNECTION_NETWORK_MAC, self._mac)},
"name": self._device_name,
"model": self._device["device_model"],
"default_name": self._device_name,
"default_model": self._device["device_model"],
"via_device": (DOMAIN, self._router.unique_id),
}
@@ -2,7 +2,7 @@
"domain": "nissan_leaf",
"name": "Nissan Leaf",
"documentation": "https://www.home-assistant.io/integrations/nissan_leaf",
"requirements": ["pycarwings2==2.11"],
"requirements": ["pycarwings2==2.12"],
"codeowners": ["@filcole"],
"iot_class": "cloud_polling"
}
@@ -22,10 +22,13 @@
"step": {
"init": {
"data": {
"consider_home": "\u7b49\u5f85\u591a\u5c11\u79d2\u540e\u5219\u5224\u5b9a\u8bbe\u5907\u79bb\u5f00",
"exclude": "\u4ece\u626b\u63cf\u4e2d\u6392\u9664\u7684\u7f51\u7edc\u5730\u5740\uff08\u4ee5\u9017\u53f7\u5206\u9694\uff09",
"home_interval": "\u626b\u63cf\u8bbe\u5907\u7684\u6700\u5c0f\u95f4\u9694\u5206\u949f\u6570\uff08\u7528\u4e8e\u8282\u7701\u7535\u91cf\uff09",
"hosts": "\u8981\u626b\u63cf\u7684\u7f51\u7edc\u5730\u5740\uff08\u4ee5\u9017\u53f7\u5206\u9694\uff09",
"scan_options": "Nmap \u7684\u539f\u59cb\u53ef\u914d\u7f6e\u626b\u63cf\u9009\u9879"
"interval_seconds": "\u626b\u63cf\u95f4\u9694\uff08\u79d2\uff09",
"scan_options": "Nmap \u7684\u539f\u59cb\u53ef\u914d\u7f6e\u626b\u63cf\u9009\u9879",
"track_new_devices": "\u8ddf\u8e2a\u65b0\u8bbe\u5907"
},
"description": "\u914d\u7f6e\u901a\u8fc7 Nmap \u626b\u63cf\u7684\u4e3b\u673a\u3002\u7f51\u7edc\u5730\u5740\u548c\u6392\u9664\u9879\u53ef\u4ee5\u662f IP \u5730\u5740 (192.168.1.1)\u3001IP \u5730\u5740\u5757 (192.168.0.0/24) \u6216 IP \u8303\u56f4 (192.168.1.0-32)\u3002"
}
@@ -0,0 +1,22 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_auth": "\u05d0\u05d9\u05de\u05d5\u05ea \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",
"unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4"
},
"step": {
"user": {
"data": {
"device_key": "\u05de\u05e4\u05ea\u05d7 \u05d4\u05ea\u05e7\u05df",
"host": "\u05de\u05d0\u05e8\u05d7",
"port": "\u05e4\u05ea\u05d7\u05d4",
"verify_ssl": "\u05d0\u05d9\u05de\u05d5\u05ea \u05d0\u05d9\u05e9\u05d5\u05e8 SSL"
}
}
}
}
}
@@ -0,0 +1,12 @@
{
"config": {
"abort": {
"already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e"
},
"error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25",
"invalid_auth": "\u51ed\u8bc1\u65e0\u6548",
"unknown": "\u672a\u77e5\u9519\u8bef"
}
}
}
+4 -1
View File
@@ -5,7 +5,10 @@ def pretty_title(media, short_name=False):
"""Return a formatted title for the given media item."""
year = None
if media.type == "album":
title = f"{media.parentTitle} - {media.title}"
if short_name:
title = media.title
else:
title = f"{media.parentTitle} - {media.title}"
elif media.type == "episode":
title = f"{media.seasonEpisode.upper()} - {media.title}"
if not short_name:
+23 -10
View File
@@ -91,11 +91,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry_id = entry.entry_id
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN].setdefault(entry_id, {})
http_session = requests.Session()
ip_address = entry.data[CONF_IP_ADDRESS]
password = entry.data.get(CONF_PASSWORD)
power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session)
power_wall = Powerwall(ip_address, http_session=http_session)
try:
powerwall_data = await hass.async_add_executor_job(
_login_and_fetch_base_info, power_wall, password
@@ -115,13 +115,28 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await _migrate_old_unique_ids(hass, entry_id, powerwall_data)
login_failed_count = 0
runtime_data = hass.data[DOMAIN][entry.entry_id] = {
POWERWALL_API_CHANGED: False,
POWERWALL_HTTP_SESSION: http_session,
}
def _recreate_powerwall_login():
nonlocal http_session
nonlocal power_wall
http_session.close()
http_session = requests.Session()
power_wall = Powerwall(ip_address, http_session=http_session)
runtime_data[POWERWALL_OBJECT] = power_wall
runtime_data[POWERWALL_HTTP_SESSION] = http_session
power_wall.login("", password)
async def async_update_data():
"""Fetch data from API endpoint."""
# Check if we had an error before
nonlocal login_failed_count
_LOGGER.debug("Checking if update failed")
if hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]:
return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data
if runtime_data[POWERWALL_API_CHANGED]:
return runtime_data[POWERWALL_COORDINATOR].data
_LOGGER.debug("Updating data")
try:
@@ -130,9 +145,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if password is None:
raise ConfigEntryAuthFailed from err
# If the session expired, relogin, and try again
# If the session expired, recreate, relogin, and try again
try:
await hass.async_add_executor_job(power_wall.login, "", password)
await hass.async_add_executor_job(_recreate_powerwall_login)
return await _async_update_powerwall_data(hass, entry, power_wall)
except AccessDeniedError as ex:
login_failed_count += 1
@@ -153,13 +168,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)
hass.data[DOMAIN][entry.entry_id] = powerwall_data
hass.data[DOMAIN][entry.entry_id].update(
runtime_data.update(
{
**powerwall_data,
POWERWALL_OBJECT: power_wall,
POWERWALL_COORDINATOR: coordinator,
POWERWALL_HTTP_SESSION: http_session,
POWERWALL_API_CHANGED: False,
}
)
@@ -1,5 +1,5 @@
"""Support for powerwall binary sensors."""
from tesla_powerwall import GridStatus
from tesla_powerwall import GridStatus, MeterType
from homeassistant.components.binary_sensor import (
DEVICE_CLASS_BATTERY_CHARGING,
@@ -142,4 +142,8 @@ class PowerWallChargingStatusSensor(PowerWallEntity, BinarySensorEntity):
def is_on(self):
"""Powerwall is charging."""
# is_sending_to returns true for values greater than 100 watts
return self.coordinator.data[POWERWALL_API_METERS].battery.is_sending_to()
return (
self.coordinator.data[POWERWALL_API_METERS]
.get_meter(MeterType.BATTERY)
.is_sending_to()
)
@@ -3,7 +3,7 @@
"name": "Tesla Powerwall",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/powerwall",
"requirements": ["tesla-powerwall==0.3.10"],
"requirements": ["tesla-powerwall==0.3.11"],
"codeowners": ["@bdraco", "@jrester"],
"dhcp": [
{
+2 -1
View File
@@ -53,7 +53,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
powerwalls_serial_numbers = powerwall_data[POWERWALL_API_SERIAL_NUMBERS]
entities = []
for meter in MeterType:
# coordinator.data[POWERWALL_API_METERS].meters holds all meters that are available
for meter in coordinator.data[POWERWALL_API_METERS].meters:
entities.append(
PowerWallEnergySensor(
meter,
+21 -26
View File
@@ -470,17 +470,20 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901
elif new_version == 18:
# Recreate the statistics and statistics meta tables.
#
# Order matters! Statistics has a relation with StatisticsMeta,
# so statistics need to be deleted before meta (or in pair depending
# on the SQL backend); and meta needs to be created before statistics.
if sqlalchemy.inspect(engine).has_table(
StatisticsMeta.__tablename__
) or sqlalchemy.inspect(engine).has_table(Statistics.__tablename__):
Base.metadata.drop_all(
bind=engine, tables=[Statistics.__table__, StatisticsMeta.__table__]
)
# Order matters! Statistics and StatisticsShortTerm have a relation with
# StatisticsMeta, so statistics need to be deleted before meta (or in pair
# depending on the SQL backend); and meta needs to be created before statistics.
Base.metadata.drop_all(
bind=engine,
tables=[
StatisticsShortTerm.__table__,
Statistics.__table__,
StatisticsMeta.__table__,
],
)
StatisticsMeta.__table__.create(engine)
StatisticsShortTerm.__table__.create(engine)
Statistics.__table__.create(engine)
elif new_version == 19:
# This adds the statistic runs table, insert a fake run to prevent duplicating
@@ -527,23 +530,15 @@ def _apply_update(instance, session, new_version, old_version): # noqa: C901
# so statistics need to be deleted before meta (or in pair depending
# on the SQL backend); and meta needs to be created before statistics.
if engine.dialect.name == "oracle":
if (
sqlalchemy.inspect(engine).has_table(StatisticsMeta.__tablename__)
or sqlalchemy.inspect(engine).has_table(Statistics.__tablename__)
or sqlalchemy.inspect(engine).has_table(StatisticsRuns.__tablename__)
or sqlalchemy.inspect(engine).has_table(
StatisticsShortTerm.__tablename__
)
):
Base.metadata.drop_all(
bind=engine,
tables=[
StatisticsShortTerm.__table__,
Statistics.__table__,
StatisticsMeta.__table__,
StatisticsRuns.__table__,
],
)
Base.metadata.drop_all(
bind=engine,
tables=[
StatisticsShortTerm.__table__,
Statistics.__table__,
StatisticsMeta.__table__,
StatisticsRuns.__table__,
],
)
StatisticsRuns.__table__.create(engine)
StatisticsMeta.__table__.create(engine)
+38 -13
View File
@@ -38,7 +38,8 @@ def purge_old_data(
event_ids = _select_event_ids_to_purge(session, purge_before)
state_ids = _select_state_ids_to_purge(session, purge_before, event_ids)
if state_ids:
_purge_state_ids(session, state_ids)
_purge_state_ids(instance, session, state_ids)
if event_ids:
_purge_event_ids(session, event_ids)
# If states or events purging isn't processing the purge_before yet,
@@ -68,10 +69,10 @@ def _select_event_ids_to_purge(session: Session, purge_before: datetime) -> list
def _select_state_ids_to_purge(
session: Session, purge_before: datetime, event_ids: list[int]
) -> list[int]:
) -> set[int]:
"""Return a list of state ids to purge."""
if not event_ids:
return []
return set()
states = (
session.query(States.state_id)
.filter(States.last_updated < purge_before)
@@ -79,10 +80,10 @@ def _select_state_ids_to_purge(
.all()
)
_LOGGER.debug("Selected %s state ids to remove", len(states))
return [state.state_id for state in states]
return {state.state_id for state in states}
def _purge_state_ids(session: Session, state_ids: list[int]) -> None:
def _purge_state_ids(instance: Recorder, session: Session, state_ids: set[int]) -> None:
"""Disconnect states and delete by state id."""
# Update old_state_id to NULL before deleting to ensure
@@ -103,6 +104,26 @@ def _purge_state_ids(session: Session, state_ids: list[int]) -> None:
)
_LOGGER.debug("Deleted %s states", deleted_rows)
# Evict eny entries in the old_states cache referring to a purged state
_evict_purged_states_from_old_states_cache(instance, state_ids)
def _evict_purged_states_from_old_states_cache(
instance: Recorder, purged_state_ids: set[int]
) -> None:
"""Evict purged states from the old states cache."""
# Make a map from old_state_id to entity_id
old_states = instance._old_states # pylint: disable=protected-access
old_state_reversed = {
old_state.state_id: entity_id
for entity_id, old_state in old_states.items()
if old_state.state_id
}
# Evict any purged state from the old states cache
for purged_state_id in purged_state_ids.intersection(old_state_reversed):
old_states.pop(old_state_reversed[purged_state_id], None)
def _purge_event_ids(session: Session, event_ids: list[int]) -> None:
"""Delete by event id."""
@@ -139,7 +160,7 @@ def _purge_filtered_data(instance: Recorder, session: Session) -> bool:
if not instance.entity_filter(entity_id)
]
if len(excluded_entity_ids) > 0:
_purge_filtered_states(session, excluded_entity_ids)
_purge_filtered_states(instance, session, excluded_entity_ids)
return False
# Check if excluded event_types are in database
@@ -149,13 +170,15 @@ def _purge_filtered_data(instance: Recorder, session: Session) -> bool:
if event_type in instance.exclude_t
]
if len(excluded_event_types) > 0:
_purge_filtered_events(session, excluded_event_types)
_purge_filtered_events(instance, session, excluded_event_types)
return False
return True
def _purge_filtered_states(session: Session, excluded_entity_ids: list[str]) -> None:
def _purge_filtered_states(
instance: Recorder, session: Session, excluded_entity_ids: list[str]
) -> None:
"""Remove filtered states and linked events."""
state_ids: list[int]
event_ids: list[int | None]
@@ -171,11 +194,13 @@ def _purge_filtered_states(session: Session, excluded_entity_ids: list[str]) ->
_LOGGER.debug(
"Selected %s state_ids to remove that should be filtered", len(state_ids)
)
_purge_state_ids(session, state_ids)
_purge_state_ids(instance, session, set(state_ids))
_purge_event_ids(session, event_ids) # type: ignore # type of event_ids already narrowed to 'list[int]'
def _purge_filtered_events(session: Session, excluded_event_types: list[str]) -> None:
def _purge_filtered_events(
instance: Recorder, session: Session, excluded_event_types: list[str]
) -> None:
"""Remove filtered events and linked states."""
events: list[Events] = (
session.query(Events.event_id)
@@ -190,8 +215,8 @@ def _purge_filtered_events(session: Session, excluded_event_types: list[str]) ->
states: list[States] = (
session.query(States.state_id).filter(States.event_id.in_(event_ids)).all()
)
state_ids: list[int] = [state.state_id for state in states]
_purge_state_ids(session, state_ids)
state_ids: set[int] = {state.state_id for state in states}
_purge_state_ids(instance, session, state_ids)
_purge_event_ids(session, event_ids)
@@ -207,7 +232,7 @@ def purge_entity_data(instance: Recorder, entity_filter: Callable[[str], bool])
_LOGGER.debug("Purging entity data for %s", selected_entity_ids)
if len(selected_entity_ids) > 0:
# Purge a max of MAX_ROWS_TO_PURGE, based on the oldest states or events record
_purge_filtered_states(session, selected_entity_ids)
_purge_filtered_states(instance, session, selected_entity_ids)
_LOGGER.debug("Purging entity data hasn't fully completed yet")
return False
@@ -13,6 +13,7 @@ from sqlalchemy import bindparam, func
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext import baked
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.sql.expression import true
from homeassistant.const import (
PRESSURE_PA,
@@ -396,9 +397,9 @@ def get_metadata_with_session(
StatisticsMeta.statistic_id.in_(bindparam("statistic_ids"))
)
if statistic_type == "mean":
baked_query += lambda q: q.filter(StatisticsMeta.has_mean.isnot(False))
baked_query += lambda q: q.filter(StatisticsMeta.has_mean == true())
elif statistic_type == "sum":
baked_query += lambda q: q.filter(StatisticsMeta.has_sum.isnot(False))
baked_query += lambda q: q.filter(StatisticsMeta.has_sum == true())
result = execute(baked_query(session).params(statistic_ids=statistic_ids))
if not result:
return {}
@@ -9,7 +9,7 @@
},
"step": {
"link": {
"description": "Enged\u00e9lyeznie kell az Home Assistant-ot a Roonban. Miut\u00e1n r\u00e1kattintott a K\u00fcld\u00e9s gombra, nyissa meg a Roon Core alkalmaz\u00e1st, nyissa meg a Be\u00e1ll\u00edt\u00e1sokat, \u00e9s enged\u00e9lyezze a Home Assistant funkci\u00f3t a B\u0151v\u00edtm\u00e9nyek lapon.",
"description": "Enged\u00e9lyeznie kell az Home Assistantot a Roonban. Miut\u00e1n r\u00e1kattintott a K\u00fcld\u00e9s gombra, nyissa meg a Roon Core alkalmaz\u00e1st, nyissa meg a Be\u00e1ll\u00edt\u00e1sokat, \u00e9s enged\u00e9lyezze a Home Assistant funkci\u00f3t a B\u0151v\u00edtm\u00e9nyek lapon.",
"title": "Enged\u00e9lyezze a Home Assistant alkalmaz\u00e1st Roon-ban"
},
"user": {
@@ -133,7 +133,7 @@ class SamsungTVDevice(MediaPlayerEntity):
def update(self) -> None:
"""Update state of device."""
if self._auth_failed:
if self._auth_failed or self.hass.is_stopping:
return
if self._power_off_in_progress():
self._state = STATE_OFF
@@ -17,7 +17,7 @@
"flow_title": "{device}",
"step": {
"confirm": {
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani {device} k\u00e9sz\u00fcl\u00e9k\u00e9t? Ha kor\u00e1bban m\u00e9g sosem csatlakoztatta Home Assistant-hoz, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ami j\u00f3v\u00e1hagy\u00e1sra v\u00e1r.",
"description": "Szeretn\u00e9 be\u00e1ll\u00edtani {device} k\u00e9sz\u00fcl\u00e9k\u00e9t? Ha kor\u00e1bban m\u00e9g sosem csatlakoztatta Home Assistanthoz, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ami j\u00f3v\u00e1hagy\u00e1sra v\u00e1r.",
"title": "Samsung TV"
},
"reauth_confirm": {
@@ -28,7 +28,7 @@
"host": "C\u00edm",
"name": "N\u00e9v"
},
"description": "\u00cdrd be a Samsung TV adatait. Ha m\u00e9g soha nem csatlakozott Home Assistant-hez, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ahol hiteles\u00edt\u00e9st k\u00e9r."
"description": "\u00cdrja be a Samsung TV adatait. Ha m\u00e9g soha nem csatlakozott Home Assistanthoz, akkor meg kell jelennie egy felugr\u00f3 ablaknak a TV k\u00e9perny\u0151j\u00e9n, ahol meg kell adni az enged\u00e9lyt."
}
}
}
+53 -16
View File
@@ -7,6 +7,7 @@ import datetime
import itertools
import logging
import math
from typing import Any
from sqlalchemy.orm.session import Session
@@ -362,13 +363,14 @@ def _wanted_statistics(sensor_states: list[State]) -> dict[str, set[str]]:
return wanted_statistics
def _last_reset_as_utc_isoformat(
last_reset_s: str | None, entity_id: str
) -> str | None:
def _last_reset_as_utc_isoformat(last_reset_s: Any, entity_id: str) -> str | None:
"""Parse last_reset and convert it to UTC."""
if last_reset_s is None:
return None
last_reset = dt_util.parse_datetime(last_reset_s)
if isinstance(last_reset_s, str):
last_reset = dt_util.parse_datetime(last_reset_s)
else:
last_reset = None
if last_reset is None:
_LOGGER.warning(
"Ignoring invalid last reset '%s' for %s", last_reset_s, entity_id
@@ -637,35 +639,70 @@ def validate_statistics(
"""Validate statistics."""
validation_result = defaultdict(list)
sensor_states = _get_sensor_states(hass)
sensor_states = hass.states.all(DOMAIN)
metadatas = statistics.get_metadata(hass, [i.entity_id for i in sensor_states])
for state in sensor_states:
entity_id = state.entity_id
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
state_class = state.attributes.get(ATTR_STATE_CLASS)
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if device_class not in UNIT_CONVERSIONS:
metadata = statistics.get_metadata(hass, (entity_id,))
if not metadata:
continue
metadata_unit = metadata[entity_id][1]["unit_of_measurement"]
if state_unit != metadata_unit:
if metadata := metadatas.get(entity_id):
if not is_entity_recorded(hass, state.entity_id):
# Sensor was previously recorded, but no longer is
validation_result[entity_id].append(
statistics.ValidationIssue(
"units_changed",
"entity_not_recorded",
{"statistic_id": entity_id},
)
)
if state_class not in STATE_CLASSES:
# Sensor no longer has a valid state class
validation_result[entity_id].append(
statistics.ValidationIssue(
"unsupported_state_class",
{"statistic_id": entity_id, "state_class": state_class},
)
)
metadata_unit = metadata[1]["unit_of_measurement"]
if device_class not in UNIT_CONVERSIONS:
if state_unit != metadata_unit:
# The unit has changed
validation_result[entity_id].append(
statistics.ValidationIssue(
"units_changed",
{
"statistic_id": entity_id,
"state_unit": state_unit,
"metadata_unit": metadata_unit,
},
)
)
elif metadata_unit != DEVICE_CLASS_UNITS[device_class]:
# The unit in metadata is not supported for this device class
validation_result[entity_id].append(
statistics.ValidationIssue(
"unsupported_unit_metadata",
{
"statistic_id": entity_id,
"state_unit": state_unit,
"device_class": device_class,
"metadata_unit": metadata_unit,
"supported_unit": DEVICE_CLASS_UNITS[device_class],
},
)
)
continue
if state_unit not in UNIT_CONVERSIONS[device_class]:
if (
device_class in UNIT_CONVERSIONS
and state_unit not in UNIT_CONVERSIONS[device_class]
):
# The unit in the state is not supported for this device class
validation_result[entity_id].append(
statistics.ValidationIssue(
"unsupported_unit",
"unsupported_unit_state",
{
"statistic_id": entity_id,
"device_class": device_class,
@@ -122,34 +122,28 @@ REST_SENSORS: Final = {
RPC_SENSORS: Final = {
"input": RpcAttributeDescription(
key="input",
sub_key="state",
name="Input",
value=lambda status, _: status["state"],
device_class=DEVICE_CLASS_POWER,
default_enabled=False,
removal_condition=is_rpc_momentary_input,
),
"cloud": RpcAttributeDescription(
key="cloud",
sub_key="connected",
name="Cloud",
value=lambda status, _: status["connected"],
device_class=DEVICE_CLASS_CONNECTIVITY,
default_enabled=False,
),
"fwupdate": RpcAttributeDescription(
key="sys",
sub_key="available_updates",
name="Firmware Update",
device_class=DEVICE_CLASS_UPDATE,
value=lambda status, _: status["available_updates"],
default_enabled=False,
extra_state_attributes=lambda status: {
"latest_stable_version": status["available_updates"].get(
"stable",
{"version": ""},
)["version"],
"beta_version": status["available_updates"].get(
"beta",
{"version": ""},
)["version"],
"latest_stable_version": status.get("stable", {"version": ""})["version"],
"beta_version": status.get("beta", {"version": ""})["version"],
},
),
}
+15 -4
View File
@@ -166,6 +166,10 @@ async def async_setup_entry_rpc(
key_instances = get_rpc_key_instances(wrapper.device.status, description.key)
for key in key_instances:
# Filter non-existing sensors
if description.sub_key not in wrapper.device.status[key]:
continue
# Filter and remove entities that according to settings should not create an entity
if description.removal_condition and description.removal_condition(
wrapper.device.config, key
@@ -240,10 +244,11 @@ class RpcAttributeDescription:
"""Class to describe a RPC sensor."""
key: str
sub_key: str
name: str
icon: str | None = None
unit: str | None = None
value: Callable[[dict, Any], Any] | None = None
value: Callable[[Any, Any], Any] | None = None
device_class: str | None = None
state_class: str | None = None
default_enabled: bool = True
@@ -549,6 +554,7 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
) -> None:
"""Initialize sensor."""
super().__init__(wrapper, key)
self.sub_key = description.sub_key
self.attribute = attribute
self.description = description
@@ -564,8 +570,11 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
"""Value of sensor."""
if callable(self.description.value):
self._last_value = self.description.value(
self.wrapper.device.status[self.key], self._last_value
self.wrapper.device.status[self.key][self.sub_key], self._last_value
)
else:
self._last_value = self.wrapper.device.status[self.key][self.sub_key]
return self._last_value
@property
@@ -576,7 +585,9 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
if not available or not self.description.available:
return available
return self.description.available(self.wrapper.device.status[self.key])
return self.description.available(
self.wrapper.device.status[self.key][self.sub_key]
)
@property
def extra_state_attributes(self) -> dict[str, Any] | None:
@@ -585,7 +596,7 @@ class ShellyRpcAttributeEntity(ShellyRpcEntity, entity.Entity):
return None
return self.description.extra_state_attributes(
self.wrapper.device.status[self.key]
self.wrapper.device.status[self.key][self.sub_key]
)
@@ -3,7 +3,7 @@
"name": "Shelly",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/shelly",
"requirements": ["aioshelly==1.0.1"],
"requirements": ["aioshelly==1.0.2"],
"zeroconf": [
{
"type": "_http._tcp.local.",
+11 -6
View File
@@ -242,51 +242,56 @@ REST_SENSORS: Final = {
RPC_SENSORS: Final = {
"power": RpcAttributeDescription(
key="switch",
sub_key="apower",
name="Power",
unit=POWER_WATT,
value=lambda status, _: round(float(status["apower"]), 1),
value=lambda status, _: round(float(status), 1),
device_class=sensor.DEVICE_CLASS_POWER,
state_class=sensor.STATE_CLASS_MEASUREMENT,
),
"voltage": RpcAttributeDescription(
key="switch",
sub_key="voltage",
name="Voltage",
unit=ELECTRIC_POTENTIAL_VOLT,
value=lambda status, _: round(float(status["voltage"]), 1),
value=lambda status, _: round(float(status), 1),
device_class=sensor.DEVICE_CLASS_VOLTAGE,
state_class=sensor.STATE_CLASS_MEASUREMENT,
default_enabled=False,
),
"energy": RpcAttributeDescription(
key="switch",
sub_key="aenergy",
name="Energy",
unit=ENERGY_KILO_WATT_HOUR,
value=lambda status, _: round(status["aenergy"]["total"] / 1000, 2),
value=lambda status, _: round(status["total"] / 1000, 2),
device_class=sensor.DEVICE_CLASS_ENERGY,
state_class=sensor.STATE_CLASS_TOTAL_INCREASING,
),
"temperature": RpcAttributeDescription(
key="switch",
sub_key="temperature",
name="Temperature",
unit=TEMP_CELSIUS,
value=lambda status, _: round(status["temperature"]["tC"], 1),
value=lambda status, _: round(status["tC"], 1),
device_class=sensor.DEVICE_CLASS_TEMPERATURE,
state_class=sensor.STATE_CLASS_MEASUREMENT,
default_enabled=False,
),
"rssi": RpcAttributeDescription(
key="wifi",
sub_key="rssi",
name="RSSI",
unit=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
value=lambda status, _: status["rssi"],
device_class=sensor.DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=sensor.STATE_CLASS_MEASUREMENT,
default_enabled=False,
),
"uptime": RpcAttributeDescription(
key="sys",
sub_key="uptime",
name="Uptime",
value=lambda status, last: get_device_uptime(status["uptime"], last),
value=get_device_uptime,
device_class=sensor.DEVICE_CLASS_TIMESTAMP,
default_enabled=False,
),
@@ -15,7 +15,7 @@
"data": {
"environment": "K\u00f6rnyezet"
},
"description": "\u00c1ll\u00edtsa be a Smappee k\u00e9sz\u00fcl\u00e9ket az HomeAssistant-al val\u00f3 integr\u00e1ci\u00f3hoz."
"description": "Integr\u00e1lja \u00f6ssze Smappee k\u00e9sz\u00fcl\u00e9ket HomeAssistanttal."
},
"local": {
"data": {
+1 -1
View File
@@ -2,7 +2,7 @@
"domain": "ssdp",
"name": "Simple Service Discovery Protocol (SSDP)",
"documentation": "https://www.home-assistant.io/integrations/ssdp",
"requirements": ["async-upnp-client==0.22.4"],
"requirements": ["async-upnp-client==0.22.8"],
"dependencies": ["network"],
"after_dependencies": ["zeroconf"],
"codeowners": [],
@@ -2,6 +2,7 @@
"config": {
"abort": {
"already_configured_device": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"unknown": "\u05e9\u05d2\u05d9\u05d0\u05d4 \u05d1\u05dc\u05ea\u05d9 \u05e6\u05e4\u05d5\u05d9\u05d4"
},
"error": {
@@ -8,7 +8,9 @@
"unknown": "Errore imprevisto"
},
"error": {
"cannot_connect": "Impossibile connettersi"
"cannot_connect": "Impossibile connettersi",
"one": "Vuoto",
"other": "Vuoti"
},
"flow_title": "{name}",
"step": {
+19 -1
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from datetime import timedelta
from typing import Any
from kasa import SmartDevice, SmartDeviceException
@@ -11,9 +12,15 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components import network
from homeassistant.config_entries import ConfigEntry, ConfigEntryNotReady
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.const import (
CONF_HOST,
CONF_MAC,
CONF_NAME,
EVENT_HOMEASSISTANT_STARTED,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from .const import (
@@ -32,6 +39,8 @@ from .migration import (
async_migrate_yaml_entries,
)
DISCOVERY_INTERVAL = timedelta(minutes=15)
TPLINK_HOST_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})
CONFIG_SCHEMA = vol.Schema(
@@ -111,6 +120,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async_migrate_legacy_entries(
hass, hosts_by_mac, config_entries_by_mac, legacy_entry
)
# Migrate the yaml entry that was previously imported
async_migrate_yaml_entries(hass, legacy_entry.data)
if conf is not None:
async_migrate_yaml_entries(hass, conf)
@@ -118,6 +129,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if discovered_devices:
async_trigger_discovery(hass, discovered_devices)
async def _async_discovery(*_: Any) -> None:
if discovered := await async_discover_devices(hass):
async_trigger_discovery(hass, discovered)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_discovery)
async_track_time_interval(hass, _async_discovery, DISCOVERY_INTERVAL)
return True
+15 -3
View File
@@ -63,10 +63,20 @@ class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity):
@async_refresh_after
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
transition = kwargs.get(ATTR_TRANSITION)
if (transition := kwargs.get(ATTR_TRANSITION)) is not None:
transition = int(transition * 1_000)
if (brightness := kwargs.get(ATTR_BRIGHTNESS)) is not None:
brightness = round((brightness * 100.0) / 255.0)
if self.device.is_dimmer and transition is None:
# This is a stopgap solution for inconsistent set_brightness handling
# in the upstream library, see #57265.
# This should be removed when the upstream has fixed the issue.
# The device logic is to change the settings without turning it on
# except when transition is defined, so we leverage that here for now.
transition = 1
# Handle turning to temp mode
if ATTR_COLOR_TEMP in kwargs:
color_tmp = mired_to_kelvin(int(kwargs[ATTR_COLOR_TEMP]))
@@ -92,7 +102,9 @@ class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity):
@async_refresh_after
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self.device.turn_off(transition=kwargs.get(ATTR_TRANSITION))
if (transition := kwargs.get(ATTR_TRANSITION)) is not None:
transition = int(transition * 1_000)
await self.device.turn_off(transition=transition)
@property
def min_mireds(self) -> int:
@@ -145,7 +157,7 @@ class TPLinkSmartBulb(CoordinatedTPLinkEntity, LightEntity):
def color_mode(self) -> str | None:
"""Return the active color mode."""
if self.device.is_color:
if self.device.color_temp:
if self.device.is_variable_color_temp and self.device.color_temp:
return COLOR_MODE_COLOR_TEMP
return COLOR_MODE_HS
if self.device.is_variable_color_temp:
@@ -9,6 +9,10 @@
"quality_scale": "platinum",
"iot_class": "local_polling",
"dhcp": [
{
"hostname": "k[lp]*",
"macaddress": "403F8C*"
},
{
"hostname": "ep*",
"macaddress": "E848B8*"
+5 -1
View File
@@ -2,6 +2,8 @@
from __future__ import annotations
from datetime import datetime
from types import MappingProxyType
from typing import Any
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
@@ -65,7 +67,9 @@ def async_migrate_legacy_entries(
@callback
def async_migrate_yaml_entries(hass: HomeAssistant, conf: ConfigType) -> None:
def async_migrate_yaml_entries(
hass: HomeAssistant, conf: ConfigType | MappingProxyType[str, Any]
) -> None:
"""Migrate yaml to config entries."""
for device_type in (CONF_LIGHT, CONF_SWITCH, CONF_STRIP, CONF_DIMMER):
for device in conf.get(device_type, []):
+8 -2
View File
@@ -44,6 +44,7 @@ class TPLinkSensorEntityDescription(SensorEntityDescription):
"""Describes TPLink sensor entity."""
emeter_attr: str | None = None
precision: int | None = None
ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
@@ -54,6 +55,7 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
state_class=STATE_CLASS_MEASUREMENT,
name="Current Consumption",
emeter_attr="power",
precision=1,
),
TPLinkSensorEntityDescription(
key=ATTR_TOTAL_ENERGY_KWH,
@@ -62,6 +64,7 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
state_class=STATE_CLASS_TOTAL_INCREASING,
name="Total Consumption",
emeter_attr="total",
precision=3,
),
TPLinkSensorEntityDescription(
key=ATTR_TODAY_ENERGY_KWH,
@@ -69,6 +72,7 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
device_class=DEVICE_CLASS_ENERGY,
state_class=STATE_CLASS_TOTAL_INCREASING,
name="Today's Consumption",
precision=3,
),
TPLinkSensorEntityDescription(
key=ATTR_VOLTAGE,
@@ -77,6 +81,7 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
state_class=STATE_CLASS_MEASUREMENT,
name="Voltage",
emeter_attr="voltage",
precision=1,
),
TPLinkSensorEntityDescription(
key=ATTR_CURRENT_A,
@@ -85,6 +90,7 @@ ENERGY_SENSORS: tuple[TPLinkSensorEntityDescription, ...] = (
state_class=STATE_CLASS_MEASUREMENT,
name="Current",
emeter_attr="current",
precision=2,
),
)
@@ -97,11 +103,11 @@ def async_emeter_from_device(
val = getattr(device.emeter_realtime, attr)
if val is None:
return None
return cast(float, val)
return round(cast(float, val), description.precision)
# ATTR_TODAY_ENERGY_KWH
if (emeter_today := device.emeter_today) is not None:
return cast(float, emeter_today)
return round(cast(float, emeter_today), description.precision)
# today's consumption not available, when device was off all the day
# bulb's do not report this information, so filter it out
return None if device.is_bulb else 0.0
@@ -1,12 +1,26 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4",
"no_devices_found": "\u05dc\u05d0 \u05e0\u05de\u05e6\u05d0\u05d5 \u05de\u05db\u05e9\u05d9\u05e8\u05d9\u05dd \u05d1\u05e8\u05e9\u05ea",
"single_instance_allowed": "\u05ea\u05e6\u05d5\u05e8\u05ea\u05d5 \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4. \u05e8\u05e7 \u05ea\u05e6\u05d5\u05e8\u05d4 \u05d0\u05d7\u05ea \u05d0\u05e4\u05e9\u05e8\u05d9\u05ea."
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"step": {
"confirm": {
"description": "\u05d4\u05d0\u05dd \u05d1\u05e8\u05e6\u05d5\u05e0\u05da \u05dc\u05d4\u05d2\u05d3\u05d9\u05e8 \u05d4\u05ea\u05e7\u05e0\u05d9\u05dd \u05d7\u05db\u05de\u05d9\u05dd \u05e9\u05dc TP-Link ?"
},
"pick_device": {
"data": {
"device": "\u05d4\u05ea\u05e7\u05df"
}
},
"user": {
"data": {
"host": "\u05de\u05d0\u05e8\u05d7"
}
}
}
}

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