AN INTEGRATED SOLUTION TO REDUCING FOAM WASTE IN AN

AN INTEGRATED SOLUTION TO REDUCING FOAM WASTE IN AN
AUTOMATED BEVERAGE DISPENSER
Jacob Enoch Locken
B.S., California State University, Sacramento, 2007
PROJECT
Submitted in partial satisfaction of
the requirements for the degree of
MASTER OF SCIENCE
in
COMPUTER ENGINEERING
at
CALIFORNIA STATE UNIVERSITY, SACRAMENTO
SPRING
2010
© 2009-2010
Jacob Enoch Locken
ALL RIGHTS RESERVED
ii
AN INTEGRATED SOLUTION TO REDUCING FOAM WASTE IN AN
AUTOMATED BEVERAGE DISPENSER
A Project
by
Jacob Enoch Locken
Approved by:
__________________________________, Committee Chair
Scott Gordon, Ph.D.
__________________________________, Second Reader
Akihiko Kumagai, Ph.D.
____________________________
Date
iii
Student: Jacob Enoch Locken
I certify that this student has met the requirements for format contained in the University
format manual, and that this project is suitable for shelving in the Library and credit is to
be awarded for the project.
__________________________, Department Chair
Suresh Vadhva, Ph.D.
Department of Computer Engineering
iv
___________________
Date
Abstract
of
AN INTEGRATED SOLUTION TO REDUCING FOAM WASTE IN AN
AUTOMATED BEVERAGE DISPENSER
by
Jacob Enoch Locken
Beer foam is a major source of waste in most bars today. An average bar will lose
close to $10,000 a year due to beer waste. Automated Beverage Dispensers attempt to
eliminate this waste, however most fail due to the inability to prevent excess amounts of
beer foam during pouring of a beer. The root cause of beer foam is best described by
Henry’s Law which describes the relationship between the solubility of a gas in a liquid
due to temperature and pressure.
Automated Beverage Dispensers currently regulate pressure however they do not
control the beverage temperature; this must be regulated in order to eliminate beer foam.
The beverage temperature can be regulated by an Automated Beverage Dispenser
through the use of a custom computer controlled heat exchanging system. This heat
exchanging system combines hardware, software, and mechanical components in a way
that is energy efficient, environmentally safe, and cost effective. In the context of an
Automated Beverage Dispenser this integrated solution holds temperature and pressure
constant thus eliminating beer waste due to foam.
, Committee Chair
Scott Gordon, Ph.D.
______________________
Date
v
TABLE OF CONTENTS
List of Figures .................................................................................................................. viii
Chapter
1
2
BACKGROUND ......................................................................................................... 1
1.1
The Problem ......................................................................................................... 1
1.1
Common Causes of Foamy Beer .......................................................................... 3
1.1.1
Chemistry ...................................................................................................... 4
1.1.2
Automated Bartender Context ...................................................................... 5
THE PROJECT............................................................................................................ 6
2.1
Project Goal .......................................................................................................... 6
2.1.1
2.2
Construction ......................................................................................................... 8
2.2.1
Construction Design...................................................................................... 8
2.2.2
Construction Implementation...................................................................... 13
2.3
Hardware ............................................................................................................ 20
2.3.1
Hardware Design ........................................................................................ 20
2.3.2
Hardware Implementation .......................................................................... 21
2.4
3
Design Overview .......................................................................................... 6
Software ............................................................................................................. 25
2.4.1
Software Design .......................................................................................... 25
2.4.2
Software Implementation ............................................................................ 27
TESTING................................................................................................................... 34
3.1
Testing Plan ........................................................................................................ 34
3.2
Testing Results ................................................................................................... 35
3.2.1
No Insulation Manual Pumping Test .......................................................... 35
3.2.2
No Pumping No Insulation Test ................................................................. 37
3.2.3
Pump Cycling No Insulation Test ............................................................... 39
3.2.4
Constant Pumping No Insulation Test ........................................................ 40
vi
4
3.2.5
No Pumping with Insulation Test ............................................................... 41
3.2.6
Pump Cycle with Insulation Test ................................................................ 42
3.2.7
Constant Pumping with Insulation Test ...................................................... 43
CONCLUSION ......................................................................................................... 45
4.1
Findings .............................................................................................................. 45
4.2
Future Enhancements ......................................................................................... 47
Appendix ........................................................................................................................... 50
Bibliography ..................................................................................................................... 80
vii
LIST OF FIGURES
1. Figure 1.1.1 Wasted Beer Calculation .................................................................. 2
2. Figure 2.2.1 S.S.A.T Cross Section ...................................................................... 8
3. Figure 2.2.2 Previous BeverageBotTM Dispensing Tower .................................. 8
4. Figure 2.2.3 Initial Heat Exchanger Design.......................................................... 8
5. Figure 2.2.4 Previous Dispensing Spout ............................................................. 12
6. Figure 2.2.5 Proposed Generic Spout Design ..................................................... 12
7. Figure 2.2.6 Inner Shell of S.S.A.T .................................................................... 13
8. Figure 2.2.7 Inner Shell with Spout .................................................................... 14
9. Figure 2.2.8 Half the Outer Shell of the S.S.A.T ................................................ 14
10. Figure 2.2.9 S.S.A.T Entrance and Exit Chambers ............................................ 14
11. Figure 2.2.10 S.S.A.T Spout ............................................................................... 15
12. Figure 2.2.11 S.S.A.T Heat Exchanger ............................................................... 15
13. Figure 2.2.12 Flow path through the reservoir ................................................... 16
14. Figure 2.2.13 Flow Dividers ............................................................................... 16
15. Figure 2.2.14 Reservoir in pieces ....................................................................... 16
16. Figure 2.2.15 Finished Reservoir ........................................................................ 17
17. Figure 2.2.16 Finished S.S.A.T Heat Exchanger ................................................ 17
18. Figure 2.2.17 Finished Hood .............................................................................. 18
19. Figure 2.2.18 Partially Insulated hood ................................................................ 18
20. Figure 2.2.19 Dispenser Spout ............................................................................ 19
viii
21. Figure 2.3.1 Schematic of Pump Controller ....................................................... 23
22. Figure 2.4.1 Block Diagram of BeverageBotTM’s software ................................ 25
23. Figure 2.4.2 Block diagram of the Existing BeverageBotTM software, the
BeverageBotTM Integrated Application, the Silent Mode application,
and the Temperature Recording Application ................................... 28
24. Figure 2.4.3
Screenshot of SSAT Monitoring Application when pump is off .. 32
25. Figure 2.4.4
Screenshot of SSAT Monitoring Application when pump is on ... 33
26. Figure 3.2.1 Test 1, No Insulation and Manual Pumping (NIMP) ..................... 36
27. Figure 3.2.2 Test 2, No Pumping and No Insulation (NPNI) ............................. 38
28. Figure 3.2.3 Test 3, Pump Cycling with No Insulation (PCNI) ......................... 40
29. Figure 3.2.4 Test 4, Constant Pumping and No Insulation (CPNI) .................... 41
30. Figure 3.2.5 Test 5, No Pumping with Insulation (NPWI) ................................. 42
31. Figure 3.2.6 Test 6, Pump Cycling with Insulation (PCWI) .............................. 43
32. Figure 3.2.7 Test 7, Constant Pumping with Inulation (CPWI) ......................... 44
33. Figure 4.1.1 Beer Poured by SSAT heat exchanger ........................................... 45
34. Figure 4.1.2 Beer Poured by Original Beverage Dispenser ................................ 45
35. Figure 4.2.1 Pump Energy Usage Calculation .................................................... 47
ix
1
Chapter 1
BACKGROUND
1.1
The Problem
One of the biggest issues in the world of automated beverage dispensing is the
ability to dispense a non-foamy beer. Beer foam causes many difficulties in sophisticated
beverage dispensing systems, like the BeverageBotTM. The BeverageBotTM dispenses
beer with a dispensing device called the Perfect Head Every Time (PHET) system. This
PHET system is mechanical device which holds a pub glass, then tilts the glass to a 30
degree angle, a valve opens and beer begins to flow into the glass. Once the glass is
approximately 95% full the PHET device slowly tilts the glass down. After the glass
reaches an upright position, the beer stops being dispensed and the glass is filled with a
non-foamy, beer also known as the “perfectly poured beer.” The problem with the
BeverageBotTM PHET system, similar to other automated beverage dispensers, is the
inability to control the amount of beer foam created during dispensing. When there is too
much or too little foam poured into the glass the PHET system will not predictably pour a
perfect beer.
With traditional bartending foamy beer is a constant issue. After conversations
with multiple bartenders and bar owners I discovered foamy beer is one of the biggest
sources of waste in the bar business. Most bartenders when pouring a beer from a tap
throw open the tap and wait a few seconds before placing the glass beneath it. The
2
purpose is to evacuate the foamy beer from the dispensing line. This allows the bartender
to pour a perfect beer.
It may seem insignificant just a splash of beer wasted for every beer poured,
however one conversation with a bar owner revealed otherwise. On average his
customers consume 10-20 kegs per month at anywhere from $3 to $7 a beer with an
average beer foam waste of 2 ounces per beer. Figure 1.1.1 shows the breakdown of cost
due to beer foam waste over a period of one year. For this small bar almost $10,000 a
year is lost because of this problem. Imagine the magnitude of waste large bars or
restaurants could incur.
1984 𝑜𝑧 1𝑝𝑖𝑛𝑡
∗
= 124 𝑝𝑖𝑛𝑡𝑠 = 124 𝑏𝑒𝑒𝑟𝑠
1 𝑘𝑒𝑔
16𝑜𝑧
2 𝑜𝑧 𝑜𝑓 𝑤𝑎𝑠𝑡𝑒
∗ 124 𝑝𝑖𝑛𝑡𝑠
1 𝑝𝑖𝑛𝑡
248 𝑜𝑧 𝑜𝑓 𝑤𝑎𝑠𝑡𝑒
=
1 𝑘𝑒𝑔
248 𝑜𝑧 𝑜𝑓 𝑤𝑎𝑠𝑡𝑒
$5.00
= 15.5 𝑏𝑒𝑒𝑟𝑠 ∗
16𝑜𝑢𝑛𝑐𝑒𝑠
1 𝑏𝑒𝑒𝑟
$77.50 𝑤𝑎𝑠𝑡𝑒𝑑 120 𝑘𝑒𝑔𝑠
∗
1 𝑘𝑒𝑔
1 𝑦𝑒𝑎𝑟
= $9,300.00 𝑤𝑎𝑠𝑡𝑒𝑑 𝑝𝑒𝑟 𝑦𝑒𝑎𝑟
=
Figure 1.1.1 - Wasted Beer Calculation
3
1.2
Common Causes of Foamy Beer
In the United States the majority of breweries deliver their kegs with an inside
pressure range of 12-14 pounds per square inch (psi) (13 psi being the most common).
The reason kegs are kept in this range is to maintain enough pressure on the beer to retain
the internal carbonation. Once the keg is less than 13 psi the internal carbonation is
released and will produce a large layer of foam at the top of the keg. The initial release of
carbonation results in foamy beer and then as time progresses the lack of carbonation
results in flat beer. If a keg’s internal pressure exceeds 13 psi when the beverage is
dispensed it is forced out of the nozzle with such ferociousness the collision between the
beer and the bottom of the glass results in foam (Micro Matic [1]). Most beer enthusiasts
believe when hooking up a keg to CO2 (used to create pressure and maintain carbonation
inside the keg) the pressure should be kept low and slowly turned up until the flow rate of
the beer is as desired. This common assumption is inaccurate. As mentioned previously
when a keg is tapped using pressures lower than 13 psi, the CO2 is immediately expelled
from the beer causing a layer of foam. The proper way to tap and maintain a keg is to
always use the recommended pressure as stated by the Brewer.
In the United States most draft beer is not pasteurized thus it can spoil if not
refrigerated. At temperatures above 50 degrees Fahrenheit non-pasteurized beer will
grow bacteria and spoil (commonly identified by a bitter taste). At 32 degrees Fahrenheit
the water in the beer will freeze causing foamy beer and at 38.5 degrees Fahrenheit the
CO2 releases causing foam. Therefore the target temperature range for non-pasteurized
4
beer is 36-38 degrees Fahrenheit. This range is the temperature of the beer at any point
between the keg and the nozzle. Not refrigerating the beer all the way to the dispensing
point is the most common cause of foamy beer (Micro Matic[1]).
1.2.1
Chemistry
Beer foam is composed of CO2 bubbles which rise from the beer itself. Starches,
sugars, and yeast are among the ingredients used in brewing beer. When the yeast
metabolizes the sugars and starch, CO2 gas is produced. There is a fine balance between
temperature pressure and beer foam. This balance was explained in 1803 by Chemist
William Henry. He stated the weight of a gas dissolved is proportional to the pressure of
the gas acting on the liquid (Dr. Walt Volland[2]). This is known as Henry’s Law.
Henry’s Law is best explained in two parts. The first part explains when gas
pressure increases the solubility of said gas in the liquid increases, the reverse is also true.
The second part states as temperature of the liquid increases the solubility of the gas
decreases, and as the temperature of the liquid decreases solubility increases.
When applying Henry’s law to the application of pouring the perfect beer, the
focus must be on the variables: pressure and temperature. If the beer is kept inside the
keg with too much pressure then the beer will hold too much dissolved gas. When the
over carbonated beer is no longer contained by the keg, the CO2 gas inside the beer is
higher than the atmospheric pressure acting upon it. This difference in pressure causes the
CO2 to be expelled resulting in foam. If the pressure is too low the beer will contain
5
minimal carbonation and will become flat. Likewise, if the temperature is too great
(above 50 degrees Fahrenheit) the beer will become flat and spoiled, or when the
temperature is too low (32 degrees Fahrenheit) the result is frozen and foamy beer.
1.2.2
Automated Bartender Context
The automated beverage dispenser is the perfect tool for accurately controlling the
temperature and pressure variables from Henry’s Law. The BeverageBotTM is equipped
with the ability to regulate the pressure inside of the keg. This apparatus can be used to
maintain the beer at a constant pressure as recommended by the brewers. The
BeverageBotTM is built around a mini refrigerator as a tool to keep the ingredients cold.
This refrigeration does not extend from the keg to the dispensing point, and therefore
fails to keep the beer at a constant temperature. With the pressure variable eliminated, the
BeverageBot’s TM only uncontrolled variable in overcoming Henry’s Law is temperature.
6
Chapter 2
THE PROJECT
2.1
Project Goal
The goal of this project is to pour the “perfect beer.” The driving forces of this
goal, are to design and integrate a solution which is environmentally friendly, energy
efficient, cost effective, and meets the regulations of the Food and Drug Administration.
2.1.1
Design Overview
As previously noted the main obstacle of this project is temperature. In order for
the beer foam to be controlled the temperature of the beer must be regulated from the keg
to the dispensing unit. One of the most effective ways of cooling is through traditional
circulating Freon refrigeration systems. These systems are expensive as well as
dangerous; the leaking of Freon into the atmosphere can have extremely detrimental
effects on the ozone. Therefore using a heat exchanger to keep the beer cold was the most
probable solution. The heat exchanger is cost effective, environmentally friendly, and
easier to implement. The mini refrigerator was the most cost effective cooling device for
the integration of the heat exchanger into the existing BeverageBotTM.
The design uses a pump to move cold liquid from the refrigerator into the
dispensing tower (the hood). The cold liquid will envelop the beer lines, maintaining the
temperature from keg to the dispensing point. The pump is computer controlled and
integrated into the BeverageBot’sTM existing control system. The control system uses
temperature probes to monitor the coolant temperature at different locations within the
7
pumping system. Finally, the current status of the system is integrated into the existing
BeverageBot’s user interface (UI).
The subsequent sections will discuss the design and implementation of the three
major portions of this project construction, hardware, and software.
8
2.2
2.2.1
Construction
Construction Design
2.2.1.1 Heat Exchanger
Initially the heat exchanger best suited for the project
was a shell and tube (SAT) heat exchanger. An SAT heat
exchanger consists of a shell (a pipe, in its most basic form)
which passes hot or cold fluid over one or more inner tubes
(Absolute Astronomy[3]). The tubes are then cooled or heated
through the conduction of thermals in the fluid. The existing
Figure 2.2.3 - Previous
BeverageBotTM Dispensing
Tower
BeverageBotTM (black tower shown in Figure 2.2.2) had
all the components necessary to build an SAT. This
included a cold source (the mini refrigerator) directly
below the tower which is used to moderate temperature
of the coolant before pumping it through the shell which
in turn cooled the
beverage lines. The
initial design being
described is
Figure 2.2.1 - Initial Heat Exchanger
Design
Figure 2.2.2 - S.S.A.T Cross Section
exemplified in Figure
2.2.3. This design holds the coolant inside the gray square tubing which envelops the blue
9
tubes, creating a text book SAT heat exchanger. Temperatures in the mini refrigerator of
the BeverageBotTM can approach the freezing point of water therefore the coolant must
contain an additive to prevent freezing. Two common approaches to prevent the coolant
from freezing are antifreeze, or a salt water solution. Both these solutions would prevent
freezing. However, they are corrosive compounds and could possibly compromise the
vinyl outer walls of the beverage lines (shown in blue in Figure 2.2.3). If the vinyl outer
walls were to fail the toxic coolant would mix with the beverage causing contamination.
This contamination is unacceptable for maintaining the project goal of adhering to FDA
regulations.
To eliminate this potential hazard the basic SAT design was modified to contain
two shells, one inside another, creating an empty chamber for the beverage lines to run
through. In this design if the outer wall of the inner beverage lines were to fail, the leak
would quickly be discovered before it compromised the beverage lines. Figure 2.2.1
depicts this design; the green torus (shaped similar to a donut) is demonstrating the
coolant between the two shells. The blue tubes are the beverage lines; the figure depicts
(in black) how they are suspended in an open space. This guarantees no direct contact
between the beverage lines and the coolant. This is the major difference between the SAT
design and the shell in shell and tube (SSAT) design.
The beverage lines in original SAT design are in direct contact with the coolant
and therefore are cooled through conduction. Conduction is the transfer of heat energy
10
from adjoining particle to adjoining particle. The tubes in the SSAT design are not in
direct contact with the coolant and are not cooled by conduction; instead they are cooled
by convection. Convection is the transfer of heat by the means of moving currents in a
liquid or gas, in the case of this project the gas is air. As the air warms it rises from the
refrigerator into the chamber, depicted in black in Figure 2.2.1. This creates a current
and draws the cold air from the refrigerator into the chamber. When the cold coolant is
pumped into the tower, conduction will occur between the inner shell’s outer wall and a
thin layer of air inside the inner shell. The cooled thin layer of air (inside the inner shell)
reduces the temperature of the beverage lines (Mansfield[4]).
Heat transfer by convection is superior to heat transfer by conduction in liquids
and gas. Dale Durran and Yaga Beres, demonstrate this principle in their experiment
“Comparing Heat Transfer by Convection and Conduction” (Durran and Beres [5]). Their
experiment introduced ice cubes made from dyed water to two different glasses, one cube
is sunk to the bottom and the other cube floats. Due to the principles of heat transfer
liquids and gas are interchangeable.
The most effective methodology for transferring heat in a solid is through
conduction. By definition a solid has no flowing compounds, and therefore convection
cannot take place within the solid object. Heat transfer instead takes place at the
molecular level when atoms come in to contact with one another, the electrons of the
atoms transfer energy in the form of heat.
11
In order to gain the benefits of conduction without submersing the beverage tubes
in the coolant a conductive material such as steel was used to build the inner shell. Due
to the steel’s conductivity the coolant cools the inner shell which then cools the beverage
lines. Conduction occurs between the tubes and the inner shell because the beverage lines
are in contact with the inner shell. Convection also takes place within the liquid, which
aids in the transfer of heat.
With the small inner shell the design uses conduction, and convection to keep the
lines cool while avoiding submersion of the hoses in a corrosive material. If the inner
shell wall fails coolant will begin to leak into the refrigerator which will quickly be
noticed by the administrator, instead of the coolant breaking down the vinyl beverage
tubes resulting in contamination of the beverage. The inner shell design maintains the
products compliance with FDA regulations.
The SSAT design is based on the idea that coolant which enters the chamber is
cold (between the shells) and the coolant that leaves the chamber will be cooled before it
reenters. As mentioned before this cooling will be done by a mini refrigerator. A mini
refrigerator uses a similar technology to a heat exchanger to quickly cool a cold plate,
which can reach temperatures below freezing. This cold plate is mounted inside the mini
refrigerator and as it cools it will in turn cool the refrigerator. In order to reduce the
temperature of the coolant the design includes a reservoir which will come in direct
contact with the cold plate and allow conduction to take place thus cooling the reservoir’s
12
contents (the coolant). To ensure the coolant is cooled before reentering the heat
exchanger a path was necessary for the coolant, this path ensures the coolant which
renters the heat exchanger is the coolant with the most direct
contact with the cold plate.
2.2.1.2 Dispensing Spout Design
Another portion of the Automated Beverage Dispenser
which was addressed in the design was the dispensing spout. In
Figure 2.2.4 - Previous
Dispensing Spout
an automated beverage dispenser there has to be a method to
control the angle at which the beverage lines dispense the
beverage. Where this dispensing occurs is referred to as the spout. If the beverage lines
are not held in place and the angles set fixed at the spout the product will not be
dispensed into the glass, rather it will spill. In previous versions of the BeverageBotTM
hand-made spout spacers were built as seen in Figure 2.2.4. This design implemented a
spout system which can be quickly removed and different style spouts be used for
different applications (i.e. dispensing soda).
In order to cut down on costs, a 3D modeling software
called Google SketchUp was used to create a virtual
representation of potential spout systems. The modeling
software allowed for the creation of a generic spout system
Figure 2.2.5 - Proposed
Generic Spout Design
which allows for future design and implementation of more
13
complex spout systems. Figure 2.2.5 shows the generic spout spacer design; the design is
3”outside diameter with two 1/4” notches which connects with the SSAT heat exchanger
hood. This spout holds the 9 beverage lines in an evenly spaced vertical arrangement.
This spout is for general beverage dispensing, it can pour anything from juices to liquor
but has no specialized mixing functions.
2.2.2
Construction Implementation
With limited time, money and resources 1/8 inch cold rolled steel was used
because it is an inexpensive material. Mig welding (also known as wire feed welding), a
common and familiar technique was used to adhere the steel pieces together. Mig
welding is done by running an electrical current through the steel until it becomes molten
hot at which point weld wire is fed into the molten steel fusing the materials together, this
creates the weld.
In order to simplify installation the heat exchanger was built in two separate
pieces; the hood and the reservoir. The hood is the SSAT heat exchanger; the reservoir is
a container which is attached near the refrigerator’s cold plate which is used to quickly
reduce the temperature of the coolant.
2.2.2.1 Constructing the Hood
The hood was built in specific stages to guarantee all
parts could be welded together. It was also built in stages to
ensure the coolant would travel from the hoods entrance to its
Figure 2.2.6 – Inner Shell of
S.S.A.T
14
exit. If the coolant did not travel around the hood completely then the beverage lines
would not be kept cool thus compromising the entire design.
The first step was to build the inner shell chamber of the hood. In order to take
advantage of conduction as explained above the outside dimensions of the inner shell
needed to be 1.5”x 2” of steel tubing. This is not a common size, therefore a 2”x2” square
tube had to be cut length wise and welded together. After the tube was fabricated a leak
test was performed by capping one end and filling it with water. Figure 2.2.6 shows the
tube after a successful leak test, the resulting rectangular tube
was 1.5”x2”x30”.
The next step was to add the inner shell’s spout portion.
The spout is the portion of the heat exchanger where the beverage
Figure 2.2.7 – Inner Shell
with Spout
lines dispense the product. For this step a 1.5”x2” square was cut
out of a 3” inside diameter exhaust tube. The spout was then welded to the rectangular
tube and a 90 degree angle was added, this resulted in the complete inner shell of the
SSAT, as shown in Figure 2.2.7.
The next step was to create the outside shell. This was
done by taking flat stock metal (1/8” strips of metal cut into any
Figure 2.2.9 – S.S.A.T
Entrance and Exit
Chambers
height/width necessary) and welding a larger identical shape to
the inner shell. As shown in Figure 2.2.9, a 1” divider was
welded into the center between the inner and outer shells to
Figure 2.2.8 – Half the Outer
Shell of the S.S.A.T
15
force the coolant to flow from the entrance to the exit of the hood. In order for all pieces
of the inner and outer shells to be welded and water tight the outer chamber had to be
built in two pieces, Figure 2.2.8 shows one half of the outer chamber. After creating the
other half of the outer shell and welding it together a distinct path was available for the
coolant to flow. In Figure 2.2.9 the inner camber is shown, this will eventually contain
the beverage lines. The inner shell is shifted towards the entrance flow chamber (chamber
on the left of Figure 2.2.9). This was done to completely envelope the inner chamber
(which contains the beverage lines), with the coolant which is
fresh from the reservoir. The idea behind this is to guarantee the
inner chamber is enveloped in the coldest possible coolant; thus
Figure 2.2.10 – S.S.A.T
Spout
allowing conduction the best chance to cool the inner chamber.
The chamber on the right of Figure 2.2.9 will contain the coolant returning to the
reservoir, this coolant will be warmer than the coolant on the
left. Figure 2.2.10 shows the inner and outer shells around the
spout. The complexity of welding dividers within the spout
forced the design to assume the coolant flow coming from the
dividers in between the inner/outer shells would be enough to
force the coolant around the spout.
After welding on a base, drilling entrance/exit holes for
Figure 2.2.11 – S.S.A.T Heat
Exchanger
the coolant tubes, and welding adapters designed to accept the
16
coolant tubes, the result was a water tight hood (see figure 2.2.11).
2.2.2.2 Constructing the Reservoir
The reservoir was created using the same technology as the hood. As mentioned
in the design section the reservoir needed to have a path through which the coolant could
flow from the entrance to the exit. The reservoir was constructed to be thin so conduction
from the cold plate could occur much faster. Rectangular tubing was used to eliminate
Figure 2.2.13- Flow Dividers
Figure 2.2.14- Reservoir in
pieces
Figure 2.2.12- Flow path
through the reservoir
unnecessary welds, reducing the potential for leaks caused by defective welds.
The first step in building the reservoir was to cut four 3”x1”x14” tubes and then
to cut two 1”x1”x18” side pieces. As shown in Figure 2.2.14, the 1”x1” square sides were
cut to allow coolant to flow between the 3” horizontal tubes. In order to force the coolant
to flow from the entrance to the exit dividers were welded into the 1”x1” square tubes as
seen in Figure 2.2.13.
The vertical and horizontal pieces were welded together; mounting brackets were
added in order to mount the reservoir to the cold plate and adapters were added to
connect the entrance and exit vinyl tubes. Once these additions were complete a fully
17
functioning reservoir with a path for the coolant was complete (coolant path is depicted in
red, Figure 2.2.12).
With the heat exchanging hood and reservoir complete the next step was to
integrate these items into the existing BeverageBotTM. The hood and reservoir were
painted so as to protect the steel from rust. The reservoir was then mounted to the cold
plate inside the refrigerator and the hood was mounted to the counter, as seen in Figures
2.2.15 and 2.2.16.
Figure 2.2.16 – Finished
S.S.A.T Heat Exchanger
Figure 2.2.15 – Finished
Reservoir
As described below in the testing plan, the goal was to test the differences
between an insulated heat exchanger environment and a non-insulated environment. Once
the testing was complete in the non-insulated heat exchanger environment final
construction began on the insulated portion of the heat exchanger hood.
18
The insulated version of the hood consisted of a wooden
box which encapsulates the hood with a large air cavity
between the SSAT exchanger and the wooden exterior. The
purpose of the wooden box was to have the ability to mount the
BeverageBotTM’s touch screen and PHET device. Once the box
Figure 2.2.18 – Partially
Insulated hood
was built/primed, the screen and PHET devices were mounted
and wired; the compartment was then filled with insulating
spray foam. The spray foam used is open cell foam. When open
cell foam expands small pockets of air are trapped. This type of
foam was chosen for its affordable cost however it is not as
dense and has a reduced R-value (measurement used to rate the
Figure 2.2.17 – Finished
Hood
insulation factor) as compared to closed cell foam. In Figures
2.2.17 and 2.2.18 you can see the insulated version of the SSAT
exchanger.
2.2.2.3 Constructing the Dispensing Spout
The final step was to build the spout spacer system. As described in the Design
section above a 3D model was created to the exact size specifications. This model was
then given to Jim Ster, head of the Machine Shop at Sac-State, he then took a chunk of
polypropylene and used a lathe to turn the chunk of material into the spherical shape you
see in Figure 2.2.19. After Ster had the spherical shape he then used a drill press to drill
the 9 holes seen in Figure 2.2.19, he also used it to drill a hole on each side of the
19
spherical piece. Plastic dowels (1/4” in diameter) where then forced into the holes using a
press. These dowels created a means to connect the spout spacer system to the heat
exchanging hood. Figure 2.2.19 shows the final spacer.
Figure 2.2.19 – Dispenser
Spout
20
2.3
Hardware
This project’s design uses the existing refrigerator’s cooling mechanism to keep
the beverage lines at temperatures between 32 and 40 degrees Fahrenheit. The SSAT
Heat Exchanger design relies on the ability to circulate the coolant from the reservoir into
the hood and back again, thus cooling the beverage lines. In order to move liquid from
one place to another the most common methods are gravity, a vacuum, or a pump. The
method of moving liquid using gravity was not an option due to the liquid travelling from
a lower point to a higher point in a sealed container. The next option was to create a
vacuum which would suck the coolant from the reservoir into the hood. This option was
expensive, complex and bulky. The final choice was to use a pump.
2.3.1
Hardware Design
There are many different types of pumps available hydraulic ram pumps, injection
pumps, gravity pumps, and impeller pumps. The criteria this pump needed to meet are as
follows; as small as possible, pumping control, and powerful enough to pump the coolant
3 feet higher in elevation with minimal pressure. It was decided to use an
electromechanical impeller pump. An electromechanical impeller pump is a pumping
device that uses an electrical motor to spin an axle. This axle has an impeller attached to
it which pushes liquid from an entrance tube to an exit tube, similar in function to the
main wheel on a paddle boat. An impeller pump can be controlled by switching the
power on and off which will start and stop pumping. This style of pump meets all the
needed criteria with the added bonus of producing minimal heat due to friction.
21
In order to implement a system that would only pump the coolant when the
beverage lines are warm more than just an electromechanical impeller pump was needed.
Also needed, was a way to measure temperature at certain locations. The temperature
readings from those locations would be used as a deciding factor to turn the pump on or
off as well as signaling the pump to pump or not. To fulfill this task a temperature sensor,
a controller/brain, and on/off circuitry for the impeller pump were necessary.
The BeverageBotTM uses a mini PC to control various electromechanical devices
thus it was decided to use this as the main controller of the pumping system. Following
the same practices/technologies put in place by the existing BeverageBotTM the control
logic was written using JAVA technology and the electromechanical interfacing
hardware used consisted of multiple Phidgets devices.
2.3.2
Hardware Implementation
Three different Phidgets devices were used to measure the temperature of three
different locations. The three devices are; Phidget Temperature Sensor 1051, Precision
Temperature Sensor 1124, and Phidget Interface Kit 8/8/8. The 1051 Phidget is a printed
circuit board (PCB) that interfaces the mini PC’s control logic and a thermocouple using
a universal serial bus (USB) connection. A thermocouple is an analog measuring device
that measures the voltage between two dissimilar metal wires. The voltage between these
two metal wires is a function of the temperature between them. As a result the
thermocouple can accurately identify the temperature at the junction of these wires
22
(Groover, pg118 [6]). The 1051 Phidget also contains a thermocouple at the cold junction
(where the wires connect to the PCB) which is used to adjust for inaccuracies in the
measuring of the thermocouple. The 1124 Phidget is also a PCB interface board. This
PCB uses a ratiometric sensor to measure temperature. The 1124 Phidget’s ratiometric
temperature sensor measures the ratio between the input and output voltages of a circuit.
This ratio is a function of temperature and therefore can measure ambient temperature to
within a ½ degree Celsius. The 1124 Phidget does not use a USB connection to interface
with the PC instead it makes an analog connection to the Phidget Interface Kit 8/8/8 PCB.
This PCB is an Interface board which uses a USB connection to allow the PC to
drive/read 8 digital inputs, 8 digital outputs, and 8 analog Inputs.
23
Phidget Interface
Kit 8/8/8/
NPN
Transistor
5VDC
5
6
+5vdc
+Vout
-5vdc
-Vout
Relay
+Vin
-Vin
1
2
PUMP
3
4
12VDC
Figure 2.3.1 Schematic of Pump Controller
To turn the pump on and off the mini PC’s control logic sends a signal to one of
the Phidget Interface Kit 8/8/8’s digital outputs. This digital output then drives a relay
that sends the required voltage to the electromechanical impeller pump turning it on or
off. This initial design failed due to the Phidget Interface Kit 8/8/8’s digital output not
supplying enough current to drive the relay. To correct this instead of driving the relay
directly it was decided to have the control logic drive a small transistor which in turn
would send 5 volts at 1 ampere to the relay switch, which then passes 12 volts at 1
24
ampere to the impeller pump turning it on or off. For more details see the schematic in
Figure 2.3.1.
25
2.4
Software
2.4.1
Software Design
By using the mini PC as the main brain of the project it allowed for the
consolidation of resources and tighter integration of the pumping system into the existing
BeverageBotTM by integrating the pumping control logic into the existing BeverageBotTM
software.
The BeverageBotTM uses a mini PC running
Phidget Hardware Devices
the Ubuntu distribution of Linux as its Operating
System (OS). This OS has been configured to
launch the BeverageBotTM application on startup.
Phidget USB API
The BeverageBotTM application is a multithreaded
JAVA process that uses Phidgets USB API to
Hibernate
JPA
Layer
control the beverage dispensing portion of the
BeverageBot
Control Logic
BeverageBot
FrameWork
system (this consists of all the motors, sensors,
pumps, etc). This JAVA process also has access to a
MySQL database using an implementation of the
MySQL DB
BeverageBot
SWING UI
Java Persistence API (JPA) called Hibernate. This
database holds all the BeverageBotTM’s information
in regards to drinks, customer information, and
BeverageBot Touch Screen
And Human Interfacing Devices
Figure 2.4.1 – Block Diagram of
BeverageBotTM’s software
various BeverageBotTM configurations. This JAVA
26
process also interacts with the customer using a SWING user interface (UI) displayed on
a touch screen. The BeverageBotTM Framework (Figure 2.4.1) is the main engine of this
JAVA process, it includes the Phidgets control logic and UI’s data provider.
The BeverageBotTM 2.0 software is a fairly mature application with a well defined
structure. In order to achieve a scalable and cutting edge UI the next version of the
software, BeverageBotTM 3.0, must adopt a services type model where the majority of the
computations are done inside of an Enterprise Java Bean (EJB) container, such as JBoss.
This would allow the BeverageBotTM to provide light weight UIs that can be quickly reskinned or redesigned to keep customers engaged. This paradigm is sometimes referred
to as a services model or a model view controller pattern (MVC).
A MVC design pattern is setup to allow developers the ability to eliminate
duplicated code and to create thin UI layers. The model portion of a MVC is where the
majority of the computations are. This layer is where all the common functionality exists.
The controller portion is where the behavior of the application is specified. This layer can
be exchanged from existing MVC applications to create different behaviors without
altering the model or the view. The view is the UI portion of the application. By using
this design pattern the application’s designer can re-use code, quickly change the
behavior of an application, and change the look of the application without having to
change any behavioral or model code.
27
In the first release of the BeverageBotTM 3.0 software the new JavaFX technology
for the UI layer will be used. The reason for using the JavaFX technology is its ability to
create rich and interactive UIs, which is one of the major weaknesses of the
BeverageBotTM 2.0 software. Due to JavaFX being in its adolescence research was
needed to overcome the obstacles of using JAVA and JavaFX together in the MVC
pattern.
2.4.2
Software Implementation
Three applications were created for three distinct purposes. Each of the three
applications controls the SSAT heat exchanger’s pumping system. A block diagram of
Phidget Hardware Devices
Hibernate
JPA
Layer
Phidget USB API
BeverageBot
Control Logic
BeverageBot
FrameWork
8/8/8 Phidget
Controller
MySQL DB
BeverageBot
SWING UI
Thermal Monitor
Temperature Listener
Temperature Listener
Coolant Pump
Controller
8/8/8 Phidget
Controller
Coolant Pump
Controller
Temperature
Recording
Application
JavaFX Listener
Mapping
Thermal Monitor
Temperature Listener
8/8/8 Phidget
Controller
Coolant Pump
Controller
Pump System Listener
Pump System Listener
U
ay
pl
is
D re
to tu
n ra ns
io e io
ct mp dit
I A Te on
C
BeverageBot
2.0
SSAT
Monitoring
Application
Thermal Monitor
Silent Mode
Application
Java
Temperature Recorder
Temperature
Recordings
File
JavaFX UI
BeverageBot Touch Screen
And Human Interfacing Devices
Figure 2.4.2 – Block diagram of the Existing BeverageBotTM software, the BeverageBotTM Integrated Application, the
Silent Mode application, and the Temperature Recording Application
28
the entire software used in this project is shown in figure 2.4.2.
2.4.2.1 Silent Mode Application
The first application, referred to as the Silent Mode Application, was designed to
run the SSAT heat exchanger’s pumping system. This application is made up of three
major pieces; the thermal monitor, the Coolant Pump Controller, and the 8/8/8 Phidget
Controller. The Thermal Monitor implements an action listener framework that supplies
the changes in temperature found by the 1051 Phidget temperature sensor. As noted
above the 1051 Phidget temperature sensor has two temperature sensors: a thermocouple,
and an ambient temperature sensor. The Phidgets API surfaces an action listener for the
thermocouple sensor which allows the Thermal Monitor to receive notifications of
temperature changes sensed by the thermocouple. This API however does not surface an
action listener for the ambient temperature sensor; resulting in the Thermal Monitor being
unaware of any changes in ambient temperature. To receive notifications of ambient
temperature changes, a timer was created inside the Thermal Monitor which periodically
checks the ambient temperature and if a change has occurred an action is fired signifying
this change. The Coolant Pump Controller (CPC) facilitates the control logic used by the
SSAT heat exchanger’s pumping system. The CPC registers itself with the Thermal
Monitor to receive notifications when temperature changes occur. The CPC also creates a
timer that runs after every pumping cycle. Once this timer has reached the full time
duration the CPC will compare the desired temperature range of the SSAT’s inner shell
29
(dispensing temperature) to the actual dispensing temperature and if the actual
temperature is warmer than the desired range it starts another pump cycle. These
temperature settings and times are specified in a properties file and are read in when the
application starts. To control the pumping cycles the CPC sends on/off calls to the 8/8/8
Phidget Controller. The 8/8/8 Phidget Controller makes the appropriate calls to the
Phidgets USB API library which in turn sends signals to the Phidgets hardware via USB.
The CPC also implements an action listener framework that forwards the Thermal
Monitor’s temperature events, and supplies temperature changes found by the 1124
Phidget temperature sensor. The Phidgets USB API does not offer an action listener for
the 1124 Phidget temperature sensor so the CPC has no way of knowing when the
temperature sensed by the 1124 Phidget temperature sensor has changed. To know when
the temperature changes the Thermal Monitor creates a timer to periodically check the
sensor for changes, if changes are found the Thermal Monitor fires the temperature
changed event. This application is named the silent mode application because it supplies
all the functionality necessary to run the SSAT heat exchanger’s coolant pumping system
but does so without surfacing anything to the BeverageBotTM user.
The Silent Mode Application facilitates all the functionality necessary to interact
with the SSAT heat exchanger’s coolant pumping system which completely fulfils the
requirements of the MVC’s model layer. Therefore I decided to use Apache’s ANT
technology to bundle up this application into a self-executing Java archive resource
(JAR) so it can be reused by other applications. Instead of rewriting or referencing this
30
code for each new application all the new application must do is import this jar
(SSATController.jar), register itself to the CPC’s listener framework and finally start the
CPC. The Silent Mode Application is bundled into a self executing jar instead of just a
regular jar so it can be deployed with less work.
2.4.2.2 Temperature Recording Application
The second application, referred to as the Temperature Recording Application,
records temperature changes and pump times during testing. This application records the
pump start times and temperature changes which can be imported into Microsoft Excel to
generate graphs. By looking at this data in chart form trends and/or any correlations
between temperature time and pumping cycles becomes more apparent.
The Temperature Recording Application imports the SSATController.jar registers
itself with the CPC and then starts the CPC. It then expands on the Silent Mode
application by adding a timer which at specific intervals (as defined in the properties file)
records the current system time and temperatures. This application also records the
system time whenever the pump cycle is started. It takes this recorded data and writes it
to two files, one for temperature recordings and one for pump time recordings. Each of
these files is created in comma-separated values (CSV) format. This format is specifically
how Microsoft Excel is able to generate graphs from the imported temperature data.
The Temperature Recording application is also bundled into a self executing jar
(TempRecorder.jar) using Apache’s ANT technology.
31
2.4.2.3 SSAT Monitoring Application
The third application, referred to as the SSAT Monitoring Application,
experimented with the JavaFX technology in a JAVA MVC environment. The SSAT
Monitoring Application was created to expose the current status of the SSAT heat
exchanger’s coolant pumping system to the BeverageBotTM user. As shown in Figure
2.4.2 the BeverageBotTM SSAT Monitoring Application is composed of three major
components the SSAT Controller, a JavaFX listener mapping, and a JavaFX UI. This
application similar to the Temperature Recording Application imports the
SSATController.jar, registers with the CPC, and then starts the CPC.
The JavaFX technology was designed to allow the developer to make JAVA calls
from JavaFX source code but does not allow JAVA code to make calls into JavaFX.
Without this ability the JavaFX UI would not have notifications of temperature changes
and pumping cycles; instead it would have to poll the JAVA code which is inefficient.
One JavaFX developer’s blog “Life: a Journey to Explore” (Interoperability Between
JavaFX and JAVA [7]), suggests three solutions for calling JavaFX code from Java code.
The solution that best fits the SSAT Monitoring Application is through the use of Java
Interfaces. This is done by extending a Java interface within a JavaFX class. The JavaFX
listener mapping component was therefore similarly created to this solution. This
component implements the CPC’s listener interface which allows the SSAT Controller
(made entirely of Java code) to notify the JavaFX component when temperatures change
and pumping cycles occur.
32
The last component of this application was the JavaFX UI. In order to maintain
the goal of exposing the status of the SSAT heat exchanger’s coolant pumping system to
the user three temperatures and the state of the coolant pump (pumping or not) were
needed to be displayed. JavaFX is built on a powerful UI framework called the Scene
Graph. One UI component (widget) was created to display temperature and then reused to
display the temperature sensor’s three current values in the JavaFX scene (the visual
component of the application is displayed on the screen). You can see this widget being
reused three times in Figure 2.4.3. This widget was created by using a JavaFX Custom
Node (JavaFX equivalent to a widget) which allows you to combine multiple UI
components into a single node. In the case of the temperature node multiple pieces of
text, effects, gradients and shadows were combined. In order to portray the coolant
pumps state a hypnotizing spiral animation was used. When the pump starts the spiral is
faded in and rotated clockwise, when the pump stops the animation fades away (see
figure 2.4.4).
Figure 2.4.3- Screenshot of SSAT Monitoring Application when pump is off
33
Figure 2.4.2- Screenshot of SSAT Monitoring Application when pump is on
34
Chapter 3
TESTING
3.1
Testing Plan
The main goal of this project was to eliminate the foam created while dispensing a
beer. The obvious test was to compare the foam produced while pouring a beer from the
existing automated beverage dispenser to the foam in a beer poured by the new version.
This test however does not provide much information on how well the proposed solution
actually performed. As we learned in the background section the major contributor to
foamy beer is temperature. Therefore, a more insightful test is to compare the differences
in the old and new beverage dispenser’s poured beer temperatures. However this test plan
has a major flaw beer is expensive!
As mentioned above in the background section beer foam is mainly caused when
the beer temperature exceeds 38 degrees Fahrenheit. The refrigerator holds the keg at a
constant temperature range between 33-36 degrees Fahrenheit so the only place the beer
could exceed 38 degrees is outside the shelter of the refrigerator (the hood). It was
therefore decided the cheapest and most effective way to test this solution was to
compare the difference in temperatures inside the hood between both solutions. The
temperatures inside the hood of the existing BeverageBotTM were measured and were
found to be at least equal to the outside air temperature. The success of the new hood can
therefore be measured by comparing the temperatures inside the hood to the outside air
temperature.
35
To accurately record these temperatures over time a software application was
created(for details see the Software Implementation section) that recorded times,
temperatures, and pumping statuses in a way that allowed Microsoft Excel graphs to be
generated of the results. Another aspect of this project that needed to be tested was the
differences in temperature in both the insulated and non-insulated versions of the SSAT
heat exchanger design. Data was collected during 24 hour periods for three different
pumping cycles; no pumping, pumping constantly, and pump cycling (pumping the
entire reservoir into the hood then allowing it to cool before the next pumping cycle).
These three tests were done on both the insulated and non-insulated versions of the SSAT
heat exchanger. An initial test was done to demonstrate the effects on the hood’s
temperature when pumping coolant into the hood.
3.2
3.2.1
Testing Results
No Insulation Manual Pumping Test
The initial test, No Insulation Manual Pumping (NIMP), was done to prove that
the SSAT heat exchanger did reduce the temperature inside the hood, which will be
referred to as the dispensing temperature (DT). Figure 3.2.1 shows a graph of this initial
test. In this test I had the Phidget 1051 temperature sensor running to measure the
dispensing temperature as well as the ambient temperature (AT) but at the time of the test
I was using a thermometer to measure the refrigerator’s temperature. This test did not use
a constant pumping cycle the pump instead was turned on manually at different intervals.
36
I did this to test and emphasis a few theories.
Figure 3.2.1 – Test 1, No Insulation and Manual Pumping (NIMP)
If you look at the line in red (the Dispensing temperature) you will notice that the
initial two pump cycles had absolutely no effect on the dispensing temperature. The
reason for this is not entirely apparent when looking at this graph. The refrigerator
temperature (RT) was less than 40 degrees when the first pump cycle occurred which
should have dropped the hood’s temperature. The vital information this chart is not
referencing are the following facts; 30 minutes before the NIMP test started the
refrigerator began cooling and minutes before the NIMP test was initiated the pump was
37
turned on twice which resulted in the dispensing temperature dropping from
approximately 70 degrees to approximately 55 degrees. As the chart exemplifies, when
the pump was turned on at 6:24 pm the coolant inside the reservoir was approximately
the same temperature as the dispensing temperature (55 degrees). At 6:25pm the coolant
from the reservoir was pumped in to the hood and due to the equivalent temperature there
was no change in the dispensing temperature. At this point the coolant inside the
reservoir is approximately 70 degrees. After 15 minutes of cooling the reservoir the pump
was cycled. This pump cycle allowed the temperature of the refrigerator to drop due to
the exchange of coolant inside the reservoir (from 70 degrees to 55 degrees). This pump
cycle also resulted in an increase of dispensing temperature due to the exchange of
coolant inside the hood.
At approximately 8:04 pm the pump was again cycled. The coolant being pumped
into the hood at this point had been cooling inside the reservoir for one hour and twentysix minutes and as a result the dispensing temperature drops from 67 degrees to 47
degrees. At 8:09 pm the dispensing temperature begins to increase due to the effects of
the dramatic difference between the ambient temperature and the dispensing temperature
(47 degrees vs. 67 degrees).
3.2.2
No Pumping No Insulation Test
The next test, No Pumping No Insulation (NPNI) demonstrates the effects on the
dispensing temperature when the hood has no insulation and the coolant is not being
38
pumped. During the NPNI test and all following tests the Temperature Recording
Application was used to collect temperatures over 24 hour periods.
Figure 3.2.2 – Test 2, No Pumping and No Insulation (NPNI)
Figure 3.2.2 shows the dispensing temperature when the coolant pump is off
being dictated by the ambient temperature. The dispensing temperature holds within 5 –
10 degrees colder than the outside temperature. If one were to draw trend lines on these
three data plots the dispensing temperature and ambient temperature trend lines would be
almost identical. During the NPNI test the BeverageBotTM was placed close to two other
refrigerators that were constantly (by the looks of the graph every twenty minutes)
39
producing excess amounts of heat, this caused the constant spiking seen in Figure 3.2.2.
One thing to note from these spikes is a 10 degree spike in ambient temperature is only
causing a five degree spike in the dispensing temperature. This tells us the hood even
without pumping or insulation is sheltering the beverage lines from the ambient
temperature.
3.2.3
Pump Cycling No Insulation Test
In the third test, Pump Cycling No Insulation (PCNI) tests the effects on the
dispensing temperature when the hood has no insulation and the reservoir is being
completely flushed into the hood every five minutes. Figure 3.2.3 shows this test enacted
over a 24 hour period. During the test the DT corresponded with RT. The DT stayed
consistently 3-5 degrees warmer than the RT even through a 10 degree ambient
40
temperature spike, which reached 69 degrees.
Figure 3.2.3– Test 3, Pump Cycling with No Insulation (PCNI)
3.2.4
Constant Pumping No Insulation Test
In the fourth test, Constant Pumping No Insulation (CPNI) the effects on the DT
are explored when the hood has no insulation and the coolant is constantly being pumped
through the system. Figure 3.2.4 shows similar results to the PCNI test. In the PCNI test
the DT followed the RT within 3–5 degrees. From these results it can be concluded there
is no difference between PCNI and CPNI pumping methods.
41
Figure 3.2.4 – Test 4, Constant Pumping and No Insulation (CPNI)
3.2.5
No Pumping with Insulation Test
The first test with insulation, No Pumping with Insulation (NPWI) tests the
effects on the DT when the hood is insulated from the elements and the coolant is not
being pumped. Figure 3.2.5 demonstrates similar results to the NPNI test. The differences
between the two tests are the DT in the NPWI test is within 3 degrees of the AT instead
of 5 as seen in NPNI test. The NPWI test does not have the temperature spikes in the AT
and DT. This was due to the Phidget 1051 sensor residing on the opposite side of the
insulated hood as the external refrigerators thus eliminating the refrigerators effect on the
42
Phidget 1051 temperature sensor. Figure 3.2.5 demonstrates the external refrigerators
spiking temperatures still had an effect on the mini refrigerator temperature.
Figure 3.2.5 – Test 5, No Pumping with Insulation (NPWI)
3.2.6
Pump Cycle with Insulation Test
The next test on the insulated SSAT heat exchanger, Pump Cycle with Insulation
(PCWI) had similar results to the PCNI test. As you can see in Figure 3.2.6, the DT and
RT averaged around three degrees colder than the PCNI test. This could have been
43
caused by the AT being 5 degrees colder than during the PCNI test or it could have been
caused by the insulation.
Figure 3.2.6 – Test 6, Pump Cycling with Insulation (PCWI)
3.2.7
Constant Pumping with Insulation Test
The last test, Constant Pumping with Insulation (CPWI) tested the effects of DT
when constantly cycling the coolant through the insulated SSAT heat exchanger system.
Figure 3.2.7 demonstrates similar effects to that of the CPNI test in regards to the DT
following the RT within about 3-5 degrees.
44
Figure 3.2.7 – Test 7, Constant Pumping with Inulation (CPWI)
45
Chapter 4
CONCLUSION
4.1
Findings
The main goal of this
project was to eliminate the
excessive foam produced when
pouring a beer in an automated
beverage dispenser, specifically
the BeverageBotTM. As shown in
Figure 4.1.1 – Beer Poured
by Original Beverage
Dispenser
Figure 4.1.2 – Beer Poured
by SSAT heat exchanger
Figures 4.1.1 and 4.1.2 the SSAT
heat exchanger addition to the BeverageBotTM had a dramatic decrease in the amount of
beer foam produced. By maintaining the CO2 pressure at a constant 13 psi and reducing
the dispensing temperature of the beer the excessive foam created during the pouring of a
beer can be significantly reduced.
The main implementation goal was to reduce the dispensing temperature. Using
an existing refrigeration system as opposed to a custom Freon refrigeration system saved
money, and helped protect the environment from hazardous Freon exposure. With the
implemented design, light was shed on many conjectures which together can be used as
grounds to build more sophisticated and successful beverage dispensers.
These conjectures were solidified during the testing of this project and are as
follows:
46
o If the temperature of the coolant in the reservoir is not significantly less
than the dispensing temperature a pumping cycle will not reduce the
temperature of the beer.

If the coolant in the reservoir is warmer it will cause adverse
effects on the beer temperature.
o If the ambient temperature is much greater than the dispensing
temperature and the coolant is not pumped, the dispensing temperature
will slowly increase.

The NIMP test demonstrated as ambient temperatures increased so
did the rate at which dispensing temperature increased over time.
For example, dispensing temperature increased at a rate of 2.5
degrees an hour when the difference between dispensing
temperature and ambient temperature was 15 degrees. This rate
increased to 4 degrees an hour when the difference was 20 degrees.
o Refrigerator temperatures will increase if the coolant in the reservoir is
much greater than the current refrigerator temperature.
o Without pumping and no insulation the dispensing temperature is directly
proportional to the ambient temperature. The NPNI test suggests without
pumping or insulation the hood is sheltering the beverage lines from the
ambient temperature.
47
From these conjectures 3 major guidelines were developed and should be
followed when using a heat exchanger in an automated beverage dispenser for the
purpose of reducing beer foam.
Rule 1:
The more the coolant in the heat exchanger is insulated from the
ambient temperature the better.
Rule 2:
The heat exchanger must use some form of a pumping cycle.
Rule 3:
The colder you make the reservoir the more efficient the heat
exchanger.
4.2
Future Enhancements
The SSAT heat exchanger designed and implemented in this project adheres
loosely to these three rules. However future enhancements could be implemented into
this design to more closely satisfy these rules. The principles used in this project can also
The Pump is a 12 volt pump drawing 1 ampere so:
12𝑣 ∗ 1 𝑎 = 12 𝑤𝑎𝑡𝑡𝑠 = 12 𝑤𝑎𝑡𝑡𝑠
The Constant pump pumps 12 watts for 60 minutes straight therefore;
12 𝑤𝑎𝑡𝑡𝑠 ∗ 60 min/ 60 min = 12 𝑤𝑎𝑡𝑡 ℎ𝑜𝑢𝑟𝑠
The Cycling pump pumps 12 watts for 1.76 minutes every 6.76 minutes
therefore;
60 min/6.76 𝑚𝑖𝑛 = 8.88 𝑝𝑒𝑟𝑖𝑜𝑑𝑠
8.88 𝑝𝑒𝑟𝑖𝑜𝑑𝑠 ∗ 1.76 𝑚𝑖𝑛𝑢𝑡𝑒𝑠 𝑜𝑛 𝑡𝑖𝑚𝑒 = 15.63 𝑚𝑖𝑛𝑢𝑡𝑒𝑠 𝑜𝑛 /ℎ𝑜𝑢𝑟
12 𝑤𝑎𝑡𝑡𝑠 ∗ (15.63
𝑚𝑖𝑛𝑢𝑡𝑒𝑠
60
𝑚𝑖𝑛𝑢𝑡𝑒𝑠) = 3.26 𝑤𝑎𝑡𝑡 ℎ𝑜𝑢𝑟𝑠
Figure 4.2.1 – Pump Energy Usage Calculation
48
be applied to the automated dispensing of other carbonated beverages (i.e. soda).
The initial version of the SSAT heat exchanger was insulated using an
inexpensive over the counter open cell spray foam. This type of foam is not as efficient
an insulator as closed cell foam and therefore could be replaced to prevent the dispensing
temperature from being affected by the ambient temperature. The open cell foam in this
project does not seal the outer shell of the SSAT from the outside air. As a result the
water vapor in the air comes in to contact with the outer shell creating condensation,
which over long periods of time can cause problems. If the SSAT heat exchanger was
properly sealed the condensation would be eliminated, a closed cell foam would be the
best solution.
When reviewing the test results the differences in dispensing temperature between
the constant pumping and the pumping cycling methods were negligible. As
demonstrated in Figure 4.2.1 the pump cycling method uses a quarter of the energy of the
constant pumping method. The constant pumping method is a more energy efficient
choice.
The SSAT heat exchanger, in a best case scenario, can only get the dispensing
temperature as cold as the refrigerator temperature. The refrigerator in this project is only
capable of reaching temperatures as low as 34 degrees Fahrenheit, which isn’t conducive
to maintaining a target temperature of 36-38 degrees. The next iteration of this heat
exchanger should use a refrigeration source that can reach temperatures below freezing.
49
The pumping system would therefore have to withstand the below freezing temperatures
and in turn would be more efficient and effective in reducing the amount of beer foam.
50
APPENDIX
Source Code
Coolant PumpController.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package controller;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import monitor.AmbientTemperatureChangeEvent;
import monitor.InterfaceKit_8_8_8_Driver;
import monitor.ThermalMonitor;
import monitor.ThermoCoupleTemperatureChangeEvent;
import monitor.PhidgetTemperatureListener;
import monitor.ratiometricTemperatureChangeEvent;
/**
*
* @author jake
*/
public class CoolantPumpController implements PhidgetTemperatureListener{
private volatile static CoolantPumpController instance = null;
private Timer dispensingTempTimer;
private int timerDelay = 1000; //wait 10 ms before starting timer
private int coolDownPeriod = 300000;//Check the Temperature of the Dispensing tower every 5
minutes.
private int timeToPump = 60000;//Time the pump will be running for, time it takes to heat exchange.
private double tempThreshold = 2.7;//The threshold at which the pump should turn on (In celsius, 2.7
is roughly 37degF)
private int pumpLocation = 0;//Location on the phidget 8_8_8 of the Coolant pump
private InterfaceKit_8_8_8_Driver interfaceKitPhidget;
private ThermalMonitor thermalMonitor;
private ArrayList<PumpSystemListener> pumpListeners = new ArrayList<PumpSystemListener>();
private double refrigeratorTemp = 0.0;
51
private double ambientTemp = 0.0;
private double dispensingTemp = 0.0;
/**
* Get Properties from Temperature.properties file
*/
public void getPropertiesFromFile(){
Properties props = new Properties();
try {
props.load(getClass().getClassLoader().getResourceAsStream("Temperature.properties"));
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//coolDownPeriod
String coolDownPeriodString = props.getProperty("coolDownPeriod");
if(coolDownPeriodString!=null && coolDownPeriodString.length()>0){
this.coolDownPeriod= Integer.parseInt(coolDownPeriodString);
}
//timeToPump
String timeToPumpString = props.getProperty("timeToPump");
if(timeToPumpString!=null && timeToPumpString.length()>0){
this.timeToPump= Integer.parseInt(timeToPumpString);
}
//tempThreshold
String tempThresholdString = props.getProperty("tempThreshold");
if(tempThresholdString!=null && tempThresholdString.length()>0){
this.tempThreshold= Double.parseDouble(tempThresholdString);
}
//pumpLocation
String pumpLocationString = props.getProperty("pumpLocation");
if(pumpLocationString!=null && pumpLocationString.length()>0){
this.pumpLocation= Integer.parseInt(pumpLocationString);
}
//silentMode
System.setProperty("silentMode", props.getProperty("silentMode"));
System.out.println("**************************************************************
*******************");
52
System.out.println("***Inside CoolantPump Controller the coolDownPeriod is=
"+this.coolDownPeriod);
System.out.println("***Inside CoolantPump Controller the timeToPump is=
"+this.timeToPump);
System.out.println("***Inside CoolantPump Controller the tempThreshold is=
"+this.tempThreshold);
System.out.println("***Inside CoolantPump Controller the pumpLocation is=
"+this.pumpLocation);
System.out.println("***Inside CoolantPump Controller silentMode
is="+System.getProperty("silentMode"));
System.out.println("**************************************************************
*******************");
}
/**
* Constructor
*/
private CoolantPumpController(){
getPropertiesFromFile();
thermalMonitor = ThermalMonitor.getInstance();
interfaceKitPhidget = InterfaceKit_8_8_8_Driver.getInstance();
//Register this with the ThermalMonitor
thermalMonitor.addPhidgetTemperatureListener(this);
//Monitor the Temperature at the Dispensing tower and turn the pump on when necessary
startMonitoringDispensingTemp();
}
/**
* @return
* Handle to this Singleton
*/
public static CoolantPumpController getInstance(){
if(instance == null){
synchronized (CoolantPumpController.class) {
instance = new CoolantPumpController();
}
}
return instance;
}
/**
* This method will check the Thermo-Couple Temperature
* Every CoolDown period and see if a heat exchange is needed.
* If so it will cause a pump cycle
*/
private void startMonitoringDispensingTemp(){
53
this.dispensingTempTimer = new Timer();
this.dispensingTempTimer.schedule(new TimerTask(){
@Override
public void run() {
int result = Double.compare(dispensingTemp, tempThreshold);
if(result > 0){
firePumpingStartedEvent();
turnPumpOn();
new Timer().schedule(new TimerTask(){
@Override
public void run() {
firePumpingStoppedEvent();
turnPumpOff();
}
},timeToPump);
}
}
},timerDelay,coolDownPeriod);
}
/**
* Turn 8/8/8 pump on
*/
private void turnPumpOn(){
try {
interfaceKitPhidget.assertLine(pumpLocation);
} catch (Exception e) {
try {
interfaceKitPhidget.deAssertLine(pumpLocation);
} catch (Exception e1) {
System.err.println("Lets Hope the pump is not stuck on!");
e1.printStackTrace();
}
e.printStackTrace();
}
}
/**
* Turn 8/8/8 pump off
*/
private void turnPumpOff(){
try {
interfaceKitPhidget.deAssertLine(pumpLocation);
} catch (Exception e) {
System.err.println("Lets Hope the pump is not stuck on!");
e.printStackTrace();
}
}
54
/***************************************************************************
* Listener Framework
***************************************************************************/
public void addPumpingNotificationListener( PumpSystemListener l){
if(pumpListeners == null){
pumpListeners = new ArrayList<PumpSystemListener>();
}
pumpListeners.add(l);
}
public void removePumpingNotificationListener( PumpSystemListener l){
if(pumpListeners != null){
pumpListeners.remove(l);
}
}
private void firePumpingStartedEvent(){
if(pumpListeners != null && !pumpListeners.isEmpty()){
for (PumpSystemListener l : pumpListeners) {
l.pumpingStarted();
}
}
}
private void firePumpingStoppedEvent(){
if(pumpListeners != null && !pumpListeners.isEmpty()){
for (PumpSystemListener l : pumpListeners) {
l.pumpingStopped();
}
}
}
@Override
public void
thermoCoupleTemperatureChanged(ThermoCoupleTemperatureChangeEvent e) {
dispensingTemp = e.getThermoCoupleTemperature();
//Pass event on to the Pump Listeners
if(pumpListeners != null && !pumpListeners.isEmpty()){
for (PumpSystemListener l : pumpListeners) {
l.thermoCoupleTemperatureChanged(e);
}
}
String sMode = System.getProperty("silentMode");
if(sMode ==null || !sMode.equals("1")){
System.out.println("ThermoCouple Temp Has Changed to =>"+dispensingTemp);
}
}
55
@Override
public void ambientTemperatureChanged(AmbientTemperatureChangeEvent e) {
ambientTemp = e.getAmbientTemperature();
//Pass event on to the Pump Listeners
if(pumpListeners != null && !pumpListeners.isEmpty()){
for (PumpSystemListener l : pumpListeners) {
l.ambientTemperatureChanged(e);
}
}
String sMode = System.getProperty("silentMode");
if(sMode ==null || !sMode.equals("1")){
System.out.println("Ambient Temp Has Changed to =>"+
}
}
ambientTemp);
@Override
public void ratiometricTemperatureChanged(ratiometricTemperatureChangeEvent e) {
refrigeratorTemp = e.getRatiometricTemperature();
if(pumpListeners != null && !pumpListeners.isEmpty()){
for (PumpSystemListener l : pumpListeners) {
l.ratiometricTemperatureChanged(e);
}
}
String sMode = System.getProperty("silentMode");
if(sMode ==null || !sMode.equals("1")){
System.out.println("Ratiometric Temp Has Changed to =>"+refrigeratorTemp);
}
}
/**
* Gracefully shutdown
*/
public void shutdown(){
this.dispensingTempTimer.cancel();
thermalMonitor.shutdown();
interfaceKitPhidget.shutdown();
instance = null;
}
/**
* TESTING...
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
CoolantPumpController cntrlr = new CoolantPumpController();
System.out.println("HIT ANY KEY TO QUIT!!!!");
System.in.read();
56
cntrlr.shutdown();
System.exit(0);
}
}
PumpSystemListener.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package controller;
import monitor.PhidgetTemperatureListener;
public interface PumpSystemListener extends PhidgetTemperatureListener{
/**
* Pump Started Method
*/
public void pumpingStarted();
/**
* Pump Stopped Method
*/
public void pumpingStopped();
}
RecordTemps.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package controller;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import monitor.AmbientTemperatureChangeEvent;
import monitor.ThermoCoupleTemperatureChangeEvent;
import monitor.ratiometricTemperatureChangeEvent;
/**
*
* @author jake
*/
public class RecordTemps implements PumpSystemListener{
57
private double ambientTemp = -33;
private double thermoCoupleTemp = -33;
private double ratiometricTemp = -33;
private double prevAmbientTemp = -34;
private double prevThermoCoupleTemp = -34;
private double prevRatiometricTemp = -34;
private String recordingsFile;
private PrintWriter pumpTimesPrintWriter;
private PrintWriter tempsPrintWriter;
private Timer recordingTimer;
/**
* Constructor
*/
public RecordTemps() {
//GET Phidget Temperature Sensor 1124 props
Properties props = new Properties();
try {
props.load(getClass().getClassLoader().getResourceAsStream("Temperature.properties"));
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//Recordings File Output Location
String fileOutString = props.getProperty("recordingsFile");
if(fileOutString!=null && fileOutString.length()>0){
this.recordingsFile= fileOutString;
}
System.out.println("**************************************************************
*******");
System.out.println("***Inside RecordTemps the recordingsFile is=
"+this.recordingsFile);
System.out.println("**************************************************************
*******");
try {
File tempsfile = new File(recordingsFile);
this.tempsPrintWriter = new PrintWriter(new FileWriter(tempsfile));
this.pumpTimesPrintWriter = new PrintWriter(new FileWriter(new
File(tempsfile.getParent()+File.separator+"PumpTimes_"+tempsfile.getName())));
} catch (IOException e) {
58
// TODO Auto-generated catch block
e.printStackTrace();
System.err.println("Failed to get FileWriter Exiting...");
System.exit(-1);
}
//Start the PumpController (AKA Start the pump Cycling)
CoolantPumpController.getInstance().addPumpingNotificationListener(this);
//Start Recording
this.startRecordingTemps();
}
/**
* Start Recording Temps
*/
private void startRecordingTemps(){
this.tempsPrintWriter.println("Time
1124Sensor Temp");
Ambient Temp ThermoCouple Temp
//Run every 30 seconds and record Ambient ThermoCouple Temps
this.recordingTimer = new Timer();
recordingTimer.schedule(new TimerTask(){
@Override
public void run() {
double ambDifference = Math.abs(ambientTemp - prevAmbientTemp);
double thermDifference = Math.abs(thermoCoupleTemp - prevThermoCoupleTemp);
if(ambDifference >= .5 || thermDifference >= .5){
//convert to Fahrenheit
prevAmbientTemp = ((9 * ambientTemp)/5) + 32;
prevThermoCoupleTemp = ((9 * thermoCoupleTemp)/5) + 32;
prevRatiometricTemp = ((9 * ratiometricTemp)/5) + 32;
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("H:mm:ss");
tempsPrintWriter.println(sdf.format(cal.getTime())+","+prevAmbientTemp+","+prevThermoCoupleTemp+
","+prevRatiometricTemp);
tempsPrintWriter.flush();
}
}
},10,30000);
}
59
@Override
public void ambientTemperatureChanged(AmbientTemperatureChangeEvent e) {
ambientTemp = e.getAmbientTemperature();
}
@Override
public void thermoCoupleTemperatureChanged(ThermoCoupleTemperatureChangeEvent e) {
thermoCoupleTemp = e.getThermoCoupleTemperature();
}
@Override
public void ratiometricTemperatureChanged(ratiometricTemperatureChangeEvent e) {
}
/**
* Write Pump Times to PumpTimes_ file
*/
@Override
public void pumpingStarted() {
//Record Pump Time
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("H:mm:ss");
pumpTimesPrintWriter.println(sdf.format(cal.getTime()));
pumpTimesPrintWriter.flush();
}
@Override
public void pumpingStopped() {//Do Nothing
}
/**
* Gracefully shutdown
*/
public void shutdown(){
recordingTimer.cancel();
if(pumpTimesPrintWriter != null){
pumpTimesPrintWriter.close();
}
if(tempsPrintWriter != null){
tempsPrintWriter.close();
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
shutdown();
}
60
public static void main(String[] args) throws IOException {
new RecordTemps();
System.out.println("HIT ANY KEY TO QUIT!!!!");
System.in.read();
System.exit(0);
}
}
AmbientTemperatureChangeEvent.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package monitor;
import java.util.EventObject;
public class AmbientTemperatureChangeEvent extends EventObject {
private static final long serialVersionUID = 1L;
double ambientTemperature;
public AmbientTemperatureChangeEvent(Object source,double ambientTemperature) {
super(source);
this.ambientTemperature = ambientTemperature;
}
public double getAmbientTemperature() {
return ambientTemperature;
}
}
InterfaceKit_8_8_8Driver.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package monitor;
import com.phidgets.InterfaceKitPhidget;
import com.phidgets.PhidgetException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
61
public class InterfaceKit_8_8_8_Driver
{
private volatile static InterfaceKit_8_8_8_Driver instance;
private InterfaceKitPhidget ik ;
private int serialNum;
public boolean[] input = new boolean[8];
private InterfaceKit_8_8_8_Driver () throws PhidgetException{
ik = new InterfaceKitPhidget();
/*ik.addAttachListener(new AttachListener() {
public void attached(AttachEvent ae) {
//System.out.println("attachment of " + ae);
}
});
ik.addDetachListener(new DetachListener() {
public void detached(DetachEvent ae) {
//System.out.println("detachment of " + ae);
}
});
ik.addErrorListener(new ErrorListener() {
public void error(ErrorEvent ee) {
//System.out.println("error event for " + ee);
}
});
ik.addInputChangeListener(new InputChangeListener() {
public void inputChanged(InputChangeEvent oe) {
switch (oe.getIndex())
{case 0:
if (oe.getState() == true)
{
}
else
{
}
break;
}
//TODO FIX CUP DETECTOR AND SENSOR LOGIC...
}
});
ik.addOutputChangeListener(new OutputChangeListener() {
public void outputChanged(OutputChangeEvent oe) {
//System.out.println(oe);
}
});*/
getPropertiesFromFile();
62
ik.open(serialNum); // useful for one Interface Kit Phidget, but we could open a specific phidget if
we enter its serial number.
System.out.print("Waiting for 8/8/8 Phidget to attach.");
final Timer timer= new Timer();
timer.schedule(new TimerTask(){
@Override
public void run() {
System.out.print(".");
}
},0,1000);
ik.waitForAttachment(); // waits until Phidget is attached
timer.cancel();
}
/**
* Get Properties from Temperature.properties file
*/
public void getPropertiesFromFile(){
Properties props = new Properties();
try {
props.load(getClass().getClassLoader().getResourceAsStream("Temperature.properties"));
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//serialNum888
String serialNum888String = props.getProperty("serialNum888");
if(serialNum888String!=null && serialNum888String.length()>0){
this.serialNum= Integer.parseInt(serialNum888String);
}
System.out.println("***The serialNum of the InterfaceKit_8_8_8 phidget is=
"+this.serialNum);
}
public void assertLine(int Loc)throws Exception{
ik.setOutputState(Loc,true);
}
public void deAssertLine(int Loc)throws Exception{
ik.setOutputState(Loc,false);
}
public void toggleLine(int loc) throws PhidgetException{
if(ik.getOutputState(loc)){
63
ik.setOutputState(loc,false);
}else{
ik.setOutputState(loc,true);
}
}
/**
* Gracefully ShutDown.
*/
public void shutdown(){
System.out.print("Shutting Down Interface Kit 8/8/8...");
if(ik != null){
try {
ik.close();
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ik = null;
}
instance = null;
}
/**
* @return
* A handle to this Singleton.
*/
public static InterfaceKit_8_8_8_Driver getInstance(){
if(instance == null){
synchronized (InterfaceKit_8_8_8_Driver.class) {
try{
instance = new InterfaceKit_8_8_8_Driver();
}catch(PhidgetException e){
e.printStackTrace();
}
}
}
return instance;
}
/**
* Assumes location is the Analog location to a phidgets
* Temperature Sensor 1124.
*
* @param location
* @return
* (analog reading * .2222) - 61.111
* This is the temperature in celsius
* @throws PhidgetException
*/
64
public double getTemperatureSensorReading(int location) throws PhidgetException{
return ((ik.getSensorValue(location) * .2222) - 61.111);
}
/**
* If using an analog ratiometric sensor this method should be
* called with on being true to turn on ratiometric analog reading.
* @param on
* @throws PhidgetException
*/
public void setAnalogRatiometricState(boolean on) throws PhidgetException{
ik.setRatiometric(on);
}
}
PhidgetTemperatureListener.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package monitor;
import java.util.EventListener;
public interface PhidgetTemperatureListener extends EventListener {
/**
* Called when the Ambient Temperature of the Phidget Changes.
* @param e
*/
public void ambientTemperatureChanged(AmbientTemperatureChangeEvent e);
/**
* Called when the Thermo Couple Temperature of the Phidget Changes.
* @param e
*/
public void thermoCoupleTemperatureChanged(ThermoCoupleTemperatureChangeEvent e);
/**
* Called when the 1124 Phidgets Temperature Changes.
* @param e
*/
public void ratiometricTemperatureChanged(ratiometricTemperatureChangeEvent e);
}
RatiometricTemperatureChangeEvent.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package monitor;
65
import java.util.EventObject;
public class ratiometricTemperatureChangeEvent extends EventObject {
private static final long serialVersionUID = 1L;
double ratiometricTemperature;
public ratiometricTemperatureChangeEvent(Object source,double ratiometricTemperature) {
super(source);
this.ratiometricTemperature = ratiometricTemperature;
}
public double getRatiometricTemperature() {
return ratiometricTemperature;
}
}
ThermalMonitor.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package monitor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import com.phidgets.PhidgetException;
import com.phidgets.TemperatureSensorPhidget;
import com.phidgets.event.TemperatureChangeEvent;
import com.phidgets.event.TemperatureChangeListener;
/**
* This Singleton is in charge of monitoring the TemperatureSensorPhidget.
* It will monitor the RAtiometric 1124 sensor as well as the
* 1051s Ambient Temperature and ThermoCouple temperature.
* @author jake
*/
public class ThermalMonitor {
public volatile static ThermalMonitor instance = null;
private Timer ambiantTempTimer;
private Timer ratiometricTimer;
private TemperatureSensorPhidget tempSensorPhidget;
private InterfaceKit_8_8_8_Driver interfaceKitPhidget;
private double triggerVal = .01;//Temperature Delta needed to invoke changeListener
66
private ArrayList<PhidgetTemperatureListener> temperatureListeners = new
ArrayList<PhidgetTemperatureListener>();
private int timerDelay = 1000; //wait 10 ms before starting timer
private int timerPeriod = 1000;//Check ambient temp every second
private double lastAmbTemperature = 0;
private double lastRatTemperature = 0;
private int tempSensorLocation = 7;//Location on the phidget 8_8_8 of the Temperature sensor
1124
public ThermalMonitor() {
getPropertiesFromFile();
//Create tempSensorPhidgetPhidget
try {
tempSensorPhidget = new TemperatureSensorPhidget();
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
interfaceKitPhidget = InterfaceKit_8_8_8_Driver.getInstance();
if(tempSensorLocation != -1){ //If there is a 1124 temperature sensor attached
try {
interfaceKitPhidget.setAnalogRatiometricState(true);
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Monitor the 1124 Phidget
startMonitoringRatiometricTemp();
}
//Monitor the 1051 Phidget
setupAndStartMonitoring1051PhidgetTemps();
}
/**
* @return
* Handle to ThermalMonitor
*/
public static ThermalMonitor getInstance(){
if(instance == null){
synchronized (ThermalMonitor.class){
instance = new ThermalMonitor();
}
}
return instance;
}
67
/**
* Get Properties from Temperature.properties file
*/
public void getPropertiesFromFile(){
Properties props = new Properties();
try {
props.load(getClass().getClassLoader().getResourceAsStream("Temperature.properties"));
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//phidget 1124 temperature sensor location
//tempSensorLocation
String tempSensorLocationLocationString = props.getProperty("tempSensorLocation");
if(tempSensorLocationLocationString!=null &&
tempSensorLocationLocationString.length()>0){
this.tempSensorLocation= Integer.parseInt(tempSensorLocationLocationString);
}else {
this.tempSensorLocation = -1; //Turned Off
}
//temperatureChangedCheckPeriod
String temperatureChangedCheckPeriodString =
props.getProperty("temperatureChangedCheckPeriod");
if(temperatureChangedCheckPeriodString!=null &&
temperatureChangedCheckPeriodString.length()>0){
this.timerPeriod= Integer.parseInt(temperatureChangedCheckPeriodString);
}
//triggerVal
String triggerValString = props.getProperty("triggerVal");
if(triggerValString!=null && triggerValString.length()>0){
this.triggerVal= Double.parseDouble(triggerValString);
}
System.out.println("**************************************************************
*******");
System.out.println("***Inside Thermal Monitor the trggerVal is= "+this.triggerVal);
System.out.println("***Inside Thermal Monitor the temperatureChangedCheckPeriod is=
"+this.timerPeriod);
System.out.println("***Inside Thermal Monitor the tempSensorLocation is=
"+this.tempSensorLocation);
System.out.println("**************************************************************
*******");
68
}
/**
* Sets up all the Listeners and parameters used by this TemperatureSensorPhidget.
*/
private void setupAndStartMonitoring1051PhidgetTemps(){
/*Create Phidget Listeners
tempSensorPhidget.addAttachListener(new AttachListener() {
public void attached(AttachEvent ae) {
System.out.println("attachment of " + ae);
}
});
tempSensorPhidget.addDetachListener(new DetachListener() {
public void detached(DetachEvent ae) {
System.out.println("detachment of " + ae);
}
});
tempSensorPhidget.addErrorListener(new ErrorListener() {
public void error(ErrorEvent ee) {
System.out.println("error event for " + ee);
}
});*/
//Temperature Change Listener
tempSensorPhidget.addTemperatureChangeListener(new TemperatureChangeListener()
{
public void temperatureChanged(TemperatureChangeEvent oe)
{
fireThermoCoupleTemperatureChangeEvent(oe.getValue());
}
});
//Open connection to the Phidget and Attach to it.
try {
tempSensorPhidget.openAny();
System.out.print("waiting for TemperatureSensor Phidget to attach.");
final Timer timer= new Timer();
timer.schedule(new TimerTask(){
@Override
public void run() {
System.out.print(".");
}
},0,1000);
tempSensorPhidget.waitForAttachment();
timer.cancel();
System.out.println("Serial: " + tempSensorPhidget.getSerialNumber());
69
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Set the ThermoCouple type.
try {
tempSensorPhidget.setThermocoupleType(0,
TemperatureSensorPhidget.PHIDGET_TEMPERATURE_SENSOR_K_TYPE);
} catch (PhidgetException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//Set Temperature Trigger
//This is the amount the temperature must change in order for
//the changeListeners to be called.
try {
tempSensorPhidget.setTemperatureChangeTrigger(0,triggerVal);
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Start separate thread to monitor changes in AmbientTemperature.
startMonitoringAmbiantTemp();
//Get the current Thermo Couple Temperature (that way we don't have to wait for
//a temperature change to see a value on the UI.
new Timer().schedule(new TimerTask(){
@Override
public void run() {
try {
fireThermoCoupleTemperatureChangeEvent(tempSensorPhidget.getTemperature(0));
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},timerDelay);
}
/**
* Start separate thread to monitor changes in AmbientTemperature
* as produced by the cold joint on the 1051 phidget.
* AmbientTemperatureEvents will be fired if change in Ambient
* temperature exceeds triggerVal.
*/
private void startMonitoringAmbiantTemp(){
this.ambiantTempTimer = new Timer();
70
this.ambiantTempTimer.schedule(new TimerTask() {
@Override
public void run() {
try {
double ambTmp =
tempSensorPhidget.getAmbientTemperature();
if(Math.abs(ambTmp - lastAmbTemperature) >=
triggerVal){//A change in Ambient Temperature has occurred
fireAmbientTemperatureChangedEvent(ambTmp);
lastAmbTemperature = ambTmp;
}
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},timerDelay,timerPeriod);
}
/**
* Start separate thread to monitor changes in Phidget 1124
* ratiometric temperature.
* ratiometricTemperatureChangeEvents will be fired if change in ratiometric
* temperature exceeds triggerVal.
*/
private void startMonitoringRatiometricTemp(){
this.ratiometricTimer = new Timer();
this.ratiometricTimer.schedule(new TimerTask() {
@Override
public void run() {
try {
double ratTmp =
interfaceKitPhidget.getTemperatureSensorReading(tempSensorLocation);
if(Math.abs(ratTmp - lastRatTemperature) >= triggerVal){//A
change in ratiometric Temperature has occurred
fireRatiometricTemperatureChangedEvent(ratTmp);
lastRatTemperature = ratTmp;
}
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},timerDelay,timerPeriod);
}
/**
* Notify all PhidgetTemperatureListeners that a AmbientTemperatureChangedEvent has occured.
* @param temp
71
*/
private void fireRatiometricTemperatureChangedEvent(double temp){
if(temperatureListeners != null && !temperatureListeners.isEmpty()){
ratiometricTemperatureChangeEvent event = new
ratiometricTemperatureChangeEvent(this, temp);
for (PhidgetTemperatureListener l : temperatureListeners) {
l.ratiometricTemperatureChanged(event);
}
}
}
/**
* Notify all PhidgetTemperatureListeners that a AmbientTemperatureChangedEvent has occured.
* @param temp
*/
private void fireAmbientTemperatureChangedEvent(double temp){
if(temperatureListeners != null && !temperatureListeners.isEmpty()){
AmbientTemperatureChangeEvent event = new
AmbientTemperatureChangeEvent(this, temp);
for (PhidgetTemperatureListener l : temperatureListeners) {
l.ambientTemperatureChanged(event);
}
}
}
/**
* Notify all PhidgetTemperatureListeners that a thermoCoupleTemperatureChangeEvent has
occured.
* @param temp
*/
private void fireThermoCoupleTemperatureChangeEvent(double temp){
if(temperatureListeners != null && !temperatureListeners.isEmpty()){
ThermoCoupleTemperatureChangeEvent event = new
ThermoCoupleTemperatureChangeEvent(this, temp);
for (PhidgetTemperatureListener l : temperatureListeners) {
l.thermoCoupleTemperatureChanged(event);
}
}
}
/**
* Use this method to register an PhidgetTemperatureListener.
* @param l
*/
public synchronized void addPhidgetTemperatureListener(PhidgetTemperatureListener l){
if(temperatureListeners == null){
temperatureListeners = new ArrayList<PhidgetTemperatureListener>();
}
temperatureListeners.add(l);
}
/**
72
* Use this method to Un-register an PhidgetTemperatureListener.
* @param l
*/
public synchronized void removePhidgetTemperatureListener(PhidgetTemperatureListener l){
if(temperatureListeners != null){
temperatureListeners.remove(l);
}
}
/**
* Gracefully ShutDown.
*/
public void shutdown(){
System.out.print("Shutting Down Thermal Monitor...");
this.ratiometricTimer.cancel();
this.ambiantTempTimer.cancel();
interfaceKitPhidget.shutdown();
try {
tempSensorPhidget.close();
} catch (PhidgetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tempSensorPhidget = null;
instance = null;
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
ThermalMonitor monitor = ThermalMonitor.getInstance();
monitor.addPhidgetTemperatureListener(new PhidgetTemperatureListener() {
@Override
public void thermoCoupleTemperatureChanged(
ThermoCoupleTemperatureChangeEvent e) {
System.out.println("ThermoCouple Temp Has Change to
=>"+e.getThermoCoupleTemperature());
}
@Override
public void ambientTemperatureChanged(AmbientTemperatureChangeEvent e)
{
System.out.println("Ambient Temp Has Change to
=>"+e.getAmbientTemperature());
}
73
@Override
public void
ratiometricTemperatureChanged(ratiometricTemperatureChangeEvent e) {
System.out.println("Ratiometric Temp Has Change to
=>"+e.getRatiometricTemperature());
}
});
System.out.println("HIT ANY KEY TO QUIT!!!!");
System.in.read();
monitor.shutdown();
System.exit(0);
}
}
ThermoCoupleTemperatureChangeEvent.java
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package monitor;
import java.util.EventObject;
public class ThermoCoupleTemperatureChangeEvent extends EventObject {
private static final long serialVersionUID = 1L;
double thermoCoupleTemperature;
public double getThermoCoupleTemperature() {
return thermoCoupleTemperature;
}
public ThermoCoupleTemperatureChangeEvent(Object arg0,double thermoCoupleTemperature) {
super(arg0);
this.thermoCoupleTemperature = thermoCoupleTemperature;
}
}
TemperatureListenerImpl.fx
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package controller;
import java.math.BigDecimal;
import controller.*;
import monitor.*;
74
public class TemperatureListenerImpl extends PumpSystemListener {
public var ambTemp:String = "00.0";
public var thermTemp:String = "00.0";
public var ratiometricTemp = "00.0";
public var pumping:Boolean = false;
var mode = 0;
init {
//postinit{ will run after attributes are initialized
println("#### INIT ####");
CoolantPumpController.getInstance().addPumpingNotificationListener(this);
}
public function setPrintMode(quietMode: Integer): Void{
mode = quietMode;
}
public override function pumpingStarted(): Void {
javafx.lang.FX.deferAction(
function() {
pumping = true;
if(mode ==1) {
println("Pump Started");
}
});
}
public override function pumpingStopped(): Void {
javafx.lang.FX.deferAction(
function() {
pumping = false;
if(mode ==1) {
println("Pump Stopped");
}
});
}
public override function ambientTemperatureChanged(e: AmbientTemperatureChangeEvent):
Void {
javafx.lang.FX.deferAction(
function() {
ambTemp = convertTempToString(e.getAmbientTemperature());
if(mode ==1) {
println("Ambient Temp Has Change to =>
{e.getAmbientTemperature()}");
}
});
}
75
public override function ratiometricTemperatureChanged(e:
ratiometricTemperatureChangeEvent): Void {
javafx.lang.FX.deferAction(
function(){
ratiometricTemp =
convertTempToString(e.getRatiometricTemperature());
if(mode ==1) {
println("ThermoCouple Temp Has Change to =>
{e.getRatiometricTemperature()}");
}
});
}
public override function thermoCoupleTemperatureChanged(e:
ThermoCoupleTemperatureChangeEvent): Void {
javafx.lang.FX.deferAction(
function(){
thermTemp =
convertTempToString(e.getThermoCoupleTemperature());
if(mode ==1) {
println("ThermoCouple Temp Has Change to =>
{e.getThermoCoupleTemperature()}");
}
});
}
/**
* This method will convert the phidget temperatures to the appropriate String representation.
* @param temp
* @return
* String Fahrenheit representation of temp
*/
function convertTempToString(temp: Number): String{
var temperature= ((9*temp)/5)+32;
var bd = new BigDecimal(temperature);
bd = bd.setScale(2, BigDecimal.ROUND_DOWN);
return bd.toString();
}
}
/*************************************
* This is the Bootstrapping method.
**************************************/
function run(){
var TemperatureListenerImpl = new TemperatureListenerImpl();
76
TemperatureListenerImpl.setPrintMode(1);
}
JimiSpinner.fx
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package view;
/**
* @author jake
*/
import javafx.scene.image.*;
import javafx.scene.*;
import javafx.animation.*;
import javafx.animation.transition.*;
public class JimiSpinner extends CustomNode {
public var imageView:ImageView;
public function startSpinning(): Void{
spinnerAnimation.play();
fadeAnimIn.playFromStart();
}
public function stopSpinning(): Void{
fadeAnimOut.playFromStart();
}
var angle = 0;
var fadeAnimIn: FadeTransition = FadeTransition{
duration: 1s
node: imageView
fromValue: 0.0 toValue: 1.0
};
var fadeAnimOut: FadeTransition = FadeTransition{
duration: 1s
node: imageView
fromValue: 1.0 toValue: 0.0
action: function():Void {
spinnerAnimation.stop();
}
};
var spinnerAnimation: RotateTransition = RotateTransition{
repeatCount: Timeline.INDEFINITE
77
duration: 3s
fromAngle: 0
toAngle: 360
node: imageView
//framerate: 12
interpolate: true
interpolator:Interpolator.LINEAR
autoReverse: false
};
override public function create():Node {
return Group {
content: [ getImageView() ]
}
}
function getImageView():ImageView{
return imageView;
}
}
Main.fx
/*
* Copyright (c) 2010 Jacob Locken. All rights reserved
*/
package view;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.Group;
import view.TemperatureNode;
import controller.TemperatureListenerImpl;
import java.lang.System;
import javafx.scene.layout.*;
import javafx.geometry.*;
import javafx.scene.text.Text;
import javafx.scene.effect.Reflection;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
/**
* @author jake
*/
78
//Fixing Native Lib Bug
println("JavaFX library Path is: {System.getProperty("java.library.path")}");
var tempListener = new TemperatureListenerImpl();
var sceneNormal:Scene;
var scenePumping:Scene;
var ambTempRef:TemperatureNode;
var thermTempRef:TemperatureNode;
var refrigTempRef:TemperatureNode;
var backgroundColor= Color.YELLOW;
var jimiSpinner:JimiSpinner;
var hbox: HBox;
var pumping = bind tempListener.pumping on replace{
if(pumping == true){
jimiSpinner.startSpinning();
}else {
jimiSpinner.stopSpinning();
}
}
Stage {
title: "Temperature Monitor"
//style: StageStyle.TRANSPARENT
width: 1000
height: 400
scene: sceneNormal=Scene {
fill: backgroundColor
content: [
Stack{
width: bind sceneNormal.width
height: bind sceneNormal.height
nodeVPos: VPos.CENTER
nodeHPos: HPos.CENTER
content:[
jimiSpinner=JimiSpinner{
imageView:ImageView{
opacity: 0.0;
image:Image {
backgroundLoading: true,
url: "{__DIR__}JimiSpiral.png"
};
}
},
hbox= HBox{
spacing: 50
nodeVPos: VPos.CENTER
hpos: HPos.CENTER
vpos: VPos.CENTER
79
onKeyPressed: function (e: KeyEvent){
print("{e.code} KeyPressed");
if (e.code == KeyCode.VK_P) {
jimiSpinner.startSpinning();
}else if (e.code == KeyCode.VK_S) {
jimiSpinner.stopSpinning();
}
}
content:[
ambTempRef=TemperatureNode{
title: "{__DIR__}Ambient.fxz"
temperature: bind tempListener.ambTemp;
},
thermTempRef=TemperatureNode{
title: "{__DIR__}Dispensing.fxz"
temperature: bind tempListener.thermTemp;
},
refrigTempRef=TemperatureNode{
title: "{__DIR__}Refrigerator.fxz"
temperature: bind tempListener.ratiometricTemp;
},
]
}
]
}
]
}
}
hbox.requestFocus();
80
BIBLIOGRAPHY
[1] Frequently Asked Draft Beer Questions. 2009. Micro Matic. 15 Sept. 2009
<http://www.micromatic.com/draft-keg-beer-edu/beer-questions/critical-issues-cid2453.html>.
[2] Online Introductory Chemistry. 2005. Dr. Walt Volland. 15 Sept. 2009
<http://www.800mainstreet.com/9/0009-006-henry.html>.
[3] Heat Exchanger. 2009. Absolute Astronomy. 25 Jan. 2010
<http://www.absoluteastronomy.com/topics/Heat_exchanger>.
[4] Convection, Conduction and Radiation. Mansfield CT. 25 Jan. 2010
<http://www.mansfieldct.org/schools/MMS/staff/hand/convcondrad.htm>.
[5] Comparing Heat Transfer by Convection and Conduction. Dale Durran and Yaga
Beres. 27 Jan. 2010
<http://www.atmos.washington.edu/~durrand/demos/convection_conduction.htm>.
[6] Mikell P. Groover. Autmation Production Systems, and Computer-Integrated
Manufacturing. New Jersey: Pearson, 2008.
[7] Interoperability Between JavaFX and JAVA. [Weblog entry.] Life: a Journey to
Explore. 25 Dec. 2008. <http://www.compare-review-information.com/javafx-javainteroperability/>. 2 Feb. 2010