Project tutorial
Alexa Smart Socket

Alexa Smart Socket © Apache-2.0

Control your home appliances connected to a smart socket extension box with your voice using Alexa.

  • 1,823 views
  • 4 comments
  • 9 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

Introduction

This project describes the design and fabrication of a home automated indoor gadget "Smart Socket" which is simply an AC mains power socket extension that integrates with the Amazon Alexa voice service for voice interraction and control of the device. Typically the mains socket extension has four sockets, with an enabled alexa voice device like the Amazon Echo Dot, you gain the ability to use your voice to control the states (ON/OFF) of these sockets. On top of that, there exists the fifth socket which implemented dimming capability. This dimming capability can serve to power your incandescent side bed lamp in your room. And with your alexa enabled device, you can easily use your voice to control the brightness by simply saying "Alexa ask smart socket to set dimmer to thirty percent". And there you'll observe the light as it smoothly increases or decreases its brightness, instead of a sudden change of brightness that could trigger seizure. Check the video below to have a full idea of what this alexa voice enabled device can do.

The Design

Basically, this project consist of four major components as described by the diagram below.

  • The Alexa skill service
  • The MQTT server
  • Persisitence server (Redis)
  • Smart socket

The Alexa skill

The Alexa skill consists of two components ; The Skill Interface and the skill service. The skill Interface is what is responsible for processing a user's spoken words and delivers a payload of request to the skill service. The skill service on the other hand, which is hosted on cloud, handles request from the alexa skill interface. The skill interface configuration is developed on the amazons developer portal and communicates with the skill server. When a user speaks into an alexa voice enabled device, the speech is converted to text and sent to the skill interface for processing using already defined utterances (Spoken words with similar intentions). Once the skill interface is done with the user's translation, a request is pushed to its skill service to handle the actions of the request and also returns a response back to the skill interface which in turn is translated back into voice for the user. The Skill interface consist of confiugartion on how to process and handle spoken words from users. Basically the skill interface consits of utterances grouped into intents. Intents are actions to be handled by the skill service, when an utterance is invoked the intent under which the utternace is grouped is sent to the skill server and the skill server must implement a handle of the intent.

In this project, we defined seven intents in the skill interface to be handled by the skill service written in node.js.

  • ChangeSocketNameIntent - used to assign custom device name to a socket number e.g from the four sockets, we can say let socket 1 (generic naming convention of sockets) become fridge. so we can easily say "Alexa ask smart socket to turn on the fridge" instead of trying to remeber which socket your fridge is connected to.
  • SwitchNamedSocketOnIntent - this intent is responsible for turning on socket number that has been mapped to an appliance's name i.e "Fan".
  • SwitchNamedSocketOffIntent - this intent is responsible for turning off socket number that has been mapped to an appliance's name i.e "Lamp".
  • SwitchSocketIntent - this intent is responsible for turning off and on of generic numbered socket names ie. "Alexa ask smart socket to turn socket one on"
  • DimLampIntent - used for setting the dimmer level
  • DimmerLevelIntent - this intent is used to query the current dimmer level in percent.
  • SocketStateIntent - used to query the states of all four sockets.

Creating an alexa skill is quite straight forward, all you need is an Amazon developer account, then you are good to go.

Creating the smart socket skill

This is where things started getting interesting for me. To create the smart socket skill with our define intents as defined earlier.

Step 1

Login to the amazon developer console, with your amazon developer's account and choose "ALEXA" from the list of tabs on the console and select get started on the alexa skill kit (ASK).

Step 2

Click the add a new skill button to begin with creating a new skill:

Step 3

Select create custom interraction model in skill type and enter the skill name and its invocation name, click save to continue. And next would be, configuring the interraction model as i have specified ealier.

Step 4

Building the interaction model, this is a JSON format where you configure your utterances, intents and slot. I found the use of model builder to be very useful and quick. It comes with a beautiful and easy to use user interface. So for the Intents mentioned earlier this is where we need to define those utterances that would trigger a particular intent.

In the builder, from my configuration, my ChangeSocketNameIntent has what are called slots. Slots are elegant way of passing variables in user spoken words. for instance the sample utterance in the ChangeSocketNameIntent is "change socket {number} to {device}. This could simply mean, in usage, "Change socket one to lamp".

Step 5

And here comes the step where you would need to specify where you are going to be hosting your Skill service. As seen in the image below, options are given to either use Lambda function, or you specify a Https url to where your skill service is hosted. In this project i decided to use an external source to host the skill service written in node.JS.

The skill service implemented the the intent handlers for the intents from the skill interface configuration that holds our utterances. Writing the skill service using the alexa kit was good, but i needed to do my development and test on my local machine and also deploy on Heroku. So i came across JOVO, a voice application framework that makes writing voice applications for alexa much simpler, and also already provided in the framework, database managemt api to manage local file db persistence and dynamoDB. To cover it all up the framework provides a snippet of how to test on your skill on your local machine.

Here is a sample intent handling snippet;

'SwitchNamedSocketOnIntent': function(device){
     }else{
          app.tell('Sorry there seems to be no socket mapped to that name');
     }  
   });
 },

The MQTT Broker

What the skill service does when it receives an intent to turn the light on or off for example, is to find a way to communicate to the smart socket the intention of turning on or off a specific socket number. One of the ways to archieve this, is via MQTT protocol. MQTT protocol works as publish and subscribe kind of protocol. Messages are being published with a topic to a MQTT broker (server) and subscribers listens for incoming messages from the topic they have subscribed to.

For this project, the skill service and smart socket implemented this communication protocol. MQTT is effiecient as it can work where there is network latency. The Node server skill server leveraged on mqtt.js. this can be installed using npm.

$ npm install --save mqtt 

The MQTT broker used in this project is mosquitto. it is a public broker which you can use for your test if you wish not to host one on your local machine. The snippet below shows how to connect, subscribe to a topic.

var subClient = mqtt.connect('mqtt://test.mosquitto.org');
 subClient.on('connect', function () {
     subClient.subscribe('smartsocket/state/shadow');
     subClient.publish('smartsocket/state/shadow', 'Hello mqtt');
 });
 subClient.on('message', function (topic, message) {
 console.log(message.toString());
 if(topic === 'smartsocket/state/shadow'){
     saveSocketStates(message.toString());
    }
 });

The use of mqtt in this design is for the skill service to be able to send (publish) messages to a topic that the smart socket has also subsribed to. When the smart socket receives these messages, which is in Json format, it parses the json object to determine what to do. On the other hand the smart socket constantly publishes its ON/OFF states to the Broker. the skill service as well listens or subscribes to this topic and saves received messages in an in memory database Redis.

Redis is an in memory database, this means the database uses the ram for its persistence which makes it relatively fast to save and retrive information. Redis majorly is used for queueing and for temporary storage of data. But it is used here just to demonstrate a full funtionality of this project. Redis is available on windows and linux. After installing redis and started on my machine, redis node js client installed from npm is used to persist data into the redis database.

$npm install --save redis

To run the service locally on my machine i used ngrok.com . i used ngrok because the amazon skill interface that needed to communicate with the skill service only works on https and hence ngrok coming to the rescue.

Run:

$ngrok http 3000   

This simply binds the port to a public https url that can be supplied to allow skill interface to interract with the skill service hosted on my local machine.

Smart socket design

This is the most critical part of this project. The smart socket comprises of two main components an Arduino Pro Mini and Node-MCU ESP8266. The Node-MCU is responsible for providing the wifi capabilty.

The schematic below elaborates on the smart socket main components.

The schematic does not include the dimmer, but the pin has been described inthe image above. The image below gives a close up of how the dimmer looks like.

The Function of the dimmer is to allow us be able to control the brightness of an incandescence lamp. not recommended for inductive load.

The only task by the ESP8266 is wifi connectivity and the implementation of the mqtt protocol. While the Arduino Pro mini major task is to respond to commands from the ESP. The commands include:

  • Turn of or on socket 1
  • Turn of or on socket 2
  • Turn of or on socket 3
  • Turn of or on socket 4
  • Set dimmer to a level.

And to also publish its current state. The code for both Microcontroller are given below.

For the ESP8266:

#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
WiFiClient espClient;
PubSubClient client(espClient);
const char* ssid = "Andym";
const char* password = "ctronix1";
const char* mqtt_server = "test.mosquitto.org";
long lastMsg = 0;
char msg[50], socketStates[60];
int value = 0;
int CONNECTION_STATUS = 2;
String stateData;
void setup() {
 pinMode(BUILTIN_LED, OUTPUT);                 // Initialize the BUILTIN_LED pin as an output
 pinMode(CONNECTION_STATUS, OUTPUT);
 Serial.begin(9600);
 setupWifi();                                  // begins wifi setup to connect to an access point
 client.setServer(mqtt_server, 1883);          // setup broker server and port
 client.setCallback(callback);                 // mqtt subscription callback
}
void loop() {
 if (!client.connected()) {
   reconnect();
 }
 client.loop();
 readSocketStates();
 long now = millis();
 if (now - lastMsg > 2000) {
   lastMsg = now;
   client.publish("smartsocket/state/shadow", socketStates);
 }
}
void callback(char* topic, byte* payload, unsigned int length) {
 for (int i = 0; i < length; i++) {
   Serial.print((char)payload[i]);
 }
 Serial.println();
}
void setupWifi() {
 delay(10);
 Serial.println();
 Serial.print("Connecting to ");
 Serial.println(ssid);
 WiFi.begin(ssid, password);
 while (WiFi.status() != WL_CONNECTED) {
   delay(500);
   Serial.print(".");
   digitalWrite(CONNECTION_STATUS, HIGH); 
   delay(500);
   Serial.print(".");
   digitalWrite(CONNECTION_STATUS, LOW); 
 }
 Serial.println("");
 Serial.println("WiFi connected");
 Serial.println("IP address: ");
 digitalWrite(CONNECTION_STATUS, LOW); 
 Serial.println(WiFi.localIP());
}
void reconnect() {
 // Loop until we're reconnected
 while (!client.connected()) {
   Serial.print("Attempting MQTT connection...");
   // Attempt to connect
   if (client.connect("SmartSocket")) {
     Serial.println("connected");
     // Once connected, publish an announcement...
     client.publish("outTopic", "hello world");
     // ... and resubscribe
     client.subscribe("smartsocket/state/control");
   } else {
     Serial.print("failed, rc=");
     Serial.print(client.state());
     Serial.println(" try again in 5 seconds");
     // Wait 5 seconds before retrying
     delay(5000);
   }
 }
}
void readSocketStates(){
 while(Serial.available() > 0){
   char data = Serial.read();
   stateData += data;
   if(data == '\n'){
     StaticJsonBuffer<200> jsonBuffer;
     JsonObject& root = jsonBuffer.parseObject(stateData);
     if(!root.success()){
        //Serial.println("parseObject() failed");
        stateData = "";
        return;
     }
     stateData.toCharArray(socketStates, stateData.length());
     stateData = "";
     //Serial.println(socketStates);
   }
 }
}

For the Arduino Pro Mini:

#include <SoftwareSerial.h>
#include <ArduinoJson.h>
#include "TimerOne.h"
SoftwareSerial swSer(8, 9); // RX, TX
String inData;
long lastMsg = 0;
int counter = 0;
const int SOCKET_ONE = 10;
const int SOCKET_TWO = 11;
const int SOCKET_THREE = 12;
const int SOCKET_FOUR = 13;
const int MAX_DELAY = 40;
int AC_LOAD = 3;
const boolean OFF;
const boolean ON;
boolean dimmerState;
boolean zcFlag = false;
int dim = MAX_DELAY;                                //dim delay
void setup() {
 Serial.begin(9600);
 //swSer.begin(9600);
 pinMode(SOCKET_ONE, OUTPUT);
 pinMode(SOCKET_TWO, OUTPUT);
 pinMode(SOCKET_THREE, OUTPUT);
 pinMode(SOCKET_FOUR, OUTPUT);
 pinMode(AC_LOAD, OUTPUT);                         // Set AC Load pin as output
 attachInterrupt(0, zero_crosss_int, RISING);      // Choose the zero cross interrupt on interrupt pin 0 (D2)
 Timer1.initialize(125);                           // initialize the timer 1 with 125us because we need 80steps to delay 10ms (1/2 period of 50Hz) 
 Timer1.attachInterrupt(timerOneCallBack);         // attach interrupt callback function
}
void loop() {
 receiveCommand();
 long now = millis();
 if (now - lastMsg > 2000) {
   lastMsg = now;
   pushSocketState();
 }
}
void receiveCommand(){
 while (Serial.available() > 0) {
   char recieved = Serial.read();
   inData += recieved;
   Serial.write(recieved);
   if (recieved == '\n'){
           StaticJsonBuffer<200> jsonBuffer;
           Serial.print("Arduino Received: ");
           Serial.print("De-serialised ");
           Serial.print(inData);
           JsonObject& root = jsonBuffer.parseObject(inData);
           if (!root.success()) {
               Serial.println("parseObject() failed");
               inData = "";
               return;
           }
           inData = "";                  // Clear recieved buffer
           int value = root["value"];
           int socket = root["socket"];
           Serial.print("Command ");
           Serial.println(value);
           Serial.print("Socket");
           Serial.println(socket);
           parseCommand(socket, value);
    }
 }
}
void parseCommand(int socket, int value){
 switch(socket){
   case 1:{
     digitalWrite(SOCKET_ONE, value==1? HIGH : LOW);
     break;
   }
   case 2:{
     digitalWrite(SOCKET_TWO, value==1? HIGH : LOW);
     break;
   }
   case 3:{
     digitalWrite(SOCKET_THREE, value==1? HIGH : LOW);
     break;
   }
   case 4:{
     digitalWrite(SOCKET_FOUR, value==1? HIGH : LOW);
     break;
   }
   case 20:{
     int percent = (value*60)/100;
     if(dim < percent){
       smoothIncrease(percent);
     }
     if(dim > percent){
       smoothDecrease(percent);
     }
     Serial.print(percent);
     break;
   }
 }
}
void zero_crosss_int()  //function to be fired at the zero crossing to dim the light
{
   zcFlag = true;
}
void timerOneCallBack(){
 if(zcFlag) {
     counter++; 
 }
 if(counter >= dim){
   digitalWrite(AC_LOAD, HIGH);   // Fire the TRIAC
   delayMicroseconds(30);         // triac On propogation delay 
   digitalWrite(AC_LOAD, LOW);    // No longer trigger the TRIAC (the next zero crossing will swith it off) TRIAC
   counter = 0;
   zcFlag = false;
 }
}
void pushSocketState(){
   StaticJsonBuffer<100> jsonBuffer;
   JsonObject& root = jsonBuffer.createObject();
   root["one"]   = digitalRead(SOCKET_ONE);
   root["two"]   = digitalRead(SOCKET_TWO);
   root["three"] = digitalRead(SOCKET_THREE);
   root["four"]  = digitalRead(SOCKET_FOUR);
   root["dim"] = dim;
   root.printTo(Serial);
   Serial.println();
}
void smoothIncrease(int value){
 while(dim <= value){
   dim ++;
   delay(10);
 }
}
void smoothDecrease(int value){
 while(dim >= value){
   dim --;
   delay(10);
 }
}

Putting it together:

Testing switching socket number:

testing name change intent

testing dimmer intent

Thanks

Code

The skill serviceJavaScript
This is a node js application skill service for smart socket.
'use strict';

// =================================================================================
// App Configuration
// =================================================================================

const DEVICE_NAME_MAP = 'DEVICE_NAME_MAP';
const SOCKET_STATES = 'SOCKET_STATES';
const DIMMER_LEVEL = 'DIMMER_LEVEL';


const app = require('jovo-framework').Jovo;
const webhook = require('jovo-framework').Webhook;
var redis = require('redis');
var mqtt = require('mqtt');
var Map = require("collections/map");
var List = require("collections/list");
var subClient = mqtt.connect('mqtt://test.mosquitto.org');
var redisClient;

// Listen for post requests
webhook.listen(3000, function() {
    console.log('Local development server listening on port 3000.');

    redisClient = redis.createClient(6379, '127.0.0.1');
    redisClient.set("key", "value");
    console.log(redisClient.get("key"));

    subClient.on('connect', function () {
        subClient.subscribe('smartsocket/state/shadow');
        subClient.publish('smartsocket/state/shadow', 'Hello mqtt');
    });

    subClient.on('message', function (topic, message) {
        console.log(message.toString());
        if(topic === 'smartsocket/state/shadow'){
            saveSocketStates(message.toString());
        }
    });
});

webhook.post('/webhook', function(req, res) {
    app.handleRequest(req, res, handlers);
    console.log(req);
    app.execute();
});


// =================================================================================
// App Logic
// =================================================================================

const handlers = {

    'LAUNCH': function() {
        app.toIntent('HelloWorldIntent');
    },

    'HelloWorldIntent': function() {
        app.ask('Ok');
    },

    'ChangeSocketNameIntent': function(number, device) {
        console.log(device + ' ' + number);
        redisClient.get(DEVICE_NAME_MAP, function(err, data) {
            if(data !== null){
                var map = new Map(JSON.parse(data));
                unAssignDevice(map, device);
                assignDevice(map, number, device);
            }else{
                var map = new Map();
                unAssignDevice(map, device);
                assignDevice(map, number, device);
            }
        });
    },

    'SwitchNamedSocketOnIntent': function(device){
        console.log(device);
        redisClient.get(DEVICE_NAME_MAP, function(err, data) {
            console.log(data);
            if(data !== null){
                var map = new Map(JSON.parse(data));
                switchMapDevice(map, device, 1);
            }else{
                app.tell('Sorry there seems to be no socket mapped to that name');  //todo: make it an ask and use alexa state to know what to say yes or no to
            }                                                                       // example: would you like to assign it to a socket number?
        });
    },

    'SwitchNamedSocketOffIntent': function(device){
        console.log(device);
        redisClient.get(DEVICE_NAME_MAP, function(err, data) {
            console.log(data);
            if(data !== null){
                var map = new Map(JSON.parse(data));
                switchMapDevice(map, device, 0);
            }else{
                app.tell('Sorry there seems to be no socket mapped to that name');  //todo: make it an ask and use alexa state to know what to say yes or no to
            }                                                                       // example: would you like to assign it to a socket number?
        });
    },

    'SwitchSocketIntent': function(status, number) {
        var num = parseInt(number);
        if(isNaN(num)){
            app.ask('Sorry i did not fully understand that!, you can please ask me again or say "help" for other things you can try');
            return;
        }
        
        if(num > 4 || num < 0 ){
            app.ask('Sorry, you have specified an invalid socket number, please try again');
            return;
        }
        var state = (status === 'on')? 1 : 0;
        publishTopic('smartsocket/state/control', JSON.stringify({value: state, socket: num}));
        app.tell('socket ' + number + ' ' + status);
        
    },

    'DimLampIntent': function(percent) {
        var num = parseInt(percent);
        if(isNaN(num)){
            app.ask('Sorry i did not fully understand that!, you can please ask me again or say "help" for other things you can try');
        }else if (num > 100){
            app.ask('Sorry ' + percent + 'is not a valid level, you can try levels in the range of 1 to 100 percent');
        }
        else{
            publishTopic('smartsocket/state/control', JSON.stringify({value: percent, socket: 20}));
            app.tell('Ok, level set to ' + percent + ' percent');
        } 
    },

    'DimmerLevelIntent': function() {
        redisClient.get(SOCKET_STATES, function(err, reply){
            if(!err){
                try{
                    var states = JSON.parse(reply);
                    if(states !== null){
                        var percent = states.dim;
                        app.tell('Dimmer level is ' + percent + ' percent');
                    }else{
                        app.tell('Sorry, somthing went wrong. please you can try again');
                    }
                }catch(err){
                        app.tell('Sorry, somthing went wrong. please you can try again');
                }
            }else{
                app.tell('Sorry, somthing went wrong. please you can try again');
                console.log(reply.toString());
            }
        });
    },

    'SocketStateIntent': function(number) {
        var num = parseInt(number);
        if(isNaN(num)){
            app.ask('Sorry i did not fully understand that!, you can please ask me again or say "help" for other things you can try');
            return;
        }
        
        if(num > 4 || num < 0 ){
            app.ask('Sorry, you have specified an invalid socket number, please try again');
            return;
        }
        redisClient.get(SOCKET_STATES, function(err, reply){
            if(!err){
                try{
                    var states = JSON.parse(reply);
                    if(states !== null){
                        switch(num){
                            case 1:{
                                var state = states.one;
                                var status = state === 1 ? ' on': ' off';
                                app.tell('Socket one is' + status);
                                break;
                            }

                            case 2:{
                                var state = states.two;
                                var status = state === 1 ? ' on': ' off';
                                app.tell('Socket two is' + status);
                                break;
                            }

                            case 3:{
                                var state = states.three;
                                var status = state === 1 ? ' on': ' off';
                                app.tell('Socket three is' + status);
                                break;
                            }

                            case 4:{
                                var state = states.four;
                                var status = state === 1 ? ' on': ' off';
                                app.tell('Socket four is ' + status);
                                break;
                            }
                        }
                    }else{
                        app.tell('Sorry, somthing went wrong. please you can try again');
                    }
                }catch(err){
                        app.tell('Sorry, somthing went wrong. please you can try again');
                }
            }else{
                app.tell('Sorry, somthing went wrong. please you can try again');
                console.log(reply.toString());
            }
        });
    }
};




/**
 *unassigns a device's name from a map socket number
 * @param {*} map the map object that maintains the map between socket number and device name
 * @param {*} device the device's valid name
 */
function unAssignDevice(map, device){
    var numbers = new List();
    console.log('listing device name ====')
    for(var i = 1; i <= 4; i++){
        var key = i;
        var deviceName = map.get(key.toString());
        console.log(deviceName);
        if(deviceName === device){
            numbers.add(i);
        }
    }
    console.log('length of list ====');
    console.log(numbers.length);
    for(var m = 0; m < numbers.length; m++){
        var key = numbers.pop();
        console.log(key);
        map.delete(key.toString());
        console.log(m);
    }
}



/**
 * assigns a device's name to a socket number
 * @param {*} map 
 * @param {*} number 
 * @param {*} device 
 */
function assignDevice(map, number, device){
    console.log(map);
    map.set(number, device);
    console.log('After setting');
    console.log(map);
    var mapString = JSON.stringify(map);
    console.log("String map", mapString);
    redisClient.set(DEVICE_NAME_MAP, mapString, function(err) {
        app.tell('Ok, socket ' + number + ' has been named as ' + device);
    });
}


/**
 * searches for socket number mapped to the device mentioned
 * @param {*} map 
 * @param {*} device 
 * @param {*} state 
 */
function switchMapDevice(map, device, state){
    for(var i = 1; i <= 4; i++){
        var key = i;
        console.log(map);
        console.log(state);
        var deviceName = map.get(key.toString());
        console.log(deviceName);
        if(deviceName === device){
            console.log('socket: ' + i);
            publishTopic('smartsocket/state/control', JSON.stringify({value: state, socket: i}));
            var status = (state === 1)? ' on': ' off';
            app.tell('Ok, ' + device + ' ' + status);
            return;
        }
    }
    app.tell('Sorry, device ' + device + ', was not found');
}



function publishTopic(topic, message){
    var client  = mqtt.connect('mqtt://test.mosquitto.org');
    client.publish(topic, message);
    client.end();
}

function alarmTask(){
    console.log("im here tick tock!");
    if(subClient === null){
        subClient = mqtt.connect('mqtt://test.mosquitto.org');
    }
    if(subClient.disconnected){}
        subClient = mqtt.connect('mqtt://test.mosquitto.org');
        subClient.subscribe('smartsocket/state/shadow')
        subClient.on('message', function (topic, message) {
        console.log(message);
        console.log(topic);
    });
}

function saveSocketStates(states){
    try{
        var socketStates = JSON.parse(states);
        if(socketStates !== null){
            redisClient.set(SOCKET_STATES, JSON.stringify(socketStates));
        }
    }catch(err){
        console.log(err);
    }
}

// https://stackoverflow.com/questions/19349162/run-continuous-background-job-with-node-js
// https://stackoverflow.com/questions/17861362/node-js-child-process-difference-between-spawn-fork
        // console.log(device + ' ' + number);
        // app.db().load(DEVICE_NAME_MAP, function(err, data) {
        //     console.log(data);
        //     if(data !== null){
        //         var map = new Map(data);
        //         unAssignDevice(map, device);
        //         assignDevice(map, number, device);
        //     }else{
        //         var map = new Map();
        //         unAssignDevice(map, device);
        //         assignDevice(map, number, device);
        //     }
        // });

Schematics

Smart socket schematics
smart_socket_design_schematic_ja3WPrfCyb.fzz

Comments

Similar projects you might like

Alexa Based Smart Home Monitoring

Project tutorial by Adithya TG

  • 32,272 views
  • 25 comments
  • 71 respects

Alexa as a Smart Switch!

by FelixAdiy

  • 1,596 views
  • 1 comment
  • 5 respects

Hygge Home - Alexa Smart Bath

Project tutorial by J Howard

  • 6,303 views
  • 2 comments
  • 22 respects

DIY Air Humidifier with Backlight Controlled by Alexa

Project tutorial by Andrii Romanenko

  • 2,043 views
  • 0 comments
  • 8 respects

Smart Pool: Alexa Controlled Pool Manager

Project tutorial by Benjamin Winiarski

  • 3,265 views
  • 3 comments
  • 9 respects
Add projectSign up / Login