RoboTEAc Steeper

A Robotic Tea Steeper
Embedded Project By Kayla Rossi.

Demonstration Video


This system consisted of multiple elements that combined together to make a robotic tea steeper. It cumulates mechanical and electronic parts through python programming in a linux operating system on a raspberry pi 4 (RPi) to create one large embedded system. This system works using multiple servo motors to dispense loose leaf tea, as chosen by the user, into a diffuser. It then pulls the diffuser through a self-closing ramp and dips the diffuser into a mug of hot water. Meanwhile, a countdown for the tea brew starts on the piTFT screen, the diffuser is steeped by being slightly lowered and lifted in the hot water. Once the steeping time is completed, the diffuser is withdrawn from the mug and the tea is brewed.

two views of the overall system

Project Objectives:

  • Tea (type based on user selection from three types) is dispensed into a diffuser
  • Diffuser closes through self-closing ramp and drops into mug of hot water
  • A visual countdown starts, based on time associated with selected tea type and strength from user selection
  • Diffuser jitters up and down in mug throughout the duration of steeping time
  • Once tea is brewed, diffuser is lifted from mug and system is ready to be reset


Mechanical Design

The mechanical design of the system evolved as the idea become more refined after idea conception. As depicted in figure 1, the original sketch involved a complex design of rotational to linear motion mechanisms and two continuous motors. After some discussion during project proposal, a few idea improvements were presented for consideration. One idea was to consider integrating a way for hot water to be automatically brewed and placed into position under the diffuser. Another idea was to incorporate a way to automate dispensing tea into a diffuser thus replacing the idea of a user putting a previously filled diffuser into the brewing mechanism. Lastly, the brewing mechanism that was used in the proposal, as seen in figure 1, was suggested to utilize a standard servo motor in place of the rotational to linear motion actuated by a continuous servo. This combination of ideas sparked larger changes for the entire mechanical design.

Photo of early concept drawing
Figure 1. Inital conceptual design of overall system. Two continuous motors represented as circles would utilize linear to rotational mechanisms to provide desired range of motion. Upper platform would tilt steeper into and out of mug.

An amount of time was spent on considering how to involve a keurig style machine to heat up hot water and use a turn-style/lazy-susan type mechanism to swivel the mug into location under the diffuser. The complications that surfaced with this idea were largely surrounding concerns of weight. The combined weight of a ceramic mug plus 8-12 ounces of water, approximately as much as 1 pound, would be a heavy load for a small geared DC or servo motor to move. A more thorough motor selection would be needed in this regard and it was felt that the general idea addition did not add any significant value to the overall project.

It was decided that a more value-added path for the project direction was to incorporate a way to dispense tea into the diffuser before it was delivered into the mug. The original idea did allow for three types of tea to be selected by user input that would have an assigned brewing time to each. The assigned brewing time would be a specified duration that the diffuser would be in the hot water, to be discussed in more detail in programming design section below. Three types of tea were chosen to be dispensed - black tea, green tea, and herbal tea. The manner in which the teas were to be dispensed is based on a revolver/hopper design and as such the dispenser is referred to as the teavolver. The final overall system design can be seen in the CAD assembly in figure 2.

Full CAD view of the overall system
Figure 2. CAD model, isometric view on the left and side view of the system with translucent self-closing slide on the right. The views show a dispensing teavolver with a mounted motor on the top. This motor is mounted to a common back plane (not pictured). Additionally, there is a rectangular plate with a half circle cut out of it and a nesting circular plate with a hole removed. This nesting circular plate will be referred to in the following sections and programming portion as the pacman plate. The rectangular plate is mounted to the top surface of the diffuser slide with two long mounts spanning the width of the rectangular base plate. The pacman plate has a motor attached to the underside which rotates the plate 180 degrees to either block tea or allow tea to fall through the plate, into the diffuser. The motor for that plate is mounted to a diffuser slide that has an outer thick rectangular prism body. This slide is mounted to a base plane (not pictured) and the opposite end sees a roller which reels in the chain of the diffuser through the slide. This roller is mounted on one end to a continuous servo motor and the opposite end is nested inside a mounted constraint to allow free rotation of the roller.

The manner in which each motorized part would actuate was straightforward. A standard servo would be needed for the teavolver and pacman plate. These parts would need to rotate to a specific angle each time, making the standard servo ideal. The motor that would be responsible for reeling in the diffuser through the self-closing ramp would need to be a continuous motor. A DC motor was ruled inadequate due to very low torque, as such, a continuous servo motor was used. All of the servo motors that were used were parallax motors and datasheets can be located in the references section.

Electrical Design

The electrical design was relatively simple due to the nature of servo motors. Three GPIO (general purpose input/output) pins were used, one for each servo. The two standard servo motors were connected to the the two GPIO pins that are associated with hardware PWM (pulse width modulation) within the RPi, pins 12 and 13. As can be seen in figure 3, each GPIO was connected to a 1 kOhm resistor to protect the RPi from any feedback current. The resistor then connected to an LED which provided a visual feedback that the GPIO pin was producing output, and the other end of the LED went to ground. In parallel with the LED was the signal input for the servo motor. Each motor was also connected to input voltage from 6V power supply (battery pack) and connected to a common ground. The common ground in the circuit comes from the RPi GPIO breakout header and is connected to the ground header on the protoboard. All equipment in the system is connected to the same common ground.

The continuous motor was connected in the exact same manner as the two standard servos, the only difference being that it was not connected to a GPIO pin that was associated with hardware PWM in the RPi. It was connected to GPIO pin 19.

Photo of protoboard circuit
Figure 3. System circuit. Motor connections are shown as groupings of 3 wired headers.

Programming Design

All of the programming for the system was conducted in python on a linux operating system, run on the RPi. The script that provided all of the functionality of each component for the whole system was called Numerous test scripts were created to assist in determining the necessary motion of the standard and continuous servos. This was then fed back into the script and implemented. Additionally, the touchscreen prompts and buttons were first tested in test scripts. The piTFT display and touchscreen used pygame, a program that assists in visual display using Python language. The test scripts for the pygame aspects insured proper functionality of the pygame features and display before integrating into the script.

There are two menus in the system that require user selection. The first menu presents tea types, i.e. black tea, green tea, and herbal tea, and the second menu lists tea strengths, i.e. weak, normal, strong. The tea strengths determine the duration of steep time per tea type as can be seen in Table 1. The final screen on the piTFT shows the countdown of time in minutes and seconds based on the prior selections of tea type and strength. In addition to these various selections, each screen has an option to quit the program by utilizing a programmed quit button displayed on the piTFT screen for the first two menus, or the indicated piTFT physical button while the countdown is running. The physical button was a necessity on the countdown screen due to the usage of time.sleep to control the continuous motor in that particular section of code. When using time.sleep, the touchscreen is unavailable to recognize or read touches during 4 out of the 5 seconds between the usage of time.sleep, thus rendering the touchscreen unusable during that time.

Table 1. Tea types with respective steep times at various strengths of brew.
table listing tea types and respective times for various strengths

Multiple functions were needed in the code to keep it condensed and to keep the motor actuations concise for each call. The following were functions created for the specified motor actions:

  • setMotorP() - rotates pacman motor from 180 degrees to 0 degrees. This rotates the plate around to allow the teavolver to dispense, if in position for dispensing.
  • setMotorT(angleT) - rotates the teavolver based on user tea selection for the appropriate cylinder which houses the tea selected. This function took in a variable called angleT, an angle between 0 and 180 degrees, which is the specific angle for each tea selection. 0 degrees - Herbal, 60 degrees - Green, 180 degrees - Black.
  • PullTea(direction, secs) - rotates the continuous motor a specified direction (clockwise or counter-clockwise as defined in the code) and for a specific amount of time in seconds
  • HomingP() - sets the pacman motor to home position if it is not already at the home position of 180 degrees. This ensures the hole in the plate will not be near the teavolver when it is rotating into position and thus does not result in unwanted tea dispensing.
  • HomingT(angleT) - sets the teavolver motor to home position if it is not already at the home position of 0 degrees. This ensures that the teavolver starts from the same position each time. It is not entirely necessary since the exact position is input upon tea selection, but it keeps a uniformity to the code and system.
  • countdown(t) - starts the sequence for dispensing tea, closing the diffuser, dropping it into the tea, counting down the time, steeping the tea, and lifting the diffuser from the mug. This function takes in a variable t, which is time in seconds, defined based on the user selection of type of tea and strength from the touchscreen menus. It then actuates the pacman motor using function setMotorP() (the teavolver is already rotated into positon at this point from another point in the code). The continuous motor is actuated using the function PullTea which then reels in the diffuser through the self-closing slide and closes the diffuser. PullTea function is used again but in the opposite direction lowering the closed diffuser into the mug of hot water. The countdown then starts on the piTFT screen shown in mins:seconds format every 5 seconds. During the 5 seconds between countdown, the continuous motor is steeping the tea by reeling in for 2 seconds and unreeling for 2 seconds to allow the water to agitate the tea leaves and brew the tea using the PullTea function. Once the countdown time is complete or if the quit button is pressed, the PullTea function reels up the diffuser so that it is no longer in the hot water, the tea is brewed, and the script is complete and exited.

The script was run with one main while loop. The loop had two if statements that represented the two menus on the piTFT. They not only consisted of the display for each menu and associated touch recognition, but they also set the teavolver into the correct position based on tea selection and fed the proper steep time into the countdown function based on user selection.


At the start of system assembly, all parts were hot glued to the base plane or to each other. The thought was that if any parts interfere, they would not be permanently mounted and thus would be easier to be relocated and realigned within the system. A few parts in the original design were not designed with alignment references and this lead to interference when parts were starting to be assembled, especially when attached to motors. When some other parts needed to be redesigned, alignment references were added to avoid some of the alignment problems.

One of the first issues that was noticed during testing was that all of the cylinders in the teavolver were unable to be reached by the servo motor. This took a while to realize that the servo only had a travel distance of 180 degrees instead of 360 degrees. The part was designed with the intent that the motor could reach 180 degrees one direction and 180 degrees the opposite direction. After reading through the Parallax data sheets, it was understood that that thought was incorrect and the teavolver had to be redesigned to include all three tea holes within 180 degrees of range (figure 4). The two outer holes were tangent to the midplane at 180 degrees, to ensure the full diameter of the circle would be reachable. Additionally, a small recessed hole was added to the teavolver and the pacman plate on the centerline of each to help center the parts relative to the respective motors. Lastly, during the redesign a deduction in part size for the teavolver, pacman plate, and the rectangular base plate was introduced. The deduction in size was a result of various cascading geometric factors between the parts.

four photos of redesigned parts next to original parts
Figure 4. Four redesigned parts for the tea dispensing mechanism. Upper left: original teavolver design on the left showing cylinders 360 degrees around the part and redesign on the right showing cylinders constrained within 180 degrees. Upper right: redesigned part on the left showing some alignment features for supports, original part on the right. Lower left: redesigned pacman plate design on the left to accommondate the smaller teavolver holes, original pacman plate on the right. Lower right: redesigned base plate supports with new alignment features on the left, original supports lacking alignment features on the right.

The self-closing slide was not fully redesigned to the extent that it was reprinted, but it did have significant modification using cardboard. A more narrow ramp was created internal to the slide that allowed for more of a cradle for the diffuser, making it more stable in position. Additionally, two flat layers of cardboard were added to the base at the exit of the slide to create a tighter exit location. This encouraged a more secure close on the diffuser. Another addition to encourage a secure closure on the diffuser after tea was dispensed, was the addition of magnets. Two very strong magnets were glued to the outside of the diffuser on either side of the shell. This addition does mean that the tea would not necessarily be drinkable due to the glue in hot water. As such, this is more of a prototyped system overall due to this addition. It would be suggested that either a diffuser that already is constructed using magnets is used in the system, or a custom diffuser is made for the system that utilizes magnets for closure. In addition to the magnets on the diffuser, a weak flexible magnet was used at the starting point of the slide to encourage the diffuser to sit in a more upright, right-angled position for tea dispensing. Because the magnet in this location was weak, it easily released the magnet on the diffuser, allowing for a guaranteed closure when the diffuser was pulled through the slide.

As stated in the program design section, in an effort to test each aspect before converging on one large main script, various smaller test scripts were written specifically for various components in the system. Once these were proven to work, they were implemented into The programming for the standard and continuous servos took some time to understand and as such, individual test codes were written. When using the continuous servo, it was only vital for the system to use a clockwise and counterclockwise action. These actions were taken from the Parallax data sheets on pages 5 and 6, shown under Communication Protocol. To gain a clockwise rotation, the pulse width of 1.3 ms is needed and to gain a counterclockwise rotation, a pulse width of 1.7 ms is needed. The respective duty cycles were calculated from these numbers, 6.5% and 8.5% for clockwise and counterclockwise, and were hardcoded into the script. The second variable needed for actuation of the continuous motors was time. The way in which this was implemented in the code was to set the duty cycle for the respective direction that was desired and follow it with a time.sleep call of a specified number of seconds, then change the duty cycle to 0% to stop the motor action.

The standard servo had some more complications when determining how to articulate the desired motor angles. Looking at the Parallax data sheets, on page 1 it is listed that the communication band from 0 to 180 degrees uses the pulse widths of 0.75 ms to 2.25 ms. An equation was developed to associate these limits in duty cycle percentage with an input angle variable. This allowed a streamlined method to dictate angle positional output of the motor through a duty cycle input. The equation that was developed was derived to be: dutycycle = ((((top of range - bottom of range) * angle) / 180) + bottom of range), which resulted in the following when applying the specific requirements for the Parallax motor: dutycycle = int((((7.5 * angle) / 180) + 3.75) * 10000). The 10000 factor was needed in order to scale the duty cycle to be in the proper input range. Hardware PWM was used for both standard servo motors, and as such, the duty cycle is multiplied by 1,000,000 as per specifications. This equation was incredibly useful in the script for easy motor actuation and had successful output.

Results and Conclusion

There was overall success with the final system as demonstrated and the system worked as intended, meeting the majority of goals originally set forth. The only goals that were not reached where those of implementing an emergency stop feature that would stop the countdown when pressed and lift the diffuser out of the mug. The user could then press resume that would continue the countdown and place the diffuser back into the mug. Additionally, it was originally stated that the piTFT would alert when the brewing was complete. In the current design, the piTFT returns to the linux command line when complete. It was not thought to be value added to add in an alert screen when the brewing was complete since there is a visual indication that brewing is complete with the diffuser lifting out of the mug upon completion.

The panic stop and resume button did not end up working due to the steeping action of the continuous motor. This motor was programmed systematically using time.sleep to control the duration of time the motor was actuated. The piTFT did not recognize any touches on the screen when the motor was performing the functions that went through the steeping action of lower and raising the diffuser slightly in the mug during the steeping time. The panic button was considered a lower priority item than the implementation of the steeping motor action which more readily assisted in the process of tea brewing.

Future Work:

Some aspects that can be considered for future work include more refined prototyped parts such as the self-closing ramp and the teavolver. It was incredibly tedious and difficult to refill the teavolver once the tea had been dispensed, especially the green tea cylinder. The green tea cylinder sits at 60 degrees with respect to the motor positions. Due to the motor position and the design of the motor mourning points, there is a gap over the green tea cylinder that is far too small to reasonably refill by hand or by small paper funnel; The refill has to be done a few leaves at a time. This could be solved by redesigning the teavolver to have a standoff connected to the motor instead of the motor being directly attached to the top of the teavolver. Additionally, it would help if the motor mount for the teavolver had some holes in it that would allow one to look down through that area of the system and see the parts below it.

The self-closing ramp also needed the addition of cardboard inserts in order for the diffuser to properly nest inside the mechanism and allow the diffuser to close. Due to the inability to reasonably prototype in real time, the cardboard additions were the best solution given the circumstances and as such the whole part should be redesigned with the inclusion of the cardboard geometries.

Work Distribution

Kayla with system

Project group picture

Kayla Rossi Photo

Kayla Rossi

Project Contribution: All aspects in totality.

Parts List

Total: $39.32


Parallax Continuous Motor DataSheet
Parallax Standard Servo Datasheet
Pigpio Library for Hardware PWM
RPi GPIO Document
How to Control a Servo with RPi

Code Appendix

# Kayla Rossi -kmr262 
# System run code for ECE5725 Final Project - RoboTEAc Steeper
import RPi.GPIO as GPIO
import time
import os
import sys
import subprocess
import pigpio
import pygame
from pygame.locals import*

os.putenv('SDL_VIDEODRIVER', 'fbcon')
os.putenv('SDL_FBDEV', '/dev/fb0')
os.putenv('SDL_MOUSEDRV', 'TSLIB')
os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')

# pygame parameters
screen = pygame.display.set_mode((320,240))
white = [255, 255, 255]
black = [0, 0, 0]
red = [255, 0, 0]
green = [0, 255, 0]
grey = [50, 50, 50]
my_font = pygame.font.Font(None, 40)
big_font = pygame.font.Font(None, 100)

countquit = 1

##### GPIO set up  #####
GPIO.setup(19, GPIO.OUT)
cm = GPIO.PWM(19, 50)   #initalize continous motor
cm.start(0)             #give continuous motor 0 duty cycle
freqms = 20             #freq in milliseconds for 50 Hz used for calc dutycycle
ccw = (1.3/20) * 100    #duty cycle equvialent countclockwise viewed from back of motor
cw = (1.7/20) * 100     #duty cycle equivalent clockwise viewed from back of motor

# connect to pi gpio deamon for hardware PWM used for stnd servos
pi_hw = pigpio.pi()

# Standard servos pulse width from 0.75  ms to 2.25 ms - 0 to 180 deig
# inital pos 0deg - 0.75 ms/20 ms = 3.75%
# max pos 180deg - 2.25 ms/20 ms = 11.25%
# neutral pos 90 deg - 1.5 ms/20 ms = 7.5%
# duty cycle between 3.75 and 11.25% for standard servo

freq = 50
pacman = 12     #GPIO pin for motor actuating pacman plate
teavolver = 13  #GPIO pin for motor actuating teavolver
# percentage multipled by 1M because hardware PWM makes 1M steps when fully on
dczero = int(.0375 * 1000000)
dcmax = int(.1125 * 1000000)
dcmid = int(.075 * 1000000)

# call back routine and function for physical quit button on piTFT
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def GPIO27_cb(channel):
    global button_run
    global countquit 
    print('Physical quit pressed')
    countquit = 0
    button_run = False

GPIO.add_event_detect(27, GPIO.FALLING, callback=GPIO27_cb, bouncetime=300)

##### Function definitions #####

def countdown(t):
    # function  input variable t - units of seconds
    #           output - actuate motor for tea dispense into diffuser
    #                   pull diffuser through self-closing ramp and reel into mug of tea
    #                   countdown on piTFT starts based on tea and strength previously selected
    #                   steep tea for 2 seconds clockwise and 2 seconds counterclockwise
    #                   countdown updates on piTFT screen every 5 seconds
    #                   countdown ends, diffuser is pulled out of mug end of function
    PullTea(ccw,10) #pull through slide
    PullTea(cw, 10)  #drop into mug

    global countquit
    toggle = 0
    chcolor = 1
    while (t >= 0 and countquit):
        mins, secs = divmod(t,60)  
        timer = '{:02d}:{:02d}'.format(mins, secs)
        print(timer, end="\r")

        textsurf = big_font.render(str(timer), True, white)
        recttime = textsurf.get_rect(center = (160,120))
        screen.blit(textsurf, recttime)

            t -= 5
        for my_wtext, wtext_pos in wait.items():
            wtext_surface = my_font.render(my_wtext, True, white)
            wrect = wtext_surface.get_rect(center=wtext_pos)
            screen.blit(wtext_surface, wrect)




def setMotorP():
    # dutycyle multiplied by 10000 because dc var is in units (%), need to
    # convert back to decimal percentage and multiply by 1M for hardware PWM
    # multiply dc by 0.01 and then by 1000000 equates to one multiply by 10000
    # Start on 0 deg and rotate 180 for tea drop then return to 0 deg once
    # tea has been dispensed
    angleP = 180
    dc = int((((7.5*angleP)/180) + 3.75) * 10000)
    pi_hw.hardware_PWM(pacman, freq, dczero)
    print('tea drop')
    pi_hw.hardware_PWM(pacman, freq, dc)
    pi_hw.hardware_PWM(pacman, 0, 0)

def setMotorT(angleT):
    # Start on 0 degrees irrelavant of tea choice and depending on choice
    # rotate to input angleT variable (an angle between 0 and 180)
    dc = int((((7.5*angleT)/180) + 3.75) * 10000)
    pi_hw.hardware_PWM(teavolver, freq, dc)
    print('tea motor angle')
    pi_hw.hardware_PWM(teavolver, 0, 0)

def PullTea(direction,secs):
    # controls actuation of continuous motor
    # function input - a direction (cw or ccw variable as defined above) and duration of time in seconds 

def HomingP():
    # Insure pacman motor is in home position
    # if motor isn't at angle 180, set to angle 180
    if pi_hw.hardware_PWM(pacman, freq, 112500) == False:
        pi_hw.hardware_PWM(pacman, freq, 112500)

def HomingT(angleT):
    # Insure teavolver is in home position
    # if motor isn't at angle 0, set to angle 0
    dchome = int((((7.5*angleT)/180) + 3.75) * 10000)
    if pi_hw.hardware_PWM(teavolver, freq, dchome) == False:
        pi_hw.hardware_PWM(teavolver, freq, dchome)

# Dictionaries ##
dispense = {'quit':(280,200), 'Green Tea':(80,40), 'Black Tea':(80, 120), 'Herbal Tea':(80,200)}
brew = {'back':(280, 200), 'Weak':(80,40), 'Normal':(80, 120), 'Strong':(80, 200)}
wait = {'quit-->':(280, 215)}
teatype = {'Green Tea':(60), 'Normal':(1), 'Weak':(.75), 'Strong':(1.5), 'Black Tea':(180),'Herbal Tea':(180)}

button_run = True
timeout = 30
starttime = time.time()
m1 = 1
m2 = 0

while button_run:
    #time bail
    now = time.time()
    elaptime = now - starttime
    if elaptime > timeout:
        button_run = False

#menu 1 - tea type selection displayed on piTFT
    if (m1==1):
    # dispense menu rect buttons
        dispense_rect = {}
        for my_text, text_pos in dispense.items():
            text_surface = my_font.render(my_text, True, white)
            rect = text_surface.get_rect(center=text_pos)
            screen.blit(text_surface, rect)
            dispense_rect[my_text] = rect

    #look for touch and determine associated tea selection
        for event in pygame.event.get():
            if(event.type is MOUSEBUTTONDOWN):
            elif(event.type is MOUSEBUTTONUP):
                pos = pygame.mouse.get_pos()
                x,y = pos
                for (my_text, rect) in dispense_rect.items():
                        if(my_text == str('quit')):
                            print('quit pressed')
                            m2 = 0
                            m1 = 0
                            button_run = False
                            print(my_text + ' selected')
                            teatime = teatype[(my_text)]
                            ##### rotate teavolver into position based on selection
                            if (my_text == 'Green Tea'):
                                setMotorT(75) # need to go slightly past 60 in order for tea to pour in diffuser cleanly
                                print('in position')
                            elif (my_text == 'Black Tea'):
                                print('in position')
                            elif (my_text == 'Herbal Tea'):
                                print('in position')
                        m2 = 1 
                        m1 = 0

#menu 2 - tea strength selection displayed on piTFT
    if (m2==1):
        brew_rect = {}
        for my_btext, btext_pos in brew.items():
            btext_surface = my_font.render(my_btext, True, white)
            brect = btext_surface.get_rect(center=btext_pos)
            screen.blit(btext_surface, brect)
            brew_rect[my_btext] = brect
        for event in pygame.event.get():
            if(event.type is MOUSEBUTTONDOWN):
            elif(event.type is MOUSEBUTTONUP):
                pos = pygame.mouse.get_pos()
                x,y = pos
                for (my_btext, brect) in brew_rect.items():

                        if(my_btext == str('back')):
                            print('back pressed')
                            #returns back to menu 1 to change tea type selection
                            m1 = 1
                            m2 = 0

                            print(my_btext + ' selected')
                            teatime = int(teatime * teatype[(my_btext)])
                            button_run = False

print('outta here')