Kyelo Torres
Published

Machines Automate the Boring Stuff

Imagine having to run a life cycle test by hand! Boring! Don't worry, this machine can automate that process and all it takes is $150.

IntermediateProtip7 hours1,050
Machines Automate the Boring Stuff

Things used in this project

Hardware components

Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
Adafruit Nema 17
×1
Adafruit Stepper Motor Driver HAT
×1
Camera Module V2
Raspberry Pi Camera Module V2
×1

Software apps and online services

Raspbian
Raspberry Pi Raspbian

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Laser cutter (generic)
Laser cutter (generic)

Story

Read more

Custom parts and enclosures

Frame

Code

mainEx.py

Python
Running this file will launch:
1. UI for adjusting Camera
2. UI for selecting folder to store data
3. UI for controlling machine during test
#!/usr/bin/env python3

########### Position Camera Function#############
## This block of code opens up a user interface that allows the user to position the camera
## Warning: do NOT use the [x] to close the camera. use the 'Exit Program' button
from tkinter import *
import picamera
from time import sleep

# create a camera object
camera = picamera.PiCamera()

# Function to create a open the camera object
def CameraON():
    camera.preview_fullscreen=False
    camera.preview_window=(90,140, 320, 240)
    camera.resolution=(640,480)
    camera.rotation = 270
    camera.start_preview()

# Function to stop camera preview
def CameraOFF():
    camera.stop_preview()

# Function to destroy the UI correctly
def EXIT():
    root.destroy()
    camera.stop_preview()
    camera.close()

# Create a root for the UI
root = Tk()
root.resizable(width=False, height=False)
root.geometry("320x300+89+50")
root.title("Camera Button Test")

root.buttonframe = Frame(root)
root.buttonframe.grid(row=5, column=3, columnspan=2)

# Create some buttons and the functions that are tied to the buttons
Button(root.buttonframe, text='Start Camera', command=CameraON).grid(row=1, column = 1)
Button(root.buttonframe, text='Kill Camera', command=CameraOFF).grid(row=1, column = 2)
Button(root.buttonframe, text='Exit Program', command=EXIT).grid(row=1, column = 3)

root.labelframe = Frame(root)
root.labelframe.grid(row=3, column=3, columnspan=1)

Label(root,text="Position the Camera").grid(row=3,column = 3)

root.mainloop()

###########End of Position Camera Function#######

########### Folder Select Function###############
# This block of code requires that the user select a folder where they want to save the photos
from tkinter.filedialog import askdirectory

def directoryBox(title=None, dirName=None):
        options = {}
        options['initialdir'] = dirName
        options['title'] = title
        options['mustexist'] = False
        fileName =askdirectory(**options)
        if fileName == "":
            return None
        else:
            return fileName

root = Tk()
root.geometry(str(480)+"x"+str(320)+"+2+10")
root.title =('Where do you want to save the Photos')
folder = directoryBox('Where do you want to save the Photos','/media/pi')
root.destroy()

if folder != None:
        # write to file
        f=open('/home/pi/Desktop/FatigueTest/folderName.txt','w')
        f.write(folder)
        f.close()

while folder == None:
    root = Tk()
    root.geometry(str(480)+"x"+str(320)+"+50+50")
    folder = directoryBox('Choose a valid location Hoe','/media/pi')
    root.destroy()
    if folder != None:
        # write to file
        f=open('/home/pi/Desktop/FatigueTest/folderName.txt','w')
        f.write(folder)
        f.close()

print(folder)
########### End of Folder Select Function########

############# Start of During Test Function##############
# This function creates a UI that controls the motor and camera during the test
# Warnings: Do not 'Show Camera' when the camera is about to take a photo -- the program will crash
# Warnings: The buttons will only activate after the end of the cycle. Be patient
# Warnings: The motor can only be move after 'Stop Cycle' has be clicked
# Warnings: Only press 'Goodbye' after you have stopped the cycle
# If you want to reset the cycle and runtime, edit the 'Data.txt file' First number is cycle and second is runtime
from time import time
import threading
from picamera import PiCamera
from time import time
import datetime as dt
from time import strftime
from Adafruit_MotorHAT import Adafruit_MotorHAT, Adafruit_DCMotor, Adafruit_StepperMotor
import atexit
import os
class App(threading.Thread):
    def __init__(self,window,window_title):

        # Create a root and give a title
        self.root = window
        self.root.title =(window_title)
        threading.Thread.__init__(self)
        self.width=480
        self.height = 320
        self.root.geometry(str(self.width)+"x"+str(self.height)+"+50+50")
        self.isTesting = True

        # Start the Thread
        self.start()


    def update(self):
        pass

    def close_window(self):
        if not self.isTesting:
            self.root.destroy()

    def show_camera(self):
        os.system('python3 /home/pi/Desktop/FatigueTest/PositionCameraUI.py')

    def show_camera_thread(self):
        threading.Thread(target=self.show_camera).start()

    def start_cycle(self):
        print('Resuming Test...')
        self.isTesting = True
        self.run()

    def stop_cycle(self):
        print('Stopping Test...')
        self.isTesting = False


    def motor_up(self):
        if self.isTesting == False:
            print('Motor going up...')
            mh = Adafruit_MotorHAT()
            myStepper = mh.getStepper(200, 2)  # 200 steps/rev, motor port #2
            myStepper.setSpeed(1000)             # 30 RPM
            myStepper.step(20, Adafruit_MotorHAT.BACKWARD,  Adafruit_MotorHAT.MICROSTEP)
            sleep(0.1)
            mh.getMotor(1).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(2).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(3).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(4).run(Adafruit_MotorHAT.RELEASE)

    def motor_down(self):
        if self.isTesting == False:
            print('Motor going down...')
            mh = Adafruit_MotorHAT()
            myStepper = mh.getStepper(200, 2)  # 200 steps/rev, motor port #1
            myStepper.setSpeed(1000)             # 30 RPM
            myStepper.step(20, Adafruit_MotorHAT.FORWARD,  Adafruit_MotorHAT.MICROSTEP)
            sleep(0.1)
            mh.getMotor(1).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(2).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(3).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(4).run(Adafruit_MotorHAT.RELEASE)
    
    def reset_count_thread(self):
        threading.Thread(target=self.reset_count).start()
    
    def reset_count(self):
        pass
        #f = open('/home/pi/Desktop/FatigueTest/Data.txt','w')
        #if not self.isTesting:
        #    f = open('/home/pi/Desktop/FatigueTest/Data.txt','w')
        #    f.write('0\n0')
        #    f.close()

    def run(self):
        # Create loop to move motor, update label and update root
        ## Step 1: Set up the camera and motor, specify folder to save in
        #camera = PiCamera()
        #camera.vflip = True
        #camera.hflip = True
        mh = Adafruit_MotorHAT()
        foldername = open('/home/pi/Desktop/FatigueTest/folderName.txt',"r").read()
        Datafile = open('/home/pi/Desktop/FatigueTest/Data.txt',"r")
        self.cycle = int(Datafile.readline())
        self.runtime = int(Datafile.readline())
        Datafile.close()
        
        ## Edit the following lines of code to change when the photo is taken and the maximum cycles
        takePictureEvery = 50
        self.maximumCycles = 10000
        ## Function Definitions
        # recommended for auto-disabling motors on shutdown!
        def turnOffMotors():
            mh.getMotor(1).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(2).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(3).run(Adafruit_MotorHAT.RELEASE)
            mh.getMotor(4).run(Adafruit_MotorHAT.RELEASE)

        def savePic(counter,time_now):
            cam=PiCamera()
            cam.rotation = 270
            cam.resolution=(640,480)
            sleep(2)
            cam.annotate_text = 'Cycle:{} Time:{}'.format(counter,time_now.strftime('%m-%d-%Y %H:%M:%S'))
            cam.capture(foldername+'/'+'Cycle{}_'.format(counter)+time_now.strftime('%m_%d_%Y__%H_%M_%S.jpg'))
            cam.close()
            
        ## Step 2: exit safely
        #At exit - turn off the motors and the camera
        atexit.register(turnOffMotors)

        ## Step 4: Move the Motor and take a photo of LCD Screen
        myStepper = mh.getStepper(200, 2)  # 200 steps/rev, motor port #2
        myStepper.setSpeed(100)             # 800 RPM
        begin_time = time()

        while self.isTesting and (self.cycle < self.maximumCycles):

            self.root.update()
            start_time = time()

            foldername = open('/home/pi/Desktop/FatigueTest/folderName.txt','r').read()
            Datafile = open('/home/pi/Desktop/FatigueTest/Data.txt',"r")
            self.cycle = int(Datafile.readline())
            self.runtime = int(Datafile.readline())
            Datafile.close()

            # Create some Labels to display buttons
            Label(text='Cycle: {}/{}\nRun Time: {} s'.format(self.cycle,self.maximumCycles,str(dt.timedelta(seconds=int(self.runtime)))),
              font=('Comic Sans MS',26)).place(anchor="center",x=int(self.width/2),y=int(self.height/2))
            # Create some buttons
            buttonwidth=int(self.width/5)
            buttonheight = int(self.height/5)
            Button(text = "Good-bye.", command = self.close_window).place(height = buttonheight,width=buttonwidth,x=self.width-buttonwidth,y=0)
            #Button(text = "Reset Count", command = self.reset_count).place(height = buttonheight,width=buttonwidth,x=0,y=0)
            Button(text = "Start Cycle", command = self.start_cycle).place(height = buttonheight,width=buttonwidth,x=0,y=self.height-buttonheight)
            Button(text = "Stop Cycle", command = self.stop_cycle).place(height = buttonheight, width=buttonwidth,x=buttonwidth,y=self.height-buttonheight)
            Button(text = "Motor Up", command = self.motor_up).place(height = buttonheight,width=buttonwidth,x=self.width-buttonwidth*2,y=self.height-buttonheight)
            Button(text = "Motor Down", command = self.motor_down).place(height = buttonheight,width=buttonwidth,x=self.width-buttonwidth,y=self.height-buttonheight)
            Button(text = "Show Camera", command = self.show_camera_thread).place(height = buttonheight,width=buttonwidth,x=buttonwidth*2,y=self.height-buttonheight)

            # Move the gears
            myStepper.step(250, Adafruit_MotorHAT.BACKWARD,  Adafruit_MotorHAT.MICROSTEP)
            sleep(5)
            myStepper.step(251, Adafruit_MotorHAT.FORWARD, Adafruit_MotorHAT.MICROSTEP)
            sleep(5)

            # Save the photo in the file with the current time
            if self.cycle%takePictureEvery == 0:
                savePic(self.cycle, dt.datetime.now())

            stop_time = time()-start_time

            self.cycle = self.cycle + 1

            self.runtime = int(self.runtime + stop_time)
            # Write total time and cycle to file
            Datafile = open('/home/pi/Desktop/FatigueTest/Data.txt','w')
            Datafile.write(str(self.cycle)+'\n')
            Datafile.write(str(self.runtime))
            Datafile.close()
            print('Time of cycle {0:6d} : {1:2.2f}'.format(self.cycle,stop_time))

            self.root.update()
        # when exit loop update labels and properly close camera
        turnOffMotors()

        # Change labels and update one last time
        # Create some Labels to display buttons
        if self.isTesting and (self.cycle >= self.maximumCycles):
            Datafile = open('/home/pi/Desktop/FatigueTest/Data.txt','w')
            Datafile.write('0\n0')
            Datafile.close()
            Label(text='DONE!!\nCycle: {}/{}\nRun Time: {} s'.format(self.cycle,self.maximumCycles,dt.timedelta(seconds=int(self.runtime))),
              font=('Comic Sans MS',30)).place(anchor="center",x=int(self.width/2),y=int(self.height/2))
        else:
            Label(text='Cycle: {}/{}\nRun Time: {} s'.format(self.cycle,self.maximumCycles,dt.timedelta(seconds=int(self.runtime))),
              font=('Comic Sans MS',26)).place(anchor="center",x=int(self.width/2),y=int(self.height/2))
        # Create some buttons
        buttonwidth=int(self.width/5)
        buttonheight = int(self.height/5)
        Button(text = "Good-bye.", command = self.close_window).place(height = buttonheight,width=buttonwidth,x=self.width-buttonwidth,y=0)
        #Button(text = "Reset Count", command = self.reset_count_thread).place(height = buttonheight,width=buttonwidth,x=0,y=0)
        Button(text = "Start Cycle", command = self.start_cycle).place(height = buttonheight,width=buttonwidth,x=0,y=self.height-buttonheight)
        Button(text = "Stop Cycle", command = self.stop_cycle).place(height = buttonheight, width=buttonwidth,x=buttonwidth,y=self.height-buttonheight)
        Button(text = "Motor Up", command = self.motor_up).place(height = buttonheight,width=buttonwidth,x=self.width-buttonwidth*2,y=self.height-buttonheight)
        Button(text = "Motor Down", command = self.motor_down).place(height = buttonheight,width=buttonwidth,x=self.width-buttonwidth,y=self.height-buttonheight)
        Button(text = "Show Camera", command = self.show_camera_thread).place(height = buttonheight,width=buttonwidth,x=buttonwidth*2,y=self.height-buttonheight)
        self.root.update()

Window = Tk()
App(Window,"Testing in Progess")
Window.mainloop()

Credits

Kyelo Torres

Kyelo Torres

9 projects • 5 followers
Hi, my name is Kyelo and I am a 4th-year mechanical engineer at UC Berkeley. My class, club and personal projects will be posted here!

Comments