homeassistant 2025.10.0b0__py3-none-any.whl → 2025.10.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of homeassistant might be problematic. Click here for more details.
- homeassistant/components/accuweather/manifest.json +1 -1
- homeassistant/components/accuweather/translations/nl.json +7 -1
- homeassistant/components/airzone/translations/nl.json +5 -0
- homeassistant/components/airzone/translations/zh-Hans.json +10 -0
- homeassistant/components/alexa_devices/binary_sensor.py +36 -38
- homeassistant/components/alexa_devices/config_flow.py +2 -2
- homeassistant/components/alexa_devices/coordinator.py +1 -1
- homeassistant/components/alexa_devices/diagnostics.py +1 -3
- homeassistant/components/alexa_devices/icons.json +0 -40
- homeassistant/components/alexa_devices/manifest.json +2 -2
- homeassistant/components/alexa_devices/sensor.py +13 -0
- homeassistant/components/alexa_devices/strings.json +0 -20
- homeassistant/components/alexa_devices/switch.py +27 -7
- homeassistant/components/alexa_devices/translations/bg.json +0 -13
- homeassistant/components/alexa_devices/translations/cs.json +0 -20
- homeassistant/components/alexa_devices/translations/de.json +0 -20
- homeassistant/components/alexa_devices/translations/el.json +0 -20
- homeassistant/components/alexa_devices/translations/en-GB.json +0 -20
- homeassistant/components/alexa_devices/translations/en.json +0 -20
- homeassistant/components/alexa_devices/translations/es.json +0 -20
- homeassistant/components/alexa_devices/translations/et.json +0 -20
- homeassistant/components/alexa_devices/translations/ga.json +0 -20
- homeassistant/components/alexa_devices/translations/he.json +0 -7
- homeassistant/components/alexa_devices/translations/hu.json +0 -20
- homeassistant/components/alexa_devices/translations/it.json +0 -20
- homeassistant/components/alexa_devices/translations/ja.json +0 -19
- homeassistant/components/alexa_devices/translations/lt.json +0 -20
- homeassistant/components/alexa_devices/translations/mk.json +0 -14
- homeassistant/components/alexa_devices/translations/nl.json +0 -20
- homeassistant/components/alexa_devices/translations/pl.json +0 -5
- homeassistant/components/alexa_devices/translations/pt.json +0 -20
- homeassistant/components/alexa_devices/translations/ru.json +0 -5
- homeassistant/components/alexa_devices/translations/sk.json +0 -20
- homeassistant/components/alexa_devices/translations/sv.json +0 -20
- homeassistant/components/alexa_devices/translations/tr.json +0 -5
- homeassistant/components/alexa_devices/translations/zh-Hans.json +0 -20
- homeassistant/components/alexa_devices/translations/zh-Hant.json +0 -20
- homeassistant/components/alexa_devices/utils.py +24 -1
- homeassistant/components/assist_pipeline/translations/nl.json +1 -0
- homeassistant/components/blue_current/translations/zh-Hans.json +44 -0
- homeassistant/components/comelit/manifest.json +1 -1
- homeassistant/components/cync/translations/cs.json +32 -0
- homeassistant/components/cync/translations/nl.json +20 -0
- homeassistant/components/cync/translations/sk.json +32 -0
- homeassistant/components/cync/translations/zh-Hans.json +32 -0
- homeassistant/components/cync/translations/zh-Hant.json +32 -0
- homeassistant/components/ekeybionyx/translations/nl.json +24 -0
- homeassistant/components/ekeybionyx/translations/zh-Hans.json +66 -0
- homeassistant/components/esphome/config_flow.py +9 -1
- homeassistant/components/esphome/manager.py +10 -0
- homeassistant/components/esphome/manifest.json +1 -1
- homeassistant/components/esphome/translations/nl.json +1 -1
- homeassistant/components/frontend/manifest.json +1 -1
- homeassistant/components/letpot/translations/nl.json +5 -0
- homeassistant/components/letpot/translations/zh-Hans.json +10 -0
- homeassistant/components/libre_hardware_monitor/manifest.json +1 -1
- homeassistant/components/mvglive/manifest.json +2 -4
- homeassistant/components/mvglive/sensor.py +116 -86
- homeassistant/components/portainer/binary_sensor.py +9 -1
- homeassistant/components/portainer/entity.py +1 -1
- homeassistant/components/roborock/coordinator.py +3 -7
- homeassistant/components/route_b_smart_meter/translations/nl.json +24 -0
- homeassistant/components/route_b_smart_meter/translations/zh-Hans.json +42 -0
- homeassistant/components/smartthings/manifest.json +1 -1
- homeassistant/components/smartthings/translations/nl.json +7 -0
- homeassistant/components/smartthings/translations/zh-Hans.json +21 -0
- homeassistant/components/switchbot/translations/nl.json +5 -0
- homeassistant/components/switchbot_cloud/translations/nl.json +2 -1
- homeassistant/components/tolo/translations/nl.json +2 -1
- homeassistant/components/usage_prediction/common_control.py +56 -59
- homeassistant/components/voip/translations/nl.json +1 -1
- homeassistant/components/wyoming/translations/nl.json +1 -1
- homeassistant/const.py +1 -1
- homeassistant/loader.py +3 -0
- homeassistant/package_constraints.txt +1 -1
- {homeassistant-2025.10.0b0.dist-info → homeassistant-2025.10.0b2.dist-info}/METADATA +1 -1
- {homeassistant-2025.10.0b0.dist-info → homeassistant-2025.10.0b2.dist-info}/RECORD +82 -73
- {homeassistant-2025.10.0b0.dist-info → homeassistant-2025.10.0b2.dist-info}/WHEEL +0 -0
- {homeassistant-2025.10.0b0.dist-info → homeassistant-2025.10.0b2.dist-info}/entry_points.txt +0 -0
- {homeassistant-2025.10.0b0.dist-info → homeassistant-2025.10.0b2.dist-info}/licenses/LICENSE.md +0 -0
- {homeassistant-2025.10.0b0.dist-info → homeassistant-2025.10.0b2.dist-info}/licenses/homeassistant/backports/LICENSE.Python +0 -0
- {homeassistant-2025.10.0b0.dist-info → homeassistant-2025.10.0b2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": {
|
|
3
|
+
"abort": {
|
|
4
|
+
"already_configured": "\u8d26\u6237\u5df2\u914d\u7f6e",
|
|
5
|
+
"already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d",
|
|
6
|
+
"authorize_url_timeout": "\u751f\u6210\u6388\u6743\u7f51\u5740\u8d85\u65f6\u3002",
|
|
7
|
+
"cannot_connect": "\u8fde\u63a5\u5230 {ekeybionyx} \u5931\u8d25\u3002\u8bf7\u68c0\u67e5\u60a8\u7684\u4e92\u8054\u7f51\u8fde\u63a5\uff0c\u7136\u540e\u91cd\u8bd5\u3002",
|
|
8
|
+
"missing_configuration": "\u6b64\u7ec4\u4ef6\u5c1a\u672a\u914d\u7f6e\u3002\u8bf7\u53c2\u9605\u6587\u6863\u3002",
|
|
9
|
+
"no_available_webhooks": "{ekeybionyx} \u7cfb\u7edf\u4e2d\u6ca1\u6709\u53ef\u7528\u7684 webhook \u3002\u8bf7\u5220\u9664\u4e00\u4e9b webhook \u540e\u91cd\u8bd5\u3002",
|
|
10
|
+
"no_own_systems": "\u60a8\u7684\u8d26\u6237\u4e0d\u5177\u5907\u4efb\u4f55\u7cfb\u7edf\u7684\u7ba1\u7406\u5458\u8bbf\u95ee\u6743\u9650\u3002",
|
|
11
|
+
"no_url_available": "\u6ca1\u6709\u53ef\u7528\u7684\u7f51\u5740\u3002\u6709\u5173\u6b64\u9519\u8bef\u7684\u4fe1\u606f\uff0c\u8bf7[\u68c0\u67e5\u5e2e\u52a9\u7ae0\u8282]({docs_url})",
|
|
12
|
+
"oauth_error": "\u6536\u5230\u65e0\u6548\u7684\u4ee4\u724c\u6570\u636e\u3002",
|
|
13
|
+
"oauth_failed": "\u83b7\u53d6\u8bbf\u95ee\u4ee4\u724c\u65f6\u51fa\u9519\u3002",
|
|
14
|
+
"oauth_timeout": "\u89e3\u6790 OAuth \u4ee4\u724c\u8d85\u65f6\u3002",
|
|
15
|
+
"oauth_unauthorized": "\u83b7\u53d6\u8bbf\u95ee\u4ee4\u724c\u65f6\u51fa\u73b0 OAuth \u6388\u6743\u9519\u8bef\u3002",
|
|
16
|
+
"user_rejected_authorize": "\u8d26\u6237\u94fe\u63a5\u88ab\u62d2\u7edd\uff1a{error}"
|
|
17
|
+
},
|
|
18
|
+
"create_entry": {
|
|
19
|
+
"default": "\u8ba4\u8bc1\u6210\u529f"
|
|
20
|
+
},
|
|
21
|
+
"error": {
|
|
22
|
+
"invalid_name": "\u540d\u79f0\u65e0\u6548",
|
|
23
|
+
"invalid_url": "\u7f51\u5740\u65e0\u6548",
|
|
24
|
+
"no_webhooks_provided": "\u672a\u63d0\u4f9b\u4e8b\u4ef6\u540d\u79f0"
|
|
25
|
+
},
|
|
26
|
+
"progress": {
|
|
27
|
+
"check_deletion_status": "\u8bf7\u6253\u5f00 {ekeybionyx} \u5e94\u7528\u5e76\u786e\u8ba4\u5220\u9664\u8be5\u529f\u80fd\u3002"
|
|
28
|
+
},
|
|
29
|
+
"step": {
|
|
30
|
+
"choose_system": {
|
|
31
|
+
"data": {
|
|
32
|
+
"system": "\u7cfb\u7edf"
|
|
33
|
+
},
|
|
34
|
+
"data_description": {
|
|
35
|
+
"system": "\u5e94\u8bbe\u7f6e\u4e8b\u4ef6\u5b9e\u4f53\u7684\u7cfb\u7edf\u3002"
|
|
36
|
+
},
|
|
37
|
+
"description": "\u8bf7\u9009\u62e9\u60a8\u60f3\u8981\u8fde\u63a5\u5230 Home Assistant \u7684 {ekeybionyx} \u7cfb\u7edf\u3002"
|
|
38
|
+
},
|
|
39
|
+
"delete_webhooks": {
|
|
40
|
+
"description": "\u6b64\u7cfb\u7edf\u5df2\u8fde\u63a5\u81f3 Home Assistant \u3002\u5982\u679c\u7ee7\u7eed\u64cd\u4f5c\uff0c\u4e4b\u524d\u914d\u7f6e\u7684\u529f\u80fd\u5c06\u88ab\u5220\u9664\u3002"
|
|
41
|
+
},
|
|
42
|
+
"pick_implementation": {
|
|
43
|
+
"title": "\u9009\u62e9\u8eab\u4efd\u9a8c\u8bc1\u65b9\u6cd5"
|
|
44
|
+
},
|
|
45
|
+
"webhooks": {
|
|
46
|
+
"data": {
|
|
47
|
+
"url": "Home Assistant \u7f51\u5740",
|
|
48
|
+
"webhook1": "\u4e8b\u4ef6\u5b9e\u4f53 1",
|
|
49
|
+
"webhook2": "\u4e8b\u4ef6\u5b9e\u4f53 2",
|
|
50
|
+
"webhook3": "\u4e8b\u4ef6\u5b9e\u4f53 3",
|
|
51
|
+
"webhook4": "\u4e8b\u4ef6\u5b9e\u4f53 4",
|
|
52
|
+
"webhook5": "\u4e8b\u4ef6\u5b9e\u4f53 5"
|
|
53
|
+
},
|
|
54
|
+
"data_description": {
|
|
55
|
+
"url": "\u53ef\u4ee5\u4ece\u6307\u7eb9\u63a7\u5236\u5668\u8bbf\u95ee\u7684 Home Assistant \u5b9e\u4f8b\u7f51\u5740",
|
|
56
|
+
"webhook1": "\u88ab\u6620\u5c04\u5230\u51fd\u6570\u7684\u4e8b\u4ef6\u5b9e\u4f53 1 \u7684\u540d\u79f0",
|
|
57
|
+
"webhook2": "\u88ab\u6620\u5c04\u5230\u51fd\u6570\u7684\u4e8b\u4ef6\u5b9e\u4f53 2 \u7684\u540d\u79f0",
|
|
58
|
+
"webhook3": "\u88ab\u6620\u5c04\u5230\u51fd\u6570\u7684\u4e8b\u4ef6\u5b9e\u4f53 3 \u7684\u540d\u79f0",
|
|
59
|
+
"webhook4": "\u88ab\u6620\u5c04\u5230\u51fd\u6570\u7684\u4e8b\u4ef6\u5b9e\u4f53 4 \u7684\u540d\u79f0",
|
|
60
|
+
"webhook5": "\u88ab\u6620\u5c04\u5230\u51fd\u6570\u7684\u4e8b\u4ef6\u5b9e\u4f53 5 \u7684\u540d\u79f0"
|
|
61
|
+
},
|
|
62
|
+
"description": "\u8bf7\u4e3a\u60a8\u7684\u4e8b\u4ef6\u5b9e\u4f53\u547d\u540d\u3002\u8fd9\u4e9b\u4e8b\u4ef6\u5b9e\u4f53\u5c06\u5728 {ekeybionyx} \u5e94\u7528\u4e2d\u6620\u5c04\u4e3a\u51fd\u6570\u3002\u60a8\u6700\u591a\u53ef\u4ee5\u914d\u7f6e {webhooks_available} \u4e2a\u4e8b\u4ef6\u5b9e\u4f53\u3002\u5982\u679c\u540d\u79f0\u7559\u7a7a\uff0c\u5219\u8df3\u8fc7\u8be5\u4e8b\u4ef6\u5b9e\u4f53\u7684\u8bbe\u7f6e\u3002"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -57,6 +57,7 @@ from .manager import async_replace_device
|
|
|
57
57
|
|
|
58
58
|
ERROR_REQUIRES_ENCRYPTION_KEY = "requires_encryption_key"
|
|
59
59
|
ERROR_INVALID_ENCRYPTION_KEY = "invalid_psk"
|
|
60
|
+
ERROR_INVALID_PASSWORD_AUTH = "invalid_auth"
|
|
60
61
|
_LOGGER = logging.getLogger(__name__)
|
|
61
62
|
|
|
62
63
|
ZERO_NOISE_PSK = "MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA="
|
|
@@ -137,6 +138,11 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|
|
137
138
|
self._password = ""
|
|
138
139
|
return await self._async_authenticate_or_add()
|
|
139
140
|
|
|
141
|
+
if error == ERROR_INVALID_PASSWORD_AUTH or (
|
|
142
|
+
error is None and self._device_info and self._device_info.uses_password
|
|
143
|
+
):
|
|
144
|
+
return await self.async_step_authenticate()
|
|
145
|
+
|
|
140
146
|
if error is None and entry_data.get(CONF_NOISE_PSK):
|
|
141
147
|
# Device was configured with encryption but now connects without it.
|
|
142
148
|
# Check if it's the same device before offering to remove encryption.
|
|
@@ -690,13 +696,15 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
|
|
690
696
|
cli = APIClient(
|
|
691
697
|
host,
|
|
692
698
|
port or DEFAULT_PORT,
|
|
693
|
-
"",
|
|
699
|
+
self._password or "",
|
|
694
700
|
zeroconf_instance=zeroconf_instance,
|
|
695
701
|
noise_psk=noise_psk,
|
|
696
702
|
)
|
|
697
703
|
try:
|
|
698
704
|
await cli.connect()
|
|
699
705
|
self._device_info = await cli.device_info()
|
|
706
|
+
except InvalidAuthAPIError:
|
|
707
|
+
return ERROR_INVALID_PASSWORD_AUTH
|
|
700
708
|
except RequiresEncryptionAPIError:
|
|
701
709
|
return ERROR_REQUIRES_ENCRYPTION_KEY
|
|
702
710
|
except InvalidEncryptionKeyAPIError as ex:
|
|
@@ -372,6 +372,9 @@ class ESPHomeManager:
|
|
|
372
372
|
"""Subscribe to states and list entities on successful API login."""
|
|
373
373
|
try:
|
|
374
374
|
await self._on_connect()
|
|
375
|
+
except InvalidAuthAPIError as err:
|
|
376
|
+
_LOGGER.warning("Authentication failed for %s: %s", self.host, err)
|
|
377
|
+
await self._start_reauth_and_disconnect()
|
|
375
378
|
except APIConnectionError as err:
|
|
376
379
|
_LOGGER.warning(
|
|
377
380
|
"Error getting setting up connection for %s: %s", self.host, err
|
|
@@ -641,7 +644,14 @@ class ESPHomeManager:
|
|
|
641
644
|
if self.reconnect_logic:
|
|
642
645
|
await self.reconnect_logic.stop()
|
|
643
646
|
return
|
|
647
|
+
await self._start_reauth_and_disconnect()
|
|
648
|
+
|
|
649
|
+
async def _start_reauth_and_disconnect(self) -> None:
|
|
650
|
+
"""Start reauth flow and stop reconnection attempts."""
|
|
644
651
|
self.entry.async_start_reauth(self.hass)
|
|
652
|
+
await self.cli.disconnect()
|
|
653
|
+
if self.reconnect_logic:
|
|
654
|
+
await self.reconnect_logic.stop()
|
|
645
655
|
|
|
646
656
|
async def _handle_dynamic_encryption_key(
|
|
647
657
|
self, device_info: EsphomeDeviceInfo
|
|
@@ -49,6 +49,15 @@
|
|
|
49
49
|
"name": "\u91cd\u65b0\u586b\u5145\u51fa\u9519"
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
|
+
"number": {
|
|
53
|
+
"light_brightness": {
|
|
54
|
+
"name": "\u706f\u5149\u4eae\u5ea6"
|
|
55
|
+
},
|
|
56
|
+
"plant_days": {
|
|
57
|
+
"name": "\u690d\u7269\u5e74\u9f84",
|
|
58
|
+
"unit_of_measurement": "\u5929"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
52
61
|
"select": {
|
|
53
62
|
"display_temperature_unit": {
|
|
54
63
|
"name": "\u6e29\u5ea6\u663e\u793a\u5355\u4f4d",
|
|
@@ -58,6 +67,7 @@
|
|
|
58
67
|
}
|
|
59
68
|
},
|
|
60
69
|
"light_brightness": {
|
|
70
|
+
"name": "\u706f\u5149\u4eae\u5ea6",
|
|
61
71
|
"state": {
|
|
62
72
|
"high": "\u9ad8",
|
|
63
73
|
"low": "\u4f4e"
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
"domain": "mvglive",
|
|
3
3
|
"name": "MVG",
|
|
4
4
|
"codeowners": [],
|
|
5
|
-
"disabled": "This integration is disabled because it uses non-open source code to operate.",
|
|
6
5
|
"documentation": "https://www.home-assistant.io/integrations/mvglive",
|
|
7
6
|
"iot_class": "cloud_polling",
|
|
8
|
-
"loggers": ["
|
|
9
|
-
"
|
|
10
|
-
"requirements": ["PyMVGLive==1.1.4"]
|
|
7
|
+
"loggers": ["MVG"],
|
|
8
|
+
"requirements": ["mvg==1.4.0"]
|
|
11
9
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"""Support for departure information for public transport in Munich."""
|
|
2
2
|
|
|
3
|
-
# mypy: ignore-errors
|
|
4
3
|
from __future__ import annotations
|
|
5
4
|
|
|
5
|
+
from collections.abc import Mapping
|
|
6
6
|
from copy import deepcopy
|
|
7
7
|
from datetime import timedelta
|
|
8
8
|
import logging
|
|
9
|
+
from typing import Any
|
|
9
10
|
|
|
10
|
-
import
|
|
11
|
+
from mvg import MvgApi, MvgApiError, TransportType
|
|
11
12
|
import voluptuous as vol
|
|
12
13
|
|
|
13
14
|
from homeassistant.components.sensor import (
|
|
@@ -19,6 +20,7 @@ from homeassistant.core import HomeAssistant
|
|
|
19
20
|
from homeassistant.helpers import config_validation as cv
|
|
20
21
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
21
22
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
23
|
+
import homeassistant.util.dt as dt_util
|
|
22
24
|
|
|
23
25
|
_LOGGER = logging.getLogger(__name__)
|
|
24
26
|
|
|
@@ -44,53 +46,55 @@ ICONS = {
|
|
|
44
46
|
"SEV": "mdi:checkbox-blank-circle-outline",
|
|
45
47
|
"-": "mdi:clock",
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
|
|
50
|
+
ATTRIBUTION = "Data provided by mvg.de"
|
|
48
51
|
|
|
49
52
|
SCAN_INTERVAL = timedelta(seconds=30)
|
|
50
53
|
|
|
51
|
-
PLATFORM_SCHEMA =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
PLATFORM_SCHEMA = vol.All(
|
|
55
|
+
cv.deprecated(CONF_DIRECTIONS),
|
|
56
|
+
SENSOR_PLATFORM_SCHEMA.extend(
|
|
57
|
+
{
|
|
58
|
+
vol.Required(CONF_NEXT_DEPARTURE): [
|
|
59
|
+
{
|
|
60
|
+
vol.Required(CONF_STATION): cv.string,
|
|
61
|
+
vol.Optional(CONF_DESTINATIONS, default=[""]): cv.ensure_list_csv,
|
|
62
|
+
vol.Optional(CONF_DIRECTIONS, default=[""]): cv.ensure_list_csv,
|
|
63
|
+
vol.Optional(CONF_LINES, default=[""]): cv.ensure_list_csv,
|
|
64
|
+
vol.Optional(
|
|
65
|
+
CONF_PRODUCTS, default=DEFAULT_PRODUCT
|
|
66
|
+
): cv.ensure_list_csv,
|
|
67
|
+
vol.Optional(CONF_TIMEOFFSET, default=0): cv.positive_int,
|
|
68
|
+
vol.Optional(CONF_NUMBER, default=1): cv.positive_int,
|
|
69
|
+
vol.Optional(CONF_NAME): cv.string,
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
),
|
|
68
74
|
)
|
|
69
75
|
|
|
70
76
|
|
|
71
|
-
def
|
|
77
|
+
async def async_setup_platform(
|
|
72
78
|
hass: HomeAssistant,
|
|
73
79
|
config: ConfigType,
|
|
74
80
|
add_entities: AddEntitiesCallback,
|
|
75
81
|
discovery_info: DiscoveryInfoType | None = None,
|
|
76
82
|
) -> None:
|
|
77
83
|
"""Set up the MVGLive sensor."""
|
|
78
|
-
|
|
79
|
-
(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
True,
|
|
93
|
-
)
|
|
84
|
+
sensors = [
|
|
85
|
+
MVGLiveSensor(
|
|
86
|
+
hass,
|
|
87
|
+
nextdeparture.get(CONF_STATION),
|
|
88
|
+
nextdeparture.get(CONF_DESTINATIONS),
|
|
89
|
+
nextdeparture.get(CONF_LINES),
|
|
90
|
+
nextdeparture.get(CONF_PRODUCTS),
|
|
91
|
+
nextdeparture.get(CONF_TIMEOFFSET),
|
|
92
|
+
nextdeparture.get(CONF_NUMBER),
|
|
93
|
+
nextdeparture.get(CONF_NAME),
|
|
94
|
+
)
|
|
95
|
+
for nextdeparture in config[CONF_NEXT_DEPARTURE]
|
|
96
|
+
]
|
|
97
|
+
add_entities(sensors, True)
|
|
94
98
|
|
|
95
99
|
|
|
96
100
|
class MVGLiveSensor(SensorEntity):
|
|
@@ -100,38 +104,38 @@ class MVGLiveSensor(SensorEntity):
|
|
|
100
104
|
|
|
101
105
|
def __init__(
|
|
102
106
|
self,
|
|
103
|
-
|
|
107
|
+
hass: HomeAssistant,
|
|
108
|
+
station_name,
|
|
104
109
|
destinations,
|
|
105
|
-
directions,
|
|
106
110
|
lines,
|
|
107
111
|
products,
|
|
108
112
|
timeoffset,
|
|
109
113
|
number,
|
|
110
114
|
name,
|
|
111
|
-
):
|
|
115
|
+
) -> None:
|
|
112
116
|
"""Initialize the sensor."""
|
|
113
|
-
self._station = station
|
|
114
117
|
self._name = name
|
|
118
|
+
self._station_name = station_name
|
|
115
119
|
self.data = MVGLiveData(
|
|
116
|
-
|
|
120
|
+
hass, station_name, destinations, lines, products, timeoffset, number
|
|
117
121
|
)
|
|
118
122
|
self._state = None
|
|
119
123
|
self._icon = ICONS["-"]
|
|
120
124
|
|
|
121
125
|
@property
|
|
122
|
-
def name(self):
|
|
126
|
+
def name(self) -> str | None:
|
|
123
127
|
"""Return the name of the sensor."""
|
|
124
128
|
if self._name:
|
|
125
129
|
return self._name
|
|
126
|
-
return self.
|
|
130
|
+
return self._station_name
|
|
127
131
|
|
|
128
132
|
@property
|
|
129
|
-
def native_value(self):
|
|
133
|
+
def native_value(self) -> str | None:
|
|
130
134
|
"""Return the next departure time."""
|
|
131
135
|
return self._state
|
|
132
136
|
|
|
133
137
|
@property
|
|
134
|
-
def extra_state_attributes(self):
|
|
138
|
+
def extra_state_attributes(self) -> Mapping[str, Any] | None:
|
|
135
139
|
"""Return the state attributes."""
|
|
136
140
|
if not (dep := self.data.departures):
|
|
137
141
|
return None
|
|
@@ -140,88 +144,114 @@ class MVGLiveSensor(SensorEntity):
|
|
|
140
144
|
return attr
|
|
141
145
|
|
|
142
146
|
@property
|
|
143
|
-
def icon(self):
|
|
147
|
+
def icon(self) -> str | None:
|
|
144
148
|
"""Icon to use in the frontend, if any."""
|
|
145
149
|
return self._icon
|
|
146
150
|
|
|
147
151
|
@property
|
|
148
|
-
def native_unit_of_measurement(self):
|
|
152
|
+
def native_unit_of_measurement(self) -> str | None:
|
|
149
153
|
"""Return the unit this state is expressed in."""
|
|
150
154
|
return UnitOfTime.MINUTES
|
|
151
155
|
|
|
152
|
-
def
|
|
156
|
+
async def async_update(self) -> None:
|
|
153
157
|
"""Get the latest data and update the state."""
|
|
154
|
-
self.data.update()
|
|
158
|
+
await self.data.update()
|
|
155
159
|
if not self.data.departures:
|
|
156
|
-
self._state =
|
|
160
|
+
self._state = None
|
|
157
161
|
self._icon = ICONS["-"]
|
|
158
162
|
else:
|
|
159
|
-
self._state = self.data.departures[0].get("
|
|
160
|
-
self._icon =
|
|
163
|
+
self._state = self.data.departures[0].get("time_in_mins", "-")
|
|
164
|
+
self._icon = self.data.departures[0].get("icon", ICONS["-"])
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _get_minutes_until_departure(departure_time: int) -> int:
|
|
168
|
+
"""Calculate the time difference in minutes between the current time and a given departure time.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
departure_time: Unix timestamp of the departure time, in seconds.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
The time difference in minutes, as an integer.
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
current_time = dt_util.utcnow()
|
|
178
|
+
departure_datetime = dt_util.utc_from_timestamp(departure_time)
|
|
179
|
+
time_difference = (departure_datetime - current_time).total_seconds()
|
|
180
|
+
return int(time_difference / 60.0)
|
|
161
181
|
|
|
162
182
|
|
|
163
183
|
class MVGLiveData:
|
|
164
|
-
"""Pull data from the mvg
|
|
184
|
+
"""Pull data from the mvg.de web page."""
|
|
165
185
|
|
|
166
186
|
def __init__(
|
|
167
|
-
self,
|
|
168
|
-
|
|
187
|
+
self,
|
|
188
|
+
hass: HomeAssistant,
|
|
189
|
+
station_name,
|
|
190
|
+
destinations,
|
|
191
|
+
lines,
|
|
192
|
+
products,
|
|
193
|
+
timeoffset,
|
|
194
|
+
number,
|
|
195
|
+
) -> None:
|
|
169
196
|
"""Initialize the sensor."""
|
|
170
|
-
self.
|
|
197
|
+
self._hass = hass
|
|
198
|
+
self._station_name = station_name
|
|
199
|
+
self._station_id = None
|
|
171
200
|
self._destinations = destinations
|
|
172
|
-
self._directions = directions
|
|
173
201
|
self._lines = lines
|
|
174
202
|
self._products = products
|
|
175
203
|
self._timeoffset = timeoffset
|
|
176
204
|
self._number = number
|
|
177
|
-
self.
|
|
178
|
-
self._include_tram = "Tram" in self._products
|
|
179
|
-
self._include_bus = "Bus" in self._products
|
|
180
|
-
self._include_sbahn = "S-Bahn" in self._products
|
|
181
|
-
self.mvg = MVGLive.MVGLive()
|
|
182
|
-
self.departures = []
|
|
205
|
+
self.departures: list[dict[str, Any]] = []
|
|
183
206
|
|
|
184
|
-
def update(self):
|
|
207
|
+
async def update(self):
|
|
185
208
|
"""Update the connection data."""
|
|
209
|
+
if self._station_id is None:
|
|
210
|
+
try:
|
|
211
|
+
station = await MvgApi.station_async(self._station_name)
|
|
212
|
+
self._station_id = station["id"]
|
|
213
|
+
except MvgApiError as err:
|
|
214
|
+
_LOGGER.error(
|
|
215
|
+
"Failed to resolve station %s: %s", self._station_name, err
|
|
216
|
+
)
|
|
217
|
+
self.departures = []
|
|
218
|
+
return
|
|
219
|
+
|
|
186
220
|
try:
|
|
187
|
-
_departures =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
221
|
+
_departures = await MvgApi.departures_async(
|
|
222
|
+
station_id=self._station_id,
|
|
223
|
+
offset=self._timeoffset,
|
|
224
|
+
limit=self._number,
|
|
225
|
+
transport_types=[
|
|
226
|
+
transport_type
|
|
227
|
+
for transport_type in TransportType
|
|
228
|
+
if transport_type.value[0] in self._products
|
|
229
|
+
]
|
|
230
|
+
if self._products
|
|
231
|
+
else None,
|
|
194
232
|
)
|
|
195
233
|
except ValueError:
|
|
196
234
|
self.departures = []
|
|
197
235
|
_LOGGER.warning("Returned data not understood")
|
|
198
236
|
return
|
|
199
237
|
self.departures = []
|
|
200
|
-
for
|
|
201
|
-
# find the first departure meeting the criteria
|
|
238
|
+
for _departure in _departures:
|
|
202
239
|
if (
|
|
203
240
|
"" not in self._destinations[:1]
|
|
204
241
|
and _departure["destination"] not in self._destinations
|
|
205
242
|
):
|
|
206
243
|
continue
|
|
207
244
|
|
|
208
|
-
if
|
|
209
|
-
"" not in self._directions[:1]
|
|
210
|
-
and _departure["direction"] not in self._directions
|
|
211
|
-
):
|
|
245
|
+
if "" not in self._lines[:1] and _departure["line"] not in self._lines:
|
|
212
246
|
continue
|
|
213
247
|
|
|
214
|
-
|
|
215
|
-
continue
|
|
248
|
+
time_to_departure = _get_minutes_until_departure(_departure["time"])
|
|
216
249
|
|
|
217
|
-
if
|
|
250
|
+
if time_to_departure < self._timeoffset:
|
|
218
251
|
continue
|
|
219
252
|
|
|
220
|
-
# now select the relevant data
|
|
221
253
|
_nextdep = {}
|
|
222
|
-
for k in ("destination", "
|
|
254
|
+
for k in ("destination", "line", "type", "cancelled", "icon"):
|
|
223
255
|
_nextdep[k] = _departure.get(k, "")
|
|
224
|
-
_nextdep["
|
|
256
|
+
_nextdep["time_in_mins"] = time_to_departure
|
|
225
257
|
self.departures.append(_nextdep)
|
|
226
|
-
if i == self._number - 1:
|
|
227
|
-
break
|
|
@@ -131,7 +131,15 @@ class PortainerContainerSensor(PortainerContainerEntity, BinarySensorEntity):
|
|
|
131
131
|
self.entity_description = entity_description
|
|
132
132
|
super().__init__(device_info, coordinator, via_device)
|
|
133
133
|
|
|
134
|
-
|
|
134
|
+
# Container ID's are ephemeral, so use the container name for the unique ID
|
|
135
|
+
# The first one, should always be unique, it's fine if users have aliases
|
|
136
|
+
# According to Docker's API docs, the first name is unique
|
|
137
|
+
device_identifier = (
|
|
138
|
+
self._device_info.names[0].replace("/", " ").strip()
|
|
139
|
+
if self._device_info.names
|
|
140
|
+
else None
|
|
141
|
+
)
|
|
142
|
+
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_{device_identifier}_{entity_description.key}"
|
|
135
143
|
|
|
136
144
|
@property
|
|
137
145
|
def available(self) -> bool:
|
|
@@ -60,7 +60,7 @@ class PortainerContainerEntity(PortainerCoordinatorEntity):
|
|
|
60
60
|
|
|
61
61
|
self._attr_device_info = DeviceInfo(
|
|
62
62
|
identifiers={
|
|
63
|
-
(DOMAIN, f"{self.coordinator.config_entry.entry_id}_{
|
|
63
|
+
(DOMAIN, f"{self.coordinator.config_entry.entry_id}_{device_name}")
|
|
64
64
|
},
|
|
65
65
|
manufacturer=DEFAULT_NAME,
|
|
66
66
|
model="Container",
|
|
@@ -351,13 +351,9 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|
|
351
351
|
def _set_current_map(self) -> None:
|
|
352
352
|
if (
|
|
353
353
|
self.roborock_device_info.props.status is not None
|
|
354
|
-
and self.roborock_device_info.props.status.
|
|
354
|
+
and self.roborock_device_info.props.status.current_map is not None
|
|
355
355
|
):
|
|
356
|
-
|
|
357
|
-
# so we have to invert that in order to get the map flag that we can use to set the current map.
|
|
358
|
-
self.current_map = (
|
|
359
|
-
self.roborock_device_info.props.status.map_status - 3
|
|
360
|
-
) // 4
|
|
356
|
+
self.current_map = self.roborock_device_info.props.status.current_map
|
|
361
357
|
|
|
362
358
|
async def set_current_map_rooms(self) -> None:
|
|
363
359
|
"""Fetch all of the rooms for the current map and set on RoborockMapInfo."""
|
|
@@ -440,7 +436,7 @@ class RoborockDataUpdateCoordinator(DataUpdateCoordinator[DeviceProp]):
|
|
|
440
436
|
# If either of these fail, we don't care, and we want to continue.
|
|
441
437
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
442
438
|
|
|
443
|
-
if len(self.maps)
|
|
439
|
+
if len(self.maps) > 1:
|
|
444
440
|
# Set the map back to the map the user previously had selected so that it
|
|
445
441
|
# does not change the end user's app.
|
|
446
442
|
# Only needs to happen when we changed maps above.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": {
|
|
3
|
+
"abort": {
|
|
4
|
+
"already_configured": "Apparaat is al geconfigureerd"
|
|
5
|
+
},
|
|
6
|
+
"error": {
|
|
7
|
+
"cannot_connect": "Kan geen verbinding maken",
|
|
8
|
+
"invalid_auth": "Ongeldige authenticatie",
|
|
9
|
+
"unknown": "Onverwachte fout"
|
|
10
|
+
},
|
|
11
|
+
"step": {
|
|
12
|
+
"user": {
|
|
13
|
+
"data": {
|
|
14
|
+
"device": "Apparaat",
|
|
15
|
+
"password": "Wachtwoord"
|
|
16
|
+
},
|
|
17
|
+
"data_description": {
|
|
18
|
+
"device": "Apparaat",
|
|
19
|
+
"password": "Wachtwoord"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": {
|
|
3
|
+
"abort": {
|
|
4
|
+
"already_configured": "\u8bbe\u5907\u5df2\u914d\u7f6e"
|
|
5
|
+
},
|
|
6
|
+
"error": {
|
|
7
|
+
"cannot_connect": "\u8fde\u63a5\u5931\u8d25",
|
|
8
|
+
"invalid_auth": "\u8eab\u4efd\u9a8c\u8bc1\u65e0\u6548",
|
|
9
|
+
"unknown": "\u610f\u5916\u9519\u8bef"
|
|
10
|
+
},
|
|
11
|
+
"step": {
|
|
12
|
+
"user": {
|
|
13
|
+
"data": {
|
|
14
|
+
"device": "\u8bbe\u5907",
|
|
15
|
+
"id": "B \u8def\u7ebf ID",
|
|
16
|
+
"password": "\u5bc6\u7801"
|
|
17
|
+
},
|
|
18
|
+
"data_description": {
|
|
19
|
+
"device": "\u8bbe\u5907",
|
|
20
|
+
"id": "B \u8def\u7ebf ID",
|
|
21
|
+
"password": "\u5bc6\u7801"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"entity": {
|
|
27
|
+
"sensor": {
|
|
28
|
+
"instantaneous_current_r_phase": {
|
|
29
|
+
"name": "\u77ac\u65f6\u7535\u6d41 R \u76f8"
|
|
30
|
+
},
|
|
31
|
+
"instantaneous_current_t_phase": {
|
|
32
|
+
"name": "\u77ac\u65f6\u7535\u6d41 T \u76f8"
|
|
33
|
+
},
|
|
34
|
+
"instantaneous_power": {
|
|
35
|
+
"name": "\u77ac\u65f6\u529f\u7387"
|
|
36
|
+
},
|
|
37
|
+
"total_consumption": {
|
|
38
|
+
"name": "\u603b\u6d88\u8017"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|