J Howard
Published © CC BY-NC-SA

Hygge Home - Alexa Smart Bath

Using Alexa and Arduino to instil a sense of Hygge upon your arrival home. Fully functioning Alexa controlled smart bath.

IntermediateFull instructions provided12 hours9,456

Things used in this project

Hardware components

Arduino Yun Mini
Arduino Yun Mini
×1
Echo Dot
Amazon Alexa Echo Dot
×1
Jumper wires (generic)
Jumper wires (generic)
×1
1/2 Inch 12V Electric Solenoid Valve for Water
×2
Flexible Tap Connector 15mm x ½" x 300mm
×2
Equal Tee 15mm x 15mm x 15mm
×4
Male Coupler 15mm x ½"
×4
Female Coupler 15mm x ½"
×8
15mm Copper Pipe
×1
3 Hole Bath Faucet Tap
×1
SparkFun Snappable Protoboard
SparkFun Snappable Protoboard
×1
Resistor 10k ohm
Resistor 10k ohm
×2
mosfet
×2

Software apps and online services

Alexa Skills Kit
Amazon Alexa Alexa Skills Kit
AWS Lambda
Amazon Web Services AWS Lambda
AWS IoT
Amazon Web Services AWS IoT
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Pipe Cutter
Soldering iron (generic)
Soldering iron (generic)
Pliers
Adjustable Wrench

Story

Read more

Schematics

Circuit

Code

Arduino Code

C/C++
/*
 * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

// Bath Timers
#define hotThreshold 20 //How long hot tap will run
#define coldThreshold 10 //How long cold tap will run
// Bath Pins
#define hotPin 3  //Pin for hot pipe solenoid
#define coldPin 6 //Pin for cold pipe solenoid
#define button 8  //Pin for button input



#include <aws_iot_mqtt.h>
#include <aws_iot_version.h>
#include "aws_iot_config.h"
int hotTimer = 0;
int coldTimer = 0;
int bathState = 0;
int buttonState = 1;
String bath = "off";

aws_iot_mqtt_client myClient;
char JSON_buf[100];
int cnt = 0;
int rc = 1;
bool success_connect = false;

bool print_log(const char* src, int code) {
  bool ret = true;
  if(code == 0) {
    #ifdef AWS_IOT_DEBUG
      Serial.print(F("[LOG] command: "));
      Serial.print(src);
      Serial.println(F(" completed."));
    #endif
    ret = true;
  }
  else {
    #ifdef AWS_IOT_DEBUG
      Serial.print(F("[ERR] command: "));
      Serial.print(src);
      Serial.print(F(" code: "));
     Serial.println(code);
    #endif
    ret = false;
  }
  Serial.flush();
  return ret;
}

void msg_callback_delta(char* src, unsigned int len, Message_status_t flag) {
  if(flag == STATUS_NORMAL) {
    // Get delta for light key
    print_log("getDeltaKeyValue", myClient.getDeltaValueByKey(src, "light", JSON_buf, 100));
    Serial.println(JSON_buf);
    if (strcmp(JSON_buf, "on") == 0)
        bath = "on";
    else
        bath = "off";
        
    String payload = "{\"state\":{\"reported\":";
    payload += "{\"light\":\"";
    payload += JSON_buf;
    payload += "\"}}}";
    payload.toCharArray(JSON_buf, 100);
    Serial.println(JSON_buf);
    print_log("update thing shadow", myClient.shadow_update(AWS_IOT_MY_THING_NAME, JSON_buf, strlen(JSON_buf), NULL, 5));
  }
}


void setup() {
  pinMode (hotPin, OUTPUT);
  pinMode (coldPin, OUTPUT);
  pinMode (button, INPUT_PULLUP);
  Serial.begin(115200);
  while(!Serial);

  char curr_version[80];
  snprintf_P(curr_version, 80, PSTR("AWS IoT SDK Version(dev) %d.%d.%d-%s\n"), VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_TAG);
  Serial.println(curr_version);

  if(print_log("setup", myClient.setup(AWS_IOT_CLIENT_ID))) {
    if(print_log("config", myClient.config(AWS_IOT_MQTT_HOST, AWS_IOT_MQTT_PORT, AWS_IOT_ROOT_CA_PATH, AWS_IOT_PRIVATE_KEY_PATH, AWS_IOT_CERTIFICATE_PATH))) {
      if(print_log("connect", myClient.connect())) {
        success_connect = true;
        print_log("shadow init", myClient.shadow_init(AWS_IOT_MY_THING_NAME));
        print_log("register thing shadow delta function", myClient.shadow_register_delta_func(AWS_IOT_MY_THING_NAME, msg_callback_delta));
      }
    }
  }
}

void loop() {
// Bath Control
 Serial.print (bath);
 Serial.println ();
 if (bath ==  "on") {
  while (hotTimer < hotThreshold && bath == "on") { 
  hotTimer ++;
  digitalWrite (hotPin,HIGH);
  delay (800);
    if(myClient.yield()) {
    Serial.println("Yield failed.");
  }
  Serial.print (hotTimer);
  Serial.println ();
 }
 digitalWrite (hotPin, LOW);
  while (coldTimer < coldThreshold && bath == "on" && bathState == 0) { 
  coldTimer ++;
  digitalWrite (coldPin,HIGH);
  delay (800);
    if(myClient.yield()) {
    Serial.println("Yield failed.");
  }
  Serial.print (coldTimer);
  Serial.println ();
 }
 bathState = 1;       //Turn off bath and enter reset loop
 bath = "off";
 digitalWrite (hotPin, LOW);
 digitalWrite (coldPin, LOW);
 }

 if (bath == "off") {
  delay (800);
      if(myClient.yield()) {
    Serial.println("Yield failed.");
  }
 }

 if (bathState == 1) {      //Bath is full, Wait for reset button push
  while (buttonState == 1) {
    buttonState = digitalRead (button);
    delay (10);
  }
  bathState = 0;
  buttonState = 1;
    delay (800);
    if(myClient.yield()) {
    Serial.println("Yield failed.");
  }
 }
 delay (1000);
}

AWS Lambda Code

Python
import logging
import time
import json
import uuid
import datetime

import boto3


client = boto3.client('iot-data')

def get_utc_timestamp(seconds=None):
    return time.strftime("%Y-%m-%dT%H:%M:%S.00Z", time.gmtime(seconds))

def bath_state(state):
    # Change topic, qos and payload
    response = client.update_thing_shadow(
        thingName = "smart-bath",  #YOUR THING NAME HERE
        payload = json.dumps({
            'state': {
                'desired': {
                    'bath': state
                }
            }
            }
            )
        )

def get_uuid():
    return str(uuid.uuid4())
    
# Setup logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    logger.info("Received v3 directive!")

    logger.info(json.dumps(event))
    
    if event['directive']['header']['name'] == "Discover":
        logger.info("Discover")
        response = handle_discovery(event)
    else:
        logger.info("Control")
        response = handle_control(event, context)    

    logger.info("response" + json.dumps(response))  
    return response
 

def handle_discovery(event):

        response = {
            "event":
                {
                    "header":
                    {
                        "correlationToken": "12345692749237492",
                        "namespace": "Alexa.Discovery",
                        "name": "Discover.Response",
                        "payloadVersion": "3",
                        "messageId": event['directive']['header']['messageId']
                    },
            "payload":
                {
                "endpoints":[
                        {
                          "endpointId": "smart-bath", 
                          "endpoint": "acws3fpjvf7o5.iot.eu-west-1.amazonaws.com", #AWS IOT THING ENDPOINT HERE
                          "manufacturerName": "JHoward",
                          "friendlyName": "Smart Bath",
                          "description": "The smart bath",
                          "displayCategories": ["SWITCH"],
                          "cookie": {
                            "key1": "arbitrary key/value pairs for skill to reference this endpoint.",
                          },
                          "capabilities": [
                            {
                              "type": "AlexaInterface",
                              "interface": "Alexa",
                              "version": "3"
                            },
                            {
                              "interface": "Alexa.PowerController",
                              "version": "3",
                              "type": "AlexaInterface",
                              "properties": {
                                "supported": [
                                  {
                                    "name": "powerState"
                                  }
                                ],
                                "proactivelyReported": True,
                                "retrievable": True
                              }
                            }
                        ]
                    },
                ]
            }
        }  
        }
        logger.info("Response: " +json.dumps(response))
        return response
 

def handle_control(request, context):
    request_namespace = request["directive"]["header"]["namespace"]
    request_name = request["directive"]["header"]["name"]
    
    if request_namespace == "Alexa.PowerController":
        if request_name == "TurnOn":
            bath_state("on")
            value = "ON"
        else:
            bath_state("off")
            value = "OFF"
            
            
        response = {
            
            "context": {
                "properties": [
                    {
                        "namespace": "Alexa.PowerController",
                        "name": "powerState",
                        "value": value,
                        "timeOfSample": get_utc_timestamp(),
                        "uncertaintyInMilliseconds": 500
                    }
                ]
            },
            "event": {
                "header": {
                    "namespace": "Alexa",
                    "name": "Response",
                    "payloadVersion": "3",
                    "messageId": get_uuid(),
                    "correlationToken": request["directive"]["header"]["correlationToken"]
                },
                "endpoint": {
                    "scope": {
                        "type": "BearerToken",
                        "token": "access-token-from-Amazon"
                    },
                    "endpointId": request["directive"]["endpoint"]["endpointId"]
                    
                },
                "payload": {}
            }
            
        }
        
        
        return response

Credits

J Howard

J Howard

10 projects • 6 followers

Comments