Yu Jiang Tham

Dream it. Build it.

Month: May 2014

Build your very own drink mixing robot! (part 2)

This is a continuation of the previous post where I went over how to print, wire, solder, and assemble the robot.  With this post, I will be discussing the software design of the robot, as well as the considerations that I took in designing it.  I’m assuming that you have some working knowledge of JavaScript and MongoDB.  I realize that the Angular.js code that is written here may not be the most efficient, since the whole point of me building the robot was to learn Angular (and also make some thing awesome).  Therefore, if you spot any changes that need to be made to the code to increase efficiency or whatnot, please let me know in the comments or send a pull request to the Bar Mixvah GitHub.  Here’s a video of the robot in case you missed the previous post:

Navigation:

 

Why the MEAN Stack

So, before I proceed, a few people have asked why we need so much technology to do something so simple as mixing drinks.  What is the MEAN stack?  It consists of MongoDB, Express.js, Angular.js, and Node.js.  Everything works beautifully together because it’s all JavaScript (and MongoDB, which uses BSON, similar to JavaScript’s JSON).  Why do we need the MEAN stack?  Well, we don’t need it.  But it makes things easier, so that’s why I’m using it.

  • MongoDB is used to store all of the drink and pump information
  • Express.js is the web server so that your WiFi connected devices can access the interface
  • Angular.js is used on the front end to filter drinks based on what ingredients have been selected
  • Node.js is used on the backend to communicate with the Arduino

 

Preparing Your Arduino

The Arduino Nano talks to the Johnny-Five node.js package via the Standard Firmata protocol.  You will need to flash Standard Firmata onto your Arduino:

  1. Download the Arduino software
  2. Plug in the Arduino to your USB port on your computer
  3. Open the Arduino software
  4. Go to File > Examples > Firmata > StandardFirmata
  5. Click the Upload button in the top-left

Screen Shot 2013-07-29 at 2.05.08 PM

 

Starting the App

Bar Mixvah requires node.jsMongoDB, and git.  Follow the respective links to download and install them if you haven’t already.  The commands here are run in the Mac OS or Linux shell; I don’t have much experience with the Windows command line, but I believe most of the stuff is the same (except you should remove sudo if you see it, and run your command prompt as an administrator).  To start off, clone the Bar Mixvah GitHub repository onto your local hard drive:

git clone https://github.com/ytham/barmixvah.git

cd into the directory and install all node.js packages:

cd barmixvah
npm install

This will install all of the packages required for the node.js app to run.  Before you can run it, you must start mongodb:

sudo mongod

In a new terminal window, cd to the barmixvah directory again.  Plug in the Arduino to your computer via USB.  You can now start the robot app:

node app.js

Congrats! It should now be up and running.  You can now point your web browser to http://localhost:3000 to check it out.  The rest of this post will go towards explaining the code for the robot.  If you haven’t yet set up the Arduino, you can read the next section to set up the UI without having it connected to an Arduino for testing purposes.

 

Debugging the App Without the Arduino

The app can be started without an Arduino by commenting out the following section:

> public/javascripts/robot/backend.js
var board, pump0, pump1, pump2, pump3, pump4;
/*
var five = require('johnny-five');

board = new five.Board();
board.on('ready', function () {
 // Counting down pins because that's the orientation 
 // that my Arduino happens to be in
 pump0 = new five.Led(7);
 pump1 = new five.Led(6);
 pump2 = new five.Led(5);
 pump3 = new five.Led(4);
 pump4 = new five.Led(3);

 board.repl.inject({
  p0: pump0,
  p1: pump1,
  p2: pump2,
  p3: pump3,
  p4: pump4
 });

 console.log("\033[31m[MSG] Bar Mixvah Ready\033[91m");
});
*/

This will prevent Johnny-Five from initializing the Arduino.  This is useful for testing UI or backend code on the go if you’re not tethered to an Arduino.

 

General Flow

Here’s a pretty simplified design that gives you a general picture of how the code works with the physical elements:

generalflow

When a user interacts with the UI, Angular.js (or some custom JavaScript/jQuery) will change what is displayed on the screen.  If the user decides to make a drink with the Make button, the selected drink JS object and drink size  are passed to the backend via socket.io.  From there, the app passes the data to the Johnny-Five package and communicates when to start/stop the array of pumps.

 

Drink and Pump Schemas

The drink schema that will be saved into MongoDB (via the Mongoose node.js package) is specified below:

> models/Drink.js
exports.DrinkSchema = new Mongoose.Schema({
 name: { type: String, required: true },
 image: { type: String, required: false },
 ingredients: [{
  name: String,
  amount: Number
 }]
});

The drink objects have a name, image, and an array of ingredient objects that contain the their name and relative amount.  Ingredient amounts are relative and unitless because then the user can specify the drink size and get the actual amount of each ingredient to pump based on the specified drink size.

Here is the pump schema:

> models/Pump.js
exports.PumpSchema = new Mongoose.Schema({
 label: { type: String, unique: true, sparse: true, required: true },
 ingredients: [{ 
  label: String, 
  ingredient: String
 }]
});

We have one pump object that contains an array of pumps that have a label (such as “pump0”, “pump1”, etc…) and an ingredient associated with that pump.  In this case, we use the label because order is important and we want to ensure that the pumps are in the correct order regardless of how they are modified.  When any pump is updated via the UI, the entire pump object that contains all of the (up to five) pumps is updated in MongoDB.  This keeps things consistent and ensures that pumps are updated correctly.

> routes/index.js
exports.updatePump = function (Pump) {
 return function (req, res) {
  Pump.findOneAndUpdate({ _id: req.body._id }, 
   {
    ingredients: req.body.ingredients
   },
  function (err, pump) {
   if (pump == null) {
    Pump.create(req.body);
    pump = req.body;
   }
   res.send(pump);
  });
 }
}

 

Choosing Pumps

The pump system is set up so that as soon as the any of the pump ingredients are changed, the entire pumps object (containing all of the individual pump ingredients) is changed.  The ng-click directive in this case calls two functions.  One function saves the pumps object by overriding the previous pumps object, the other figures out the number of duplicate ingredients and writes the number of duplicates that are checked at other times (such as when the Make button is pressed).  The reason why we don’t just check pumps for duplicates immediately is if, say you are a user and you want to move “Orange Juice” from pump0 to pump2.  You might change pump2 to “Orange Juice” first, but if that throws an error since “Orange Juice” is also currently on pump0, that is not a very good user experience.

> views/index.jade
div.pumpContainer(ng-repeat="pump in pumps.ingredients")
 select.mixers(ng-change="savePumpValue($index); writeNumDuplicates()", ng-model="pump.ingredient", ng-options="i for i in ingredientsList")

The savePumpValue function sends a post request with the $scope.pumps object.  The pumps object is data bound to the view via Angular, so the changes in the dropdown are automatically modified on the front end, but we just need to save it into the database so that when the user refreshes the page, they get the same pumps instead of having to start over.

> public/javascripts/controller/DrinkController.js
$scope.savePumpValue = function (pumpNumber) {
 $http.post('/updatepump.json', $scope.pumps).success(function (data) {
  if (data) {
   console.log(data);
  }
 });
};

From here, the web server receives the HTTP POST request via the updatepump.json endpoint.

> app.js
var routes = require('./routes/index');
...
app.post('/updatepump.json', routes.updatePump(Pump));

And then the updatePump function is run, creating a pump if there is none, and updating the current pumps object if it already exists.

> routes/index.js
exports.updatePump = function (Pump) {
 return function (req, res) {
  Pump.findOneAndUpdate({ _id: req.body._id }, 
  {
   ingredients: req.body.ingredients
  },
  function (err, pump) {
   if (pump == null) {
    Pump.create(req.body);
    pump = req.body;
   }
  res.send(pump);
  });
 }
}

 

Pump Operation

The pumps are switched on/off by the 5V from the Arduino pins going to the each of the TIP120 transistors, which in turn switch the 12V for the individual pumps.  Since the Johnny-Five package contains a simple interface for LEDs, I decided to use its switch on/off properties for switching the pumps because it’s just a simple digitalWrite(HIGH/LOW).  Here’s the code for it:

> public/javascripts/robot/backend.js
 pump0 = new five.Led(7);
 pump1 = new five.Led(6);
 pump2 = new five.Led(5);
 pump3 = new five.Led(4);
 pump4 = new five.Led(3);

The pumpMilliseconds function is used to run a single pump for a number of milliseconds.  The usePump function (not shown here) determines which pump to use based on the pump input string.

> public/javascripts/robot/backend.js
function pumpMilliseconds(pump, ms) {
 exports.startPump(pump);
  setTimeout(function () {
 exports.stopPump(pump);
 }, ms);
}

exports.startPump = function (pump) {
 console.log("\033[32m[PUMP] Starting " + pump + "\033[91m");
 var p = exports.usePump(pump);
 p.on();
}

exports.stopPump = function (pump) {
 console.log("\033[32m[PUMP] Stopping " + pump + "\033[91m");
 var p = exports.usePump(pump);
 p.off();
}

 

Making a Drink

The simplicity of the UI hides much of the complexity behind the actual making of a drink.  We want to make a drink that is top-biased in terms of ingredients.  That is, all of the ingredients with smaller amounts should be on top so that gravity will cause them to mix into the drink.  This adds a little bit of complexity in that we need to also pass a delay amount for each pump, but it is worth it for a drink that is mixed better!  Here’s a diagram of how the pump timings will work out:

drinkdiagram

 

When the user picks a drink and size, the data is stored as a local variable in the Angular $scope.

views/index.jade
div.drinkContainer(ng-repeat="drink in drinks | orderBy: 'name' | filter: containsIngredients", ng-click="selectDrink(drink)")
public/javascripts/controllers/DrinkController.js
$scope.selectDrink = function (drink) {
 $scope.selectedDrink = drink;

 if ($scope.lastSelected) {
 $scope.lastSelected.selectedDrink = '';
 }

 this.selectedDrink = 'selectedDrink';
 $scope.lastSelected = this;
};

The Angular ng-click directive in the jade template file specifies the function to be run in the Angular $scope when the div is clicked.  In this case, when the div is clicked, the $scope.selectedDrink variable gets set to the current drink object.   When the Make button is pressed, code on frontend.js does two things: 1) it does some visual trickery to turn the Make button into a progress bar, and 2) it does the calculations required to determine how long each of the pumps should fire for based on the ingredients in the drink and the size of the drink selected.  So, here’s the code for what happens when we tap the Make button:

> public/javascripts/robot/frontend.js
$('#make').on('click touch', function () {
 if ($('#make').hasClass('noselection') === true) {
  alert('Please select a drink first.');
 return;
 }

 if ($('#make').hasClass('disabled') === true) {
  return;
 }

First, we double check to make sure that a drink has been selected first.  We can’t make anything if there is no selected drink.  Additionally, if the robot is already making a drink, the Make button will be disabled and should not make a drink until it is done with the drink it is already making.  Next, in the following code, you’ll see how we do the visual progress bar for the Make button.  We add the ‘disabled’ class to prevent additional drinks from being made until the current one is done, show the hidden #makeProgress div, and then animate it via its margin-left style.  At the end of the animation, the anonymous callback function hides the makeProgress bar and removes the ‘disabled’ class.  The whole thing is wrapped around a 200ms delay in order for us to get the $scope.pumpTime, which is calculated in the makeDrink function that is explained further down in this section.  After this, we call the makeDrink function with the drink’s ingredients, the pumps, and the selected drink size ($scope.drinkTime).

> public/javascripts/robot/frontend.js (continuing $('#make').on...)
 console.log('Making Drink');
 $('#make').addClass('disabled');
 $('#makeProgress').show();
 setTimeout(function () {
  console.log("Time to Dispense Drink: " + $scope.pumpTime + "ms");
  $('#makeProgress').animate({
   'margin-left': String($(window).width()) + 'px'
  }, parseInt($scope.pumpTime), 'linear', function () {
   $('#make').removeClass('disabled');
   $('#makeProgress').hide();
   $('#makeProgress').css('margin-left', '-10px');
  });
 }, 200);

 // Start dispensing drink
 makeDrink($scope.selectedDrink.ingredients, $scope.pumps, parseInt($scope.drinkTime));
 });

The code below goes through getting the total amount of all of the ingredients, finding the ingredient with the largest amount, and also  appending pump labels to the ingredients as a string so that we will be able to determine which pump to use after this data is sent to the backend.

> public/javascripts/robot/frontend.js
function makeDrink(ingredients, pumps, drinkSize) {
 // Check that there are no duplicate pumps ingredients
 if ($scope.pumpDuplicates > 0) {
  alert("Pump values must be unique");
  return;
 }

 // Get largest amount and index of that ingredient
 var largestAmount = 0;
 var amountTotal = 0;
 var largestIndex = 0;
 for (var i in ingredients) {
  amountTotal += Number(ingredients[i].amount);
  if (Number(ingredients[i].amount) > largestAmount) {
   largestAmount = ingredients[i].amount;
   largestIndex = i;
  }

  // Append pump numbers to the ingredients
  for (var j in pumps.ingredients) {
   if (ingredients[i].name === pumps.ingredients[j].ingredient) {
    ingredients[i].pump = pumps.ingredients[j].label;
    continue;
   }
  }
 }

After all of this, in the code below, you will see that we get the normalization factor, which is the drinkSize divided by the total amount of all drinks.  With this normalization factor, we can multiply the largest amount of drink by this value in order to get the total pump time (since pumps will be running in parallel, the total pump time is the pump time of the ingredient with the highest amount).  If you recall from above, this is the $scope.pumpTime that we delayed 200ms to get on the front end.  After this, we modify the amounts all of the ingredients in the array based on the normalization factor, and add the delay so that we can top-weight the ingredients in the drink.  At the end, we use socket.io to pass the ingredients object to the backend.

> public/javascripts/robot/frontend.js (continuation of makeDrink function)
 // Normalize
 var normFactor = drinkSize/amountTotal;

 var totalPumpMilliseconds = parseInt(normFactor * largestAmount); 
 $scope.pumpTime = totalPumpMilliseconds;

 // Set the normalized amount and delay for each ingredient
 ingredients[largestIndex].amount = parseInt(normFactor * Number(ingredients[largestIndex].amount));
 ingredients[largestIndex].delay = 0;
 for (var i in ingredients) {
  if (i === largestIndex) continue;
  ingredients[i].amount = parseInt(normFactor * Number(ingredients[i].amount));
  ingredients[i].delay = ingredients[largestIndex].amount - ingredients[i].amount;
 }

 socket.emit("Make Drink", ingredients);
}

At the backend, app.js catches the “Make Drink” event from the frontend and passes it to the robot portion that handles the actual pumping.

> app.js
var robot = require('./public/javascripts/robot/backend.js');
...
io.sockets.on('connection', function (socket) {
 socket.on("Make Drink", function (ingredients) {
  robot.pump(ingredients);
 });
> public/javascripts/robot/backend.js
exports.pump = function (ingredients) {
 for (var i in ingredients) {
  (function (i) {
   setTimeout(function () { // Delay implemented to have a top-biased mix
    pumpMilliseconds(ingredients[i].pump, ingredients[i].amount);
   }, ingredients[i].delay);
  })(i);
 }
};

And that’s all there is to it!

Well, those are the main points that I wanted to highlight about the software design of the robot.  If you have any questions or comments, post them in the comments section below and I’ll try my best to answer them.  And, of course, don’t forget to follow me on Twitter: @yujiangtham.

Navigation:

 

Build your very own drink mixing robot! (part 1)

I built a robot that mixes drinks named Bar Mixvah.  It utilizes an Arduino microcontroller switching a series of pumps via transistors on the physical layer, and the MEAN stack (MongoDB, Express.js, Angular.js, Node.js) and jQuery for the frontend and backend.  In this post, I’ll teach you how I made it.  You can follow along and build one just like it!  I’ve also put the 3d model (blender), stl files, and the code up on GitHub for you guys to download.  See the link at the bottom of the page.  Here’s the video of the robot:

Navigation:

 

First, a little bit more about the robot.  The entire thing costs approximately $180 to make.  All of the parts are 3d printed, so you’ll need a 3d printer to build this.  I used the MakerBot Replicator 2X, but any 3d printer should do.  Total time to print the pieces is about 18 hours, depending on your settings, and assembly wiring, and  Here’s a list of parts that need to be purchased:

Other tools required for the job are: a hacksaw to cut two of the 12″ rods in half, a wire stripper, soldering iron, and solder to connect the wire to the pin connectors and coaxial power connector, and a multimeter to check your work.

For the first part of this tutorial, I’ll focus on the 3d model, printing, and assembling the physical robot.  The second part of the tutorial deals with the code, and the third part will deal with the operation of the robot.

 

Design

Bar Mixvah is designed to use a system of 5 peristaltic pumps that are switched by 5 bipolar junction transistors (TIP120), all controlled by an Arduino, which itself is controlled by the Johnny-Five package on the node.js/express web server that is running on your laptop/windows tablet (or maybe Raspberry Pi?  I haven’t tried).  Having it on a web server allows users to order from any device, be it a phone, tablet, or other laptop that can connect to your WiFi access point’s internal network.  Practicality-wise, maybe it’s not necessary.  However, in my experience, people seem to enjoy ordering from a tablet that they’re holding in their hands more  than a stationary screen attached to the robot.

The physical design of Bar Mixvah around the usage of 5/16″x12″ steel rods.  I chose this length and size because they’re sturdy, readily available at your local hardware store, and not too big or small.  They’re also relatively cheap at ~$2-3 per piece, depending on where you buy from.  The problem with 3d printing is that it’s goddamn slow.  If you want to build a medium sized robot like this one, it would take days to print all of the necessary parts.  In fact, you don’t even need to print these parts; you could fasten them together using plenty of other methods.  However, I don’t have access to a metal shop, am a terrible welder, and wanted a friendly looking robot, so I chose this combination of 3d printing the joints and connecting them via metal shafts.

Here’s a screenshot of the 3d model, which, fortunately, looks exactly like the real thing after I finished building it.  Ah, the miracles of science!

3dmodel

 

Printing the Parts

The stl files can mostly be printed in the orientation that they are in, however two files should be rotated 180 degrees on the x-axis so that they can be printed without major supports.  These two pieces are Center – Board Cover – Top.stl and Center – Common Drink Channel.stl.  Additionally, Center – Pump Holder.stl should be printed flat by rotating it 90 degrees so that no support pieces are needed.  You will need to turn on printing with supports to ensure that the holes where we will be inserting the 5/16″ steel rods are printed to the right size.

One more thing that has been brought to my attention: the .stl files are may be 10x smaller than they should be in your 3d printing software.  If that’s the case, you will need to scale up the objects 10x in all dimensions.  It seems to happen regardless of the 3d software that I use when I convert to .stl.  No idea why.

 

The Peristaltic Pumps

What is a peristaltic pump?  If the word sounds familiar to you, it’s because you most likely heard of peristalsis in one of your biology classes at some point in time.  Peristalsis is the system that your body uses to swallow food.  Your throat muscles contract and relax in a way to create a waveform that pushes food in your throat down into your stomach.  Peristaltic pumps work on the same principle, albeit with a slightly different execution.  The clear plastic tube extends through the pump, and rollers propelled by a DC motor create a waveform that pushes liquid through the tube.  Peristaltic pumps are safe and hygienic because liquid never actually contacts any part of the pump; only the plastic tubing is ever in contact with the liquid.

The peristaltic pumps come with some very short plastic tubing.  This is obviously inadequate for our current application, so we’ll have to replace the plastic tubing.  This requires us to take apart the pump.  Fortunately, this is not a hard thing.  Instead of trying to explain it, you can view the following video to figure out how it is done.

 

Soldering

Before connecting any wires, you’ll want to do all of the soldering.  You’ll have to solder the 5.5mm x 2.1mm coaxial power connector to two jumper wires, one for the positive lead and one for the negative lead.  Plug in your 12V DC power supply to the wall, then plug the coaxial power connector into the DC power supply.  Use your multimeter to find out which lead is positive and negative by placing the probes on two of the leads until you find that the multimeter says 12V; those are you positive and negative leads (if it says -12V, then you’ve got the positive and negative leads switched).  Unplug the coaxial power connector.  Strip two wires and solder one to each of the coaxial power connector’s leads.  After you’re done soldering, wrap any exposed metal around the leads in electrical tape.

Next, you’ll want to solder wires to the leads of the peristaltic pumps.  The positive lead of the pump should be labeled, so you should not need to guess.  If it is not labeled, you will just need to make note of which way the pump is turning and make sure that all of them are turning in the same direction.  Don’t worry about the polarity of the leads on the pump breaking anything, since connecting them backwards will just make the pump go in the opposite direction.  However, I want to emphasize that you’ll probably want to ensure all of the pumps are pumping clockwise (if they are facing you; you can see through the tiny circle in the middle which direction they are pumping when turned on).  After this is done, once again wrap the leads in electrical tape.

 

Wiring

Here’s where it gets a little bit tricky.  The actual wiring is not too complicated, but it requires a little bit of finesse due to the confines of space that we are working with.  Since we are fitting everything on a single breadboard, we need to ensure everything is placed in the right spot.

In case you haven’t used a breadboard in a while, each of the numbers running down the breadboard indicate an individual node.  The center divides the two sides, so they are separate nodes.  The (+) rail running up the left and right side of the breadboard is one node per side, and it is the same with the (-) rail.

The first thing that you should do before getting any wiring done is to hook up your pumps individually and ensure they are all working.  The photo below shows a little bit more complex of a circuit.  To check if it’s working, you can just connect the coaxial power connector and pump on a breadboard and plug in the power and ensure that the pump works.

IMG_2408

Here’s the wiring diagram for the robot.  As you can see, it’s relatively simple:

circuitdiagram

The tough part is that there is not much space, so you may need to have a set of needle-nosed pliers ready to put some of the things in.  I recommend adding everything except transistors in first.  Here’s how my breadboard looked like after I finished wiring it up:

IMG_2514 wiring2

Obviously, yours may look slightly different.  However, I recommend placing the Arduino’s nano so that its USB port is on the edge of either side of the breadboard.

 

Assembly

After printing all of the pieces, remember which piece is which based on the 3d model.  Remove all of the support pieces from the 3d printed items with pliers and/or a flathead screwdriver.  It is likely that it will be a very tight fit for the steel rods, so you’ll need to push the rods in with a lot of force.  I recommend using gloves.

IMG_2405

Also, the order in which you assemble the robot IS important.  Here’s the order that I used to assemble.  Basically, you need to remember to assemble the middle parts first before connecting the left and right sides to them.

  1. Insert all 5 of the pump holders onto one of the steel rods
  2. Insert the drink tray into the center of two of the steel rods
  3. Insert the breadboard holder (bottom piece) into the center of one steel rod
  4. Assemble the left side, then assemble the right side
  5. Insert the steel rods for the drink tray, pump holders, and breadboard holder into the left side
  6. Connect all of the parts of the right side
  7. Insert each of the pumps into a pump holder and screw them in with two #4 screws each
  8. Attach the center channel to the top center section using two #6 screws
  9. Tape the breadboard to the center of the top-middle section, ensuring that the top piece of this section will fit over it
  10. Wire everything up based on the circuit schematic
  11. Place the top piece (labeled #BarMixvah in the photo below) over the breadboard, moving wires around until everything fits snugly inside
  12. Insert two #6 screws through the screw hole and tighten the nuts at the bottom to secure it in place

IMG_2519

 

Well, that’s all for this week.  Let me know if you have any questions in the comments and I’ll try my best to answer them.

GitHub link: https://github.com/ytham/barmixvah

Note that the 3D model and stl files are located in the CAD/ folder, and they are printed with the MakerBot Replicator 2X.

Stay tuned for part 2, the software design!

Follow me on twitter: @yujiangtham

Navigation:

© 2024 Yu Jiang Tham

Theme by Anders NorenUp ↑