Il robot aspirapolvere di casa è un Dreame, collegato a Home Assistant con l'integrazione comunitaria dreame_vacuum. Nella dashboard di Home Assistant arrivavano tantissime notifiche: spazzola da pulire, robot bloccato, scarico dell'acqua completato, spesso con un'immagine. Le vedevo solo aprendo Home Assistant. Volevo riceverle su WhatsApp, come gli altri avvisi di casa, e volevo anche poter dire al robot cosa fare scrivendogli un messaggio.
Da dove arrivano le notifiche
Prima di scrivere qualcosa ho letto il codice dell'integrazione, per capire che cosa produce davvero. Ci sono due canali.
Il primo sono le notifiche persistenti di Home Assistant. Per errori e avvisi il testo contiene un'illustrazione, inserita direttamente nel messaggio come immagine in base64:
### Spazzola principale incastrata
Pulisci la spazzola principale
Non è una foto della casa: è un disegno del robot che mostra dove sta il problema. Lo stesso vale per lo scarico dell'acqua. Consumabili, pausa e raccolta della polvere sono solo testo.
Il secondo canale sono gli eventi, come dreame_vacuum_task_status, che a fine pulizia porta un campo completed e i dati del lavoro.
Nel codice ho trovato anche un dettaglio che conviene sapere prima di fare danni: quando si chiude in Home Assistant la notifica di un avviso, l'integrazione cancella l'avviso anche sul robot. Per questo le notifiche le giro su WhatsApp ma non le tocco.
Girare le notifiche su WhatsApp
Home Assistant ha un trigger che scatta quando viene creata una notifica persistente. Accetta però solo un identificativo esatto, e quelle del robot hanno identificativi variabili, del tipo dreame_vacuum_<dispositivo>_<tipo>. Quindi il trigger ascolta tutte le notifiche nuove e una condizione tiene solo quelle che cominciano con dreame_vacuum_:
triggers:
- trigger: persistent_notification
update_type:
- added
conditions:
- condition: template
value_template: >-
{{ trigger.notification.notification_id.startswith('dreame_vacuum_') }}
Dal testo estraggo tre cose: il tipo (errore, avviso o manutenzione), il titolo senza i simboli ### e l'immagine in base64:
immagine: "{{ (msg | regex_findall('data:image/png;base64,([A-Za-z0-9+/=]+)') | first) | default('') }}"
Tutto passa poi allo script degli avvisi che uso per il resto della casa: stessa grafica, scelta dei canali per tipo di evento, limite giornaliero. La fine della pulizia arriva invece dall'evento, con area, tempo e batteria letti dai sensori del robot e la foto della mappa.
Le regole di estrazione le ho provate prima nell'editor dei template, con un messaggio costruito come quelli veri: tipo, immagine e righe del testo uscivano corretti, e senza immagine il campo restava vuoto.
Lo sfondo nero
Il primo avviso è arrivato su WhatsApp con l'immagine giusta, ma su sfondo nero. Le illustrazioni di Dreame sono PNG con lo sfondo trasparente, e WhatsApp riempie la trasparenza di nero. Lo stesso succedeva poi con la mappa, che pur salvata con estensione .jpg era in realtà un PNG trasparente: l'ho scoperto aprendo il file e leggendone il formato reale invece di fidarmi del nome.
La soluzione è appoggiare l'immagine su uno sfondo bianco e salvarla in JPEG prima di mandarla. Nel container di Home Assistant c'è già Pillow:
img = Image.open(io.BytesIO(dati)).convert("RGBA")
fondo = Image.new("RGB", img.size, (255, 255, 255))
fondo.paste(img, mask=img.split()[-1])
fondo.save(out, "JPEG", quality=90)
Lo script vale sia per le illustrazioni in base64 sia per i file su disco, come la mappa. Con i file rifiuta un'origine più vecchia di due minuti: se lo scatto non è riuscito, meglio un messaggio senza foto che una foto di un altro momento fatta passare per nuova.
Il limite di 128 KB per argomento
Per convertire l'illustrazione bisogna passarla allo script Python, e qui c'era un dubbio. Nel container il sistema dichiarava un limite di 128 KB per gli argomenti, mentre un'illustrazione arrivava a 116 KB in base64, già al limite. Invece di fidarmi del numero l'ho misurato:
1 argomento da 120000 caratteri: ok
1 argomento da 300000 caratteri: Argument list too long
4 argomenti da 100000 caratteri: ok
Il limite vale per il singolo argomento, non per l'insieme. Così il comando divide il base64 in pezzi da 100 KB e lo script li ricompone:
immagine_b64_jpg: >-
python3 /config/scripts/immagine_b64.py {{ file }}
{%- for i in range(0, b64 | length, 100000) %} {{ b64[i:i + 100000] }}{% endfor %}
Comandare il robot
Al numero WhatsApp di casa ora si può scrivere robot. La risposta contiene la mappa, lo stato, la batteria, la stanza in cui si trova, i dati dell'ultima pulizia e l'elenco dei comandi, uno per riga:
• robot pulisci · tutta la casa
• robot pulisci cucina e bagno · solo quelle stanze
• robot pausa · robot riprendi · robot base · robot stop
• robot potenza turbo · silenzioso, standard, alta, turbo
• robot modalita lava bagno · aspira, lava, entrambi, lava dopo
• robot mappa
Le stanze vengono dalla mappa del robot. Un sensore template abbina a ciascuna il nome, l'identificativo che serve per pulirla da sola e il numero delle sue impostazioni:
- action: dreame_vacuum.vacuum_clean_segment
target:
entity_id: vacuum.speedyair
data:
segments: "{{ stanze | map(attribute='id') | list }}"
Per riconoscere le stanze in una frase basta una parola del nome di almeno quattro lettere: «cucina», «bagno», ma anche «camera» per la camera da letto.
Una sorpresa nelle impostazioni
Volevo che robot potenza turbo cambiasse la potenza di aspirazione. Nella mia installazione però le impostazioni generali di potenza e modalità risultavano «non disponibili». Leggendo il codice dell'integrazione ho trovato il motivo: era attiva la pulizia personalizzata per stanza, e in quel caso il robot ignora le impostazioni generali e usa quelle di ogni stanza. Il comando quindi lavora stanza per stanza: con il nome di una stanza cambia solo quella, senza, cambia tutte e sei.
Una nota sui riavvii
Durante le prove un messaggio e una notifica sono andati persi. Erano arrivati pochi secondi dopo un riavvio di Home Assistant, quando le automazioni non erano ancora caricate. Non è un errore della configurazione: dopo un riavvio conviene aspettare un minuto prima di provare.
In sintesi
- Prima di automatizzare un'integrazione conviene leggerne il codice: si scopre cosa produce e quali azioni hanno effetti collaterali.
- Una notifica di Home Assistant può contenere un'immagine in base64, ed è più comodo estrarla che cercarla altrove.
- Le immagini trasparenti su WhatsApp diventano nere: vanno appoggiate su un fondo bianco.
- Un limite di sistema va misurato, non letto: qui valeva per il singolo argomento, non per il totale.
- Se un'impostazione risulta non disponibile, spesso un'altra opzione ha la precedenza.