Introduction: AUTOMATIC PET FOOD DISPENSER
Ever felt like wasting too much time feeding your pet? Ever had to call someone to feed your pets while you were on a holiday? I have tried to fix both these issues with my current school project: Petfeed!
Supplies
Raspberry Pi 3b
Bar Load Cell (10kg)
HX711 Load Cell Amplifier
Waterlevel Sensor (https://www.dfrobot.com/product-1493.html)
Ultrasonic Proximity Sensor
LCD 16-pins
2x stepper motor 28byj-48
2x stepper motor driver ULN2003
Step 1: Wiring
lots of cabling here. Get out your jumper cables and start pinning!
Step 2: Make Your Load Cell Usable
to use the load cell, we first need to attach it to two plates: a bottom plate, and a plate on which we will weigh our food.
The screws you need are a pair of M4 screws with matching bolts and a pair of M5 screws with matching bolts. I used a small drill to make the holes.
(pic: https://tutorials-raspberrypi.com/digital-raspberry-pi-scale-weight-sensor-hx711/)
Step 3: Normalised Database
data from our sensors has to be saved in a database. For the python files to connect to the database: see below.
then you also need a config file:
[connector_python]<br>user = *yourusername* host = 127.0.0.1 #if local port = 3306 password = *yourpassword* database = *yourdb* [application_config] driver = 'SQL Server'
Attachments
Step 4: Coding the Load Cell
import RPi.GPIO as GPIO<br>import threading import time from hx711 import HX711 from helpers.stepperFood import StepperFood from helpers.LCDWrite import LCDWrite from repositories.DataRepository import DataRepository <br>
After importing all of our libraries (note, we're using the HX711 Library to drive the load cell) we can start writing our actual code
TARRA_CONSTANT = 80600 GRAM_CONSTANT = 101
For finding out our constants, first set TARRA_CONSTANT = 0 and GRAM_CONSTANT = 1.
Next we need to find out the value our load cell reads when theres nothing being weighed. This value will be TARRA_CONSTANT.
As for GRAM_CONSTANT, simply take an object you know the weight of (i used a pack of spaghetti's), weigh it and divide the load cell readout with the actual weight of the object. For me this was 101.
class LoadCell(threading.Thread): def __init__(self, socket, lcd): threading.Thread.__init__(self) self.hx711 = HX711( dout_pin=5, pd_sck_pin=6, channel='A', gain=64 ) self.socket = socket self.lcd = lcd
here we initialize the LoadCell class and map the pins.
def run(self): try: while True: self.hx711.reset() # Before we start, reset the HX711 (not obligate) measures_avg = sum(self.hx711.get_raw_data()) / 5 weight = round( (measures_avg - TARRA_CONSTANT) / GRAM_CONSTANT, 0) print("weight: {0}".format(weight)) DataRepository.insert_weight(weight) data_weight = DataRepository.get_data_sensor(3) historyId = data_weight["SensorsHistory"] db_weight = data_weight["value"] actionTime = data_weight["actionTime"] self.socket.emit('data_weight', { "id": historyId, "Weight": db_weight, "Time": DataRepository.serializeDateTime(actionTime)}) print("zou moeten emitten") writeWeight = "weight: " + str(db_weight) msg = "PETFEED" LCDWrite.message() if int(db_weight[:-2]) <= 100: StepperFood.run() time.sleep(20) except Exception as e: print("Error with weighing" + str(e))
Step 5: Coding the Water Sensor
import time<br>import threading from repositories.DataRepository import DataRepository from RPi import GPIOGPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO_Water = 18 GPIO.setup(GPIO_Water, GPIO.IN) class WaterSensor(threading.Thread): def __init__(self, socket): threading.Thread.__init__(self) self.socket = socket self.vorige_status = 0 def run(self): try: while True: water = self.is_water() print(water) status = water["status"] action = water["action"] DataRepository.insert_water(str(status), action) data_water = DataRepository.get_data_sensor(2) historyId = data_water["SensorsHistory"] value = data_water["value"] if value == "0": value = "te weinig water" else: value = "genoeg water" actionTime = data_water["actionTime"] self.socket.emit('data_water', { "id": historyId, "value": value, "Time": DataRepository.serializeDateTime(actionTime), "action": action}) time.sleep(5) except Exception as ex: print(ex) print('error bij watersensor') def is_water(self): status = GPIO.input(GPIO_Water) if self.vorige_status == 0 and status == 1: print('water gedetecteerd') sensorData = {"status": status, "action": "water gedetecteerd"} self.vorige_status = status status = GPIO.input(GPIO_Water) if self.vorige_status == 1 and status == 1: print('water aanwezig') sensorData = {"status": status, "action": "water aanwezig"} status = GPIO.input(GPIO_Water) if self.vorige_status == 1 and status == 0: print('water weg') sensorData = {"status": status, "action": "water weg"} self.vorige_status = status status = GPIO.input(GPIO_Water) if self.vorige_status == 0 and status == 0: print('startpositie') status = GPIO.input(GPIO_Water) sensorData = {"status": status, "action": "startpositie"} return sensorData
Step 6: Coding the Proximity Sensor
import time<br>import threading from repositories.DataRepository import DataRepository from RPi import GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) GPIO_Trig = 4 GPIO_Echo = 17 GPIO.setup(GPIO_Trig, GPIO.OUT) GPIO.setup(GPIO_Echo, GPIO.IN) def current_milli_time(): return int(round(time.time() * 1000)) class UltrasonicSensor(threading.Thread): def __init__(self, socket): threading.Thread.__init__(self) self.socket = socket def run(self): try: last_reading = 0 interval = 5000 while True: if current_milli_time() > last_reading + interval: dist = self.distance() print("Measured Distance = %.1f cm" % dist) DataRepository.insert_proximity(dist) data_prox = DataRepository.get_data_sensor(1) historyId = data_prox["SensorsHistory"] prox = data_prox["value"] actionTime = data_prox["actionTime"] self.socket.emit('data_proximity', { "id": historyId, "Proximity": prox, "Time": DataRepository.serializeDateTime(actionTime)}) last_reading = current_milli_time() except Exception as ex: print(ex) def distance(self): # set Trigger to HIGH GPIO.output(GPIO_Trig, True) # set Trigger after 0.01ms to LOW time.sleep(0.00001) GPIO.output(GPIO_Trig, False) StartTime = time.time() StopTime = time.time() # save StartTime while GPIO.input(GPIO_Echo) == 0: StartTime = time.time() # save time of arrival while GPIO.input(GPIO_Echo) == 1: StopTime = time.time() # time difference between start and arrival TimeElapsed = StopTime - StartTime # multiply with the sonic speed (34300 cm/s) # and divide by 2, because there and back distance = (TimeElapsed * 34300) / 2 return distance
Step 7: Coding the Stepper Motors
import RPi.GPIO as GPIO<br>import time import threading GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) control_pins = [12, 16, 20, 21] for pin in control_pins: GPIO.setup(pin, GPIO.OUT) GPIO.output(pin, 0) halfstep_seq = [ [1, 0, 0, 0], [1, 1, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 1, 0], [0, 0, 1, 1], [0, 0, 0, 1], [1, 0, 0, 1] ] class StepperFood: # 512 is 1 keer rond def run(): for i in range(512): for halfstep in range(8): for pin in range(4): GPIO.output(control_pins[pin], halfstep_seq[halfstep][pin]) time.sleep(0.001) for pin in range(4): GPIO.output(control_pins[pin], 0)
This code is reusable for the other stepper motor, just set the control pin numbers to their repective pins and rename the class to StepperWater:
Step 8: Coding the LCD
Lots of code, but we're almost done.
The LCD class is included as file LCD.py
from helpers.LCD import LCD E = 26 RS = 25 D0 = 19 D1 = 13 D2 = 24 D3 = 22 D4 = 23 D5 = 8 D6 = 7 D7 = 10 lcd = LCD(E, RS, [D0, D1, D2, D3, D4, D5, D6, D7]) class LCDWrite: def message(msg): try: print("try") lcd.init_LCD() lcd.send_instruction(12) lcd.clear_display() lcd.write_message(msg, '1') except: print("error LCDWrite")
Step 9: The End
final result: how we drew it up vs. how it ended up.