Main notes about the machine :
- The machine serves shooters, mixing different beverages directly into shot glasses
- Each bottle is connected to the head (lets say there are 4 of them) and has its own extruder
- The bottle will be disposed with the head down like in Roman's design and we will use solenoid valves for the distribution of liquids
- For the moment we will only use one axis, X, to move the nozzle horizontaly (and we will add an other one later I we have time)
- The glasses we be disposed in one raw (or several raws if we decide to use a Y axis)
- The glasses will be placed on a removable bed standing on a multi-level rack. This way we will be able to manually modify the Z. Also, the bed can act directly as a tray for serving the shooters.
Student | Task |
---|---|
Frédéric | Fluid mechanics, development |
Guillaume | Prototyping and Development |
Ludovic | 3D model of the machine |
Roman | Development, electronics |
Thomas | Documentation, 3D model of the machine |
Vincent | Documentation, prototyping |
Week9 - Assembly from Thomas Feminier on Vimeo.
Week9 - Assembly from Thomas Feminier on Vimeo.
The concept of a peristaltic pump is to pinch a tube containing liquid between an outside wheel and inside wheel. By pinching the tube, the liquid is up with control. The peristaltic pumps are used in the medical industry to manage the flow of blood, ...
We 3D-printed a pre-made 3D STL file of such a pump. Result is quite good! To complete the system, we bought some plastic tubes and some motors capable to make the pump turn. We began to buy 2 motors for testing.
Week10 - Test from Thomas Feminier on Vimeo.
We've done a sketch of all mechanical functions and how they are controlled by electronics and embedded programs.
- As explained above, we chose to use a Raspberry Pi to manage the main functions of the machine : on the one hand, the Raspberry is controling the FabNet / Geshalt / X motor node, and on the other hand, the Raspberry is controlling the Liguid distribution system via an Arduino electronic interface.
- The machine is piloted by web interfaces, thanks to a mini-website and a mini-cocktail-database hosted in the Raspberry
1 - the progam to pilot the X Axis, embedded on the Geshalt via the Fabnet
2 - an interface program between the Raspberry and the Arduino, in order to make the program 3 works.
3 - the 3d program, coded in a Arduino language (Sketch), to pilot the Liquid Distribution System, and to control the End-stop machine.
4 - the 4th program is the core program in the heart of the Raspberry ; it ensures the treatments of the input data and distributes the command lines between programs 1 and 2. And "vice versa", the 4 program takes the information about liquid distribution process and output the results in the web interface - the 5th program.
5 - The Human-machine interface is done by a mini-website and a mini-cocktail database hosted in the Raspberry.
We decided to realize everything in micro-service architecture so each component may be used and developed separately. Each component above (python) are realized as web server responding on kind of REST API interface with few methods.
Here is the short video of communication with brain component that is in center of all micro-service architecture. This brain component call x-axis and l-axis components to ask move to correct position and ask to serve liquid in requested amount. In the video below I execute 2 requests - first is to brain module to ask to serve: small&glasses={"1": {"water": 10, "whiskey": 20}, "3": {"soda": 40}} - that mean I need in small glasses range deliver into first glass - 10 ml of water and 20ml of whiskey, and then in glass #3 - 10ml of soda. And then I check the status of the machine in "realtime". It looks like this:
$ curl "http://localhost:5001/mix?type=small&glasses=%7B%221%22%3A+%7B%22water%22%3A+10%2C+%22whiskey%22%3A+20%7D%2C+%223%22%3A+%7B%22soda%22%3A+40%7D%7D"
$ curl "http://localhost:5001/state"
added 40.0ml of soda to small glass #3
$ curl "http://localhost:5001/state"
added 40.0ml of soda to small glass #3
$ curl "http://localhost:5001/state"
added 40.0ml of soda to small glass #3
$ curl "http://localhost:5001/state"
ready
One of the difficult part was discovery functionality of gestalt python code. So we did it via exploration existing examples delivered with the library. As well one of the point was that initial "gestalt" python library named "gestalt" and it overridden by standard python library on MacOS X :-). So usage library internally iside project is the solution or switching to new version of library that has corrected name "pygestalt" :-).
MBP-de-Thomas:htmaa thomasfeminier$ python single_node.py
single_node.py: Warning: setting persistence without providing a name to the virtual machine can result in a conflict in multi-machine persistence files.
FABNET: port /dev/tty.usbserial-FTZ56QNH connected succesfully.
X Axis: http://www.fabunit.com/vn/086-005a.py
X Axis: RUNNING IN APPLICATION MODE
X Axis: loaded node from: 086-005a.py
CONTINUITY CHECK PASSED
X Axis: ERROR IN BOOTLOADER: CANNOT RESET NODE
X Axis: Motor current set to: 1.04A
26
{'writePosition': 1, 'stepsRemaining': 0, 'readPosition': 0, 'currentKey': 0, 'statusCode': 1}
{'writePosition': 2, 'stepsRemaining': 216, 'readPosition': 1, 'currentKey': 0, 'statusCode': 1}
{'writePosition': 3, 'stepsRemaining': 196, 'readPosition': 1, 'currentKey': 0, 'statusCode': 1}
{"writePosition": 3, "stepsRemaining": 159, "readPosition": 1, "currentKey": 0, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 117, "readPosition": 1, "currentKey": 0, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 95, "readPosition": 1, "currentKey": 0, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 54, "readPosition": 1, "currentKey": 0, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 34, "readPosition": 1, "currentKey": 0, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 214, "readPosition": 2, "currentKey": 1, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 174, "readPosition": 2, "currentKey": 1, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 149, "readPosition": 2, "currentKey": 1, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 106, "readPosition": 2, "currentKey": 1, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 86, "readPosition": 2, "currentKey": 1, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 48, "readPosition": 2, "currentKey": 1, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 27, "readPosition": 2, "currentKey": 1, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 207, "readPosition": 3, "currentKey": 2, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 169, "readPosition": 3, "currentKey": 2, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 149, "readPosition": 3, "currentKey": 2, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 110, "readPosition": 3, "currentKey": 2, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 68, "readPosition": 3, "currentKey": 2, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 46, "readPosition": 3, "currentKey": 2, "statusCode": 1}
{"writePosition": 3, "stepsRemaining": 5, "readPosition": 3, "currentKey": 2, "statusCode": 1}
MBP-de-Thomas:htmaa thomasfeminier$
MBP-de-Thomas:htmaa thomasfeminier$
Above is screenshot of our first start f the node. So it moves! :)
For debug purpose we defined a special module debug.py (accessible in git). It permit to execute some commands from python command line.
$ python
Python 2.7.10 (default, Nov 11 2015, 16:14:53)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import debug
>>> x=debug.xNodeController()
debug.py: Warning: setting persistence without providing a name to the virtual machine can result in a conflict in multi-machine persistence files.
FABNET: port /dev/tty.usbserial-FTZ56QNH connected succesfully.
X Axis: http://www.fabunit.com/vn/086-005a.py
X Axis: RUNNING IN APPLICATION MODE
X Axis: loaded node from: 086-005a.py
CONTINUITY CHECK PASSED
X Axis: ERROR IN BOOTLOADER: CANNOT RESET NODE
X Axis: Motor current set to: 1.04A
>>> x.stage.move([10], 1000)
......
To drive the machine we installed RaspberryPI model 2 near by the machine. On the raspberry we have all required components and running nodejs provided Web interface. This model contain enough USB ports to manage machine drive (gestalt FabNet) and arduino to manage Liquid-axis.
Here is the Arduino sketch that we used to pilot the stepper motors that control the taps :
#include
#define HALFSTEP 8
// Motor pin definitions
#define motor1Pin1 8 // IN1 on the ULN2003 driver 1
#define motor1Pin2 9 // IN2 on the ULN2003 driver 1
#define motor1Pin3 10 // IN3 on the ULN2003 driver 1
#define motor1Pin4 11 // IN4 on the ULN2003 driver 1
#define motor2Pin1 4 // IN1 on the ULN2003 driver 2
#define motor2Pin2 5 // IN2 on the ULN2003 driver 2
#define motor2Pin3 6 // IN3 on the ULN2003 driver 2
#define motor2Pin4 7 // IN4 on the ULN2003 driver 2
String inString = ""; // string to hold input
boolean connectedMotors = true; // To be changed on true when motors are connected and on false when used just for debug
boolean debug = true; // Some debug output can be activated here
int open_dist = 1500;
int measurement[16]={0,2000,2500,3000,4000,5500,7000,8500,10000,11500,13000,14500,16000,17500,19000,20500}; // Array to match duration (in ms) with quantity (in cl) - quantity as index of the array
// Initialize with pin sequence IN1-IN3-IN2-IN4 for using the AccelStepper with 28BYJ-48
AccelStepper stepper1(HALFSTEP, motor1Pin1, motor1Pin3, motor1Pin2, motor1Pin4);
AccelStepper stepper2(HALFSTEP, motor2Pin1, motor2Pin3, motor2Pin2, motor2Pin4);
void setup() {
stepper1.setMaxSpeed(1500.0);
stepper1.setAcceleration(200.0);
stepper1.setSpeed(200);
stepper2.setMaxSpeed(1500.0);
stepper2.setAcceleration(200.0);
stepper2.setSpeed(200);
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
ready();
}//--(end setup )---
void loop() {
while(Serial.available() > 0) {
char c=Serial.read(); // Read next char
inString += (char)c;
if (c==';') break; // End of command?
if (Serial.available()==0) delay(5);
}
if(inString.length()>2 && !isDigit(inString.charAt(0)) && isDigit(inString.charAt(1)) && isDigit(inString.charAt(2))) { // End of line? Anything received?
inString.trim();
if (debug){
Serial.println("Arduino program output:");
Serial.print(" On serial string received: ");
Serial.print(inString);
Serial.print(" Tap: ");
Serial.println(inString.charAt(0));
Serial.print(" Value: ");
Serial.print(inString.substring(1, 3).toInt());
Serial.println("cl");
}
commandAnalyze(); // Analyze
ready(); // Ready for next command
}
else if(inString.length()>2){
// No ; or newline?
Serial.println("Please format string correctly");
ready();
} else {
Serial.print(inString);
}
}
void commandAnalyze() {
// Command need to be send in following way. <id of tap A, B, C, D.. etc.<>qantity of liquid in centiliters in 1 decimal byte>. Example: A1, A5, B10
int qnt = inString.substring(1, 3).toInt();
if (qnt > 15){
inString="";
Serial.println("ERROR: We can not serve more then 15 centiliters");
sendERROR();
return;
}
if (qnt < 1){
inString="";
Serial.println("ERROR: We can not serve less then 1 centiliter");
sendERROR();
return;
}
if(inString.substring(0,1) == "A"){
if (debug){
Serial.println(" Open/Close tap A");
Serial.print(" For duration: ");
Serial.print(measurement[qnt]);
Serial.println("ms");
}
tap_A_open(measurement[qnt]);
sendOK();
} else if(inString.substring(0,1) == "B"){
if (debug){
Serial.println(" Open/Close tap B");
Serial.print(" For duration: ");
Serial.print(measurement[qnt]);
Serial.println("ms");
}
tap_B_open(measurement[qnt]);
sendOK();
} else {
Serial.println("Only taps A and B are supported");
sendERROR();
}
inString="";
}
void sendOK() {
Serial.println(F("<ok>"));
}
void sendERROR() {
Serial.println(F("<error>"));
}
void ready() {
inString="";
Serial.print(F(">>>\n")); // ready to receive input
}
void tap_A_open(int duration) {
if (connectedMotors){
stepper1.moveTo(open_dist);
while(stepper1.distanceToGo() != 0) {
stepper1.run();
}
}
delay(duration);
if (connectedMotors){
stepper1.moveTo(0);
while(stepper1.distanceToGo() != 0) {
stepper1.run();
}
}
}
void tap_B_open(int duration) {
if (connectedMotors){
stepper2.moveTo(open_dist);
while(stepper2.distanceToGo() != 0) {
stepper2.run();
}
}
delay(duration);
if (connectedMotors){
stepper2.moveTo(0);
while(stepper2.distanceToGo() != 0) {
stepper2.run();
}
}
}
This program drives the stepper motor, 1000 steps in a direction, then 1000 steps in the other direction. We used it to test the join/box mount as you can see in the video below :We use the serial monitor to pilot the distribution of liquid. Here is a test video where you can see the process :
Website to make an interface between human and the machine we developed in nodejs express using the bootstrap theme. This interface will permit in few steps order the shots using the simple and visual process of selection.
Main features :
- Number of glasses on the plate
- Small (10cl) or big glasses (25cl)?
- Number of glasses on the plate (5 max fpr small glasses, 4 mx for big glasses)
- Kind of shot: choose among the 4 liquids (to get a sigle alcoholc liquid shot) or choose between a combination of available liquids (for multi-layers alcoholic liquid shot) ?
- "GO" button, "In progress" progression bar, and "shots ready" alert on the screen
Technical aspects :
- A HTML responsive website for a multi-screen usage
This part is not completely finished yet but will be soon.