Hello ca fonctionne
tuto :
creation du fichier ecodevices_reboot.py dans le repertoire config de home assistant :
import json
import re
import sys
import base64
import urllib.parse
import urllib.request
from pathlib import Path
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.padding import PKCS7
SECRETS_FILE = Path("/config/secrets.yaml")
def read_secret(name):
text = SECRETS_FILE.read_text(encoding="utf-8")
match = re.search(
r"(?m)^" + re.escape(name) + r"\s*:\s*(.*?)\s*$",
text,
)
if not match:
raise RuntimeError(f"Clé absente de secrets.yaml : {name}")
value = match.group(1).strip()
if (
len(value) >= 2
and value[0] == value[-1]
and value[0] in "'\""
):
value = value[1:-1]
return value
def post(base_url, path, data):
request = urllib.request.Request(
base_url.rstrip("/") + path,
data=json.dumps(
data,
ensure_ascii=False,
).encode("utf-8"),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(request, timeout=10) as response:
body = response.read().decode("utf-8")
if not body.strip():
return {}
return json.loads(body)
def authenticate(base_url, login, password):
encoded_login = urllib.parse.quote(login, safe="")
# Étape 1 : récupération de la clé de chiffrement
challenge = post(
base_url,
f"/api/auth/secure/{encoded_login}",
{},
)
secure_key = challenge["secureKey"].encode("utf-8")
if len(secure_key) not in (16, 24, 32):
raise RuntimeError(
f"Longueur inattendue de secureKey : "
f"{len(secure_key)} octets"
)
# Étape 2 : chiffrement des identifiants selon l'API GCE
payload = json.dumps(
{
"login": login,
"passwd": password,
},
separators=(",", ":"),
ensure_ascii=False,
).encode("utf-8")
padder = PKCS7(128).padder()
padded_payload = (
padder.update(payload) + padder.finalize()
)
encryptor = Cipher(
algorithms.AES(secure_key),
modes.ECB(),
).encryptor()
encrypted_payload = (
encryptor.update(padded_payload)
+ encryptor.finalize()
)
secure_content = base64.b64encode(
encrypted_payload
).decode("ascii")
# Étape 3 : authentification
result = post(
base_url,
f"/api/auth/login/{encoded_login}?secure=true",
{"secureContent": secure_content},
)
token = result.get("AuthToken")
if not token:
raise RuntimeError(
"Authentification refusée : AuthToken absent"
)
return token
def reboot():
base_url = read_secret("ecodevices_url").rstrip("/")
login = read_secret("ecodevices_login")
password = read_secret("ecodevices_password")
token = authenticate(base_url, login, password)
# Étape 4 : envoi de la commande de redémarrage
path = "/api/system/reboot?" + urllib.parse.urlencode(
{"AuthToken": token}
)
post(base_url, path, {})
print("Commande de redémarrage envoyée à l'ECODEVICES 3.")
if __name__ == "__main__":
try:
reboot()
except Exception as err:
print(f"Erreur : {type(err).__name__}: {err}")
sys.exit(1)
dans le fichier configuration.yaml :
shell_command:
reboot_ecodevices3: "/usr/local/bin/python3 /config/ecodevices_reboot.py"
dans le fichier secret.yaml :
ecodevices_login: "root"
ecodevices_password: "mot_de_passe_root"
ecodevices_url: "http://192.168.x.xx:xxxx"
il suffit après de creer une automatisation du type :
alias: Linky - Alerte si tarif invalide
description: >-
Alerte dès la perte du tarif, puis jusqu'à deux tentatives
de redémarrage de l'EcoDevices 3 si le problème persiste.
triggers:
- trigger: template
id: tarif_invalide
value_template: >-
{{ states('sensor.linky_tarif_en_cours') not in
['HEURE PLEINE', 'HEURE CREUSE'] }}
for:
minutes: 1
- trigger: homeassistant
event: start
id: demarrage_ha
conditions: []
actions:
# Après démarrage de HA, laisser les intégrations se stabiliser
- if:
- condition: trigger
id: demarrage_ha
then:
- delay:
minutes: 2
# Vérifier que le tarif est toujours invalide
- condition: template
value_template: >-
{{ states('sensor.linky_tarif_en_cours') not in
['HEURE PLEINE', 'HEURE CREUSE'] }}
# Notification immédiate
- action: notify.mobile_app_christophe
data:
title: "⚠️ ALERTE Linky"
message: >-
Perte ou valeur invalide du tarif Linky détectée.
Valeur actuelle : {{ states('sensor.linky_tarif_en_cours') }}.
Tentative de redémarrage de l'EcoDevices 3 en cours.
# Première tentative de redémarrage
- action: shell_command.reboot_ecodevices3
continue_on_error: true
- delay:
minutes: 1
# Deuxième tentative seulement si le tarif reste invalide
- if:
- condition: template
value_template: >-
{{ states('sensor.linky_tarif_en_cours') not in
['HEURE PLEINE', 'HEURE CREUSE'] }}
then:
- action: shell_command.reboot_ecodevices3
continue_on_error: true
- delay:
minutes: 1
# Notification finale si le problème persiste
- if:
- condition: template
value_template: >-
{{ states('sensor.linky_tarif_en_cours') not in
['HEURE PLEINE', 'HEURE CREUSE'] }}
then:
- action: notify.mobile_app_christophe
data:
title: "🚨 Linky - Tarif toujours invalide"
message: >-
Le tarif reste invalide après les tentatives de redémarrage
de l'EcoDevices 3.
Valeur actuelle : {{ states('sensor.linky_tarif_en_cours') }}.
Vérifier l'appareil et sa communication.
mode: single
max_exceeded: silent