Il progetto é finalizzato a rendere automatico un impianto domestico attraverso Home Assistant. L'utente ha il compito di inserire in uno specifico calendario Google il nome dell'evento e l'addon che viene eseguito periodicamente accende lo switch associato.
Questo progetto è open source, chiunque può scaricare i file necessari, ricreare il progetto e contribuire al suo miglioramento. Non ci sono restrizioni di licenza d'uso, ma si invita a citare che è stato realizzato dagli studenti ASIRID.
Tutto il materiale necessario si trova su Gitlab.
- Aprire Home Assistant.
- Navigare in Impostazioni > Dispositivi e Servizi.
- Cliccare sul pulsante in basso a destra per aggiungere una nuova integrazione.
- Dalla lista selezionare ESPHome.
- Seguire le istruzioni a schermo per completare il setup.
- Una volta avviata l'intergrazione, comparirà a schermo una pagina in cui è possibile configurare un nuovo dispositivo.
- Collegare al computer il dispositivo da programmare.
- Cliccare sul pulsante verde + New Device e successivamente su OPEN ESPHOMEWEB.
- Iniziare la procedura di installazione cliccando sul pulsante PREPARE FOR FIRST USE.
- Durante questa fase si potrà configurare la rete alla quale il dispositivo si deve connettere e un friendly-name.
- Terminata questa fase, si potrà vedere il dispositivo sulla dashboard di ESPHome.
Assegnare un nome al dispositivo e un nome da visualizzare nell'interfaccia
esphome:
name: controllo-impianti
friendly_name: Controllo Impianti
Specificare il microntrollore utilizzato e il tipo di scheda
esp8266:
board: d1
Abilitare il logging per il debug o diagnostica
logger:
Abilitare le API di Home Assistant per la comunicazione con ESPHome
api:
Abilitare Over-The-Air per effettuare aggiornamenti al dispositivo senza la necessità di collegamenti ad esso fisici
ota:
Abilitare un server web sul dispositivo ESP, il che può essere utile per il debug o il controllo diretto tramite browser
web_server:
Configurare rete Wi-Fi utilizzando le credenziale non in chiaro e configurare un hotspot di fallback nel caso di problemi alla rete principale
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "<identificativo rete hotspot>"
password: "<password rete hotspot>"
Abilitare 'captive_portal' per fornire una rete di emergenza che può essere utilizzata per configurare il dispositivo se la connessione WiFi non funziona
captive_portal:
Configurare il bus I2C e la frequenza (400khz è la frequenza standard per la comunicazione con molti dispositivi e sensori)
i2c:
frequency: 400khz
Configurare un chip di espansione I/O PCF8574, che consente di aggiungere più pin I/O al microcontrollore. Viene assegnato un indirizzo sul bus I2C e identificato come 'pcf8574_hub'
pcf8574:
- id: 'pcf8574_hub'
address: 0x20
pcf8575: false
Configurare i pin necessari del PCF8574 come sensori binari (input), che vengono utilizzati per rilevare la pressione di un pulsante on/off. Quando viene rilevato un input (on_press), viene eseguita un'azione per commutare (toggle) uno stato di un interruttore definito più avanti nello script
binary_sensor:
- platform: gpio
name: "PCF8574 Pin #4"
pin:
pcf8574: pcf8574_hub
number: 4
mode:
input
on_press:
then:
- switch.toggle: pin_0
- platform: gpio
name: "PCF8574 Pin #5"
pin:
pcf8574: pcf8574_hub
number: 5
mode:
input
on_press:
then:
- switch.toggle: pin_1
- platform: gpio
name: "PCF8574 Pin #6"
pin:
pcf8574: pcf8574_hub
number: 6
mode:
input
on_press:
then:
- switch.toggle: pin_2
- platform: gpio
name: "PCF8574 Pin #7"
pin:
pcf8574: pcf8574_hub
number: 7
mode:
input
inverted: true
on_press:
then:
- switch.toggle: pin_3
Configurare gli switch che controllano i dispositivi connessi ai pin definiti in precedenza. In questo caso, gli stati di questi switch sono invertiti il che significa che un valore logico alto (true) corrisponde ad un livello basso di tensione.
switch:
- platform: gpio
name: "A1"
id: pin_0
pin:
pcf8574: pcf8574_hub
number: 0
mode:
output: true
inverted: true
- platform: gpio
name: "A2"
id: pin_1
pin:
pcf8574: pcf8574_hub
number: 1
mode:
output: true
inverted: true
- platform: gpio
name: "A3"
id: pin_2
pin:
pcf8574: pcf8574_hub
number: 2
mode:
output: true
inverted: true
- platform: gpio
name: "A4"
id: pin_3
pin:
pcf8574: pcf8574_hub
number: 3
mode:
output: true
inverted: true
Configurare il display LCD collegato al bus I2C (se presente) in modo tale che mostri lo stato degli switch
display:
- platform: lcd_pcf8574
update_interval: 100ms
dimensions: 20x4
address: 0x27
lambda: |-
it.print(0,0,"Impianti");
it.printf(0, 1, "%s", id(pin_0).state ? "ON" : "OFF" );
it.printf(4, 1, "%s", id(pin_1).state ? "ON" : "OFF" );
it.printf(8, 1, "%s", id(pin_2).state ? "ON" : "OFF" );
it.printf(12, 1, "%s", id(pin_3).state ? "ON" : "OFF" );
- Per modificare il codice cliccare su Edit.
- Cliccare sul pulsante in alto a destra INSTALL.
- La prima volta si deve procedere con l'istallazione manuale cliccando su Plug into this computer.
- Attendere la compilazione del codice.
- Una volta terminata, cliccare su Download Project che scaricherà un file con estensione .bin.
- Cliccare su Open ESPHome Web che aprirà una nuova pagina web.
- Cliccare su Connect e caricare il file .bin scaricato in precedenza.
- Verificare che il dispositivo sia Online dalla dashboard di ESPHome.
N.B.: se il dispositivo é stato programmato almeno una volta e si devono apportare modifiche al codice, la programmazione può essere fatta tramite OTA (Over-The-Air) cioè tramite la connessione Wi-Fi. Per fare questo cliccare sul pulsante INSTALL e successivamente su Wirelessly
- Per aggiungere alla plancia una vista cliccare sulla barra laterale su Panoramica.
- Cliccare sul pulsante + per aggiungere una nuova vista.
- Cliccare sul pulsante in basso a destra AGGIUNGI SCHEDA.
- Digitare entità e selezionare la prima nella ricerca.
- Assegnare un titolo alla vista.
- Selezionare le entità configurate in precedenza.
- Cliccare sul pulsante in basso a destra SALVA.
N.B.: L'interfaccia può essere personalizzata secondo i gusti e le esigenze dell'utente, ad esempio aggiungendo il calendario, un grafico, ecc.
Per il monitoraggio del calendario è stato sviluppato un addon locale in Home Assistant che utilizza le API. Grazie ad esso siamo riusciti a garantire un controllo più affidabile e resiliente degli impianti. L'addon monitora periodicamente gli eventi del calendario e gestisce gli impianti in base a essi. Ad esempio, se un impianto rimane acceso in assenza dell'evento associato, l'addon si occupa di spegnere automaticamente l'interruttore, evitando sprechi di energia e mantenendo il sistema sotto controllo. Questo approccio rende l’intera gestione degli impianti più efficiente e reattiva.
-
Andare in Esplora File e digitare sulla barra di ricerca \\IP_HOME_ASSISTANT>\addons
-
Creare la cartella ControlloImpianti
-
Questa è la struttura della cartella:
ControlloImpianti/ ├── Dockerfile ├── build.yaml ├── config.yaml ├── CHANGELOG.md ├── README.md ├── icon.png ├── rootfs │ ├── controlloimpianti │ │ ├── main.py │ │ ├── impiantiConf.py │ │ └── interface.sh │ └── etc │ └── services.d │ └── interface │ └── run
Un Dockerfile è un file di testo che definisce come creare un’immagine di Docker, ovvero una “foto” del contenitore che contiene l’applicazione e le sue dipendenze. Il Dockerfile può contenere istruzioni per copiare i file dell’applicazione, installare dipendenze e configurare l’ambiente.
ARG BUILD_FROM
FROM ${BUILD_FROM}
RUN apt-get update -y && apt upgrade -y
RUN apt-get install -y \
coreutils \
wget \
curl \
python3 \
python3-dev \
python3-pip
RUN pip3 install requests==2.24.0
COPY rootfs /
- Utilizza l'immagine di base specificata dalla variabile BUILD_FROM.
- Aggiorna l'elenco dei pacchetti e aggiorna i pacchetti installati all'ultima versione.
- Installa i pacchetti necessari per il funzionamento dell'Add-On.
- Installa i moduli Python specifici utilizzando pip3.
- Copia il contenuto della directory rootfs nel file system del container.
Questo file di configurazione fornisce la basi per l'integrazione dell'Add-On con Home Assistant, specificando le caratteristiche e i requisiti essenziali
---
version: "1.0.0"
name: Controllo Impianti
description: Monitoraggio periodico entity_id_calendar locale home assistant e accensione spegnimento impianti
slug: controlloimpianti
arch:
- aarch64
- amd64
- armhf
- armv7
- i386
init: false
homeassistant_api: true
panel_admin: false
panel_title: Controllo Impianti
panel_icon: mdi:calendar-clock
map:
- share:rw
options:
configuration: []
entity_id_calendar: ""
run_interval: 5
schema:
configuration:
- keyword: str
linked_entity_id:
- str
entity_id_calendar: str
run_interval: "int(1,5)"
-
version: "1.0.0"
- Specifica la versione corrente dell'addon per la gestione degli aggiornamenti.
-
name: Controllo Impianti
- Nome visualizzato dell'addon nell'interfaccia utente di Home Assistant.
-
description:
- Descrive brevemente le funzionalità, fornendo una panoramica rapida.
-
slug: controlloimpianti
- Identificatore univoco usato internamente da Home Assistant.
-
arch:
- Indica le architetture hardware supportate.
-
init: false
- Specifica se l'addon richiede un processo di inizializzazione.
-
homeassistant_api: true
- Abilita l'accesso alle API di Home Assistant.
-
panel_admin: false
- Definisce se l'addon dovrebbe avere un pannello amministrativo.
-
panel_title:
- Titolo del pannello nell'interfaccia utente.
-
panel_icon:
- Icona del pannello utilizzando la libreria Material Design Icons.
-
map:
- Monta la directory
share
con permessi di lettura e scrittura.
- Monta la directory
---
build_from:
aarch64: ghcr.io/hassio-addons/debian-base/aarch64:5.3.1
amd64: ghcr.io/hassio-addons/debian-base/amd64:5.3.1
armhf: ghcr.io/hassio-addons/debian-base/armhf:5.3.1
armv7: ghcr.io/hassio-addons/debian-base/armv7:5.3.1
i386: ghcr.io/hassio-addons/debian-base/i386:5.3.1
codenotary:
base_image: notary@home-assistant.io
signer: notary@home-assistant.io
Questo file contiene le immagini Docker che devono essere utilizzate per costruire l'Add-On. Durante la fase di build, Docker scaricherà le immagini specificate e le utilizzerà per costruire l'Add-On
Attraverso il seguente codice otteniamo dall'istanza di Home Assistant il token di autenticazione (SUPERVISOR_TOKEN
) e l'indirizzo URL (HASSIO_URL
) necessari per utilizzare le API, recuperandoli dalle variabili d'ambiente del sistema.
import os
SUPERVISOR_TOKEN = os.environ["SUPERVISOR_TOKEN"]
HASSIO_URL = os.environ.get("HASSIO_URL","http://hassio/homeassistant/api")
Il seguente script è suddiviso nelle seguenti funzioni:
-
fetch_events
: Questa funzione accetta tre parametri: l'ID del calendario da monitorare e due timestamp che definiscono l'intervallo di tempo da osservare. Effettua una chiamata HTTP GET all'API di Home Assistant per recuperare gli eventi del calendario nel formato JSON. Se la chiamata ha successo (HTTP 200), restituisce i dati degli eventi; altrimenti, gestisce l'errore restituendo un array vuoto e scrivendo un messaggio di errore nel log. Questo è fondamentale per identificare gli eventi che influenzeranno il comportamento degli switch. -
categorize_events
: Riceve come parametri gli eventi ottenuti da fetch_events e la configurazione utente, che specifica i nomi degli eventi e i dispositivi associati (switch). Analizza gli eventi in corso e non in corso, creando due liste distinte: una per gli eventi attivi (in corso) e una per quelli inattivi. Questo permette di distinguere quali azioni devono essere intraprese sui dispositivi (accensione/spegnimento), assicurando che il sistema risponda correttamente allo stato corrente del calendario. -
switch_devices
: Questa funzione gestisce l'accensione o lo spegnimento degli switch, ricevendo una lista di ID di dispositivi e l'azione da eseguire (turn_on/turn_off). Esegue una chiamata HTTP POST per attivare o disattivare i dispositivi e logga i dispositivi coinvolti, offrendo trasparenza nelle operazioni svolte. In caso di fallimento della chiamata, registra un errore nel log, aiutando nella diagnostica di eventuali problemi di comunicazione con Home Assistant. -
switch_on_off
: Funzione principale che coordina l'intero processo. Recupera l'ID del calendario da monitorare e imposta l'intervallo di tempo per la ricerca degli eventi. Utilizzacategorize_events
per distinguere tra eventi in corso e non in corso, e determina quali dispositivi devono essere accesi o spenti in base allo stato degli eventi. Esegue quindi le chiamate HTTP necessarie per gestire gli switch, assicurando che il comportamento dei dispositivi rispecchi le esigenze definite dalla configurazione dell'utente. -
main
: Esegue ciclicamente la funzioneswitch_on_off
ogni tot minuti, con l'intervallo di esecuzione configurato dall'utente tra 1 e 5 minuti. Questo garantisce che il sistema sia sempre aggiornato in base agli eventi del calendario e che i dispositivi reagiscano prontamente ai cambiamenti dello stato degli eventi. L'intervallo di esecuzione è un parametro configurabile nell'addon, permettendo una personalizzazione della frequenza di monitoraggio.from datetime import datetime, timedelta, timezone import requests, logging, json, impiantiConf, time token = impiantiConf.SUPERVISOR_TOKEN url = impiantiConf.HASSIO_URL headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%H:%M:%S') with open("/data/options.json", "r") as f: options = json.load(f) config = options.get("configuration") def fetch_events(entity_id_calendar, start, end): """Fetch events from the calendar.""" url_get = f"{url}/calendars/{entity_id_calendar}?start={start}&end={end}" try: response = requests.get(url_get, headers=headers) response.raise_for_status() return response.json() except Exception as e: logging.error(f"Error fetching events: {e}") return [] def categorize_events(events, config): """Categorize events as in progress or not based on keywords.""" events_on, keywords_checked = set(), {kw.get("keyword", "").lower() for kw in config} for event in events: event_summary = event.get("summary", "").lower() matched_keywords = {kw.get("keyword", "").lower() for kw in config if kw.get("keyword", "").lower() == event_summary} events_on.update(matched_keywords) keywords_checked -= matched_keywords return list(events_on), list(keywords_checked) def switch_devices(entity_ids, action): """Switch devices on or off.""" if not entity_ids: return url_action = f"{url}/services/switch/{action}" body = {"entity_id": sorted(entity_ids)} logging.info(f"Switching {action}:") logging.info(f"- {', '.join(entity_ids) if entity_ids else 'None'}") try: response = requests.post(url_action, headers=headers, data=json.dumps(body)) response.raise_for_status() except requests.exceptions.RequestException as e: logging.error(f"HTTP error during switching: {e}") def switch_on_off(): """Main function to handle switching devices based on events.""" entity_id_calendar = options.get("entity_id_calendar") start, end = datetime.now(timezone.utc), datetime.now(timezone.utc) + timedelta(minutes=5) start_str, end_str = start.strftime("%Y-%m-%dT%H:%M:%S.000Z"), end.strftime("%Y-%m-%dT%H:%M:%S.000Z") events = fetch_events(entity_id_calendar, start_str, end_str) events_on, events_off = categorize_events(events, config) linked_entities_on = sorted({e_id for kw in config if kw.get("keyword", "").lower() in events_on for e_id in kw.get("linked_entity_id", [])}) linked_entities_off = sorted({e_id for kw in config if kw.get("keyword", "").lower() in events_off for e_id in kw.get("linked_entity_id", [])}) linked_entities_off = [e_id for e_id in linked_entities_off if e_id not in linked_entities_on] logging.info("Events in Progress:") logging.info(f"- {', '.join(events_on) if events_on else 'There are no events in progress'}") logging.info("Events not in Progress:") logging.info(f"- {', '.join(events_off) if events_off else 'None'}") switch_devices(linked_entities_on, "turn_on") switch_devices(linked_entities_off, "turn_off") if __name__ == "__main__": if not options.get("entity_id_calendar") or not options.get("configuration") or not options.get("run_interval"): logging.error("Please provide entity_id_calendar, configuration and run_interval in the options.") exit(1) run_interval = options.get("run_interval", 5) while True: switch_on_off() logging.info(f"Waiting for {run_interval} minutes...") time.sleep(run_interval * 60)
Questo script esegue il file Python main.py
, gestendo l’esecuzione principale del programma.
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Home Assistant Add-on: ControlloImpianti
# ==============================================================================
bashio::log.info "Running interface.sh"
python3 /controlloimpianti/main.py
Questo script esegue l'interfaccia interface.sh
, che a sua volta avvia lo script Python.
#!/usr/bin/with-contenv bashio
# ==============================================================================
# Home Assistant Add-on: ControlloImpianti
# ==============================================================================
bashio::log.info "Starting service.d [Interface]"
bashio::log.info "Executing interface script..."
/controlloimpianti/interface.sh
La sequenza organizza correttamente il flusso di esecuzione, con run.sh
che chiama interface.sh
, che a sua volta esegue main.py
.
Cliccare sul pulsante per installare l'Add-On oppure seguire la procedura indicata di seguito.
- Una volta configurato l'Add-On, riavvia Home Assistant.
- Clicca su Impostazioni > Componenti Aggiuntivi.
- Seleziona l'Add-On e poi clicca su Installa.
- Configurare l'addon nel modo giusto per il corretto funzionamento dell'addon.
- Una volta configurato, fai clic su Avvia.
- Dopo l'avvio, sarà possibile vedere tutti i messaggi segnalati dall'Add-On nella sezione Registro.
Nella sezione di configurazione ci sono tre campi: due stringhe e un intero.
-
Primo campo: Specifica il nome dell'evento da monitorare e i dispositivi associati da accendere o spegnere. Deve avere la seguente struttura:
- keyword: <parola_chiave_1> linked_entity_id: - <entity_id_device_1> - <entity_id_device_2> - keyword: <parola_chiave_2> linked_entity_id: - <entity_id_device_3> - <entity_id_device_4>
-
Secondo campo: Inserisci
l'entity_id
del calendario da monitorare, ad esempiocalendar.<nome_calendario>
. -
Terzo campo: Inserisci un numero intero tra 1 e 5 che indica l'intervallo di esecuzione dell'addon.
Il progetto è stato pubblicato a Gennaio 2024, nella sua prima versione ed è attualmente funzionante. Sviluppi futuri da definire.
Francesco Sparascio, Francesco Rinaldi