Controllo impianti tramite Home Assistant ed eventi Google

Controllo Impianti - Home Assistant

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.

Open source e documentazione

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.

Aggiungere e configurare ESPHome

  • 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.

Configurare il dispositivo

  • 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.

dashboard_empty

connect_device

prepare_for_first_use

dasboard_full

Descrizione configurazione dispositivo

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" );   

Programmare il dispositivo

  1. Per modificare il codice cliccare su Edit.
  2. Cliccare sul pulsante in alto a destra INSTALL.
  3. La prima volta si deve procedere con l'istallazione manuale cliccando su Plug into this computer.
  4. Attendere la compilazione del codice.
  5. Una volta terminata, cliccare su Download Project che scaricherà un file con estensione .bin.
  6. Cliccare su Open ESPHome Web che aprirà una nuova pagina web.
  7. Cliccare su Connect e caricare il file .bin scaricato in precedenza.
  8. 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

install_code

Aggiungere controllo impianti alla plancia

  1. Per aggiungere alla plancia una vista cliccare sulla barra laterale su Panoramica.
  2. Cliccare sul pulsante + per aggiungere una nuova vista.
  3. Cliccare sul pulsante in basso a destra AGGIUNGI SCHEDA.
  4. Digitare entità e selezionare la prima nella ricerca.
  5. Assegnare un titolo alla vista.
  6. Selezionare le entità configurate in precedenza.
  7. 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.

set_view

set_view_2

Monitoraggio calendario e accensione / spegnimento switch

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.

Implementazione Add-On

  1. Andare in Esplora File e digitare sulla barra di ricerca \\IP_HOME_ASSISTANT>\addons

  2. Creare la cartella ControlloImpianti

  3. 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
    

Il file Dockerfile

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 /
  1. Utilizza l'immagine di base specificata dalla variabile BUILD_FROM.
  2. Aggiorna l'elenco dei pacchetti e aggiorna i pacchetti installati all'ultima versione.
  3. Installa i pacchetti necessari per il funzionamento dell'Add-On.
  4. Installa i moduli Python specifici utilizzando pip3.
  5. Copia il contenuto della directory rootfs nel file system del container.

Il file config.yaml

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)"
  1. version: "1.0.0"
    • Specifica la versione corrente dell'addon per la gestione degli aggiornamenti.
  2. name: Controllo Impianti
    • Nome visualizzato dell'addon nell'interfaccia utente di Home Assistant.
  3. description:
    • Descrive brevemente le funzionalità, fornendo una panoramica rapida.
  4. slug: controlloimpianti
    • Identificatore univoco usato internamente da Home Assistant.
  5. arch:
    • Indica le architetture hardware supportate.
  6. init: false
    • Specifica se l'addon richiede un processo di inizializzazione.
  7. homeassistant_api: true
    • Abilita l'accesso alle API di Home Assistant.
  8. panel_admin: false
    • Definisce se l'addon dovrebbe avere un pannello amministrativo.
  9. panel_title:
    • Titolo del pannello nell'interfaccia utente.
  10. panel_icon:
    • Icona del pannello utilizzando la libreria Material Design Icons.
  11. map:
    • Monta la directory share con permessi di lettura e scrittura.

Il file build.yaml

---
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

I file main.py e impiantiConf.py

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. Utilizza categorize_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 funzione switch_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)

Il file interface.sh

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

Il file run.sh

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.

Installazione Add-On

Cliccare sul pulsante per installare l'Add-On oppure seguire la procedura indicata di seguito.

Supervisor Add Addon Repository

  1. Una volta configurato l'Add-On, riavvia Home Assistant.
  2. Clicca su Impostazioni > Componenti Aggiuntivi.
  3. Seleziona l'Add-On e poi clicca su Installa.
  4. Configurare l'addon nel modo giusto per il corretto funzionamento dell'addon.
  5. Una volta configurato, fai clic su Avvia.
  6. Dopo l'avvio, sarà possibile vedere tutti i messaggi segnalati dall'Add-On nella sezione Registro.

Configurazione Add-On

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 esempio calendar.<nome_calendario>.

  • Terzo campo: Inserisci un numero intero tra 1 e 5 che indica l'intervallo di esecuzione dell'addon.

Stato attuale del progetto

Il progetto è stato pubblicato a Gennaio 2024, nella sua prima versione ed è attualmente funzionante. Sviluppi futuri da definire.

Autori

Francesco Sparascio, Francesco Rinaldi