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
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
Accelerometer | Mbed |
---|---|
gnd | gnd |
3.3V | Vout (3.3V) |
SDA | P9 |
SCL | P10 |
Bluetooth Module
Bluetooth | Mbed |
---|---|
gnd | gnd |
Vin | Vout (3.3V) |
TXO | P28 |
RXI | P27 |
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.