Controllare una lampadina WiFi con moduli ESP

Abbiamo analizzato in un precedente articolo, il protocollo utilizzato dall’applicazione SmartLife (e altre similari) per controllare una lampadina WiFi ed abbiamo scritto su PC un programma C# per testarlo, grazie al quale siamo in grado di effettuare le operazioni principali come accendere e spegnere la lampadina, cambiare tonalità del colore e della luce bianca. Questo programma non è fine a se stesso, ma nasce come punto di riferimento da cui partire per effettuare il porting su piattaforme a microprocessore come i noti moduli ESP che hanno uno stack TCP/IP e sono WiFi. Sarebbe stato impensabile eseguire le varie prove di analisi direttamente su questi moduli anche solo per il tempo di programmazione che, seppur molto ridotto, avrebbe reso ogni singolo test più complicato. Ora però possiamo iniziare.

Schema a blocchi dell’encoding

Prima di addentrarci nella scrittura della libreria è doverosa una breve analisi dei blocchi che compongono l’encoding dei comandi da inviare alla lampadina. Abbiamo visto, nel precedente articolo, come l’app per gestire la lampadina su cui ci siamo focalizzati (Tuya Smart) usi una codifica AES128 per trasformare una stringa di testo in chiaro in tanti blocchi di 16 byte che poi vengono uniti ed inseriti nel payload del pacchetto TCP da inviare alla lampadina. A questo pacchetto va però, inserito una CRC di controllo che va calcolata ed inserita in fondo (prima di un suffisso che è sempre uguale per tutti i pacchetti di questo tipo).
Quindi avremo uno schema di funzionamento complessivo come indicato dalla Fig. 1.

 

Fig. 1

Non scenderemo nei dettagli del pacchetto e della codifica perché già trattate nell’articolo precedente, ma ci occuperemo di scrivere una libreria per permettere ai moduli hardware di interfacciarsi alla lampadina senza che l’utilizzatore finale debba essere per forza a conoscenza di tutti i passaggi interni.
Ad esempio, il primo blocco si riferisce alla stringa di testo che contiene il comando da inviare alla lampadina dopo essere stato codificato, ma sarebbe impensabile dover usare quella stringa come parametro di ingresso di una funzione, perché renderebbe tutto poco chiaro e poco gestibile.
Dovremmo scrivere funzioni di alto livello che poi, internamente, si occupino di generare la stringa di comando, di codificarla tramite codifica AES128 e di costruire il pacchetto così formato aggiungendogli in fondo i 4 byte di CRC, prima di inviarlo nella rete verso la lampadina.
Riportiamo in Fig. 2 la lista di comandi analizzati nell’articolo precedente e che useremo per la scrittura delle funzioni.
Da notare come il comando 5, usato per rappresentare il colore, necessiti di una stringa contenente sia le componenti RGB sia quelle HSV quindi dovremo creare anche un modulo software per convertire il colore tra i due formati e chi userà la libreria potrà ad esempio scegliere di impostare il colore nella modalità preferita, senza dover passare direttamente la stringa di testo.
Queste funzioni di conversione sono già state utilizzate anche nel programma C# quindi abbiamo tutto per iniziare a scrivere la libreria eseguendo il porting sia su un ESP8266 che su un ESP32.

 

Fig. 2

Moduli ESP

Entrambi questi moduli sono WiFi e sono dotati di uno stack TCP/IP che li rende molto versatili. Esistono diverse versioni, sono economici, hanno dimensioni ridotte, possono comportarsi sia come AccessPoint, sia come Station, non hanno bisogno di un controllore esterno perché ne hanno uno già integrato, possono essere programmati anche dall’IDE di Arduino e per tutti questi motivi sono diffusissimi in campo IOT.
Questi moduli verranno utilizzati all’interno di una scheda di sviluppo chiamata NodeMCU (è forse quella più usata, ma ne esistono di tante versione similari) che si interfaccia al PC tramite connettore USB (connettore tipo micro B) e presenta un regolatore di tensione (per far funzionare il modulo ESP a 3.3V) ed un chip di conversione seriale (tra USB e i pin del modulo) ed ovviamente il modulo stesso.
Per scrivere la nostra libreria non ci servirà nient’altro che questa scheda e la lampada WiFi.
Se non avessimo già installato la toolchain di sviluppo per ESP8266, dovremmo farlo impostando dall’IDE di Arduino il campo Additional Boards Reference (nella finestra di Preferences) con questa stringa per il modulo ESP8266 e questa per il modulo ESP32. Successivamente occorre installare le toolchain di sviluppo dalla finestra Boards Manager, come in Fig. 3, in modo da scaricare su PC le librerie native, i file con le piattaforme disponibili e qualche esempio per iniziare a programmare su questi moduli. Il modulo ESP32, rispetto al cugino ESP8266 integra al suo interno anche il protocollo Bluetooth BLE e ci permetterà di ampliare il raggio di applicazioni possibili.

 

Fig. 3

Noi useremo il modulo ESP in modalità Station, in modo da farlo connettere al nostro router, sul quale sarà già stata connessa la lampadina WiFi che controlleremo. Il codice per il collegamento al router è già presente negli esempi forniti con la toolchain e si presenta come nel Listato 1.

Listato 1

#include <ESP8266WiFi.h>
const char* ssid = “STATION”;
const char* password = “pwd1234”;
void setup()
{
Serial.begin(115200);
Serial.print(“Connecting to “);
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(“.”);
}
Serial.println(“WiFi connected”);
Serial.println(“IP address: “);
Serial.println(WiFi.localIP());
}

Libreria TuyaLamp

Inizieremo scrivendo una libreria per il modulo ESP8266, ma in buona parte dei casi una libreria scritta per ESP8266 può funzionare anche sul più evoluto ESP32, occorre, tuttavia prestare particolari accorgimenti nel caso in cui vengano usate funzioni di librerie native che hanno subito modifiche da un modulo all’altro ed in tal caso intervenire con le direttive #if defined() specificando il diverso comportamento. Ad esempio, per connettere il modulo alla lampada ed inviare pacchetti TCP, alcune funzioni come connect() sono contenute in librerie diverse (WifiCLient.h per ESP8266 e WiFi.h per ESP32) e presentano un numero di parametri differenti, altre funzioni invece, non sono nemmeno comuni.
Prima di tutto inseriamo una nuova cartella sotto la cartella Libraries dove sono contenute le altre librerie, creando una struttura come in Fig. 4.

Fig. 4

In particolare il nome della cartella avrà il nome della libreria, conterrà un file descrittivo library.properties con alcune informazioni e due cartelle. Sotto src inseriremo i file che costituiscono la libreria vera e propria che sarà scritta in C++, si tratta di TuyaLamp.cpp e TuyaLamp.h (dal nome della libreria). All’interno della stessa cartella potrebbero esserci altri file di codice di supporto che potranno essere scritti in C (file con estensione .c) o anche in C++ (file con estensione .cpp) e che quindi conterranno altri classi, come ad esempio la classe colorconverter che, nel nostro caso, useremo per effettuare le conversioni da un formato di colore all’altro.
Potrebbe esserci, anche se non è obbligatorio, anche una cartella include che contiene file.h con funzioni ausiliarie; nel nostro caso troviamo il file tuyacrc.h in cui abbiamo scritto una tabella per il calcolo CRC del pacchetto TCP. Infine parallelamente alla cartella src potrà esserci una cartella examples contenente file con estensione .ino, quindi veri e propri sketch in cui mostriamo come utilizzare la nostra libreria.
Rivediamo la lista dei comandi presenti in Fig. 2 e consideriamo che tutto si basa sulla codifica di una stringa di testo di questo tipo:

{“devId”:”3602383098f4abc8b73c”,”t”:1613242568,”dps”:{“5”:”ff00000167ffff”}}

In cui devId è il codice univoco che rappresenta la particolare lampadina, t è il timestamp che, però, può essere sempre lo stesso e non dobbiamo preoccuparci di aggiornarlo, dps è la lista di comandi e “5” è il comando per settare il colore secondo lo schema in Fig. 2.
Possiamo già pensare all’interfaccia della nostra libreria scrivendo il file .h che sarà come mostrato nel Listato 2.

Listato 2

class TuyaLamp
{
Public:
TuyaLamp(String ip, String deviceId, String localKey);
private:
int _port;
String _ip;
String _deviceId;

AES128 _aes128;
WiFiClient _client;
ColorConverter _colorUtility;
// functions
void encryptMessage(String plainText, byte outputBuffer[]);
uint32_t tuyacrc(byte arrbytes[], int len);
void createPacket(byte encBuffer[], byte outpacket[]);
void preparePacketFromCommand(String command, byte packet[], int len);
String commandColor(byte r, byte g, byte b);
String commandColor(short h, byte s, byte v);
String commandBuilder(String timestamp, String cmdType, String cmdValue);
int connect();
int sendPacket(byte packet[], int len);
void cmdActivator(String command);
public:
void TurnOn();
void TurnOff();
void SetColor();
void SetColor(byte r, byte g, byte b);
void SetColor(short h, byte s, byte v);
void SetWhite();
void SetWhiteTemp(int temp);
void SetWhiteBright(int brl);
void SetWhite(int temb, int brl);

 

Commentiamo brevemente i vari metodi che compaiono nell’interfaccia della classe TuyaLamp, senza entrare nel dettaglio del codice interno anche perché è pressoché lo stesso di quello presente nel programma in C# visto precedentemente, salvo alcuni accorgimenti dovuti al fatto che stiamo scrivendo codice per microprocessori che, se pur potenti, hanno risorse di memoria comunque limitate. Per questo motivo, se per esempio in C# potevamo usare variabili di tipo intero per rappresentare le singole componenti RGB, qua conviene usare byte per ottimizzare spazio.
Partendo dall’alto abbiamo le funzioni private come encryptMessage() che effettua la codifica AES128 del messaggio di testo suddividendolo in blocchi da 16 byte (128 bit da cui appunto AES128) e trasformandoli in altrettanti blocchi della stessa dimensione, ma codificati usando il modulo AES128 che appartiene ad una libreria esterna e che, se non già presente all’interno della cartella Libraries, occorrerà scaricare preventivamente. Ogni blocco verrà codificato usando la chiave di criptazione ricavata dal sito Tuya come visto nell’articolo precedente, e che abbiamo a disposizione avendola ricevuta dal costruttore (localkey). Questi blocchi verranno uniti in un unico buffer interno che costituirà parte del payload del pacchetto finale. Ovviamente più lunga sarà la stringa di comando, maggiori saranno i blocchi e più lungo sarà il buffer che però avrà sempre una lunghezza multipla di 16 byte; nel caso molto probabile in cui la stringa di testo avrà una lunghezza non multipla di 16 caratteri, sul blocco finale verrà fatto un padding di byte a zero.
La funzione createPacket() utilizza il buffer appena creato e lo inserisce all’interno di un altro ancora più grande aggiungendo alcuni byte di comando, prefisso e suffisso (già esaminati in passato) e su questo calcola la CRC di controllo utilizzando la funzione tuyacrc() che a sua volta sfrutta un buffer contenuto nella cartella include vista prima.
La funzione preparePacketFromCommand() è solo un wrapper che usa encryptMessage() e createPacket() e nasce per eseguire i primi test passandogli direttamente l’intera stringa di comando vista sopra, poco pratica, ma utile in fase di debug. A tal scopo viene usata commandBuilder() che costruisce in automatico la stringa di test inserendo al suo interno il deviceId, il timestamp e la stringa di comando vero e proprio; a seconda del tipo di comando (modalità colore, modalità bianco, cambio componenti colore, cambio luminosità e temperatura del bianco) si comporterà in modo diverso per comporre correttamente la stringa di testo che rappresenta il comando.
Infine, tra le funzioni private, la connect() e la sendPacket() usano il modulo esterno WiFiClient (occorre includere la libreria WifiClient.h o la Wifi.h nel caso di ESP32, che sono di sistema e si trovano già nella toolchain scaricata in precedenza) e si occupano rispettivamente di connettersi alla lampadina ed inviare il pacchetto costruito dalla linea di comando. Entrambe sono chiamate da cmdActivator() che prende in ingresso proprio la stringa di comando completa e crea il pacchetto TCP prima di inviarlo effettivamente alla lampadina.
Passiamo ora alle funzioni pubbliche che userà l’utente finale e che chiameranno tutte la cmdActivator() dopo aver interpretato il comando. TurnOn() e TurnOff() non hanno bisogno di commenti ed usano il comando di tipo1, passando parametro true o false rispettivamente.
SetColor() si presenta in tre forme diverse e sfruttando l’overloading del C++, ha tre comportamenti diversi a seconda del numero e del tipo dei parametri usati; senza parametri abbiamo il passaggio alla modalità colore ritrovando il colore impostato in precedenza, con tre parametri di tipo byte imposteremo il colore RGB corrispondente, mentre con il primo parametro di tipo short (la tonalità Hue è rappresentata da un valore che varia da 0 a 360, quindi un byte non basterebbe) e altri due byte (Saturation e Value sono rappresentati da un valore che varia da 0 a 100) imposteremo il colore secondo lo schema HSV. Mentre la prima usa il comandi di tipo 2 al quale passa il parametro “colour”, le altre due usano il comando di tipo 5 e per scrivere la stringa nel giusto formato con tutte le componenti di colore (sia RGB che HSV) viene usato l’oggetto _colorUtility che è un’istanza della classe ColorConverter creata in precedenza e contenuta nei file ColorConverter.cpp e ColorConverter.h visti prima.
Discorso analogo per le due SetWhite(), questa volta le componenti in gioco sono solo due, luminosità e temperatura del bianco; chiamando la SetWhite() senza parametri passeremo alla modalità del bianco col comando di tipo 2, mentre usandola con i due parametri verranno gestiti i comandi di tipo 3 e 4.
Per permettere una regolazione separata tra luminosità e temperatura e dal momento che i comandi sono indipendenti, sono state create anche altre due funzioni specifiche: SetWhiteBright() e SetWhiteTemp().
Chiudono la libreria la funzione DebugEnable() per abilitare la stampa di messaggi di debug su seriale e la funzione ShowBuffer() che ci permette di visualizzare l’ultimo pacchetto codificato e che, in caso di problemi iniziali, potrà essere confrontato con quello in uscita dal programma C# per capire meglio gli errori in fase di sviluppo.

Applicazioni per ESP8266

Iniziamo dal modulo ESP8266 e vediamo qualche possibile applicazione che utilizzi questa libreria.
La prima è di sicuro la più semplice, ed è nata in fase di test ed in parallelo con lo sviluppo della libreria stessa; ovvero la possibilità di impartire i comandi da seriale per controllare la lampadina in modo da verificare il corretto funzionamento di ogni metodo creato. Dopo aver inizializzato l’oggetto TuyaLamp passandogli l’IP della lampadina, il suo deviceId e la localKey e dopo aver connesso ESP8266 al nostro router, aspettiamo nel loop principale che venga premuto un tasto dal terminale seriale ed associamo una funziona ad ogni tasto. Nel caso delle funzioni che richiedono parametri, come ad esempio la SetColor() con i tre paramentri a byte per impostare il colore usando componenti RGB, dobbiamo prevedere il caricamento (sempre via seriale e convertendo i caratteri inseriti in numeri) di tre variabili indipendenti che verranno visualizzate a schermo premendo il tasto ‘h’ (help) e che verranno usate nelle funzioni che usano parametri. Il numero di queste variabili è tre proprio perché il numero massimo di parametri usati dalle nostre funzioni è tre.
Una parte del codice del loop principale è visibile in figura nel Listato 3.

Listato 3

void loop()
{
if (Serial.available() > 0)
{
// get incoming byte:
char charReceived = Serial.read();
Serial.print(“pressed:”);
Serial.println(charReceived);
if (recVar1==true)
{
var1[i1] = charReceived;
i1++;
}

if (charReceived == ‘x’)
{
Serial.println(“x”);
recVar1 = true;
}

if (charReceived == 0x0D)
{
if (recVar1 == true)
{
recVar1 = false;
var1[i1] = ‘\0’;
i1=0;
v1 = atoi(var1);
Serial.print(“x is “);
Serial.println(v1);
}

}

// — turn on —
if (charReceived == ‘a’)
{
lamp.TurnOn();
}
// — color hsv —
if (charReceived == ‘v’)
{
lamp.SetColor((short)v1, (byte)v2, (byte)v3);
}
// — color rgb —
if (charReceived == ‘u’)
{
lamp.SetColor((byte)v1, (byte)v2, (byte)v3);
}

 

Ovviamente prima di tutto occorrerà richiamerà la nostra libreria usando l’include

#include <TuyaLamp.h>

ed istanziare l’oggetto con:

TuyaLamp lamp(“192.168.1.5”, “3602383098f4abc8b73c”, “c7e961ddad19566e”);

Altra possibile applicazione potrebbe essere l’utilizzo del modulo in abbinamento con un sensore di distanza ad ultrasuoni (come il famoso HC04, già ampliamente utilizzato) installato nel garage in prossimità di zone “limite” in modo da segnalare con un colore rosso quando ci stiamo avvicinando troppo ad oggetti posti di fronte a noi e con un colore verde il via libera, come una sorta di semaforo.
Occorrerà utilizzare un controllo con isteresi in modo da ridurre il rumore intorno alla distanza limite.
Successivamente potrebbe rimanere acceso in modalità bianco per un tempo prefissato per darci possibilità di lasciare l’auto e chiudere il cancello.
Il modulo ESP8266 ha ovviamente anche varie modalità di sleep per abbassare i consumi e nel caso il garage fosse lontano dal nostro router potremmo sempre configurare il modulo in modalità AccessPoint e connettere la lampada alla sua rete. Potere trovare un frammento di codice nel Listato 4.

Listato 4

#include <TuyaLamp.h>
#define THRESHOLD 100
#define SUP_LIMIT 10
#define INF_LIMIT 10
#define MAX_THRESHOLD (THRESHOLD + SUPLIMIT)
#define MIN_THRESHOLD (THRESHOLD + INFLIMIT)

TuyaLamp wifiLamp(“192.168.1.5”,
“3602383098f4abc8b73c”, “c7e961ddad19566e”);
void setup(void)
{
Serial.begin(115200);
initSonar();
wifiLamp.SetColor((byte)0, 255, 0);
}
void loop(void)
{
pulse_10us();
float distance = getDistance();
Serial.print(distance);
Serial.println(“cm”);
delay(250);
if (distance > MAX_THRESHOLD)
{
// green
wifiLamp.SetColor((byte)0, 255, 0);
}
else if ( distance < MIN_THRESHOLD )
{
// red
wifiLamp.SetColor((byte)255, 0, 0);
}
else
{
// do nothing

Immagine da collocare Fig. 5

Applicazioni per ESP32

Proviamo ora la stessa libreria sul modulo ESP32 e sfruttando la sua capacità di gestire anche il protocollo Bluetooth, possiamo collegare un controller della PlayStation3 (Sixaxis) che con i suoi tasti e levette analogiche ben si presta per gestire la lampadina in tutte le sue potenzialità, regolandola nelle varie tonalità in modalità HSV o con le singole componenti di colore in modalità RGB o ancora modificando l’intensità e la temperatura del bianco.
Per la gestione del controller Sixaxis, le cui caratteristiche sono elencate nel box presente in queste pagine, useremo una libreria già esistente, la “PS3 Controller Host” (scaricabile anche dall’IDE di Arduino dalla finestra Library Manager) che permette di gestire gli eventi di pressione e di rilascio di ogni singolo tasto e levetta, ma anche di rilevare i valori dell’accelerometro interno e dei motorini per la vibrazione.
A noi, in questo caso, interessa qualche pulsante digitale per selezionare tre diverse modalità di regolazione (due per i colori, HSV e RGB, e una per il bianco) ed i controlli analogici per variare le singole componenti di colore e del bianco, approfittando anche del fatto che nella modalità di colore HSV (rappresentato proprio da un angolo giro) avere un controllo analogico a levetta come quello del Sixasix è l’ideale.
Il modello HSV viene infatti, rappresentato proprio da un angolo giro, o meglio da un cilindro; se ci spostiamo lungo il perimetro del bordo abbiamo il colore pieno in tutte le sue 360 tonalità di colore (Hue), se, a parità di tonalità, ci avviciniamo verso il centro abbiamo un valore diverso di saturazione (Saturation) e se poi ci muoviamo in altezza otterremo un diverso valore di intensità (Value).
Inoltre il cambiamento di tonalità (Hue) del colore appare più evidente ai nostri occhi rispetto alla variazione di una singola componente RGB.
Occorre però un calcolo aggiuntivo per questa rappresentazione, perché la libreria del controller ci fornisce le coordinate x e y della levetta analogica, ma a noi interessa l’angolo per calcolare il valore Hue e quanto siamo distanti dall’origine (levetta ferma) per calcolare il valore di saturazione (Saturation). Per quanto riguarda Value dovremmo usare un altro tasto o levetta analogica.
Per ricavare l’angolo useremo l’arcotangente in questo modo:

int deg = round( atan2 (-ly, lx) * 180 / 3.14159265 );

in cui lx ed ly sono i valori delle coordinati della levetta del controller forniti dalla libreria; sono di tipo byte e variano da -128 a 127, quindi nel caso in cui deg fosse negativo dovremo normalizzarlo eseguendo una somma di questo tipo:

deg = 360 + deg;

Per ricavare Saturation calcoleremo il modulo di questo vettore usando teorema di Pitagora, dopo aver normalizzato le coordinate della levetta ed infine moltiplicarlo per 100 dal momento che Saturation varia da 0 a 100.

float mod = sqrt(pow(lx/128.0, 2) + pow(ly/128.0, 2));
byte hsv_sat = mod * 100;

Infine per Value basterà convertire il valore ottenuto da un altro tasto analogico in modo che vari da 0 a 100, in questo modo:

byte hsv_val = 100 – (ry * 100/255);

in cui ry è il valore analogico fornito dalla libreria e e normalmente vale 100.
Abbiamo testato il funzionamento completo della lampadina associando i tasti come in Fig. 6, ma ovviamente è sempre possibile effettuare scelte diverse modificando le funzioni nel file principale, in particolare nella gestione degli eventi all’interno della funzione notify().

 

Fig. 6

A livello di funzionamento abbiamo scelto di rendere effettive le modifiche solo sulla pressione dei tasti SET in modo da non appesantire lo stack TCP inviando di continuo pacchetti sulla rete ad ogni rilevazione di un nuovo evento, mentre useremo stringhe di debug seriali per testare ogni cambiamento.
In particolare per quando riguarda la modalità del bianco, useremo il tasto Down per impostare il valore di luminosità al valore regolabile dal pulsante analogico L2, mentre per impostare il valore di temperatura agiremo sul pulsante analogico R2. Per la modalità HSV useremo solo il tasto Croce per settare il colore che varieremo con la levetta di sinistra (Hue e Saturation) ed il pulsante R1 (Value), mentre per quanto riguarda la modalità RGB useremo sempre lo stesso pulsante, ma questa volta le componenti varieranno dalla pressione dei pulsanti Left, Up e Right, sulla sinistra.
Presentiamo nel Listato 5 il loop dell’applicazione principale.

Listato 5


void loop()
{
if(!Ps3.isConnected())
{
return;
}
int c = Ps3.data.analog.button.cross;
if (c > 0)
{
if (_colorMode == COLOR_HSV)
{
Serial.println(“CH”);
wifiLamp.SetColor((short)hsv_hue, hsv_sat, hsv_val);
}
else if (_colorMode == WHITE)
{
Serial.println(“WT”);
wifiLamp.SetWhiteTemp(wTemp);
}
else if (_colorMode == COLOR_RGB)
{
Serial.println(“CR”);
Serial.println((int)_rgb);
if (_rgb == RED)
{
red = Ps3.data.analog.button.left;
}
else if (_rgb == GREEN)
{
green = Ps3.data.analog.button.up;
}
else if (_rgb == BLUE)
{
blue = Ps3.data.analog.button.right;
}
wifiLamp.SetColor((byte)red, green, blue);
}
else
{
}
}
int d = Ps3.data.analog.button.down;
if (d > 0)
{
Serial.println(“WL”);
wifiLamp.SetWhiteBright(wLight);

 

Conclusioni

Dal momento che il modulo ESP32 supporta anche Bluetooth 5.0, detto anche BLE da Low Energy, ed è lo stesso utilizzato dai vari dispositivi wearable che ci circondano, potremmo anche pensare di associare una fascia cardio al modulo e collegare anche la lampadina facendo in modo di farla accendere e spegnere al ritmo del nostro battito cardiaco, mentre eseguiamo esercizi di ginnastica o pedaliamo su una cyclette; ovviamente anche l’intensità del colore potrà cambiare a seconda dello sforzo eseguito.
Allo stesso modo tante nostre applicazioni potrebbero avere, è il caso di dirlo, una nuova luce ed avere anche un risvolto più domotico. L’utilizzo di questa nuova libreria potrà essere insomma molto vasto e limitato solo dalla nostra fantasia, a voi scoprirlo.

Download

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

Menu