iot_smart_hedgehog_home

Introduction

We are four Georgia Tech students who built a smart home for a hedge hog. Credits:
Bryan Crumpton (Saul)
Alberto Li
Nicholas Stolfus
Michael Tatum

The iot smart hedgehog home exploits the benefits of Amazon Web Services (AWS) cloud technologies to bring control over a hedgehog's environment from the web to the pet owner from his/her convenience. The user will interact with the iot smart hedgehog system through a front end deployed from the cloud as a Python Flask server. The smart environment will allow monitoring features for the hedgehog to be driven by data collected from a range of sensors.

The main microcontroller is the Raspberry Pi while utilizing WIFI for networking. It will push sensor readings to the AWS endpoint in Python using the Boto3 library. It will also be the main interface for the camera. An MBED is used for the other sensors, and pushes readings to the PI as necessary.

Hardware

The hardware used for this project was:

  1. MBED LPC1768
  2. Raspberry Pi Zero W
  3. Raspberry Pi Camera Board v2
  4. TMP36 temperature sensor
  5. Class D amplifier
  6. Speaker (in MBED kit)
  7. SD Card
  8. Hall Effect Sensor
  9. Servo

Pin Assignment

PinSensor
P15Thermometer
P18D amp +
P21Servo controller
P11Hall Effect Sensor
P5SD Di
P6SD Do
P7SD SCK
P8SD CS

Basic Architecture Model

/media/uploads/albertoli/architecture_model.png

Features

Features include:

AWSRaspberry PiMBED
SQSPythonC++
S3 BucketBoto3 APISerial Comm
DynamoDBSubprocessMultithreaded
EC2Parallel ProcessesRead after Write Consistency
Flask APISerial CommTemperature Sensor, Hall Effect Sensor, Speaker, Servo motor, SD Card

/media/uploads/albertoli/features_model2.png

Smart Hedgehog Home Setup

/media/uploads/albertoli/mbedsetup2.jpg /media/uploads/albertoli/fooddispenser.jpg /media/uploads/albertoli/smarthomesetup.jpg

MBED Source Code

Multithreaded C++ code that interfaces with I/O hardware: Speaker, SD Card, Hall Effect Sensor, Servo, Temperature Sensor

Import programiot_smart_hedgehog_home

iot_smart_hedgehog_home

#include "mbed.h"
#include "Servo.h"
#include "rtos.h"
#include "SDFileSystem.h"
#include "wave_player.h"
RawSerial  pi(USBTX, USBRX);
Mutex serial_mutex;

Servo myservo(p21);
AnalogIn LM61(p15);

SDFileSystem sd(p5, p6, p7, p8, "sd"); // the pinout on the mbed Cool Components workshop board
AnalogOut DACout(p18);
//On Board Speaker
//PwmOut PWMout(p25);
wave_player waver(&DACout);

volatile float temp_out;
volatile float rpm_out;
volatile float wheel_speed_out;
volatile int treat = 0;
volatile int music = 0;
volatile bool x;

DigitalOut myled1(LED1);
DigitalOut myled2(LED2);
DigitalOut myled3(LED3);

volatile long int count;

Serial pc(USBTX, USBRX);
Timer t;
InterruptIn risingEdge(p11);

void dev_recv()
{   while(1){
        char temp = 0;
        while(pi.readable()) {
            myled3 = !myled3;
            serial_mutex.lock();
            temp = pi.getc();
            if (temp=='t'){
                myled2=1;
                treat=1;
            }
            if (temp=='m'){
                myled1=1;
                music=1;
            }
            serial_mutex.unlock();
        }
    }
}

void check_temp() {
    float tempC, tempF;
    while (1) {
        //conversion to degrees C - from sensor output voltage per LM61 data sheet
        tempC = ((LM61*3.3) - 0.600)*100.0;
        //convert to degrees F
        tempF = (9.0*tempC) / 5.0 + 32.0;
        temp_out = tempF + 16.7;
        //print current temp
        Thread::wait(500);
    }
}

void deliver_snack()
{   while(1){
        if (treat == 1) {
            myservo = 1; //closed position
            Thread::wait(1000);
            myservo = .7; //open position
            Thread::wait(200); // open for .2 secs delivers half a small tupperware
            myservo = 1;
            Thread::wait(500);
            treat = 0;
            myled2=0;
        }
        Thread::yield();
    }
}




char* getOut()
{
    char output[22];

    snprintf(output, 22, "%3.1f, %4.0f, %1.5f \n", temp_out, rpm_out, wheel_speed_out);
    return output;
}

void send_data() {
    while(1)
    {
        serial_mutex.lock();
        pi.puts(getOut());
        serial_mutex.unlock();
        Thread::wait(5000);
        
    }
}

void pulses() {
    count++;
}

void check_wheel() {
    double rpm = 0;
    double speed = 0;
    double circumference = 0.266 * 3.1416; // 26. cm wheel diameter * pi 
    risingEdge.rise(&pulses);
    long int temp = count;
    while (1) {
        count = 0;
        t.reset();
        t.start();
        while (t.read_ms() < 2001) {
            ;
        }
        t.stop();
        temp=count;
        double rev = (double)temp;
        double rpm = rev * 30;
        double speed = circumference * rev;
        rpm_out = (float)rpm;
        wheel_speed_out = (float)speed;
    }
}


int main() {
    //printf("Hello, in Main");
    Thread t1(check_temp);
    Thread t2(send_data);
    Thread t3(check_wheel);
    Thread t4(dev_recv);
    Thread t5(deliver_snack);

    while (1) {
        if (music == 1) {
            FILE *wave_file = fopen("/sd/wavfiles/crickets.wav", "r");
            waver.play(wave_file);
            fclose(wave_file);
            Thread::wait(1000);
            music = 0;
            myled2=0;
        }
    }
}

Raspberry Pi Source Code

Two separate Python Processes are ran in parallel to:

  1. Handle taking images of hedgehogs and uploading to AWS S3 Bucket for front-end to consume
  2. Handle Raspberry Pi Communications between Cloud and MBED

Raspberry Pi Communication to MBED and Cloud

Python process on Raspberry Pi that:

  1. Receives sensor data from Mbed, packages it up in JSON format and writes to AWS DynamoDB table
  2. Receives RPC command from front-end through AWS SQS queue and sends commands through serial to Mbed

import time
import serial
import boto3
from credentials import AWS_KEY, AWS_SECRET, REGION
from flask import json
import datetime
from decimal import *

#Get the queue
sqs = boto3.resource('sqs', aws_access_key_id=AWS_KEY,
                            aws_secret_access_key=AWS_SECRET,
                            region_name=REGION)
queue = sqs.get_queue_by_name(QueueName='PiQueue')
# Get the table
dynamodb = boto3.resource('dynamodb', aws_access_key_id=AWS_KEY,
                            aws_secret_access_key=AWS_SECRET,
                            region_name=REGION)
table = dynamodb.Table('SensorData') #Load Table

out= ""                            
# configure the serial connections (the parameters differs on the device you are connecting to)
# Get the queue
ser = serial.Serial(
    port='/dev/ttyACM0',
    baudrate=9600
)

ser.isOpen()

# print 'Enter your commands below.\r\nInsert "exit" to leave the application.'
print 'Starting pi application...'

input=1
while 1 :
    for message in queue.receive_messages(MaxNumberOfMessages=1):
        data = message.body
        print message.body

        if (data[2:7] == "Music"):
            print("sending m on serial \n")
            ser.write("m")
        elif (data[2:7] == "Snack"):
            print("sending t on serial \n")
            ser.write("t")
        else:
            print("input is NONE \n")
            input = None

        message.delete()
        time.sleep(0.7)
    input=None
    if input == None:
            while ser.inWaiting() > 0:
                out += ser.read(size=21)

            if out != '':
                print ">>" + out
                temp,rpm,speed = out.split(",")
                if temp != 0:
                    ts=time.time()
                    timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
                    table.put_item(
                       Item={
                                "Timestamp": timestamp,
                                "Temperature": Decimal(temp),
                                "Speed": Decimal(speed),
                                "RPM": Decimal(rpm)
                            }
                        )
                    print "Pushed {Timestamp: " + timestamp + " Temperature: " + temp + " Speed: " + speed + " RPM: " + rpm + "} to Dynamodb"
                out=''

Raspberry Pi Camera in Python

Python process on Raspberry Pi to upload hedgehog picture to AWS S3 Bucket every 5 Seconds.

import boto3
import time
from credentials import AWS_KEY, AWS_SECRET, REGION, BUCKET
import subprocess

s3 = boto3.client('s3', aws_access_key_id=AWS_KEY,
                            aws_secret_access_key=AWS_SECRET)
                            
while(True):
    subprocess.call(["raspistill", "-o","pygmy_hedgehogs_test.jpg"])
    filenameWithPath = "./pygmy_hedgehogs_test.jpg"                            
    path_filename='pygmy_hedgehogs_test.jpg'

    s3.upload_file(filenameWithPath, BUCKET, path_filename)
    s3.put_object_acl(ACL='public-read', Bucket=BUCKET, Key=path_filename)
    time.sleep(5)

500

Cloud Source Code on EC2: Python Flask and HTML/CSS/Javascript

This section includes the source code used on the AWS EC2 server to deploy the Flask Application.

Flask App

#!/usr/bin/env python
import flask
from flask import request, render_template, jsonify
import boto3
from boto3.dynamodb.conditions import Key, Attr
import cPickle as pickle
import datetime
import time
import json
import sys
import os
import logging
import logging.handlers
from credentials import AWS_KEY, AWS_SECRET, REGION


dynamodb = boto3.resource('dynamodb', aws_access_key_id=AWS_KEY,
                            aws_secret_access_key=AWS_SECRET,
                            region_name=REGION)

table = dynamodb.Table('SensorData')

sqs = boto3.resource('sqs', aws_access_key_id=AWS_KEY,
                            aws_secret_access_key=AWS_SECRET,
                            region_name=REGION)
queue = sqs.get_queue_by_name(QueueName='PiQueue')

APP = flask.Flask(__name__)
statisticstimestamp=""
averages={"temperature":0,"speed":0,'rpm':0}
averagecount=0
runningaverages={"temperature":0,"speed":0,"rpm":0}

def publish(data):
    queue.send_message(MessageBody=json.dumps(data))

@APP.route('/data')
def get_Data():
    global statisticstimestamp
    global averages
    global averagecount
    global runningaverages

    if(statisticstimestamp==""):
        statisticstimestamp=time.time()
    try:
        data = {}
        ts=time.time()
        
        timestampold = datetime.datetime.fromtimestamp(ts-10).strftime('%Y-%m-%d %H:%M:%S')
        timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
        
        response = table.scan(FilterExpression=Key('Timestamp').between(timestampold, timestamp))
        if(statisticstimestamp<(time.time()-60)):
            averages["temperature"]=float(runningaverages["temperature"])/averagecount
            runningaverages["temperature"]=0
            averages["speed"]=float(runningaverages["speed"])/averagecount
            runningaverages["speed"]=0
            averages["rpm"]=float(runningaverages["rpm"])/averagecount
            runningaverages["rpm"]=0
            statisticstimestamp=time.time()
            averagecount=0
            print(averages)
        items = response['Items']
        if len(items) > 0:
            averagecount+=1
            #print(str(items[0]["Speed"]) + "\n")
            runningaverages["speed"]+=int(items[0]["Speed"])
            runningaverages["temperature"]+=int(items[0]["Temperature"])
            runningaverages["rpm"]+=int(items[0]["RPM"])
            data["Timestamp"] = str(items[0]["Timestamp"])
            data["temperature"] = int(items[0]["Temperature"])
            data["speed"] = int(items[0]["Speed"])
            data["rpm"] = float(items[0]["RPM"])
            data["temperatureavg"] = float(averages["temperature"])
            data["speedaverage"] = float(averages["speed"])
            data["rpmaverage"] = float(averages["rpm"])
            print(data)
            return jsonify(json.dumps(data))

        else:
            return jsonify({'temperature': 0, 'speed': 0,'rpm':0})


    except Exception as err:
        print("Unexpected error:", err)
        pass
            
    return jsonify({'temperature': 0, 'speed': 0,'rpm':0})

@APP.route('/', methods=['GET'])
def home_page():
    return render_template('dashboard.html',avs=averages)

@APP.route('/musicsubmission', methods=['GET'])
def musicsubmission_page():
    data={"Music":True}
    publish(data)
    return render_template('musicsubmission.html')

@APP.route('/snacksubmission', methods=['GET'])
def snacksubmission_page():
    data={"Snack":True}
    publish(data)
    return render_template('snacksubmission.html')

if __name__ == '__main__':
    APP.debug=True
    APP.run(host='0.0.0.0', port=5000)

Dashboard.html

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Hedgehog Smart Home</title>
        <meta name="viewport" content="width=device-width">
        <style>
            .wrapper {
            position: relative;
            width: 640px;
            height: 480px;
            margin: 50px auto 0 auto;
            padding-bottom: 30px;
            border: 1px solid #ccc;
            border-radius: 3px;
            clear: both;
            }
            .box {
            float: left;
            width: 50%;
            height: 50%;
            box-sizing: border-box;
            }
            .container {
            width: 450px;
            margin: 0 auto;
            text-align: center;
            }
            .gauge {
            width: 320px;
            height: 240px;
            }
            button {
            margin: 30px 5px 0 2px;
            padding: 16px 40px;
            border-radius: 5px;
            font-size: 18px;
            border: none;
            background: #34aadc;
            color: white;
            cursor: pointer;
            }
        </style>
        <script src="{{ url_for('static', filename='jquery.min.js') }}"></script>
        <script>
            window.onload = function() {
                var image = document.getElementById("img");

                function updateImage() {
                    image.src = image.src.split("?")[0] + "?" + new Date().getTime();
                }

                setInterval(updateImage, 5000);
            }
        </script>
    </head>
    <body>
        <center>
            <h1>Dashboard</h1>
        </center>
        
        <div class="wrapper">
          <center>
                <img id="img" src="https://s3.us-east-2.amazonaws.com/iot-smart-hedgehog-home-bucket/pygmy_hedgehogs_test.jpg" alt="Hedgehog" style="width:300px;height:200px">
            </center>
            <center>
                <form action="/musicsubmission" method="get">
                  <button type="submit" id="btnsubmit" class="btn btn-default">Play a sound for the hedgehogs</button>
                </form>
                <form action="/snacksubmission" method="get">
                  <button type="submit" id="btnsubmit" class="btn btn-default">Give the hedgehogs a snack</button>
                </form>
            <center>
        </div>
       <div class="wrapper">
            <div class="box">
                <div id="g1" class="gauge"></div>
                <center>Current Temperature (F)</center>
            </div>
            <div class="box">
                <div id="g2" class="gauge"></div>
                <center>Current Speed (m/s)</center>
            </div>
            <div class="box">
                <div id="g3" class="gauge"></div>
                <center>Current RPM </center>
            </div>
        </div> 
        <div class="wrapper">
            <div class="box">
                <div id="g4" class="gauge"></div>
                <center>1 min avg Temperature (F)</center>
            </div>
            <div class="box">
                <div id="g5" class="gauge"></div>
                <center>1 min avg Speed (m/s)</center>
            </div>
            <div class="box">
                <div id="g6" class="gauge"></div>
                <center>1 min avg RPM </center>
            </div>
        </div> 
        <script src="{{ url_for('static', filename='raphael-2.1.4.min.js') }}"></script>
        <script src="{{ url_for('static', filename='justgage.js') }}"></script>
        <script>
            document.addEventListener("DOMContentLoaded", function(event) {
            
              var g1 = new JustGage({
                id: 'g1',
                value: 0,
                min: 0,
                max: 100,
                pointer: true,
                pointerOptions: {
                  toplength: -15,
                  bottomlength: 10,
                  bottomwidth: 12,
                  color: '#8e8e93',
                  stroke: '#ffffff',
                  stroke_width: 3,
                  stroke_linecap: 'round'
                },
                gaugeWidthScale: 0.6,
                counter: true
              });

              var g2 = new JustGage({
                id: 'g2',
                value: 0,
                min: 0,
                max: 3,
                pointer: true,
                pointerOptions: {
                  toplength: -15,
                  bottomlength: 10,
                  bottomwidth: 12,
                  color: '#8e8e93',
                  stroke: '#ffffff',
                  stroke_width: 3,
                  stroke_linecap: 'round'
                },
                gaugeWidthScale: 0.6,
                decimals: true,
                counter: true
              });
            
            var g3 = new JustGage({
                id: 'g3',
                value: 0,
                min: 0,
                max: 210,
                pointer: true,
                pointerOptions: {
                  toplength: -15,
                  bottomlength: 10,
                  bottomwidth: 12,
                  color: '#8e8e93',
                  stroke: '#ffffff',
                  stroke_width: 3,
                  stroke_linecap: 'round'
                },
                gaugeWidthScale: 0.6,
                counter: true
              });
            var g4 = new JustGage({
                id: 'g4',
                value: 0,
                min: 0,
                max: 100,
                pointer: true,
                pointerOptions: {
                  toplength: -15,
                  bottomlength: 10,
                  bottomwidth: 12,
                  color: '#8e8e93',
                  stroke: '#ffffff',
                  stroke_width: 3,
                  stroke_linecap: 'round'
                },
                gaugeWidthScale: 0.6,
                decimals: true,
                counter: true
              });

              var g5 = new JustGage({
                id: 'g5',
                value: 0,
                min: 0,
                max: 3,
                pointer: true,
                decimals: true,
                pointerOptions: {
                  toplength: -15,
                  bottomlength: 10,
                  bottomwidth: 12,
                  color: '#8e8e93',
                  stroke: '#ffffff',
                  stroke_width: 3,
                  stroke_linecap: 'round'
                },
                gaugeWidthScale: 0.6,
                counter: true
              });
            
            var g6 = new JustGage({
                id: 'g6',
                value: 0,
                min: 0,
                max: 210,
                pointer: true,
                pointerOptions: {
                  toplength: -15,
                  bottomlength: 10,
                  bottomwidth: 12,
                  color: '#8e8e93',
                  stroke: '#ffffff',
                  stroke_width: 3,
                  stroke_linecap: 'round'
                },
                gaugeWidthScale: 0.6,
                decimals: true,
                counter: true
              });
             
            setInterval(                                
            function()
            {
            $.getJSON('/data', {}, function(data) {
               data=JSON.parse(data);
               console.log(data);
               g1.refresh(data.temperature);
               g2.refresh(data.speed);
               g3.refresh(data.rpm);
               g4.refresh(data.temperatureavg);
               g5.refresh(data.speedaverage);
               g6.refresh(data.rpmaverage);
              });
              },
            1000); 
                     
            });
            
     
        </script>
        


    </body>
</html>

musicsubmission.html

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Hedgehog Smart Home</title>
        <meta name="viewport" content="width=device-width">
        <script src="{{ url_for('static', filename='jquery.min.js') }}"></script>
    </head>
    <body>
        <embed src="{{ url_for('static', filename='crickets.wav') }}" hidden="true" autostart="true" loop="1">
        <center>
            <h1>Thank you for your button click!</h1>
            <h1>Natural sounds will begin playing for our hedgies soon.</h1>
            <h2>This will automatically redirect in 3 seconds. </h2>
            <h2><a href="/">If not, Click Here</a></h2>
        </center>
        <script>
        var timer = setTimeout(function() {
            window.location='/'
        }, 3000);
    </script>
    </body>
</html>

snacksubmission.html

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>Hedgehog Smart Home</title>
        <meta name="viewport" content="width=device-width">
        <script src="{{ url_for('static', filename='jquery.min.js') }}"></script>
    </head>
    <body>
        <center>
            <h1>Thank you for your button click!</h1>
            <h1>The hedgehogs will now recieve a snack</h1>
            <h2>This will automatically redirect in 3 seconds. </h2>
            <h2><a href="/">If not, Click Here</a></h2>
        </center>
        <script>
        var timer = setTimeout(function() {
            window.location='/'
        }, 3000);
    </script>
    </body>
</html>

General Demo Video

Supplemental Pi Camera Demo


Please log in to post comments.