Laundry Room Manager

Team Members : Morgan Clark , Madison Hester, Peter Loiacono, Shan Suen


Description

The idea for our design project was to create an embedded system that serves as a laundry room manager using the capabilities of the Mbed and Raspberry Pi 0 W. This system updates users when certain washers and dryers are available for use. The final design detects if the machines are running, sends this data to an Amazon Web Service server, and displays this data on a website accessible to a user using a flask app. We use multiple "slave" Mbed systems to track whether certain machines are in use by detecting the movement of the machine, which usually signifies that it is on. A Raspberry Pi will be used to process the received data from all of the connected Mbed devices and sends it to a server.

Goals
  • Build 2 slave Mbed systems with accelerometer and Bluetooth module
    • Detect if washer and dryer are on
  • Master controller Raspberry Pi 0 W which captures results and posts to AWS server
  • Flask app website displays data from the server

System Setup

/media/uploads/madi98hes/img1121278200140008569.jpg

AWS Server Access

For the Raspberry Pi code, you will need to set up an AWS account. Once you have set up your account and can create credentials, you will need to set up a Dynamodb Table to store data from the Raspberry Pi and an EC2 instance to run the flask application on. This flask application will retrieve the sensor data from the table to be displayed by consumers on a public facing URL. You can sign up for access at:

Software Packages to Download

Hardware Components

  • Mbed
  • Raspberry Pi Zero W
  • Adafruit Blufruit Bluetooth Module
  • SparkFun Triple Axis Accelerometer Breakout - MMA8452Q

Wiring

Accelerometer

AccelerometerMbed
gndgnd
3.3VVout (3.3V)
SDAP9
SCLP10

Bluetooth Module

BluetoothMbed
gndgnd
VinVout (3.3V)
TXOP28
RXIP27

Code

Mbed Code

mbedLaundryManager.cpp

#include "mbed.h"
#include "MMA8452.h"
#include "math.h"
#include "rtos.h"

Serial pc(USBTX,USBRX);
RawSerial  blue(p28,p27);
DigitalOut led1(LED1);
DigitalOut led4(LED4);
double x, y, z;
 
MMA8452 acc(p9, p10, 100000);

double mag = 0;
char machineOn = '0';
int onCounter = 0, offCounter = 0;
float initial[3];
float acceleration[3];
char needStatus = '0';

void bluetoothThread(void const *args) {
    while (1) {
        needStatus = blue.getc();
        //pc.printf("%c\n", needStatus);
        switch (needStatus) {
            case 'n':
                blue.puts("W");
                break;
            case 's':
                blue.putc(machineOn);
                break;
        }
        Thread::wait(1500);
    }   
}

int main() {
    for (int i = 0; i < 257; i++) {
        acc.readXYZGravity(&x, &y, &z);
        initial[0] = initial[0] + (x);
        initial[1] = initial[1] + (y);
        initial[2] = initial[2] + (z);
    }
    
    initial[0] = initial[0]/256;
    initial[1] = initial[1]/256;
    initial[2] = initial[2]/256;
    //pc.printf("%f %f %f\n", initial[0], initial[1], initial[2]);
    
    blue.baud(9600);
    pc.baud(9600);
    Thread bthread(bluetoothThread);
    
    while(1) {
        acc.readXYZGravity(&x, &y, &z);
        acceleration[0] = (x) - initial[0];
        acceleration[1] = (y) - initial[1];
        acceleration[2] = (z) - initial[2];
        //pc.printf("%f %f %f\n",acceleration[0], acceleration[1], acceleration[2]);
        if (abs(acceleration[0]) >= 0.2 || abs(acceleration[1]) >= 0.2 || abs(acceleration[2]) >= 0.2) {
            onCounter++;
            offCounter = 0;
        } 
        else {
            offCounter++;
            onCounter = 0;
        }
        if (onCounter >= 5) {
            machineOn = '1';
            led1 = 1;
            onCounter = 0;
            offCounter = 0;
        }
        if (offCounter >= 5) {
            led1 = 0;
            machineOn = '0';
            onCounter = 0;
            offCounter = 0;
        }
        wait(5);
    }
}

Pi Code

laundryManager.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  laundryManager.py
#  
#  Copyright 2019  <pi@raspberrypi>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  
#  
# Used pieces of code from the following author. He provided these examples with the Adafruit Bluetooth BLE library.
# Example of interaction with a BLE UART device using a UART service
# implementation.
# Author: Tony DiCola

# Search for BLE UART devices and list all that are found.
# Author: Tony DiCola

import Adafruit_BluefruitLE
import boto3
import datetime
import json
import time
import signal
import sys
from Adafruit_BluefruitLE.services import UART

AWS_KEY="abc123" #Need to set up your own AWS server to get the key, secret, and region
AWS_SECRET="password456"
REGION="America"

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

table = dynamodb.Table('4180_Sensor_Data')

# Publishes data to AWS dynamoDB table
def publish(data):
    table.put_item(
        Item={
                "Timestamp": data['Timestamp'],
                "ID": data['ID'],
                "Status": data['Status']
            }
        )
'''
def signal_handler(sig, frame, device):
    print("Exiting the program...")
    sys.exit(0)
'''    
      
# Get the BLE provider for the current platform.
ble = Adafruit_BluefruitLE.get_provider()


# Main function implements the program logic so it can run in a background
# thread.  Most platforms require the main thread to handle GUI events and other
# asyncronous events like BLE actions.  All of the threading logic is taken care
# of automatically though and you just need to provide a main function that uses
# the BLE provider.
def main():
    # Clear any cached data because both bluez and CoreBluetooth have issues with
    # caching data and it going stale.
    ble.clear_cached_data()

    # Get the first available BLE network adapter and make sure it's powered on.
    adapter = ble.get_default_adapter()
    adapter.power_on()
    print('Using adapter: {0}'.format(adapter.name))

    # Disconnect any currently connected UART devices.  Good for cleaning up and
    # starting from a fresh state.
    print('Disconnecting any connected UART devices...')
    UART.disconnect_devices()
    time.sleep(30)

    # Scan for UART devices.
    print('Searching for UART device...')
    try:
        adapter.start_scan()
        # Search for the first UART device found (will time out after 60 seconds
        # but you can specify an optional timeout_sec parameter to change it).
        #device = UART.find_device()
        known_uarts = set()
        count = 0
        while count < 2:
            # Call UART.find_devices to get a list of any UART devices that
            # have been found.  This call will quickly return results and does
            # not wait for devices to appear.
            found = set(UART.find_devices())
            # Check for new devices that haven't been seen yet and print out
            # their name and ID (MAC address on Linux, GUID on OSX).
            new = found - known_uarts
            for device in new:
                count = count + 1
                print('Found UART: {0} [{1}]'.format(device.name, device.id))
            known_uarts.update(new)
        # Sleep for a second and see if new devices have appeared.
        print(known_uarts)
        time.sleep(1.0)
                
        if device is None:
            raise RuntimeError('Failed to find UART device!')
    finally:
        # Make sure scanning is stopped before exiting.
        adapter.stop_scan()

    i = 0;
    
    for device in known_uarts:       
        print(device)
        device.connect()            
        print('Discovering services...')
        UART.discover(device)
        if i == 0:
            uart1 = UART(device)
            i = i + 1
        else:
            uart2 = UART(device)
    while(1):
        try:
            # Request name from device
            uart1.write(b'n\r\n')
            print("Sent name request to the device.")
            device_name = uart1.read(timeout_sec=180)
            if device_name is not None:
                        # Received data, print it out.
                print('Device Name: {0}'.format(device_name)) 
            else:
                # Timeout waiting for data, None is returned.
                print('Received no data!')
            time.sleep(1)
            # Request status of device
            uart1.write(b's\r\n')
            print("Sent need status to the device.")
            device_status = uart1.read(timeout_sec=180)
            if device_status is not None:
                # Received data, print it out.
                print('Device_status: {0}'.format(device_status)) 
            else:
                # Timeout waiting for data, None is returned.
                print('Received no data!')
                
            # Converts status int to string literal    
            if int(device_status) is 0:
                status = "Off"
            else:
                status = "On"
                    
            ts = time.time()
            timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
            id = str(device_name)
            data = {"Timestamp": timestamp, "ID": id, "Status": status}
            print(data)
            publish(data)
            
            uart2.write(b'n\r\n')
            print("Sent name request to the device.")
            device_name = uart2.read(timeout_sec=180)
            if device_name is not None:
                # Received data, print it out.
                print('Device Name: {0}'.format(device_name)) 
            else:
                # Timeout waiting for data, None is returned.
                print('Received no data!')
            time.sleep(1)
            # Request status of device
            uart2.write(b's\r\n')
            print("Sent need status to the device.")
            device_status = uart2.read(timeout_sec=180)
            if device_status is not None:
                # Received data, print it out.
                print('Device_status: {0}'.format(device_status)) 
            else:
                # Timeout waiting for data, None is returned.
                print('Received no data!')
                
            # Converts status int to string literal    
            if int(device_status) is 0:
                status = "Off"
            else:
                status = "On"
                    
            ts = time.time()
            timestamp = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
            id = str(device_name)
            data = {"Timestamp": timestamp, "ID": id, "Status": status}
            print(data)
            publish(data)
            time.sleep(60)
            
        finally:
            print("60 second wait in between statuses")



# Initialize the BLE system.  MUST be called before other BLE calls!
ble.initialize()

# Start the mainloop to process BLE events, and run the provided function in
# a background thread.  When the provided main function stops running, returns
# an integer status code, or throws an error the program will exit.
ble.run_mainloop_with(main)

Flask App Code

Programming the Flask App

dashboard.py

##############################
# ECE 4180 Project Flask-App #
##############################
#!/usr/bin/env python
import flask
from flask import redirect, request, render_template, jsonify
import boto3
from boto3.dynamodb.conditions import Key, Attr
from boto3 import resource
import boto3.dynamodb
import cPickle as pickle
import time
import json

# Create the application.
APP = flask.Flask(__name__)

#Enter AWS Credentials
MY_ARN='YourArnHere'
AWS_ACCESS_KEY="abc123" 
AWS_SECRET_KEY="password456"
REGION="America"
index=0;

dynamodbstr = boto3.client('dynamodbstreams', aws_access_key_id=AWS_ACCESS_KEY,
                                         aws_secret_access_key=AWS_SECRET_KEY,
                                         region_name=REGION)

response = dynamodbstr.describe_stream(StreamArn=MY_ARN)
ShardID = response['StreamDescription']['Shards'][-1]['ShardId']
ShardIterator = dynamodbstr.get_shard_iterator(StreamArn=MY_ARN,
                                          ShardId=ShardID,
                                          ShardIteratorType='TRIM_HORIZON')

ShardIterator = ShardIterator['ShardIterator']

i = 1

washer = {}
dryer  = {} 
machinelist = [None]*2

##############################################
# Retrieves current data from Dynamodb table #
##############################################
@APP.route('/data')
def get_Data():
    
    global index
    global response
    global machinelist    
    global i
    global ShardIterator
    global washer
    global dryer
    
    result = {}
    response = dynamodbstr.get_records(ShardIterator=ShardIterator)
    for data in response['Records']:
        result.update(data['dynamodb']['NewImage'])
        washer = {}
        dryer  = {}
        data   ={}
        if str(result['ID']['S']) == 'W':
            washer = {}
            washer['Timestamp'] = str(result['Timestamp']['S'])
            washer['ID']        = str(result['ID']['S'])
            washer['Status']    = str(result['Status']['S'])
            machinelist[0] = washer
        elif str(result['ID']['S']) == 'D':
            dryer = {}
            dryer['Timestamp']  = str(result['Timestamp']['S'])
            dryer['ID']         = str(result['ID']['S'])
            dryer['Status']     = str(result['Status']['S'])
            machinelist[1] = dryer
        i += 1
        index =+ 1
    ShardIterator = response['NextShardIterator']
    return

##############################
# Posts current data to site #
##############################
@APP.route('/', methods = ["GET","POST"])
def index():
    global washer,dryer,machinelist
    data = []
    get_Data()
    print machinelist
    return render_template('dashboard.html',machines=machinelist)


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

Program for the website

dashboard.html

<!DOCTYPE HTML>
<html>

<head>
    <title>ECE 4180 Laundry Manager</title>

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>

    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>

<body>

    <center>
        <h1>ECE 4180 Laundry Manager</h1>
    <center>
	    
<table class="table table-striped"><tr><th>ID</th><th>Status</th><th>Last Update</th>

{% for machine in machines %}
<tr>
<td>{{machine.ID}}</td>
<td>{{machine.Status}}</td>
<td>{{machine.Timestamp}}</td>
</tr>
{% endfor %}

</table>


</body>

</html>


Future Implementations

Ideally with several Mbed slave devices, this could be helpful within shared apartment laundry rooms. It would help residents manage their time well, and know when there are available washer and dryer units. In addition, having a mobile app would allow residents to receive notifications of their complete laundry, even when they aren't on the website.


Please log in to post comments.