From b254c205da3b610449aefd3632f99dd97d81b508 Mon Sep 17 00:00:00 2001 From: Giga77 <2777446+Giga77@users.noreply.github.com> Date: Fri, 22 Mar 2024 18:17:06 +0100 Subject: [PATCH] use same syntax as Pronote to use Pronote cards use same syntax as Pronote to use Pronote cards use snake_case add comment to class, methods, etc. fix homeworks --- README.md | 53 ++- custom_components/ecole_directe/__init__.py | 9 +- .../ecole_directe/config_flow.py | 4 +- custom_components/ecole_directe/const.py | 3 +- .../ecole_directe/coordinator.py | 72 ++-- ...ecte_helper.py => ecole_directe_helper.py} | 317 +++++++++++------- custom_components/ecole_directe/sensor.py | 85 +++-- 7 files changed, 339 insertions(+), 204 deletions(-) rename custom_components/ecole_directe/{ecoleDirecte_helper.py => ecole_directe_helper.py} (51%) diff --git a/README.md b/README.md index 4a72d57..bee82bb 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,54 @@ -# Ecole directe integration for Home Assistant +# Integration Ecole directe pour Home Assistant -![Version](https://img.shields.io/github/v/release/hacf-fr/hass-ecoledirecte?label=version) +![Version](https://img.shields.io/github/v/release/hacf-fr/hass-ecoledirecte?label=version) [![HACS: Custom](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs) -### Manual install +- [Installation](#Installation) + - [Installation via l'interface utilisateur via HACS](#Installation-via-l'interface-utilisateur-via-HACS) + - [Installation manuelle](<#Installation-manuelle>) +- [Configuration](#Configuration) +- [Utilisation](#Utilisation) -Copy the `ecole_directe` folder from latest release to the `custom_components` folder in your `config` folder. -Restart Home Assistant + +## Installation + + +### Installation via l'interface utilisateur via HACS + +1. Depuis [HACS](https://hacs.xyz/) (Home Assistant Community Store), sélectionner `Intégrations`. Puis ouvrez le menu en haut à droite et utiliser l'option `Dépôts personnalisés` pour ajouter le dépôt de l'intégration. + +2. Ajoutez l'adresse avec pour catégorie `Intégration`, et faire `AJOUTER`. Le dépôt apparaît dans la liste. + +3. La carte de ce `nouveau dépôt` va s'afficher, cliquez sur celle-ci puis `Télécharger` en bas à droite. + +4. Laisser le choix de la dernière version et utiliser l'option `Télécharger`. + +5. Il faut ensuite redémarrer Home Assistant. + + +### Installation manuelle +Copier le répertoire ecole_directe de la dernière release dans le répertoire custom_components de votre répertoire config. Redémarrer Home Assistant ## Configuration -Click on the following button: +Cliquer sur ce boutton: [![Open your Home Assistant instance and start setting up a new integration of a specific brand.](https://my.home-assistant.io/badges/brand.svg)](https://my.home-assistant.io/redirect/brand/?brand=ecole_directe) -Or go to : -Settings > Devices & Sevices > Integrations > Add Integration, and search for "Ecole Directe" - -Use your username and password : +Ou aller dans : +Paramètres > Appareils et services > Intégrations > Ajouter une intégration, et chercher "Ecole Directe" +Utiliser votre identifiant et mot de passe password : ![Ecole directe config flow](doc/config_flow_username_password.png) -## Usage +## Utilisation -This integration provides several sensors, always prefixed with `ecole_directe_FIRSTNAME_LASTNAME` (where `FIRSTNAME` and `LASTNAME` are replaced). +Cette intégration fournit plusieurs entités, toujours prefixées avec `ecole_directe_PRENOM_NOM` (où `PRENOM` et `NOM` sont remplacé). | Sensor | Description | |--------|-------------| -| `sensor.ecole_directe_FIRSTNAME_LASTNAME` | basic informations about your child | -| `[...]_homework` | homework | -| `[...]_evaluations` | evaluations | +| `sensor.ecole_directe_PRENOM_NOM` | informations basique de l'enfant | +| `[...]_homework` | devoirs | +| `[...]_grades` | notes | + +Les entités sont mises à jour toutes les 30 minutes. -The sensors are updated every 30 minutes. diff --git a/custom_components/ecole_directe/__init__.py b/custom_components/ecole_directe/__init__.py index c9cd20f..c37d8f7 100644 --- a/custom_components/ecole_directe/__init__.py +++ b/custom_components/ecole_directe/__init__.py @@ -2,16 +2,15 @@ from __future__ import annotations -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady +import logging from datetime import timedelta -import logging +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady from .coordinator import EDDataUpdateCoordinator - from .const import DEFAULT_REFRESH_INTERVAL, DOMAIN, PLATFORMS _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/ecole_directe/config_flow.py b/custom_components/ecole_directe/config_flow.py index befea66..ec9cdf2 100644 --- a/custom_components/ecole_directe/config_flow.py +++ b/custom_components/ecole_directe/config_flow.py @@ -12,7 +12,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.core import callback -from .ecoleDirecte_helper import ( +from .ecole_directe_helper import ( get_ecoledirecte_session, ) @@ -83,6 +83,8 @@ class InvalidAuth(HomeAssistantError): class OptionsFlowHandler(config_entries.OptionsFlow): + """Configuration of integration options""" + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: """Initialize options flow.""" self.config_entry = config_entry diff --git a/custom_components/ecole_directe/const.py b/custom_components/ecole_directe/const.py index 5ba35ef..6b7cc48 100644 --- a/custom_components/ecole_directe/const.py +++ b/custom_components/ecole_directe/const.py @@ -7,4 +7,5 @@ # default values for options DEFAULT_REFRESH_INTERVAL = 30 -NOTES_TO_DISPLAY = 15 +GRADES_TO_DISPLAY = 15 +HOMEWORK_DESC_MAX_LENGTH = 125 diff --git a/custom_components/ecole_directe/coordinator.py b/custom_components/ecole_directe/coordinator.py index 0bdcadf..5f264b9 100644 --- a/custom_components/ecole_directe/coordinator.py +++ b/custom_components/ecole_directe/coordinator.py @@ -12,7 +12,12 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import TimestampDataUpdateCoordinator -from .ecoleDirecte_helper import get_ecoledirecte_session, getDevoirs, getNotes +from .ecole_directe_helper import ( + get_ecoledirecte_session, + get_homeworks, + get_grades, + get_homeworks_by_date, +) from .const import ( DEFAULT_REFRESH_INTERVAL, @@ -52,45 +57,70 @@ async def _async_update_data(self) -> dict[Platform, dict[str, Any]]: self.data = {} self.data["session"] = session - currentYear = datetime.datetime.now().year - if (currentYear % 2) == 0: - yearData = f"{str(currentYear-1)}-{str(currentYear)}" + current_year = datetime.datetime.now().year + if (current_year % 2) == 0: + year_data = f"{str(current_year-1)}-{str(current_year)}" else: - yearData = f"{str(currentYear)}-{str(currentYear + 1)}" + year_data = f"{str(current_year)}-{str(current_year + 1)}" - # if (session.typeCompte == '1'): # famille + # if session._account_type == "1": # famille # if "MESSAGERIE" in session.modules: # try: - # self.data['messages'] = await self.hass.async_add_executor_job(getMessages, session, None, yearData) + # self.data["messages"] = await self.hass.async_add_executor_job( + # get_messages, session, None, year_data + # ) # except Exception as ex: - # self.data['messages'] = None - # _LOGGER.warning("Error getting messages for family from ecole directe: %s", ex) + # self.data["messages"] = None + # _LOGGER.warning( + # "Error getting messages for family from ecole directe: %s", ex + # ) for eleve in session.eleves: if "CAHIER_DE_TEXTES" in eleve.modules: try: - self.data[ - "devoirs" + eleve.get_fullnameLower() - ] = await self.hass.async_add_executor_job( - getDevoirs, session, eleve + homeworks_json = await self.hass.async_add_executor_job( + get_homeworks, session, eleve ) + for key in homeworks_json.keys(): + homeworks_by_date_json = await self.hass.async_add_executor_job( + get_homeworks_by_date, session, eleve, key + ) + _LOGGER.debug( + "homeworks_by_date_json:%s", homeworks_by_date_json + ) + for matiere in homeworks_by_date_json["matieres"]: + for homework in homeworks_json[key]: + if matiere["id"] == homework["idDevoir"]: + homework["nbJourMaxRenduDevoir"] = matiere[ + "nbJourMaxRenduDevoir" + ] + homework["contenu"] = matiere["aFaire"]["contenu"] + + _LOGGER.debug("homeworks_json:%s", homeworks_json) + self.data[f"homeworks{eleve.get_fullname_lower()}"] = homeworks_json except Exception as ex: - _LOGGER.warning("Error getting devoirs from ecole directe: %s", ex) - self.data["devoirs" + eleve.get_fullnameLower()] = {} + _LOGGER.warning( + "Error getting homeworks from ecole directe: %s", ex + ) + self.data[f"homeworks{eleve.get_fullname_lower()}"] = {} if "NOTES" in eleve.modules: try: self.data[ - "notes" + eleve.get_fullnameLower() + f"grades{eleve.get_fullname_lower()}" ] = await self.hass.async_add_executor_job( - getNotes, session, eleve, yearData + get_grades, session, eleve, year_data ) except Exception as ex: - _LOGGER.warning("Error getting notes from ecole directe: %s", ex) - self.data["notes" + eleve.get_fullnameLower()] = {} + _LOGGER.warning("Error getting grades from ecole directe: %s", ex) + self.data[f"grades{eleve.get_fullname_lower()}"] = {} # if "MESSAGERIE" in eleve.modules: # try: - # self.data['messages'+ eleve.eleve_id] = await self.hass.async_add_executor_job(getMessages, session, eleve, yearData) + # self.data[ + # "messages" + eleve.eleve_id + # ] = await self.hass.async_add_executor_job( + # get_messages, session, eleve, year_data + # ) # except Exception as ex: - # _LOGGER.warning("Error getting notes from ecole directe: %s", ex) + # _LOGGER.warning("Error getting messages from ecole directe: %s", ex) return self.data diff --git a/custom_components/ecole_directe/ecoleDirecte_helper.py b/custom_components/ecole_directe/ecole_directe_helper.py similarity index 51% rename from custom_components/ecole_directe/ecoleDirecte_helper.py rename to custom_components/ecole_directe/ecole_directe_helper.py index eff3117..50bd7df 100644 --- a/custom_components/ecole_directe/ecoleDirecte_helper.py +++ b/custom_components/ecole_directe/ecole_directe_helper.py @@ -1,7 +1,11 @@ +"""Module to help communication with Ecole Directe API""" + import re -import requests import logging import urllib +import requests + +from .const import HOMEWORK_DESC_MAX_LENGTH _LOGGER = logging.getLogger(__name__) @@ -9,16 +13,17 @@ APIVERSION = "4.53.0" -def encodeBody(dictionnary, isRecursive=False): +def encode_body(dictionnary, is_recursive=False): + """encode payload to send to API""" body = "" for key in dictionnary: - if isRecursive: + if is_recursive: body += '"' + key + '":' else: body += key + "=" if isinstance(dictionnary[key], dict): - body += "{" + encodeBody(dictionnary[key], True) + "}" + body += "{" + encode_body(dictionnary[key], True) + "}" else: body += '"' + urllib.parse.quote(str(dictionnary[key]), safe="") + '"' body += "," @@ -26,8 +31,9 @@ def encodeBody(dictionnary, isRecursive=False): return body[:-1] -def getResponse(session, url, payload): - if session is not None and isLogin(session): +def get_response(session, url, payload): + """send a request to API and return a json if possible or raise an error""" + if session is not None and is_login(session): token = session.token else: token = None @@ -36,43 +42,48 @@ def getResponse(session, url, payload): payload = "data={}" _LOGGER.debug("URL: [%s] - Payload: [%s]", url, payload) - response = requests.post(url, data=payload, headers=getHeaders(token), timeout=120) - - if "application/json" in response.headers.get("Content-Type", ""): - respJson = response.json() - if respJson["code"] != 200: - raise RequestError( - "Error with URL:[{}] - Code {}: {}".format( - url, respJson["code"], respJson["message"] - ) - ) - _LOGGER.debug("%s", respJson) - return respJson + response = requests.post(url, data=payload, headers=get_headers(token), timeout=120) - raise RequestError("Error with URL:[{}]: {}".format(url, response.content)) + try: + resp_json = response.json() + except Exception as ex: + raise RequestError(f"Error with URL:[{url}]: {response.content}") from ex + if "code" not in resp_json: + raise RequestError(f"Error with URL:[{url}]: json:[{resp_json}]") + if resp_json["code"] != 200: + raise RequestError( + f"Error with URL:[{url}] - Code {resp_json["code"]}: {resp_json["message"]}" + ) + _LOGGER.debug("%s", resp_json) + return resp_json class RequestError(Exception): + """Request error from API""" + def __init__(self, message): super(RequestError, self).__init__(message) -class ED_Session: +class EDSession: + """Ecole Directe session with Token""" + def __init__(self, data): self.token = data["token"] self.id = data["data"]["accounts"][0]["id"] self.identifiant = data["data"]["accounts"][0]["identifiant"] - self.idLogin = data["data"]["accounts"][0]["idLogin"] - self.typeCompte = data["data"]["accounts"][0]["typeCompte"] + self._id_login = data["data"]["accounts"][0]["idLogin"] + self._account_type = data["data"]["accounts"][0]["typeCompte"] self.modules = [] for module in data["data"]["accounts"][0]["modules"]: if module["enable"]: self.modules.append(module["code"]) self.eleves = [] - if self.typeCompte == "E": + if self._account_type == "E": self.eleves.append( - ED_Eleve( + EDEleve( None, + data["data"]["accounts"][0]["nomEtablissement"], self.id, data["data"]["accounts"][0]["prenom"], data["data"]["accounts"][0]["nom"], @@ -84,16 +95,21 @@ def __init__(self, data): else: if "eleves" in data["data"]["accounts"][0]["profile"]: for eleve in data["data"]["accounts"][0]["profile"]["eleves"]: - self.eleves.append(ED_Eleve(eleve)) + self.eleves.append( + EDEleve(eleve, data["data"]["accounts"][0]["nomEtablissement"]) + ) + +class EDEleve: + """Student information""" -class ED_Eleve: def __init__( self, data=None, - id=None, - firstName=None, - lastName=None, + establishment=None, + eleve_id=None, + first_name=None, + last_name=None, classe_id=None, classe_name=None, modules=None, @@ -101,10 +117,11 @@ def __init__( if data is None: self.classe_id = classe_id self.classe_name = classe_name - self.eleve_id = id - self.eleve_lastname = lastName - self.eleve_firstname = firstName + self.eleve_id = eleve_id + self.eleve_lastname = last_name + self.eleve_firstname = first_name self.modules = modules + self.establishment = establishment else: if "classe" in data: self.classe_id = data["classe"]["id"] @@ -112,46 +129,54 @@ def __init__( self.eleve_id = data["id"] self.eleve_lastname = data["nom"] self.eleve_firstname = data["prenom"] + self.establishment = establishment self.modules = [] for module in data["modules"]: if module["enable"]: self.modules.append(module["code"]) - def get_fullnameLower(self) -> str | None: - return f"{re.sub("[^A-Za-z]", "_", self.eleve_firstname.lower())}_{re.sub("[^A-Za-z]", "_", self.eleve_lastname.lower())}" + def get_fullname_lower(self) -> str | None: + """Student fullname lowercase""" + return f"{re.sub("[^A-Za-z]", "_", + self.eleve_firstname.lower()) + }_{ + re.sub("[^A-Za-z]", "_", self.eleve_lastname.lower())}" def get_fullname(self) -> str | None: + """Student fullname""" return f"{self.eleve_firstname} {self.eleve_lastname}" -class ED_Devoir: - def __init__(self, data, pourLe): +class EDHomework: + """Homework information""" + + def __init__(self, data, pour_le): try: if "matiere" in data: self.matiere = data["matiere"] else: self.matiere = "" if "codeMatiere" in data: - self.codeMatiere = data["codeMatiere"] + self.code_matiere = data["codeMatiere"] else: - self.codeMatiere = "" + self.code_matiere = "" if "aFaire" in data: - self.aFaire = data["aFaire"] + self.a_faire = data["aFaire"] else: - self.aFaire = "" + self.a_faire = "" if "idDevoir" in data: - self.idDevoir = data["idDevoir"] + self.id_devoir = data["idDevoir"] else: - self.idDevoir = "" + self.id_devoir = "" if "documentsAFaire" in data: - self.documentsAFaire = data["documentsAFaire"] + self.documents_a_faire = data["documentsAFaire"] else: - self.documentsAFaire = "" + self.documents_a_faire = "" if "donneLe" in data: - self.donneLe = data["donneLe"] + self.donne_le = data["donneLe"] else: - self.donneLe = "" - self.pourLe = pourLe + self.donne_le = "" + self.pour_le = pour_le if "effectue" in data: self.effectue = data["effectue"] else: @@ -161,31 +186,51 @@ def __init__(self, data, pourLe): else: self.interrogation = "" if "rendreEnLigne" in data: - self.rendreEnLigne = data["rendreEnLigne"] + self.rendre_en_ligne = data["rendreEnLigne"] + else: + self.rendre_en_ligne = "" + if "nbJourMaxRenduDevoir" in data: + self.nb_jour_max_rendu_devoir = data["nbJourMaxRenduDevoir"] else: - self.rendreEnLigne = "" + self.nb_jour_max_rendu_devoir = "" + if "contenu" in data: + self.contenu = data["contenu"] + else: + self.contenu = "" except Exception as err: _LOGGER.warning("ED_Devoir Error: [%s] - Data[%s]", err, data) - def format(self): + def format_homework(self): + """format homework""" try: return { + "date": self.pour_le, + "subject": self.matiere, + "short_description": self.contenu[0:HOMEWORK_DESC_MAX_LENGTH], + "description": self.contenu, + "done": self.effectue, + "background_color": None, + "files": None, "matiere": self.matiere, - "codeMatiere": self.codeMatiere, - "aFaire": self.aFaire, - "idDevoir": self.idDevoir, - "documentsAFaire": self.documentsAFaire, - "donneLe": self.donneLe, - "pourLe": self.pourLe, + "codeMatiere": self.code_matiere, + "aFaire": self.a_faire, + "idDevoir": self.id_devoir, + "documentsAFaire": self.documents_a_faire, + "donneLe": self.donne_le, + "pourLe": self.pour_le, "effectue": self.effectue, "interrogation": self.interrogation, - "rendreEnLigne": self.rendreEnLigne, + "rendreEnLigne": self.rendre_en_ligne, + "nbJourMaxRenduDevoir": self.nb_jour_max_rendu_devoir, + "contenu": self.contenu, } except Exception: return {} -class ED_Note: +class EDGrade: + """Grade information""" + def __init__(self, data): try: if "id" in data: @@ -197,119 +242,133 @@ def __init__(self, data): else: self.devoir = "" if "codePeriode" in data: - self.codePeriode = data["codePeriode"] + self.code_periode = data["codePeriode"] else: - self.codePeriode = "" + self.code_periode = "" if "codeMatiere" in data: - self.codeMatiere = data["codeMatiere"] + self.code_matiere = data["codeMatiere"] else: - self.codeMatiere = "" + self.code_matiere = "" if "libelleMatiere" in data: - self.libelleMatiere = data["libelleMatiere"] + self.libelle_matiere = data["libelleMatiere"] else: - self.libelleMatiere = "" + self.libelle_matiere = "" if "codeSousMatiere" in data: - self.codeSousMatiere = data["codeSousMatiere"] + self.code_sous_matiere = data["codeSousMatiere"] else: - self.codeSousMatiere = "" + self.code_sous_matiere = "" if "typeDevoir" in data: - self.typeDevoir = data["typeDevoir"] + self.type_devoir = data["typeDevoir"] else: - self.typeDevoir = "" + self.type_devoir = "" if "enLettre" in data: - self.enLettre = data["enLettre"] + self.en_lettre = data["enLettre"] else: - self.enLettre = "" + self.en_lettre = "" if "commentaire" in data: self.commentaire = data["commentaire"] else: self.commentaire = "" if "uncSujet" in data: - self.uncSujet = data["uncSujet"] + self.unc_sujet = data["uncSujet"] else: - self.uncSujet = "" + self.unc_sujet = "" if "uncCorrige" in data: - self.uncCorrige = data["uncCorrige"] + self.unc_corrige = data["uncCorrige"] else: - self.uncCorrige = "" + self.unc_corrige = "" if "coef" in data: self.coef = data["coef"] else: self.coef = "" if "noteSur" in data: - self.noteSur = data["noteSur"] + self.note_sur = data["noteSur"] else: - self.noteSur = "" + self.note_sur = "" if "valeur" in data: self.valeur = data["valeur"] else: self.valeur = "" if "nonSignificatif" in data: - self.nonSignificatif = data["nonSignificatif"] + self.non_significatif = data["nonSignificatif"] else: - self.nonSignificatif = "" + self.non_significatif = "" if "date" in data: self.date = data["date"] else: self.date = "" if "dateSaisie" in data: - self.dateSaisie = data["dateSaisie"] + self.date_saisie = data["dateSaisie"] else: - self.dateSaisie = "" + self.date_saisie = "" if "valeurisee" in data: self.valeurisee = data["valeurisee"] else: self.valeurisee = "" if "moyenneClasse" in data: - self.moyenneClasse = data["moyenneClasse"] + self.moyenne_classe = data["moyenneClasse"] else: - self.moyenneClasse = "" + self.moyenne_classe = "" if "minClasse" in data: - self.minClasse = data["minClasse"] + self.min_classe = data["minClasse"] else: - self.minClasse = "" + self.min_classe = "" if "maxClasse" in data: - self.maxClasse = data["maxClasse"] + self.max_classe = data["maxClasse"] else: - self.maxClasse = "" + self.max_classe = "" if "elementsProgramme" in data: - self.elementsProgramme = data["elementsProgramme"] + self.elements_programme = data["elementsProgramme"] else: - self.elementsProgramme = "" + self.elements_programme = "" except Exception as err: _LOGGER.warning("ED_Note error: [%s] - Data[%s]", err, data) - def format(self): + def format_grade(self): + """grade fromat""" try: return { + "date": self.date, + "subject": self.code_matiere, + "comment": self.devoir, + "grade": self.valeur, + "out_of": str(self.note_sur).replace(".", ","), + "default_out_of": str(self.note_sur).replace(".", ","), + "grade_out_of": self.valeur + "/" + self.note_sur, + "coefficient": str(self.coef).replace(".", ","), + "class_average": str(self.moyenne_classe).replace(".", ","), + "max": str(self.max_classe).replace(".", ","), + "min": str(self.min_classe).replace(".", ","), + "is_bonus": None, + "is_optionnal": None, + "is_out_of_20": None, "id": self.id, "devoir": self.devoir, - "codePeriode": self.codePeriode, - "codeMatiere": self.codeMatiere, - "libelleMatiere": self.libelleMatiere, - "codeSousMatiere": self.codeSousMatiere, - "typeDevoir": self.typeDevoir, - "enLettre": self.enLettre, + "codePeriode": self.code_periode, + "codeMatiere": self.code_matiere, + "libelleMatiere": self.libelle_matiere, + "codeSousMatiere": self.code_sous_matiere, + "typeDevoir": self.type_devoir, + "enLettre": self.en_lettre, "commentaire": self.commentaire, - "uncSujet": self.uncSujet, - "uncCorrige": self.uncCorrige, + "uncSujet": self.unc_sujet, + "uncCorrige": self.unc_corrige, "coef": self.coef, - "noteSur": self.noteSur, + "noteSur": self.note_sur, "valeur": self.valeur, - "nonSignificatif": self.nonSignificatif, - "date": self.date, - "dateSaisie": self.dateSaisie, + "nonSignificatif": self.non_significatif, + "dateSaisie": self.date_saisie, "valeurisee": self.valeurisee, - "moyenneClasse": self.moyenneClasse, - "minClasse": self.minClasse, - "maxClasse": self.maxClasse, - "elementsProgramme": self.elementsProgramme, + "moyenneClasse": self.moyenne_classe, + "minClasse": self.min_classe, + "maxClasse": self.max_classe, + "elementsProgramme": self.elements_programme, } except Exception: return {} -def get_ecoledirecte_session(data) -> ED_Session | None: +def get_ecoledirecte_session(data) -> EDSession | None: """Function connecting to Ecole Directe""" try: _LOGGER.debug( @@ -318,10 +377,10 @@ def get_ecoledirecte_session(data) -> ED_Session | None: data["password"], ) - login = getResponse( + login = get_response( None, f"{APIURL}/login.awp?v={APIVERSION}", - encodeBody( + encode_body( { "data": { "identifiant": data["username"], @@ -335,63 +394,68 @@ def get_ecoledirecte_session(data) -> ED_Session | None: "Connection OK - identifiant: [{%s}]", login["data"]["accounts"][0]["identifiant"], ) - return ED_Session(login) + return EDSession(login) except Exception as err: _LOGGER.critical(err) return None -# def getMessages(session, eleve, anneeScolaire): +# def get_messages(session, eleve, annee_scolaire): +# """Get messages from Ecole Directe""" # if eleve is None: -# return getResponse( +# return get_response( # session, # f"{APIURL}/familles/{session.id}/messages.awp?force=false&typeRecuperation=received&idClasseur=0&orderBy=date&order=desc&query=&onlyRead=&page=0&itemsPerPage=100&getAll=0&verbe=get&v={APIVERSION}", -# encodeBody({"data": {"anneeMessages": anneeScolaire}}), +# encode_body({"data": {"anneeMessages": annee_scolaire}}), # ) -# return getResponse( +# return get_response( # session, # f"{APIURL}/eleves/{eleve.eleve_id}/messages.awp?force=false&typeRecuperation=received&idClasseur=0&orderBy=date&order=desc&query=&onlyRead=&page=0&itemsPerPage=100&getAll=0&verbe=get&v={APIVERSION}", -# encodeBody({"data": {"anneeMessages": anneeScolaire}}), +# encode_body({"data": {"anneeMessages": annee_scolaire}}), # ) -def getDevoirsByDate(session, eleve, date): - json = getResponse( +def get_homeworks_by_date(session, eleve, date): + """get homeworks by date""" + json = get_response( session, f"{APIURL}/Eleves/{eleve.eleve_id}/cahierdetexte/{date}.awp?verbe=get&v={APIVERSION}", None, ) if "data" in json: return json["data"] - _LOGGER.warning("getDevoirsByDate: [%s]", json) + _LOGGER.warning("get_homeworks_by_date: [%s]", json) return None -def getDevoirs(session, eleve): - json = getResponse( +def get_homeworks(session, eleve): + """get homeworks""" + json = get_response( session, f"{APIURL}/Eleves/{eleve.eleve_id}/cahierdetexte.awp?verbe=get&v={APIVERSION}", None, ) if "data" in json: return json["data"] - _LOGGER.warning("getDevoirs: [%s]", json) + _LOGGER.warning("get_homeworks: [%s]", json) return None -def getNotes(session, eleve, anneeScolaire): - json = getResponse( +def get_grades(session, eleve, annee_scolaire): + """get grades""" + json = get_response( session, f"{APIURL}/eleves/{eleve.eleve_id}/notes.awp?verbe=get&v={APIVERSION}", - encodeBody({"data": {"anneeScolaire": anneeScolaire}}), + encode_body({"data": {"anneeScolaire": annee_scolaire}}), ) if "data" in json: return json["data"] - _LOGGER.warning("getNotes: [%s]", json) + _LOGGER.warning("get_grades: [%s]", json) return None -def getHeaders(token): +def get_headers(token): + """return headers needed from Ecole Directe API""" headers = { "Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate, br", @@ -414,7 +478,8 @@ def getHeaders(token): return headers -def isLogin(session): +def is_login(session): + """Ckeck valid login""" if session.token is not None and session.id is not None: return True return False diff --git a/custom_components/ecole_directe/sensor.py b/custom_components/ecole_directe/sensor.py index 7bcddf3..6f42418 100644 --- a/custom_components/ecole_directe/sensor.py +++ b/custom_components/ecole_directe/sensor.py @@ -1,3 +1,5 @@ +"""Module providing sensors to Home Assistant.""" + import logging from homeassistant.core import HomeAssistant @@ -12,11 +14,11 @@ CoordinatorEntity, ) -from .ecoleDirecte_helper import ED_Eleve, ED_Devoir, ED_Note +from .ecole_directe_helper import EDEleve, EDHomework, EDGrade from .coordinator import EDDataUpdateCoordinator from .const import ( DOMAIN, - NOTES_TO_DISPLAY, + GRADES_TO_DISPLAY, ) _LOGGER = logging.getLogger(__name__) @@ -27,6 +29,8 @@ async def async_setup_entry( config_entry: ConfigEntry, async_add_entities: AddEntitiesCallback, ) -> None: + """Set up a bridge from a config entry.""" + coordinator: EDDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id][ "coordinator" ] @@ -36,9 +40,9 @@ async def async_setup_entry( for eleve in coordinator.data["session"].eleves: sensors.append(EDChildSensor(coordinator, eleve)) if "CAHIER_DE_TEXTES" in eleve.modules: - sensors.append(EDDevoirsSensor(coordinator, eleve)) + sensors.append(EDHomeworksSensor(coordinator, eleve)) if "NOTES" in eleve.modules: - sensors.append(EDNotesSensor(coordinator, eleve)) + sensors.append(EDGradesSensor(coordinator, eleve)) async_add_entities(sensors, False) @@ -50,7 +54,7 @@ def __init__( self, coordinator, name: str, - eleve: ED_Eleve = None, + eleve: EDEleve = None, state: str = None, device_class: str = None, ) -> None: @@ -68,7 +72,7 @@ def __init__( entry_type=DeviceEntryType.SERVICE, identifiers={(DOMAIN, f"ED - {identifiant}")}, manufacturer="Ecole Directe", - model=str(f"ED - {identifiant}"), + model=f"ED - {identifiant}", ) if device_class is not None: @@ -106,10 +110,11 @@ def available(self) -> bool: class EDChildSensor(EDGenericSensor): """Representation of a ED child sensor.""" - def __init__(self, coordinator: EDDataUpdateCoordinator, eleve: ED_Eleve) -> None: + def __init__(self, coordinator: EDDataUpdateCoordinator, eleve: EDEleve) -> None: """Initialize the ED sensor.""" super().__init__(coordinator, eleve.get_fullname(), eleve, "len") - self._attr_unique_id = f"ed_{eleve.get_fullnameLower()}_{eleve.eleve_id}]" + self._attr_unique_id = f"ed_{eleve.get_fullname_lower()}_{eleve.eleve_id}]" + self._account_type = self.coordinator.data["session"]._account_type @property def name(self): @@ -125,8 +130,12 @@ def native_value(self): def extra_state_attributes(self): """Return the state attributes.""" return { + "firstname": self._child_info.eleve_firstname, + "lastname": self._child_info.eleve_lastname, "full_name": self._child_info.get_fullname(), "class_name": self._child_info.classe_name, + "establishment": self._child_info.establishment, + "via_parent_account": self._account_type == "1", "updated_at": self.coordinator.last_update_success_time, } @@ -136,13 +145,16 @@ def available(self) -> bool: return self.coordinator.last_update_success -class EDDevoirsSensor(EDGenericSensor): +class EDHomeworksSensor(EDGenericSensor): """Representation of a ED sensor.""" - def __init__(self, coordinator: EDDataUpdateCoordinator, eleve: ED_Eleve) -> None: + def __init__(self, coordinator: EDDataUpdateCoordinator, eleve: EDEleve) -> None: """Initialize the ED sensor.""" super().__init__( - coordinator, "devoirs" + eleve.get_fullnameLower(), eleve, "len" + coordinator, + f"homeworks{self._child_info.get_fullname_lower()}", + eleve, + "len", ) @property @@ -150,54 +162,59 @@ def extra_state_attributes(self): """Return the state attributes.""" attributes = [] todo_counter = 0 - if f"devoirs{self._child_info.get_fullnameLower()}" in self.coordinator.data: + if f"homeworks{self._child_info.get_fullname_lower()}" in self.coordinator.data: json = self.coordinator.data[ - f"devoirs{self._child_info.get_fullnameLower()}" + f"homeworks{self._child_info.get_fullname_lower()}" ] - _LOGGER.debug("EDDevoirsSensor attributes json: [%s]", json) - for date in json: - for devoirs in date: - for devoir_json in devoirs: - devoir = ED_Devoir(devoir_json, date) - if devoir is not None: - attributes.append(devoir.format()) - if devoir.effectue is False: + _LOGGER.debug("EDHomeworksSensor attributes json: [%s]", json) + for key in json.keys(): + for homeworks in json[key]: + for homework_json in homeworks: + homework = EDHomework(homework_json, key) + if homework is not None: + attributes.append(homework.format_homework()) + if homework.effectue is False: todo_counter += 1 else: attributes.append( { - "Erreur": f"devoirs{self._child_info.get_fullnameLower()} n'existe pas." + "Erreur": f"homeworks{self._child_info.get_fullname_lower()} n'existe pas." } ) + attributes.sort(key="pour_le") + return { "updated_at": self.coordinator.last_update_success_time, - "devoirs": attributes, + "homeworks": attributes, "todo_counter": todo_counter, } -class EDNotesSensor(EDGenericSensor): +class EDGradesSensor(EDGenericSensor): """Representation of a ED sensor.""" - def __init__(self, coordinator: EDDataUpdateCoordinator, eleve: ED_Eleve) -> None: + def __init__(self, coordinator: EDDataUpdateCoordinator, eleve: EDEleve) -> None: """Initialize the ED sensor.""" - super().__init__(coordinator, "notes" + eleve.get_fullnameLower(), eleve, "len") + super().__init__( + coordinator, f"grades{self._child_info.get_fullname_lower()}", eleve, "len" + ) @property def extra_state_attributes(self): """Return the state attributes.""" attributes = [] - json = self.coordinator.data["notes" + self._child_info.get_fullnameLower()] - index_note = 0 + json = self.coordinator.data[f"grades{self._child_info.get_fullname_lower()}"] + index = 0 if json is not None and "notes" in json: - _LOGGER.debug("EDNotesSensor attributes json: [%s]", json) - for note in json["notes"]: - index_note += 1 - if index_note == NOTES_TO_DISPLAY: + _LOGGER.debug("EDGradesSensor attributes json: [%s]", json) + grades = sorted(json["notes"], key=lambda grade: grade.date, reverse=True) + for grade_json in grades: + index += 1 + if index == GRADES_TO_DISPLAY: break - n = ED_Note(note) - attributes.append(n.format()) + grade = EDGrade(grade_json) + attributes.append(grade.format_grade()) return { "updated_at": self.coordinator.last_update_success_time,