Skip to content

Commit a6b5f9b

Browse files
authored
Merge pull request #19 from rine77/11-feature_request-sensoren-über-stundenplan-generieren
11 feature request sensoren über stundenplan generieren
2 parents 6580797 + 184e567 commit a6b5f9b

File tree

5 files changed

+125
-58
lines changed

5 files changed

+125
-58
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,7 @@ cython_debug/
160160
#.idea/
161161

162162
# secrets
163-
*creds.sec*
163+
*creds.sec*
164+
165+
# test
166+
test/

README.md

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
# homeassistantedupage
22
An HomeAssistant integration of the EduPage Schooling System based on the edupage_api library found here https://github.com/EdupageAPI/edupage-api
33

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

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

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

custom_components/homeassistantedupage/__init__.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import asyncio
33
from datetime import timedelta
4+
import datetime
45
from edupage_api.exceptions import BadCredentialsException, CaptchaException
56
from homeassistant.config_entries import ConfigEntry
67
from homeassistant.core import HomeAssistant
@@ -13,19 +14,13 @@
1314

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

18-
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
19-
"""create ConfigEntry an DataUpdateCoordinator"""
20-
_LOGGER.info("called async_setup_entry")
21-
22-
username = entry.data["username"]
23-
password = entry.data["password"]
24-
subdomain = entry.data["subdomain"]
25-
edupage = Edupage(hass)
26-
2720
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
2821
"""initializin EduPage-integration and validate API-login"""
22+
_LOGGER.info("INIT called async_setup_entry")
23+
2924
username = entry.data["username"]
3025
password = entry.data["password"]
3126
subdomain = entry.data["subdomain"]
@@ -36,32 +31,57 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
3631
login_success = await hass.async_add_executor_job(
3732
edupage.login, username, password, subdomain
3833
)
34+
_LOGGER.info("INIT login_success")
3935

4036
except BadCredentialsException as e:
41-
_LOGGER.error("login failed: bad credentials. %s", e)
42-
return False # stop initialization on any exception
37+
_LOGGER.error("INIT login failed: bad credentials. %s", e)
38+
return False
4339

4440
except CaptchaException as e:
45-
_LOGGER.error("login failed: CAPTCHA needed. %s", e)
41+
_LOGGER.error("INIT login failed: CAPTCHA needed. %s", e)
4642
return False
4743

4844
except Exception as e:
49-
_LOGGER.error("unexpected login error: %s", e)
45+
_LOGGER.error("INIT unexpected login error: %s", e)
5046
return False
5147

5248
fetch_lock = asyncio.Lock()
5349

5450
async def fetch_data():
55-
"""function to fetch grade data."""
51+
"""Function to fetch grade and timetable data."""
52+
_LOGGER.info("INIT called fetch_data")
5653
async with fetch_lock:
57-
5854
try:
55+
# request classes
56+
classes_data = await edupage.get_classes()
57+
# _LOGGER.info("INIT classes count: " + str(len(classes_data)))
58+
59+
# request grades
5960
grades_data = await edupage.get_grades()
60-
_LOGGER.debug("grades_data: %s", grades_data) # Zeigt die Daten im Log
61-
return grades_data
61+
# _LOGGER.info("INIT grade count: " + str(len(grades_data)))
62+
63+
# request user_id
64+
userid = await edupage.get_user_id()
65+
# _LOGGER.info("INIT user_id: "+str(userid))
66+
67+
# request all possible subjects
68+
subjects_data = await edupage.get_subjects()
69+
# _LOGGER.info("INIT subject count: " + str(len(subjects_data)))
70+
71+
# request all possible students
72+
students_data = await edupage.get_students()
73+
# _LOGGER.info("INIT students count: " + str(len(students_data)))
74+
75+
return {
76+
"grades": grades_data,
77+
# "timetable": timetable_data,
78+
"user_id": userid,
79+
"subjects": subjects_data
80+
}
81+
6282
except Exception as e:
63-
_LOGGER.error("error fetching grades data: %s", e)
64-
return []
83+
_LOGGER.error("INIT error fetching data: %s", e)
84+
return False
6585

6686
coordinator = DataUpdateCoordinator(
6787
hass,
@@ -71,22 +91,23 @@ async def fetch_data():
7191
update_interval=timedelta(minutes=5),
7292
)
7393

74-
# first data fetch
94+
# First data fetch
7595
await asyncio.sleep(1)
7696
await coordinator.async_config_entry_first_refresh()
77-
#await coordinator.async_request_refresh()
7897

79-
# save coordinator
98+
# Save coordinator
8099
hass.data.setdefault(DOMAIN, {})
81100
hass.data[DOMAIN][entry.entry_id] = coordinator
82101

83-
# platforms forward
102+
# Forward platforms
84103
await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR])
85104

86105
return True
87106

107+
88108
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
89109
"""Unload ConfigEntry."""
110+
_LOGGER.info("INIT called async_unload_entry")
90111
unload_ok = await hass.config_entries.async_forward_entry_unload(entry, Platform.SENSOR)
91112
if unload_ok:
92113
hass.data[DOMAIN].pop(entry.entry_id)

custom_components/homeassistantedupage/homeassistant_edupage.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from homeassistant.helpers.update_coordinator import UpdateFailed
55

66
_LOGGER = logging.getLogger(__name__)
7+
78
class Edupage:
89
def __init__(self,hass):
910
self.hass = hass
@@ -13,23 +14,45 @@ def login(self, username, password, subdomain):
1314

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

17+
async def get_classes(self):
18+
19+
try:
20+
classes_data = await self.hass.async_add_executor_job(self.api.get_classes)
21+
return classes_data
22+
except Exception as e:
23+
raise UpdateFailed(F"EDUPAGE error updating get_classes() data from API: {e}")
24+
1625
async def get_grades(self):
1726

1827
try:
1928
grades = await self.hass.async_add_executor_job(self.api.get_grades)
20-
_LOGGER.debug("get_grades() successful from API")
2129
return grades
2230
except Exception as e:
23-
raise UpdateFailed(F"error updating get_grades() data from API: {e}")
31+
raise UpdateFailed(F"EDUPAGE error updating get_grades() data from API: {e}")
32+
33+
async def get_subjects(self):
34+
35+
try:
36+
all_subjects = await self.hass.async_add_executor_job(self.api.get_subjects)
37+
return all_subjects
38+
except Exception as e:
39+
raise UpdateFailed(F"EDUPAGE error updating get_subjects() data from API: {e}")
40+
41+
async def get_students(self):
42+
43+
try:
44+
all_students = await self.hass.async_add_executor_job(self.api.get_students)
45+
return all_students
46+
except Exception as e:
47+
raise UpdateFailed(F"EDUPAGE error updating get_students() data from API: {e}")
2448

25-
async def get_timetable(self, dateTT: datetime):
49+
async def get_user_id(self):
2650

2751
try:
28-
timetable = await self.hass.aync_add_executor_job(self.api.get_timetable())
29-
_LOGGER.debug("get_timetable() successful from API")
30-
return timetable
52+
user_id_data = await self.hass.async_add_executor_job(self.api.get_user_id)
53+
return user_id_data
3154
except Exception as e:
32-
raise UpdateFailed(F"error updating get_timetable() data from API: {e}")
55+
raise UpdateFailed(F"EDUPAGE error updating get_user_id() data from API: {e}")
3356

3457
async def async_update(self):
3558

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,72 @@
1+
import logging
12
from homeassistant.components.sensor import SensorEntity
23
from homeassistant.config_entries import ConfigEntry
34
from homeassistant.core import HomeAssistant
45
from homeassistant.helpers.entity_platform import AddEntitiesCallback
56
from homeassistant.helpers.update_coordinator import CoordinatorEntity
67
from .const import DOMAIN
8+
from collections import defaultdict
9+
10+
_LOGGER = logging.getLogger("custom_components.homeassistant_edupage")
11+
12+
def group_grades_by_subject(grades):
13+
"""grouping grades based on subject_id."""
14+
grouped = defaultdict(list)
15+
for grade in grades:
16+
grouped[grade.subject_id].append(grade)
17+
return grouped
718

819
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
9-
"""Set up EduPage sensors based on subjects in the data."""
20+
"""Set up EduPage sensors based on subjects and their grades."""
21+
_LOGGER.info("SENSOR called async_setup_entry")
22+
1023
coordinator = hass.data[DOMAIN][entry.entry_id]
11-
subjects = {}
24+
subjects = coordinator.data.get("subjects", [])
25+
grades = coordinator.data.get("grades", [])
1226

13-
# Sortiere Noten nach Fächern
14-
for grade in coordinator.data:
15-
subject = grade.subject_name
16-
if subject not in subjects:
17-
subjects[subject] = []
18-
subjects[subject].append(grade)
27+
# group grades based on subject_id
28+
grades_by_subject = group_grades_by_subject(grades)
29+
30+
sensors = []
31+
for subject in subjects:
32+
# get grades per subject based on subject_id
33+
subject_grades = grades_by_subject.get(subject.subject_id, [])
34+
sensor = EduPageSubjectSensor(
35+
coordinator,
36+
subject.name,
37+
subject_grades
38+
)
39+
sensors.append(sensor)
1940

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

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

27-
def __init__(self, coordinator, subject_name, grades):
28-
"""Initialisierung des Fach-Sensors."""
46+
def __init__(self, coordinator, subject_name, grades=None):
47+
"""initializing"""
2948
super().__init__(coordinator)
3049
self._subject_name = subject_name
31-
self._grades = grades
32-
self._attr_name = f"EduPage Noten - {subject_name}" # Name des Sensors basierend auf dem Fach
50+
self._grades = grades or []
51+
self._attr_name = f"EduPage subject - {subject_name}"
3352
self._attr_unique_id = f"edupage_grades_{subject_name.lower().replace(' ', '_')}"
3453

3554
@property
3655
def state(self):
37-
"""Gibt die Anzahl der Noten für dieses Fach zurück."""
56+
"""return grade count"""
3857
return len(self._grades)
3958

4059
@property
4160
def extra_state_attributes(self):
42-
"""Rückgabe zusätzlicher Attribute für den Sensor."""
61+
"""return additional attributes"""
62+
if not self._grades:
63+
return {"info": "no grades yet"}
64+
4365
attributes = {}
4466
for i, grade in enumerate(self._grades):
4567
attributes[f"grade_{i+1}_title"] = grade.title
4668
attributes[f"grade_{i+1}_grade_n"] = grade.grade_n
4769
attributes[f"grade_{i+1}_date"] = grade.date.strftime("%Y-%m-%d %H:%M:%S")
70+
attributes[f"grade_{i+1}_teacher"] = grade.teacher.name
4871
return attributes
72+

0 commit comments

Comments
 (0)