Je prĂ©viens tout de suite, ce Retex n’est qu’Ă vocation pĂ©dagogique, je le rĂ©dige pour partager une mĂ©thodologie, Ă savoir que j’ai rĂ©alisĂ© cette fonctionnalitĂ© diffĂ©remment depuis, donc je n’ai pas ce process en prod.
L’intĂ©gration pour exĂ©cuter le script Python
Pour une raison que je n’ai jamais compris, les fonctionnalitĂ©s scripts python intĂ©grĂ©es Ă HA sont archi-mini, aucun import n’est possible, je dois donc utiliser une intĂ©gration appelĂ©e pyscript.
Je passe les dĂ©tails pour l’installer, c’est très simple avec HACS et tout est dans la doc.
Dans configuration.yaml, j’ai bien ces lignes :
pyscript: allow_all_imports: true hass_is_global: true
Je récupère un jeton
J’ouvre l’interface utilisateur de Home Assistant.
Dans le coin infĂ©rieur gauche, je clique sur mon nom d’utilisateur (ou l’icĂ´ne du profil).
Je clique sur “Profil”.
Je fais dĂ©filer vers le bas jusqu’Ă la section “Jetons d’accès longue durĂ©e”.Â
Je clique sur “CrĂ©er un jeton” pour gĂ©nĂ©rer un nouveau jeton.
Je donne un nom Ă mon jeton pour m’y retrouver plus facilement.
Une fois le jeton créé, je vois son contenu. Je copie la valeur du jeton, c’est-Ă -dire la sĂ©rie de caractères alphanumĂ©riques, et je la colle dans ma configuration (paragraphe suivant)
Déclaration de la plateforme qui fait le lien entre Python et le sensor
Dans configuration.yaml, j’ajoute ces lignes :
rest_command: send_data_to_api: url: 'http://homeassistant.local:8123/api/states/sensor.wallbox_charges' method: 'post' content_type: 'application/json' headers: Authorization: 'Bearer TOKEN' # Remplacer TOKEN par le token cf paragraphe précédent payload: '{{ data | tojson }}'
Il s’agit de la porte d’entrĂ©e qui sera approvisionnĂ© par le script Python (plus bas) et qui enverra les informations dans le sensor sensor.wallbox_charges.
Mon script Python
Je place le fichier wallbox_charges.py dans le dossier /config/pyscript
import datetime import pytz @service def wallbox_charges(lastdata=None, action=None, prix="123", km="123", dateDebut="01/01/2000 12:00:00", valeur="123"): fuseau_horaire_paris = pytz.timezone("Europe/Paris") maintenant_timestamp = int(datetime.datetime.now().timestamp()) maintenant_heure_paris = datetime.datetime.fromtimestamp(maintenant_timestamp, fuseau_horaire_paris) maintenant_formatee = maintenant_heure_paris.strftime("%d/%m/%Y %H:%M:%S") maintenantMoins20min = datetime.datetime.now() - datetime.timedelta(minutes=20) maintenantMoins20min_formatee = maintenantMoins20min.strftime("%H:%M") dateDebutRecue = datetime.datetime.strptime(dateDebut, '%d/%m/%Y %H:%M:%S').replace(tzinfo=fuseau_horaire_paris) debut_timestamp_formatee = dateDebutRecue.strftime("%d/%m/%Y %H:%M:%S") debut_timestamp_formatee_SansSecondes = dateDebutRecue.strftime("%d/%m/%Y %H:%M") # Transformez les chaînes en objets datetime maintenant_dt = datetime.datetime.strptime(maintenant_formatee, "%d/%m/%Y %H:%M:%S") debut_dt = datetime.datetime.strptime(debut_timestamp_formatee, "%d/%m/%Y %H:%M:%S") # Calculez la différence entre les objets datetime tempsDeCharge = maintenant_dt - debut_dt - datetime.timedelta(minutes=20) # Formatez la durée en heures, minutes et secondes heures, reste = divmod(tempsDeCharge.seconds, 3600) minutes, secondes = divmod(reste, 60) # Affichez la durée au format HH:MM:SS tempsDeCharge_formatee = f"{heures:02}:{minutes:02}" # Écrivez un message d'information dans le journal #log.info(action) if action == "enregistrer": valeuraajouter = [{ "saved_at": maintenant_timestamp, "début": debut_timestamp_formatee_SansSecondes, "fin": maintenantMoins20min_formatee, "durée": tempsDeCharge_formatee, "consommation": valeur, "km": km, "coût": round(valeur * prix, 2) }] if lastdata is not None: valeuraajouter = lastdata + valeuraajouter data = { "state": debut_timestamp_formatee_SansSecondes, "attributes": { "updated_at": maintenant_dt.strftime("%d/%m/%Y %H:%M:%S"), "sessionscharge": valeuraajouter } } if action == "init": data = { "state": "on", "attributes": { "updated_at": "14/10/2023 16:42:39", "sessionscharge": [ { "consommation": 2.14, "coût": 0.43, "durée": "00:27", "début": "19/09/2023 20:36", "fin": "21:03", "km": 17, "saved_at": 1695150359 }, { "consommation": 23.29, "coût": 4.66, "durée": "04:19", "début": "21/09/2023 21:06", "fin": "01:25", "km": 192, "saved_at": 1695323159 }, { "consommation": 7.68, "coût": 1.54, "durée": "01:23", "début": "22/09/2023 08:45", "fin": "10:08", "km": 63, "saved_at": 1695409559 }, { "consommation": 23.80, "coût": 4.76, "durée": "04:26", "début": "23/09/2023 16:57", "fin": "21:23", "km": 196, "saved_at": 1695495959 } ] } } # Appelez le service REST pour envoyer les données service_data = { "token": "xxxxxx", # Remplacer par le token "data": data } if action == "enregistrer" or action == "init": # Appelez le service REST de manière asynchrone await hass.services.async_call('rest_command', 'send_data_to_api', service_data) # Écrivez un message d'erreur dans le journal #log.error("Fin script")
Ajouter deux automatisations
Une pour initialiser le sensor, l’autre pour ajouter une donnĂ©e
Initialiser les données
alias: Initialise les sessions de charge description: "" trigger: [] condition: [] action: - service: pyscript.wallbox_charges data: action: init mode: single
Enregistrer les données
alias: Enregistre une session de charge description: >- Cette automatisation se déclenche lorsque le capteur n'a pas changé de valeur depuis 20 minutes et n'est pas à 0 trigger: - platform: state entity_id: - sensor.wallbox_portal_added_range for: hours: 0 minutes: 20 seconds: 0 enabled: true condition: - condition: numeric_state entity_id: sensor.wallbox_portal_added_energy above: 0 - condition: not conditions: - condition: state entity_id: sensor.wallbox_charges state: >- {{ as_timestamp(states('sensor.wallbox_portal_added_range_min')) | int | timestamp_custom('%d/%m/%Y %H:%M:%S', true)}} enabled: false action: - service: pyscript.wallbox_charges data: action: enregistrer valeur: "{{states('sensor.wallbox_portal_added_energy')}}" prix: "{{states('sensor.wallbox_portal_energy_price')}}" km: "{{states('sensor.wallbox_portal_added_range')}}" lastdata: "{{state_attr('sensor.wallbox_charges', 'sessionscharge')}}" dateDebut: >- {{ as_timestamp(states('sensor.wallbox_portal_added_range_min')) | int | timestamp_custom('%d/%m/%Y %H:%M:%S', true)}} - service: variable.update_sensor data: replace_attributes: false value: >- {{ as_timestamp(states('sensor.wallbox_portal_added_range_min')) | int | timestamp_custom('%d/%m/%Y %H:%M:%S', true)}} target: entity_id: sensor.wallbox_charges enabled: false mode: single
Pour avoir la valeur minimal de la charge, j’utilise statistic pour avoir la valeur min
Ce sensor est déclaré dans sensors.yaml
- platform: statistics name: "Wallbox Portal Added Range Min" entity_id: sensor.wallbox_portal_added_range state_characteristic: datetime_value_min max_age: hours: 10
Résultat du stockage des infos
Carte d’affichage de toutes les sessions
<div>Sessions de charge</div> Dernière mise à jour : {{ state_attr('sensor.wallbox_charges','updated_at')}} <br><br> {% set items = state_attr('sensor.wallbox_charges', 'sessionscharge') %} <table width='100%'> <tbody> {% for i in range(0, items | count, 1) %} <tr> <td>{{ items[i].début }} à {{ items[i].fin }} ({{ items[i].durée }})</td> <td>{{ items[i].consommation }} kWh</td> <td>+{{ items[i].km }} km</td> <td>{{ items[i].coût }} €</td> </tr> {% endfor %} </tbody> </table>
Carte d’affichage avec des totaux par annĂ©e, mois, semaine
type: markdown content: >- <div><h1>Sessions de charge</div> {%- set default_language = 'fr' %} Dernière mise Ă jour : {{ state_attr('sensor.wallbox_charges','updated_at')}} <br><br> {% set items = state_attr('sensor.wallbox_charges', 'sessionscharge') %} {% set septjours = now() - timedelta(days=7) %} {% set power_semaine = namespace(value=0) %} {% set cout_semaine = namespace(value=0) %} {% set unmois = now() - timedelta(days=30) %} {% set power_mois = namespace(value=0) %} {% set cout_mois = namespace(value=0) %} {% set uneannee = now() - timedelta(days=365) %} {% set power_annee = namespace(value=0) %} {% set cout_annee = namespace(value=0) %} {% for i in range(0, items | count, 1) %} {% if items[i].saved_at | int >= as_timestamp(uneannee) %} {% set power_annee.value = power_annee.value + items[i].consommation %} {% set cout_annee.value = cout_annee.value + items[i].coĂ»t %} {% if items[i].saved_at | int >= as_timestamp(unmois) %} {% set power_mois.value = power_mois.value + items[i].consommation %} {% set cout_mois.value = cout_mois.value + items[i].coĂ»t %} {% if items[i].saved_at | int >= as_timestamp(septjours) %} {% set power_semaine.value = power_semaine.value + items[i].consommation %} {% set cout_semaine.value = cout_semaine.value + items[i].coĂ»t %} {% endif %} {% endif %} {% endif %} {% endfor %} </ha-alert> <ha-alert alert-type="info" title="Dernier mois : {{ power_mois.value | int }} kWh - {{ cout_mois.value | float | round(2) }} €"></ha-alert> <ha-alert alert-type="info" title="Dernière annĂ©e : {{ power_annee.value | int }} kWh - {{ cout_annee.value | float | round(2) }} €"></ha-alert> <br> {% set items = state_attr('sensor.wallbox_charges', 'sessionscharge') %} {% set septjours = now() - timedelta(days=7) %} <table width='100%'> <tbody> {% for i in range(0, items | count, 1) %} {% if items[i].saved_at | int >= as_timestamp(septjours) %} {% set datetime_obj = strptime(items[i].dĂ©but, '%d/%m/%Y %H:%M') %} <tr> <td><h5>{{items[i].dĂ©but }} Ă {{ items[i].fin }} ({{ items[i].durĂ©e }})</td> <td>{{ items[i].consommation }} kWh</td> <td>+{{ items[i].km }} km</td> <td>{{ items[i].coĂ»t }} €</td> </tr> {% endif %} {% endfor %} </tbody> </table> <ha-alert alert-type="success" title="Dernière semaine : {{ power_semaine.value | int}} kWh - {{ cout_semaine.value | float | round(2) }}€"> style: .: | ha-card { margin: 0px 0px 0px 0px; box-shadow: none; } ha-markdown: $: | h5 { font-size: 15px !important; font-weight: normal !important; padding: 0px 0px 0px 8px !important; border-left: 3px solid #429f46; }
Â