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.