Usar un script de Python para guardar datos en HA

Advierto inmediatamente, este Retex es solo para fines pedagógicos, lo escribo para compartir una metodología, a saber, que me he dado cuenta de esta característica de manera diferente desde entonces, por lo que no tengo este proceso en prod.

Integración para ejecutar el script de Python

Por una razón que nunca entendí, las características de scripting python integradas en HA son archi-mini, no es posible importar, por lo que tengo que usar una integración llamada pyscript.

El doctor está aquí.

Paso los detalles para instalarlo, es muy sencillo con HACS y todo está en el doc.

En configuration.yaml, tengo estas líneas:

pictograma: permitir _all_imports: autenticas molestias _is _global: cierto

Obtengo un token

Abro la interfaz de usuario de Home Assistant.

En la esquina inferior izquierda, hago clic en mi nombre de usuario (o icono de perfil).

Hago clic en "Perfil".

Me desplazo hacia abajo hasta la sección «Fichas de acceso a largo plazo». 

Hago clic en "Crear token" para generar un nuevo token.

Le doy un nombre a mi token para que me sea más fácil encontrarlo.

Una vez que se crea el token, veo su contenido. Copio el valor del token, es decir, la serie de caracteres alfanuméricos, y lo pego en mi configuración (párrafo siguiente)

Declaración de la plataforma que hace el enlace entre Python y el sensor

En configuration.yaml, añado estas líneas:

_comando de descanso: enviar _data_to_api: url: «http://homeassistant.local:8123/api/states/sensor.wallbox _charges» método: 'post' content_type: Encabezados «application/json»: Autorización: «Bearer TOKEN» # Sustitúyase TOKEN por el token cf párrafo anterior payload: «{{ data | tojson }}»  

Esta es la puerta principal que será suministrada por el script Python (abajo) y enviará la información al sensor.wallbox _charges.

Mi script de Python

¡Coloco el archivo Wallbox de _charges.py en la carpeta /config/pyscript!

_charges(lastdata=None, action=None, price="123", km="123", dateStart="01/01/2000 12:00:00", value="123"): _time _paris = pytz.timezone("Europa/París") ahora _timestamp = int(datetime.datetime.now().timestamp()) ahora _time _paris = datetime.datetime.fromtimestamp(ahora _timestamp, _time _paris) ahora _formatee = ahora _time _paris.strftime("%d/%m/%Y %H:%M:%S") nowMoins20min = datetime.datetime.now() - datetime.timedelta(minutes=20) nowMoins20min _formatee = nowMoins20min.strftime("%H:%M") dateDebutRecue = datetime.datetime.strptime(dateDebut, '%d/%m/%Y %H:%M:%S').replace(tzinfo=fuseau_heure_paris) debut_timestamp_formatee = dateDebutRecue.strftime("%d/%m/%Y %H:%M:%S") debut_timestamp_formatee_SansSecondes = dateDebutRecue.strftime("%d/%m/%Y %H:%M") # Convierta cadenas en objetos datetime ahora _dt = datetime.datetime.strptime(now _formatee, "%d/%m/%Y %H:%M:%S") debut_dt = datetime.datetime.strptime(debut_timestamp_formatee, "%d/%m/%Y %H:%M:%S") # Calcula la diferencia entre objetos de tiempoDeCharge = now_dt - debut_dt - datetime.timedelta(minutes=20) # Formatear la duración en horas, minutos y segundos horas, descanso = divmod(timeDeCharge.seconds, 3600) minutos, segundos = divmod(rest, 60) # Mostrar la duración como HH:MM:SS timeDeCharge_formatee = f"{horas:02}:{minutos:02}" # Escribe un mensaje informativo en el periódico #log.info(action) if action == "registrar": valueaadd = [{ "salvado _at": A partir de ahora _timestamp, "comenzando": debut_timestamp_formatee_SansSecondes, "fin": A partir de ahoraMenos20min _formatee, "duración": timeDeCharge_formatee, "consumo": valor, "km": km, "coste": round(value * price, 2) }] si lastdata no es None: valuea add = lastdata + valuea add data = { "estado": debut_timestamp_formatee_SansSecondes, "atributos": { "_at actualizado": ahora _dt.strftime("%d/%m/%Y %H:%M:%S"), "sesión de carga": valueadd } } if action == "init": datos = { "estado": "on", "atributos": { "_at actualizado": «14/10/2023 16:42:39», «aprobación de la gestión»: [ { "consumo": 2.14, "Costo": 0,43, "duración": "00:27", "principio": «19/09/2023 20:36», «fin»: "21:03", "km": 17, "Salvado _at": 1695150359 }, { "consumo": 23.29, "coste": 4.66, "duración": "04:19", "principio": «21/09/2023 21:06», «fin»: "01:25", "km": 192, "Salvado _at": 1695323159 }, { "consumo": 7.68, "coste": 1.54, "duración": "01:23", "principio": «22/09/2023 08:45», «fin»: "10:08", "km": 63, "saved_at": 1695409559 }, { "consumo": 23.80, "coste": 4.76, "duración": "04:26", "principio": «23/09/2023 16:57», «fin»: "21:23", "km": 196, "Salvado _at": 1695495959 } ] } } # Llame al servicio REST para enviar el servicio de datos _data = { "token": "xxxxxx", # Sustitúyase por el token "datos": data } if action == "registro" o acción == "init": # Llamar al servicio REST asíncronamente esperar hass.services.async_call('rest_command', 'send_data_to_api', _data service) # Escribe un mensaje de error en el registro #log.error("Fin del script")

Añadir dos automatizaciones

Uno para inicializar el sensor, el otro para agregar datos

Inicializar los datos

alias: Inicializa la descripción de las sesiones de carga: Activador "": [] condición: [] acción: - servicio: pyscript.wallbox _carga datos: acción: modo init: soltero

Guardar datos

alias: Guarda una descripción de la sesión de carga: >- Esta automatización se activa cuando el sensor no ha cambiado su valor durante 20 minutos y no está en 0 trigger: - plataforma: _id, entidad estatal: - sensor.wallbox _portal _added _range para: horas: 0 minutos: 20 segundos: 0 habilitado: verdadera condición: - condición: _id, entidad _state numérica: Sensor.wallbox _portal _added _energy arriba: 0 - condición: no condiciones: - condición: _id, entidad estatal: Sensor.wallbox _charges estado: >- {{ as_timestamp(states('sensor.wallbox _portal _added_range _min'))) | int | timestamp _custom('%d/%m/%Y %H:%M:%S', true)}}} activado: acción falsa: - servicio: pyscript.wallbox _carga datos: acción: valor de registro: Premio "{{states('sensor.wallbox _portal _added_energy')}": «{{states('sensor.wallbox _portal _energy_price')}}» km: "{{states('sensor.wallbox _portal _added_range')}}" últimos datos: «{{state _attr('sensor.wallbox _charges', 'sessionscharge')}}» Fecha de inicio: >- {{ as_timestamp(states('sensor.wallbox _portal _added_range _min'))) | int | timestamp _custom('%d/%m/%Y %H:%M:%S', true)}} - servicio: variable.update _sensor data: sustitúyase _attributes: valor falso: >- {{ as_timestamp(states('sensor.wallbox _portal _added_range _min'))) | int | timestamp _custom('%d/%m/%Y %H:%M:%S', verdadero)}} objetivo: _id (entidad): Sensor.wallbox _charges habilitado: Modo falso: soltero

Para tener el valor mínimo de la carga, utilizo estadísticas para tener el valor mínimo

Este sensor se declara en sensors.yaml

- plataforma: nombre estadístico: "Wallbox Portal Added Range Min" entidad_id: Sensor.wallbox _portal _added _range state _característica: fecha _value _min max_age: horas: 10  
   

Resultado del almacenamiento de información

Tarjeta de visualización de todas las sesiones

<div>Sesiones de carga</div> 
Última actualización: {{ state_attr('sensor.wallbox _charges','updated _at')}} <br><br>
{% set items = state_attr('sensor.wallbox _charges', 'sessionscharge') %}
<table width='100%'>
<tbody>
{% para i en rango(0, items | count, 1) %}
<tr>
<td>{{ items[i].start }} a {{ items[i].fin }} ({ items[i].duration }})</td>
<td>{{ items[i].consumo }} kWh</td>
<td>+{{ items[i].km }} km</td>
<td>{{ items[i].cost }} €</td>
</tr>

{% endfor %}
</tbody>
</table>

Tarjeta de visualización con totales por año, mes, semana

Tipo: contenido de la reducción: &gt;-
  <div><h1>Sesiones de carga</div>  {%- establecer por defecto _language = 'en' %} Última actualización: {{ 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(valor=0) %} {% set cout_semaine = namespace(valor=0) %} {% set unmois = now() - timedelta(days=30) %} {% set power_months = namespace(valor=0) %} {% set cout_mois = namespace(valor=0) %} {% set uneannee = now() - timedelta(days=365) %} {% set power_annee = namespace(valor=0) %} {% set cout_annee = namespace(valor=0) %} {% para i en rango(0, items | count, 1) %} {% if items[i].saved_at | int &gt;= as_timestamp(uneannee) %} {% set power_annee.value = power_annee.value + items[i].consumo %} {% set cout_annee.value = cout_annee.value + items[i].cost %} {% if items[i].saved_at | int &gt;= as_timestamp(unmonth) %} {% set power_mois.value = power_mois.value + items[i].consumo %} {% set cout_mois.value = cout_mois.value + items[i].cost %} {% if items[i].saved_at | int &gt;= as_timestamp(sevendays) %} {% set power_semaine.value = power_semaine.value + items[i].consumo %} {% set cout_semaine.value = cout_semaine.value + items[i].cost %} {% endif %} {% endif %} {% endif %} {% endfor %}

  </ha-alert> <ha-alert alert-type="info" title="El mes pasado: {{ potencia _mois.value | int }} kWh - {{ coste _mois.value | float | round(2) }} €"></ha-alert> <ha-alert alert-type="info" title="El año pasado: {{ 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>  {% para i en rango(0, items | count, 1) %} {% if items[i].saved_at | int &gt;= as_timestamp(sevendays) %} {% _obj = strptime(items[i].start, '%d/%m/%Y %H:%M') %}

  <tr> <td><h5>{{items[i].start }} a {{ items[i].fin }} ({ items[i].duration }})</td> <td>{{ items[i].consumo }} kWh</td> <td>+{{ items[i].km }} km</td> <td>{{ items[i].cost }} €</td> </tr> {% endif %} {% endfor %} </tbody> </table>
    <ha-alert alert-type="success" title="La semana pasada: {{ power_semaine.value | int}} kWh - {{ cost_semaine.value | float | round(2) }}€">    
estilo: .: | ha-card { margen: 0px 0px 0px 0px; Sombra de caja: ninguna; } ha-markdown: $: | h5 { tamaño de fuente: 15px!importante; peso de fuente: normal!importante; relleno: 0px 0px 0px 8px!importante; frontera izquierda: 3px sólido #429f46; }