Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

calendar for canteen lunches #69

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions custom_components/homeassistantedupage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .homeassistant_edupage import Edupage
from edupage_api.lunches import Lunch

from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from .const import DOMAIN, CONF_PHPSESSID, CONF_SUBDOMAIN, CONF_STUDENT_ID
Expand Down Expand Up @@ -93,11 +94,35 @@ async def fetch_data():
if canceled_lessons:
timetable_data_canceled[current_date] = canceled_lessons

canteen_menu_data = {}
canteen_calendar_enabled = True
for offset in range(14):
current_date = today + timedelta(days=offset)
try:
lunch = await edupage.get_lunches(current_date)
except Exception as e:
_LOGGER.error(f"Failed to fetch lunch data for {current_date}: {e}")
lunch = None
canteen_calendar_enabled = False
break
meals_to_add = []
if lunch is not None and lunch.menus is not None and len(lunch.menus) > 0:
_LOGGER.debug(f"Lunch for {current_date}: {lunch}")
meals_to_add.append(lunch)

if meals_to_add:
_LOGGER.debug(f"Daily menu for {current_date}: {lessons_to_add}")
canteen_menu_data[current_date] = meals_to_add
else:
_LOGGER.warning(f"INIT No daily menu found for {current_date}")

return_data = {
"student": {"id": student.person_id, "name": student.name},
"grades": grades,
"subjects": subjects,
"timetable": timetable_data,
"canteen_menu": canteen_menu_data,
"canteen_calendar_enabled": canteen_calendar_enabled,
"cancelled_lessons": timetable_data_canceled,
"notifications": notifications,
}
Expand Down
108 changes: 106 additions & 2 deletions custom_components/homeassistantedupage/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,35 @@
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from zoneinfo import ZoneInfo
from edupage_api.timetables import Lesson
from edupage_api.lunches import Lunch

_LOGGER = logging.getLogger("custom_components.homeassistant_edupage")
_LOGGER.debug("CALENDAR Edupage calendar.py is being loaded")

async def async_setup_entry(hass, entry, async_add_entities: AddEntitiesCallback) -> None:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Set up Edupage calendar entities."""
_LOGGER.debug("CALENDAR called async_setup_entry")

coordinator = hass.data[DOMAIN][entry.entry_id]

calendars = []

edupage_calendar = EdupageCalendar(coordinator, entry.data)
calendars.append(edupage_calendar)

if coordinator.data.get("canteen_calendar_enabled", {}):
edupage_canteen_calendar = EdupageCanteenCalendar(coordinator, entry.data)
calendars.append(edupage_canteen_calendar)
_LOGGER.debug("Canteen calendar added")
else:
_LOGGER.debug("Canteen calendar skipped, calendar disabled due to exceptions")

async_add_entities([edupage_calendar])
async_add_entities(calendars)

_LOGGER.debug("CALENDAR async_setup_entry finished.")

Expand Down Expand Up @@ -170,3 +183,94 @@ def find_lesson_now_or_next_across_days(self) -> Optional[CalendarEvent]:
return self.map_lesson_to_calender_event(next_lesson, day)

return None

class EdupageCanteenCalendar(CoordinatorEntity, CalendarEntity):
"""Representation of an Edupage canteen calendar entity."""

def __init__(self, coordinator, data):
super().__init__(coordinator)
self._data = data
self._events = []
self._attr_name = "Edupage Canteen Calendar"
_LOGGER.debug(f"CALENDAR Initialized EdupageCanteenCalendar with data: {data}")

@property
def unique_id(self):
"""Return a unique ID for this calendar."""
return f"edupage_canteen_calendar"

@property
def name(self):
"""Return the name of the calendar."""
return f"Edupage - Canteen"

@property
def extra_state_attributes(self):
"""Return the extra state attributes."""
return {
"unique_id": self.unique_id,
"other_info": "debug info"
}

@property
def available(self) -> bool:
"""Return True if the calendar is available."""
_LOGGER.debug("CALENDAR Checking availability of Edupage Canteen Calendar")
return True

@property
def event(self):
"""Return the next upcoming event or None if no event exists."""
return self.find_meal_now_or_next_across_days()

async def async_get_events(self, hass, start_date: datetime, end_date: datetime):
"""Return events in a specific date range."""
events = []

_LOGGER.debug(f"CALENDAR Fetching canteen calendar between {start_date} and {end_date}")
canteen_menu = self.coordinator.data.get("canteen_menu", {})
_LOGGER.debug(f"CALENDAR Coordinator data: {self.coordinator.data}")
_LOGGER.debug(f"CALENDAR Fetched canteen_menu data: {canteen_menu}")

if not canteen_menu:
_LOGGER.warning("CALENDAR Canteen menu data is missing.")
return events

current_date = start_date.date()
while current_date <= end_date.date():
events.extend(self.get_events(canteen_menu, current_date))
current_date += timedelta(days=1)

_LOGGER.debug(f"CALENDAR Fetched {len(events)} events from {start_date} to {end_date}")
return events

def get_events(self, canteen_menu, current_date):
events = []
daily_menu = canteen_menu.get(current_date)
if daily_menu:
for meal in daily_menu:
_LOGGER.debug(f"CALENDAR Meal attributes: {vars(meal)}")
events.append(
self.map_meal_to_calender_event(meal, current_date)
)
return events


def map_meal_to_calender_event(self, meal: Lunch, day: date) -> CalendarEvent:
local_tz = ZoneInfo(self.hass.config.time_zone)
start_time = datetime.combine(day, meal.served_from.time()).astimezone(local_tz)
end_time = datetime.combine(day, meal.served_to.time()).astimezone(local_tz)
summary = "Lunch"
description = meal.title

cal_event = CalendarEvent(
start=start_time,
end=end_time,
summary=summary,
description=description
)
return cal_event

def find_meal_now_or_next_across_days(self) -> Optional[CalendarEvent]:
# TODO implement
return None
20 changes: 16 additions & 4 deletions custom_components/homeassistantedupage/homeassistant_edupage.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ async def login(self, username: str, password: str, subdomain: str):

except CaptchaException as e:
_LOGGER.error("EDUPAGE login failed: CAPTCHA needed. %s", e)
return False
return False

except SecondFactorFailedException as e:
#TODO hier müsste man dann irgendwie abfangen, falls die session mal abgelaufen ist. und dies dann auch irgendwie via HA sauber zum Nutzer bringen!?
_LOGGER.error("EDUPAGE login failed: 2FA error. %s", e)
return False
return False

except Exception as e:
_LOGGER.error("EDUPAGE unexpected login error: %s", e)
return False
return False

async def get_classes(self):

Expand Down Expand Up @@ -101,7 +101,7 @@ async def get_classrooms(self):
return all_classrooms
except Exception as e:
raise UpdateFailed(F"EDUPAGE error updating get_classrooms data from API: {e}")

async def get_teachers(self):

try:
Expand All @@ -122,6 +122,18 @@ async def get_timetable(self, EduStudent, date):
_LOGGER.error(f"EDUPAGE error updating get_timetable() data for {date}: {e}")
raise UpdateFailed(f"EDUPAGE error updating get_timetable() data for {date}: {e}")

async def get_lunches(self, date):
try:
lunches_data = await self.hass.async_add_executor_job(self.api.get_lunches, date)
if lunches_data is None:
_LOGGER.debug("EDUPAGE lunches is None")
else:
_LOGGER.debug(f"EDUPAGE lunches_data for {date}: {lunches_data}")
return lunches_data
except Exception as e:
_LOGGER.error(f"EDUPAGE error updating get_lunches() data for {date}: {e}")
raise UpdateFailed(f"EDUPAGE error updating get_lunches() data for {date}: {e}")

async def async_update(self):

pass
Loading