mirror of
https://github.com/home-assistant/core.git
synced 2026-05-05 12:24:48 +02:00
Compare commits
90 Commits
2021.10.0b3
...
2021.10.2
| Author | SHA1 | Date | |
|---|---|---|---|
| b110a5bbfc | |||
| 3c2f3f2ade | |||
| 1ea0891602 | |||
| 54acc5bade | |||
| 0c886e2990 | |||
| f10a469d4f | |||
| 0358a13536 | |||
| 1a78c461a0 | |||
| c6d506cb4f | |||
| 5bb4bc3d13 | |||
| 387249b593 | |||
| 1a376b25af | |||
| ed4b44c126 | |||
| ef7f1ffddc | |||
| e3e64130f1 | |||
| 4e49e3a3fb | |||
| fd371f2887 | |||
| ec0256e27f | |||
| 425015eb8b | |||
| 0b26b15749 | |||
| 06befe906b | |||
| bf4a3d8d35 | |||
| 691d8d6b80 | |||
| 7f49e02a4d | |||
| 7544ec2399 | |||
| 32889dbfbe | |||
| 46394b50d8 | |||
| 25fc479cd4 | |||
| 9636799dfb | |||
| e01e575092 | |||
| 2fc9cdbe68 | |||
| 72b3bc13e4 | |||
| 9e755fcc49 | |||
| a692d4de64 | |||
| bb617d7b89 | |||
| 790a61cfae | |||
| 8e02ea1936 | |||
| 7502956d2b | |||
| 8394157c0a | |||
| 76a6edb4ed | |||
| 73bf538736 | |||
| dbde2f1b92 | |||
| 78bb2f5b73 | |||
| 657037553a | |||
| 0e00075628 | |||
| ecdbb5ff17 | |||
| a13a6bc9c9 | |||
| 5b550689e0 | |||
| c5992e2967 | |||
| b76e19c8c2 | |||
| d1f790fab0 | |||
| 4d44beb938 | |||
| 70a6930a8a | |||
| b289bb2f57 | |||
| a457bb7446 | |||
| f0b22e2f40 | |||
| d42350f986 | |||
| 74aa57e764 | |||
| fb9a119fc7 | |||
| f82fe9d8bb | |||
| b086266508 | |||
| 491abf0608 | |||
| da61696566 | |||
| 688884ccfe | |||
| 62390b9531 | |||
| 7ddb399178 | |||
| 1fe4e08003 | |||
| b106dab916 | |||
| 36bac936d1 | |||
| 4ca0c0d3a9 | |||
| cdaa7b7db7 | |||
| d1f3602732 | |||
| 8ee8aade86 | |||
| 6aad751056 | |||
| 2d8684283f | |||
| 779ae6c801 | |||
| 48fecc916a | |||
| 0084db3ad2 | |||
| ea2113d5d2 | |||
| c22ec32726 | |||
| 645cb53284 | |||
| 5237817109 | |||
| 45b7922a6a | |||
| 6f39632583 | |||
| 757c5b9201 | |||
| 7203f58b69 | |||
| a527c451c3 | |||
| 201be1a59d | |||
| e454c6628a | |||
| 2dac92df0c |
+1
-1
@@ -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.
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
"description": "Nyomja meg a gombot a bridge-en a Philips Hue Home Assistantban val\u00f3 regisztr\u00e1l\u00e1s\u00e1hoz.\n\n",
|
||||
"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,
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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(
|
||||
[
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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": [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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*"
|
||||
|
||||
@@ -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, []):
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user