Skip to content

Commit

Permalink
Merge pull request #19 from rine77/11-feature_request-sensoren-über-s…
Browse files Browse the repository at this point in the history
…tundenplan-generieren

11 feature request sensoren über stundenplan generieren
  • Loading branch information
rine77 authored Nov 22, 2024
2 parents 6580797 + 184e567 commit a6b5f9b
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 58 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,7 @@ cython_debug/
#.idea/

# secrets
*creds.sec*
*creds.sec*

# test
test/
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
# homeassistantedupage
An HomeAssistant integration of the EduPage Schooling System based on the edupage_api library found here https://github.com/EdupageAPI/edupage-api

## Installation without HACS
* Extract files in /custom_components/homeassistantedupage to your installation.
* restart Home Assistant
* Add new integration and search for "Edupage"
* enter Username, Password and Subdomain (w/o ".edupage.org")
* based on your subjects you should find more or less sensors now, named bei the subject with grade-counts
* data is to be found as "attributes", see screenshot
# IMPORTANT
In this phase of development please remove integration after update and reinstall because there are major changes.

## Installation with HACS
* open HACS
Expand All @@ -17,13 +12,14 @@ An HomeAssistant integration of the EduPage Schooling System based on the edupag
* type "integration"
* add
* choose download
* please alway select at least a release with "HACS" in releasename
* please alway select the last one because its work in progress
* restart HA
* add integration
* look for "edupage"
* look for "edupage" with the nice "E" icon
* use "homeassistantedupage" integration
* enter login, password und (ONLY!) subdomain (no .edupage.com or something)
* if there are grades in your account there should spawn one ore more entities
* you should see now a lot of sensors with the subjects of your school
* grades are attributes if existing

![screenshot of sensors](./img/edupage_subjects_grades.jpg)

Expand Down
67 changes: 44 additions & 23 deletions custom_components/homeassistantedupage/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import asyncio
from datetime import timedelta
import datetime
from edupage_api.exceptions import BadCredentialsException, CaptchaException
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
Expand All @@ -13,19 +14,13 @@

async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""only ConfigEntry supported, no configuration.yaml yet"""
_LOGGER.info("INIT called async_setup")
return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""create ConfigEntry an DataUpdateCoordinator"""
_LOGGER.info("called async_setup_entry")

username = entry.data["username"]
password = entry.data["password"]
subdomain = entry.data["subdomain"]
edupage = Edupage(hass)

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""initializin EduPage-integration and validate API-login"""
_LOGGER.info("INIT called async_setup_entry")

username = entry.data["username"]
password = entry.data["password"]
subdomain = entry.data["subdomain"]
Expand All @@ -36,32 +31,57 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
login_success = await hass.async_add_executor_job(
edupage.login, username, password, subdomain
)
_LOGGER.info("INIT login_success")

except BadCredentialsException as e:
_LOGGER.error("login failed: bad credentials. %s", e)
return False # stop initialization on any exception
_LOGGER.error("INIT login failed: bad credentials. %s", e)
return False

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

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

fetch_lock = asyncio.Lock()

async def fetch_data():
"""function to fetch grade data."""
"""Function to fetch grade and timetable data."""
_LOGGER.info("INIT called fetch_data")
async with fetch_lock:

try:
# request classes
classes_data = await edupage.get_classes()
# _LOGGER.info("INIT classes count: " + str(len(classes_data)))

# request grades
grades_data = await edupage.get_grades()
_LOGGER.debug("grades_data: %s", grades_data) # Zeigt die Daten im Log
return grades_data
# _LOGGER.info("INIT grade count: " + str(len(grades_data)))

# request user_id
userid = await edupage.get_user_id()
# _LOGGER.info("INIT user_id: "+str(userid))

# request all possible subjects
subjects_data = await edupage.get_subjects()
# _LOGGER.info("INIT subject count: " + str(len(subjects_data)))

# request all possible students
students_data = await edupage.get_students()
# _LOGGER.info("INIT students count: " + str(len(students_data)))

return {
"grades": grades_data,
# "timetable": timetable_data,
"user_id": userid,
"subjects": subjects_data
}

except Exception as e:
_LOGGER.error("error fetching grades data: %s", e)
return []
_LOGGER.error("INIT error fetching data: %s", e)
return False

coordinator = DataUpdateCoordinator(
hass,
Expand All @@ -71,22 +91,23 @@ async def fetch_data():
update_interval=timedelta(minutes=5),
)

# first data fetch
# First data fetch
await asyncio.sleep(1)
await coordinator.async_config_entry_first_refresh()
#await coordinator.async_request_refresh()

# save coordinator
# Save coordinator
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator

# platforms forward
# Forward platforms
await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR])

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload ConfigEntry."""
_LOGGER.info("INIT called async_unload_entry")
unload_ok = await hass.config_entries.async_forward_entry_unload(entry, Platform.SENSOR)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
Expand Down
37 changes: 30 additions & 7 deletions custom_components/homeassistantedupage/homeassistant_edupage.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from homeassistant.helpers.update_coordinator import UpdateFailed

_LOGGER = logging.getLogger(__name__)

class Edupage:
def __init__(self,hass):
self.hass = hass
Expand All @@ -13,23 +14,45 @@ def login(self, username, password, subdomain):

return self.api.login(username, password, subdomain)

async def get_classes(self):

try:
classes_data = await self.hass.async_add_executor_job(self.api.get_classes)
return classes_data
except Exception as e:
raise UpdateFailed(F"EDUPAGE error updating get_classes() data from API: {e}")

async def get_grades(self):

try:
grades = await self.hass.async_add_executor_job(self.api.get_grades)
_LOGGER.debug("get_grades() successful from API")
return grades
except Exception as e:
raise UpdateFailed(F"error updating get_grades() data from API: {e}")
raise UpdateFailed(F"EDUPAGE error updating get_grades() data from API: {e}")

async def get_subjects(self):

try:
all_subjects = await self.hass.async_add_executor_job(self.api.get_subjects)
return all_subjects
except Exception as e:
raise UpdateFailed(F"EDUPAGE error updating get_subjects() data from API: {e}")

async def get_students(self):

try:
all_students = await self.hass.async_add_executor_job(self.api.get_students)
return all_students
except Exception as e:
raise UpdateFailed(F"EDUPAGE error updating get_students() data from API: {e}")

async def get_timetable(self, dateTT: datetime):
async def get_user_id(self):

try:
timetable = await self.hass.aync_add_executor_job(self.api.get_timetable())
_LOGGER.debug("get_timetable() successful from API")
return timetable
user_id_data = await self.hass.async_add_executor_job(self.api.get_user_id)
return user_id_data
except Exception as e:
raise UpdateFailed(F"error updating get_timetable() data from API: {e}")
raise UpdateFailed(F"EDUPAGE error updating get_user_id() data from API: {e}")

async def async_update(self):

Expand Down
58 changes: 41 additions & 17 deletions custom_components/homeassistantedupage/sensor.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,72 @@
import logging
from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from collections import defaultdict

_LOGGER = logging.getLogger("custom_components.homeassistant_edupage")

def group_grades_by_subject(grades):
"""grouping grades based on subject_id."""
grouped = defaultdict(list)
for grade in grades:
grouped[grade.subject_id].append(grade)
return grouped

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
"""Set up EduPage sensors based on subjects in the data."""
"""Set up EduPage sensors based on subjects and their grades."""
_LOGGER.info("SENSOR called async_setup_entry")

coordinator = hass.data[DOMAIN][entry.entry_id]
subjects = {}
subjects = coordinator.data.get("subjects", [])
grades = coordinator.data.get("grades", [])

# Sortiere Noten nach Fächern
for grade in coordinator.data:
subject = grade.subject_name
if subject not in subjects:
subjects[subject] = []
subjects[subject].append(grade)
# group grades based on subject_id
grades_by_subject = group_grades_by_subject(grades)

sensors = []
for subject in subjects:
# get grades per subject based on subject_id
subject_grades = grades_by_subject.get(subject.subject_id, [])
sensor = EduPageSubjectSensor(
coordinator,
subject.name,
subject_grades
)
sensors.append(sensor)

# Erstelle für jedes Fach einen Sensor
sensors = [EduPageSubjectSensor(coordinator, subject, grades) for subject, grades in subjects.items()]
async_add_entities(sensors, True)

class EduPageSubjectSensor(CoordinatorEntity, SensorEntity):
"""Sensor-Entität für ein bestimmtes Unterrichtsfach."""
"""subject sensor entity."""

def __init__(self, coordinator, subject_name, grades):
"""Initialisierung des Fach-Sensors."""
def __init__(self, coordinator, subject_name, grades=None):
"""initializing"""
super().__init__(coordinator)
self._subject_name = subject_name
self._grades = grades
self._attr_name = f"EduPage Noten - {subject_name}" # Name des Sensors basierend auf dem Fach
self._grades = grades or []
self._attr_name = f"EduPage subject - {subject_name}"
self._attr_unique_id = f"edupage_grades_{subject_name.lower().replace(' ', '_')}"

@property
def state(self):
"""Gibt die Anzahl der Noten für dieses Fach zurück."""
"""return grade count"""
return len(self._grades)

@property
def extra_state_attributes(self):
"""Rückgabe zusätzlicher Attribute für den Sensor."""
"""return additional attributes"""
if not self._grades:
return {"info": "no grades yet"}

attributes = {}
for i, grade in enumerate(self._grades):
attributes[f"grade_{i+1}_title"] = grade.title
attributes[f"grade_{i+1}_grade_n"] = grade.grade_n
attributes[f"grade_{i+1}_date"] = grade.date.strftime("%Y-%m-%d %H:%M:%S")
attributes[f"grade_{i+1}_teacher"] = grade.teacher.name
return attributes

0 comments on commit a6b5f9b

Please sign in to comment.