"""Support for Vulcan Calendar platform.""" import copy from datetime import date, datetime, timedelta import logging from aiohttp import ClientConnectorError from vulcan._utils import VulcanAPIException from homeassistant.components.calendar import ENTITY_ID_FORMAT, CalendarEventDevice from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_SCAN_INTERVAL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.util import Throttle, dt from . import DOMAIN from .const import DEFAULT_SCAN_INTERVAL from .fetch_data import get_lessons, get_student_info _LOGGER = logging.getLogger(__name__) async def async_setup_entry( hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: """Set up the calendar platform for event devices.""" VulcanCalendarData.MIN_TIME_BETWEEN_UPDATES = timedelta( minutes=config_entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) ) client = hass.data[DOMAIN][config_entry.entry_id] data = { "student_info": await get_student_info( client, config_entry.data.get("student_id") ), "students_number": hass.data[DOMAIN]["students_number"], } async_add_entities( [ VulcanCalendarEventDevice( client, data, generate_entity_id( ENTITY_ID_FORMAT, f"vulcan_calendar_{data['student_info']['full_name']}", hass=hass, ), ) ], ) class VulcanCalendarEventDevice(CalendarEventDevice): """A calendar event device.""" def __init__(self, client, data, entity_id): """Create the Calendar event device.""" self.student_info = data["student_info"] self.data = VulcanCalendarData( client, self.student_info, self.hass, ) self._event = None self.entity_id = entity_id self._unique_id = f"vulcan_calendar_{self.student_info['id']}" if data["students_number"] == 1: self._attr_name = "Vulcan calendar" self.device_name = "Calendar" else: self._attr_name = f"Vulcan calendar - {self.student_info['full_name']}" self.device_name = f"{self.student_info['full_name']}: Calendar" self._attr_unique_id = f"vulcan_calendar_{self.student_info['id']}" self._attr_device_info = { "identifiers": {(DOMAIN, f"calendar_{self.student_info['id']}")}, "entry_type": DeviceEntryType.SERVICE, "name": self.device_name, "model": f"{self.student_info['full_name']} - {self.student_info['class']} {self.student_info['school']}", "manufacturer": "Uonet +", "configuration_url": f"https://uonetplus.vulcan.net.pl/{self.student_info['symbol']}", } @property def event(self): """Return the next upcoming event.""" return self._event async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" return await self.data.async_get_events(hass, start_date, end_date) async def async_update(self): """Update event data.""" await self.data.async_update() event = copy.deepcopy(self.data.event) if event is None: self._event = event return event["start"] = { "dateTime": datetime.combine(event["date"], event["time"].from_) .astimezone(dt.DEFAULT_TIME_ZONE) .isoformat() } event["end"] = { "dateTime": datetime.combine(event["date"], event["time"].to) .astimezone(dt.DEFAULT_TIME_ZONE) .isoformat() } self._event = event class VulcanCalendarData: """Class to utilize calendar service object to get next event.""" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=DEFAULT_SCAN_INTERVAL) def __init__(self, client, student_info, hass): """Set up how we are going to search the Vulcan calendar.""" self.client = client self.event = None self.hass = hass self.student_info = student_info self._available = True async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" try: events = await get_lessons( self.client, date_from=start_date, date_to=end_date, ) except VulcanAPIException as err: if str(err) == "The certificate is not authorized.": _LOGGER.error( "The certificate is not authorized, please authorize integration again" ) raise ConfigEntryAuthFailed from err _LOGGER.error("An API error has occurred: %s", err) events = [] except ClientConnectorError as err: if self._available: _LOGGER.warning( "Connection error - please check your internet connection: %s", err ) events = [] event_list = [] for item in events: event = { "uid": item["id"], "start": { "dateTime": datetime.combine( item["date"], item["time"].from_ ).strftime(DATE_STR_FORMAT) }, "end": { "dateTime": datetime.combine( item["date"], item["time"].to ).strftime(DATE_STR_FORMAT) }, "summary": item["lesson"], "location": item["room"], "description": item["teacher"], } event_list.append(event) return event_list @Throttle(MIN_TIME_BETWEEN_UPDATES) async def async_update(self): """Get the latest data.""" try: events = await get_lessons(self.client) if not self._available: _LOGGER.info("Restored connection with API") self._available = True if events == []: events = await get_lessons( self.client, date_to=date.today() + timedelta(days=7), ) if events == []: self.event = None return except VulcanAPIException as err: if str(err) == "The certificate is not authorized.": _LOGGER.error( "The certificate is not authorized, please authorize integration again" ) raise ConfigEntryAuthFailed from err _LOGGER.error("An API error has occurred: %s", err) return except ClientConnectorError as err: if self._available: _LOGGER.warning( "Connection error - please check your internet connection: %s", err ) self._available = False return new_event = min( events, key=lambda d: ( datetime.combine(d["date"], d["time"].to) < datetime.now(), abs(datetime.combine(d["date"], d["time"].to) - datetime.now()), ), ) self.event = { "uid": new_event["id"], "date": new_event["date"], "time": new_event["time"], "summary": new_event["lesson"], "location": new_event["room"], "description": new_event["teacher"], }