Ammettilo, questa cosa ha un po’ il sapore del già visto e già vissuto. Parzialmente vero, rispondo io. Negli ultimi tempi mi sta capitando sempre più spesso di mettere mano a codice sorgente scritto in Python e ci sto un po’ entrando in confidenza, c’è un mondo di librerie e supporto lì fuori al quale non avevo mai dato retta.
Ci sono cose che in Python puoi risolvere più facilmente che in altra maniera, perché quindi non approfittarne considerato che ormai spesso delego dei compiti ripetitivi e automatizzati al Raspberry Pi o al NAS? Questa è quindi la naturale evoluzione di ciò che avevo scritto in passato (tado°: dialogare con il sistema tramite bash) e che oggi approda su codice Python, decisamente più snello e ottimizzato (spero e credo).
Si riparte
Io parto dando per scontato che i fondamentali tu li abbia affrontati nel vecchio articolo (che rimane comunque valido). Se così non fosse, credo che dargli un’occhiata possa essere un’ottima idea:
Detto ciò, quello che forse ti interesserà sapere, è che quel “clientSecret” presente nel file my.tado.com/webapp/env.js non è mai cambiato. Forse resterà tale anche in futuro, al momento ho scelto di inserirlo all’interno delle variabili dello script in maniera secca, non recuperata dinamicamente dallo script stesso. Se un domani cambierà, vorrà dire che sarà cura dell’utilizzatore andare a modificare il valore della variabile contenuta nel file di .env
sul sistema che fa uso dello script Python che spiegherò di seguito.
Lo script in Python
Non ti far spaventare. Preferisco stavolta portarti immediatamente allo script e poi spiegartelo passo-passo.
from dotenv import load_dotenv, find_dotenv from libtado import api from fritzconnection.lib.fritzhosts import FritzHosts import datetime import requests import base64 import os load_dotenv(find_dotenv()) TADO_USER = os.environ.get("TADO_USER") TADO_PWD = os.environ.get("TADO_PWD") TADO_TOKEN = os.environ.get("TADO_TOKEN") FRITZ_IP = os.environ.get("FRITZ_IP") MACS = os.environ.get("MACS") BOT_TOKEN = os.environ.get("BOT_TOKEN") CHAT_ID = os.environ.get("CHAT_ID") MSG_TADO_HOME = "tado° in modalità Home. Bentornati nell'area di casa 🏠 (%s)" % datetime.datetime.now().strftime("%d/%m/%Y alle %H:%M:%S") # https://stackoverflow.com/a/7999977 MSG_TADO_AWAY = "tado° in modalità Away. Abbasso il riscaldamento, a più tardi 👋 (%s)" % datetime.datetime.now().strftime("%d/%m/%Y alle %H:%M:%S") def sendtotelegram(token,chatid,message): url = "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s" % (token, chatid, message) response = requests.post(url) return response def get_home_state(TADO_USER, TADO_PWD, TADO_TOKEN): homestate = api.Tado(TADO_USER, TADO_PWD, TADO_TOKEN) response = homestate.get_home_state() return list(response.values())[0] def set_home_state(TADO_USER, TADO_PWD, TADO_TOKEN, STATE): homestate = api.Tado(TADO_USER, TADO_PWD, TADO_TOKEN) if str(STATE) == "True": homestate.set_home_state(True) if str(STATE) == "False": homestate.set_home_state(False) return def main(): if TADO_USER is None or TADO_PWD is None or TADO_TOKEN is None or FRITZ_IP is None or MACS is None: print("Environment variables have not been loaded!") return fh = FritzHosts(address=FRITZ_IP) macaddresses = os.environ['MACS'].split() # https://stackoverflow.com/a/54027239 presence = 0 homestate = get_home_state(TADO_USER, TADO_PWD, TADO_TOKEN) for mac in macaddresses: check = fh.get_host_status(mac) print("Check", mac, ":", check) if str(check) == "True": presence += 1 print("Check presence:", presence) if presence > 0: if str(homestate) == "AWAY": print("Found at least one device in home, set tado° to HOME") sendtotelegram(BOT_TOKEN,CHAT_ID,MSG_TADO_HOME) set_home_state(TADO_USER, TADO_PWD, TADO_TOKEN, True) else: if str(homestate) == "HOME": print("No devices found in home, set tado° to AWAY") sendtotelegram(BOT_TOKEN,CHAT_ID,MSG_TADO_AWAY) set_home_state(TADO_USER, TADO_PWD, TADO_TOKEN, False) main()
Le variabili usate
Ho fatto uso ancora una volta del file .env
per indicare le variabili richiamate poi all’interno dello script Python ogni volta che c’è stata la necessità. L’ho già fatto negli ultimi repository pubblicati su GitHub (tipo netflix-leaving e spotify-save-new-music-friday) e anche stavolta non si fa eccezione. Il file è strutturato banalmente in questa maniera (e si deve trovare nella stessa identica cartella in cui risiede lo script Python):
TADO_USER=user@contoso.com TADO_PWD=MySuperSecretPassword TADO_TOKEN=wZaRN7rpjn3FoNyF5IFuxg9uMzYJcvOoQ8QWiIqS3hfk6gLhVlG57j5YNoZL2Rtc FRITZ_IP=192.168.178.1 MACS=XX:XX:XX:XX:XX:XX YY:YY:YY:YY:YY:YY ZZ:ZZ:ZZ:ZZ:ZZ:ZZ BOT_TOKEN=MyTelegramBotToken CHAT_ID=-000000000
TADO_USER
*: è l’utente con il quale sei registrato su my.tado.com.TADO_PWD
*: devo seriamente specificarlo?TADO_TOKEN
*: ilclientSecret
recuperato da my.tado.com/webapp/env.js.FRITZ_IP
*: l’IP del tuo router casalingo.MACS
*: uno o più MAC Address da verificare (se sono collegati o meno alla WiFi / LAN casalinga), da specificare secondo la formula nell’esempio, quindi distanziati l’uno dall’altro semplicemente con un colpo di barra spaziatrice, non c’è un reale limite al numero massimo di MAC Address controllabili.BOT_TOKEN
: ho voluto mantenere l’uso dell’alert inviato su Telegram, quindi questa variabile contiene il token che mi permette di lavorare con il bot di casa.CHAT_ID
: completa quanto detto poco sopra. L’ID del gruppo / utente con cui far comunicare il bot Telegram in caso di alert.
Tutti i parametri con * vicino sono obbligatori, gli altri puoi bellamente ignorarli se modifichi un pelo lo script, ti spiego come tra poco.
All’interno dello script Python trovi anche le due variabili relative ai messaggi da spedire via Telegram (MSG_TADO_AWAY
e MSG_TADO_HOME
), puoi ritoccarli secondo tua preferenza, occhio solo a non toccare le variabili richiamate.
Librerie usate
Tralasciando quelle più d’uso comune, in questo script ho integrato due librerie importanti per parlare con il router FRITZ!Box e con il sistema API di tado°. Si tratta rispettivamente della fritzconnection e della libtado, entrambe disponibili sia su GitHub (github.com/kbr/fritzconnection e github.com/germainlefebvre4/libtado) che su pypi.org (pypi.org/project/fritzconnection e pypi.org/project/libtado), installabili quindi tramite Pip. Ho preferito raccogliere i requisiti necessari all’interno di un file requirements.txt
che puoi quindi richiamare facilmente tramite pip install -r requirements.txt
:
certifi==2020.12.5 chardet==4.0.0 idna==2.10 python-dotenv==0.16.0 requests==2.25.1 urllib3==1.26.5 libtado==3.2.3 fritzconnection==1.9.1
Chi, cosa, come
Le due librerie godono di ottima documentazione disponibile online, rispondono rispettivamente all’URL fritzconnection.readthedocs.io/en/1.9.1/index.html e libtado.readthedocs.io/index.html. In linea di massima si può anche effettuare una connessione di test per entrambe, utilizzando gli snippet di codice proposti nei repository GitHub. Per il sistema tado° si può effettuare connessione tramite username e password (e clientSecret
) e farsi rispondere con i dettagli sull’account, sulla casa, sulle zone e sullo stato, il tutto con queste poche righe di codice:
import libtado.api t = api.Tado('my@email.com', 'myPassword', 'client_secret') print(t.get_me()) print(t.get_home()) print(t.get_zones()) print(t.get_state(1))
Per quanto invece riguarda il router FRITZ!Box in uso, puoi per esempio chiedere di conoscere il modello rilevato dalla libreria, così (inutile dire che al posto di 192.168.178.1
andrà indicato il reale IP assegnato al tuo router casalingo, giusto?):
from fritzconnection import FritzConnection fc = FritzConnection(address='192.168.178.1') print(fc) # print router model informations
Se entrambe le connessioni di test vanno a buon fine, direi che sei già a metà dell’opera :-)
Lo scopo del gioco
È capire se in casa c’è almeno un dispositivo riconosciuto (il suo MAC Address è stato specificato nella lista MACS
) e mantenere quindi acceso il sistema di riscaldamento, in posizione Home e non Away. Per farlo mi sono basato su un ragionamento tanto stupido quanto efficace: se il conteggio dispositivi è maggiore di zero allora resta in “Home“, altrimenti spostati in “Away“:
presence = 0 for mac in macaddresses: check = fh.get_host_status(mac) print("Check", mac, ":", check) if str(check) == "True": presence += 1 print("Check presence:", presence)
Quella variabile “presence” precedentemente impostata a zero (prima del controllo dei MAC) potrà uscire dal check pari ancora a zero (quindi Away) o pari/maggiore di uno (quindi Home). Le chiamate di check al router le faccio tramite l’API get_host_status, quelle al sistema tado° sono due e rispondono al get_home_state e (con molta poca fantasia) al set_home_state per modificare lo stato di presenza automaticamente tramite script.
Non uso e non voglio Telegram
Non c’è problema, le variabili relative al token del bot e della chat in cui inviare gli alert sono infatti facoltative. Nello script però non ho attualmente previsto un controllo che tenga conto variabili volutamente lasciate vuote per evitare di richiamare la funzione di invio messaggio a Telegram. Se vuoi togliere di mezzo questo passaggio, ti basterà commentare le due chiamate alla funzione “sendtotelegram“, parlo di queste:
sendtotelegram(BOT_TOKEN,CHAT_ID,MSG_TADO_HOME) sendtotelegram(BOT_TOKEN,CHAT_ID,MSG_TADO_AWAY)
Aggiungi un cancelletto prima (esempio: #sendtotelegram(BOT_TOKEN,CHAT_ID,MSG_TADO_AWAY)
) e avrai così commentato la riga. Lo script funzionerà tranquillamente ma non proverà neanche a chiamare in causa Telegram.
Automatizza e concludi
Lo script è pronto, se lo lanci funziona e sei ora un bambino contento, mi fa piacere :-)
Cosa manca? Crontab. Uno script senza un’esecuzione programmata e ripetuta nel tempo è un po’ un ammasso di codice fermo al palo delle inutilità. A questo punto dovrai quindi decidere ogni quanto tempo eseguire questo controllo sul tuo Raspberry Pi (o altra macchina equivalente, poco importa, basta avere qualcosa di acceso e che possa eseguire Python), io ho scelto di farlo fare al mio RPi ogni 5 minuti:
*/5 * * * * /usr/local/bin/python3.8 /home/pi/Scripts/Tado/tado.py
Nel tuo caso quasi certamente cambierà la posizione dello script e magari anche quella di Python, dovrai quindi ritoccare un pelo quanto riportato sopra, in ogni caso dovrebbe essere abbastanza chiaro come comportarsi. Se vuoi essere certo che lo script vada in esecuzione puoi modificarlo e aggiungere queste righe di codice prima di main()
:
# Debug Crontab with open('/home/pi/Scripts/Tado/debug.txt', 'w') as f: f.write(datetime.datetime.now().strftime("%d/%m/%Y alle %H:%M:%S"))
Anche in questo caso potrebbe cambiare la posizione del file di testo da scrivere, occhio quindi a non prendere per “bibbia” cosa ti riporto io a titolo di esempio. Andando a sbirciare il file di debug troverai la data e l’ora dell’ultima esecuzione del controllo. Puoi in qualsiasi momento – se lo reputi necessario – cancellare le righe di codice relative al Debug Crontab e dormire sonni tranquilli (e caldi, d’inverno).
L’articolo si conclude qui. Se vuoi – come sempre – l’area commenti è a tua totale disposizione per dubbi, ulteriori informazioni o suggerimenti riguardo possibili miglioramenti. Io presto pubblicherò il tutto su GitHub, aggiornerò quindi questo articolo. Nel frattempo ho disattivato questi controlli su IFTTT e presto metterò le mani anche ai controlli Arlo per spegnere le ulteriori Applet e tornare all’abbonamento gratuito di base, portando in casa tutto ciò che mi serve tenere d’occhio.
#StaySafe
Riconoscimenti (oltre quelli già riportati nell’articolo):
w3schools.com/python/python_dictionaries.asp
w3schools.com/python/gloss_python_array_loop.asp
w3schools.com/python/python_conditions.asp
stackoverflow.com/a/2632687
L'articolo potrebbe non essere aggiornato
Questo post è stato scritto più di 5 mesi fa, potrebbe non essere aggiornato. Per qualsiasi dubbio ti invito a lasciare un commento per chiedere ulteriori informazioni! :-)