The garbage collection process is a crucial service for any community or society. It ensures the cleanliness of our surroundings. However, the process has for long been rudimentary. Hence we’ve decided to automate the process. Our idea is to incorporate IoT into the already existing systems to increase the efficiency and to reduce the work load of the manual labors.
But how do we plan on accomplishing this ?
First, we need to set up some hardware in all the dustbins to help us collect data on how much garbage is present in the bin. We use an ultrasonic distance sensor and a Raspberry Pi to accomplish this. This data will be sent to the firestore database. We use this data to come up with the most efficient route plan. Finally, this route is displayed on a map for the truck drivers to follow. The resident can also view how much of the dustbin is filled and hence plan their trips to the community dustbin.
Contents
Hardware Setup
Components Required
- Ultrasonic sensor (HC-SR04)
- Jumper wires
- 1 kΩ resistor
- 2 kΩ resistor
- Small breadboard
- Raspberry pi
Circuit Diagram
Logic
The HC-SR04 Ultrasonic sensor has four pins: ground (GND), Echo Pulse Output (ECHO), Trigger Pulse Input (TRIG), and 5V Supply (Vcc).
- First, power the module using Vcc, ground it using GND,
- Send an input signal to TRIG, which triggers the sensor to send an ultrasonic pulse.
- This pulse gets reflected back to the sensor from a nearby object.
Once a return pulse is detected ECHO is set “high” (5V) for the duration of that pulse.
NOTE: The sensor output signal (ECHO) on the HC-SR04 is rated at 5V. However, the input pin on the Raspberry Pi GPIO is rated at 3.3V.Sending a 5V signal into that unprotected 3.3V input port could damage your GPIO pins, which is something we want to avoid! We’ll need to use a small voltage divider circuit, consisting of two resistors(1k,2k), to lower the sensor output voltage to something our Raspberry Pi can handle.
Code Explanation
- First import required modules
#import required modules
import RPi.GPIO as GPIO
import time
- Next, Assign GPIO pins to trigger and echo pins
GPIO.setmode(GPIO.BCM)
GPIO_TRIGGER = 26 #Connect trigger pin to GPIO 26 of Pi
GPIO_ECHO = 19 #Connect Echo pin to GPIO 19 of Pi
- Initialize trigger as Output and Echo as input
GPIO.setwarnings(False)
GPIO.setup(GPIO_TRIGGER, GPIO.OUT) #output :As Pi triggers US sensor
GPIO.setup(GPIO_ECHO, GPIO.IN) #input :As we read Echo status
- Initially set trigger to false and delay of 2 sec to let the sensor settle
GPIO.output(GPIO_TRIGGER, False) #set trigger to 0 or off state
print ("Waiting For Sensor To Settle")
time.sleep(2) #delay of 2 sec
- We define a new class dustbin(), inside which we will write all the required functions
- Within __init__(), set initial_depth to -1
class dustbin():
def __init__ (self):
self.initial_Depth =-1 #-1 represents not yet initialized
- In current_Depth() method, we write the code to get the depth dustbin is not filled. This value is later used to calculate what percentage of the dustbin is filled.
def current_Depth(self):
#trigger the ultrasonic sensor for a very short period (10us).
GPIO.output(GPIO_TRIGGER, True)
time.sleep(0.00001) # 10us or 0.00001sec
GPIO.output(GPIO_TRIGGER, False)
while GPIO.input(GPIO_ECHO) == 0:
Pass
#start timer once echo becomes high i.e. '1'
StartTime = time.time()
while GPIO.input(GPIO_ECHO) == 1:
Pass
#stop timer once signal is completely received and echo becomes 0
StopTime = time.time()
# This records the time duration for which echo pin was high
TimeElapsed = StopTime - StartTime
speed=34300 #speed of sound in air 343 m/s or 34300cm/s
#Time elapsed = time it takes for the pulse to go and come back
twicedistance = (TimeElapsed * speed)
distance=twicedistance/2 # to get actual distance simply divide it by 2
return round(distance,2) # round off upto 2 decimal points
- Update_Initial() function is called only at the initial setup/installation of the dustbin(). This reads the depth when dustbin is empty and assigns the same to initial_Depth() variable.
def update_Initial(self):
#set the current_Depth as the Depth of dustbin
self.initial_Depth=self.current_Depth()
def percentage_Filled(self):
diff=self.initial_Depth -self.current_Depth()
percentage=diff/self.initial_Depth
percentage*=100
if(percentage<0):
percentage=0
return round(percentage,2)
Dustbin Configuration
The purpose of this GUI is to help low skill technicians to get the dustbins up and running. The GUI does have certain functions that need not necessarily be used in a real life scenario. For instance, the current data
button gets the current depth of the dustbin form which we can calculate the percentage of garbage present in it. However, in real life we will be running this function as a scheduled task with a time interval of 6 hours.
We can also automate the enter coordinates
button by integrating the system with a GPS module.
Data flow
- Getting Data from Ultrasonic sensor
- Display the result to the user
- Get some inputs from the user
- Send the data to Cloud
User Interface
Functionality of the App
- Initial Data: It gets the initial depth of the dustbin. This data would ideally be collected only once during setup. This data is required to calculate the percentage of dustbin filled.
- Current Data: It gets the depth of the dustbin at the moment. This data tells us exactly how much of the dustbin is full. Ideally this function would be automated with a scheduler, it has been made manual here to make it easier to demonstrate.
- Enter Area: User enters the location in which the dustbin is placed.
- Enter co-ordinate: User enters the x, y co-ordinates (Longitude and Latitude) of the dustbin.
- Return Data: Sends the dustbin’s setup data to the cloud.
Code Explanation
- Importing all required modules.
from tkinter import *
import tkinter.font as tkFont
from DepthSensor.ultrasonic import dustbin#From ultrasonic.py file we import the dustbin class
from cloud_integration import transfer_data as transfer_data
from google.cloud import firestore
- We first initialize the window size (width and height) of the window using
geometry
. Indata
we store the values that we are going to send to the cloud. Finally, we create the objectb
of the type dustbin .
class app():
def __init__(self, obj):
self.obj = obj
self.obj.title("User Interface")
self.height = self.obj.winfo_screenheight()
self.width = self.obj.winfo_screenwidth()
self.app_width = int(0.5*self.width)
self.app_length = int(0.5*self.height)
self.obj.geometry(str(self.app_width)+"x"+str(self.app_length))#Geometry is defined
self.data = { }# This dictinary stores the data with key values which are to be sent to the cloud
self.b=dustbin() #to invoke the dustbin object from the dustbin class of ultrasonic.py file
- The
senddata
method sends data to the database as a dictionary, where the keys represent the field names and the values represent the data.
def senddata(self):
print(self.data)#prints the data with key values
transfer_data.send_data(self.data)
co_ordinate
method takes the comma separated values from the user which is latitude and longitude of the dustbin’s location. We then define anEntrybox
which takes the coordinates as an input from the user. This data is then stored into a list. The data from this list is then converted to the GeoPoint data type.
def co_ordinate(self):
def printdata(entrybox):#Entrybox which takes the input cordinate from the user
text = entrybox.get()
print(str(text))
l=[] # list to store x and y cordinated at the index 0 and one respectively
for cordinate in text.split(','):
l.append(float(cordinate))
self.data['location']= firestore.GeoPoint(float(l[0]), float(l[1]))
print(self.data)
cordinate = Tk() # this object is used to create the new window
cordinate.geometry(str(int(0.5*self.height))+"x"+str(int(0.3*self.width)))
cordinate.title("enter the co_ordinte values")
label1 = Label(cordinate, text= "Enter the co ordinates seperated by comma" )
label1.pack()
textin = StringVar() #String variable
e=Entry(cordinate, width=30)
button = Button(cordinate,text='okay',command=lambda:printdata(e))
button.pack(side='bottom')
e.pack()
cordinate.mainloop()
- The
getarea
method takes in the area’s name as an input from the user.
# getarea function takes the area where dustbin is located
def getarea(self):
def printdata(entrybox):#Entrybox which takes the input area from the user
text = entrybox.get()
self.data['area']=str(text)
print(self.data)
area=Tk() # this object is used to create the new window
area.geometry(str(int(0.5*self.height))+"x"+str(int(0.3*self.width)))
label1 = Label(area, text= "Enter the Area" )
label1.pack()
e=Entry(area, width=30)
button = Button(area,text='okay',command= lambda: printdata(e))
button.pack(side='bottom')
e.pack()
area.mainloop()
get_depth
method used to get the data from the sensor. We pass either True or false. Passing True runs the initial setup whereas passing False gets the current depth.
def get_depth(self,setup):
# if setup is True then initial depth is obtained else current depth is obtained.
if setup:
current_data = Tk()
current_data.geometry(str(int(0.5*self.height))+"x"+str(int(0.3*self.width)))
current_data.title("Info Page")
current = self.b.update_Initial()
self.data['initialDepth']=float(current)
label1 = Label(current_data, text= "current depth in cm = "+str(current))
label2 = Label(current_data, text= "percentage filled now= "+str(0))
label1.pack()
label2.pack()
print(self.data)
else:
current_data = Tk()
current_data.geometry(str(int(0.5*self.height))+"x"+str(int(0.3*self.width)))
current_data.title("Info Page")
current=self.b.current_Depth()
current_percentage=self.b.percentage_Filled(self.b.initial_Depth,current)
self.data['currentDepth']=float(current)
label1 = Label(current_data, text= "current depth in cm = "+str(current))
label2 = Label(current_data, text= "percentage filled now= "+str(current_percentage))
label1.pack()
label2.pack()
print(self.data)
- The
start_app
method starts the app and acts as a backbone for all other functionalities.
def start_app(self,object1):
#To start the APP
welcome = tkFont.Font(family='arial', size=55)
welcome = Label(self.obj, text='Garbage collection \n App \n\n\n',font=welcome,background="green")
welcome.pack(fill='both', expand=True, anchor=CENTER)
initial_display_button = Button(object1,text="Initial Data",background="white",command=lambda: self.get_depth(True) )
initial_display_button.place(relx=0.5, rely=0.5, anchor=CENTER)
current_display_button = Button(object1,text="current Data",background="white",command=lambda: self.get_depth(False) )
current_display_button.place(relx=0.5, rely=0.6, anchor=CENTER)
return_button = Button(object1,text="Return Data",background="white",command=self.senddata)
return_button.place(relx=0.5, rely=0.9, anchor=CENTER)
co_ordinate_button = Button(object1,text="Enter Co-Ordinates",background="white",command=self.co_ordinate )
co_ordinate_button.place(relx=0.5, rely=0.8, anchor=CENTER)
area_button = Button(object1,text="Enter Area",background="white",command=self.getarea )
area_button.place(relx=0.5, rely=0.7, anchor=CENTER)
- Finally we run the app. Here object1 is our main window object which houses our UI. Object2 creates an instance of the object
app
.object2.start_app(object1)
passes the window into the object that we created.
if __name__ == "__main__":
object1 = Tk()
object2 = app(object1)
object2.start_app(object1)
object1.mainloop()
Cloud integration
The various data produced in the GUI has to be stored in the cloud to be used later by the android application. For this function we have decided to use Google Firestore. Firestore works on the principle of documents and fields. For this project we’ve created a collection called “dustbin” which contains as many documents as there are dustbins.
Each document has exactly 5 fields namely area, currentDepth, initialDepth, location, perc.
- area: This field contains a simple string which is the locality’s name.
- currentDepth: This field contains details on the depth of the dustbin at present.
- initialDepth: This field has data that stores the depth of the empty dustbin.
- location: It stores the latitude and longitude of the dustbin. The data type used to store this data is GeoPoint. It can help us determine the route to be followed by the truck driver.
- perc: perc stores the percentage of the dustbin that has been filled (as explained here).
Code Explanation
- First of all, lets import the required libraries. For this section we need to import only 3 modules – Firestore, credentials and firebase_admin (all of them belong to the firebase_admin package)
import firebase_admin
from firebase_admin import credentials
from firebase_admin import firestore
- Next we need to initialize a credential object. We do this with a credentials JSON file that we create here: https://console.cloud.google.com/apis/credentials/serviceaccountkey
cred = credentials.Certificate('firebase-test-4b2e17880be1.json')
firebase_admin.initialize_app(cred)
- Next we initialize a database object called
db
db = firestore.client()
- Now let’s create a function that sends data to our collection named
dustbin
def send_data(data):
db.collection(u'dustbin').add(data)
The add
method used above essentially adds data into the specified collection without requiring us to set a name for each of the documents. This feature is required in this case because by using this we eliminate the need to know the number of dustbins that are in service.
Here the data that we pass as parameter is a dictionary such that the keys of the dictionary correspond to the field names in each document.
- Next, we will write a function to get the data back from our Firestore database. This function returns a list of dictionaries which hold data from each document.
def get_data():
users_ref = db.collection(u'dustbin')
docs = users_ref.stream()
doc_list = []
for doc in docs:
data = doc.to_dict()
print(data)
doc_list.append(data)
return doc_list
Plotting the Data on Google Maps
Routing And Optimization
To optimize the routes between the dustbins we will use google maps. The dustbin nearest to the truck becomes the source, farthest becomes the destination and all others act as way-points.
Why google Maps? Today, google maps contains over 20 petabytes of data and is used by millions of people everyday for routing. It not only uses one routing algorithm like A* or Dijkstra but several combined together as shown in the above diagram.
Directions API
For routing the shortest path, we will use the directions API provided by google maps in python to get an efficient route and plot it. We will provide it with a source, way-points and destination to get a JSON output. It gives directions between various points, also through various driving modes. Start by enabling the directions API and generating an API key from google cloud.
Code Explanation
We can either use the gmaps library to perform the task or simply access it through its URL. For the latter we need only two simple dependencies, install them and import
import urllib.request, json
import webbrowser
Create a function map with all the points as input which returns a URL containing the final plotted map.
def opt_route(origin, waypoints, destination):
#Google MapsDdirections API endpoint
endpoint = 'https://maps.googleapis.com/maps/api/directions/json?'
# please set your api_key below
api_key = "API_key"
#adding mode of routing
mode='driving'
#Building the URL for the request
nav_request = 'origin={}&destination={}&waypoints=optimize:true|{}|{}|{}|{}&mode={}&key={}'.format(origin,destination,waypoints[0],waypoints[1],waypoints[2],waypoints[3],mode,api_key)
request = endpoint + nav_request
#Sends the request and reads the response.
response = urllib.request.urlopen(request).read()
#Loads response as JSON
directions = json.loads(response)
routes=directions["routes"]
# get the order for route planning
waypoint_order=routes[0]["waypoint_order"]
# url part without optimisation
part1='https://www.google.co.in/maps/dir/'
# url part with optimisation (using index values stored in waypoint_order)
part2='{}/{}/{}/{}/{}/{}/'.format(origin,waypoints[waypoint_order[0]],waypoints[waypoint_order[1]],waypoints[waypoint_order[2]],waypoints[waypoint_order[3]],destination)
url=part1+part2
return url
- The API key created must be copied here.
- The directions API returns a JSON file from which data needs to be extracted.
- 1 source, 1 destination and several way-points can be provided. The mode of routing (i.e. driving, walking) should also be given.
Output
You can find the entire source code for this project on GitHub here : https://github.com/sashreek1/garbage_collection_system
Hope this article was enlightening and helped you to understand the usage of IoT in our day-to-day life. The process of garbage collection has been around for quite a while but it still continues to be the inefficient process that it had always been. We hope our idea will change the communities that we all live in. Thank you for reading.
Happy Learning 😄
- AWS Cloud Practitioner (CLF-C02) | Mock Test – 19
- AWS Cloud Practitioner (CLF-C02) | Mock Test – 20
- AWS Cloud Practitioner (CLF-C02) | Mock Test – 22
- AWS Cloud Practitioner (CLF-C02) | Mock Test – 23
- AWS Cloud Practitioner (CLF-C02) | Mock Test – 26
- AWS Cloud Practitioner (CLF-C02) | Mock Test – 27