Il mio Home Assistant manda avvisi su tre canali: l'app sul telefono, l'email e WhatsApp. Per WhatsApp usa OpenWA, un gateway che gira in Docker accanto a Home Assistant e collega un numero dedicato tramite WhatsApp Web. Gli avvisi sono una ventina: temperature del mini PC, disco, access point che cadono, backup non riusciti, batterie delle telecamere, il robot aspirapolvere bloccato.

Funzionavano, ma arrivavano così:

Access point non raggiungibile: Non risponde: AP-DEPOSITO

Una riga. Giusta, ma uguale a tutte le altre. In una chat dove arrivano avvisi diversi, distinguere a colpo d'occhio un disco pieno da un access point spento era impossibile.

Cosa sa fare WhatsApp, e cosa sa fare il gateway

Prima di disegnare qualcosa ho controllato due cose, perché non volevo costruire su ipotesi.

La prima è la formattazione che WhatsApp interpreta da solo nel testo: *grassetto*, _corsivo_, ~barrato~, il codice tra apici inversi, le citazioni con > all'inizio della riga. Nessuna API speciale: basta scriverlo nel messaggio.

La seconda è cosa permette l'API di OpenWA. La risposta l'ho presa dagli schemi dei messaggi dentro il container, non dalla memoria. Testo fino a 4096 caratteri, risposta citata a un messaggio precedente (quotedMessageId), immagini, documenti, posizione, sondaggi, reazioni. Pulsanti interattivi no: WhatsApp Web non li offre.

Poi ho mandato tre messaggi di prova al mio numero e ho guardato come apparivano sul telefono. Grassetto, separatori, emoji e citazione funzionavano. La foto no, e ci torno più avanti.

Un impaginatore solo

In Home Assistant tutti gli avvisi passavano già da uno script centrale, script.avviso, che legge da un menu della dashboard su quali canali mandare ciascun evento. È il posto giusto per decidere anche l'aspetto: le automazioni passano i dati, lo script li impagina.

Le automazioni ora passano qualche campo in più:

- action: script.avviso
  data:
    evento: temp_cpu
    titolo: "Temperatura CPU alta"
    messaggio: "🖥️ Mini PC"
    dettagli:
      - "🌡️ CPU: *{{ states('sensor.cpu_temp') | int }} °C* da oltre 5 minuti"
      - "📈 Soglia: 85 °C"
    nota: "Controlla ventola e prese d'aria"

E lo script compone il messaggio:

testo_wa: >-
  {{ icona }} *{{ titolo | upper }}*
  {{- '\n' }}━━━━━━━━━━━━━━
  {{- '\n' }}{{ messaggio }}
  {%- for r in righe %}{{ '\n' }}{{ r }}{% endfor %}
  {%- if nota is defined and nota %}{{ '\n\n' }}👉 _{{ nota }}_{% endif %}
  {{- '\n\n' }}🕒 {{ now().strftime('%d/%m · %H:%M') }}

Il risultato sul telefono:

🔥 *TEMPERATURA CPU ALTA*
━━━━━━━━━━━━━━
🖥️ Mini PC
🌡️ CPU: *87 °C* da oltre 5 minuti
📈 Soglia: 85 °C

👉 _Controlla ventola e prese d'aria_
🕒 14/10 · 22:41

L'icona non la sceglie l'automazione: sta in una tabella dentro lo script, una riga per evento. 🔥 per la CPU, 💾 per il disco, 📡 per gli access point, 🪫 per le batterie, 🤖 per il robot. Se un domani voglio cambiarle, le cambio in un punto solo.

App ed email ricevono lo stesso contenuto, con l'icona nel titolo e senza asterischi e trattini bassi, che lì resterebbero visibili come simboli.

Lo stesso trattamento l'hanno avuto le risposte ai comandi. Al numero di casa si può scrivere stato, wifi, lista o spesa latte, pane, e ora le risposte hanno titolo, icone ed elenchi invece di righe nude.

La risposta che cita il guasto

È la parte che mi piace di più. Quando un access point cade arriva l'avviso. Quando torna, il messaggio «di nuovo online» arriva come risposta citata a quello del guasto, con la durata dell'interruzione. Nella chat si vede subito quale problema si è chiuso, senza scorrere.

Per farlo servono due cose. OpenWA, quando invia, restituisce l'identificativo del messaggio: lo salvo in un piccolo file JSON, indicizzato per evento e per destinatario. Quando parte l'avviso di risoluzione, l'automazione dice a quale evento risponde:

evento: ap_online
risponde_a: ap_offline

Lo script cerca l'ultimo messaggio di ap_offline per quel destinatario e lo passa al gateway come quotedMessageId. Gli identificativi più vecchi di sette giorni vengono scartati: oltre quel tempo, citare il messaggio originale non aiuta più nessuno.

Due errori che vale la pena raccontare

Una condizione che non era una condizione. La prima prova reale è partita bene, ma gli identificativi non venivano salvati. La traccia dello script in Home Assistant mostrava che il ramo di salvataggio veniva saltato. La mia condizione finiva con resp.content.get('messageId'), cioè restituiva il testo dell'identificativo. In Jinja quel valore sarebbe stato «vero», ma una condizione template di Home Assistant considera vero solo un risultato che vale true: un testo qualsiasi è falso. La correzione è confrontare in modo esplicito:

- if: "{{ (resp.content.get('messageId') or '') | string != '' }}"

Le tracce degli script (Impostazioni, Automazioni e scene, Script, Tracce) sono state decisive: mostrano passo per passo cosa è stato eseguito e con quali variabili, compresa la risposta del gateway.

Le foto non partono. L'idea era mandare l'istantanea della telecamera insieme all'avviso di movimento. Con la versione di OpenWA che uso, l'invio di un'immagine in base64 fallisce con un errore interno del motore WhatsApp Web. L'invio per URL invece viene rifiutato prima ancora di partire, perché la validazione richiede un indirizzo con un dominio e il nome del container di Home Assistant non ne ha uno. Per ora gli avvisi delle telecamere restano testuali, con il rimando alla dashboard. Lo segnalo perché è facile trovare guide che danno l'invio di immagini per scontato.

Come provare senza disturbare nessuno

Gli avvisi vanno a più persone e su più canali. Per provarli ho aggiunto allo script due campi facoltativi: canali, che forza i canali ignorando il menu, e destinatario, che limita l'invio a una persona. Una prova si lancia così, e arriva solo a me, solo su WhatsApp:

- action: script.avviso
  data:
    canali: "Solo WhatsApp"
    destinatario: "1"
    evento: ap_offline
    titolo: "Access point offline"
    messaggio: "🧪 _PROVA_ · ❌ Non risponde: *AP-DEPOSITO*"

Dopo la prova ho cancellato gli identificativi salvati. Altrimenti il primo vero «access point di nuovo online» avrebbe citato il messaggio di prova.

In sintesi

  • La formattazione di WhatsApp si scrive nel testo: non serve altro.
  • Un solo script decide l'aspetto di tutti gli avvisi; le automazioni passano dati, non frasi.
  • La risposta citata richiede di salvare l'identificativo restituito dal gateway.
  • Le condizioni template di Home Assistant vogliono un vero true, non un valore qualsiasi.
  • Prima di dare per buona una funzione del gateway, va provata con un messaggio vero.