ESP8266 WeMos Alexa sketch

ESP8266 WeMos Alexa sketch ti mostra come realizzare la parte di algoritmo che consente ad Alexa di controllare il tuo albero di natale.

ESP8266 WeMos Alexa sketch

Ovviamente puoi utilizzare questa tipologia di connessione ed Skill Alexa per connettere altri progetti.

Trattandosi di una serie di articoli che ho scritto nel periodo natalizio mi è sembrato utile rimanere in tema e applicare ad un albero di natale il codice che leggerai.

Tipologia di led WS2812B

In questo progetto ESP8266 WeMos Alexa sketch avrai bisogno di utilizzare dei led che fungeranno da attuatori nel progetto.

Dovendo applicare il controllo da parte di Alexa al mio albero di natale, realizzato per lo scorso natale:

albero di natale spirale neopixel high

la tipologia di led che vedrai utilizzata è il classico neopixel ws2812b che useremo con la libreria FastLed.

passiamo alla realizzazione dello sketch partendo dall’esempio rilasciato su github dagli autori della skin  sinric

ESP8266 WeMos Alexa sketch

Il primo passo è selezionare dal sito ufficiale il link che ti porta al github ufficiale della Skill Sinric ( github sinric ) contenente alcuni utili esempi:

Alexa skin Sinric github

tra gli esempi arduino seleziona il primo dedicato al controllo di luci: light_example.ino che usereai come base del progetto:

Alexa skin Sinric github examples

Prima di vedere le parti dello sketch che dovrai personalizzare è importante che tu scarichi e installi la libreria arduinoWebSockets io ho utilizzato la versione 2.3.1 ( l’ultima disponibile oggi ).

Lo sketch modificato per il progetto 

L’esempio base fornito ha bisogno di alcune modifiche ed integrazioni per essere adattato al tuo progetto  ESP8266 WeMos Alexa sketch.

Ecco lo sketch finale in cui avrai integrato sia la parte di sincronia con la Skill Alexa sia la parte di controllo dei led ws2812b neopixel:

/*
 Version 0.1 - Feb 10 2018
*/ 

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <WebSocketsClient.h> //  https://github.com/kakopappa/sinric/wiki/How-to-add-dependency-libraries 
#include <ArduinoJson.h> // https://github.com/kakopappa/sinric/wiki/How-to-add-dependency-libraries
#include <FastLED.h>

#define NUM_LEDS 40
#define DATA_PIN 2
CRGB leds[NUM_LEDS];

ESP8266WiFiMulti WiFiMulti;
WebSocketsClient webSocket;

WiFiClient client;
int bri_val = 50;
int rgb[3]; 

#define MyApiKey "your API key" // TODO: Change to your sinric API Key. Your API Key is displayed on sinric.com dashboard
#define MySSID "your WiFi SSID" // TODO: Change to your Wifi network SSID
#define MyWifiPassword "your WiFi Password" // TODO: Change to your Wifi network password

#define API_ENDPOINT "http://sinric.com"
#define HEARTBEAT_INTERVAL 300000 // 5 Minutes 

uint64_t heartbeatTimestamp = 0;
bool isConnected = false;

void turnOn(String deviceId) {
  if (deviceId == "your API device Id") // Device ID of first device
  {  
    Serial.print("Turn on device id: ");
    Serial.println(deviceId); 
    Serial.print("R val: "); Serial.println(rgb[0]); 
    Serial.print("G val: "); Serial.println(rgb[1]); 
    Serial.print("B val: "); Serial.println(rgb[2]);
    
    for(int i=0;i<NUM_LEDS;i++){
      leds[i] = CRGB(rgb[0], rgb[1], rgb[2]);
      FastLED.show();   
      delay(5);
    }
  } 
  else {
    Serial.print("Turn on for unknown device id: ");
    Serial.println(deviceId);    
  }     
}

void turnOff(String deviceId) {
   if (deviceId == "your API device Id") // Device ID of first device
   {  
     Serial.print("Turn off Device ID: ");
     Serial.println(deviceId); 
     
     for(int i=0;i<NUM_LEDS;i++){
      leds[i] = CRGB(0,0,0);
      FastLED.show();
      delay(10);
    }  
   }
   else {
     Serial.print("Turn off for unknown device id: ");
     Serial.println(deviceId);    
   }
}

void updateDevice(String deviceId) {
  turnOff( deviceId );
  turnOn( deviceId );
}

void playLight(String deviceId) {
  turnOff(deviceId);
  
  
  for (byte i=0; i<20; i++) {
    
    byte n=random(1,5);
    for (byte j=n; j<NUM_LEDS; j=(j+5)) { leds[j] = CRGB(128,128,128); }
    FastLED.show();
    delay(200);
    for (byte j=n; j<NUM_LEDS; j=(j+5)) { leds[j] = CRGB(0,0,0); }
    FastLED.show();
    delay(300);
  }
  updateDevice(deviceId);
}

void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
  switch(type) {
    case WStype_DISCONNECTED:
      isConnected = false;    
      Serial.printf("[WSc] Webservice disconnected from sinric.com!\n");
      break;
    case WStype_CONNECTED: {
      isConnected = true;
      Serial.printf("[WSc] Service connected to sinric.com at url: %s\n", payload);
      Serial.printf("Waiting for commands from sinric.com ...\n");        
      }
      break;
    case WStype_TEXT: {
        Serial.printf("[WSc] get text: %s\n", payload);
        // Example payloads

        // For Light device type
        // {"deviceId": xxxx, "action": "setPowerState", value: "ON"} // https://developer.amazon.com/docs/device-apis/alexa-powercontroller.html
        // {"deviceId": xxxx, "action": "AdjustBrightness", value: 3} // https://developer.amazon.com/docs/device-apis/alexa-brightnesscontroller.html
        // {"deviceId": xxxx, "action": "setBrightness", value: 42} // https://developer.amazon.com/docs/device-apis/alexa-brightnesscontroller.html
        // {"deviceId": xxxx, "action": "SetColor", value: {"hue": 350.5,  "saturation": 0.7138, "brightness": 0.6501}} // https://developer.amazon.com/docs/device-apis/alexa-colorcontroller.html
        // {"deviceId": xxxx, "action": "DecreaseColorTemperature"} // https://developer.amazon.com/docs/device-apis/alexa-colortemperaturecontroller.html
        // {"deviceId": xxxx, "action": "IncreaseColorTemperature"} // https://developer.amazon.com/docs/device-apis/alexa-colortemperaturecontroller.html
        // {"deviceId": xxxx, "action": "SetColorTemperature", value: 2200} // https://developer.amazon.com/docs/device-apis/alexa-colortemperaturecontroller.html
        
#if ARDUINOJSON_VERSION_MAJOR == 5
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject((char*)payload);
#endif
#if ARDUINOJSON_VERSION_MAJOR == 6        
        DynamicJsonDocument json(1024);
        deserializeJson(json, (char*) payload);      
#endif        
        String deviceId = json ["deviceId"];     
        String action = json ["action"];
        
        if(action == "setPowerState") { // Switch or Light
            String value = json ["value"];
            if(value == "ON") {
                turnOn(deviceId);
            } else {
                turnOff(deviceId);
            }
        }
        else if(action == "SetColor") {
            // Alexa, set the device name to red        :: get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":0,"saturation":1,"brightness":1}}
            // Alexa, set the device name to green      ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":120,"saturation":1,"brightness":1}}
            // Alexa, set the device name to blue       ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":240,"saturation":1,"brightness":1}}
            // Alexa, set the device name to yellow     ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":60,"saturation":1,"brightness":1}}
            // Alexa, set the device name to orange     ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":39,"saturation":1,"brightness":1}}
            // Alexa, set the device name to light blue ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":180,"saturation":1,"brightness":1}}
            // Alexa, set the device name to black      ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":0,"saturation":0,"brightness":0}}
            String hue = json ["value"]["hue"];
            String saturation = json ["value"]["saturation"];
            String brightness = json ["value"]["brightness"];

            if ( hue.toInt() == 0   && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=255; rgb[1]=0; rgb[2]=0; } // red
            if ( hue.toInt() == 120 && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=0; rgb[1]=255; rgb[2]=0; } // green
            if ( hue.toInt() == 240 && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=0; rgb[1]=0; rgb[2]=255; } // blue
            if ( hue.toInt() == 60  && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=255; rgb[1]=255; rgb[2]=0; } // yellow
            if ( hue.toInt() == 39  && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=255; rgb[1]=140; rgb[2]=0; } // orange
            if ( hue.toInt() == 180 && saturation.toInt() == 0 && brightness.toInt() == 1 ) { rgb[0]=0; rgb[1]=255; rgb[2]=255; } // light blue

            Serial.print("[WSc] hue int: "); Serial.println(hue.toInt());
            Serial.print("[WSc] saturation: "); Serial.println(saturation.toInt());
            Serial.print("[WSc] brightness: "); Serial.println(brightness.toInt());
            
            Serial.println("[WSc] hue: " + hue);
            Serial.println("[WSc] saturation: " + saturation);
            Serial.println("[WSc] brightness: " + brightness);

            if ( hue.toInt() == 0 && saturation.toInt() == 0 && brightness.toInt() == 0 ) { playLight(deviceId); } else { updateDevice(deviceId); }
        }
        else if(action == "SetBrightness") {
            String brightness = json ["value"];
            bri_val = brightness.toInt();
            FastLED.setBrightness( map(bri_val,0,100,0,255) );
            
            Serial.println("[WSc] brightness: " + brightness);
            updateDevice(deviceId);            
        }
        else if(action == "AdjustBrightness") {
            String brightness = json ["value"];
            bri_val += brightness.toInt();
            FastLED.setBrightness( map(bri_val,0,100,0,255) );
            
            Serial.println("[WSc] brightness: " + brightness);
            updateDevice(deviceId);            
        }
        else if (action == "SetColorTemperature") {
            String temperature = json ["value"];
            Serial.println("[WSc] color temperature: " + temperature);
        }
        else if (action == "test") {
            Serial.println("[WSc] received test command from sinric.com");
        }
      }
      break;
    case WStype_BIN:
      Serial.printf("[WSc] get binary length: %u\n", length);
      break;
    default: break;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode ( LED_BUILTIN, OUTPUT );
  WiFiMulti.addAP(MySSID, MyWifiPassword);
  Serial.println();
  Serial.print("Connecting to Wifi: ");
  Serial.println(MySSID);  

  // Waiting for Wifi connect
  while(WiFiMulti.run() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  if(WiFiMulti.run() == WL_CONNECTED) {
    Serial.println("");
    Serial.print("WiFi connected. ");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
    
    for(int i=0;i<NUM_LEDS;i++){
      leds[i] = CRGB(128,128,128);
      FastLED.show();
      delay(100);
    }
    delay(500);
    
    for(int i=0;i<NUM_LEDS;i++){
      leds[i] = CHSV(0,0,0);
      FastLED.show(); 
      delay(100);
    }
  }

  // server address, port and URL
  webSocket.begin("iot.sinric.com", 80, "/");

  // event handler
  webSocket.onEvent(webSocketEvent);
  webSocket.setAuthorization("apikey", MyApiKey);
  
  // try again every 5000ms if connection has failed
  webSocket.setReconnectInterval(5000);   // If you see 'class WebSocketsClient' has no member named 'setReconnectInterval' error update arduinoWebSockets
}

void loop() {
  webSocket.loop();
  
  if(isConnected) {
      uint64_t now = millis();
      
      // Send heartbeat in order to avoid disconnections during ISP resetting IPs over night. Thanks @MacSass
      if((now - heartbeatTimestamp) > HEARTBEAT_INTERVAL) {
          heartbeatTimestamp = now;
          webSocket.sendTXT("H");          
      }
  }   
}

Descrizione dello sketch

Analizzerai solo le parti da modificare:

#include <FastLED.h>

#define NUM_LEDS 40
#define DATA_PIN 2
CRGB leds[NUM_LEDS];

alla linea 10 includi la libreria FastLed.h per il controllo dei led;

le linee 12-14 servono a definire le tre variabili principali in cui sono indicati:

  • il numero dei led da controllare;
  • il pin a cui ha connesso i led;
  • l’array leds RGB delle dimensioni del numero di led;

la parte di codice seguente da modificare è quella relativa alle chiavi API e connessione WiFI:

int bri_val = 50;
int rgb[3]; 

#define MyApiKey "your API key" // TODO: Change to your sinric API Key. Your API Key is displayed on sinric.com dashboard
#define MySSID "your WiFi SSID" // TODO: Change to your Wifi network SSID
#define MyWifiPassword "your WiFi Password" // TODO: Change to your Wifi network password

iniziando dalla linea 20 ti serviranno due variabili:

  • bri_val: per memorizzare il livello di luminosità; 
  • rgb[3]: un array di tre elementi in cui memorizzerai il colore da impartire ai led;

le linee 23-25 servono a impostare l’API rilasciata durante la registrazione sul sito sinric.com

Alexa skin Sinric API Key

e le credenziali per accedere alla tua rete WiFI.

alla linea 35 dovrai inserire il codice del tuo device, anche questo è stato rilasciato dal sito sinric.com quando hai creato il device albero:

if (deviceId == "your API device Id ") // Device ID of first device

Alexa skin Sinric Add new device created

passiamo alle modifiche delle funzioni necessarie per il controllo dei led:

void turnOn(String deviceId) {
  if (deviceId == "your API device Id") // Device ID of first device
  {  
    Serial.print("Turn on device id: ");
    Serial.println(deviceId); 
    Serial.print("R val: "); Serial.println(rgb[0]); 
    Serial.print("G val: "); Serial.println(rgb[1]); 
    Serial.print("B val: "); Serial.println(rgb[2]);
    
    for(int i=0;i<NUM_LEDS;i++){
      leds[i] = CRGB(rgb[0], rgb[1], rgb[2]);
      FastLED.show();   
      delay(5);
    }
  } 
  else {
    Serial.print("Turn on for unknown device id: ");
    Serial.println(deviceId);    
  }     
}

la funzione turnOn() è già presente nel codice di esempio sinric e ti serve per accendere le luci, in questo progetto i led we2812b.

la funzione alle linee 36-40 usa la comunicazione Seriale per informarti sui valori di:

  • deviceId
  • rgb[0]
  • rgb[1]
  • rgb[2]

alle linee 42-46 inizi un ciclo che imposta il colore definito dalle tre variabili rgb[] su ciascuno dei led presenti nell’albero.

le parti relative alle linee 36-46 sono quelle personalizzate.

La funzione turnOff() è simile alla precedente funzione a parte la mancanza delle parti di comunicazione seriale dei tre valori RGB e la linea 61:

void turnOff(String deviceId) {
   if (deviceId == "your API device Id") // Device ID of first device
   {  
     Serial.print("Turn off Device ID: ");
     Serial.println(deviceId); 
     
     for(int i=0;i<NUM_LEDS;i++){
      leds[i] = CRGB(0,0,0);
      FastLED.show();
      delay(10);
    }  
   }
   else {
     Serial.print("Turn off for unknown device id: ");
     Serial.println(deviceId);    
   }
}

tale linea serve a impostare la terzina (0,0,0) su ciascun led dell’albero, questo equivale al totale spegnimento dei led.

Funzioni aggiuntive dello sketch

per migliorare l’eperienza di utilizzo del progetto ESP8266 WeMos Alexa sketch ho deciso di aggingere due nuove funzioni:

void updateDevice(String deviceId) {
  turnOff( deviceId );
  turnOn( deviceId );
}

la funzione updateDevice() si occupa di richiamare in sequenza

  • turnOff()
  • turnOn()

in modo che siano aggiornate le informazioni di colore sui led ad ogni interazione;

la funzione playLight() è un semplice gioco di luce che applicheremo ai led quando imposterai il colore nero, ovviamente non è possibile realizzare tale colore sull’albero mediante i led ws2812b.

A tale scopo ho definito questa funzione per avere un gioco di luci sull’albero:

void playLight(String deviceId) {
  turnOff(deviceId);
  
  
  for (byte i=0; i<20; i++) {
    
    byte n=random(1,5);
    for (byte j=n; j<NUM_LEDS; j=(j+5)) { leds[j] = CRGB(128,128,128); }
    FastLED.show();
    delay(200);
    for (byte j=n; j<NUM_LEDS; j=(j+5)) { leds[j] = CRGB(0,0,0); }
    FastLED.show();
    delay(300);
  }
  updateDevice(deviceId);
}

la linea 78 richiama la funzione turnOff() per spegnere tutti i led;

la linea 81 imposta un ciclo di 20 interazioni in cui eseguirà il gioco di luce definito;

linea 83: sfrutta la funzione random per selezionare un numero da 1 a 5 casualmente e utilizzarlo alla linea 83 che parte da tale numero incrementando di 5 in 5 fino al numero massimo di led presenti nell’albero.

per ciascun led, estratto a caso dalla funzione random, imposta il colore 128,128,128 ossia un grigio;

alla linea 87 un ciclo for similare spegne gli stessi led;

questa funzione realizza un classico esempio di gioco di luce accendendo e spegnendo casualmente i led dell’albero di natale.

l’ultima linea della playLight() richiama la funzione updateDevice() che ripristina il funzionamento dell’albero accendendolo dell’ultimo colore impostato;

Selezione del colore ESP8266 WeMos Alexa sketch

la selezione del colore avviene nella funzione webSocketEvent e precisamente a partire dalla linea 138:

        else if(action == "SetColor") {
            // Alexa, set the device name to red        :: get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":0,"saturation":1,"brightness":1}}
            // Alexa, set the device name to green      ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":120,"saturation":1,"brightness":1}}
            // Alexa, set the device name to blue       ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":240,"saturation":1,"brightness":1}}
            // Alexa, set the device name to yellow     ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":60,"saturation":1,"brightness":1}}
            // Alexa, set the device name to orange     ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":39,"saturation":1,"brightness":1}}
            // Alexa, set the device name to light blue ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":180,"saturation":1,"brightness":1}}
            // Alexa, set the device name to black      ::get text: {"deviceId":"xxxx","action":"SetColor","value":{"hue":0,"saturation":0,"brightness":0}}
            String hue = json ["value"]["hue"];
            String saturation = json ["value"]["saturation"];
            String brightness = json ["value"]["brightness"];
 
            if ( hue.toInt() == 0   && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=255; rgb[1]=0; rgb[2]=0; } // red
            if ( hue.toInt() == 120 && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=0; rgb[1]=255; rgb[2]=0; } // green
            if ( hue.toInt() == 240 && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=0; rgb[1]=0; rgb[2]=255; } // blue
            if ( hue.toInt() == 60  && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=255; rgb[1]=255; rgb[2]=0; } // yellow
            if ( hue.toInt() == 39  && saturation.toInt() == 1 && brightness.toInt() == 1 ) { rgb[0]=255; rgb[1]=140; rgb[2]=0; } // orange
            if ( hue.toInt() == 180 && saturation.toInt() == 0 && brightness.toInt() == 1 ) { rgb[0]=0; rgb[1]=255; rgb[2]=255; } // light blue
 
            Serial.print("[WSc] hue int: "); Serial.println(hue.toInt());
            Serial.print("[WSc] saturation: "); Serial.println(saturation.toInt());
            Serial.print("[WSc] brightness: "); Serial.println(brightness.toInt());
             
            Serial.println("[WSc] hue: " + hue);
            Serial.println("[WSc] saturation: " + saturation);
            Serial.println("[WSc] brightness: " + brightness);
 
            if ( hue.toInt() == 0 && saturation.toInt() == 0 && brightness.toInt() == 0 ) { playLight(deviceId); } else { updateDevice(deviceId); }

in cui lo sketch arriva quando pronunci la frase: “Alexa, albero colore …..” dove al posto dei puntini puoi inserire uno dei colori che ho catturato attraverso il monitor seriale ed impartendo comandi colore.

Le linee 139-145 ti mostrano i messaggi, in formato json, con cui la skill sinric ti passa i valori di colore dopo che avrai impartito il comando vocale ad alexa;

per ciascun colore testato ho raccolto i valori per:

  • hue
  • saturation
  • brightness

e grazie ad uno straordinario tool on-line ( colorizer.org ) li ho convertiti in valori RGB che trovi esplicitati alle linee 150-155 a parte l’ultimo colore: il nero;

per il colore nero ho deciso, come descritto in precedenza, di collegare la funzione playLight() in modo da realizzare un bel gioco di luce sull’albero.

if ( hue.toInt() == 0 && saturation.toInt() == 0 && brightness.toInt() == 0 ) { playLight(deviceId); } else { updateDevice(deviceId); }

la linea 165 richiama la funzione playLight() quando i valori di hue, saturation e brightness sono tutti a 0 ( colore nero ) in caso contrario richiama la funzione updateDevice() per aggiornare i valori RGB sui ws2812b dell’albero.

Il video demo

  • Questo sito ed i suoi contenuti è fornito "così com'è" e Mauro Alfieri non rilascia alcuna dichiarazione o garanzia di alcun tipo, esplicita o implicita, riguardo alla completezza, accuratezza, affidabilità, idoneità o disponibilità del sito o delle informazioni, prodotti, servizi o grafiche correlate contenute sul sito per qualsiasi scopo.
  • Ti chiedo di leggere e rispettare il regolamento del sito prima di utilizzarlo
  • Ti chiedo di leggere i Termini e Condizioni d'uso del sito prima di utilizzarlo
  • In qualità di Affiliato Amazon io ricevo un guadagno dagli acquisti idonei qualora siano presenti link al suddetto sito.

Permalink link a questo articolo: https://www.mauroalfieri.it/make/esp8266-wemos-alexa-sketch.html

2 commenti

    • rudy il 11 Gennaio 2021 alle 02:43
    • Rispondi

    Ciao ho provato per tutto il giorno a capirci qualcosa ma purtroppo sono ignorante sul linguaggio di arduino, https://www.mauroalfieri.it/make/esp8266-wemos-alexa-sketch.html
    in questo progetto tramite alexa il comando di spegnimento e di accensione non funziona , cosi come le varie temperature del bianco. Mi piacerebbe riuscire a risolvere questo problema, ti ringrazio anticipatamente per la risposta , naturalmente se cè bisogno di qualsiasi cosa per aiutarmi dimmi pure.

    1. Ciao Ruby,
      hai ragione in merito alle temperature colore: non ho inserito ancora la parte di gestione, lo farò certamente in un prossimo progetto.
      L’accensione e lo spegnimento funzionano, almeno nel mio esempio e sono regolate dalle funzioni: turnOn e turnOff dello sketch.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.