Alvaro Martin
Created February 22, 2018

Smart Shower Gadget

Control water's temperature and water's flow to have a great shower just with your voice. This is the future!

128
Smart Shower Gadget

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
Waterproof temperature sensor DS18B20
×1
NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
NodeMCU V3 ESP-12E Module Lolin black.
×1
4-CHANNEL RELAY CONTROLLER FOR I2C
ControlEverything.com 4-CHANNEL RELAY CONTROLLER FOR I2C
×1
9V battery (generic)
9V battery (generic)
×1
9V Battery Clip
9V Battery Clip
×1
USB-A to Micro-USB Cable
USB-A to Micro-USB Cable
×1
USB-A to B Cable
USB-A to B Cable
×1
Light 220V 12W
×1
Old heater of an electric therma and its components to connect it to the water supply.
×1
1/2 inch ball valve
×1
Jumper wires (generic)
Jumper wires (generic)
×1

Software apps and online services

Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
AWS Lambda
Amazon Web Services AWS Lambda
ThingSpeak API
ThingSpeak API

Story

Read more

Schematics

Flow Diagram Part I

First part of code related to process through Arduino get information from cloud through serial communication with NodeMCU.

Flow Diagram Part II

Second part of code related to Arduino's temperature control and water's flow.

Flow Diagram Part III

Third part of code related to the process how NodeMCU get data from ThingSpeak's cloud.

Smart Shower Gadget Schematics

Smart shower gadget

This is my idea, to have an smart and ecologic shower. I couldn't finish it to this contest but It wil be succesfull later.

Code

SSG_NodeMCUVFinal.ino

Arduino
This is the code for the NodeMCU board, it's connected with arduino through Serial communication trough its pins D2 and D3, it's got the funcion to get data from ThingSpeak cloud and to send it via serial communication to arduino and the computer's screen.
//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
Code for Arduino to control through PID the water's temperature and the water's flow.
#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
This is the code of Lambda function, which let you set your shower's temperature and water's flow.
//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
			}
}

Credits

Alvaro Martin

Alvaro Martin

2 projects • 3 followers
I´m 25. Ï´m electrical engineer. I started learning arduino recently.

Comments