Quantcast
Channel: Tutorials – lucadentella.it
Viewing all 84 articles
Browse latest View live

ESP32 (28) – MQTT e SSL

$
0
0

Riprendiamo il tema sicurezza per i broker MQTT. In un precedente articolo, vi ho mostrato come gestire autenticazioneautorizzazione. La debolezza di tale configurazione è che le credenziali sono trasmesse in chiaro, è quindi possibile – se un attaccante può sniffare il traffico di rete – leggere utenza e password e quindi utilizzarle per impersonificare un client autorizzato.

Oggi vediamo come attivare la cifratura del canale di comunicazione tra client e broker utilizzando certificati SSL. Vi mostrerò inoltre come realizzare un programma per il chip esp32 che invii dati al broker su canale cifrato…

Certificato SSL

Per poter cifrare la comunicazione, dobbiamo fornire a mosquitto un certificato server.

Generiamo per prima cosa la chiave privata (algoritmo RSA, lunghezza 2048bit):

openssl genrsa -out mosquitto.key 2048

quindi creiamo il file CSR:

openssl req -new -out mosquitto.csr -key mosquitto.key

compiliamo i vari campi; il più importante è il common name che identificherà il certificato:

mq-ssl-001

Firmiamo quindi il file CSR con la nostra CA (o inviamolo ad una CA pubblica) per ottenere il certificato:

openssl ca -config openssl.cnf -extensions server_cert 
 -notext -in mosquitto.csr -out mosquitto.cer

mq-ssl-002

Creiamo la cartella ssl all’interno della cartella dove abbiamo installato mosquitto e copiamo in tale cartella certificato e chiave privata appena generati e il certificato della CA:

mq-ssl-003

Configurazione

Apriamo il file mosquitto.conf e inseriamo le seguenti righe:

mq-ssl-004

La prima riga cambia la porta su cui il server mosquitto è normalmente in ascolto (1883) in 8883, porta di default per le connessioni in SSL.

Le successive tre indicano a mosquitto il path dei certificati (server e CA) e la chiave privata relativa al certificato server. L’ultima – non obbligatoria – forza l’uso del protocollo TLS v1.2, il più sicuro attualmente disponibile.

Una volta configurato il server, possiamo eseguirlo (-v per la modalità verbosa):

mosquitto.exe -c mosquitto.conf -v

Per utilizzare gli strumenti mosquitto_pub e mosquitto_sub, dobbiamo specificare nuovi parametri:

mosquitto_sub.exe -p 8883 -t test --cafile .\ssl\ca.cer --insecure
mosquitto_pub.exe -p 8883 -m 20 -t test --cafile .\ssl\ca.cer --insecure

Con -p indichiamo la nuova porta utilizzata dal server, con –cafile indichiamo il percorso del certificato della CA che ha firmato il certificato esposto dal server mosquitto e infine con –insecure chiediamo ai due client di non verificare che il common name del certificato (nel mio esempio mymosquitto.local) corrisponda al nome del server.

Possiamo evitare l’utilizzo di –insecure generando un certificato che abbia come common name il nome esatto del computer che esegue il server mosquitto o – in alternativa – aggiungendo un alias DNS che colleghi il common name del certificato all’IP del server ed eseguendo i client con il parametro -h commonName

esp32

Tuan PM ha sviluppato una libreria (espmqtt) per il framework esp-idf che implementa un completo client MQTT. La libreria inoltre supporta connessioni sicure, possiamo quindi utilizzarla per collegarci al server mosquitto con TLS attivo.

mq-ssl-005

Copiamo il contenuto del repository Github della libreria nella cartella components del nostro progetto e includiamo il file header della libreria nel codice sorgente del nostro programma:

#include "mqtt.h"

La configurazione del client MQTT avviene tramite la struct mqtt_settings:

mqtt-001

I parametri principali sono:

  • il server (host) a cui collegarsi (si può utilizzare l’indirizzo IP o il nome DNS)
  • la porta (port) su cui il server è in ascolto (di default sono 1883 o 8883 se è abilitato SSL)
  • eventuali usernamepassword da utilizzare se il server richiede autenticazione
  • una o più funzioni di callback che la libreria espmqtt chiamerà al verificarsi dell’evento associato
La libreria espmqtt non effettua la copia dei parametri in una struttura interna. E’ quindi importante che la variabile di tipo mqtt_settings sia definita a livello globale, fuori dalle varie funzioni.

L’interazione con il client MQTT avviene implementando – nel proprio programma – una o più funzioni di callback.

Ad esempio la connessione e disconnessione dal server MQTT avviene in questo modo:

mqtt-002

Le funzioni connect_cbdisconnect_cb effettuano la connessione e la disconnessione dal server, mentre connected_cbdisconnected_cb vengono chiamate quando il relativo evento (connessione/disconnessione) avviene. Normalmente il nostro programma non andrà a ridefinire le funzioni principali, ma andrà ad implementare quelle relative agli eventi, per eseguire operazioni (ad esempio la sottoscrizione ad un topic) in contemporanea a tali eventi.

Preparata la configurazione del client MQTT, è possibile avviarlo con:

mqtt_start(&settings);

Una volta effettuata la connessione al server (evento connected_cb) è possibile sottoscrivere topics e togliere la sottoscrizione o inviare messaggi con:

void mqtt_subscribe(mqtt_client *client, const char *topic, uint8_t qos);
void mqtt_unsubscribe(mqtt_client *client, const char *topic);

e

void mqtt_publish(mqtt_client* client, const char *topic, 
  const char *data, int len, int qos, int retain);

Demo

Ho preparato un esempio che mostra l’interazione tra una scheda di sviluppo esp32 e mosquitto, configurato in SSL.

Ho collegato alla scheda di sviluppo un sensore HTU21D come spiegato in un mio precedente articolo per inviare, ogni 5 secondi, i dati di temperatura e umidità rilevati. Per visualizzare i dati ricevuti dal broker MQTT, ho utilizzato un comodo programma opensource, HelloIoT.

Codice sorgente del programma e configurazione di HelloIoT si trovano nel mio repository Github; di seguito un filmato che mostra il programma in esecuzione (sottotitoli in italiano disponibili):


ESP32 (29) – Deep sleep

$
0
0

Una delle tematiche principali per dispositivi embedded è il consumo energetico. Se infatti il dispositivo che si sta realizzando dovrà essere alimentato a batteria, è necessario ridurre al minimo il consumo di corrente in modo da massimizzare l’autonomia (= il tempo di funzionamento prima che sia necessario sostituire o ricaricare la batteria).

Il chip esp32 offre 5 diverse modalità di consumo energetico (power modes). La modalità di funzionamento “nomale” prende il nome di active mode; in tale modalità tutte le funzionalità del chip sono disponibili. Iniziando a ridurre la velocità di funzionamento e disattivando periferiche e cores, si passa a diverse modalità di risparmio energetico, riassunte nel seguente schema:

sleep-001

In questo primo articolo relativo al risparmio energetico del chip esp32, vi parlerò della modalità deep sleep.

Deep sleep

Il framework esp-idf supporta attualmente due modalità di risparmio ergetico: light sleepdeep sleep. Tra le due, la modalità deep sleep è quella che offre un risparmio maggiore di energia. In tale modalità vengono spente:

  • entrambe le CPU
  • la maggior parte della memoria RAM
  • tutte le periferiche

sono invece di default mantenute in esecuzione:

  • il controller RTC
  • le periferiche RTC, incluso il coprocessore ULP
  • Le memorie RTC lenta e veloce (slowfast)

L’attivazione della modalità deep sleep avviene con il comando esp_deep_sleep_start(), mentre è possibile “risvegliarsi” (wakeup) da tale modalità in base a diversi eventi:

sleep-002

Al risveglio dalla modalità deep sleep, avviene un nuovo boot. E’ molto importante comprendere quindi che il programma non riprende dal punto in cui è stato chiamato il metodo esp_deep_sleep_start().

Vediamo nel dettaglio la configurazione e l’utilizzo di due eventi: di touch pad e ULP vi parlerò in un prossimo articolo.

Timer

L’evento di wakeup più semplice è quello che utilizza un timer del controller RTC. Utilizzando il metodo:

esp_err_t esp_sleep_enable_timer_wakeup(uint64_t time_in_us)

è possibile risvegliare il chip esp32 dopo il numero di microsecondi specificati come parametro. Il metodo deve essere chiamato prima di attivare la modalità deep sleep:

// wakeup after 10 seconds
esp_sleep_enable_timer_wakeup(10000000);
esp_deep_sleep_start();

I/O triggers

In un precedente articolo vi ho parlato della possibilità di avere interrupts al cambio di stato di uno dei pin digitali del chip esp32. Possiamo sfruttare una funzionalità simile per risvegliare il chip esp32 dallo sleep.

Con il metodo

esp_err_t esp_sleep_enable_ext0_wakeup(gpio_num_t gpio_num, int level)

è possibile attivare il wakeup se avviene un cambio di stato (level) del pin specificato (gpio_num).

I pin utilizzabili sono soltanto quelli con funzionalità RTC (0, 2, 4, 12-15, 25-27, 32-39) e i livelli sono 0 (= basso) o 1 (alto). Se ad esempio vogliamo configurare il risveglio da deep sleep quando il pin 4 ha uno stato logico basso, scriveremo:

esp_sleep_enable_ext0_wakeup(4, 0);

E’ disponibile anche un metodo per monitorare diversi pins:

esp_err_t esp_sleep_enable_ext1_wakeup(uint64_t mask, esp_sleep_ext1_wakeup_mode_t mode)

i pin (anche per tale metodo è possibile specificare solo quelli sopra elencati) vanno indicati come bitmask, mentre le modalità possibili sono:

  • ESP_EXT1_WAKEUP_ALL_LOW = wakeup quanto tutti i pins sono a livello logico basso
  • ESP_EXT1_WAKEUP_ANY_HIGH = wakeup quando almeno un pin è a livello logico alto
Al ritorno dallo sleep, i pin specificati saranno configurati come RTC IO. Per poterli utilizzare come pin digitali, è necessario chiamare il metodo rtc_gpio_deinit(gpio_num). Il metodo ext0_wakeup non è attualmente compatibile con wakeup basati su touch pad o ULP.

Dopo il risveglio…

Se sono stati attivati più eventi di wakeup, è possibile conoscere quale evento ha causato il risveglio con:

esp_sleep_wakeup_cause_t esp_sleep_get_wakeup_cause()

Le costanti possibili sono:

sleep-003

Per l’evento ext1_wakeup, è possibile conoscere la bitmask dei pin che hanno causato il risveglio con:

uint64_t esp_sleep_get_ext1_wakeup_status()

Memoria

Come spiegato sopra, in deep sleep viene preservato il contenuto delle memorie RTC fastRTC slow. E’ quindi possibile utilizzare tali aree di memoria per memorizzare dati da conservare durante lo sleep.

Per indicare al compilatore che una variabile deve essere memorizzata nella RTC slow memory è sufficiente usare l’attributo RTC_DATA_ATTR, oppure RTC_RODATA_ATTR se tale variabile è in sola lettura (read only):

RTC_DATA_ATTR static time_t last;

Demo

Ho preparato un programma (il codice sorgente è disponibile nel mio repository Github) che mostra il funzionamento della modalità deep sleep e due diversi eventi di wake up (sottitoli in italiano disponibili):

ESP32 (30) – HTTP server in modalità SoftAP

$
0
0

Una delle domande che ricevo più spesso tramite il form sul sito o nella pagina Facebook è se sia possibile pubblicare un server HTTP quando il chip esp32 è in modalità SoftAP, ovvero quando pubblica una propria rete wifi.

In precedenti tutorial (18 – Access Point20 – Webserver) ho già trattato separatamente le due tematiche, oggi vediamo come combinarle.

Access Point

Iniziamo con la definizione dei parametri della rete TCP/IP che sarà pubblicata dal chip esp32. Dobbiamo scegliere un piano di indirizzamento, ovvero quali indirizzi IP apparterranno alla rete. Possiamo scegliere tra gli indirizzi IP dedicati dalla IANA alle reti private (RFC1918):

  • 10.0.0.0/8
  • 172.16.0.0/12
  • 192.168.0.0/16
I numeri /8/12/16 indicano, in forma ridotta, la maschera di rete. Ad esempio /8 significa che alla rete 10.0.0.0/8 appartengono tutti gli indirizzi da 10.0.0.1 a 10.255.255.254, per un totale di 16.777.216 indirizzi possibili. Grazie al subnetting è possibile tagliare una rete in sottoreti più piccole.

In questo esempio utilizzerò la rete 192.168.1.0 con netmask 255.255.255.0 (/24), ovvero una rete con 254 indirizzi possibili (192.168.1.1 – 192.168.1.254). Al chip esp32 dovrà essere assegnato staticamente un indirizzo appartenente a tale rete; per semplicità ho scelto il primo (192.168.1.1):

tcpip_adapter_ip_info_t info;
memset(&info, 0, sizeof(info));
IP4_ADDR(&info.ip, 192, 168, 1, 1);
IP4_ADDR(&info.gw, 192, 168, 1, 1);
IP4_ADDR(&info.netmask, 255, 255, 255, 0);
ESP_ERROR_CHECK(tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_AP, &info));

I vari dispositivi che si collegheranno alla rete dovranno ricevere dinamicamente un indirizzo IP, grazie al servizio DHCP. Per utilizzarlo nel mio programma devo stopparlo prima della configurazione dei parametri di rete, quindi avviarlo al termine della configurazione:

ESP_ERROR_CHECK(tcpip_adapter_dhcps_stop(TCPIP_ADAPTER_IF_AP));
[...]
ESP_ERROR_CHECK(tcpip_adapter_dhcps_start(TCPIP_ADAPTER_IF_AP));

Una volta terminata la configurazione della rete TCP/IP e dei servizi associati, possiamo configurare l’interfaccia wifi del chip esp32 e attivare la modalità SoftAP:

wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_init_config));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));

Dobbiamo preparare una struct wifi_config_t con i diversi parametri della rete wifi (SSID, autenticazione…) e passarla al metodo:

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));

Possiamo decidere di inserire i parametri hardcoded nel programma, di renderli configurabili tramite menuconfig (come nell’esempio) o ancora di leggerli da files di configurazione o variabili NVS.

Infine avviamo l’interfaccia wifi con:

ESP_ERROR_CHECK(esp_wifi_start());

HTTP server

Utilizziamo le Netconn API della libreria lwip per mettere il nostro programma in ascolto sulla porta TCP/80 (è la porta standard utilizzata dal protocollo HTTP):

struct netconn *conn;
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn, NULL, 80);
netconn_listen(conn);

Il metodo accept() blocca il programma fino all’arrivo di una nuova connessione:

struct netconn *newconn;
netconn_accept(conn, &newconn);

Una volta stabilita la connessione, dobbiamo implementare nel nostro programma il “linguaggio” (protocollo) che parla il client; in questo caso il protocollo HTTP utilizzato dai browser web.

Per semplicità nell’esempio pubblicherò un sito statico, il cui contenuto è memorizzato in una partizione SPIFFS (vedi il progetto ESP32lights per maggiori dettagli), quindi risponderò a richieste di tipo GET <risorsa> da parte del browser cercando la risorsa nella partizione SPIFFS e inviando il suo contenuto con il metodo netconn_write():

http-ap-001

Bonus, mDNS

Per potersi collegare al server HTTP pubblicato dal chip esp32, un client deve conoscere l’indirizzo IP (192.168.1.1) assegnato al chip.

Possiamo sfruttare il servizio mDNS (come spiegato qui) per consentire ai client di collegarsi utilizzando un alias (ad esempio esp32web):

mdns_server_t* mDNS = NULL;
ESP_ERROR_CHECK(mdns_init(TCPIP_ADAPTER_IF_AP, &mDNS));
ESP_ERROR_CHECK(mdns_set_hostname(mDNS, "esp32web"));
ESP_ERROR_CHECK(mdns_set_instance(mDNS, "esp32 webserver"));

Se il dispositivo che utilizziamo supporta mDNS, il sito web sarà quindi accessibile usando l’indirizzo esp32web.local:

http-ap-002

Demo

Il programma completo è disponibile nel mio repository Github. Una volta caricato sulla devboard, sarà disponibile una nuova rete wifi:

http-ap-003

collegandosi a tale rete il proprio PC otterrà un IP sulla rete 192.168.1.0/24:

http-ap-004

e collegandosi via browser al sito http://esp32web.local (o http://192.168.1.1) sarà possibile accedere al sito pubblicato:

http-ap-005

ESP32 (31) – BLE, GAP

$
0
0

Nei precedenti tutorial avete imparato come utilizzare le funzionalità wifi del chip esp32. A partire da questo tutorial vi illustrerò invece la seconda tecnologia di comunicazione wireless che il chip supporta: il bluetooth.

In particolare tratteremo lo standard Bluetooth Low Energy (BLE), chiamato anche Bluetooth 4.0 o Bluetooth Smart:

ble-001

Bluetooth Low Energy

BLE è una tecnologia per la realizzazione di reti personali wireless (WPAN); ovvero consente di mettere in comunicazione diversi dispositivi (computer, smartphones, smartwatches…) “vicini” tra loro (distanza massima teorica 100m). Come indica anche il nome, la versione 4.0 dello standard Bluetooth è stata disegnata per ridurre il consumo di energia dei dispositivi collegati.

I dispositivi sono suddivisi in due tipologie:

  • central
  • peripheral

I primi (central) sono dispositivi quali PC, tablet o smartphones con elevata quantità di memoria ed elevata capacità di calcolo. I secondi (peripheral) sono invece sensori, tag… con ridotte risorse hardware e bassi consumi. Un central device può essere connesso contemporaneamente a più peripheral devices, mentre non è vero il contrario:

ble-002

I dispositivi BLE segnalano periodicamente la loro presenza trasmettendo pacchetti di advertising. Il pacchetto di advertising può contenere fino a 31 bytes di dati e la frequenza di trasmissione può essere scelta dal singolo dispositivo: diminuendo tale frequenza è infatti possibile ridurre il consumo energetico.

Se un dispositivo BLE, alla ricezione di un pacchetto di avertising, vuole ottenere maggiori informazioni dal dispositivo che lo ha trasmesso, può richiedere a questo un secondo pacchetto di informazioni (sempre per un massimo di 31 bytes), il pacchetto di scan response. La trasmissione di questo secondo pacchetto di dati è facoltativo:

ble-003

Un dispositivo BLE può sfruttare i pacchetti di advertising per inviare dati in modalità broadcast. In questo caso tale dispositivo viene chiamato broadcaster, mentre i dispositivi che ricevono i dati sono chiamati observers.

Quanto spiegato sopra è definito all’interno di una specifica BLE chiamata Generic Access Profile (GAP).

esp32

In questo primo tutorial dedicato a BLE vediamo come sviluppare un programma che effettua periodicamente lo scan alla ricerca di periferiche BLE, ovvero riceve i pacchetti di advertising e visualizza in console i dati ricevuti.

Prima di poter eseguire un programma che utilizza il controller Bluetooth del chip, verifichiamo sempre (tramite menuconfig) che tale controller sia abilitato (Component config -> Bluetooth):

ble-004

Iniziamo il nostro programma includendo gli headers necessari:

#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"

Per poter utilizzare il controller Bluetooth, abbiamo bisogno della partizione NVS:

ESP_ERROR_CHECK(nvs_flash_init());

Il controller Bluetooth del chip esp32 supporta sia la modalità classic che low energy. Se non è necessaria una delle due modalità, è possibile liberare la memoria normalmente allocata dal framework per gestirla con il comando esp_bt_controller_mem_release(). Nel nostro caso non utilizzeremo la modalità classic, quindi:

ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

Possiamo ora configurare il controller (utilizzeremo la configurazione di default) e abilitarlo in modalità BLE:

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);

Il framework esp-idf utilizza lo stack Bluetooth Bluedroid. Questa libreria è stata sviluppata da Broadcom e utilizzata da Android dalla versione 4.2. Bluedroid viene inizializzato e abilitato con i seguenti comandi:

esp_bluedroid_init();
esp_bluedroid_enable();

Siamo ora pronti ad eseguire lo scan…

GAP, eventi

In maniera simile a quanto visto con il driver wifi, anche il driver bluetooth viene eseguito in un thread separato rispetto al nostro programma e comunica con esso tramite eventi. Per poter ricevere tali eventi, dobbiamo implementare una funzione di callback. Ogni volta che il driver bluetooth dovrà notificare un evento, chiamerà tale funzione.

Il prototipo della funzione di callback è:

static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);

Si indica al driver quale funzione di callback utilizzare con il metodo esp_ble_gap_register_callback():

ESP_ERROR_CHECK(esp_ble_gap_register_callback(esp_gap_cb));

Gli eventi possibili sono tantissimi, vediamo quelli relativi al processo di scan:

ble-005

Prima di poter eseguire il processo di scan, è necessario configurare i relativi parametri. La configurazione avviene tramite la struct esp_ble_scan_params_t. Importante è che la variable che contiene i parametri sia disponibile durante tutto lo scan; conviene quindi definirla globale:

static esp_ble_scan_params_t ble_scan_params = {
  .scan_type              = BLE_SCAN_TYPE_ACTIVE,
  .own_addr_type          = BLE_ADDR_TYPE_PUBLIC,
  .scan_filter_policy     = BLE_SCAN_FILTER_ALLOW_ALL,
  .scan_interval          = 0x50,
  .scan_window            = 0x30
};

Con il metodo esp_ble_gap_set_scan_params() si configura il processo di scan passando al driver la struct sopra definita:

esp_ble_gap_set_scan_params(&ble_scan_params);

Quando il driver ha terminato la configurazione, chiama la funzione di callback con l’evento ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT. In base all’evento sollevato, la funzione di callback riceve anche dei parametri. La Programming Guide del framework riporta – per ogni evento – i relativi parametri. Per questo evento è disponibile la variabile scan_param_cmpl che contiene solo il parametro status.

Nella funzione di callback utilizziamo un comando switch per identificare il singolo evento:

switch (event) {
  case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT:
  [...]
  break;

e verifichiamo se la configurazione ha avuto esito positivo con:

if(param->scan_param_cmpl.status == ESP_BT_STATUS_SUCCESS)

In caso affermativo possiamo lanciare il processo di scan con:

esp_ble_gap_start_scanning(10);

Il parametro indica la durata (in secondi) della scansione.

Una volta avviato il processo di scan, il driver solleva l’evento ESP_GAP_BLE_SCAN_START_COMPLETE_EVT. Anche qui è possibile verificare la corretta esecuzione del processo leggendo il parametro status (attenzione, cambia il nome della variabile che lo contiene!):

case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
  if(param->scan_start_cmpl.status == ESP_BT_STATUS_SUCCESS)
    printf("Scan started\n\n");
  else 
    printf("Unable to start scan process");
  break;

GAP, processo di scan

Quando il processo di scan è in esecuzione, per ogni pacchetto di advertising ricevuto viene sollevato l’evento ESP_GAP_BLE_SCAN_RESULT_EVT.

Questo evento contiene a sua volta dei sottoeventi. E’ possibile identificare di quale sottoevento si tratta leggendo il parametro scan_rst.search_evt. Ci interessano due sottoeventi in particolare:

ble-006

il primo indica che un nuovo dispositivo è stato rilevato, mentre il secondo che il processo di scan è terminato.

Per ogni dispositivo rilevato sono disponibili diverse informazioni, per ora stampiamo in console il suo indirizzo:

case ESP_GAP_BLE_SCAN_RESULT_EVT:
  if(param->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
    printf("Device found: ADDR=");
    for(int i = 0; i < ESP_BD_ADDR_LEN; i++) {
      printf("%02X", param->scan_rst.bda[i]);
      if(i != ESP_BD_ADDR_LEN -1) printf(":");
    }

L’indirizzo è rappresentato da un array di uint8_t. La dimensione dell’array è definita dalla costante ESP_BD_ADDR_LEN. Normalmente l’indirizzo viene visualizzato in forma esadecimale, con i bytes divisi da :

ble-007

Gestione elenco devices

Come detto, l’evento ESP_GAP_BLE_SCAN_RESULT_EVT viene sollevato ogni volta che un dispositivo invia un pacchetto di advertising. Questo significa che un singolo dispositivo sarà rilevato più volte durante il processo di scansione.

E’ quindi necessario mantenere un elenco dei dispositivi già noti. Nel mio repository Github trovate un programma di esempio che effettua uno scan e stampa tutti i dispositivi rilevati.

E’ possibile verificare il corretto funzionamento confrontando quanto rilevato dal chip esp32 con quanto rilevato da uno smartphone… per Android ad esempio si può utilizzare l’ottima applicazione nRF Connect di Nordic.

Ecco quanto rilevato dal mio programma:

ble-008

ed ecco lo screenshot di nRF Connect:

ble-009

 

ESP32 (32) – BLE, iBeacon

$
0
0

Nel precedente articolo ho introdotto la tecnologia Bluetooth Low Energy e il processo di advertising.

Abbiamo visto che un dispositivo BLE può sfruttare i pacchetti di advertising per inviare dati; in tal caso il dispositivo viene chiamato broadcaster, mentre i dispositivi che ricevono i dati sono chiamati observers.

Il payload di un pacchetto di advertising ha la seguente struttura:

ibeacon-002

ADV ADDR è l’indirizzo MAC del dispositivo (indirizzo che veniva visualizzato in console dal programma sviluppato nel precedente articolo), mentre ADV DATA è un campo, lungo un massimo di 31 bytes, organizzato in una o più strutture formate da 3 elementi:

  • AD length è la lunghezza complessiva (in bytes) della singola struttura dati
  • AD type è il tipo di informazione riportata in tale struttura
  • AD data è l’informazione

Il sito ufficiale del Bluetooth Special Interest Group riporta l’elenco dei possibili AD types.

Un dispositivo può ad esempio trasmettere il proprio nome utilizzando l’AD type 0x09:

ibeacon-003

Durante lo scan, il driver Bluetooth mette a disposizione del programma i dati (ADV DATA) ricevuti nell’array scan_result->scan_rst.ble_adv. Tale array contiene valori uint8_t ed ha dimensione scan_result->scan_rst.adv_data_len.

La libreria Bluedroid contiene un metodo, esp_ble_resolve_adv_data(), che consente di ottenere il valore di un particolare AD type passando i dati grezzi (raw data). Il file di header esp_gap_ble_api.h contiene anche la definzione degli AD types più comuni:

ibeacon-004

Nel mio repository Github trovate una nuova versione del programma che effettua lo scan: utilizzando quanto spiegato sopra ora estrae anche – se disponibile – il nome del device e lo visualizza in console:

ibeacon-009

iBeacon

Una particolare famiglia di dispositivi broadcasters sono gli iBeacon. Tali dispositivi sono stati pensati da Apple per consentire interazioni con dispositivi IOS (iPhone…) basati sulla location awareness. Facciamo un esempio: un telefono iPhone può “accorgersi” di essere vicino ad un particolare iBeacon, associato ad una stanza di un museo, e proporre all’utente la visualizzazione di una breve guida delle opere esposte.

ibeacon-001

Le specifiche dei dispositivi iBeacon si trovano sul portale sviluppatore Apple. Gli iBeacon funzionano inserendo nei pacchetti di advertising un particolare payload (ADV DATA):

ibeacon-006

Il primo dato inviato è di tipo flags (0x01). Ogni bit ha un diverso significato, tipicamente gli iBeacon utilizzano il valore 0x0A.

Il secondo dato inviato è di tipo 0xFF, ovvero Manufacturer Specific Data. Lo standard Bluetooth lascia liberi i vari produttori di usare l’ID 0xFF per inviare dati custom. I dati inviati hanno lunghezza 25 bytes (0x1A – 0x01 che è la lunghezza del campo AD type).

Le specifiche Apple per gli iBeacon suddividono ulteriormente il campo AD data in diversi elementi:

ibeacon-008

Il primo campo indica il produttore; normalmente gli iBeacon utilizzano il codice 0x004C, assegnato ad Apple Inc. I successivi due indicano il tipo di iBeacon e hanno valore fisso (0x02 e 0x15). Il campo UUID, insieme con i campi Major e Minor (facoltativi, possono essere impostati a 0) identificano univocamente il singolo iBeacon. Infine il campo TX power contiene una misurazione, ad un metro di distanza dall’iBeacon, della potenza ricevuta ed è utile per rendere più accurata la stima della distanza tra il telefono e l’iBeacon stesso.

esp32

Ho sviluppato un programma per il chip esp32 che attiva un relay se rileva un particolare iBeacon. Tramite menuconfig è possibile configurare l’UUID dell’iBeacon da monitorare, il pin a cui è collegato il led e il timeout in secondi trascorsi i quali – se non viene nuovamente rilevato l’iBeacon – il programma spegne il led. E’ inoltre possibile impostare una soglia minima di potenza in modo da poter regolare la distanza di rilevamento dell’iBeacon.

Per effettuare il parsing del pacchetto ricevuto ed estrarre il valore di UUID ho utilizzato nel mio programma la tecnica descritta in questo mio articolo (parsing attraverso l’uso di struct).

Il programma verifica se il pacchetto ricevuto (evento ESP_GAP_SEARCH_INQ_RES_EVT) è stato inviato da un iBeacon verificando che tale pacchetto sia di 30 bytes e che contenga nel suo header i valori indicati sopra:

// iBeacon fixed header
ibeacon_header_t ibeacon_fixed_header = {
  .flags = {0x02, 0x01, 0x06},
  .length = 0x1A,
  .type = 0xFF,
  .company_id = 0x004C,
  .beacon_type = 0x1502
};

La verifica avviene utilizzando il comando memcmp che confronta due blocchi di memoria:

if(memcmp(adv_data, ibeacon_fixed_header, sizeof(ibeacon_fixed_header)))
  result = true;

Il sorgente del programma si trova nel mio repository Github, ecco un video che mostra il suo funzionamento:

Arduino bootloader e ISP

$
0
0

Dopo lo sviluppo di uno sketch con l’IDE di Arduino, possiamo compilarlo e caricarlo sulla scheda Arduino collegata al nostro PC con un click sul pulsante upload:

isp-002

Il programma viene memorizzato nella memoria flash del microcontrollore (per una scheda Arduino Uno questo è l’ATmega328p).

E’ possibile caricare il nostro programma sul microcontrollore senza necessità di un programmatore esterno grazie alla esecuzione, sul microcontrollore stesso, di un piccolo programma chiamato bootloader.

Il bootloader è un programma, già caricato su ogni microcontrollore delle schede Arduino, che viene eseguito ad ogni reset. La prima operazione compiuta dal bootloader è verificare se via seriale/USB arriva la richiesta di caricare un nuovo programma. In tal caso è il bootloader che dialoga con l’IDE di Arduino, riceve il nuovo programma e lo memorizza nella memoria flash:

isp-001

Il bootloader attualmente utilizzato sulle schede Arduino Uno si chiama optiboot.

Se acquistiamo un chip ATmega328p e vogliamo utilizzarlo con l’IDE di Arduino per eseguire i nostri sketch, dobbiamo per prima cosa caricare sul chip il bootloader. La programmazione di un chip avviene normalmente tramite un programmatore dedicato. I chip ATmega supportano una modalità di programmazione detta In System Programming (ISP), pensata per programmare il chip direttamente sulla scheda che lo ospita.

Tale modalità richiede il collegamento di 6 pins del chip con il programmatore:

  • 5V e massa
  • reset
  • MISO, MOSI e SCK

Ogni scheda Arduino Uno ad esempio ha un apposito connettore che consente di riprogrammare il chip ATmega328p senza toglierlo:

isp-003

In rete si trovano diversi programmatori per chip ATmega (es questo di Adafruit); molto interessante è però la possibilità di utilizzare un Arduino come programmatore, grazie al lavoro di Randall Bohn.

Per prima cosa dobbiamo caricare su Arduino lo sketch ArduinoISP, che troviamo tra gli esempi disponibili nell’IDE:

isp-004

quindi dobbiamo predisporre – ad esempio su una breadboard – un circuito minimale che comprende il chip da programmare, un quarzo da 16MHz e due condensatori da 22pF:

isp-013

colleghiamo tramite jumpers il nostro Arduino Uno al chip ATMega328p da programmare. I collegamenti da effettuare sono:

  • 5V -> pin 7 e 20
  • Massa -> pin 8, 22 e i due condensatori
  • pin 10 di Arduino o RESET del connettore ICSP -> pin 1
  • pin 11 di Arduino o MOSI del connettore ICSP -> pin 17
  • pin 12 di Arduino o MISO del connettore ICSP -> pin 18
  • pin 13 di Arduino o CLK del connettore ICSP -> pin 19

isp-014 isp-015

Indichiamo all’IDE che vogliamo utilizzare Arduino come programmatore:

isp-005

quindi programmiamo il bootloader:

isp-006

dopo qualche istante, l’IDE dovrebbe indicare che la programmazione è andata a buon fine:

isp-011

Shield

In vendita su diversi store online si trova uno shield per Arduino che semplifica la programmazione dei chip ATmega, chiamato AVR ISP Shield e prodotto da “OPEN-SMART”:

isp-012

Questo shield ospita uno zoccolo ZIF (Zero Insertion Force) in cui va inserito il chip da programmare, alcuni connettori per collegarlo a schede esterne e un buzzer che segnala con due beep l’avvenuta programmazione.

Ho trovato su Internet un archivio contenente la documentazione relativa a questo shield e lo sketch da utilizzare. E’ comunque possibile utilizzare anche lo sketch fornito con l’IDE, semplicemente non verrà attivato il buzzer al termine della programmazione.

Se avete la necessità di programmare diversi chip ATmega328p (ad esempio se state realizzando vostre schede Arduino-compatibili) l’uso di questo shield rende l’operazione sicuramente più veloce!

 

ESP32 (33) – BLE, advertising

$
0
0

Nei precedenti articoli abbiamo visto come utilizzare il chip esp32 per ricevere ed interpretare i pacchetti di advertising trasmessi da periferiche Bluetooth Low Energy. Come esempio pratico, abbiamo sviluppato un programma per rilevare la presenza di un particolare iBeacon e attivare di conseguenza una uscita.

Nel tutorial di oggi vedremo invece come trasmettere pacchetti di advertising.

Processo di advertising

Abbiamo già scoperto che il driver Bluetooth dello stack esp-idf viene eseguito in un thread separato. Ogni volta che il driver deve inviare una notifica al nostro programma, chiama una funzione di callback indicando quale evento si è scatenato.

Il processo di advertising è molto semplice:

  • il programma configura i dati da trasmettere con il comando esp_ble_gap_config_adv_data()
  • il driver segnala di aver terminato la configurazione con l’evento ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT
  • il programma può ora avviare il processo di advertising con il comando esp_ble_gap_start_advertising()
  • il driver segnala di aver avviato il processo con l’evento ESP_GAP_BLE_ADV_START_COMPLETE_EVT

esp32-adv-001

Dati di advertising

E’ possibile indicare al driver quali dati includere nel pacchetto di advertising con il comando:

esp_err_t esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data);

Il comando accetta come parametro un puntatore ad una struct esp_ble_adv_data_t:

esp32-adv-002

Il significato dei vari parametri è dettagliato nel documento Supplement to the Bluetooth Core Specification.

Per prima cosa vediamo come trasmettere il nome del dispositivo. Dobbiamo utilizzare il metodo esp_ble_gap_set_device_name() per indicare al driver quale nome utilizzare e settare a true il campo include_name nella struct:

static esp_ble_adv_data_t adv_data = {
  .include_name = true,
};
[...]
ESP_ERROR_CHECK(esp_ble_gap_set_device_name("ESP32_BLE"));
ESP_ERROR_CHECK(esp_ble_gap_config_adv_data(&adv_data));

Tramite i flags possiamo indicare alcune caratteristiche del nostro dispositivo. Le costanti a disposizione sono:

esp32-adv-003

possiamo utilizzarle con l’operatore OR. Se ad esempio vogliamo indicare che il nostro dispositivo è limited discoverable (ovvero effettua la trasmissione dei pacchetti di advertising per un tempo limitato, solitamente 30 secondi) e che non supporta il Bluetooth classico (BR/EDR, Basic Rate/Enhanced Data Rate) scriveremo:

static esp_ble_adv_data_t adv_data = {
  .flag = ESP_BLE_ADV_FLAG_LIMIT_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT,
};

Parametri di advertising

Dopo aver configurato il contenuto del pacchetto di advertising, dobbiamo indicare al driver anche la modalità con cui inviare tale pacchetto.

Il comando:

esp_err_t esp_ble_gap_start_advertising(esp_ble_adv_params_t *adv_params);

accetta come parametro una struct esp_ble_adv_params_t:

esp32-adv-004

Possiamo configurare l’intervallo minimomassimo di trasmissione del pacchetto. I due parametri possono assumere un valore da 0x20 a 0x4000. Per conoscere l’intervallo in millisecondi, va moltiplicato il valore indicato per 0,625. Questo significa che il valore minimo (0x20) corrisponde ad un intervallo di 12,5ms.

Nel file esp_gap_ble_api.h sono elencate le costanti utilizzabili per gli altri parametri (esp_ble_adv_type_t, esp_ble_addr_type_t…).

Come esempio configuriamo il processo di advertising come segue:

  • intervallo minimo di trasmissione 0x20 e massimo 0x40
  • tipo di dispositivo non connectable (non accetta connessioni ma effettua solo invio dati in broadcast)
  • indirizzo MAC pubblico
  • trasmissione su tutti e 3 i canali dedicati ai pacchetti di advertising
  • nessun filtro sui dispositivi che possono effettuare scan o collegarsi
static esp_ble_adv_params_t ble_adv_params = {
  .adv_int_min = 0x20,
  .adv_int_max = 0x40,
  .adv_type = ADV_TYPE_NONCONN_IND,
  .own_addr_type  = BLE_ADDR_TYPE_PUBLIC,
  .channel_map = ADV_CHNL_ALL,
  .adv_filter_policy  = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
[...]
esp_ble_gap_start_advertising(&ble_adv_params);

Demo

Ho preparato un programma che raccoglie quanto spiegato sopra, il codice sorgente è disponibile nel mio repository Github.

Ecco una demo del suo funzionamento (sottotitoli in italiano disponibili):

ESP32 (34) – BLE, raw advertising

$
0
0

Nel precedente articolo abbiamo visto come è possibile inviare pacchetti di advertising con il chip esp32.

Per definire il contenuto del pacchetto, abbiamo utilizzato una struct, di tipo esp_ble_adv_data_t:

raw-adv-001

La definizione di tale struct è nel file esp_gap_ble_api.h:

raw-adv-002

Sebbene i campi disponibili siano molti, a volte è necessario poter definire il contenuto del pacchetto di advertising in modo arbitrario. Per questa ragione il framework esp-idf ci mette a disposizione la modalità raw.

Invece che definire una struct, creiamo un array di byte e memorizziamo al suo interno l’intero contenuto del payload del pacchetto:

static uint8_t adv_raw_data[10] = 
  {0x09,0x09,0x4c,0x75,0x6b,0x45,0x53,0x50,0x33,0x32};

quindi utilizziamo la funzione esp_ble_gap_config_scan_rsp_data_raw() per passare tale array al driver Bluetooth. Dobbiamo specificare come parametri sia l’array che la sua dimensione:

esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_raw_data, 8);

Utilizzando questa nuova funzione, cambia anche l’evento che il driver passa alla nostra funzione di callback una volta terminata la configurazione. Il nuovo evento è ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT. Come nell’esempio precedente, quando tale evento si presenta è possibile avviare il processo di advertising:

case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: 
  esp_ble_gap_start_advertising(&ble_adv_params);
  break;

Raw data

Perché il processo di advertising funzioni, i dati contenuti nell’array devono corrispondere ad un payload valido.

Nell’articolo relativo agli iBeacons vi ho già mostrato la sua struttura. Rivediamola brevemente:

ibeacon-002

Il payload contiene una o più strutture AD (advertising data). Ogni struttura è formata da 3 campi:

  • un byte iniziale che rappresenta la lunghezza (in byte) della struttura, escluso sè stesso
  • un byte che rappresenta il tipo di dato contenuto nella struttura
  • un numero variabile di byte che sono il dato effettivo

I codici utilizzabili per definire il tipo di dato si trovano nelle specifiche Bluetooth. In base al tipo di dato, è poi necessario applicare un particolare fomato ai dati che lo seguono. Nel documento Core Specification Supplement (disponibile sempre sul sito Bluetooth.com) si trovano le informazioni necessarie.

Vediamo un semplice esempio: l’AD type 0x09 rappresenta il complete local name, ovvero il nome del dispositivo. Tale nome deve essere specificato nel campo AD data semplicemente come sequenza dei codici ASCII che corrispondono alle diverse lettere.

Possiamo utilizzare un sito web per effettuare la conversione:

raw-adv-003

Il payload per trasmettere tale nome sarà quindi:

adv_raw_data[7] = {0x06,0x09,0x4d,0x79,0x42,0x4c,0x45};

Il primo byte ha il valore 0x06 ovvero la somma della lunghezza del nome (5 byte) + 1 byte per il tipo di dato (0x09).

Demo

Nel video seguente (sottotitoli in italiano disponibili) mostro come utilizzare le funzionalità di raw advertising per simulare il pacchetto di advertising trasmesso dal mio iBeacon e quindi attivare il relay come mostrato nell’esempio precedente.

Il codice sorgente del programma è disponibile nel mio repository Github.


ESP32 (35) – BLE, scan response

$
0
0

Nei precedenti articoli vi ho mostrato come ricevere e inviare pacchetti di advertising secondo lo standard Bluetooth LE.

Il payload (ovvero la porzione di dati “utili”) di tali pacchetti è al massimo 31 bytes. Si tratta di una quantità di dati piuttosto limitata: se ad esempio vogliamo includere il nome del nostro dispositivo, rimane poco spazio per altri dati.

Lo standard BLE consente alle periferiche di inviare dati aggiuntivi utilizzando il processo di scan request – scan response.

Quando un dispositivo riceve un pacchetto di advertising, può contattare chi ha effettuato la trasmissione inviando un pacchetto di scan request, ovvero richiedendo ulteriori informazioni. Ad un pacchetto di scan request, la periferica può rispondere con un pacchetto di scan response:

scan-response-001

I pacchetti di advertising e di scan response hanno il medesimo formato; è quindi possibile trasferire, tramite scan response, ulteriori 31 bytes di dati.

esp32

Il framework esp-idf mette a disposizione due modalità per configurare il contenuto del pacchetto di scan response: tramite la struct esp_ble_adv_data_t o tramite un array di byte (raw mode). Si tratta delle medesime modalità già viste nei due articoli precedenti (struct e raw mode), relativi alla trasmissione di pacchetti di advertising.

Nel primo caso, dobbiamo definire una seconda struct, in aggiunta a quella relativa al pacchetto di advertising, per definire il contenuto del pacchetto di scan response:

static uint8_t manufacturer_data[6] = {0xE5,0x02,0x01,0x01,0x01,0x01};
static esp_ble_adv_data_t scan_rsp_data = {
  .set_scan_rsp = true,
  .manufacturer_len = 6,
  .p_manufacturer_data = manufacturer_data,
};

Molto importante è configurare a true il parametro set_scan_rsp. E’ infatti questo parametro che indica al driver che questa struct è relativa al pacchetto di scan response.

Possiamo quindi passare la nuova struct al driver con lo stesso metodo usato in precedenza:

esp_ble_gap_config_adv_data(&scan_rsp_data);

Il driver chiamerà la funzione di callback due volte: una per indicare l’avvenuta configurazione del pacchetto di advertising e una per la configurazione del pacchetto di scan response. I due eventi sono differenti:

case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
  [...]
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
  [...]

Dobbiamo attendere che entrambi gli eventi si siano verificati prima di lanciare il processo di advertising. Nel programma di esempio, che trovate nel mio repository Github, utilizzo due variabili boolean:

bool adv_data_set = false;
bool scan_rsp_data_set = false;
[...]
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
  adv_data_set = true;
  if(scan_rsp_data_set) esp_ble_gap_start_advertising(&ble_adv_params); break;
 
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
  scan_rsp_data_set = true;
  if(adv_data_set) esp_ble_gap_start_advertising(&ble_adv_params); break;

Se invece vogliamo configurare il pacchetto di scan response in modalità raw, dobbiamo definire un array di byte e memorizzarvi il contenuto del payload del pacchetto. Quindi utilizziamo una apposita funzione del framework per passare tale array al driver:

static uint8_t scan_rsp_raw_data[8] = {0x07,0xFF,0xE5,0x02,0x01,0x01,0x01,0x01};
[...]
esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_raw_data, 8);

avete notato che il contenuto del pachetto di scan response è il medesimo nei due esempi?

Il driver segnalerà l’avvenuta configurazione con un apposito evento. Anche in questo caso dobbiamo attendere il termine di entrambe le configurazioni (advertisingscan response):

case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
  scan_rsp_data_set = true;
  if(adv_data_set) esp_ble_gap_start_advertising(&ble_adv_params); break;
E’ possibile utilizzare entrambe le modalità nel proprio programma. Ad esempio si può configurare il pacchetto di advertising tramite struct, mentre quello di scan response in modalità raw.

Utilizzando l’app nRF Connect è possibile verificare che il pacchetto di scan response è correttamente ricevuto dallo smartphone:

scan-response-002

Nel video seguente vi mostro come ho costruito il payload del pacchetto e il funzionamento del programma:

ESP32 (36) – OTA con Freshen

$
0
0

In uno dei primi articoli di questo tutorial, vi ho parlato del bootloader e della struttura della memoria flash. Nell’articolo scrivevo:

In questo modo è possibile implementare un meccanismo di aggiornamento over-the-air (OTA): si invia la nuova versione dell’applicazione al chip esp32 mentre è in funzione; tale versione viene memorizzata in una nuova partizione

Esistono diversi modi per implementare le funzionalità di aggiornamento OTA per la propria applicazione… oggi vi voglio mostrare come effettuarlo in maniera molto semplice, grazie ad un servizio cloud chiamato Freshen.

Freshen

Freshen è un IoT backend, ovvero uno strumento in cloud (disponibile su Internet) per la gestione di dispositivi IoT.

Dopo aver collegato i propri dispositivi a Freshen (vedremo nel proseguo dell’articolo come fare), è possibile:

  • visualizzare la lista dei dispositivi e il loro stato
  • inviare comandi ad ogni dispositivo
  • gestire i files memorizzati su ogni dispositivo
  • aggiornare il firmware over-the-air (OTA)

Freshen è sviluppato da Cesanta, la compagnia produttrice anche di Mongoose e MongooseOS, molto utilizzati in ambito embedded e compatibili con il chip esp32. Il servizio offre diverse tariffe, inclusa una gratuita:

freshen-001s

Per poter utilizzare Freshen è necessario per prima cosa registrarsi, utilizzando il proprio account Github o Google:

freshen-002

Libreria client

Cesanta mette a disposizione una libreria client che possiamo utilizzare per collegare il nostro progetto IoT basato sul chip esp32 alla dashboard di Freshen. La libreria è racchiusa in un solo file header (freshen.h), scaricabile dal sito ufficiale.

La libreria è pienamente compatibile con il framework esp-idf e mette a disposizione tutte le funzionalità del portale:

freshen-003

Il suo utilizzo è molto semplice. Per prima cosa copiamo il file freshen.h nella cartella main del nostro progetto:

freshen-009

Includiamo quindi il file nel nostro programma:

#include "freshen.h"

Per consentire alla libreria di comunicare con la piattaforma Freshen nel cloud, dobbiamo richiamare periodicamente nel nostro programma la funzione freshen_loop().

Possiamo farlo utilizzando un task apposito:

void freshen_task(void *pvParameter) {
  while(1) {	
    freshen_loop(FIRMWARE_VERSION, ACCESS_TOKEN);
    vTaskDelay(2000 / portTICK_RATE_MS);
  }
}

Il task esegue la funzione ogni 2 secondi. Creiamo il task all’interno di app_main() con:

// start the freshen update task
xTaskCreate(&freshen_task, "freshen_task", 10000, NULL, 5, NULL);

Il metodo freshen_loop() richiede due parametri: la versione del firmware (una stringa a nostra scelta, ad esempio “v1.0″) e l’access token, un codice che ci viene fornito dalla piattaforma dopo aver registrato il dispositivo.

Colleghiamoci quindi alla dashboard e clicchiamo Add new device:

freshen-010

Apparirà un nuovo dispositivo (My Device #n). Possiamo cliccare sul nome per visualizzarne i dettagli.

L’access token del dispositivo è oscurato… è possibile cliccare su click to copy per memorizzarlo nella clipboard del nostro computer e quindi incollarlo nel programma:

freshen-011

La pagina che visualizza i dettagli del dispositivo consente anche di cambiare il suo nome.

Se eseguiamo il nostro semplice programma (lo trovate nel mio repository Github), vedremo – dopo qualche secondo – cambiare lo stato del dispositivo in online, ad indicare che sta correttamente comunicando con la dashboard:

freshen-012

Nella pagina di dettaglio, vedremo ora quali metodi è possibile eseguire da remoto:

freshen-013

Ad esempio se invochiamo il metodo Sys.GetInfo vedremo apparire le informazioni su versione, architettura, data di compilazione:

freshen-014

OTA

Per poter effettuare l’aggiornamento da remoto del nostro dispositivo, dobbiamo ricordarci di utilizzare un layout di partizioni adeguato. Ad esempio possiamo scegliere uno dei layout già forniti con il framework:

freshen-006

Proviamo a modificare la versione del nostro firmware, portandola a “1.1”:

freshen-015

quindi compiliamo il programma ma non diamo il comando di flash. Prendiamo nota del percorso dove è memorizzato il firmware compilato:

freshen-016

Selezioniamo nella dashboard il nostro dispositivo e clicchiamo su OTA update selected:

freshen-017

 

Scegliamo il file .bin che contiene il nuovo firmware.

Dopo qualche istante, vedremo il dispositivo andare per alcuni secondi offline, quindi tornare online. Se il processo di update si è concluso con successo, invocando nuovamente il metodo Sys.GetInfo vedremo la nuova versione:

freshen-018

 

ESP32 (37) – OTA via https

$
0
0

Nel precedente articolo di questo tutorial, vi ho mostrato come sia possibile effettuare un aggiornamento Over-The-Air grazie alle funzionalità della dashboard Freshen.

Oggi vi spiegherò come aggiornare il firmware in esecuzione sul chip esp32 utilizzando solo il framework esp-idf, senza la necessità di dashboard esterne.

OTA API

Il framework esp-idf mette a disposizione una serie di funzioni native per implementare, in un nostro programma, la capacità di aggiornarsi over the air.

Tali funzioni si trovano nel componente app_update e per utilizzarle dobbiamo includere il relativo header file:

#include "esp_ota_ops.h"

Sebbene non sia complesso l’utilizzo di queste funzioni native (su Github potete trovare un programma di esempio), gli sviluppatori di Espressif hanno aggiunto al framework un componente che rende ancora più facile l’aggiornamento over the air, nel caso in cui il nuovo firmware si trovi su un sito web.

Tale componente si chiama esp_https_ota.

esp_https_ota

Il componente esp_https_ota utilizza le OTA API per aggiornare il firmware, scaricando la nuova versione da un sito web. Come il nome suggerisce, l’unico requisito (per questioni di sicurezza) è che tale sito web sia disponibile con protocollo sicuro (HTTPS).

Il componente è in grado di identificare automaticamente la partizione OTA non in uso nella memoria flash e di caricare in tale partizione il nuovo firmware; configurando infine il chip per effettuare il boot da tale partizione:

https_ota_001

Il suo utilizzo è davvero semplice. Per prima cosa creiamo una struct di tipo esp_http_client_config_t dove configurare l’URL del file che contiene il nuovo firmware e il certificato SSL del server (o della CA che lo ha firmato):

esp_http_client_config_t ota_client_config = {
  .url = "https://mysite.com/newfirmware.bin",
  .cert_pem = server_cert_pem_start,
};

Il certificato deve essere fornito in formato PEM. Per caricarlo all’interno del nostro programma, possiamo utilizzare le funzionalità di embedding binary files del framework come vi ho già spiegato in un precedente tutorial.

Ora è sufficiente il comando:

esp_err_t ret = esp_https_ota(&ota_client_config);

Per avviare il processo di aggiornamento. Se al termine la variabile ret contiene un esito positivo (ESP_OK), possiamo riavviare il chip in modo che il nuovo firmware sia eseguito:

esp_restart();

In una applicazione reale, abbiamo però la necessità di controllare periodicamente se sia disponibile un nuovo firmware e, solo in tal caso, procedere con l’aggiornamento. Come possiamo fare?

Nel programma che ho preparato per questo articolo e che illustro nel video seguente, vi mostro una modalità molto utilizzata anche in progetti commerciali… buona visione ;)

come al solito, il sorgente del programma è disponibile nel mio repository Github e il video contiene sottotitoli in italiano

DCC, configurazione decoder con CV

$
0
0

Nel precedente tutorial abbiamo visto come realizzare un decoder accessori DCC con Arduino.

Per semplificare lo sketch, tutti i parametri di configurazione del decoder (in particolare il suo indirizzo) sono stati definiti come costanti:

dcc-cv-001Spesso è però utile poter cambiare la configurazione del decoder senza riprogrammare il suo firmware (= lo sketch in esecuzione nel microcontrollore ATMega). Le specifiche DCC comprendono proprio un documento (S-9.2.2 DCC Configuration Variables) che introduce – per i decoder – il concetto di Configuration Variables (CV).

Le CV sono dei parametri, memorizzati in maniera non volatile (ovvero vengono mantenuti anche in mancanza di corrente) all’interno dei decoder, che possono definire diversi aspetti del funzionamento del decoder stesso.

La NMRA definisce, sempre nel documento S-9.2.2, un elenco di CV, numerandole da 1 a 1024 e attribuendo ad ognuna di esse un significato. Ad esempio per un decoder accessori è stabilito che l’indirizzo (9 bit) sia contenuto nelle CV 513 e 521:

dcc-cv-002

Quando acquistiamo un decoder commerciale, nel manuale è sempre riportata una tabella che indica, per ciascuna CV supportata, il significato. Ad esempio il manuale del decoder ESU SwitchPilot Servo, decoder in grado di comandare dei servocomandi per attivare scambi o altri movimenti, elenca le CV che controllano la posizione e la velocità dei 4 servocomandi:

dcc-cv-003

Molte centrali digitali supportano la service mode (documento S-9.2.3), ovvero sono in grado di programmare i valori delle CV di un decoder collegato ad una loro particolare uscita, detta binario di programmazione. Il modellista può quindi regolare il comportamento dei decoder installati nelle locomotive o che controllano accessori del plastico semplicemente posizionando le locomotive (o collegando i decoder accessori) sul binario di programmazione e programmando i corretti valori nelle CV.

CV1 vs CV513

Lo standard NMRA prevede che le CV utilizzabili dai decoder accessori siano quelle da 5131024. In realtà molti produttori, per non creare confusione o differenze tra i diversi tipi di decoder, hanno deciso di utilizzare anche per i decoder accessori i numeri di CV “bassi”. Ecco perché molti decoder accessori utilizzano, per configurare il proprio indirizzo, le CV 1 e 9.

NmraDcc

Una volta appreso il significato delle CV, vediamo come gestire la loro programmazione all’interno del nostro sketch.

Dobbiamo memorizzare i valori delle diverse CV in un’area di memoria che non venga cancellata ogni volta che spegniamo il decoder. Il processore ATMega328 utilizzato dal nostro Arduino Uno ha al suo interno una memoria non volatile da 1024 bytes; grazie alla libreria EEPROM di Arduino possiamo memorizzare e leggere valori in tale area di memoria.

La libreria NmraDcc può gestire autonomamente la gestione delle CV o – in alternativa – lasciarla completamente al programma esterno.

Se prendiamo il caso di scrittura di una CV (la centrale che scrive il valore di una CV nel decoder), la libreria NmraDcc offre infatti due metodi di callback:

  • extern uint8_t notifyCVWrite( uint16_t CV, uint8_t Value);
  • extern void notifyCVChange( uint16_t CV, uint8_t Value);

Il primo metodo sostituisce la gestione CV standard della libreria, mentre il secondo viene chiamato per notificare che una CV ha cambiato valore. Se guardiamo il codice della libreria la differenza risulta chiara:

dcc-cv-004

La libreria definisce inoltre i numeri di alcune CV standard, tra le quali quelle relative all’indirizzo del decoder:

dcc-cv-005

Decoder LED

Riprendiamo lo sketch del decoder LED del precedente tutorial e aggiungiamo la gestione delle CV.

Definiamo due CV:

  • la modalità di funzionamento (CV10)
  • la velocità di lampeggio (CV11)

Grazie alla CV10 sarà possibile scegliere se il led attivato sarà acceso fisso (valore della CV = 0) oppure lampeggiante (valore della CV = 1). In questo secondo caso, la velocità di lampeggio sarà configurata tramite la CV11 (dal valore 1 = 5 secondi al valore 100 = 50ms).

Definiamo per prima cosa una costante per ogni CV e il relativo numero:

#define CV_ACCESSORY_DECODER_MODE        10
#define CV_ACCESSORY_DECODER_BLINK_FREQ  11

Definiamo inoltre delle variabili che conterranno il valore a runtime di tali CV per evitare di leggere continuamente la EEPROM (che, secondo il datasheet, ha un ciclo di vita di circa 100.000 letture/scritture):

int decoderMode;
int blinkFrequency;

Nel metodo setup() dobbiamo quindi leggere i valori delle CV dalla EEPROM e assegnarli alle variabili:

decoderMode = Dcc.getCV(CV_ACCESSORY_DECODER_MODE);
blinkFrequency = Dcc.getCV(CV_ACCESSORY_DECODER_BLINK_FREQ);

Lasciamo gestire alla libreria la memorizzazione dei valori delle CV nella EEPROM. Per essere informati di quando uno di questi valori cambia e quindi aggiornare le nostre variabili, definiamo il metodo notifyCVChange():

void notifyCVChange(uint16_t CV, uint8_t Value) {
 
  if(CV == CV_ACCESSORY_DECODER_MODE) decoderMode = Value;
  else if(CV == CV_ACCESSORY_DECODER_BLINK_FREQ) blinkFrequency = Value;
}

Blink

Per gestire il lampeggio dei led, possiamo utilizzare la libreria Timer1. Tale libreria consente di eseguire periodicamente una funzione di callback.

La funzione di callback semplicemente cambia lo stato del led per ottenere il lampeggio:

void blinkLED() {
  digitalWrite(ledActive, !digitalRead(ledActive));
}

Se il decoder è in modalità lampeggio, al cambio di stato dell’uscita viene identificato il led attivo e schedulata la funzione di callback in base alla frequenza configurata nella apposita CV. In caso contrario viene semplicemente acceso il led:

if(outputInPair == 0) {      
  digitalWrite(GREEN_LED_PIN, LOW);
  ledActive = RED_LED_PIN;
} else {
  digitalWrite(RED_LED_PIN, LOW);
  ledActive = GREEN_LED_PIN;    
}
if(decoderMode == 1) {
  Timer1.setPeriod(5000000 / blinkFrequency);
  Timer1.attachInterrupt(blinkLED);
} 
else digitalWrite(ledActive, HIGH);
}

lettura CV

Lo standard DCC prevede anche la possibilità – per una centrale digitale – di leggere il valore di una CV del decoder collegato al binario di programmazione. In un prossimo articolo vedremo come implementare anche questa funzionalità!

Demo

Ecco un filmato che mostra come sia possibile modificare il funzionamento del decoder led programmando opportunamente le CV tramite PC (software Rocrail) e centrale digitale SPROG (sottotitoli in italiano disponibili):

ESP32 (38) – Factory reset

$
0
0

Negli ultimi due articoli di questo tutorial, vi ho mostrato come aggiornare over the air il firmware in esecuzione nel chip esp32.

A volte è però necessario ritornare al firmware di fabbrica, ovvero quello memorizzato nella flash al momento della programmazione del chip. Molti dispositivi elettronici dispongono di un pulsante o pin che, se premuto per qualche secondo, effettua questo reset:

A10317_image1

In questo articolo vi mostrerò come aggiungere questa possibilità al vostro progetto.

Partizioni

Come spiegato in un precedente articolo, la memoria flash collegata al chip esp32 è suddivisa in diverse partizioni, secondo un layout configurato in fase di programmazione del chip.

Le partizioni che possono contenere un firmware sono di tipo app. La partizione che contiene il firmware memorizzato in fase di programmazione ha il sottotipo factory.

Il framework esp-idf mette a disposizione una funzione per cercare le partizioni all’interno della memoria flash:

#include "esp_partition.h"
[...]
esp_partition_iterator_t esp_partition_find(
  esp_partition_type_t type, esp_partition_subtype_t subtype, const char* label);

E’ possibile indicare alcuni filtri per restringere la ricerca: il tipo (type) di partizione, l’eventuale sottotipo (subtype) e infine una etichetta (label).

Per cercare la partizione che contiene il firmware di fabbrica possiamo quindi scrivere:

esp_partition_iterator_t pi = esp_partition_find(
  ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);

La funzione restituisce un partition iterator, ovvero un oggetto che consente di scorrere tra le diverse partizioni trovate.

Se la ricerca ha avuto successo, tale oggetto è diverso da NULL ed è possibile ottenere un puntatore alla partizione con la funzione:

const esp_partition_t* esp_partition_get(esp_partition_iterator_t iterator);

Una volta terminato l’uso, è importante rilasciare l’oggetto iterator con:

void esp_partition_iterator_release(esp_partition_iterator_t iterator);

Ottenuta la partizione con il firmware di fabbrica, non rimane che configurarla nuovamente come partizione di boot e riavviare il chip:

if(pi != NULL) {
  const esp_partition_t* factory = esp_partition_get(pi);
  esp_partition_iterator_release(pi);
  if(esp_ota_set_boot_partition(factory) == ESP_OK) esp_restart();	
}

Demo

Nel video seguente (sottotitoli in italiano disponibili), vi mostro come effettuare il factory reset del chip. Nel video spiego inoltre come poter “contare” il numero di secondi in cui un pulsante è premuto per attivare la procedura di reset solo oltre una soglia fissata (3 secondi in questo esempio). Buona visione!

Display cinesi ST7735 e Arduino

$
0
0

Alla ricerca di un display TFT per un mio progetto con Arduino, ho trovato in vendita su diversi siti cinesi dei display basati sul chip ST7735 di Sitronix (datasheet).

Il chip ST7735 offre una interfaccia SPI (Serial Peripheral Interface) ma la piedinatura dei display che ho ricevuto “sembra” riportare i classici PIN del bus I2C (SDA, SCL…):

Si tratta in realtà solo di una nomenclatura poco corretta; il significato dei PIN è il seguente:

  • SDA -> MOSI (segnale Master Out Slave In del bus SPI)
  • SCL -> SCK (segnale di clock del bus SPI)
  • CS -> SS (segnale di Slave Select del bus SPI)
  • RST -> reset
  • DC -> Data/Command (pin utilizzato per distinguere l’invio di dati o comandi al display)
  • BLK -> Backlight (abilita la retro-illuminazione)

Una volta noto il significato dei diversi PIN, il collegamento ad Arduino è semplice.

Per prima cosa identifichiamo – in base alla nostra scheda Arduino – quali PIN corrispondono ai segnali del bus SPI. Per gli altri, possiamo scegliere liberamente tra i PIN liberi.

Ad esempio per un Arduino Uno ho effettuato i seguenti collegamenti:

(come vedete, ho collegato il pin BLK direttamente a Vcc per tenere sempre accesa la retro-illuminazione. E’ anche possibile collegarlo ad un PIN di Arduino per poterla controllare via software, ad esempio per esigenze di power saving).

Per comandare il display, dobbiamo utilizzare una apposita libreria. La mia preferenza va a quelle sviluppate da Adafruit, in particolare:

Le due librerie – installabili tramite il Library Manager dell’IDE di Arduino – lavorano insieme per consentirci di utilizzare al meglio il display.

Adafruit ha preparato un ottimo tutorial per il loro utilizzo, qui vi voglio solo indicare come configurare il display rispetto ai collegamenti che vi ho riportato sopra:

#define TFT_CS        10
#define TFT_RST        2
#define TFT_DC         3
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

Ecco in esecuzione l’esempio “graphictest” fornito insieme alla libreria:

Se state utilizzando una scheda basata su chip esp32 e volete visualizzare immagini bitmap, date un’occhiata alla mia libreria, SPIFFS_ImageReader, che si integra perfettamente con quelle di Adafruit di cui vi ho parlato sopra!

Display con ST7735 – risparmio energetico

$
0
0

Come anticipato in un precedente articolo, sto utilizzando dei display basati sul driver ST7735 per un futuro progetto.

Visto che il mio progetto sarà alimentato a batteria, è molto importante il tema del risparmio energetico.

Durante un utilizzo normale, con la retroilluminazione accesa, il display consuma circa 67mA:

Come prima cosa, possiamo spegnere la retroilluminazione portando a massa il pin BLK. In questo caso il consumo scende a circa 3mA:

Si può ridurre ulteriormente il consumo del display? Leggendo il datasheet, si trova che il chip ST7735 può entrare in uno stato di sleep, a minimo consumo.

Per farlo è necessario inviare il comando SLPIN, mentre per uscire da tale stato il comando da usare è SLPOUT:

Inizialmente, la libreria Adafruit-ST7735-Library non supportava questi comandi. Ho quindi proposto, ed è stata accettata, una modifica che includesse il nuovo metodo:

void Adafruit_ST77xx::enableSleep(boolean enable)

Con il parametro enableTRUE viene mandato il comando SLPIN e il chip ST7735 entra in modalità di sleep. In tale modalità, il consumo è di soli 0,47mA:


Impariamo insieme

$
0
0

Inizio con questo post una nuova serie di tutorial, con una impostazione un po’ diversa rispetto a quanto avete trovato finora sul mio sito.

Il nome di questa serie di tutorial, impariamo insieme, vuole raccontare lo spirito che metterò nel realizzarli. Saranno video in cui voi ed io impareremo insieme un nuovo argomento, procedendo passo a passo, senza dare nulla per scontato!

Utilizzerò quindi in prevalenza il formato video, caricandoli su Youtube, mentre sul sito pubblicherò approfondimenti, listati, link a materiale aggiuntivo…

Buona visione!

Modificare una lampadina smart (parte 1)

$
0
0

Ho recentemente acquistato un paio di lampadine smart, che possono essere controllate con il proprio smartphone tramite una app.

Le lampadine sono RGBW, ovvero hanno la possibilità di emettere diversi colori (RGB) oltre al colore bianco freddo (W) e sono di marca Fcmila:

Non sono riuscito a trovare il sito ufficiale del produttore ma soltanto il negozio su Aliexpress.

A differenza di altre lampadine acquistate in passato, queste utilizzano l’applicazione Cloud Intelligence

non è quindi possibile utilizzare tuya-convert per installare un firmware alternativo e consentire il controllo tramite altri sistemi di domotica (ad esempio l’ottimo Home Assistant).

Altri utenti (come blakadder) sono riusciti a sostituire il modulo di controllo della lampadina con uno basato sul chip ESP8266. Voglio verificare se sia possibile anche per il mio modello di lampadina.

Ho iniziato ad aprire la lampadina rimuovendo il diffusore. E’ sufficiente rimuovere con un taglierino la colla perché questo si stacchi facilmente, rivelando un circuito stampato (etichettato RC-6253) con i led bianchi all’esterno e quelli RGB all’interno:

  

Ho quindi rimosso il silicone bianco che tiene ancorato il PCB all’involucro della lampadina e sono riuscito a sfilarlo:

 

Il PCB con i led è collegato alla scheda di controllo tramite un connettore a 6 pin. La scheda è invece collegata con due fili al connettore E27 della lampadina. Ho dovuto tagliarli per poter lavorare più facilmente sulla scheda:

 

La scheda è composta da due PCB montati a 90°. Quello principale riporta la dicitura www.ada.top e ha il compito di trasformare la tensione di rete (230V) in una tensione adatta per l’alimentazione dei circuiti logici (3.3V) e per i LED. La scheda montata a 90° sembra montare un microcontrollore per la gestione della lampadina e delle comunicazioni (WiFi/BLE):

 

Ho rimosso con una treccia dissaldante lo stagno e sono riuscito a separare le due schede:

 

Il processore è etichettato OPL1000. Cercando su Internet si trova che è prodotto da una azienda cinese, la Opulinks, e che è anche disponibile un SDK per lo sviluppo di applicazioni. Purtroppo al momento non ho trovato alcun firmware (es. Tasmota) che sia stato portato per tale processore.

Il significato dei primi 4 pin mi è sembrato ovvio: ho quindi provato a saldare dei fili e collegare il modulo ad un adattatore USB-Seriale per vedere se era possibile instaurare una comunicazione via seriale:

 

Purtroppo, pur variando i parametri di collegamento e provando ad inviare caratteri, non sono riuscito a ricevere nulla dal modulo. E’ possibile che il firmware in esecuzione non abbia un output su seriale o che serva qualche particolare sequenza di inizializzazione per attivarlo. Fatemi sapere nei commenti se siete stati in grado di comunicare con il modulo.

Nella prossima puntata analizzeremo in dettaglio il funzionamento del modulo e lo schema elettrico dei vari circuiti stampati…

Modificare una lampadina smart (parte 2)

$
0
0

Continuiamo il lavoro di modifica della lampadina smart Fcmila analizzando il modulo WiFi/BLE.

Nel primo episodio avevo provato a collegarlo al PC senza successo: la comunicazione seriale sembra disattivata.

Ho quindi saldato dei fili ai rimanenti pin del modulo per poterne analizzare il comportamento:

 

Grazie ad un oscilloscopio (il modello che sto usando è un ADS1013D) ho potuto verificare il comportamento dei pin RGBCW mentre modificavo i parametri della lampadina tramite l’app ufficiale:

Sui pin è presente una onda quadra, con frequenza di circa 4KHz. Il duty cycle dell’onda (ovvero il rapporto tra la durata del segnale “alto” e il periodo totale del segnale) varia a seconda della luminosità impostata sull’app. Questo è un modo molto utilizzato per variare la luminosità dei LED con un segnale digitale.

Il comportamento è analogo per tutti i pins tranne per il pin W, che invece in tutte le mie prove è rimasto fermo a 0V. Posso ipotizzare che il modulo sia stato pensato per lampadine con LED bianco freddo (C = cold), sia per quelle con LED bianco caldo (W = warm). Il mio modello non ha LED bianco caldo e quindi è corretto che non vi sia un segnale sul pin corrispondente.

Sono quindi passato ad analizzare la scheda con i LED, per capire dove finissero i segnali generati dal microcontrollore.

Questa scheda presenta un connettore a 6 pins per il suo collegamento alla scheda principale. Dopo alcune prove, sono riuscito a dare un significato ad ogni pin:

 

Oltre ai LED, sulla scheda sono presenti alcuni componenti SMD: si notano in particolare 3 transistor (MOSFET) e 6 resistenze.

Per il controllo dei LED RGB, lo schema per ogni canale è il seguente:

Il valore della resistenza di pull-down R1 è uguale per i 3 canali (5.1Kohm), mentre la resistenza che limita la corrente che attraversa i LED è diversa e vale:

  • LED rossi: 430ohm
  • LED verdi: 200ohm
  • LED blu: 180ohm

I diversi valori servono sicuramente per normalizzare la luminosità dei diversi LED.

Per i LED bianchi invece il collegamento è molto più semplice. I led sono divisi in due gruppi; ad ogni gruppo è collegato come ANODO il pin POWER e come CATODO il pin WHITE:

Guardando meglio la scheda principale, ho scoperto che i progettisti hanno scelto di mettere qui il MOSFET (e le resistenze) e quindi in definitiva lo schema è analogo a quanto visto sopra per i 3 colori:

Terminata l’analisi del funzionamento delle diverse parti della lampadina, nella prossima puntata vedremo come impiantare un nuovo modulo di controllo…

Modificare una lampadina smart (parte 3)

$
0
0

In questa ultima puntata, vediamo come sostituire il modulo di controllo della lampadina con uno che supporti un firmware open.

Uno dei firmware sicuramente più utilizzati in ambito domotico è tasmota. Questo firmware è sviluppato per il chip ESP8266 di Espressif, chip largamente utilizzato proprio in apparati domotici (tasmota è nato proprio come firmware alternativo per i dispositivi Sonoff di iTead)

Per iniziare i miei test, ho scelto di utilizzare una scheda di sviluppo, la Wemos D1.

Ho quindi collegato la scheda al mio computer e l’ho programmata con l’ultima versione di Tasmota utilizzando il comodo software Tasmotizer:

Avevo bisogno di 4 pin per generare i 4 segnali PWM e controllare i diversi colori (rosso/verde/blu e bianco). La serigrafia della scheda di sviluppo non riporta i nomi reali dei pin; ho quindi fatto riferimento a questo ottimo post di randomnerdtutorial per il corretto pinout e per scegliere i pin più adatti.

La configurazione di Tasmota prevede di associare i pin alle funzioni PWM1,2,3… Per conoscere quale colore è associato ad ogni funzione PWM, ho utilizzato la tabella presente nella wiki ufficiale:

Il mapping che ho utilizzato è stato quindi il seguente:

  • pin D5 (GPIO14) – PWM1 (colore ROSSO)
  • pin D6 (GPIO12) – PWM2 (colore VERDE)
  • pin D7 (GPIO13) – PWM3 (colore BLU)
  • pin D8 (GPIO15) – PWM4 (colore BIANCO)

Ho creato un nuovo template con questo mapping collegandomi alla interfaccia web di Tasmota e selezionando Configuration – Configure Template:

Per i primi test, ho preferito avere a disposizione il controllo indipendente dei 4 canali PWM. Per fare questo ho inserito il comando

SetOption68 1

via console:

In questo modo nella schermata principale sono disponibili 4 sliders e 4 pulsanti per controllare i 4 colori:

Ed ho avuto successo! Tramite l’interfaccia web ero in grado di controllare la lampadina:

 

Natualmente la scheda Wemos D1 non può essere inserita nella lampadina. Sono quindi passato ad un modulo ESP-12

Per la programmazione di questi moduli sono disponibili delle ottime schede… che purtroppo non possiedo 😉 Ho quindi adottato una soluzione quick and dirty, realizzando su millefori il circuito illustrato in questo articolo di Hackster:

 

Sono cosi riuscito a programmare Tasmota, sempre via Tasmotizer. Ho anche utilizzato la comoda funzione di Tasmotizer per configurare direttamente i parametri della mia rete WiFi e ottenere l’indirizzo IP del modulo una volta connesso:

 

Il chip ESP8266 richiede, per effettuare un boot normale utilizzando la memoria flash, che alcuni pin abbiano un valore logico determinato:

  • GPIO0pull up a VCC (se collegato a massa si attiva la modalità programmazione)
  • REST, CH_PDpull up a VCC
  • GPIO15, pull down a GND (se collegato a VCC si attiva il boot da SD card)

Ho quindi saldato tre resistenze da 10Kohm direttamente sul retro del modulo per effettuare tutti i collegamenti pull up

GPIO15 invece è utilizzato, nella mia configurazione, come pin PWM per il controllo dei LED bianchi. Se guardate lo schema riportato nella seconda parte di questo tutorial vedrete che è già collegato a GND tramite una resistenza da 5.1Kohm.

Ho saldato dei fili ai rimanenti pin e racchiuso il modulo in una guaina termorestringente per evitare qualsiasi contatto accidentale

 

Infine ho realizzato tutti i collegamenti tra il modulo ESP-12 e la scheda di controllo e sistemato il tutto in modo da poter chiudere nuovamente la lampadina:

 

Ecco il risultato finale, una lampadina smart con un nuovo cervello opensource! (sono disponibili i sottotitoli in italiano)

Modificare una lampadina smart (nuovo modello)

$
0
0

Dopo aver pubblicato i precedenti tre articoli su come modificare una lampadina smart fcmila (parte 1, parte 2, parte 3), un amico mi ha inviato una sua lampadina chiedendomi se fosse possibile modificarla.

A prima vista la sua lampadina (a destra) sembra simile alla mia (a sinistra):

La prima differenza che si nota dalla scatola è che l’app da utilizzare è diversa: viene indicato il download di Smart Life, che è l’applicazione ufficiale di Tuya. Questo mi fa pensare che il chip di controllo e il firmware possano essere diversi:

Anche a livello di dimensioni, pur avendo la medesima potenza (15W), la nuova lampadina è decisamente più grande:

Ancora prima di aprirla, ho provato ad utilizzare tuya-convert ma senza successo. E’ possibile che nelle nuove versioni del proprio firmware, Tuya abbia risolto i problemi di sicurezza che consentivano di aggiornare i dispositivi con firmware non ufficiali.

Ho quindi rimosso il diffusore plastico per accedere al circuito interno. La lampadina presenta due circuiti stampati: quello più esterno con i led e quello più interno con microcontrollore, memoria e mosfet. Il chip di controllo è il famoso esp8266:

 

Ho subito notato la presenza di alcuni pad per i segnali 3.3V, GND, TX, RX e IO0. Si tratta proprio dei segnali necessari per programmare il chip: immagino vengano utilizzati in fabbrica per caricare il firmware della lampadina.

Ho quindi saldato dei fili ad ognuno dei pad e li ho collegati ad un adattatore USB – Seriale.

Come già visto nel precedente post, per attivare la modalità di programmazione del chip il pin IO0 deve essere collegato a massa:

 

Grazie a Tasmotizer la programmazione di Tasmota è stata semplicissima:

L’ultima cosa da fare era identificare i corretti pin di output per i diversi colori… dopo alcuni tentativi ecco il template corretto:

In conclusione la modifica di questo modello di lampadina è stata molto più semplice, grazie alla presenza del chip esp8266 e ai pad con i segnali per la sua programmazione!

 

Viewing all 84 articles
Browse latest View live