Controllo PTZ ONVIF di una Telecamera C21A in Home Assistant con Picture Glance

Se utilizzi una telecamera PTZ compatibile ONVIF e vuoi controllarla direttamente dalla dashboard di Home Assistant, puoi creare una card con i pulsanti direzionali integrati sull’immagine live della telecamera.

Nel mio caso ho utilizzato una C21A configurata tramite integrazione ONVIF. Dopo varie prove ho scelto la modalità RelativeMove, che garantisce uno spostamento preciso ad ogni pressione del pulsante.

Risultato finale

La card mostra:

  • Streaming live della telecamera
  • Pulsante Su
  • Pulsante Giù
  • Pulsante Sinistra
  • Pulsante Destra

Ogni clic genera uno spostamento relativo della telecamera.

Codice YAML completo

type: picture-glance
title: Telecamera C21A
camera_image: camera.c21a_mainstream

entities:
  - entity: camera.c21a_mainstream
    icon: mdi:arrow-up
    name: Su
    tap_action:
      action: perform-action
      perform_action: onvif.ptz
      target:
        device_id: ff8e451cca5103ba81271e631a2b89ba
      data:
        move_mode: RelativeMove
        tilt: UP
        distance: 0.3
        speed: 1

  - entity: camera.c21a_mainstream
    icon: mdi:arrow-down
    name: Giù
    tap_action:
      action: perform-action
      perform_action: onvif.ptz
      target:
        device_id: ff8e451cca5103ba81271e631a2b89ba
      data:
        move_mode: RelativeMove
        tilt: DOWN
        distance: 0.3
        speed: 1

  - entity: camera.c21a_mainstream
    icon: mdi:arrow-left
    name: Sinistra
    tap_action:
      action: perform-action
      perform_action: onvif.ptz
      target:
        device_id: ff8e451cca5103ba81271e631a2b89ba
      data:
        move_mode: RelativeMove
        pan: RIGHT
        distance: 0.3
        speed: 1

  - entity: camera.c21a_mainstream
    icon: mdi:arrow-right
    name: Destra
    tap_action:
      action: perform-action
      perform_action: onvif.ptz
      target:
        device_id: ff8e451cca5103ba81271e631a2b89ba
      data:
        move_mode: RelativeMove
        pan: LEFT
        distance: 0.3
        speed: 1

Regolazione della sensibilità

Il parametro più importante è:

YAML

distance:

Più il valore è alto, maggiore sarà lo spostamento della telecamera per ogni click.

Esempi pratici:

YAML

distance: 0.05

Movimento molto piccolo.

YAML

distance: 0.2

Movimento medio.

YAML

distance: 0.3

Buon compromesso per la maggior parte delle PTZ.

YAML

distance: 0.5

Movimento molto evidente.

Attenzione alle direzioni

Alcune telecamere interpretano i comandi ONVIF in modo invertito. Nel mio caso ho dovuto invertire i valori:

YAML

pan: RIGHT

per il pulsante Sinistra e

YAML

pan: LEFT

per il pulsante Destra.

Se la telecamera si muove nella direzione sbagliata, è sufficiente invertire questi due parametri.

Considerazioni finali

Dopo diversi test ho verificato che il problema non era Home Assistant ma la regolazione dello spostamento. Riducendo troppo il valore distance la telecamera sembrava “lentissima” perché ogni click produceva un movimento minimo.

Con:

YAML

distance: 0.3

speed: 1

il controllo risulta molto più fluido e pratico per l’uso quotidiano dalla dashboard di Home Assistant.

Configurazione testata con telecamera PTZ ONVIF integrata in Home Assistant usando una card Picture Glance per il controllo diretto dei movimenti Pan/Tilt.

Datouboss e Solar Thing

Solar of Things per Home Assistant: monitoraggio e controllo completo degli inverter Siseli

Se possiedi un inverter compatibile con il portale Siseli e utilizzi Home Assistant per la gestione della tua smart home, l’integrazione Solar of Things rappresenta una delle soluzioni più interessanti per portare la gestione dell’impianto fotovoltaico all’interno della piattaforma domotica più diffusa al mondo. 

Cos’è Solar of Things?

Solar of Things è un’integrazione personalizzata per Home Assistant che consente di collegarsi al portale Siseli e acquisire dati energetici in tempo reale direttamente dagli inverter associati al proprio impianto fotovoltaico. 

Una volta configurato lo Station ID, il sistema rileva automaticamente tutti gli inverter presenti nella stazione, creando sensori e controlli senza configurazioni aggiuntive. 

Monitoraggio energetico in tempo reale

L’integrazione mette a disposizione oltre dieci sensori aggiornati ogni cinque minuti, tra cui: 

  • Potenza FV in ingresso dai pannelli
  • Potenza AC erogata ai carichi
  • Corrente di carica della batteria
  • Corrente di scarica della batteria
  • Tensione banco batterie
  • Potenza netta della batteria
  • Stato di carica (SOC)
  • Energia immessa in rete
  • Energia prelevata dalla rete
  • Consumo totale dell’abitazione

Questi dati consentono di costruire dashboard avanzate e automazioni intelligenti per massimizzare l’autoconsumo energetico. 

Statistiche mensili automatiche

Oltre ai dati istantanei, sono disponibili sensori riepilogativi aggiornati ogni 30 minuti che permettono di monitorare: 

  • Produzione fotovoltaica mensile
  • Energia prelevata dalla rete
  • Consumo totale della casa
  • Percentuale di copertura solare

Grazie a queste informazioni è possibile valutare facilmente l’efficienza energetica dell’impianto e il reale livello di autosufficienza raggiunto. 

Controllo remoto dell’inverter

Uno degli aspetti più interessanti di Solar of Things è la possibilità di modificare alcuni parametri dell’inverter direttamente da Home Assistant. 

Tra le funzionalità disponibili troviamo:

Gestione batteria

  • Limite massimo di carica
  • Limite minimo di scarica
  • Potenza massima di ricarica da rete

Priorità energetiche

  • Utility First (USO)
  • Solar First (SUB)
  • Solar + Battery First (SBU)

Priorità di ricarica

  • Solare + Rete
  • Solare Prioritario
  • Solo Solare

Interruttori di controllo

  • Abilitazione ricarica da rete
  • Abilitazione immissione in rete
  • Modalità backup con riserva batteria

Le funzioni disponibili dipendono comunque dal firmware e dalle API supportate dal proprio inverter. 

Compatibilità con Energy Dashboard

Tutti i principali sensori energetici sono progettati per integrarsi con l’Energy Dashboard di Home Assistant. Questo permette di visualizzare grafici storici, flussi energetici e statistiche direttamente nell’interfaccia standard della piattaforma. 

Installazione tramite HACS

L’installazione consigliata avviene tramite HACS:

  1. Aprire HACS.
  2. Selezionare “Custom Repository”.
  3. Inserire il repository Solar of Things.
  4. Installare l’integrazione.
  5. Riavviare Home Assistant.
  6. Configurare account Siseli e Station ID. 

Perché utilizzare Solar of Things?

Per gli utenti Home Assistant che vogliono una gestione completa del proprio impianto fotovoltaico, Solar of Things offre un equilibrio ideale tra semplicità e funzionalità avanzate. Grazie all’autodiscovery degli inverter, ai sensori dettagliati e alle funzioni di controllo remoto, l’integrazione permette di trasformare il sistema fotovoltaico in una parte integrante della domotica domestica. 

Conclusioni

Solar of Things è una soluzione particolarmente interessante per chi utilizza inverter collegati al portale Siseli e desidera monitorare produzione, consumi e batterie direttamente da Home Assistant. L’integrazione offre dati in tempo reale, statistiche energetiche e funzioni di controllo avanzate, rendendo possibile una gestione più efficiente dell’energia prodotta dal proprio impianto fotovoltaico. 

SEO Title: Solar of Things per Home Assistant: guida completa agli inverter Siseli

Meta Description: Scopri Solar of Things per Home Assistant. Monitoraggio in tempo reale, controllo batterie, gestione inverter Siseli e integrazione completa con Energy Dashboard. 

Monitoraggio Corrente Batteria con ESP32, ADS1115 e Sensore Hall QNHCK2-16 da 100A

Se stai realizzando un sistema di monitoraggio energetico per Home Assistant o ESPHome, una soluzione economica e precisa consiste nell’utilizzare un ESP32 insieme a un convertitore ADC ADS1115 e un sensore di corrente Hall QNHCK2-16.

In questo articolo vedremo passo passo:

  • Come funziona il sensore Hall
  • Come collegare ESP32, ADS1115 e QNHCK2-16
  • Come leggere la corrente di carica e scarica della batteria
  • Il codice ESPHome completo e funzionante
  • Come calibrare il sensore

Materiale utilizzato

ESP32

Scheda WiFi utilizzata per ESPHome e Home Assistant.

ADS1115

Convertitore ADC a 16 bit.

Vantaggi:

  • Risoluzione molto superiore all’ADC interno dell’ESP32
  • Comunicazione I²C
  • Elevata precisione
  • 4 ingressi analogici disponibili

Sensore Hall QNHCK2-16

Modello acquistato:

QNHCK2-16
100A
2.5V ±0.625V
Alimentazione 5V
Corrente nominale: ±100A
Tensione a 0A: 2.5V
Tensione a +100A: 3.125V
Tensione a -100A: 1.875V
Sensibilità: 6.25mV/A

Come funziona il sensore Hall

Il sensore non richiede il contatto elettrico diretto con il cavo.

È sufficiente far passare il cavo all’interno della finestra del sensore.

Esempio:

Batteria (+)
      │
      │
      ▼
 ┌───────────┐
 │ QNHCK2-16 │
 └───────────┘
      │
      │
      ▼
 Inverter

Il sensore genera una tensione proporzionale alla corrente.

Per il modello da 100A:

CorrenteTensione
-100A1.875V
-50A2.1875V
0A2.500V
+50A2.8125V
+100A3.125V

Piedinatura del sensore

Dal corpo del sensore escono 4 fili.

ColoreFunzione
Rosso+5V
NeroGND
VerdeVOUT
BluVREF

Utilizzeremo solamente:

Rosso
Nero
Verde

Collegamenti ESP32 ↔ ADS1115

ADS1115ESP32
VDD3.3V
GNDGND
SDAGPIO21
SCLGPIO22

Collegamenti Sensore ↔ ADS1115

Sensore HallADS1115
VOUT (verde)A0
GND (nero)GND

Alimentazione Sensore

Sensore HallESP32
Rosso5V
NeroGND

Schema completo

                 ESP32
            ┌─────────────┐
            │             │
            │ GPIO21 SDA  ├──────────── SDA ADS1115
            │ GPIO22 SCL  ├──────────── SCL ADS1115
            │ 3.3V        ├──────────── VDD ADS1115
            │ GND         ├──────────── GND ADS1115
            │ GND         ├──────────── GND Sensore
            │ 5V          ├──────────── +5V Sensore
            │             │
            └─────────────┘

                          ADS1115
                    ┌─────────────┐
                    │             │
                    │ A0          ├────────── VOUT Sensore
                    │             │
                    └─────────────┘

                     QNHCK2-16
                   ┌────────────┐
                   │ Rosso +5V  │
                   │ Nero GND   │
                   │ Verde VOUT │
                   └────────────┘

Formula di conversione

Per il modello acquistato:

0A = 2.5V

Sensibilità:

0.625V / 100A

= 0.00625V/A

Formula:

Corrente = (Tensione - 2.5) / 0.00625

Codice ESPHome completo

# Board: ESP32 DevKit V1 (Espressif)
# Definition: definitions/boards/esp32-devkit-v1/manifest.yaml

esphome:
  name: esp32-shunt
  friendly_name: ESP32 shunt

esp32:
  variant: esp32
  flash_size: 4MB
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: "6nWLRhmkOvM2QjtcRAAtkBXzZ1XVOB+e+8XU8HcuLV8="

ota:
  - platform: esphome

wifi:
  ssid: xx
  password: xx
  ap:
    ssid: ESP32 shunt Fallback Hotspot
    password: "xxxx"

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true

ads1115:
  - address: 0x48

sensor:
  - platform: ads1115
    multiplexer: A0_GND
    gain: 4.096
    sample_rate: 860  # Portato a 860 sps per mediare via l'effetto ripple dell'inverter
    id: hall_voltage_raw
    name: "Hall Voltage"
    update_interval: 500ms
    unit_of_measurement: "V"
    accuracy_decimals: 3

  - platform: template
    name: "Corrente Batteria"
    id: corrente_batteria
    unit_of_measurement: "A"
    device_class: current
    state_class: measurement
    accuracy_decimals: 2
    update_interval: 500ms

    lambda: |-
      float volts = id(hall_voltage_raw).state;
      if (isnan(volts)) return {};
      return volts;
    filters:
      # 1. Zero centrato sul punto medio esatto del rumore (2.495V)
      - calibrate_linear:
          - 1.876 -> -100.0   # (1.881 - 0.005)
          - 2.495 -> 0.0      # Punto medio reale tra 2.494V e 2.496V
          - 3.126 -> 100.0    # (3.131 - 0.005)

      # 2. Elimina i picchi spuri
      - median:
          window_size: 5
          send_every: 1

      # 3. Media mobile per la massima stabilità
      - exponential_moving_average:
          alpha: 0.05
          send_every: 1

      # 4. Zona morta portata a 0.25A per assorbire l'oscillazione +/- 0.16A
      - lambda: |-
          if (std::abs(x) < 0.25) {
            return 0.0;
          }
          return x;

Calibrazione dello zero

Ogni sensore presenta una piccola tolleranza.

Con batteria senza carica e senza scarica controllare il valore:

Hall Voltage

Potresti leggere:

2.503V


oppure:

2.497V

In tal caso modificare:

const float VZERO = 2.500;

con:

const float VZERO = 2.503;

oppure:

const float VZERO = 2.497;

ottenendo una lettura molto più precisa.

Monitoraggio carica e scarica separatamente

Per un impianto fotovoltaico è consigliabile utilizzare due sensori.

Sensore 1

MPPT → Batteria

Misura la corrente di carica.

Sensore 2

Batteria → Inverter

Misura la corrente di scarica.

Collegamenti:

Sensore 1 → A0 ADS1115
Sensore 2 → A1 ADS1115

In questo modo Home Assistant mostrerà separatamente:

  • Corrente di carica
  • Corrente di scarica
  • Potenza di carica
  • Potenza di scarica

Una soluzione molto interessante per il tuo impianto Victron perché permette di avere un monitoraggio locale tramite ESPHome e confrontare i dati con quelli dello SmartShunt e degli SmartSolar.

Guida Completa ai Gateway Ecowitt GW1100 / GWI100: Installazione, Configurazione e Gestione dei Sensori

I gateway Ecowitt della serie GWI100 / GW1100 rappresentano il cuore di una stazione meteo personale moderna. Questi dispositivi raccolgono i dati provenienti dai sensori wireless Ecowitt e li inviano sia alla rete locale sia ai principali servizi meteorologici online. In questa guida completa scoprirai come configurarli, come collegare i sensori e come sfruttare tutte le funzioni disponibili.


Cos’è il Gateway Ecowitt GW1100 / GWI100

Il GW1100 è un piccolo hub Wi‑Fi che riceve i dati dai sensori Ecowitt (temperatura, umidità, vento, pioggia, UV, luminosità, ecc.) e li rende disponibili:

  • tramite pagina web interna
  • tramite app Ecowitt
  • sul portale ecowitt.net
  • su servizi meteo esterni come Wunderground, WeatherCloud, WOW
  • su server personalizzati

È compatto, facile da configurare e compatibile con un’ampia gamma di sensori Ecowitt.


1. Prima Accensione e Accesso al Dispositivo

1.1 Avvio del gateway

Dopo aver alimentato il dispositivo:

  • attendi l’accensione
  • premi il pulsante RESET per 5 secondi per entrare in modalità configurazione

1.2 Connessione al Wi‑Fi del gateway

Il dispositivo crea un access point temporaneo:

GWI100X-XXXXXXXX

Collegati con il tuo smartphone.

1.3 Accesso alla pagina web

Apri il browser e digita:

192.168.4.1

Al primo accesso non è richiesta alcuna password.


2. Configurazione del Dispositivo

2.1 Impostazioni principali

Dalla pagina Device Setting puoi configurare:

  • Nome del dispositivo
  • Fuso orario
  • Localizzazione
  • Unità di misura
  • Password di accesso (opzionale)

Se abiliti Auto Timezone, ricordati di impostare il fuso orario corretto anche su ecowitt.net.


3. Collegamento alla Rete Wi‑Fi Domestica

3.1 Configurazione rete locale

Nella sezione Local Network:

  1. seleziona la tua rete Wi‑Fi
  2. inserisci la password
  3. premi Apply
  4. verifica che il gateway abbia ottenuto un indirizzo IP

4. Invio dei Dati ai Servizi Meteo

Il gateway può inviare i dati a diversi servizi online.

4.1 Ecowitt.net

È il servizio principale e più completo.

Per configurarlo:

  • copia il MAC address del gateway
  • registrati su ecowitt.net
  • aggiungi il dispositivo
  • imposta l’intervallo di upload (minimo 1 minuto)

4.2 Wunderground, WeatherCloud, WOW

Per ciascun servizio devi inserire:

  • Station ID
  • Station Key

4.3 Server personalizzato

Puoi inviare i dati anche a un tuo server:

  • protocollo Ecowitt o Wunderground
  • IP/hostname
  • path (es. /data/report/)
  • porta
  • intervallo di upload

5. Gestione dei Sensori Ecowitt

Il gateway supporta numerosi sensori. Ecco i principali:

5.1 Sensori di temperatura e umidità

  • WH31: multicanale (fino a 8 canali)
  • WH32: sensore esterno prioritario

5.2 Sensori di pioggia

  • WH40: pluviometro con livella a bolla

5.3 Sensori di vento

  • WS68: anemometro + UV + luce
  • WS80: anemometro ultrasonico

5.4 Sensori combinati

  • WS69: array completo (vento, pioggia, T/H, UV, luce)

6. Installazione dei Sensori

Il manuale raccomanda:

  • installare i sensori in giornate asciutte
  • verificare la ricezione del segnale prima del montaggio definitivo
  • evitare ostacoli metallici
  • montare il sensore del vento in posizione elevata
  • mantenere il pluviometro perfettamente in bolla

7. Avvertenze Importanti

  • Non installare durante un temporale
  • I pali metallici possono attirare fulmini
  • Valuta la messa a terra se monti su un edificio
  • Esegui test preliminari a terra

Conclusione

Il gateway Ecowitt GW1100/GWI100 è uno strumento potente e versatile per creare una stazione meteo completa e affidabile. Con pochi passaggi puoi configurarlo, collegarlo alla rete, aggiungere sensori e inviare i dati ai principali servizi meteorologici.

Questa guida ti permette di sfruttare al massimo tutte le funzioni del dispositivo.

template:
  - sensor:
      - name: "FV Potenza Teorica Reale"
        unique_id: fv_potenza_teorica_reale
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set G_raw = states('sensor.gw1100a_solar_radiation') %}
          {% set T_raw = states('sensor.gw1100a_outdoor_temperature') %}
          {% if G_raw in ['unknown','unavailable','none'] or T_raw in ['unknown','unavailable','none'] %}
            unavailable
          {% else %}
            {% set modules_count = 4 %}
            {% set P_stc_module = 585.0 %}
            {% set area_module = 2.582 %}
            {% set eta = 0.2266 %}
            {% set noct = 45 %}
            {% set gamma_p = -0.003 %}
            {% set tilt_deg = 30 %}
            {% set albedo = 0.20 %}
            {% set sensor_height_factor = 1.15 %}
            {% set G = (G_raw | float(0)) * sensor_height_factor %}
            {% set T_amb = (T_raw | float(20)) %}
            {% set _ = states('sensor.time') %}
            {% set area_total = area_module * modules_count %}
            {% set clearness = G / 1000.0 %}
            {% set df_raw = 0.45 + (1 - clearness) * 0.35 %}
            {% set diffuse_frac = [df_raw, 0.9] | min %}
            {% set diffuse_frac = [diffuse_frac, 0.1] | max %}
            {% set direct_frac = 1.0 - diffuse_frac %}
            {% set tilt_rad = (tilt_deg * pi / 180.0) %}
            {% set direct_factor = [ (cos(tilt_rad)), 0.0 ] | max %}
            {% set diffuse_factor = (1.0 + cos(tilt_rad)) / 2.0 %}
            {% set ground_reflect_factor = (1.0 - cos(tilt_rad)) / 2.0 %}
            {% set G_poa = G * ( direct_frac * direct_factor + diffuse_frac * diffuse_factor + albedo * ground_reflect_factor ) %}
            {% set P_stc_total = P_stc_module * modules_count %}
            {% set P_from_irradiance = (G_poa / 1000.0) * P_stc_total %}
            {% set T_cell = T_amb + (G_poa / 800.0) * (noct - 20.0) %}
            {% set deltaT = T_cell - 25.0 %}
            {% set P_temp_corr = P_from_irradiance * (1.0 + gamma_p * deltaT) %}
            {{ [ (P_temp_corr) | round(0), 0 ] | max }}
          {% endif %}

Il Motore della Previsione Solare: Un’Analisi Passo-Passo delle Formule dietro i Pannelli Fotovoltaici ☀️🔬


👋 Introduzione: Quando la Tecnologia Prevede il Futuro

Hai mai voluto sapere non solo quanta energia produce oggi il tuo impianto solare, ma quanto potrebbe produrre nel momento esatto? Questo è il potere dei “Template” avanzati che utilizziamo nei sistemi di casa intelligente. Questi Template sono veri e propri mini-calcolatori, scritti in un linguaggio chiamato Jinja2, capaci di trasformare semplici letture meteo (come l’intensità del sole o la temperatura) in una previsione molto dettagliata della potenza reale prodotta dal tuo impianto FV.

In questo articolo, smonteremo questa “ricetta digitale” per capire esattamente quali formule vengono usate e perché ogni piccolo numero conta. Prepariamoci a fare un viaggio didattico tra fisica e tecnologia!

🧱 La Struttura: Ingredienti e Obiettivo

Il template ha due compiti principali:

  1. Controllare la Salute: Verificare che tutti i dati di ingresso siano presenti e validi.
  2. Simulare la Produzione: Usando le leggi della fisica, calcolare quanta energia il pannello riceve e quanta ne riesce a trasformare.

Gli Ingredienti Necessari (I Dati Iniziali)

Il sistema parte da due letture chiave dai sensori esterni:

  • GrawGraw​: L’intensità della luce solare che arriva sull’area di misura (Irradiazione GlobaleIrradiazione Globale).
  • TrawTraw​: La temperatura dell’aria all’esterno.

📐 Analisi Passo-Passo: Le Formule Semplificate (Il Cuore del Template)

Il template non fa un singolo calcolo, ma una catena di trasformazioni. Vediamo i passaggi chiave e le formule che li guidano.

Fase 1: Preparazione della Luce

Prima di tutto, dobbiamo capire quanta luce è davvero utile per il pannello e come questa luce cade su di esso.

  • Aggiustamento dell’Altezza (GG): Il template prende l’intensità grezza (GrawGraw​) e la corregge leggermente con un fattore (sensor_height_factor = 0.98). Cosa significa? È una piccola correzione per tenere conto del fatto che il sensore non è perfettamente allineato o misurato alla stessa altezza dell’impianto reale. G=Graw×0.98G=Graw​×0.98
  • La Distribuzione della Luce (DiffuseDiffuse vs DirectDirect): Il sole non è sempre un fascio dritto e perfetto; a volte la luce viene “sparsa” dall’atmosfera.
    • Il template calcola una frazione diffusa basandosi su quanto forte sta brillando il cielo (clearnessclearness). Questa frazione ci dice quanta luce arriva da tutte le direzioni, non solo dal punto esatto del sole.
    • Successivamente, usa i dati di orientamento (tilt_deg=30∘tilt_deg=30∘) per capire quanto è efficace la superficie del pannello a catturare quella luce diretta e diffusa.

Fase 2: Calcolare l’Energia Reale sul Pannello (GPOAGPOA​)

Questo è il passaggio fondamentale! Stiamo chiedendo: “Quanta energia colpisce davvero la superficie orientata del pannello?”

Il template mescola le frazioni di luce diretta, diffusa e riflessa dal terreno (l’albedo) con i fattori geometrici legati all’angolo di inclinazione ($30^\circ$). La formula combina questi elementi per ottenere GPOAGPOA​:

GPOA=G×(Frazione Diretta×Fattore Diretto+Frazione Diffusa×Fattore Diffuso+Riflesso Terreno×Fattore Riflesso)GPOA​=G×(Frazione Diretta×Fattore Diretto+Frazione Diffusa×Fattore Diffuso+Riflesso Terreno×Fattore Riflesso)

In parole semplici: Stiamo dando al sistema una mappa precisa di come la luce cade sul pannello, tenendo conto sia dell’orientamento che del tipo di cielo.

Fase 3: La Potenza Iniziale (Dal Calore alla Forza)

Una volta che sappiamo quanta energia colpisce il pannello (GPOAGPOA​), dobbiamo trasformarla in Watt. Usiamo la potenza nominale massima (PSTC_totalPSTC_total​, ovvero quanto produce l’impianto in condizioni perfette di laboratorio) e la confrontiamo con l’irradiazione ricevuta:

Potenza Iniziale=(GPOA1000.0)×PSTC_totalPotenza Iniziale=(1000.0GPOA​​)×PSTC_total​ (Il diviso per 1000 serve perché le nostre misurazioni sono spesso in W/m2W/m2, mentre la potenza nominale è espressa su tutta l’area del modulo).

Fase 4: La Correzione Finale (L’Effetto della Temperatura)

Questo è il tocco di finitura. Anche se tutto va bene, quando i pannelli si scaldano troppo, diventano meno efficienti. Il template simula questo calore e applica una “penalità” basata sul delta termico (ΔT=Temperatura Cella−25∘CΔT=Temperatura Cella−25∘C).

La formula di correzione finale è questa: Potenza Corretta=Potenza Iniziale×(1+γp⋅ΔT)Potenza Corretta=Potenza Iniziale×(1+γp​⋅ΔT)

Spiegazione Semplice: Se il pannello si scalda molto (ΔTΔT alto), e γpγp​ è un numero negativo, la parentesi (1+...)(1+…) diventa minore di 1, riducendo artificialmente la potenza calcolata. Questo assicura che la nostra previsione sia realistica.

✅ Conclusione: Un Modello Potente ma Semplificato

Il template è estremamente potente perché automatizza un processo che, se lo facessi a mano, richiederebbe ore di calcoli trigonometrici e termici!

Cosa abbiamo imparato? Abbiamo visto che il valore finale in Watt non dipende solo da “quanto c’è sole”, ma da: come cade la luce sul mio pannello (GPOAGPOA​), e quanto è caldo quel pannello (Correzione Termica).

È un esempio brillante di come i dati, quando vengono incartati con una buona dose di logica scolastica e fisica, possano trasformarsi in strumenti di previsione super diretti per la nostra vita quotidiana.


Codice calcolo potenza presunta pannelli

template:
  - sensor:
      - name: "FV Potenza Teorica Reale"
        unique_id: fv_potenza_teorica_reale
        unit_of_measurement: "W"
        device_class: power
        state_class: measurement
        state: >
          {% set G_raw = states('sensor.gw1100a_solar_radiation') %}
          {% set T_raw = states('sensor.gw1100a_outdoor_temperature') %}
          {% if G_raw in ['unknown','unavailable','none'] or T_raw in ['unknown','unavailable','none'] %}
            unavailable
          {% else %}
            {% set modules_count = 4 %}
            {% set P_stc_module = 585.0 %}
            {% set area_module = 2.582 %}
            {% set eta = 0.2266 %}
            {% set noct = 45 %}
            {% set gamma_p = -0.003 %}
            {% set tilt_deg = 30 %}
            {% set albedo = 0.20 %}
            {% set sensor_height_factor = 0.98 %}
            {% set G = (G_raw | float(0)) * sensor_height_factor %}
            {% set T_amb = (T_raw | float(20)) %}
            {% set _ = states('sensor.time') %}
            {% set area_total = area_module * modules_count %}
            {% set clearness = G / 1000.0 %}
            {% set df_raw = 0.45 + (1 - clearness) * 0.35 %}
            {% set diffuse_frac = [df_raw, 0.9] | min %}
            {% set diffuse_frac = [diffuse_frac, 0.1] | max %}
            {% set direct_frac = 1.0 - diffuse_frac %}
            {% set tilt_rad = (tilt_deg * pi / 180.0) %}
            {% set direct_factor = [ (cos(tilt_rad)), 0.0 ] | max %}
            {% set diffuse_factor = (1.0 + cos(tilt_rad)) / 2.0 %}
            {% set ground_reflect_factor = (1.0 - cos(tilt_rad)) / 2.0 %}
            {% set G_poa = G * ( direct_frac * direct_factor + diffuse_frac * diffuse_factor + albedo * ground_reflect_factor ) %}
            {% set P_stc_total = P_stc_module * modules_count %}
            {% set P_from_irradiance = (G_poa / 1000.0) * P_stc_total %}
            {% set T_cell = T_amb + (G_poa / 800.0) * (noct - 20.0) %}
            {% set deltaT = T_cell - 25.0 %}
            {% set P_temp_corr = P_from_irradiance * (1.0 + gamma_p * deltaT) %}
            {{ [ (P_temp_corr) | round(0), 0 ] | max }}
          {% endif %}

✨ Sistema RFID con ESP32, RC522, OLED, Relè, LED e Buzzer

Una guida completa per realizzare un accesso elettronico affidabile, elegante e totalmente integrato con ESPHome.

—

🔧 Componenti necessari

  • ESP32 DevKit
  • Lettore RFID RC522 (SPI)
  • Display OLED SSD1306 128×64 (I2C)
  • Modulo relè 5V
  • LED verde + LED rosso (con resistenze 220–330 Ω)
  • Buzzer attivo a 3 pin
  • Alimentazione 5V
  • Cavi dupont

—

🧩 Collegamenti elettrici

Tabella completa dei collegamenti ESP32 → Componenti

RC522 (SPI)

Funzione RC522Pin RC522Pin ESP32
AlimentazioneVCC3.3V
MassaGNDGND
ResetRSTGPIO22
SS / SDASDAGPIO5
SCKSCKGPIO18
MOSIMOSIGPIO23
MISOMISOGPIO19


Display OLED SSD1306 (I2C)

Funzione OLEDPin OLEDPin ESP32
AlimentazioneVCC3.3V
MassaGNDGND
SDASDAGPIO21
SCLSCLGPIO17


LED di stato

LEDAnodo → ESP32Catodo →
VerdeGPIO14 (via resistenza)GND
RossoGPIO27 (via resistenza)GND


Relè cancello

FunzionePin modulo relèPin ESP32
SegnaleINGPIO32
AlimentazioneVCC5V
MassaGNDGND


Buzzer attivo 3 pin

FunzionePin buzzerPin ESP32
AlimentazioneVCC5V
MassaGNDGND
SegnaleI/OGPIO26

💡 Funzionamento del sistema

  • Quando un tag RFID viene avvicinato, l’ESP32 legge l’UID tramite il modulo RC522.
  • Se il tag è autorizzato:
    • Il relè invia un impulso per aprire il cancello
    • Il LED verde si accende per 7 secondi
    • Il display mostra “ACCESSO CONSENTITO”
    • Il buzzer emette due beep
  • Se il tag non è autorizzato:
    • Il LED rosso si accende per 7 secondi
    • Il display mostra “ACCESSO NEGATO”
    • Il buzzer emette tre beep rapidi

—

🧠 Codice ESPHome completo e funzionante

esphome:
  name: esphome-web-568b34
  friendly_name: Esp32 RFID
  min_version: 2025.11.0
  name_add_mac_suffix: false
  on_boot:
    priority: -100
    then:
      - switch.turn_on: rele

esp32:
  variant: esp32
  framework:
    type: esp-idf
    advanced:
      minimum_chip_revision: "3.1"
      sram1_as_iram: true    

logger:
  level: INFO

api:

ota:
  platform: esphome

wifi:
  ssid: Xxxx
  password: xxxx

# ---------------- SPI + RC522 ----------------
spi:
  clk_pin: 18
  mosi_pin: 23
  miso_pin: 19

rc522_spi:
  cs_pin: 5
  reset_pin: 22
  update_interval: 500ms #valore iniziale 1 sc
  on_tag:
    then:
      - lambda: |-
          std::string uid = x;

          uint32_t now = millis();
          if (now - id(ultimo_lettura) < 2000) {
            return;
          }
          id(ultimo_lettura) = now;

          ESP_LOGI("rfid", "Tag rilevato: %s", uid.c_str());

          id(ultimo_tag).publish_state(uid);

          for (auto &t : id(tag_autorizzati)) {
            if (t == uid) {
              ESP_LOGI("rfid", "Accesso consentito");

              id(accesso_consentito).publish_state(true);
              id(accesso_negato).publish_state(false);

              id(led_ok).execute();
              id(display_ok).execute();

              return;
            }
          }

          ESP_LOGW("rfid", "Accesso negato");

          id(accesso_consentito).publish_state(false);
          id(accesso_negato).publish_state(true);

          id(led_ko).execute();
          id(display_ko).execute();

# ---------------- Lista badge autorizzati ----------------
globals:
  - id: tag_autorizzati
    type: std::vector<std::string>
    initial_value: '{"67-89-90-C9","04-52-DD-01-7A-3B-03","67-FA-0A-D9"}'

  - id: ultimo_lettura
    type: uint32_t
    initial_value: '0'

# ---------------- OUTPUT (LED) ----------------
output:
  - platform: gpio
    pin: 14
    id: led_verde_pin

  - platform: gpio
    pin: 27
    id: led_rosso_pin
  
# ---------------- Script vari ----------------
script:
  - id: led_ok
    then:
      - switch.turn_on: buzzer
      - delay: 120ms
      - switch.turn_off: buzzer
      - delay: 120ms
      - switch.turn_on: buzzer
      - delay: 120ms
      - switch.turn_off: buzzer

      - switch.turn_off: rele
      - delay: 2s
      - switch.turn_on: rele

      - light.turn_on: led_verde
      - delay: 7s
      - light.turn_off: led_verde
      - lambda: |-
          id(accesso_consentito).publish_state(false);

  - id: led_ko
    then:
      - switch.turn_on: buzzer
      - delay: 80ms
      - switch.turn_off: buzzer
      - delay: 80ms
      - switch.turn_on: buzzer
      - delay: 80ms
      - switch.turn_off: buzzer
      - delay: 80ms
      - switch.turn_on: buzzer
      - delay: 80ms
      - switch.turn_off: buzzer

      - light.turn_on: led_rosso
      - delay: 7s
      - light.turn_off: led_rosso
      - lambda: |-
          id(accesso_negato).publish_state(false);

  - id: display_ok
    then:
      - display.page.show: page_ok
      - delay: 8s
      - display.page.show: page_idle

  - id: display_ko
    then:
      - display.page.show: page_ko
      - delay: 8s
      - display.page.show: page_idle
  
# ---------------- LED verde e rosso ----------------
light:
  - platform: binary
    id: led_verde
    name: "LED Verde RFID"
    output: led_verde_pin

  - platform: binary
    id: led_rosso
    name: "LED Rosso RFID"
    output: led_rosso_pin

# ---------------- RELÈ ----------------
switch:
  - platform: gpio
    id: rele
    name: "Relè Cancello"
    pin:
      number: 32
      inverted: true
    restore_mode: ALWAYS_ON

  - platform: gpio
    id: buzzer
    name: "Buzzer RFID"
    pin:
      number: 26
    restore_mode: ALWAYS_OFF

# ---------------- SENSORI RFID ----------------
text_sensor:
  - platform: template
    id: ultimo_tag
    name: "Ultimo TAG RFID"
  - platform: wifi_info
    ip_address:
      name: "ESP32 IP Address"
    ssid:
      name: "ESP32 WiFi SSID"
  - platform: version
    name: "Firmware ESPHome"           

button:
  - platform: restart
    id: riavvia_esp
    name: "Riavvia Rfid ESP32"  

  - platform: template
    id: apri_cancello
    name: "Apri Cancello"
    on_press:
      - switch.turn_off: rele
      - delay: 2s
      - switch.turn_on: rele 

      - switch.turn_on: buzzer
      - delay: 120ms
      - switch.turn_off: buzzer
      - delay: 120ms
      - switch.turn_on: buzzer
      - delay: 120ms
      - switch.turn_off: buzzer
      - delay: 120ms
      - switch.turn_on: buzzer
      - delay: 120ms
      - switch.turn_off: buzzer

      - light.turn_on: led_verde
      - delay: 7s
      - light.turn_off: led_verde 
          
binary_sensor:
  - platform: template
    id: accesso_consentito
    name: "Accesso RFID Consentito"

  - platform: template
    id: accesso_negato
    name: "Accesso RFID Negato"

# ---------------- OLED SSD1306 ----------------
i2c:
  sda: 21
  scl: 17
  scan: false
  frequency: 100kHz

font:
  - file: "gfonts://Roboto"
    id: font1
    size: 20

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    id: oled
    update_interval: 500ms
    pages:
      - id: page_idle
        lambda: |-
          it.printf(0, 0, id(font1), "RFID");
          it.printf(0, 28, id(font1), "READY");

      - id: page_ok
        lambda: |-
          it.printf(0, 0, id(font1), "ACCESSO");
          it.printf(0, 28, id(font1), "CONSENTITO");

      - id: page_ko
        lambda: |-
          it.printf(0, 0, id(font1), "ACCESSO");
          it.printf(0, 28, id(font1), "NEGATO");

sensor:
  - platform: uptime
    name: Uptime Sensor
    filters:
      - lambda: return x / 3600;
    unit_of_measurement: "h"
    accuracy_decimals: 2

Come ho stimato il SOC di una batteria LiFePO₄ con ESP32, Victron e ESPHome

In questo articolo ti porto dentro, passo passo, quello che abbiamo fatto insieme: stimare lo stato di carica (SOC) di una batteria LiFePO₄ da 51,2 V usando solo:

  • un ESP32
  • un Victron SmartSolar (via BLE)
  • ESPHome
  • e… un po’ di logica nel codice YAML 😄

Niente BMS con BLE, niente dati “magici” dalla batteria: 👉 il SOC lo ricaviamo noi, a partire dalla tensione.

1. Cos’è il SOC e perché non lo leggiamo dalla batteria

Lo stato di carica (SOC) è, in pratica, “quanta energia c’è ancora dentro la batteria”, espresso in percentuale:

  • 100% → batteria piena
  • 0% → batteria scarica

Nel nostro caso:

  • la batteria è una LiFePO₄ DCHOUSE 51,2 V
  • non abbiamo un collegamento diretto al suo BMS
  • quindi non possiamo leggere il SOC via BLE o CAN

Però abbiamo due cose fondamentali:

  1. La tensione della batteria (letta dal Victron via BLE)
  2. Lo stato del regolatore MPPT (Bulk, Absorption, Float, ecc.)

Da qui nasce l’idea:

“Se conosco la tensione e so in che stato è il caricatore, posso stimare il SOC.”

2. L’idea di base: SOC da tensione

Le batterie LiFePO₄ hanno una curva tensione ↔ SOC abbastanza nota. In pratica, a batteria a riposo:

  • sopra una certa tensione → sei vicino al 100%
  • sotto una certa tensione → sei vicino allo 0%
  • in mezzo → c’è una curva abbastanza piatta ma comunque mappabile

Quello che abbiamo fatto è:

  • definire una serie di punti di riferimento:textv_points = {54.4, 53.6, 53.1, 52.8, 52.3, 52.0, 51.5, 51.2, 48.0, 40.0} soc_points = {100, 90, 80, 70, 60, 45, 30, 20, 10, 0}
  • prendere la tensione letta dal Victron
  • trovare in quale “tratto” della curva ci troviamo
  • interpolare tra i due punti più vicini per ottenere un SOC “continuo”

3. Il cuore del sistema: il sensore template in ESPHome

Tutto il calcolo del SOC avviene in un sensore template di ESPHome.

Codice YAML del sensore SOC

yaml

esphome:
  name: esphome-web-660f74
  friendly_name: Victron 35
  min_version: 2025.11.0
  name_add_mac_suffix: false

esp32:
  variant: esp32
  framework:
    type: esp-idf

logger:
  level: INFO

api:

ota:
- platform: esphome

wifi:
  networks:
    - ssid: xx
      password:xx

esp32_ble_tracker:

external_components:
  - source: github://Fabian-Schmidt/esphome-victron_ble

victron_ble:
  - id: MySmartSolar
    mac_address: "de630e81b151"
    bindkey: "9d9701c7ec7acd40e063725bc6ce5591"

sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Battery Voltage"
    id: battery_voltage
    type: BATTERY_VOLTAGE

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Battery Current"
    id: battery_current
    type: BATTERY_CURRENT

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Yield Today"
    type: YIELD_TODAY

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "PV Power"
    id: pv_power
    type: PV_POWER

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Load Current"
    type: LOAD_CURRENT

  - platform: wifi_signal
    name: "ESP32 WiFi Strength"
    update_interval: 60s

  - platform: integration
    name: "Solar Energy Produced"
    sensor: pv_power
    unit_of_measurement: "Wh"
    time_unit: h
    accuracy_decimals: 2
    state_class: total_increasing
    device_class: energy

  - platform: uptime
    name: Uptime Sensor
    filters:
      - lambda: return x / 3600.0;
    unit_of_measurement: "h"
    accuracy_decimals: 2

  - platform: internal_temperature
    name: "Temperatura interna"
    update_interval: 60s

  # SOC intelligente
  - platform: template
    name: "SOC stimato da tensione"
    id: soc_voltage
    unit_of_measurement: "%"
    accuracy_decimals: 0
    lambda: |-
      float v = id(battery_voltage).state;
      std::string state = id(mppt_state).state;

      // Normalizza in minuscolo
      std::transform(state.begin(), state.end(), state.begin(), ::tolower);

      // 100% SOLO in float
      if (state.find("float") != std::string::npos) {
        if (v >= 54.40) return 100;
      }

      const int N = 10;
      float v_points[N]   = {54.4, 53.6, 53.1, 52.8, 52.3, 52.0, 51.5, 51.2, 48.0, 40.0};
      float soc_points[N] = {100,  90,   80,   70,   60,   40,   30,   20,   10,   0};

      // EVITA 100% fuori dal FLOAT
      if (v >= 54.4) return 99;

      if (v <= 40.0) return 0;

      for (int i = 0; i < N - 1; i++) {
        if (v <= v_points[i] && v > v_points[i + 1]) {
          float t = (v - v_points[i + 1]) / (v_points[i] - v_points[i + 1]);
          return soc_points[i + 1] + t * (soc_points[i] - soc_points[i + 1]);
        }
      }

      return 0;





binary_sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT is in Fault state"
    type: DEVICE_STATE_FAULT

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT has Error"
    type: CHARGER_ERROR

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT in FLOAT"
    id: mppt_in_float
    type: DEVICE_STATE_FLOAT

text_sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT state"
    id: mppt_state
    type: DEVICE_STATE

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT Error reason"
    type: CHARGER_ERROR

  - platform: wifi_info
    ip_address:
      name: "ESP32 IP Address"
    ssid:
      name: "ESP32 WiFi SSID"

  - platform: version
    name: "Firmware ESPHome"

output:
  - platform: gpio
    pin: 2
    id: led_ident

switch:
  - platform: output
    name: "LED Identificazione ESP32"
    output: led_ident

button:
  - platform: restart
    id: riavvia_esp
    name: "Riavvia V35 ESP32"

i2c:
  sda: 21
  scl: 22
  scan: true

font:
  - file: "gfonts://Roboto"
    id: my_font
    size: 18

globals:
  - id: lcd_page
    type: int
    restore_value: no
    initial_value: '0'

  # FLAG: SOC pronto
  - id: soc_ready
    type: bool
    initial_value: 'false'

interval:
  - interval: 1s
    then:
      - lambda: |-
          if (!id(soc_ready) &&
              id(soc_voltage).state > 0 &&
              id(battery_voltage).state > 0) {
            id(soc_ready) = true;
          }

  - interval: 6s
    then:
      - lambda: |-
          id(lcd_page)++;
          if (id(lcd_page) > 4) id(lcd_page) = 0;

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    lambda: |-
      int page = id(lcd_page);

      if (page == 0) {
        it.printf(0, 0, id(my_font), "PV Power:");
        it.printf(0, 20, id(my_font), "%.1f W", id(pv_power).state);
      }

      if (page == 1) {
        it.printf(0, 0, id(my_font), "Batt Volt:");
        it.printf(0, 20, id(my_font), "%.2f V", id(battery_voltage).state);
      }

      if (page == 2) {
        it.printf(0, 0, id(my_font), "Batt Curr:");
        it.printf(0, 20, id(my_font), "%.2f A", id(battery_current).state);
      }

      if (page == 3) {
        it.printf(0, 0, id(my_font), "MPPT State:");
        it.printf(0, 20, id(my_font), "%s", id(mppt_state).state.c_str());
      }

      if (page == 4) {

        if (!id(soc_ready)) {
          it.printf(0, 0, id(my_font), "SOC in attesa...");
          return;
        }

        float soc = id(soc_voltage).state;
        float vbat = id(battery_voltage).state;

        it.printf(0, 0, id(my_font), "SOC Batteria:");
        it.printf(0, 20, id(my_font), "%.0f %%", soc);
        it.printf(0, 40, id(my_font), "%.2f V", vbat);
      }

Cosa fa, in parole semplici

  • Legge la tensione: v = id(battery_voltage).state;
  • Legge lo stato del Victron: state = id(mppt_state).state;
  • Se il Victron è in Float e la tensione è alta → forza 100%
  • Altrimenti:
    • confronta v con i valori in v_points
    • trova il segmento giusto
    • calcola il SOC interpolando tra i due punti più vicini

4. Mostrare il SOC sul display OLED

Una volta calcolato il SOC, lo usiamo nel blocco display: per mostrarlo sul piccolo OLED SSD1306.

Estratto del display (pagina SOC)

yaml

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    lambda: |-
      int page = id(lcd_page);

      // ... altre pagine ...

      if (page == 4) {

        if (!id(soc_ready)) {
          it.printf(0, 2, id(my_font), "SOC in attesa...");
          return;
        }

        float soc = id(soc_voltage).state;
        float vbat = id(battery_voltage).state;

        it.printf(0, 2,  id(my_font), "SOC Batteria:");
        it.printf(0, 22, id(my_font), "%.0f %%", soc);
        it.printf(0, 42, id(my_font), "%.2f V", vbat);
      }

Qui succedono tre cose:

  1. Controllo soc_ready: non mostriamo il SOC finché i valori non sono sensati (tensione e SOC > 0).
  2. Lettura del SOC calcolato:cppfloat soc = id(soc_voltage).state;
  3. Stampa su display:
    • prima riga: testo “SOC Batteria:”
    • seconda riga: valore in %
    • terza riga: tensione in V

5. Il ruolo del flag soc_ready

Per evitare di mostrare numeri “a caso” all’avvio, abbiamo introdotto un flag globale:

yaml

globals:
  - id: soc_ready
    type: bool
    initial_value: 'false'

E un interval che lo attiva solo quando i dati sono validi:

yaml

interval:
  - interval: 1s
    then:
      - lambda: |-
          if (!id(soc_ready) &&
              id(soc_voltage).state > 0 &&
              id(battery_voltage).state > 0) {
            id(soc_ready) = true;
          }

Finché soc_ready è false, sul display appare:

cpp

it.printf(0, 2, id(my_font), "SOC in attesa...");

Così l’utente capisce che il sistema sta ancora “stabilizzando” i dati.

6. Limiti e onestà del metodo

È importante essere chiari: questo SOC è una stima, non un valore assoluto da BMS professionale.

Punti di forza

  • Non richiede BMS con BLE
  • Usa solo ciò che già hai: Victron + ESP32
  • È comprensibile, modificabile, documentabile
  • Per uso domestico/fotovoltaico è più che sufficiente

Limiti strutturali

  • Le LiFePO₄ hanno una curva molto piatta: piccole variazioni di tensione possono spostare molto il SOC stimato
  • Sotto carico o in carica, la tensione è “sporcata” da cadute interne
  • Temperatura, invecchiamento, cavi, ecc. possono spostare la curva reale
  • Non c’è conteggio degli Ah (coulomb counting), quindi non è un SOC “da laboratorio”

7. Perché è comunque una soluzione intelligente

Non stiamo fingendo che la batteria “parli”: 👉 siamo noi a ricavare il SOC, usando:

  • la tensione reale del banco
  • lo stato del Victron (Float)
  • una curva tensione ↔ SOC ragionata
  • un po’ di logica in ESPHome

È una soluzione:

  • trasparente (il codice è leggibile)
  • didattica (capisci cosa succede)
  • pratica (funziona davvero sul campo)

Ed è perfetta per chi vuole:

  • monitorare il proprio impianto fotovoltaico
  • capire come si comporta la batteria
  • avere un’indicazione di SOC senza comprare hardware aggiuntivo

Monitorare un Victron SmartSolar con ESP32, ESPHome e Display OLED SSD1306

Integrare un regolatore Victron SmartSolar con un ESP32 permette di ottenere un monitoraggio locale, immediato e completamente personalizzabile dei parametri principali del proprio impianto fotovoltaico. Questo documento descrive un sistema compatto che legge via BLE i dati del Victron, li elabora con ESPHome e li visualizza su un display OLED SSD1306.

Perché usare un ESP32 con ESPHome

L’ESP32 è un microcontrollore economico, potente e dotato di Bluetooth Low Energy. ESPHome semplifica la configurazione e consente di:

  • Leggere i dati via BLE dal regolatore Victron.
  • Inviarli a Home Assistant.
  • Visualizzarli su un display locale.
  • Creare logiche personalizzate.
  • Aggiornare il firmware OTA.

Il display SSD1306

Il display OLED SSD1306 (128×64 pixel) è ideale per visualizzare informazioni essenziali:

  • Consumo ridotto.
  • Ottima leggibilità.
  • Collegamento semplice tramite I2C.
  • Supporto nativo in ESPHome.

Nel progetto vengono visualizzati ciclicamente:

  1. PV Power (W)
  2. Battery Voltage (V)
  3. Battery Current (A)
  4. Stato MPPT

Collegamenti hardware

SSD1306 → ESP32
SDA     → GPIO 21
SCL     → GPIO 22
VCC     → 3.3V o 5V (in base al modulo)
GND     → GND

Codice completo ESPHome


     esphome:
  name: esphome-web-660f74
  friendly_name: Victron 35
  min_version: 2025.11.0
  name_add_mac_suffix: false

esp32:
  variant: esp32
  framework:
    type: esp-idf
    advanced:
      minimum_chip_revision: "3.1"
      sram1_as_iram: true  

logger:
  level: INFO

api:

ota:
- platform: esphome

wifi:
  networks:
    - ssid:xxx
      password: xxx

esp32_ble_tracker:

external_components:
  - source: github://Fabian-Schmidt/esphome-victron_ble

victron_ble:
  - id: MySmartSolar
    mac_address: "de630e81b151"
    bindkey: "9d9701c7ec7acd40e063725bc6ce5591"

sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Battery Voltage"
    id: battery_voltage
    type: BATTERY_VOLTAGE

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Battery Current"
    id: battery_current
    type: BATTERY_CURRENT

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Yield Today"
    type: YIELD_TODAY

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "PV Power"
    id: pv_power
    type: PV_POWER

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "Load Current"
    type: LOAD_CURRENT

  - platform: wifi_signal
    name: "ESP32 WiFi Strength"
    update_interval: 60s

  - platform: integration
    name: "Solar Energy Produced"
    sensor: pv_power
    unit_of_measurement: "Wh"
    time_unit: h
    accuracy_decimals: 2
    state_class: total_increasing
    device_class: energy

  - platform: uptime
    name: Uptime Sensor
    filters:
      - lambda: return x / 3600.0;
    unit_of_measurement: "h"
    accuracy_decimals: 2

  - platform: internal_temperature
    name: "Temperatura interna"
    update_interval: 60s

  - platform: template
    name: "SOC stimato da tensione"
    id: soc_voltage
    unit_of_measurement: "%"
    accuracy_decimals: 0
    lambda: |-
      float v = id(battery_voltage).state;
      std::string state = id(mppt_state).state;

      // Normalizza in minuscolo
      std::transform(state.begin(), state.end(), state.begin(), ::tolower);

      // 100% SOLO se lo stato è ESATTAMENTE "float"
      if (state == "float" && v >= 54.40) {
        return 100;
      }

      // EVITA 100% fuori dal FLOAT
      if (v >= 54.40) {
        return 99;
      }

      // --- CURVA NORMALE SOTTO 54.40V (tabella LiFePO4) ---
      const int N = 10;
      float v_points[N]   = {54.4, 53.6, 53.1, 52.8, 52.3, 52.2, 52.0, 51.5, 48.0, 40.0};
      float soc_points[N] = {100,  90,   80,   70,   60,   50,   40,   30,   10,   0};

      if (v <= 40.0) return 0;

      for (int i = 0; i < N - 1; i++) {
        if (v <= v_points[i] && v > v_points[i + 1]) {
          float t = (v - v_points[i + 1]) / (v_points[i] - v_points[i + 1]);
          return soc_points[i + 1] + t * (soc_points[i] - soc_points[i + 1]);
        }
      }

      return 0;


binary_sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT is in Fault state"
    type: DEVICE_STATE_FAULT

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT has Error"
    type: CHARGER_ERROR

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT in FLOAT"
    id: mppt_in_float
    type: DEVICE_STATE_FLOAT

text_sensor:
  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT state"
    id: mppt_state
    type: DEVICE_STATE

  - platform: victron_ble
    victron_ble_id: MySmartSolar
    name: "MPPT Error reason"
    type: CHARGER_ERROR

  - platform: wifi_info
    ip_address:
      name: "ESP32 IP Address"
    ssid:
      name: "ESP32 WiFi SSID"

  - platform: version
    name: "Firmware ESPHome"

output:
  - platform: gpio
    pin: 2
    id: led_ident

switch:
  - platform: output
    name: "LED Identificazione ESP32"
    output: led_ident

button:
  - platform: restart
    id: riavvia_esp
    name: "Riavvia V35 ESP32"

i2c:
  sda: 21
  scl: 22
  scan: true

font:
  - file: "gfonts://Roboto"
    id: my_font
    size: 18

globals:
  - id: lcd_page
    type: int
    restore_value: no
    initial_value: '0'

  # FLAG: SOC pronto
  - id: soc_ready
    type: bool
    initial_value: 'false'

interval:
  - interval: 1s
    then:
      - lambda: |-
          if (!id(soc_ready) &&
              id(soc_voltage).state > 0 &&
              id(battery_voltage).state > 0) {
            id(soc_ready) = true;
          }

  - interval: 6s
    then:
      - lambda: |-
          id(lcd_page)++;
          if (id(lcd_page) > 4) id(lcd_page) = 0;

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    lambda: |-
      int page = id(lcd_page);

      if (page == 0) {
        it.printf(0, 0, id(my_font), "PV Power:");
        it.printf(0, 20, id(my_font), "%.1f W", id(pv_power).state);
      }

      if (page == 1) {
        it.printf(0, 0, id(my_font), "Batt Volt:");
        it.printf(0, 20, id(my_font), "%.2f V", id(battery_voltage).state);
      }

      if (page == 2) {
        it.printf(0, 0, id(my_font), "Batt Curr:");
        it.printf(0, 20, id(my_font), "%.2f A", id(battery_current).state);
      }

      if (page == 3) {
        it.printf(0, 0, id(my_font), "MPPT State:");
        it.printf(0, 20, id(my_font), "%s", id(mppt_state).state.c_str());
      }

      if (page == 4) {

        if (!id(soc_ready)) {
          it.printf(0, 0, id(my_font), "SOC in attesa...");
          return;
        }

        float soc = id(soc_voltage).state;
        float vbat = id(battery_voltage).state;

        it.printf(0, 0, id(my_font), "SOC Batteria:");
        it.printf(0, 20, id(my_font), "%.0f %%", soc);
        it.printf(0, 40, id(my_font), "%.2f V", vbat);
      }

Risultato finale

Il display mostra ciclicamente:

  • Potenza PV
  • Tensione batteria
  • Corrente batteria
  • Stato MPPT

Il sistema è autonomo, affidabile e perfetto per monitorare un impianto solare senza aprire Home Assistant.

Spiegazione didattica del funzionamento del codice (ID sensori, globals, interval, display)

1. Perché prima servono gli ID dei sensori

Prima di poter usare un sensore nel display o in una lambda, ESPHome deve sapere come si chiama quel sensore. Questo nome è l’id:.

Senza ID, il display non può leggere il valore del sensore e il codice non compila.

Esempio corretto:

id: battery_voltage


Questo permette al display di usare:

id(battery_voltage).state

Gli ID sono quindi etichette obbligatorie che collegano i sensori al codice del display.

—

2. La variabile globale `lcd_page`

globals:
  - id: lcd_page
    type: int
    restore_value: no
    initial_value: '0'

Questa variabile è un contatore che indica quale pagina del display deve essere mostrata.

  • `type int → è un numero intero
  • `initialvalue: ‘0’ → parte dalla pagina 0
  • `restorevalue: no → al riavvio riparte da 0

È il “segnalibro” del display.

—

3. Il timer `interval` che cambia pagina ogni 5 secondi

interval:
  - interval: 5s
    then:
      - lambda: |-
          id(lcd_page)++;
          if (id(lcd_page) > 3) id(lcd_page) = 0;

Ogni 5 secondi:

  1. aumenta `cd_page di 1
  2. se supera 3, torna a 0

È un ciclo continuo:

0 → 1 → 2 → 3 → 0 → …

Questo permette al display di cambiare pagina automaticamente senza pulsanti.

—

4. La sezione `display` che disegna la pagina corretta

int page = id(lcd_page);

l display legge quale pagina deve mostrare.

Ogni blocco if (page == X) rappresenta una pagina:

  • 0 → PV Power
  • 1 → Battery Voltage
  • 2 → Battery Current
  • 3 → MPPT State

Esempio:

if (page == 0) {
  it.printf(0, 0, id(my_font), "PV Power:");
  it.printf(0, 20, id(my_font), "%.1f W", id(pv_power).state);
}


Il display mostra solo la pagina corrispondente al valore di lcd_page.

📌 Riassunto didattico

  • Prima si definiscono gli ID dei sensori, altrimenti il display non può leggerli.
  • `cd_page è la variabile che tiene memoria della pagina corrente.
  • interval cambia pagina ogni 5 secondi.
  • `isplay legge `cd_page e mostra la pagina giusta.

È un sistema semplice, elegante e molto flessibile.

ESP32 vs Arduino: quando scegliere cosa (e perché)


Per chi si avvicina al mondo IoT o microcontrolli, le due piattaforme più citate sono Arduino e ESP32. Entrambe costano poco e offrono molta flessibilità, ma hanno punti di forza molto diversi. In questo articolo vediamo come e quando l’ESP32 “prende il sopravvento” rispetto ad Arduino in molti progetti moderni.


1. Cosa sono (breve ripasso)

  • Arduino: una piattaforma open-source basata su varie schede, tipicamente con un microcontrollore AVR (es: ATmega328P nel classico Uno).
  • Facile da programmare (IDE semplice, librerie abbondanti)
  • Ottimo per progetti didattici e semplici interfacce fisiche.
  • ESP32: un chip System-on-Chip (SoC) con:
  • CPU dual-core a 240 MHz
  • Wi-Fi + Bluetooth integrati
  • Più RAM e flash del classico Arduino Uno
  • Supporto per RTOS (FreeRTOS), filesystem, stack TCP/IP.

2. Le differenze chiave in pratica

Ecco come si traducono queste caratteristiche in situazioni reali:

a) Connettività

  • Arduino di base ha solo porte I/O e seriale. Per Wi-Fi o Bluetooth serve un modulo esterno (ESP8266, BT42), che complica cablaggi e dipendenze software.
  • ESP32: Wi-Fi + BT integrati. Questo semplifica enormemente progetti IoT: sensori remoti, home automation, nodi mesh wireless, etc.

b) Potenza di calcolo e memoria

  • Un ESP32 ha tipicamente 4–8 MB di RAM e ~512 KB di flash.
  • L’ESP32 può gestire protocolli pesanti (MQTT, HTTP/HTTPS), server web embedded, caching in memoria e più eventi concorrenti senza bloccarsi.
  • Su un Arduino Uno con ATmega328P la RAM è piccolissima (circa 2 KB) e flash limitata (32 KB): si fanno a pezzi anche programmi medi.

c) Architettura e OS

  • Arduino usa loop infinito (setup(), loop()) che può diventare un collo di bottiglia in progetti complessi.
  • ESP32 supporta RTOS: puoi definire task multipli (es: uno per Wi-Fi, uno per sensori, uno per l’UI) e pianificarli indipendentemente.

d) Flessibilità hardware

Entrambi usano GPIO, SPI, I²C, ADC/DAC.
Le differenze sono più nelle capacità extra dell’ESP32:

  • Più canali di ADC (spesso 12 bit).
  • Periferiche avanzate: USB OTG, CAN bus, ecc.
  • Supporto per touch screen capacitivi integrato su alcune schede ESP32.

3. Dove l’ESP32 è più “diffuso” e perché

Ecco alcuni esempi concreti in cui l’ESP32 ha quasi sostituito Arduino:

  1. Progetti IoT semplici (misuratori di temperatura/umidità Wi-Fi, interruttori remoti)
  • ESP32 è “out of the box”: basta scrivere il codice e si connette senza moduli extra.
  1. Home automation / domotica fai-da-te
  • Nodi Zigbee o Z-Wave via Wi-Fi/BT (ESP32 + firmware compatibile).
  • Pannelli di controllo touchscreen integrati.
  1. Wearable e dispositivi connessi a basso costo
  • Smartwatch, tracker fitness DIY, smartwatch con BT audio: ESP32 offre potenza e radio in un unico chip.
  1. Server web embedded / AP personalizzati
  • Access point Wi-Fi che serve una pagina web per configurazione o controllo (es: router mesh personalizzato).
  • Server HTTP leggero per dashboard interne su LAN.
  1. Prototipazione rapida di applicazioni complesse
  • Con RTOS e librerie TCP/IP, ESP32 permette di simulare piccoli server o client senza PC intermediario (ottimo per test IoT).

4. Arduino resta rilevante dove l’ESP32 è “troppo”

Ci sono ambiti in cui Arduino è ancora una scelta migliore:

  • Semplicità estrema
  • Per progetti educativi o didattici, la versione base (Uno/Nano) è più chiara e meno sovraccarica.
  • Basso costo per alta produzione senza connettività
  • Se ti servono centinaia di nodi che leggono un solo sensore via seriale e non Wi-Fi, un Arduino Nano resta spesso più economico.
  • Ambienti industriali legacy o certificati
  • In alcuni contesti si preferiscono microcontrollori con una storia lunga e supporti certi (es: ATmega), anche se meno potenti.

5. Conclusione: “Quando scegliere cosa”

  • Scegli ESP32 se ti serve:
  • Connettività Wi-Fi + BT integrata
  • Più potenza di calcolo e memoria (per protocolli, GUI, RTOS)
  • Flessibilità per IoT, server embedded, wearable complessi.
  • Scegli Arduino se ti serve:
  • Semplicità didattica estrema
  • Bassissimo costo per progetti semplici senza Wi-Fi/BT
  • Ambiente consolidato e ben documentato per elettronica base.

In pratica: la maggior parte dei nuovi progetti IoT, wearable, smart home, dashboard embedded e prototipi veloci finiscono usando ESP32 al posto di Arduino “puro” proprio per via della connettività e delle risorse extra in un unico chip a basso costo.

Aggiungere un Pulsante di Riavvio Manuale in ESPHome per ESP32

🔧 Come aggiungere un pulsante di riavvio manuale in ESPHome

In molte installazioni basate su ESP32 può essere utile avere un modo semplice e immediato per riavviare il dispositivo direttamente da Home Assistant.

ESPHome mette a disposizione un componente dedicato che permette di creare un pulsante virtuale capace di eseguire un reboot del microcontrollore con un solo clic.

In questo articolo vediamo come aggiungerlo al tuo file YAML in modo rapido e sicuro.

—

🧩 Perché aggiungere un pulsante di riavvio?

Un pulsante di riavvio manuale è utile quando:

  • vuoi riavviare l’ESP32 senza scollegare l’alimentazione
  • stai testando nuove configurazioni
  • un sensore o il BLE si blocca e vuoi ripristinare il dispositivo
  • desideri un controllo immediato da Home Assistant

È una soluzione semplice ma molto pratica, soprattutto in installazioni remote o difficili da raggiungere fisicamente.

—

✅ Codice YAML del pulsante di riavvio

Aggiungi questo blocco nel tuo file ESPHome:

button:
  - platform: restart
    id: riavvia_esp
    name: "Riavvia ESP32"

Questo crea un pulsante visibile in Home Assistant che, quando premuto, riavvia immediatamente il dispositivo.

—

🔍 Come funziona?

  • platform: restart è un componente ufficiale ESPHome
  • il pulsante appare automaticamente tra i dispositivi dell’ESP32 in Home Assistant
  • premendolo, l’ESP esegue un reboot pulito
  • non richiede configurazioni aggiuntive o dipendenze

È uno dei metodi più affidabili e compatibili con tutte le versioni moderne di ESPHome.

—

🎯 Conclusione

Aggiungere un pulsante di riavvio manuale è un modo semplice per migliorare la gestione del tuo ESP32.

È immediato, stabile e perfettamente integrato con Home Assistant, ideale sia per debugging che per manutenzione ordinaria.