homeassistant 2025.9.0b2__py3-none-any.whl → 2025.9.0b3__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.
- homeassistant/components/airgradient/translations/lt.json +2 -1
- homeassistant/components/alexa_devices/translations/cs.json +50 -0
- homeassistant/components/alexa_devices/translations/lt.json +47 -0
- homeassistant/components/alexa_devices/translations/nl.json +17 -0
- homeassistant/components/alexa_devices/translations/pt.json +50 -0
- homeassistant/components/alexa_devices/translations/sk.json +50 -0
- homeassistant/components/bang_olufsen/translations/lt.json +4 -0
- homeassistant/components/bayesian/strings.json +1 -1
- homeassistant/components/bayesian/translations/lt.json +23 -4
- homeassistant/components/bosch_alarm/translations/lt.json +3 -0
- homeassistant/components/bsblan/climate.py +4 -0
- homeassistant/components/bsblan/config_flow.py +1 -1
- homeassistant/components/bsblan/sensor.py +23 -3
- homeassistant/components/bsblan/water_heater.py +22 -2
- homeassistant/components/button/translations/lt.json +1 -0
- homeassistant/components/devolo_home_network/translations/lt.json +4 -0
- homeassistant/components/dominos/translations/lt.json +2 -0
- homeassistant/components/dsmr/translations/lt.json +2 -1
- homeassistant/components/dsmr_reader/translations/lt.json +1 -0
- homeassistant/components/fritz/translations/lt.json +3 -0
- homeassistant/components/fritzbox/translations/lt.json +3 -1
- homeassistant/components/fritzbox_callmonitor/translations/lt.json +2 -1
- homeassistant/components/frontend/manifest.json +1 -1
- homeassistant/components/google_travel_time/translations/lt.json +3 -2
- homeassistant/components/govee_light_local/__init__.py +6 -1
- homeassistant/components/govee_light_local/config_flow.py +7 -12
- homeassistant/components/govee_light_local/coordinator.py +2 -3
- homeassistant/components/habitica/translations/lt.json +2 -1
- homeassistant/components/hassio/translations/lt.json +25 -0
- homeassistant/components/homekit/__init__.py +2 -3
- homeassistant/components/homekit/services.yaml +7 -3
- homeassistant/components/homekit/strings.json +7 -1
- homeassistant/components/homekit/translations/cs.json +7 -1
- homeassistant/components/homekit/translations/de.json +6 -0
- homeassistant/components/homekit/translations/el.json +0 -1
- homeassistant/components/homekit/translations/en-GB.json +7 -1
- homeassistant/components/homekit/translations/en.json +7 -1
- homeassistant/components/homekit/translations/es.json +0 -1
- homeassistant/components/homekit/translations/et.json +7 -1
- homeassistant/components/homekit/translations/ga.json +0 -1
- homeassistant/components/homekit/translations/hu.json +0 -1
- homeassistant/components/homekit/translations/it.json +6 -0
- homeassistant/components/homekit/translations/ja.json +0 -1
- homeassistant/components/homekit/translations/lt.json +7 -1
- homeassistant/components/homekit/translations/nl.json +5 -0
- homeassistant/components/homekit/translations/pt.json +6 -0
- homeassistant/components/homekit/translations/ru.json +0 -1
- homeassistant/components/homekit/translations/sk.json +6 -0
- homeassistant/components/homekit/translations/sv.json +0 -1
- homeassistant/components/homekit/translations/tr.json +0 -1
- homeassistant/components/homekit/translations/vi.json +0 -1
- homeassistant/components/homekit/translations/zh-Hans.json +6 -1
- homeassistant/components/homekit/translations/zh-Hant.json +6 -0
- homeassistant/components/hue/translations/ar.json +0 -5
- homeassistant/components/hue/translations/bg.json +0 -1
- homeassistant/components/hue/translations/ca.json +0 -1
- homeassistant/components/hue/translations/cs.json +1 -1
- homeassistant/components/hue/translations/cy.json +0 -1
- homeassistant/components/hue/translations/da.json +0 -1
- homeassistant/components/hue/translations/de.json +1 -1
- homeassistant/components/hue/translations/el.json +1 -1
- homeassistant/components/hue/translations/en-GB.json +1 -1
- homeassistant/components/hue/translations/en.json +1 -1
- homeassistant/components/hue/translations/es-419.json +0 -1
- homeassistant/components/hue/translations/es.json +0 -1
- homeassistant/components/hue/translations/et.json +1 -1
- homeassistant/components/hue/translations/fi.json +0 -1
- homeassistant/components/hue/translations/fr.json +0 -1
- homeassistant/components/hue/translations/ga.json +0 -1
- homeassistant/components/hue/translations/he.json +0 -1
- homeassistant/components/hue/translations/hu.json +0 -1
- homeassistant/components/hue/translations/id.json +0 -1
- homeassistant/components/hue/translations/it.json +0 -1
- homeassistant/components/hue/translations/ja.json +0 -1
- homeassistant/components/hue/translations/ko.json +0 -1
- homeassistant/components/hue/translations/lb.json +0 -1
- homeassistant/components/hue/translations/lt.json +1 -1
- homeassistant/components/hue/translations/nb.json +0 -1
- homeassistant/components/hue/translations/nl.json +0 -1
- homeassistant/components/hue/translations/nn.json +0 -1
- homeassistant/components/hue/translations/pl.json +0 -1
- homeassistant/components/hue/translations/pt-BR.json +0 -1
- homeassistant/components/hue/translations/pt.json +1 -1
- homeassistant/components/hue/translations/ro.json +0 -1
- homeassistant/components/hue/translations/ru.json +0 -1
- homeassistant/components/hue/translations/sk.json +1 -1
- homeassistant/components/hue/translations/sl.json +0 -1
- homeassistant/components/hue/translations/sv.json +0 -1
- homeassistant/components/hue/translations/tr.json +0 -1
- homeassistant/components/hue/translations/uk.json +0 -1
- homeassistant/components/hue/translations/vi.json +0 -1
- homeassistant/components/hue/translations/zh-Hans.json +0 -1
- homeassistant/components/hue/translations/zh-Hant.json +1 -1
- homeassistant/components/hue/v2/group.py +67 -12
- homeassistant/components/hydrawise/translations/lt.json +2 -1
- homeassistant/components/imeon_inverter/const.py +1 -1
- homeassistant/components/imeon_inverter/translations/cs.json +1 -0
- homeassistant/components/imeon_inverter/translations/de.json +1 -0
- homeassistant/components/imeon_inverter/translations/el.json +5 -0
- homeassistant/components/imeon_inverter/translations/en-GB.json +1 -0
- homeassistant/components/imeon_inverter/translations/et.json +1 -0
- homeassistant/components/imeon_inverter/translations/it.json +5 -0
- homeassistant/components/imeon_inverter/translations/lt.json +13 -0
- homeassistant/components/imeon_inverter/translations/pt.json +1 -0
- homeassistant/components/imeon_inverter/translations/sk.json +1 -0
- homeassistant/components/imeon_inverter/translations/zh-Hant.json +1 -0
- homeassistant/components/insteon/translations/lt.json +6 -2
- homeassistant/components/iskra/manifest.json +1 -1
- homeassistant/components/lcn/translations/lt.json +2 -0
- homeassistant/components/leaone/translations/lt.json +2 -1
- homeassistant/components/lg_thinq/translations/lt.json +4 -0
- homeassistant/components/luftdaten/translations/lt.json +7 -0
- homeassistant/components/matter/translations/lt.json +3 -0
- homeassistant/components/mealie/manifest.json +1 -1
- homeassistant/components/miele/climate.py +20 -10
- homeassistant/components/miele/translations/lt.json +3 -0
- homeassistant/components/mqtt/translations/lt.json +8 -2
- homeassistant/components/music_assistant/media_browser.py +8 -8
- homeassistant/components/music_assistant/translations/lt.json +2 -1
- homeassistant/components/onkyo/config_flow.py +1 -1
- homeassistant/components/openweathermap/translations/lt.json +2 -1
- homeassistant/components/opower/translations/lt.json +1 -0
- homeassistant/components/pooldose/entity.py +1 -4
- homeassistant/components/pooldose/manifest.json +1 -1
- homeassistant/components/pooldose/quality_scale.yaml +13 -27
- homeassistant/components/qnap/translations/lt.json +1 -0
- homeassistant/components/random/translations/lt.json +1 -1
- homeassistant/components/risco/translations/lt.json +8 -0
- homeassistant/components/scrape/translations/lt.json +2 -1
- homeassistant/components/sensibo/translations/lt.json +5 -0
- homeassistant/components/sensor/translations/lt.json +5 -0
- homeassistant/components/somfy_mylink/translations/lt.json +4 -2
- homeassistant/components/sql/translations/lt.json +2 -1
- homeassistant/components/switchbot/translations/lt.json +3 -1
- homeassistant/components/tellduslive/translations/lt.json +4 -0
- homeassistant/components/template/translations/lt.json +40 -10
- homeassistant/components/teslemetry/translations/lt.json +1 -0
- homeassistant/components/tuya/translations/lt.json +5 -2
- homeassistant/components/unifiprotect/translations/lt.json +3 -0
- homeassistant/components/waze_travel_time/translations/lt.json +8 -0
- homeassistant/components/waze_travel_time/translations/nl.json +3 -0
- homeassistant/components/whois/translations/lt.json +1 -0
- homeassistant/components/xiaomi_ble/translations/lt.json +2 -1
- homeassistant/components/yalexs_ble/translations/cs.json +16 -1
- homeassistant/components/yalexs_ble/translations/el.json +11 -0
- homeassistant/components/yalexs_ble/translations/et.json +16 -1
- homeassistant/components/yalexs_ble/translations/lt.json +16 -1
- homeassistant/components/yalexs_ble/translations/pt.json +16 -1
- homeassistant/components/yalexs_ble/translations/sk.json +16 -1
- homeassistant/components/zwave_js/translations/lt.json +2 -0
- homeassistant/const.py +1 -1
- homeassistant/generated/config_flows.py +0 -1
- homeassistant/generated/integrations.json +0 -6
- homeassistant/package_constraints.txt +1 -1
- {homeassistant-2025.9.0b2.dist-info → homeassistant-2025.9.0b3.dist-info}/METADATA +1 -1
- {homeassistant-2025.9.0b2.dist-info → homeassistant-2025.9.0b3.dist-info}/RECORD +161 -205
- homeassistant/components/vulcan/__init__.py +0 -48
- homeassistant/components/vulcan/calendar.py +0 -176
- homeassistant/components/vulcan/config_flow.py +0 -327
- homeassistant/components/vulcan/const.py +0 -3
- homeassistant/components/vulcan/fetch_data.py +0 -98
- homeassistant/components/vulcan/manifest.json +0 -9
- homeassistant/components/vulcan/register.py +0 -12
- homeassistant/components/vulcan/strings.json +0 -62
- homeassistant/components/vulcan/translations/bg.json +0 -37
- homeassistant/components/vulcan/translations/ca.json +0 -59
- homeassistant/components/vulcan/translations/cs.json +0 -62
- homeassistant/components/vulcan/translations/de.json +0 -62
- homeassistant/components/vulcan/translations/el.json +0 -61
- homeassistant/components/vulcan/translations/en-GB.json +0 -62
- homeassistant/components/vulcan/translations/en.json +0 -62
- homeassistant/components/vulcan/translations/es.json +0 -62
- homeassistant/components/vulcan/translations/et.json +0 -62
- homeassistant/components/vulcan/translations/fi.json +0 -59
- homeassistant/components/vulcan/translations/fr.json +0 -61
- homeassistant/components/vulcan/translations/ga.json +0 -9
- homeassistant/components/vulcan/translations/gl.json +0 -7
- homeassistant/components/vulcan/translations/he.json +0 -29
- homeassistant/components/vulcan/translations/hu.json +0 -62
- homeassistant/components/vulcan/translations/id.json +0 -59
- homeassistant/components/vulcan/translations/it.json +0 -60
- homeassistant/components/vulcan/translations/ja.json +0 -62
- homeassistant/components/vulcan/translations/ko.json +0 -59
- homeassistant/components/vulcan/translations/lt.json +0 -62
- homeassistant/components/vulcan/translations/mk.json +0 -11
- homeassistant/components/vulcan/translations/nb.json +0 -59
- homeassistant/components/vulcan/translations/nl.json +0 -60
- homeassistant/components/vulcan/translations/pl.json +0 -57
- homeassistant/components/vulcan/translations/pt-BR.json +0 -58
- homeassistant/components/vulcan/translations/pt.json +0 -62
- homeassistant/components/vulcan/translations/ro.json +0 -45
- homeassistant/components/vulcan/translations/ru.json +0 -62
- homeassistant/components/vulcan/translations/sk.json +0 -62
- homeassistant/components/vulcan/translations/sl.json +0 -21
- homeassistant/components/vulcan/translations/sv.json +0 -62
- homeassistant/components/vulcan/translations/tr.json +0 -62
- homeassistant/components/vulcan/translations/uk.json +0 -52
- homeassistant/components/vulcan/translations/vi.json +0 -15
- homeassistant/components/vulcan/translations/zh-Hans.json +0 -62
- homeassistant/components/vulcan/translations/zh-Hant.json +0 -62
- {homeassistant-2025.9.0b2.dist-info → homeassistant-2025.9.0b3.dist-info}/WHEEL +0 -0
- {homeassistant-2025.9.0b2.dist-info → homeassistant-2025.9.0b3.dist-info}/entry_points.txt +0 -0
- {homeassistant-2025.9.0b2.dist-info → homeassistant-2025.9.0b3.dist-info}/licenses/LICENSE.md +0 -0
- {homeassistant-2025.9.0b2.dist-info → homeassistant-2025.9.0b3.dist-info}/licenses/homeassistant/backports/LICENSE.Python +0 -0
- {homeassistant-2025.9.0b2.dist-info → homeassistant-2025.9.0b3.dist-info}/top_level.txt +0 -0
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"""The Vulcan component."""
|
|
2
|
-
|
|
3
|
-
from aiohttp import ClientConnectorError
|
|
4
|
-
from vulcan import Account, Keystore, UnauthorizedCertificateException, Vulcan
|
|
5
|
-
|
|
6
|
-
from homeassistant.config_entries import ConfigEntry
|
|
7
|
-
from homeassistant.const import Platform
|
|
8
|
-
from homeassistant.core import HomeAssistant
|
|
9
|
-
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
|
10
|
-
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
11
|
-
|
|
12
|
-
from .const import DOMAIN
|
|
13
|
-
|
|
14
|
-
PLATFORMS = [Platform.CALENDAR]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
18
|
-
"""Set up Uonet+ Vulcan integration."""
|
|
19
|
-
hass.data.setdefault(DOMAIN, {})
|
|
20
|
-
try:
|
|
21
|
-
keystore = Keystore.load(entry.data["keystore"])
|
|
22
|
-
account = Account.load(entry.data["account"])
|
|
23
|
-
client = Vulcan(keystore, account, async_get_clientsession(hass))
|
|
24
|
-
await client.select_student()
|
|
25
|
-
students = await client.get_students()
|
|
26
|
-
for student in students:
|
|
27
|
-
if str(student.pupil.id) == str(entry.data["student_id"]):
|
|
28
|
-
client.student = student
|
|
29
|
-
break
|
|
30
|
-
except UnauthorizedCertificateException as err:
|
|
31
|
-
raise ConfigEntryAuthFailed("The certificate is not authorized.") from err
|
|
32
|
-
except ClientConnectorError as err:
|
|
33
|
-
raise ConfigEntryNotReady(
|
|
34
|
-
f"Connection error - please check your internet connection: {err}"
|
|
35
|
-
) from err
|
|
36
|
-
hass.data[DOMAIN][entry.entry_id] = client
|
|
37
|
-
|
|
38
|
-
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
|
39
|
-
|
|
40
|
-
return True
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
44
|
-
"""Unload a config entry."""
|
|
45
|
-
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
|
46
|
-
hass.data[DOMAIN].pop(entry.entry_id)
|
|
47
|
-
|
|
48
|
-
return unload_ok
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
"""Support for Vulcan Calendar platform."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from datetime import date, datetime, timedelta
|
|
6
|
-
import logging
|
|
7
|
-
from typing import cast
|
|
8
|
-
from zoneinfo import ZoneInfo
|
|
9
|
-
|
|
10
|
-
from aiohttp import ClientConnectorError
|
|
11
|
-
from vulcan import UnauthorizedCertificateException
|
|
12
|
-
|
|
13
|
-
from homeassistant.components.calendar import (
|
|
14
|
-
ENTITY_ID_FORMAT,
|
|
15
|
-
CalendarEntity,
|
|
16
|
-
CalendarEvent,
|
|
17
|
-
)
|
|
18
|
-
from homeassistant.config_entries import ConfigEntry
|
|
19
|
-
from homeassistant.core import HomeAssistant
|
|
20
|
-
from homeassistant.exceptions import ConfigEntryAuthFailed
|
|
21
|
-
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
|
22
|
-
from homeassistant.helpers.entity import generate_entity_id
|
|
23
|
-
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
24
|
-
|
|
25
|
-
from . import DOMAIN
|
|
26
|
-
from .fetch_data import get_lessons, get_student_info
|
|
27
|
-
|
|
28
|
-
_LOGGER = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
async def async_setup_entry(
|
|
32
|
-
hass: HomeAssistant,
|
|
33
|
-
config_entry: ConfigEntry,
|
|
34
|
-
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
35
|
-
) -> None:
|
|
36
|
-
"""Set up the calendar platform for entity."""
|
|
37
|
-
client = hass.data[DOMAIN][config_entry.entry_id]
|
|
38
|
-
data = {
|
|
39
|
-
"student_info": await get_student_info(
|
|
40
|
-
client, config_entry.data.get("student_id")
|
|
41
|
-
),
|
|
42
|
-
}
|
|
43
|
-
async_add_entities(
|
|
44
|
-
[
|
|
45
|
-
VulcanCalendarEntity(
|
|
46
|
-
client,
|
|
47
|
-
data,
|
|
48
|
-
generate_entity_id(
|
|
49
|
-
ENTITY_ID_FORMAT,
|
|
50
|
-
f"vulcan_calendar_{data['student_info']['full_name']}",
|
|
51
|
-
hass=hass,
|
|
52
|
-
),
|
|
53
|
-
)
|
|
54
|
-
],
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class VulcanCalendarEntity(CalendarEntity):
|
|
59
|
-
"""A calendar entity."""
|
|
60
|
-
|
|
61
|
-
_attr_has_entity_name = True
|
|
62
|
-
_attr_translation_key = "calendar"
|
|
63
|
-
|
|
64
|
-
def __init__(self, client, data, entity_id) -> None:
|
|
65
|
-
"""Create the Calendar entity."""
|
|
66
|
-
self._event: CalendarEvent | None = None
|
|
67
|
-
self.client = client
|
|
68
|
-
self.entity_id = entity_id
|
|
69
|
-
student_info = data["student_info"]
|
|
70
|
-
self._attr_unique_id = f"vulcan_calendar_{student_info['id']}"
|
|
71
|
-
self._attr_device_info = DeviceInfo(
|
|
72
|
-
identifiers={(DOMAIN, f"calendar_{student_info['id']}")},
|
|
73
|
-
entry_type=DeviceEntryType.SERVICE,
|
|
74
|
-
name=cast(str, student_info["full_name"]),
|
|
75
|
-
model=(
|
|
76
|
-
f"{student_info['full_name']} -"
|
|
77
|
-
f" {student_info['class']} {student_info['school']}"
|
|
78
|
-
),
|
|
79
|
-
manufacturer="Uonet +",
|
|
80
|
-
configuration_url=(
|
|
81
|
-
f"https://uonetplus.vulcan.net.pl/{student_info['symbol']}"
|
|
82
|
-
),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def event(self) -> CalendarEvent | None:
|
|
87
|
-
"""Return the next upcoming event."""
|
|
88
|
-
return self._event
|
|
89
|
-
|
|
90
|
-
async def async_get_events(
|
|
91
|
-
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
|
92
|
-
) -> list[CalendarEvent]:
|
|
93
|
-
"""Get all events in a specific time frame."""
|
|
94
|
-
try:
|
|
95
|
-
events = await get_lessons(
|
|
96
|
-
self.client,
|
|
97
|
-
date_from=start_date,
|
|
98
|
-
date_to=end_date,
|
|
99
|
-
)
|
|
100
|
-
except UnauthorizedCertificateException as err:
|
|
101
|
-
raise ConfigEntryAuthFailed(
|
|
102
|
-
"The certificate is not authorized, please authorize integration again"
|
|
103
|
-
) from err
|
|
104
|
-
except ClientConnectorError as err:
|
|
105
|
-
if self.available:
|
|
106
|
-
_LOGGER.warning(
|
|
107
|
-
"Connection error - please check your internet connection: %s", err
|
|
108
|
-
)
|
|
109
|
-
events = []
|
|
110
|
-
|
|
111
|
-
event_list = []
|
|
112
|
-
for item in events:
|
|
113
|
-
event = CalendarEvent(
|
|
114
|
-
start=datetime.combine(
|
|
115
|
-
item["date"], item["time"].from_, ZoneInfo("Europe/Warsaw")
|
|
116
|
-
),
|
|
117
|
-
end=datetime.combine(
|
|
118
|
-
item["date"], item["time"].to, ZoneInfo("Europe/Warsaw")
|
|
119
|
-
),
|
|
120
|
-
summary=item["lesson"],
|
|
121
|
-
location=item["room"],
|
|
122
|
-
description=item["teacher"],
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
event_list.append(event)
|
|
126
|
-
|
|
127
|
-
return event_list
|
|
128
|
-
|
|
129
|
-
async def async_update(self) -> None:
|
|
130
|
-
"""Get the latest data."""
|
|
131
|
-
|
|
132
|
-
try:
|
|
133
|
-
events = await get_lessons(self.client)
|
|
134
|
-
|
|
135
|
-
if not self.available:
|
|
136
|
-
_LOGGER.warning("Restored connection with API")
|
|
137
|
-
self._attr_available = True
|
|
138
|
-
|
|
139
|
-
if events == []:
|
|
140
|
-
events = await get_lessons(
|
|
141
|
-
self.client,
|
|
142
|
-
date_to=date.today() + timedelta(days=7),
|
|
143
|
-
)
|
|
144
|
-
if events == []:
|
|
145
|
-
self._event = None
|
|
146
|
-
return
|
|
147
|
-
except UnauthorizedCertificateException as err:
|
|
148
|
-
raise ConfigEntryAuthFailed(
|
|
149
|
-
"The certificate is not authorized, please authorize integration again"
|
|
150
|
-
) from err
|
|
151
|
-
except ClientConnectorError as err:
|
|
152
|
-
if self.available:
|
|
153
|
-
_LOGGER.warning(
|
|
154
|
-
"Connection error - please check your internet connection: %s", err
|
|
155
|
-
)
|
|
156
|
-
self._attr_available = False
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
new_event = min(
|
|
160
|
-
events,
|
|
161
|
-
key=lambda d: (
|
|
162
|
-
datetime.combine(d["date"], d["time"].to) < datetime.now(),
|
|
163
|
-
abs(datetime.combine(d["date"], d["time"].to) - datetime.now()),
|
|
164
|
-
),
|
|
165
|
-
)
|
|
166
|
-
self._event = CalendarEvent(
|
|
167
|
-
start=datetime.combine(
|
|
168
|
-
new_event["date"], new_event["time"].from_, ZoneInfo("Europe/Warsaw")
|
|
169
|
-
),
|
|
170
|
-
end=datetime.combine(
|
|
171
|
-
new_event["date"], new_event["time"].to, ZoneInfo("Europe/Warsaw")
|
|
172
|
-
),
|
|
173
|
-
summary=new_event["lesson"],
|
|
174
|
-
location=new_event["room"],
|
|
175
|
-
description=new_event["teacher"],
|
|
176
|
-
)
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
"""Adds config flow for Vulcan."""
|
|
2
|
-
|
|
3
|
-
from collections.abc import Mapping
|
|
4
|
-
import logging
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
6
|
-
|
|
7
|
-
from aiohttp import ClientConnectionError
|
|
8
|
-
import voluptuous as vol
|
|
9
|
-
from vulcan import (
|
|
10
|
-
Account,
|
|
11
|
-
ExpiredTokenException,
|
|
12
|
-
InvalidPINException,
|
|
13
|
-
InvalidSymbolException,
|
|
14
|
-
InvalidTokenException,
|
|
15
|
-
Keystore,
|
|
16
|
-
UnauthorizedCertificateException,
|
|
17
|
-
Vulcan,
|
|
18
|
-
)
|
|
19
|
-
from vulcan.model import Student
|
|
20
|
-
|
|
21
|
-
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
|
22
|
-
from homeassistant.const import CONF_PIN, CONF_REGION, CONF_TOKEN
|
|
23
|
-
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
|
24
|
-
|
|
25
|
-
from . import DOMAIN
|
|
26
|
-
from .register import register
|
|
27
|
-
|
|
28
|
-
_LOGGER = logging.getLogger(__name__)
|
|
29
|
-
|
|
30
|
-
LOGIN_SCHEMA = {
|
|
31
|
-
vol.Required(CONF_TOKEN): str,
|
|
32
|
-
vol.Required(CONF_REGION): str,
|
|
33
|
-
vol.Required(CONF_PIN): str,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class VulcanFlowHandler(ConfigFlow, domain=DOMAIN):
|
|
38
|
-
"""Handle a Uonet+ Vulcan config flow."""
|
|
39
|
-
|
|
40
|
-
VERSION = 1
|
|
41
|
-
|
|
42
|
-
account: Account
|
|
43
|
-
keystore: Keystore
|
|
44
|
-
|
|
45
|
-
def __init__(self) -> None:
|
|
46
|
-
"""Initialize config flow."""
|
|
47
|
-
self.students: list[Student] | None = None
|
|
48
|
-
|
|
49
|
-
async def async_step_user(
|
|
50
|
-
self, user_input: dict[str, Any] | None = None
|
|
51
|
-
) -> ConfigFlowResult:
|
|
52
|
-
"""Handle config flow."""
|
|
53
|
-
if self._async_current_entries():
|
|
54
|
-
return await self.async_step_add_next_config_entry()
|
|
55
|
-
|
|
56
|
-
return await self.async_step_auth()
|
|
57
|
-
|
|
58
|
-
async def async_step_auth(
|
|
59
|
-
self,
|
|
60
|
-
user_input: dict[str, str] | None = None,
|
|
61
|
-
errors: dict[str, str] | None = None,
|
|
62
|
-
) -> ConfigFlowResult:
|
|
63
|
-
"""Authorize integration."""
|
|
64
|
-
|
|
65
|
-
if user_input is not None:
|
|
66
|
-
try:
|
|
67
|
-
credentials = await register(
|
|
68
|
-
user_input[CONF_TOKEN],
|
|
69
|
-
user_input[CONF_REGION],
|
|
70
|
-
user_input[CONF_PIN],
|
|
71
|
-
)
|
|
72
|
-
except InvalidSymbolException:
|
|
73
|
-
errors = {"base": "invalid_symbol"}
|
|
74
|
-
except InvalidTokenException:
|
|
75
|
-
errors = {"base": "invalid_token"}
|
|
76
|
-
except InvalidPINException:
|
|
77
|
-
errors = {"base": "invalid_pin"}
|
|
78
|
-
except ExpiredTokenException:
|
|
79
|
-
errors = {"base": "expired_token"}
|
|
80
|
-
except ClientConnectionError as err:
|
|
81
|
-
errors = {"base": "cannot_connect"}
|
|
82
|
-
_LOGGER.error("Connection error: %s", err)
|
|
83
|
-
except Exception:
|
|
84
|
-
_LOGGER.exception("Unexpected exception")
|
|
85
|
-
errors = {"base": "unknown"}
|
|
86
|
-
if not errors:
|
|
87
|
-
account = credentials["account"]
|
|
88
|
-
keystore = credentials["keystore"]
|
|
89
|
-
client = Vulcan(keystore, account, async_get_clientsession(self.hass))
|
|
90
|
-
students = await client.get_students()
|
|
91
|
-
|
|
92
|
-
if len(students) > 1:
|
|
93
|
-
self.account = account
|
|
94
|
-
self.keystore = keystore
|
|
95
|
-
self.students = students
|
|
96
|
-
return await self.async_step_select_student()
|
|
97
|
-
student = students[0]
|
|
98
|
-
await self.async_set_unique_id(str(student.pupil.id))
|
|
99
|
-
self._abort_if_unique_id_configured()
|
|
100
|
-
return self.async_create_entry(
|
|
101
|
-
title=f"{student.pupil.first_name} {student.pupil.last_name}",
|
|
102
|
-
data={
|
|
103
|
-
"student_id": str(student.pupil.id),
|
|
104
|
-
"keystore": keystore.as_dict,
|
|
105
|
-
"account": account.as_dict,
|
|
106
|
-
},
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
return self.async_show_form(
|
|
110
|
-
step_id="auth",
|
|
111
|
-
data_schema=vol.Schema(LOGIN_SCHEMA),
|
|
112
|
-
errors=errors,
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
async def async_step_select_student(
|
|
116
|
-
self, user_input: dict[str, str] | None = None
|
|
117
|
-
) -> ConfigFlowResult:
|
|
118
|
-
"""Allow user to select student."""
|
|
119
|
-
errors: dict[str, str] = {}
|
|
120
|
-
students: dict[str, str] = {}
|
|
121
|
-
if self.students is not None:
|
|
122
|
-
for student in self.students:
|
|
123
|
-
students[str(student.pupil.id)] = (
|
|
124
|
-
f"{student.pupil.first_name} {student.pupil.last_name}"
|
|
125
|
-
)
|
|
126
|
-
if user_input is not None:
|
|
127
|
-
if TYPE_CHECKING:
|
|
128
|
-
assert self.keystore is not None
|
|
129
|
-
student_id = user_input["student"]
|
|
130
|
-
await self.async_set_unique_id(str(student_id))
|
|
131
|
-
self._abort_if_unique_id_configured()
|
|
132
|
-
return self.async_create_entry(
|
|
133
|
-
title=students[student_id],
|
|
134
|
-
data={
|
|
135
|
-
"student_id": str(student_id),
|
|
136
|
-
"keystore": self.keystore.as_dict,
|
|
137
|
-
"account": self.account.as_dict,
|
|
138
|
-
},
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
return self.async_show_form(
|
|
142
|
-
step_id="select_student",
|
|
143
|
-
data_schema=vol.Schema({vol.Required("student"): vol.In(students)}),
|
|
144
|
-
errors=errors,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
async def async_step_select_saved_credentials(
|
|
148
|
-
self,
|
|
149
|
-
user_input: dict[str, str] | None = None,
|
|
150
|
-
errors: dict[str, str] | None = None,
|
|
151
|
-
) -> ConfigFlowResult:
|
|
152
|
-
"""Allow user to select saved credentials."""
|
|
153
|
-
|
|
154
|
-
credentials: dict[str, Any] = {}
|
|
155
|
-
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
|
156
|
-
credentials[entry.entry_id] = entry.data["account"]["UserName"]
|
|
157
|
-
|
|
158
|
-
if user_input is not None:
|
|
159
|
-
existing_entry = self.hass.config_entries.async_get_entry(
|
|
160
|
-
user_input["credentials"]
|
|
161
|
-
)
|
|
162
|
-
if TYPE_CHECKING:
|
|
163
|
-
assert existing_entry is not None
|
|
164
|
-
keystore = Keystore.load(existing_entry.data["keystore"])
|
|
165
|
-
account = Account.load(existing_entry.data["account"])
|
|
166
|
-
client = Vulcan(keystore, account, async_get_clientsession(self.hass))
|
|
167
|
-
try:
|
|
168
|
-
students = await client.get_students()
|
|
169
|
-
except UnauthorizedCertificateException:
|
|
170
|
-
return await self.async_step_auth(
|
|
171
|
-
errors={"base": "expired_credentials"}
|
|
172
|
-
)
|
|
173
|
-
except ClientConnectionError as err:
|
|
174
|
-
_LOGGER.error("Connection error: %s", err)
|
|
175
|
-
return await self.async_step_select_saved_credentials(
|
|
176
|
-
errors={"base": "cannot_connect"}
|
|
177
|
-
)
|
|
178
|
-
except Exception:
|
|
179
|
-
_LOGGER.exception("Unexpected exception")
|
|
180
|
-
return await self.async_step_auth(errors={"base": "unknown"})
|
|
181
|
-
if len(students) == 1:
|
|
182
|
-
student = students[0]
|
|
183
|
-
await self.async_set_unique_id(str(student.pupil.id))
|
|
184
|
-
self._abort_if_unique_id_configured()
|
|
185
|
-
return self.async_create_entry(
|
|
186
|
-
title=f"{student.pupil.first_name} {student.pupil.last_name}",
|
|
187
|
-
data={
|
|
188
|
-
"student_id": str(student.pupil.id),
|
|
189
|
-
"keystore": keystore.as_dict,
|
|
190
|
-
"account": account.as_dict,
|
|
191
|
-
},
|
|
192
|
-
)
|
|
193
|
-
self.account = account
|
|
194
|
-
self.keystore = keystore
|
|
195
|
-
self.students = students
|
|
196
|
-
return await self.async_step_select_student()
|
|
197
|
-
|
|
198
|
-
data_schema = {
|
|
199
|
-
vol.Required(
|
|
200
|
-
"credentials",
|
|
201
|
-
): vol.In(credentials),
|
|
202
|
-
}
|
|
203
|
-
return self.async_show_form(
|
|
204
|
-
step_id="select_saved_credentials",
|
|
205
|
-
data_schema=vol.Schema(data_schema),
|
|
206
|
-
errors=errors,
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
async def async_step_add_next_config_entry(
|
|
210
|
-
self, user_input: dict[str, bool] | None = None
|
|
211
|
-
) -> ConfigFlowResult:
|
|
212
|
-
"""Flow initialized when user is adding next entry of that integration."""
|
|
213
|
-
|
|
214
|
-
existing_entries = self.hass.config_entries.async_entries(DOMAIN)
|
|
215
|
-
|
|
216
|
-
errors: dict[str, str] = {}
|
|
217
|
-
|
|
218
|
-
if user_input is not None:
|
|
219
|
-
if not user_input["use_saved_credentials"]:
|
|
220
|
-
return await self.async_step_auth()
|
|
221
|
-
if len(existing_entries) > 1:
|
|
222
|
-
return await self.async_step_select_saved_credentials()
|
|
223
|
-
keystore = Keystore.load(existing_entries[0].data["keystore"])
|
|
224
|
-
account = Account.load(existing_entries[0].data["account"])
|
|
225
|
-
client = Vulcan(keystore, account, async_get_clientsession(self.hass))
|
|
226
|
-
students = await client.get_students()
|
|
227
|
-
existing_entry_ids = [
|
|
228
|
-
entry.data["student_id"] for entry in existing_entries
|
|
229
|
-
]
|
|
230
|
-
new_students = [
|
|
231
|
-
student
|
|
232
|
-
for student in students
|
|
233
|
-
if str(student.pupil.id) not in existing_entry_ids
|
|
234
|
-
]
|
|
235
|
-
if not new_students:
|
|
236
|
-
return self.async_abort(reason="all_student_already_configured")
|
|
237
|
-
if len(new_students) == 1:
|
|
238
|
-
await self.async_set_unique_id(str(new_students[0].pupil.id))
|
|
239
|
-
self._abort_if_unique_id_configured()
|
|
240
|
-
return self.async_create_entry(
|
|
241
|
-
title=(
|
|
242
|
-
f"{new_students[0].pupil.first_name} {new_students[0].pupil.last_name}"
|
|
243
|
-
),
|
|
244
|
-
data={
|
|
245
|
-
"student_id": str(new_students[0].pupil.id),
|
|
246
|
-
"keystore": keystore.as_dict,
|
|
247
|
-
"account": account.as_dict,
|
|
248
|
-
},
|
|
249
|
-
)
|
|
250
|
-
self.account = account
|
|
251
|
-
self.keystore = keystore
|
|
252
|
-
self.students = new_students
|
|
253
|
-
return await self.async_step_select_student()
|
|
254
|
-
|
|
255
|
-
data_schema = {
|
|
256
|
-
vol.Required("use_saved_credentials", default=True): bool,
|
|
257
|
-
}
|
|
258
|
-
return self.async_show_form(
|
|
259
|
-
step_id="add_next_config_entry",
|
|
260
|
-
data_schema=vol.Schema(data_schema),
|
|
261
|
-
errors=errors,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
async def async_step_reauth(
|
|
265
|
-
self, entry_data: Mapping[str, Any]
|
|
266
|
-
) -> ConfigFlowResult:
|
|
267
|
-
"""Perform reauth upon an API authentication error."""
|
|
268
|
-
return await self.async_step_reauth_confirm()
|
|
269
|
-
|
|
270
|
-
async def async_step_reauth_confirm(
|
|
271
|
-
self, user_input: dict[str, str] | None = None
|
|
272
|
-
) -> ConfigFlowResult:
|
|
273
|
-
"""Reauthorize integration."""
|
|
274
|
-
errors = {}
|
|
275
|
-
if user_input is not None:
|
|
276
|
-
try:
|
|
277
|
-
credentials = await register(
|
|
278
|
-
user_input[CONF_TOKEN],
|
|
279
|
-
user_input[CONF_REGION],
|
|
280
|
-
user_input[CONF_PIN],
|
|
281
|
-
)
|
|
282
|
-
except InvalidSymbolException:
|
|
283
|
-
errors = {"base": "invalid_symbol"}
|
|
284
|
-
except InvalidTokenException:
|
|
285
|
-
errors = {"base": "invalid_token"}
|
|
286
|
-
except InvalidPINException:
|
|
287
|
-
errors = {"base": "invalid_pin"}
|
|
288
|
-
except ExpiredTokenException:
|
|
289
|
-
errors = {"base": "expired_token"}
|
|
290
|
-
except ClientConnectionError as err:
|
|
291
|
-
errors["base"] = "cannot_connect"
|
|
292
|
-
_LOGGER.error("Connection error: %s", err)
|
|
293
|
-
except Exception:
|
|
294
|
-
_LOGGER.exception("Unexpected exception")
|
|
295
|
-
errors["base"] = "unknown"
|
|
296
|
-
if not errors:
|
|
297
|
-
account = credentials["account"]
|
|
298
|
-
keystore = credentials["keystore"]
|
|
299
|
-
client = Vulcan(keystore, account, async_get_clientsession(self.hass))
|
|
300
|
-
students = await client.get_students()
|
|
301
|
-
existing_entries = self.hass.config_entries.async_entries(DOMAIN)
|
|
302
|
-
matching_entries = False
|
|
303
|
-
for student in students:
|
|
304
|
-
for entry in existing_entries:
|
|
305
|
-
if str(student.pupil.id) == str(entry.data["student_id"]):
|
|
306
|
-
self.hass.config_entries.async_update_entry(
|
|
307
|
-
entry,
|
|
308
|
-
title=(
|
|
309
|
-
f"{student.pupil.first_name} {student.pupil.last_name}"
|
|
310
|
-
),
|
|
311
|
-
data={
|
|
312
|
-
"student_id": str(student.pupil.id),
|
|
313
|
-
"keystore": keystore.as_dict,
|
|
314
|
-
"account": account.as_dict,
|
|
315
|
-
},
|
|
316
|
-
)
|
|
317
|
-
await self.hass.config_entries.async_reload(entry.entry_id)
|
|
318
|
-
matching_entries = True
|
|
319
|
-
if not matching_entries:
|
|
320
|
-
return self.async_abort(reason="no_matching_entries")
|
|
321
|
-
return self.async_abort(reason="reauth_successful")
|
|
322
|
-
|
|
323
|
-
return self.async_show_form(
|
|
324
|
-
step_id="reauth_confirm",
|
|
325
|
-
data_schema=vol.Schema(LOGIN_SCHEMA),
|
|
326
|
-
errors=errors,
|
|
327
|
-
)
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
"""Support for fetching Vulcan data."""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
async def get_lessons(client, date_from=None, date_to=None):
|
|
5
|
-
"""Support for fetching Vulcan lessons."""
|
|
6
|
-
changes = {}
|
|
7
|
-
list_ans = []
|
|
8
|
-
async for lesson in await client.data.get_changed_lessons(
|
|
9
|
-
date_from=date_from, date_to=date_to
|
|
10
|
-
):
|
|
11
|
-
temp_dict = {}
|
|
12
|
-
_id = str(lesson.id)
|
|
13
|
-
temp_dict["id"] = lesson.id
|
|
14
|
-
temp_dict["number"] = lesson.time.position if lesson.time is not None else None
|
|
15
|
-
temp_dict["lesson"] = (
|
|
16
|
-
lesson.subject.name if lesson.subject is not None else None
|
|
17
|
-
)
|
|
18
|
-
temp_dict["room"] = lesson.room.code if lesson.room is not None else None
|
|
19
|
-
temp_dict["changes"] = lesson.changes
|
|
20
|
-
temp_dict["note"] = lesson.note
|
|
21
|
-
temp_dict["reason"] = lesson.reason
|
|
22
|
-
temp_dict["event"] = lesson.event
|
|
23
|
-
temp_dict["group"] = lesson.group
|
|
24
|
-
temp_dict["teacher"] = (
|
|
25
|
-
lesson.teacher.display_name if lesson.teacher is not None else None
|
|
26
|
-
)
|
|
27
|
-
temp_dict["from_to"] = (
|
|
28
|
-
lesson.time.displayed_time if lesson.time is not None else None
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
changes[str(_id)] = temp_dict
|
|
32
|
-
|
|
33
|
-
async for lesson in await client.data.get_lessons(
|
|
34
|
-
date_from=date_from, date_to=date_to
|
|
35
|
-
):
|
|
36
|
-
temp_dict = {}
|
|
37
|
-
temp_dict["id"] = lesson.id
|
|
38
|
-
temp_dict["number"] = lesson.time.position
|
|
39
|
-
temp_dict["time"] = lesson.time
|
|
40
|
-
temp_dict["date"] = lesson.date.date
|
|
41
|
-
temp_dict["lesson"] = (
|
|
42
|
-
lesson.subject.name if lesson.subject is not None else None
|
|
43
|
-
)
|
|
44
|
-
if lesson.room is not None:
|
|
45
|
-
temp_dict["room"] = lesson.room.code
|
|
46
|
-
else:
|
|
47
|
-
temp_dict["room"] = "-"
|
|
48
|
-
temp_dict["visible"] = lesson.visible
|
|
49
|
-
temp_dict["changes"] = lesson.changes
|
|
50
|
-
temp_dict["group"] = lesson.group
|
|
51
|
-
temp_dict["reason"] = None
|
|
52
|
-
temp_dict["teacher"] = (
|
|
53
|
-
lesson.teacher.display_name if lesson.teacher is not None else None
|
|
54
|
-
)
|
|
55
|
-
temp_dict["from_to"] = (
|
|
56
|
-
lesson.time.displayed_time if lesson.time is not None else None
|
|
57
|
-
)
|
|
58
|
-
if temp_dict["changes"] is None:
|
|
59
|
-
temp_dict["changes"] = ""
|
|
60
|
-
elif temp_dict["changes"].type == 1:
|
|
61
|
-
temp_dict["lesson"] = f"Lekcja odwołana ({temp_dict['lesson']})"
|
|
62
|
-
temp_dict["changes_info"] = f"Lekcja odwołana ({temp_dict['lesson']})"
|
|
63
|
-
if str(temp_dict["changes"].id) in changes:
|
|
64
|
-
temp_dict["reason"] = changes[str(temp_dict["changes"].id)]["reason"]
|
|
65
|
-
elif temp_dict["changes"].type == 2:
|
|
66
|
-
temp_dict["lesson"] = f"{temp_dict['lesson']} (Zastępstwo)"
|
|
67
|
-
temp_dict["teacher"] = changes[str(temp_dict["changes"].id)]["teacher"]
|
|
68
|
-
if str(temp_dict["changes"].id) in changes:
|
|
69
|
-
temp_dict["teacher"] = changes[str(temp_dict["changes"].id)]["teacher"]
|
|
70
|
-
temp_dict["reason"] = changes[str(temp_dict["changes"].id)]["reason"]
|
|
71
|
-
elif temp_dict["changes"].type == 4:
|
|
72
|
-
temp_dict["lesson"] = f"Lekcja odwołana ({temp_dict['lesson']})"
|
|
73
|
-
if str(temp_dict["changes"].id) in changes:
|
|
74
|
-
temp_dict["reason"] = changes[str(temp_dict["changes"].id)]["reason"]
|
|
75
|
-
if temp_dict["visible"]:
|
|
76
|
-
list_ans.append(temp_dict)
|
|
77
|
-
|
|
78
|
-
return list_ans
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
async def get_student_info(client, student_id):
|
|
82
|
-
"""Support for fetching Student info by student id."""
|
|
83
|
-
student_info = {}
|
|
84
|
-
for student in await client.get_students():
|
|
85
|
-
if str(student.pupil.id) == str(student_id):
|
|
86
|
-
student_info["first_name"] = student.pupil.first_name
|
|
87
|
-
if student.pupil.second_name:
|
|
88
|
-
student_info["second_name"] = student.pupil.second_name
|
|
89
|
-
student_info["last_name"] = student.pupil.last_name
|
|
90
|
-
student_info["full_name"] = (
|
|
91
|
-
f"{student.pupil.first_name} {student.pupil.last_name}"
|
|
92
|
-
)
|
|
93
|
-
student_info["id"] = student.pupil.id
|
|
94
|
-
student_info["class"] = student.class_
|
|
95
|
-
student_info["school"] = student.school.name
|
|
96
|
-
student_info["symbol"] = student.symbol
|
|
97
|
-
break
|
|
98
|
-
return student_info
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"""Support for register Vulcan account."""
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from vulcan import Account, Keystore
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
async def register(token: str, symbol: str, pin: str) -> dict[str, Any]:
|
|
9
|
-
"""Register integration and save credentials."""
|
|
10
|
-
keystore = await Keystore.create(device_model="Home Assistant")
|
|
11
|
-
account = await Account.register(keystore, token, symbol, pin)
|
|
12
|
-
return {"account": account, "keystore": keystore}
|