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:
- MBED LPC1768
- Raspberry Pi Zero W
- Raspberry Pi Camera Board v2
- TMP36 temperature sensor
- Class D amplifier
- Speaker (in MBED kit)
- SD Card
- Hall Effect Sensor
- Servo
Pin Assignment
Pin | Sensor |
---|---|
P15 | Thermometer |
P18 | D amp + |
P21 | Servo controller |
P11 | Hall Effect Sensor |
P5 | SD Di |
P6 | SD Do |
P7 | SD SCK |
P8 | SD CS |
Basic Architecture Model
Features
Features include:
AWS | Raspberry Pi | MBED |
---|---|---|
SQS | Python | C++ |
S3 Bucket | Boto3 API | Serial Comm |
DynamoDB | Subprocess | Multithreaded |
EC2 | Parallel Processes | Read after Write Consistency |
Flask API | Serial Comm | Temperature Sensor, Hall Effect Sensor, Speaker, Servo motor, SD Card |
Smart Hedgehog Home Setup
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:
- Handle taking images of hedgehogs and uploading to AWS S3 Bucket for front-end to consume
- Handle Raspberry Pi Communications between Cloud and MBED
Raspberry Pi Communication to MBED and Cloud
Python process on Raspberry Pi that:
- Receives sensor data from Mbed, packages it up in JSON format and writes to AWS DynamoDB table
- 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)
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.