2017/6/24

Control Arduino(ESP8266) with Alexa (Echo Dot) -- Smart Palette Implementation


This project demonstrates how to control your Arduino and ESP8266 module with Alexa (Echo Dot) voice command, which implies that you can control many internet-of Things including the palette light discussed below.

Again, since I am not familiar with how to implement the MQTT protocol with the AT command of ESP8266 module, I just try to go through the REST protocol to access ThingSpeak rather than AWS/IoT, as well as the communication between AWS/Lambda and ThingSpeak database. However, in this case we found another advantage of taking the MQTT protocol, maintaining the data integrity. We have to guarantee the data we write into ThingSpeak is what we want, especially for data transmission between ThingSpeak and AWS/Lambda, although I did not do that.




Please note that this article does not attempt to tell you how to implement the smart home things, but rather to share with you that we can extend our scope and connect more things that support the REST API, such as Wikipedia, Google map, Flickr, some government’s database and etc.



The Principle of Alexa Smart Palette
The concept of how to configure the palette light connected to our Arduino (ESP8266) is illustrated below. First, a ‘Green’ color value is uploaded to ThinhSpeak by making a HTTPS/GET request through a specific channel (called LED Channel), when we invoke a voice command, something like ‘Alexa, ask light config, set the light to green’ or ‘Alexa, ask light config, set red to 80% and blue to 80%’ and etc., where the ‘light config’ is our Alexa Skill APP. Second, our Arduino (ESP8266) query ThingSpeak database per about 15 seconds by making a HTTPS/GET request through the same channel, and then fetch the RGB color tag described in a JSON format once we receive the response from ESP-01. Finally, the palette light will be set based on the specified color tag when it’s different from the previous setting, which acts as a direct control to our Arduino (ESP-01) through voice command, as indicated by the orange arrows in the figure below.

Please refer to the previous project ‘Alexa Compliant Thermostat Implementation’ for more detail about the Arduino and ASK program and how the REST protocol works between our Arduino(ESP8266) and ThingSpeak IoT database.





Add a LED Channel
In this project, we registered one new TingSpeak channel for color configuration, as illustrated below. Here, we just need only one value (field1) to store the color tag in hexadecimal format, e.g., 0xRRGGBB.





ASK and AWS/Lambda Program Framework
For more detail about how to implement Alexa Skill setp by step, please refer to my previous project named ‘The first Alexa Skill – Hello World’. Assume our APP (Alexa Skill) name is ‘light config’.


ASK/Intent Schema
I expect that user may invoke/ask ‘light config’ for a specific true color light, enumerated in a custom defined variable type named COLOR_LIST, e.g., red, green, blue, yellow and purple. Besides, we reserve another invocation for users to adjust/mix RGB colors, by asking ‘light config’ with different percentage values set to red, green and blue, respectively.

{
  "intents": [
   {
      "intent": "TurnOnLightIntent"
    },
    {
      "intent": "TurnOffLightIntent"
    },
    {
      "slots": [
        {
          "name": "colorType",
          "type": "COLOR_LIST"
        }
      ],
      "intent": "SetLightToIntent"
    },
    {
      "slots": [
        {
          "name": "RedPercent",
          "type": "NUMBER"
        },
        {
          "name": "GreenPercent",
          "type": "NUMBER"
        },
        {
          "name": "BluePercent",
          "type": "NUMBER"
        }
      ],
      "intent": "LightConfigIntent"
    }
  ]
}


ASK/Sample Utterances
In order to improve the recognition of our new voice commands for Alexa, which can trigger our custom intents successfully, we enumerate some possible utterances.

TurnOnLightIntent turn on the light
TurnOffLightIntent turn off the light
SetLightToIntent set color to {colorType}
SetLightToIntent set the color to {colorType}
SetLightToIntent set light to {colorType}
SetLightToIntent set the light to {colorType}
LightConfigIntent set red to {RedPercent}
LightConfigIntent set green to {GreenPercent}
LightConfigIntent set blue to {BluePercent}
LightConfigIntent set red to {RedPercent} green to {GreenPercent} blue to {BluePercent}
LightConfigIntent set red to {RedPercent} blue to {BluePercent}
LightConfigIntent set green to {GreenPercent} blue to {BluePercent} red to {RedPercent}
LightConfigIntent set green to {GreenPercent} red to {BluePercent}
LightConfigIntent set blue to {BluePercent} red to {RedPercent} green to {GreenPercent}
LightConfigIntent set blue to {BluePercent} green to {GreenPercent}
LightConfigIntent set red to {RedPercent} percent
LightConfigIntent set green to {GreenPercent} percent
LightConfigIntent set blue to {BluePercent} percent
LightConfigIntent set red to {RedPercent} percent green to {GreenPercent} percent blue to {BluePercent} percent
LightConfigIntent set red to {RedPercent} percent blue to {BluePercent} percent
LightConfigIntent set green to {GreenPercent} percent blue to {BluePercent} percent red to {RedPercent} percent
LightConfigIntent set green to {GreenPercent} percent red to {BluePercent} percent
LightConfigIntent set blue to {BluePercent} percent red to {RedPercent} percent green to {GreenPercent} percent
LightConfigIntent set blue to {BluePercent} percent green to {GreenPercent} percent



AWS/Lambda Script
Please refer to my previous project about ‘To access ThingSpeak by making a HTTPS/GET request’, in which I utilize the package proposed by Kathryn Hodge. First, you can download the Archive.zip from her website and upload it to AWS/Lambda as a beginning. Second, you can replace the index.js with the JavaScript described below. Please fill in your Channel and APIKEY registered from ThingSpeak.

var request = require("request");

// Called when the user specifies an intent for this skill.
function onIntent(intentRequest, session, callback) {
    var intent = intentRequest.intent;
    var intentName = intentRequest.intent.name;

    // dispatch custom intents to handlers here
    if (intentName == "TurnOnLightIntent") {
        turnOnLightHandler(intent, session, callback);
    } else if (intentName == "TurnOffLightIntent") {
        turnOffLightHandler(intent, session, callback);
    } else if (intentName == "SetLightToIntent") {
        setLightToHandler(intent, session, callback);
    } else if (intentName == "LightConfigIntent") {
        lightConfigHandler(intent, session, callback);
    } else if (intentName === 'AMAZON.HelpIntent') {
        getWelcomeResponse(callback);
    } else if (intentName === 'AMAZON.StopIntent' || intentName === 'AMAZON.CancelIntent') {
        handleSessionEndRequest(callback);
    } else {
         throw "Invalid intent";
    }
}

// when APP is invoked without intent
function getWelcomeResponse(callback) {
    var speechOutput = "Welcome! I am ready to configure your light.";
    var reprompt = speechOutput;
    var header = "Light Configuration";
    var shouldEndSession = false;
    var sessionAttributes = {
        "speechOutput" : speechOutput,
        "repromptText" : reprompt
    };
    callback(sessionAttributes,
        buildSpeechletResponse(header, speechOutput, reprompt, shouldEndSession));
}

// turn on light with default white color
function turnOnLightHandler(intent, session, callback) {
    var speechOutput = "I'll turn on your light.";
    var rgbVal = 0x00ffffff; // hexadecimal format in 0x00RRGGBB
    var reprompt = "Set RGB: " + rgbVal.toString(16);
    var url = "https://api.thingspeak.com/update?api_key=APIKEY&field1=" + rgbVal.toString(16);
    request.get(url, function(error, response, body) {
        var ret = JSON.stringify(body); // the current entry Id
        reprompt = "The config colorCode = " + rgbVal.toString(16);
        callback(session.attributes,
            buildSpeechletResponseWithoutCard(speechOutput, reprompt, true));
    });
}

// turn off light
function turnOffLightHandler(intent, session, callback) {
    var speechOutput = "I'll turn off your light.";
    var rgbVal = 0x00000000; // hexadecimal format in 0x00RRGGBB
    var reprompt = "Set RGB: " + rgbVal.toString(16);
    var url = "https://api.thingspeak.com/update?api_key=APIKEY&field1=" + rgbVal.toString(16);
    request.get(url, function(error, response, body) {
        var ret = JSON.stringify(body); // the current entry Id
        reprompt = "The config colorCode = " + rgbVal.toString(16);
        callback(session.attributes,
            buildSpeechletResponseWithoutCard(speechOutput, reprompt, true));
    });
}

// set light to a specific color
function setLightToHandler(intent, session, callback) {
    var colorType = intent.slots.colorType.value;
    var speechOutput = "Set the light to " + colorType + " color.";
    var rgbVal = 0x00000000; // hexadecimal format in 0x00RRGGBB
    switch ( colorType.toLowerCase() ) {
        case 'red':
            rgbVal = 0x640000; // RGB(100,0,0)
            break;
        case 'green':
            rgbVal = 0x006400; // RGB(0,100,0)
            break;
        case 'blue':
            rgbVal = 0x000064; // RGB(0,0,100)
            break;
        case 'yellow':
            rgbVal = 0x646400; // RGB(100,100,0)
            break;
        case 'purple':
            rgbVal = 0x640064;  // RGB(100,0,100)
            break;
        default:
            rgbVal = 0x000000;  // turn-off tri-LED
            break;
    }
    var reprompt = "Set RGB: " + rgbVal.toString(16);
    var url = "https://api.thingspeak.com/update?api_key=APIKEY&field1=" + rgbVal.toString(16);
    request.get(url, function(error, response, body) {
        var ret = JSON.stringify(body); // the current entry Id
        reprompt = "The config colorCode = " + rgbVal.toString(16);
        callback(session.attributes,
            buildSpeechletResponseWithoutCard(speechOutput, reprompt, true));
    });
}

// config light with specifying the R/G/B percentage
function lightConfigHandler(intent, session, callback) {
    var speechOutput = "Configure light with the specifying RGB to ";
    var rPercent = 0; //intent.slots.RedPercent.value;
    var gPercent = 0; //intent.slots.GreenPercent.value;
    var bPercent = 0; //intent.slots.BluePercent.value;
    var rgbVal = 0x00000000; // hexadecimal format in 0x00RRGGBB
    if ( intent.slots.RedPercent.value ) {
        rPercent = intent.slots.RedPercent.value;
        rgbVal = rgbVal|((rPercent>100?100:rPercent)*255/100)<<16;
        speechOutput += "Red " + rPercent +"%.";
    }
    if ( intent.slots.GreenPercent.value ) {
        gPercent = intent.slots.GreenPercent.value;
        rgbVal = rgbVal|((gPercent>100?100:gPercent)*255/100)<<8;
        speechOutput += " and Green " + gPercent + "%.";
    }
    if ( intent.slots.BluePercent.value ) { 
        bPercent = intent.slots.BluePercent.value;
        rgbVal = rgbVal|(bPercent>100?100:bPercent)*255/100;
        speechOutput += " and Blue " + bPercent + "%." ;
    }
    var url = "https://api.thingspeak.com/update?api_key=APIKEY&field1=" + rgbVal.toString(16);
    request.get(url, function(error, response, body) {
        var ret = JSON.stringify(body); // the current entry Id
        reprompt = "The config colorCode = " + rgbVal.toString(16);
        callback(session.attributes,
            buildSpeechletResponseWithoutCard(speechOutput, reprompt, true));
    });
}

// Route the incoming request based on type (LaunchRequest, IntentRequest, etc.)
// The JSON body of the request is provided in the event parameter.
exports.handler = function (event, context) {
    try {
        console.log("event.session.application.applicationId=" + event.session.application.applicationId);
        if (event.session.new) {
            onSessionStarted({requestId: event.request.requestId}, event.session);
        }
        if (event.request.type === "LaunchRequest") {
            onLaunch(event.request,
                event.session,
                function callback(sessionAttributes, speechletResponse) {
                    context.succeed(buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === "IntentRequest") {
            onIntent(event.request,
                event.session,
                function callback(sessionAttributes, speechletResponse) {
                    context.succeed(buildResponse(sessionAttributes, speechletResponse));
                });
        } else if (event.request.type === "SessionEndedRequest") {
            onSessionEnded(event.request, event.session);
            context.succeed();
        }
    } catch (e) {
        context.fail("Exception: " + e);
    }
};

// Called when the session starts.
function onSessionStarted(sessionStartedRequest, session) {
    // add any session init logic here
}

// Called when the user invokes the skill without specifying what they want.
function onLaunch(launchRequest, session, callback) {
    console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
    getWelcomeResponse(callback);
}

// Called when the user ends the session.
// Is not called when the skill returns shouldEndSession=true.
function onSessionEnded(sessionEndedRequest, session) {
}

function handleSessionEndRequest(callback) {
    const cardTitle = 'Session Ended';
    const speechOutput = 'Light configuration done.';
    const shouldEndSession = true;

    callback({},
        buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}

// ------- Helper functions to build responses for Alexa -------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
    return {
        outputSpeech: {
            type: "PlainText",
            text: output
        },
        card: {
            type: "Simple",
            title: title,
            content: output
        },
        reprompt: {
            outputSpeech: {
                type: "PlainText",
                text: repromptText
            }
        },
        shouldEndSession: shouldEndSession
    };
}

function buildSpeechletResponseWithoutCard(output, repromptText, shouldEndSession) {
    return {
        outputSpeech: {
            type: "PlainText",
            text: output
        },
        reprompt: {
            outputSpeech: {
                type: "PlainText",
                text: repromptText
            }
        },
        shouldEndSession: shouldEndSession
    };
}

function buildResponse(sessionAttributes, speechletResponse) {
    return {
        version: "1.0",
        sessionAttributes: sessionAttributes,
        response: speechletResponse
    };
}

function capitalizeFirst(s) {
    return s.charAt(0).toUpperCase() + s.slice(1);
}






Arduino Board Connection
The wiring of our system board is based on the previous project about ‘Alexa Compliant Thermometer implementation with ESP8266’, and add one more thing -- the new palette LED. The R, G, B and GND petal on the LED module connect to Arduino pin 10, 9, 11 and GND respectively.





Tri-LED Test
Please test your RGB LED first. It’s true that the Red and Blue are misplaced in my LED module, so I switch then in my setTriLED procedure to keep the red, green and blue parameters in order.

int redPin = 10;   // R petal on RGB LED module
int greenPin = 9;  // G petal on RGB LED module
int bluePin = 11;  // B petal on RGB LED module

// the color generating function
void setTriLED(unsigned char red, unsigned char green, unsigned char blue) {
    analogWrite (redPin, blue); // yes, R and B are misplaced in my LED module
    analogWrite (greenPin, green);
    analogWrite (bluePin, red); // yes, R and B are misplaced in my LED module
}

void setup() {
}

void loop() {
    setTriLED(100, 0, 0); // turn the RGB LED red 
    delay(4000);  
    setTriLED(0,100, 0); // turn the RGB LED green 
    delay(4000);
    setTriLED(0, 0, 100); // turn the RGB LED blue 
    delay(4000);
    setTriLED(100,100,0); // turn the RGB LED yellow 
    delay(4000);
    setTriLED(128,0,200); // turn the RGB LED purple 
    delay(4000);

    color(0,0,0);  // turn OFF
    delay(5000);
}





Arduino Program
In this example, the LCD is used to display the individual RGB components set by the voice command. In the loop, we make a HTTPS/GET request through ThingSpeak LED channel to read the latest color content and display it via the LCD.

The color of the palette LED changes only when the latest color content is different from the previous setting, and it will be stored as the new color configuration. In this example, we send an intermittent request to ThingSpeak every 10~15 seconds.

#include <SoftwareSerial.h>
#include <LiquidCrystal_I2C.h>
#define DEBUG 0

SoftwareSerial ESP(3, 2); // ESP8266 ESP-01: Tx, Rx

// Addr, En, Rw, Rs, d4,d5,d6,d7 backlight, polarity
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // LCD1602 with I2C

// configuration for tri-LED
int redPin = 10;   // R petal on RGB LED module
int greenPin = 9;  // G petal on RGB LED module
int bluePin = 11;  // B petal on RGB LED module
char colorCode[8]="00000000"; // previous RGB color code

bool swState = 0;  // switch to enable/disable hunidity and temperature update
bool wifiState = 0;  // check wifi connection

const char* ThingSpeakIP = "184.106.153.149";
const char* ChannelID = ChannelID; // Tri-LED Channel
const char* ReadAPIKEY = APIKEY; // read APIKEY

void setup() {
    Serial.begin(115200);
    pinMode(7, INPUT_PULLUP);  // set pin7 as the interrupt button

    // initialize LCD
    lcd.begin(16, 2); // init LCD : 2 rows x 16 columns
    lcd.backlight();
    lcd.clear();
    lcd.print("Hello World!");
    delay(2000);
    lcd.noBacklight(); // turn off back light and wait for user input

    // initialize ESP-01
    ESP.begin(115200);
    delay(5000);
    Serial.println("ESP-01 start ...");
    // test the correctness of RGB color
    testTriLED();

  #if !DEBUG
    Serial.println("try to connect ESP-01 to WAN (through home AP) ...");
    if ( connectWAN("MOD7A", "wavelet123") ) { // join ESP-01 to WAN through your home AP
        wifiState = 1;
        Serial.println("press switch button to start ...");
    } else {
        Serial.println("fail to join WAN ...");
    }
  #endif
}

void testTriLED() {
    setTirLED(100, 0, 0); // turn the RGB LED red 
    delay(1000); 
    setTirLED(0,100, 0); // turn the RGB LED green 
    delay(1000);
    setTirLED(0, 0, 100); // turn the RGB LED blue 
    delay(1000);  
    setTirLED(100,100,0); // turn the RGB LED yellow 
    delay(1000); 
    setTirLED(128,0,200); // turn the RGB LED purple 
    delay(1000);
    setTirLED(0,0,0); // turn off LED
}

void setTirLED (unsigned char red, unsigned char green, unsigned char blue) {
    analogWrite (redPin, blue); // yes, R and B are misplaced in my LED module
    analogWrite (greenPin, green);
    analogWrite (bluePin, red);
}

void setRGBColor(char* rgbCode) {
    repairColorCode(rgbCode);
    if ( strcmp(rgbCode,colorCode)==0 ) {
        //Serial.println("<<no change>>");
        return;
    }
    strcpy(colorCode,rgbCode);

    unsigned long val = (unsigned long)strtoul(rgbCode,NULL,16)&0x00ffffff;
    unsigned char r = (unsigned char)(val>>16);
    unsigned char g = (unsigned char)(val>>8);
    unsigned char b = (unsigned char)(val);
   
    setTirLED(
        (unsigned char)r,
        (unsigned char)g,
        (unsigned char)b
    );
    // display RGB color factor
    lcd.backlight();
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print("R:" + String(r)); // Red
    lcd.setCursor(8, 0); lcd.print("G:" + String(g)); // Green
    lcd.setCursor(0, 1); lcd.print("B:" + String(b)); // Blue
    setDelay(5000); // display 5 seconds
    lcd.noBacklight(); // turn off LCD
}

void repairColorCode(char*& code) {
    if ( code!=NULL ) {
        int len = strlen(code);
        for ( int ii=0; ii<len; ii++ ) {
            if( code[ii]>'f' )
                code[ii] = 'f';
        }
    }
}

bool connectWAN(char* ssidName, char* passWord) {
    String joinCmd = "AT+CWJAP=\"" + String(ssidName) + "\",\"" + String(passWord) + "\"\r\n";
    String recMsg;
    int status = false;

    sendATcmd("AT+RST\r\n",2000,recMsg); // reset ESP-01
    sendATcmd("AT+CWMODE=1\r\n",1000,recMsg); // config ESP-01 as STA mode
    sendATcmd(joinCmd,1000,recMsg); // wait the server to ack "OK"
   
    for (int loop=0; loop<10; loop++ ) { // wait AP's response
        Serial.print(".");
        if ( ESP.find("OK") ) {
            status = true;
            Serial.println("join ESP-01 to WAN success ...");
            break;
        }
        setDelay(1000);
    }
    //Serial.println(recMsg);
    return status;
}

void setDelay(unsigned int ms) {
    unsigned long timeout = ms + millis();
    while ( millis()<timeout ) {
        checkSwitchButton();
    }
}

bool checkSwitchButton() {
    if ( digitalRead(7)==LOW ) {
        swState = !swState;
        Serial.println("\nInterrupt: Switch Button = " + String(swState) );
        delay(300); // debounce delay (depending on your behavior)
    }
    return swState;
}

// +IPD,68:{"crdated_at":"2017-06-21T08;55:01Z","entry_id":6,"field1":"ffff00"}CLOSED
char* jsonGetColorCode(String& inMsg, char (&code)[8]) {
    // repair ESP-01 one bit received char error
    inMsg.replace(';',':');  // repair ESP-01 error buffer, e.g., field1":"ffff00" --> field1";"ffff00"
    inMsg.replace('#','\"'); // repair ESP-01 error buffer, e.g., field1":#ffff00" --> field1";"ffff00"
   
    char* msg = inMsg.c_str();
    char* p1;
    char* p2;

    if ( (p1=strstr(msg,"+IP"))==NULL || (p2=strstr(msg,"CLOS"))==NULL ) {
        Serial.println("Error! fail to fetch JSON data.");
        return NULL;
    }
    if ( (p1=strchr(msg,'{'))==NULL ) {
        Serial.println("Error! fail to fetch JSON data.");
        return NULL;
    }
    msg = p1;
    *(p2) = '\0'; // end JSON

    // fetch color value in JSON, e.g., ... "field1":"ffff00"}
    if ( (p1=strrchr(msg,':'))!=NULL ) {
        msg = p1+2;  // trim the 1st bracket
        *(p2-2) = '\0'; // trim the 2nd bracket
        if ( strlen(msg)>8 ) // invalidated color code
            strcpy(code,"00000000");    
        else
            strcpy(code,msg);  
    }
    return code;
}

void sendATcmd(String cmd, unsigned int msDelay, String& outEspMsg) {
    unsigned long timeout = millis() + msDelay;
   
    Serial.print(">>ESP-01: " + String(cmd)); // debug
    ESP.print(cmd); // send AT command to ESP-01
 
    // allow delay > msDelay if ESP-01 still receives message
    outEspMsg = "";
    while ( ESP.available() || millis()<timeout ) {
        while ( ESP.available() ) {
            char ch = ESP.read();
            outEspMsg += ch;
        }
    }
}

// query data from ThingSpeak
void queryThingSpeak(const char* ipName, const char* channelName, const char* apiKey, String& outEspMsg) {
    String tcpCmd = "AT+CIPSTART=\"TCP\",\"" + String(ipName) + "\",80\r\n";
    String getCmd = "GET /channels/" + String(channelName) +
                    "/feeds/last.json?key=" + String(apiKey) + "\r\n";
    String cipCmd = "AT+CIPSEND=" + String(getCmd.length()) + "\r\n";

    sendATcmd(tcpCmd,1000,outEspMsg);  // ask HTTP connection, and wait the server to ack "OK"
    sendATcmd(cipCmd,2000,outEspMsg);  // start CIPSEND request, and wait the server to ack "OK"
    sendATcmd(getCmd,3000,outEspMsg);  // send GET request and receive response
    Serial.println(outEspMsg);
}

void loop() {
  #if DEBUG // ESP-01 debug by using AT command through serial console
    if ( Serial.available() ) {
        ESP.write( Serial.read() );
    }
    if ( ESP.available() ) {
        Serial.write( ESP.read() );
    }
  #else
    if ( swState && wifiState && dataCnt < 100 ) {
        // query the triLED color from ThingSpeak
        Serial.println("query the triLED color from ThingSpeak ...");
        String recMsg;
        queryThingSpeak(
            ThingSpeakIP, // ThingSpeak IP
            ChannelID,    // channelId (Alexa channel)
            ReadAPIKEY,  // read API-KEY (Alexa channel)
            recMsg       // received message from ThingSpeak
        );
        char code[8]="00000000";
        if ( jsonGetColorCode(recMsg,code)!=NULL ) {
            setRGBColor(code);
        }
        Serial.println("press switch to stop (will start after one minute) ...");
        if ( swState )
            setDelay(5000); // update humidity and temperature 5 second
    }
  #endif
    setDelay(500); // monitor hardware interrupt
}








Test Steps:

(Note: ‘light config’ is the APP name of my Alexa Skill, and first of all, we configure our palette light with a specific true color.)

Author: Alexa, ask light config, set the light to blue.

Author: Alexa, ask light config, set the light to yellow.

Author: Alexa, ask light config, set the light to red.
(Note: In this moment, AWS/Lambda fail to write ThingSpeak, while our ESP module is trying to read color from ThingSpeak at the same time. So, it would be better to check the write data correctness again by querying ThingSpeak through the same channel.)


(Note: Second, we test the palette light by specifying individual RGB color component.)

Author: Alexa, ask light config.

Author: Set red to 80% and green to 80%.
(Note: It’s correct yellow color.)

Author: Set red to 20% and green to 50%.

Author: Set blue to 100%. 



Issue Discussion
We have found that the write data access of AWS/Lambda to ThingSpeak may fail when ESP module is also trying to perform read access to ThingSpeak through the same channel. It would be better to make a confirmation for each data sent to IoT database, for example, to query, compare and re-write the date if it is inconsistent to ensure the data correctness.

An improved approach for data confirmation between AWS and ThingSpeak through the LED channel is illustrated below. The data request from ESP-01 to ThingSpeak is intermittent, and we also allow non-immediate data updates, so the problem is not great.




Another potential problem is respond quality of the IOT database to the client service, and the client service may also be affected by the network bandwidth and the number of client requests. We sometimes encounter that the IoT database is too slow to response our hardware's request, which may lead to a data fetch error.

It's not difficult to customize your own IoT database to improve the transmission quality and performance, since we connect Things though the REST protocol. Of course, we can implement our own IoT database within the AWS/Lambda environment as well, such that we can guarantee the data consistency coming from AWS/Lambda. On the other hand, once the data is updated, the custom IoT database can intermittently notify our system, which can be configured to listen on TCP port 1883 without going out and making TCP requests.





Interestingly, when we continue to clarify all the problems and try to fix some of them, the data transfer/operation of entire system seems to be more and more like the MQTT protocol. It may be said that the emergence of MQTT is like an inevitable result evolved gradually in order to continue to solve the problem. In other words, you should invent it as well even if IBM engineers do not invent it.