Project showcase
The Lemonade Cocktail Bar

The Lemonade Cocktail Bar © MIT

Our 3-day village festival's theme is "Ibiza," so a cocktail bartender seems applicable!

  • 1,194 views
  • 1 comment
  • 12 respects

Components and supplies

About this project

This machine is one of the controls I've built over the last, I think 6 years, based on the AFSM software for our festival. This years theme was "Ibiza." Not planning to build a new one for this year (it is a lot of work for a 3 days village festival), I could not resist building this automatic "limonade cocktail bar".

The bar is build for the children who can mix a glass of limonade. There is a choice out of 3 flavors. The choice of the flavors can be made by pushing one of the white eluminated arcade buttons. Then press "start" and the control will prepare the drink. Every drink is prepared while playing an appropriate song.

The technic of the bar consists of:

  • The software is made with the AFSM software as described on my website (www.jbsiemonsma.nl)
  • Design philosophy; when a button or LED is blinking, you must do something! In this way the cocktail bar is simple to operate, even for children...
  • A glass transport system to place the glass under the chosen dosing point of the limonade concentrate. This system is made by 2 RVS sliding bars, a RVS spindle and a stepper motor for driving the spindle.
  • A limit switch. When starting the system, it has to determine the position of the glass transport. This is done by transporting the plateau to the right until it reaches the limit switch. Now the controls knows the zero position and transports the glass to the "start" position (stepper motor 200 pulses is 8mm).
  • An ultrasonic measurement to determine if there is a glass present on the glass transport. The measurement is also used for controlling the change of a glass; an already prepared glass on the transport has to be removed to start a new cycle as a condition for a new cycle.
  • 3 membrane pumps for the dosing of the limonade (based on a fixed time for every flavor).
  • 2 valves; one for dosing an amount of fresh water in the glass and one valve for refreshing the garden hose attached to the machine. For the dosing of drinkwater into the glass; a garden hose is used. Because of the relative long length of the hose it must be refreshed. So when there is no cycle busy, every 2 minutes water is tapped from the garden hose and is drained. This to prevent children to get diarrhea!
  • 5 iluminated buttons for making the limonade mix; in fact the user machine interface.
  • Every suction side (highest point of the concentrate hoses of the bottles) is provided with a very small hole. This prevents the siphoning of limonade concentrate to the dosing point.
  • Little check valves prevent most of the dripping of concentrate (some pressure is required to open the check valves in the normal open position).

Extras:

  • When pressing the 2 most right white buttons for 5 seconds, the installation is going into the service mode (all LED's in the buttons are commanded in a "running" loop). Now it is possible to command the pumps and valves by hand, by pushing the related buttons. This is done for pressing the limonade out of the system when the installation is stopped for a while; remove the limonade out of the bottles, fill with water, go to the service mode and push the white button.... Leave the service mode by pressing the same buttons for 3 seconds.
  • The earlier mentioned CIP fase of the garden hose to prevent from getting diarrhea.
  • When a choice is made and "start" is pressed; a message is send to the PC with the choice. This triggers one of the three songs to start while preparing the most delicious drink. The songs are played by the PC running the scada part of the AFSM (visual basic).

Earlier build in this series:

Manneke pis, Rixt fan Oert, Pong spultsje, route 66, "meeste rondjes om de paal." Also see the website.

Make me a drink!

Code

UserConfigurationC/C++
Definition of I/O and machine states
// UserConfiguation.h
//
//
//*========================================================================================
//*========================================================================================
//*============  START USER CONFIGURATION FOR THE I/O   ===================================
//*========================================================================================
//*========================================================================================
//
// Define all used pins for the application.
//
// The range of digitals is commenly used. First to define the inputs,outputs and ultrasonics.
// The analog out en servo pins are in the PWN range of the board.
//
const byte NoInputPins    =  5;
const byte FirstInput     = 22;
const byte NoOutputPins   = 10;
const byte FirstOutput    = 30;
const byte NoUltrasonic   =  1;
const byte FirstUltra     = 46;
const byte NoAnaInPins    =  0;
const byte FirstAnaIn     =  0;
const byte NoAnaOutPins   =  0;
const byte FirstAnaOut    =  0;
const byte NoServos       =  0;
const byte FirstServo     =  0;
const byte NoTemp         =  0;
const byte OneWireChannel =  0;
const byte NoMarkers      =  1;
//
// IMPORTANT!
//
// No. of tagnames have to be equal with No. of pins! This is the addressing part between the pins and the tags!
// Tagnames are connected with the in/outputs by their order. When done correctly it is possible to programm all
// software by tags and phases in an easy way. Don't remove Tag lines (if not used keep them empty)!
//
String DummyTags[]   = { "" };
String InputTags[]   = { "F1","F2","F3","START","ZERO"  };
String OutputTags[]  = { "&D1","&D2","&D3","&DW","ACKD1","ACKD2","ACKD3","ACKSTART","READY","&CIP"  };
String AnaInTags[]   = { "" };
String AnaOutTags[]  = { "" };
String ServoTags[]   = { "" };
String UltraTags[]   = { "PRESENT" };
String MarkerTags[]  = { "MUZIEK" };
String TempTags[]    = { "" };
//
// Declaration of all used states in the FSM.
// The first and last state "START"and "END" are obligatory and may not be removed, the rest is up to you
//
String PossibleFSMStates[] = { "START",
                               "INIT","CHOICE","GOTO-LIQUIR", "LIQUIR","GOTO-WATER", "WATER", "READY", 
	                           "MIX","NEWCUP","CUPPRESSENT","NORMAL", "SERVICE", "CIP","CIPCYCLE",
                               "END" };
//
// Start defining User timers don't use predefined timers (stay out of range 200-300)
//
const byte timAanUit = 1;
const byte timWait = 2;
const byte timLimo = 3;
const byte timWater = 4;
const byte timTask4 = 5;
const byte timStartup = 6;
const byte timGameOver = 7;
const byte timService = 8;
const byte timServiceMode = 9;
const byte timWaitCIP = 10;
const byte timCIP = 11;
//
// End defining user tinmers
//
// START USER SPECIFIC DECLARATIONS
//
// Start User varaibles:
//
boolean AanUit;
int LiquirNo;
int PositionLiquir;
int TimeLiquir;
boolean NewCup;
boolean CupPressent;
boolean GoMix;
boolean PrepareDrink = false;
boolean Service = false;
int TimeNoCupPressent;
int PresentCount;
int ServiceLED = 1;
//
// End User varaibles:
//
// use of the extra MEGA board for the messages, use MessageI2C and/or UseHMISerial, do not remove, only set true or false
//
boolean UseI2C = false;
boolean UseHMISerial = true;
boolean UseGPS = false;
boolean UseIntercard = false;
boolean PollUltra = false;
//
//*========================================================================================
//*========================================================================================
//*==============  END USER CONFIGURATION =================================================
//*========================================================================================
//*========================================================================================
UserFiniteStateMachineC/C++
User part of the finite state machine
// UserFiniteStateMachine.h

//*========================================================================================
//*  Application code
//*
//*  Name: UserFSM()
//*  Version:
//*  Date:
//*  Author:
//*
//*  Short description:
//*
//*
//*========================================================================================
//*========================================================================================
//*============ START USER APPLICATION ======= START USER APPLICATION =====================
//*========================================================================================
//*========================================================================================
//
// State machine
//
// Each phase exists of 2 parts, e.g. the phase actions and transition conditions.
// In the phase actions, all activated tags are mentioned. Deactivation is not needed, deactivation is
// done at the end of the main loop. In the conditions section, all conditions for the transitions to another
// phase are mentioned. When all condition are true, the new phase can be set. It is possible to programm
// more then 1 transition. In such a case it is necassery to keep the priority in mind.
//
//*========================================================================================


void UserFSM()
{

	// START
	//
	// Doe de initialisatie voor het programma. 
	// Breng de spindel naar rechts tot het eindcontact, zodat het programma 
	// weet waar de beker houder is. Ga start daarna de status INIT, NORMAL enn CIP.
	//
	if (MachineState == FiniteState("START"))
	{
		// Move the cup transport to the left to find the zero position...
        // Now we know where we are in the machine
		Spindle.setSpeed(-200);
		Spindle.runSpeed();
		CupPressent = false;
		NewCup = true;
		PrepareDrink = false;
		//UpdateServo("HATCH", 90);
		//UseHMISerial = false;
		if (DigLow("ZERO"))
		{
			Spindle.stop();
			Spindle.disableOutputs();
			Spindle.setCurrentPosition(0);
			TransitionToState("INIT");
			TransitionToState("NORMAL");
			TransitionToState("CIP");
		}
	};

	// NORMAL
	//
	// Door de toetsen combinatie F1 en F2, 5 seconden in te houden en onder  
	// voorwaarde dat we géén drankjes maken; start dan nu de service mode.
	//
	if (MachineState == FiniteState("NORMAL"))
	{
		Service = 0;
		//if (DigLow("F1") && DigLow("F2") && DigLow("F3") && !Service) if (Timer(5000, timService)) TransitionToState("SERVICE");
		if (DigLow("F1") && DigLow("F2") && !PrepareDrink)
		{
			if (Timer(5000, timService)) TransitionToState("SERVICE");
		}
		else CancelTimer(timService);
	}

    // SERVICE
	//
	// Laat de LEDS in een looplicht oplichten. Het is nu mogelijk de betreffende
	// pompjes en de water klep te bedienen buiten de besturing om.
	// Leid het water naar een riool punt!
	// Wanneer 3 seconden op F1 en F2 wordt gedrukt, wordt de service mode 
	// weer verlaten.
	//
	if (MachineState == FiniteState("SERVICE"))
	{
		Service = 1;
		if (Timer(50, timServiceMode)) ServiceLED = ServiceLED + 1;
		if (ServiceLED == 6) ServiceLED = 1;
		switch (ServiceLED)
		{
		case 1:
			Activate("ACKD1");
			break;
		case 2:
			Activate("ACKD2");
			break;
		case 3:
			Activate("ACKD3");
			break;
		case 4:
			Activate("ACKSTART");
			break;
		case 5:
			Activate("READY");
			break;
		}
		if (DigLow("F1") && DigHigh("F2") && DigHigh("F3") && DigHigh("START")) { Activate("&D1"); Activate("ACKD1"); }
		if (DigHigh("F1") && DigLow("F2") && DigHigh("F3") && DigHigh("START")) { Activate("&D2"); Activate("ACKD2"); }
		if (DigHigh("F1") && DigHigh("F2") && DigLow("F3") && DigHigh("START")) { Activate("&D3"); Activate("ACKD3"); }
		if (DigHigh("F1") && DigHigh("F2") && DigHigh("F3") && DigLow("START")) { Activate("&DW"); Activate("ACKSTART"); }
		//if (DigLow("F1") && DigLow("F2") && DigLow("F3")) if (Timer(3000, timService)) TransitionToState("NORMAL");
		if (DigLow("F1") && DigLow("F2"))
		{
			if (Timer(3000, timService)) TransitionToState("NORMAL");
		}
		else CancelTimer(timService);
	};

	// INIT
	//
	// Na het initialiseren van het programma, brengt de besturing de beker houder op de 
	// laad/los positie van de machine.
	// Start nu de state CHOICE (keuze maken) en CUPPRESSENT (check op "beker aanwezig").
	//
	if (MachineState == FiniteState("INIT"))
	{
		// Move to the load/unload position
		if (Timer(2000, timStartup))
		{
			Spindle.setSpeed(350);
			Spindle.runToNewPosition(200);
			Spindle.setCurrentPosition(0);
			Spindle.disableOutputs();
			TransitionToState("CHOICE");
			TransitionToState("CUPPRESSENT");
		}        
	};

	// CHOICE
	//
	// Wacht op de keuze van de gebruiker voor één van de drie smaken.
	// Wanneer er een keuze is gemaakt EN er is een beker aanwezig EN  
	// de machine zit niet in de servide mode; Start dan status MIX.
	//
	if (MachineState == FiniteState("CHOICE"))
	{
		if (CupPressent && !Service) if (Timer(400, timAanUit)) AanUit = !AanUit; else if (AanUit)
		{
			Activate("ACKD1");
			Activate("ACKD2");
			Activate("ACKD3");
		}
		if (DigRising("F1"))
		{
			LiquirNo = 1;
			TimeLiquir = 2200;
			PositionLiquir = 6750;
		}
		if (DigRising("F2"))
		{
			LiquirNo = 2;
			TimeLiquir = 2400;
			PositionLiquir = 4850;
		}
		if (DigRising("F3"))
		{
			LiquirNo = 3;
			TimeLiquir = 2200;
			PositionLiquir = 2850;
		}
		if ((DigRising("F1") || DigRising("F2") || DigRising("F3")) && CupPressent && !Service) TransitionToState("MIX");
		if (!CupPressent) if (Timer(150, timAanUit)) AanUit = !AanUit; else if (AanUit) Activate("READY");
	};

	// MIX
	//
	// Wacht op de bevestiging van de keuze (knop "START") OF op het drukken op een 
	// keuze knop. Wanneer er weer op een keuze knop wordt gedrukt, gaat de status 
	// terug naar CHOICE. Bij een bevestiging, sluit het toegangshekje en start de 
	// besturing de status GOTO-LIQUIR, ondervoorwaarde dat er een nieuwe beker 
	// staat EN deze op de laad/los plaats staat.
	//
	if (MachineState == FiniteState("MIX"))
	{
		Activate("ACKD" + String(LiquirNo));
		if (Timer(400, timAanUit)) AanUit = !AanUit; else if (AanUit) Activate("ACKSTART");
		if (!CupPressent) if (Timer(150, timAanUit)) AanUit = !AanUit; else if (AanUit) Activate("READY");
		if ((DigRising("START")) && CupPressent && NewCup)
		{
			//UpdateServo("HATCH", 1);
			GoMix = true;
			PrepareDrink = true;
			Activate("ACKSTART");
			CupPressent = false;
			//UseHMISerial = true;
			UpdateText("MUZIEK", "---" );
			//UseHMISerial = false;
			TransitionToState("GOTO-LIQUIR");
		}
		if (DigRising("F1") || DigRising("F2") || DigRising("F3")) TransitionToState("CHOICE");
	};

	// GOTO-LIQUIR
	//
	// Stop bij de juiste positie voor één van de drie
	// limonade vulpunten. Bij aankomst start de status LIQUIR.
	//
	if (MachineState == FiniteState("GOTO-LIQUIR"))
	{
		Activate("ACKD" + String(LiquirNo));
		Activate("ACKSTART");
		UpdateText("MUZIEK", "F" + String(LiquirNo));
		if (Timer(200, timWait))
		{
			Spindle.setSpeed(350);
			Spindle.runToNewPosition(PositionLiquir);
			Spindle.disableOutputs();
			TransitionToState("LIQUIR");
		}
	};

	// LIQUIR
	//
	// Sart de pomp voor het limonade smaak voor bepaalde tijd. 
	// Ga daarna naar status GOTO-WATER.
	//
	if (MachineState == FiniteState("LIQUIR"))
	{
		Activate("ACKD" + String(LiquirNo));
		Activate("ACKSTART");
		Activate("&D" + String(LiquirNo));
		if (Timer(TimeLiquir,timLimo)) TransitionToState("GOTO-WATER");
	};

	// GOTO-WATER
	//
	// Wacht eerst 4 seconden, om de limonade te laten uitdruppen en
	// ga dan terug naar de laad/los positie. Bij aankomst start
	// de water dosering in status WATER.
	//
	if (MachineState == FiniteState("GOTO-WATER"))
	{
		Activate("ACKD" + String(LiquirNo));
		Activate("ACKSTART");
		if (Timer(4000, timWait))
		{
			Spindle.setSpeed(350);
			Spindle.runToNewPosition(0);
			Spindle.disableOutputs();
			TransitionToState("WATER");
		}
	};

	// WATER
	//
	// Start de water dosering voor bepaalde tijd door openen van de afsluiter.
	// Daarna starten de states READY en NEWCUP.
	//
	if (MachineState == FiniteState("WATER"))
	{
		Activate("ACKSTART");
		Activate("ACKD" + String(LiquirNo));
		Activate("&DW");
		if (Timer(1500, timWater))
		{
			NewCup = false;
			TransitionToState("READY");
			TransitionToState("NEWCUP");
		}
	};

	// READY
	// 
	// Hekje open sturen. Start status CHOICE en CUPPRESENT.
	//
	if (MachineState == FiniteState("READY"))
	{
		PrepareDrink = false;
		//UpdateServo("HATCH", 90);
		if (Timer(150, timAanUit)) AanUit = !AanUit; else if (AanUit && !Service) Activate("READY");
		if ((Timer(5000, timGameOver)) && NewCup)
		{
			GoMix = false;
			TransitionToState("CHOICE");
			TransitionToState("CUPPRESSENT");
		}
	};

	// NEWCUP
	//
	// Controle of de beker is wegehaald van de laad/los plaats.
	// Dit is een voorwaarde (NewCup) voor het opnieuw starten van een cyclus. 
	// Hiermee wordt voorkomen dat een gevulde beker weer de machine 
	// in gaat.. en ja daar wordt alles niet skjinner van!
	//
	if (MachineState == FiniteState("NEWCUP"))
	{
		PollUltra = true;
		if (DistanceLonger("PRESENT", 8))
		{
			PollUltra = false;
			NewCup = true;
			TransitionToState("END");
		}
	};

	// CUPPRESSENT
	//
	// In deze status wordt gecontroleerd of er een beker op de laad/los plek
	// aanwezig is (CupPressent). Check d.m.v. een ultrasone meting. 
	//
	if (MachineState == FiniteState("CUPPRESSENT"))
	{
		PollUltra = true;
		if (Distance("PRESENT") != 0) if (DistanceBetween("PRESENT", 4, 7)) CupPressent = true; else CupPressent = false;
		if (GoMix)
		{
			PollUltra = false;
			TransitionToState("END");
		}
	};
	
	// CIP
	//
	// Elke 2 minuten, wanneer er geen drankjes worden gemaakt, zal
	// een afsluiter worden open gestuurd door CIPCYCLE om de aanvoerslang 
	// te verversen. 
	//
	if (MachineState == FiniteState("CIP"))
	{
		if (Timer(120000, timWaitCIP))
		{
			if (!PrepareDrink)
			{
				TransitionToState("CIPCYCLE");
				TransitionToState("CIP");
			}
			else CancelTimer(timWaitCIP);
		}
	};

	// CIPCYCLE
	//
	// Afsluiter voor de CIP gaat open voor 10 seconden. Daarna wordt
	// de status gestopt. Het kan zijn dat iemand ondertussen weer
	// een drankje wil maken; de CIP afsluiter wordt nu ook dichtgestuurd.
	// 
	if (MachineState == FiniteState("CIPCYCLE"))
	{
		Activate("&CIP");
		if (Timer(10000, timCIP)) TransitionToState("END");
		if (PrepareDrink)
		{
			TransitionToState("END");
			CancelTimer(timCIP);
		}
	};

	//*========================================================================================
	//*========================================================================================
	//*============  END USER APPLICATION ========= END USER APPLICATION =====================
	//*========================================================================================
	//*========================================================================================
}

Comments

Similar projects you might like

Mix-A-Bot Bar Kit

Project tutorial by Team Mix-A-Bot

  • 9,236 views
  • 0 comments
  • 22 respects

Barbot: Cocktail Mixing Robot

Project showcase by sidlauskas

  • 32,649 views
  • 14 comments
  • 131 respects

IoT Pull-Up Bar

Project showcase by Mark Dreyband

  • 7,663 views
  • 7 comments
  • 27 respects

'Roger Bot' the Pet Rover

Project showcase by hannu_hell

  • 1,697 views
  • 0 comments
  • 12 respects

Cocktail Parasol

by alain_haerri

  • 3,732 views
  • 7 comments
  • 13 respects

TFT Graphing: 3D Bar Charts

Project tutorial by Andrei Florian

  • 2,949 views
  • 2 comments
  • 19 respects
Add projectSign up / Login