Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
|
Well, in my birthday -that was the past year in november- I gifted to me an Arduino, then when I enter to the main page I saw the Alexa contest and I was so excited, that motivated me to learn not only Arduino but a little of all, of javascript, of nodejs, about cloud, lambda function, IoT, NodeMCU, AWS, Serial communication, etc. I did my best, I learned a lot and I proved to myself that I could do it. The reason I chose to make an smart shower gadget was because my mother and sisters used an electric therma everyday but sometimes the water's temperature was so hot or not enough to them, another situation was that they couldn't change the water's temperature because the therma was very high. So I decided to give them a more personalized experience, now they can start, set their favorite temperature, and finish their shower with just their voice. This is the future!
Explanation step by stepFirst: START SHOWER, it starts when the skill is invoked, you have to say something like this: "Alexa, start Smart Shower Gadget Skill", this change the status of a variable store it into the ThingSpeak cloud, that help to indicate that the user call to the skill, then Alexa ask you something like this: "Welcome, what kind of shower do you desire? Cold, fresh, warm, hot or very hot?" then you must say one of the options, then Alexa will tell you the range of temperatures of the kind of shower you chose. With this information open a water valve and through PID, controls desired water's temperature through heater resistances and when the diference between the water's real temperature and the desired is less than 1°C a light is turned on, to indicate the user that can start the shower. When you want to use soap and shampoo, you must pass to the next stage.
Second: SOAP TIME, you must say something like this: "Alexa, soap time" and it will close the water's flow and turn off the lights and resistance changing the status of other variable on the cloud.
Third: RINSE TIME, when you need to rinse, just say: "Alexa, rinse time" then it'll do the same as start shower stage and will change the status of other variable in cloud.
Forth: FINISH TIME, when you finish the shower just tell: "Alexa, end the shower", it will close valve and turn off light and heater resistance and will reset status variables in the cloud.
Some images of the project:
This project works like this: You invoke the Alexa skill Smart Shower Gadget, then she process your request through an Amazon cloud, then runs a Lambda function which will tell Alexa what action will do and what she will say. In this case, the action she'll do is to change the status of variables that are store on ThingSpeak cloud, and she will say answers that I've programmed. NodeMCU is constantly reading the values stores in ThingSpeak cloud, NodeMCU read them and send them via Serial Communication to Arduino, Arduino based on this variables take different actions to control the water's temperature and the valve. To do this, following steps must be follow:
- Create an Alexa Skill with Amazon Developer.
- Create a Lambda function with AWS.
- Create the cloud to store variables with ThingSpeak.
- Configure of NodeMCU to connect the Cloud with Arduino
- Configure Arduino to control the gadget.
SSG_NodeMCUVFinal.ino
Arduino//The first section is initialization of variables. Be sure to edit the Read API Key and Write API Key, your channel numbers, and wireless network connection information.
#include <SoftwareSerial.h>
#include <ThingSpeak.h>
#include <ESP8266WiFi.h>
SoftwareSerial NodeSerial(D2, D3); //RX|TX
// Network Parameters
const char* ssid = "dlink-2BDE";
const char* password = "cqaqh83658";
// ThingSpeak information
char thingSpeakAddress[] = "api.thingspeak.com";
unsigned long channelID = 419917;
char* readAPIKey = "TI28I2USMJ3DDCU2";
char* writeAPIKey = "7AVYH203044IJG3T";
const unsigned long postingInterval = 20000;
unsigned int dataFieldOne = 1; // Field 1: Start Time
unsigned int dataFieldTwo = 2; // Field 2: Shower Temperature
unsigned int dataFieldThree = 3; // Field 3: Soap Time
unsigned int dataFieldFour = 4; // Field 4: Rinse Time
unsigned int dataFieldFive = 5; // Field 5: Finish Time
// Global variables
// These constants are device specific. You will need to get them from the manufacturer or determine them yourself.
int StartTime = 0;
int DesiredTemperature;
int SoapTime = 0;
int RinseTime = 0;
int FinishTime = 0;
int val;
long lastUpdateTime = 0;
WiFiClient client;
//In the setup function, start the serial monitor and read the calibration constants.
void setup() {
pinMode(D2, INPUT);
pinMode(D3, OUTPUT);
NodeSerial.begin(9600);
Serial.begin(115200);
Serial.println("Start");
connectWiFi();
//Set initial cloud's values variables
writeTSData(channelID, dataFieldOne, 0);
delay(15000); //Free ThingSpeak cloud let you update data each 15 seconds.
writeTSData(channelID, dataFieldTwo, 25);
delay(15000);
writeTSData(channelID, dataFieldThree, 0);
delay(15000);
writeTSData(channelID, dataFieldFour, 0);
delay(15000);
writeTSData(channelID, dataFieldFive, 0);
delay(15000);
}
//The main loop continuously checks to see how long it has been since the temperature was read. If the set time period has elapsed, the device is read, the temperature is calculated, and the output is written to your channel.
void loop() {
StartTime = readTSData( channelID, dataFieldOne );
if (StartTime == 0) {
Serial.println("The Skill has not yet been called");
NodeSerial.print("Start:");
NodeSerial.print(StartTime);
}
else if (StartTime == 1) {
// Only update if posting time is exceeded
if (millis() - lastUpdateTime >= postingInterval) {
lastUpdateTime = millis();
// Read the constants at startup.
DesiredTemperature = readTSData( channelID, dataFieldTwo );
SoapTime = readTSData( channelID, dataFieldThree );
RinseTime = readTSData( channelID, dataFieldFour );
FinishTime = readTSData( channelID, dataFieldFive );
//Print values get from cloud to computer's screen.
Serial.print("Start:");
Serial.print(StartTime);
Serial.print(", Temperature:");
Serial.print(DesiredTemperature);
Serial.print(", Soap:");
Serial.print(SoapTime);
Serial.print(", Rinse:");
Serial.print(RinseTime);
Serial.print(", Finish:");
Serial.println(FinishTime);
// Print values get from cloud to Arduino through serial communication.
NodeSerial.print("Start:");
NodeSerial.print(StartTime);
NodeSerial.print(",Temperature:");
NodeSerial.print(DesiredTemperature);
NodeSerial.print(",Soap:");
NodeSerial.print(SoapTime);
NodeSerial.print(",Rinse:");
NodeSerial.print(RinseTime);
NodeSerial.print(",Finish:");
NodeSerial.print(FinishTime);
//Depending of values of variables there are differente cases.
val = SoapTime + RinseTime * 2 + FinishTime * 3;
switch (val) {
case 0:
Serial.println("CASE 0: START SHOWER");
break;
case 1:
Serial.println("CASE 1: SOAP TIME");
break;
case 2:
Serial.println("CASE 2: RINSE TIME 2");
break;
case 3:
Serial.println("CASE 3: RINSE TIME 2");
writeTSData(channelID, dataFieldThree, 0);
break;
case 5:
Serial.println("CASE 5: FINISH TIME");
writeTSData(channelID, dataFieldOne, 0);
delay(15000);
writeTSData(channelID, dataFieldThree, 0);
delay(15000);
writeTSData(channelID, dataFieldFour, 0);
delay(15000);
writeTSData(channelID, dataFieldFive, 0);
delay(15000);
break;
default:
break;
}
if(FinishTime == 1) {
delay(15000);
Serial.println("CASE 5: FINISH TIME");
writeTSData(channelID, dataFieldOne, 0);
delay(15000);
writeTSData(channelID, dataFieldThree, 0);
delay(15000);
writeTSData(channelID, dataFieldFour, 0);
delay(15000);
writeTSData(channelID, dataFieldFive, 0);
delay(15000);
}
}
}
}
//Connect your device to the wireless network using the connectWiFi function.
int connectWiFi() {
while (WiFi.status() != WL_CONNECTED) {
WiFi.begin( ssid, password );
delay(2500);
Serial.println("Connecting to WiFi");
}
Serial.println( "Connected" );
ThingSpeak.begin( client );
}
//Read data from a single field on a channel with readTSData. Write a single value to ThingSpeak using writeTSData, write multiple values simultaneously with write2TSdata.
float readTSData( long TSChannel, unsigned int TSField ) {
float data = ThingSpeak.readFloatField( TSChannel, TSField, readAPIKey );
return data;
}
//To write data into the cloud.
int writeTSData( long TSChannel, unsigned int TSField, float data ) {
int writeSuccess = ThingSpeak.writeField( TSChannel, TSField, data, writeAPIKey ); // Write the data to the channel
if ( writeSuccess ) {
Serial.println( String(data) + " written to Thingspeak field " + String(TSField));
}
return writeSuccess;
}
SSG_ArduinoVFinal.ino
Arduino#include <Servo.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PID_v1.h>
#include <SoftwareSerial.h>
Servo myServo;
const int ServoPin = 10;
SoftwareSerial ArduinoSerial(3, 2); //RX|TX
const byte TemperaturePin = 9;
OneWire OneWireObject(TemperaturePin);
DallasTemperature sensorDS18B20(&OneWireObject);
double Setpoint, Input, Output;
double Kp = 500, Ki = 10, Kd = 1;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
int WindowSize = 10000;
unsigned long windowStartTime;
const int RelayHeaterPin = 6; //22 ohm heater's resitance
const int RelayLightPin = 8; //Light that will turn on when the temperature's difference between input
//and setpoint is less than 1C.
String cadena;
int Start = 0;
int Temperature;
int Soap = 0;
int Rinse = 0;
int Finish = 0;
int val; //Value of a combination of Soap, Rinse and Finish variables as input to Switch Case structure.
/*----------------------------SETUP---------------------------------*/
void setup() {
pinMode(3, INPUT);
pinMode(2, OUTPUT);
pinMode(TemperaturePin, INPUT);
sensorDS18B20.begin();
myServo.attach(ServoPin);
myServo.write(0);
pinMode(RelayHeaterPin, OUTPUT);
pinMode(RelayLightPin, OUTPUT);
digitalWrite(RelayHeaterPin, HIGH); //Initially off. It's use as NC (Normally Close).
digitalWrite(RelayLightPin, HIGH); //Initially off. It's use as NC (Normally Close).
windowStartTime = 0;
myPID.SetOutputLimits(0, WindowSize);
myPID.SetMode(AUTOMATIC);
Serial.begin(115200);
ArduinoSerial.begin(9600);
}
/*--------------------------LOOP---------------------------------------*/
void loop() {
if (ArduinoSerial.available() > 0) {
cadena = ArduinoSerial.readString();
Start = cadena.charAt(6) - 48; //To convert its ASCII value to integer.
if (Start == 0) {
Serial.println("The Skill has not yet been called");
digitalWrite(RelayHeaterPin, HIGH);
digitalWrite(RelayLightPin, HIGH);
}
else if (Start == 1) {
Temperature = cadena.substring(20, 22).toInt();
Soap = cadena.charAt(28) - 48;
Rinse = cadena.charAt(36) - 48;
Finish = cadena.charAt(45) - 48;
Serial.print("Start:");
Serial.print(Start);
Serial.print(", Temp:");
Serial.print(Temperature);
Serial.print(", Soap:");
Serial.print(Soap);
Serial.print(", Rinse:");
Serial.print(Rinse);
Serial.print(", Finish:");
Serial.println(Finish);
}
}
else {
if (Start == 0) {
Serial.println("The Skill has not yet been called");
delay(5000);
}
else if (Start == 1) {
val = Soap + Rinse * 2 + Finish * 3;
unsigned long now1 = millis();
unsigned long now2 = millis();
unsigned long now3 = millis();
switch (val) {
/*---------------------CASE 0: START SHOWER------------------------------------------------------------*/
case 0:
Serial.print("CASE 0: START SHOWER \t");
myServo.write(180); //Open valve.
sensorDS18B20.requestTemperatures(); //Previous step to read temperature from DS18B20 sensor.
Input = sensorDS18B20.getTempCByIndex(0); //Temperature registered by sensor DS18B20.
Setpoint = Temperature; //Cloud's temperature set by user.
Serial.print("Input: ");
Serial.print(Input);
Serial.print("\t Setpoint: ");
Serial.println(Setpoint);
delay(1000);
if (abs(Setpoint - Input) < 1) {
digitalWrite(RelayLightPin, LOW); //Turn light on if difference between input and setpoint is less than 1C.
}
else {
digitalWrite(RelayLightPin, HIGH); //Turn light off if difference between input and setpoint is greater than 1C.
}
myPID.Compute(); //To calculate Output.
if (now1 - windowStartTime > WindowSize) {
windowStartTime += WindowSize;
}
if (Output > now1 - windowStartTime) {
digitalWrite(RelayHeaterPin, LOW);
Serial.print("Output: " );
Serial.print(Output);
Serial.print("\t Tiempo: ");
Serial.println(now1 - windowStartTime);
}
else {
digitalWrite(RelayHeaterPin, HIGH);
Serial.print("Output: " );
Serial.print(Output);
Serial.print("\t Tiempo: ");
Serial.println(now1 - windowStartTime);
}
break;
/*---------------------CASE 1: SOAP TIME------------------------------------------------------------*/
case 1:
Serial.println("CASE 1: SOAP TIME");
delay(1000);
myServo.write(0); //Close valve.
digitalWrite(RelayHeaterPin, HIGH); //Turn Off Heater's relay.
digitalWrite(RelayLightPin, HIGH); //Turn Off light.
break;
/*---------------------CASE 2: RINSE TIME 2------------------------------------------------------------*/
case 2:
Serial.print("CASE 2: RINSE TIME \t");
myServo.write(180); //Open valve.
sensorDS18B20.requestTemperatures(); //Previous step to read temperature from DS18B20 sensor.
Input = sensorDS18B20.getTempCByIndex(0); //Temperature registered by sensor DS18B20.
Setpoint = Temperature; //Cloud's temperature set by user.
Serial.print("Input: ");
Serial.print(Input);
Serial.print("\t Setpoint: ");
Serial.println(Setpoint);
delay(1000);
if (abs(Setpoint - Input) < 1) {
digitalWrite(RelayLightPin, LOW); //Turn light on if difference between input and setpoint is less than 1C.
}
else {
digitalWrite(RelayLightPin, HIGH); //Apagar foco si la dif. temp. es mayor o igual a 1C.
}
myPID.Compute(); //Calcular Output.
if (now2 - windowStartTime > WindowSize) {
windowStartTime += WindowSize;
}
if (Output > now2 - windowStartTime) {
digitalWrite(RelayHeaterPin, LOW);
Serial.print("Output: " );
Serial.print(Output);
Serial.print("\t Tiempo: ");
Serial.println(now2 - windowStartTime);
}
else {
digitalWrite(RelayHeaterPin, HIGH);
Serial.print("Output: " );
Serial.print(Output);
Serial.print("\t Tiempo: ");
Serial.println(now2 - windowStartTime);
}
break;
/*---------------------CASE 3: RINSE TIME 1------------------------------------------------------------*/
case 3:
Serial.print("CASE 2: RINSE TIME \t");
myServo.write(180); //Open valve.
sensorDS18B20.requestTemperatures(); //Paso previo para leer temperatura del sensor DS18B20.
Input = sensorDS18B20.getTempCByIndex(0); //Temperatura que registra el sensor DS18B20.
Setpoint = Temperature; //Temperatura de la nube.
Serial.print("Input: ");
Serial.print(Input);
Serial.print("\t Setpoint: ");
Serial.println(Setpoint);
delay(1000);
if (abs(Setpoint - Input) < 1) {
digitalWrite(RelayLightPin, LOW); //Prender foco si la dif. temp. es menor a 1C.
}
else {
digitalWrite(RelayLightPin, HIGH); //Turn light off if difference between input and setpoint is greater than 1C.
}
myPID.Compute(); //Calculate Output.
if (now3 - windowStartTime > WindowSize) {
windowStartTime += WindowSize;
}
if (Output > now3 - windowStartTime) {
digitalWrite(RelayHeaterPin, LOW);
Serial.print("Output: " );
Serial.print(Output);
Serial.print("\t Tiempo: ");
Serial.println(now3 - windowStartTime);
}
else {
digitalWrite(RelayHeaterPin, HIGH);
Serial.print("Output: " );
Serial.print(Output);
Serial.print("\t Tiempo: ");
Serial.println(now3 - windowStartTime);
}
break;
/*---------------------CASE 5: FINISH TIME------------------------------------------------------------*/
case 5:
Serial.println("CASE 5: FINISH TIME");
delay(5000);
myServo.write(0); //Close valve.
digitalWrite(RelayHeaterPin, HIGH); //Turn Off Heater's relay.
digitalWrite(RelayLightPin, HIGH); //Turn Off light.
break;
/*---------------------CASE 4: DEFAULT------------------------------------------------------------*/
default:
break;
}
if(Finish == 1){
Serial.println("CASE 5: FINISH TIME");
Start = 0;
Soap = 0;
Rinse = 0;
Finish = 0;
myServo.write(0);
digitalWrite(RelayHeaterPin, HIGH);
digitalWrite(RelayLightPin, HIGH);
}
}
}
}
AlexaCodeJavaScriptv1.3.js
JavaScript//CODIGO PARA ALEXA SMART SHOWER GADGET
var https = require('https');
var Showers = {
"very Hot" : {"RangeOfTemperatures" : "The temperature of this shower is between 38 and 43 celsius degrees"},
"hot" : {"RangeOfTemperatures" : "The temperature of this shower is between 29 and 37 celsius degrees"},
"warm" : {"RangeOfTemperatures" : "The temperature of this shower is between 24 and 28 celsius degrees"},
"fresh" : {"RangeOfTemperatures" : "The temperature of this shower is between 18 and 23 celsius degrees"},
"cold" : {"RangeOfTemperatures" : "The temperature of this shower is less than 17 celsius degrees"}
}
var Temperatures = [
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27",
"28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43"
]
exports.handler = (event, context) => {
try{
if(event.session.new) {console.log("New Session");}
switch(event.request.type) {
/*--------------------------LAUNCH REQUEST----------------------------------------------------------*/
case "LaunchRequest" :
var endpoint = "https://api.thingspeak.com/update?api_key=7AVYH203044IJG3T&field1=1"
https.get(endpoint, function (result){console.log('Success, with: ' + result.statusCode); context.succeed(generateResponse(buildSpeechletResponse("Welcome to the Smart Shower Gadget Skill, What kind of shower do you desire today? Cold, fresh, warm, hot or very hot?",false),{})) }).on('error', function (err){console.log('Error, with: ' + err.message); context.done("Failed")});
break;
/*--------------------------INTENT REQUEST----------------------------------------------------------*/
case "IntentRequest" :
console.log("IntentRequest")
switch(event.request.intent.name)
{
/*------Kind Of Shower Intent----------*/
case "Kind_Of_Shower_Intent" :
var shower = event.request.intent.slots.Kind_Of_Shower.value
if(!Showers[shower]){
var speechOutput = "Try asking about another kind of shower"
}
else {
var RangeOfTemperatures = Showers[shower].RangeOfTemperatures
var speechOutput = "You have selected a " + shower + " shower, " + RangeOfTemperatures + " Which temperature do you prefer today?"
}
context.succeed(generateResponse(buildSpeechletResponse(speechOutput,false),{}))
break;
/*------Temperature Intent----------*/
case "Temperature_Intent" :
var temperature = event.request.intent.slots.Temperature.value
var speechOutput = "Ok, " + temperature + " celsius degrees it's fine. You'll see a light turn on when the desired temperature is reached. Then let me know when you'll start soap time."
var endpoint = "https://api.thingspeak.com/update?api_key=7AVYH203044IJG3T&field2=" + temperature
https.get(endpoint, function (result){console.log('Success, with: ' + result.statusCode); context.succeed(generateResponse(buildSpeechletResponse(speechOutput,false),{})) }).on('error', function (err){console.log('Error, with: ' + err.message); context.done("Failed")});
break;
/*------Soap Time Intent----------*/
case "Soap_Time_Intent" :
var speechOutput = "Ok, let me know when you want to start the rinse time"
var endpoint = "https://api.thingspeak.com/update?api_key=7AVYH203044IJG3T&field3=1"
https.get(endpoint, function (result){console.log('Success, with: ' + result.statusCode); context.succeed(generateResponse(buildSpeechletResponse(speechOutput,false),{})) }).on('error', function (err){console.log('Error, with: ' + err.message); context.done("Failed")});
break;
/*------Rinse Time Intent----------*/
case "Rinse_Time_Intent" :
var speechOutput = "Ok, please wait, you'll see a light turn on when desired temperature is reached."
var endpoint = "https://api.thingspeak.com/update?api_key=7AVYH203044IJG3T&field4=1"
https.get(endpoint, function (result){console.log('Success, with: ' + result.statusCode); context.succeed(generateResponse(buildSpeechletResponse(speechOutput,false),{})) }).on('error', function (err){console.log('Error, with: ' + err.message); context.done("Failed")});
break;
/*------Finish Intent----------*/
case "Finish_Intent" :
var speechOutput = "Ok. Please take your towel. Good bye! Thank you for using Smart Shower Gadget! Have a nice day"
var endpoint = "https://api.thingspeak.com/update?api_key=7AVYH203044IJG3T&field5=1"
https.get(endpoint, function (result){console.log('Success, with: ' + result.statusCode); context.succeed(generateResponse(buildSpeechletResponse(speechOutput,false),{})) }).on('error', function (err){console.log('Error, with: ' + err.message); context.done("Failed")});
break;
/*------Default----------*/
default: throw "Invalid intent";
}
break;
/*--------------------------SESSION ENDED REQUEST----------------------------------------------------------*/
case "SessionEndedRequest" :
console.log("Session Ended Request")
break;
/*--------------------------DEFAULT----------------------------------------------------------*/
default : context.fail('INVALID REQUEST TYPE: ${event.request.type}')
}
} catch(error) { context.fail(`Exception: ${error}`) }
}
/*------------FUNCIONES AUXILIARES PARA AYUDAR A ALEXA A CONSTRUIR RESPUESTAS-------------------------------*/
//LA SINTAXIS DE RESPUESTA ESTA COMPUESTA POR VERSION, SESSION ATRIBUTES Y RESPONSE, ESTA FUNCION CREA RESPONSE
buildSpeechletResponse = (outputText, shouldEndSession) =>{
return{
outputSpeech : { type : "PlainText", text : outputText}, //EN ESTE CASO NO SE ESTAN CONSIDERANDO
shouldEndSession : shouldEndSession //LOS OTROS PARAMETROS DE RESPONSE CARD; REPROMPT Y DIRECTIVES
}
};
//ESTA FUNCION CREA VERSION; SESSION ATTRIBUTES E INCLUYE EL RESPONSE QUE YA SE CONSTRUY
generateResponse = (speechLetResponse, sessionAttributes) => {
return {
version : "1.0",
sessionAttributes : sessionAttributes,
response : speechLetResponse
}
}
Comments