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