Alexa Controlled Door Sign Demo

Use Alexa to tell your door sign what message to display.

Alexa Controlled Door Sign Demo

Things used in this project

Hardware components

Arduino MKR1000
Arduino MKR1000
× 1
Waveshare 200×200, 1.54inch E-Ink display module
× 1
18650-Type Lithium Ion Battery (generic)
× 1
18650-Type Battery Holder (generic)
× 1
Breadboard (generic)
Breadboard (generic)
× 1

Software apps and online services

Arduino IDE
Arduino IDE
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
AWS Lambda
Amazon Web Services AWS Lambda
Thinger.io Platform
Thinger.io Platform

Story

To connect the display module to the MKR1000 and run the vendor’s example program on it, you can basically follow the instructions for “Working with Arduino” from the vendors documentation and consider some differences regarding the pin layout between the MKR1000 and the UNO:

The SPI pins on the MKR1000 are D8 (MOSI)D9 (SCK), and D10 (MISO) – the SPI pins on the UNO are D11 (MOSI), D12 (MISO) and D13 (SCK).

Following the instructions from the Waveshare Wiki Page the two SPI signals SCK and MOSI must be remapped to D9 and D8 for the MKR1000. The remaining signals like CS, DC, RST and BUSY can be freely mapped to the other digital signals of the MKR1000.

This is the extend mapping table for wiring the module with the MKR1000:

The resulting wiring looks like this:

And the real prototype setup looks like this:

To run the vendor provided example and your own sketches, some minor changes to the provided library code are necessary:

  • Copy the files from thedirectory arduino/libraries of the demo package to documents/arduino/libraries, the actual path can be determined by Arduino IDE –> File –> Preferences –>Sketchbook location.
  • In the file libraries/ep1in54/epdif.h make the following changes to reflect the modified pin mapping for the MKR1000:
#ifndef EPDIF_H
#define EPDIF_H
#include <arduino.h>
/* COMMENT OR REMOVE THIS SECTION: 
// Pin definition
#define RST_PIN 8
#define DC_PIN 9
#define CS_PIN 10
#define BUSY_PIN 7
*/
/* ADD THE FOLLOWING SECTION: */
// Custom pin definition (MKR1000)
#define RST_PIN 4
#define DC_PIN 5
#define CS_PIN 7
#define BUSY_PIN 3
class EpdIf {
  • Compile and upload the demo sketch epd1in54-demo to test the module and your setup.

Door Sign – Application

The door sign application is a simple combination of the two example sketches from the previous sections: The sketch for testing the Thinger.ioconnectivity and the ep1in54-demo sketch provided by Waveshare.

For this demo we basically:

closed
  • include these bitmap arrays in a sketch:
/* "imagedata_open.h" */
extern const unsigned char IMAGE_DATA_OPEN[];
/* "imagedata_open.coo" */
#include "imagedata_open.h"
#include <avr/pgmspace.h>
const unsigned char IMAGE_DATA_OPEN[] PROGMEM = {
/* 0X00,0X01,0XC8,0X00,0XC8,0X00, */
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, ...
// Image data for "We are open", "Sorry, we are closed" and "Be right back" ...
#include "imagedata_open.h"
#include "imagedata_closed.h"
#include "imagedata_brb.h"
  • define an input resource and a handler for thinger.io to receive a parameter to select which image to display:
// Handle for "image" resource:
 thing["image"] << [](pson &in) {
   boolean clean = in["clean"];
   int number = in["number"];
   displayImage(clean, number);
 };
// Handler called function for displaying images:
void displayImage(boolean clean, int number)
{
 if (clean) { ePaperClear(); }
 const unsigned char *image_data;
 switch (number)
 {
   case 0:
     image_data = IMAGE_DATA_OPEN;
     break;
   case 1:
     image_data = IMAGE_DATA_AWAY;
     break;
   case 2:
     image_data = IMAGE_DATA_CLOSED;
     break;
 }
 ePaperShowImage(image_data);
}
// Clear e-ink display:
void ePaperClear()
{
 epd.ClearFrameMemory(0xFF);   // bit set = white, bit reset = black
 epd.DisplayFrame();
 epd.ClearFrameMemory(0xFF);   // bit set = white, bit reset = black
 epd.DisplayFrame();
 epd.Init(lut_partial_update);
}
// Display image on e-ink display:
void ePaperShowImage(const unsigned char image_data[])
{
 epd.SetFrameMemory(image_data);
 epd.DisplayFrame();
 epd.SetFrameMemory(image_data);
 epd.DisplayFrame();
 epd.Init(lut_partial_update);
}

The full code can be found here.

After compiling, uploading and running the code, the device should be shown as Online in the thinger.io device status board and after clicking on the View API button you should see the previously defined input resource as image – Private in the DOORSIGN API. When you expand this item you’ll see the input parameters and you’ll be able to send a request to your device:

DOORSIGN Dashboard

Doorsign Api

The REST call shown if you select Show query will be used later in the AWS Lambda function.

More information on the thinger.io Server API can be found here.

Alexa Skill

Create the Alexa Skill starting at the Amazon Developer Console:

  • Leave the Skill Type as Custom Interaction Model.
  • If your device uses English(U.K.) change the language to English(U.K.).
  • Pick a Name, e.g. “Door Sign”.
  • Leave all other Global Fields to No.
  • Save.
  • Note the newly created Application ID on the next screen.
  • Click Next.

 

 

 

 

 

Next define

  • an Intent Schema (see below)
{
 "intents": [
    {
 "slots": [
        {
 "name": "Status",
 "type": "STATUS_TYPE"
        }
      ],
 "intent": "ShowStatus"
    },
    {
 "intent": "AMAZON.HelpIntent"
    },
    {
 "intent": "AMAZON.StopIntent"
    }
  ]
}
  • a custom Slot Type “STATUS_TYPE” with values “open”, “away” and “closed
  • and Sample Utterances (see below)
ShowStatus display that we are {Status}
ShowStatus show that i am {Status}
ShowStatus we are {Status}
ShowStatus i am {Status}

AWS Lambda

As endpoint for the Skill, we create an AWS Lambda function.

  • Sign-in to the AWS Management Console,
  • Select All Services > Compute > Lambda
  • Select Create Function
  • Choose Blueprints and filter by entering “alexa”
  • Choose any blueprint e.g. the alexa-skill-kit-sdk-factskill (Node.js) and click Configure
  • Pick a name for your function e.g. myDoorSign
  • Select an existing role or define a new one (see here)
  • After the function has been created successfully:
  • Add a trigger of type Alexa Skills Kit and configure it by entering the Application Id of the previously created Alexa Skill.
  • In the Function code section select all code in the inline editor for the file index.js and replace it by pasting the following code:
'use strict';
/*
 * App ID for the skill
 */
var APP_ID = "REPLACE_ME__ALEXA_APP_ID";
var SKILL_NAME = "REPLACE_ME__ALEXA_SKILL_NAME";
/*
 * Alexa SDK
 */
var Alexa = require('alexa-sdk');
/*
* HTTP/HTTPS
*/
var https = require('https');
/*
 * Thinger.io device
 */
const ti_user = "REPLACE_ME__THINGER_IO_USER";
const ti_device = "REPLACE_ME__THINGER_IO_DEVICE_ID";
const ti_token = "REPLACE_ME__THINGER_IO_ACCESS_TOKEN";
const ti_api_host = "api.thinger.io"
const ti_api_port = 443;
const ti_api_base_path = "/v2/users/" + ti_user + "/devices/" + ti_device + "/";
/*
 * Register handlers
 */
exports.handler = function (event, context, callback) {
 var alexa = Alexa.handler(event, context);
 alexa.appId = APP_ID;
 alexa.registerHandlers(handlers);
 alexa.execute();
};
var handlers = {
 /*
     * The "ShowStatus" intent:
     */
 "ShowStatus": function () {
 var myHandler = this;
 var speechOutput;
 var cardTitle;
 var statusSlot = this.event.request.intent.slots.Status;
 var status = "open";
 var imageNumber = 0;
 // Get slot(s):
 if (statusSlot && statusSlot.value) {
 status = statusSlot.value.toLowerCase();
        }
 // Determine image number from status:
 switch (status) {
 case "open":
 imageNumber = 0;
 break;
 case "closed":
 imageNumber = 2;
 break;
 case "away":
 imageNumber = 1;
 break;
 default:
 imageNumber = 0;
 break;
        }
 // Build path:
 var ti_api_path = ti_api_base_path + "image";
 // Build request body:
 var ti_input = { in: {
 clean: true,
 number: imageNumber
            }
        };
 // Build POST request:
 var request_body = JSON.stringify(ti_input);
 var request_headers = {
 "Authorization": "Bearer " + ti_token,
 "Content-Type": "application/json",
 "Content-Length": Buffer.byteLength(request_body)
        }
 var request_options = {
 host: ti_api_host,
 port: ti_api_port,
 path: ti_api_path,
 method: "POST",
 headers: request_headers
        }
 console.log("REQUEST - HEAD:" + JSON.stringify(request_options));
 console.log("REQUEST - BODY:" + JSON.stringify(request_body));
 // Handle POST request:
 var request = https.request(request_options, function (r) {
 console.log("RESPONSE - STATUS:" + r.statusCode);
 r.on('data', function (d) {
 console.log("RESPONSE:" + d);
 var d_json = JSON.parse(d);
            });
 r.on('end', function () {
 console.log("END: returning speech output ...");
 speechOutput = "The door sign has been updated successfully! The new status shown is '"+ status+"'.";
 cardTitle = "Success";
 myHandler.emit(':tellWithCard', speechOutput, cardTitle, speechOutput);
            });
 r.on('error', function (e) {
 console.log("ERROR:");
 console.error(e);
 speechOutput = "Sorry, there was problem - I could not update the door sign!";
 cardTitle = "Error";
 myHandler.emit(':tellWithCard', speechOutput, cardTitle, speechOutput);
            });
        });
 // Send POST request:
 request.write(request_body);
 request.end();
    },
 /*
     * Built-in intents:
     */
 "AMAZON.HelpIntent": function () {
 this.emit(':ask', "You can say tell door sign 'we are open', or, you can say exit... What can I help you with?", "What can I help you with?");
    },
 "AMAZON.StopIntent": function () {
 var speechOutput = "OK";
 this.emit(':tell', speechOutput);
    },
 'Unhandled': function () {
 this.emit(':ask', "What can I do for you?", "What can I do for you?");
    }
};

and replace the strings:

  • REPLACE_ME__THINGER_IO_USER with your thinger.io username
  • REPLACE_ME__THINGER_IO_DEVICE_ID with the Id of the previously registered device at thinger.io
  • REPLACE_ME__THINGER_IO_ACCESS_TOKEN with the previously created additional access token for your device at thinger.io
  • REPLACE_ME__ALEXA_APP_ID with the Application Id of the previously created Alexa Skill
  • REPLACE_ME__ALEXA_SKILL_NAME with the name of the previously created Alexa Skill

In the Configuration section of your Alex Skill:

  • Select AWS Lambda ARN (Amazon Resource Name) as Service Endpoint Type
  • and enter the ARN of this Lambda function in the Default field.

AWS management control

 

 

 

 

 

 

 

 

 

 

 

 

 

Sewing It All Together

Now you should have connected your device to the thinger.io platform (via username, device Id and device token), linked your device to an AWS Lambda function (via username, device Id and access token) and linked the AWS Lambda function to an Alexa Skill (via Skill Application Id and the Lambda function’s ARN).

If all steps have been performed correctly, you can test your setup.

Test

Test

Test

Test

Final test with Echo Dot

Schematics

Code

#define _DEBUG_
#define _DISABLE_TLS_#define _DEBUG_
#define _DISABLE_TLS_

/*
  INCLUDES
*/

// WiFi 101
#include <WiFi101.h>

// Thinger.io
#include <ThingerWifi101.h>

// Waveshare e-paper module
#include <SPI.h>
#include <epd1in54.h>
#include <epdpaint.h>

// Image data for "We are open", "Sorry, we are closed" and "Be right back" ...
#include "imagedata_open.h"
#include "imagedata_closed.h"
#include "imagedata_away.h"

/*
  DEFINES
*/

// WiFi parameters
#define WIFI_SSID "REPLACE_ME__WIFI_SSID"
#define WIFI_SSID_PASSWORD "REPLACE_ME__WIFI_PASSWORD"

// Thinger.io parameters
#define THINGERIO_USERNAME "REPLACE_ME__THINGER_IO_USER"
#define THINGERIO_DEVICE_ID "REPLACE_ME__THINGER_IO_DEVICE_ID"
#define THINGERIO_DEVICE_CREDENTIAL "REPLACE_ME__THINGER_IO_DEVICE_CREDENTIAL"

// E-ink colours
#define COLORED 0
#define UNCOLORED 1

#define LED_PIN 6

/*
  VARIABLES
*/

// Thinger.io device handle
ThingerWifi101 thing(THINGERIO_USERNAME, THINGERIO_DEVICE_ID, THINGERIO_DEVICE_CREDENTIAL);

// E-paper image buffer and handle
unsigned char image[5000];
Paint paint(image, 0, 0);
Epd epd;

/*
  SETUP
*/

void setup()
{
  // Configure serial
  Serial.begin(115200);

  // Initialize e-paper
  if (epd.Init(lut_full_update) != 0)
  {
    Serial.println("e-Paper init failed!");
    return;
  }

  // Configure wifi network
  thing.add_wifi(WIFI_SSID, WIFI_SSID_PASSWORD);

  // Configure ressource(s) ...

  // Handle for "text" resource:
  thing["text"] << [](pson & in) {
    boolean clean = in["clean"];
    int posX = in["posX"];
    int posY = in["posY"];
    int fontSize = in["fontSize"];
    String content = in["content"];
#ifdef _DEBUG_
    Serial.println("Received 'text' command ...");
    Serial.print("text.clean:");
    Serial.println(clean);
    Serial.print("text.posX:");
    Serial.println(posX);
    Serial.print("text.posY:");
    Serial.println(posY);
    Serial.print("text.fontSize:");
    Serial.println(fontSize);
    Serial.print("text.content:");
    Serial.println(content);
#endif
    displayText(clean, posX, posY, fontSize, content);
  };


  // Handle for "image" resource:
  thing["image"] << [](pson & in) {
    boolean clean = in["clean"];
    int number = in["number"];
#ifdef _DEBUG_
    Serial.println("Received 'image' command ...");
    Serial.print("image.clean:");
    Serial.println(clean);
    Serial.print("image.number:");
    Serial.println(number);
#endif
    displayImage(clean, number);
  };
}

/*
  LOOP
*/

void loop()
{
  // Just call Thinger.io device handler ...
  thing.handle();
}

/*
  FUNCTIONS
*/

// Handler called function for displaying text:
void displayText(boolean clean, int posX, int posY, int fontSize, String text)
{
  if (clean)
  {
    ePaperClear();
  }
  ePaperDisplayText(clean, posX, posY, fontSize, text);
}

// Handler called function for displaying images:
void displayImage(boolean clean, int number)
{
  if (clean)
  {
    ePaperClear();
  }

  const unsigned char *image_data;
  switch (number)
  {
    case 0:
      image_data = IMAGE_DATA_OPEN;
      break;
    case 1:
      image_data = IMAGE_DATA_AWAY;
      break;
    case 2:
      image_data = IMAGE_DATA_CLOSED;
      break;
  }

  ePaperShowImage(image_data);
}

// E-paper functions ...

// Clear e-ink display:
void ePaperClear()
{
  /**
     From epd1in54-demo.ino - Waveshare 1.54inch e-paper display demo:
     There are 2 memory areas embedded in the e-paper display
     and once the display is refreshed, the memory area will be auto-toggled,
     i.e. the next action of SetFrameMemory will set the other memory area
     therefore you have to clear the frame memory twice.
  */
  epd.ClearFrameMemory(0xFF);   // bit set = white, bit reset = black
  epd.DisplayFrame();
  epd.ClearFrameMemory(0xFF);   // bit set = white, bit reset = black
  epd.DisplayFrame();

  if (epd.Init(lut_partial_update) != 0)
  {
    Serial.print("e-Paper init failed!");
    return;
  }
}

// Display text on e-ink display:
void ePaperDisplayText(boolean clean, int posX, int posY, int fontSize, String text)
{
  paint.SetRotate(ROTATE_0);
  paint.SetWidth(200);
  paint.SetHeight(200);

  if (clean)
  {
    paint.Clear(UNCOLORED);
  }

  sFONT *font;
  switch (fontSize)
  {
    case 8:
      font = &Font8;
      break;
    case 12:
      font = &Font12;
      break;
    case 16:
      font = &Font16;
      break;
    case 20:
      font = &Font20;
      break;
    case 24:
      font = &Font24;
      break;
    default:
      font = &Font24;
      break;
  }

  paint.DrawStringAt(posX, posY, text.c_str(), font, COLORED);
  epd.SetFrameMemory(paint.GetImage(), 0, 0, paint.GetWidth(), paint.GetHeight());
  epd.DisplayFrame();

  if (epd.Init(lut_partial_update) != 0)
  {
    Serial.print("e-Paper init failed");
    return;
  }
}


// Display image on e-ink display:
void ePaperShowImage(const unsigned char image_data[])
{
  /**
      From epd1in54-demo.ino - Waveshare 1.54inch e-paper display demo:
      There are 2 memory areas embedded in the e-paper display
      and once the display is refreshed, the memory area will be auto-toggled,
      i.e. the next action of SetFrameMemory will set the other memory area
      therefore you have to clear the frame memory twice.
  */
  epd.SetFrameMemory(image_data);
  epd.DisplayFrame();
  epd.SetFrameMemory(image_data);
  epd.DisplayFrame();

  if (epd.Init(lut_partial_update) != 0)
  {
    Serial.print("e-Paper init failed!");
    return;
  }
}

Alexa controlled door sign demo

Alexa skill interaction model, AWS Lambda function code, Arduino code …

Alexa controlled door sign demo — Read More

 


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top