Contribution
One part of our project is to create an incubator. To do that we designed a hardware and software system.
In the hardware system we built a module to move a plate in 2D. This module is used to agitate, and line up the wells with the detection module. To build it, we used 3D printed pieces, motors, ball bearing, etc… On this page we explain how to build this module.
For the software system we designed a communication protocol to permit great communication between the different microcontrollers. This protocol is the HermAs protocol, and we explain on this page how it works and how to use it. To fully understand this part, it is useful to have bases in object-oriented programming.
We also worked on a way to control the whole incubator using a graphical interface. For that we used a web server that directly communicate with Arduino microcontrollers using the communication protocol described above. As the web server is coded using the js language (nodejs), and the interface using html,css and js; knowing the basics of web/js programming is useful.
How to build an agitation module for a 96 well plate
List of necessary equipment:
- 4 linear rail bearing (ref. SCS8UU, dimensions, Ø: 8mm)
- 4 linear axis chromed steel (dimensions, l: 200mm, Ø: 8mm)
- 1m belt (dimensions, 6mm large)
- 2 pulley (dimensions, Ø: 5mm)
- 2 ball bearing (ref. 608Zz, dimensions, 8mmx22mmx7mm)
- bolts
- PLA plastic for the 3D printer
- 2 step motors Nema 17HS4401
- 2 motor drivers TMC2130
- 1 arduino + the power cable
- some arduino wires
- 2 Micro Switch V-156-1C25
- 2 steel axis with different diameters (dimensions, Ø: 5mm on 16mm and Ø: 8mm on 8mm)
List of necessary tools:
- 3D printer
- screwdrivers adapted to the screws
Building steps:
You can find here the .stl files needed to build the agitation module.
With those files and with the help of the software Cura, you can managed the parameters of the printed
pieces
to have the best result depending on the 3D printer you have.
After printing the seven plastic pieces, it’s now time to build the system !
Step 1: Insert the steel axis into the linear bearing before putting the two pieces from at the end from either side.
Step 2: Repeat the operation on the other side with the two same pieces.
Step 3: Insert the steel axis with the linear bearings into the two blue pieces.
Step 4: Fix with four screws from either side the part previously assembled.
Step 5: You can now fix the last plastic piece of this agitation module. It’s the 96 well plate holder, which is screwed to the two linear bearing.
Step 6: Insert the two ball bearing into the two pieces and the two steel axis needed to fix joint the bearings and the pulleys.
Step 7: Insert the pulley on the rotor axis of the two Nema motors and fix them wit screws to the two plastic pieces adapted.
Now the agitation module is complete, let’s deal with the electronic part !
The motors, the drivers, the switches and the Arduino Mega need power and a correct wire
network
to function properly.
In order to make the circuit connections, you juste have to follow the wire network
displayed
below.
You can find here the .stl files needed to build the agitation module.
HermAs protocol
The HermAs protocol is a master/slave communication protocol between a web interface named Pythie (master), and several Arduino microcontrollers (slaves). This protocol has the advantage to be adapted for any Arduino applications, for several Arduino at the same time, and to parallelize actions and requests in the same Arduino. The protocol is coded in javascript, css, html for the Pythie web interface, and in c++ for the Arduino microcontrollers. This page describes in detail the protocol and how to use it for your own engineer project.
To understand this page and use the protocol you need to have prerequisites in c++ programmation, and in web server development.
Explanation of HermAs
The HermAs protocol Arduino side is coded in c++. The c++ language is an object-oriented language, thus the HermAs c++ is a c++ class. The HermAs C++ class allows to read a message from the Raspberry, to interpret it, and to execute actions according to the message. These actions can be to only send an information, a confirmation, or to perform classic tasks for an Arduino (i.e. move a motor, open a relay, light a led, etc…). To do these tasks and to have a unique HermAs class for all the Arduinos (no matter the Arduino goal), the HermAs class has a OperatingClass argument, which is a class containing all the functions and all the arguments to release the Arduino goal.
The figure shows the main points of the HermAs communication for the Arduinos. In the first 2 lines we include 2 librairies: HermAs.h is the header file of the HermAs class. OpClassTest.h is the header file of the operating class.
On line 3, we create the operating object with its constructor. On the next line we create the HermAs object, with as parameters the arduino id, and the operating object. The operating object becomes an argument of the HermAs object.
In order for the HermAs object to have access to the arguments and execute the
operating class’s
methods (function of the class), the operating class must have an array of pointers for each
type of
arguments, but also for the methods. To simplify the code with the pointers, the operating
class can
only access to unint8_t, uint16_t, uint32_t, or float arguments. And it can only have
methods with
this prototype: uint8_t NameFunction( unint8_t notuse );
, no matter if the method uses or
doesn't
use the parameter (because of the message from the Raspberry, this parameter uses only 5
bits, and
not 8). The arguments and methods are numbered.
To force the operating class to have this array, this class inherits to a
virtual
superclass (named:
OperatingClass). This superclass allows to implement polymorphism in the HermAs class, and
to imperatively have arguments and methods. The mandatory arguments are: a methods pointers array,
the
number of methods, pointer arrays to each argument types, the number of each types. There
are 3
mandatory methods: 1 virtual method, and 2 pure virtual methods. The first virtual method
permits to
have a setup function for the operating class (to initiate the pin for example). These 2
other pure
virtual functions are used to initialise the pointers arrays. The arrays are initiated in a
HermAs
method: void setup_OpClass();
(used on the line 6, in the example). To initialise the arrays,
the
first pure virtual method ( void nbArgMeth();
) permits to define the number of each type
and the
number of methods. Then a HermAs method does a dynamic memory allocation of the arrays.
Finally the
second pure virtual method (void SetArgMeth_inTab();
) permits to address the pointers at
the
arguments, and at the methods. Thus each argument has an argument number, and each method
has a
method number.
The Waiting list
With these arrays, the HermAs class has easy access to the operating class arguments and methods. One HermAs protocol goal is to allow one or several operating class methods to be executed. To do that we use a waiting list. When the Raspberry demands to execute a function, it indicates if it is a loop execution and its periode. If it is not a loop execution, it indicates the time before the execution. The structure named WlFct contains all this information: the method’s number, the method parameter, the time before the execution, if it is a loop function, the loop periode. Thus the waiting list is a WlFct array. Every time the Raspberry asks to execute a function, the arduino fills the waiting list with a WlFct structure corresponding to the request. At each arduino loop, the HermAs object browses the array and updates the time before the execution (on line 12, in the Arduino script). If this time becomes negative, the HermAs object executes the function. And if it is a looping function the time before the execution is evaluated at the loop periode. Else the function is removed from the waiting list. The function can also be removed from the waiting list, if it returns an error (with the number 0), or if it is a loop function and it returns the number 2 (means that it works correctly but it is time to be removed from the waiting list). If the function works correctly, it returns 1.
The messages
Now that the HermAs c++ class can execute operating class methods, it must read and interpret a message from the Raspberry. The message is coded on 32 bits, with 8 header bits, and 24 body bits. In the header we find the id Arduino (to know for or from which Arduino is the message) on 3 bits. There is also the type of the message (on 2 bits): an acknowledgement / error message (noted ack), a debug message (noted debug), a request message (noted rqt), and an information message (noted inf). The last 3 bits are for the type of acknowledgement, the type of request, or the type of information. According to these types, the message's body has a different structure.
The debug messages are used to help the developer to design the HermAs class. The developer puts in the body message the data to send, so that the developer analyzes this data.
The ack messages are sent by an Arduino, for the Raspberry. They indicate an operation success, an error from the HermAs class, or an error from the operating class. The next figure explains the link between the coding message and its meaning:
header | Body | Note | ||||||
---|---|---|---|---|---|---|---|---|
id arduino | Type of message | T type | ||||||
id Arduino [3 bits] 0b111 to broadcast to all the Arduinos |
Tack 0b00 |
succes 0b000 |
0b000: "Hello" | To says that everthing is okay | ||||
0b001: "Ok" | ||||||||
0b010: "processing" | time before the end of the execution [21 bits] | |||||||
0b011: "continue" | ||||||||
0b100: "request well executed" | Type of request [21 bits] | |||||||
0b101: "function well executed" | function number [21 bits] | |||||||
error interpret 0b001 |
0b000: "wrong recipient" | id recipient [21 bits] | To says that there are an error with the operating class | |||||
0b001: "not understand" | ||||||||
0b010: "no content argument" | request argument number [21 bits] | |||||||
0b011: "no content function" | request function number [21 bits] | |||||||
0b100: "error remove function from the waiting list" | ||||||||
0b101: "waiting list full" | number of functions in the waiting list [21 bits] | |||||||
error functioning 0b010 |
0b000: "error pointer allocation" | type of allocation [21 bits] | To says that there are an error in the Hermes process | |||||
0b001: "error functioning operation class" | function number [21 bits] | |||||||
0b010: "error delay" | ||||||||
0b011: "error clacul" |
The inf messages are sent by an Arduino, for the Raspberry. They are an answer to a request message to give information about an argument, the waiting list, etc… The next table explains the link between the coding message and this meaning.
header | Body | Note | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
id arduino | Type of message | T type | ||||||||
id Arduino [3 bits] 0b111 to broadcast to all the Arduinos |
Tinf 0b11 |
1 argument 0b000 |
argument number [6 bits] | argument type [2 bits] | argument value [16 bits] | Give the value and the type of an argument | ||||
2 arguments 0b001 |
argument number 1 [6 bits] | arguments type [2 bits] | argument value 1 [8 bits] | argument value 2 [8 bits] | Give the value and the type of 2 arguments. They are consecutive and with the same type | |||||
number of arguments and functions 0b010 |
number of function [6 bits] | number of float type [5 bits] | number of uint8_t type [5 bits] | number of uint16_t type [5 bits] | number of uint32_t type [3 bits] | Give the number of function and the number of each type | ||||
Waiting list 0b011 |
number of function [6 bits] | the function parameter [5 bits] | loop function [1 bit] | time unit [2 bits] | time value [10 bits] | Give a waiting list content |
The rqt messages are sent by the Raspberry, for an Arduino. These messages can request to give information, set an argument, add a function in the waiting list, etc… The next table explains the link between the coding message and this meaning.
header | Body | Note | |||||||
---|---|---|---|---|---|---|---|---|---|
id arduino | Type of message | T type | |||||||
id Arduino [3 bits] 0b111 to broadcast to all the Arduinos |
Trqt 0b10 |
get 1 information 0b000 |
Type of information (Tinf) [3 bits] | argument number [6 bits] | ask an information | ||||
get 2 informations 0b001 |
Tinf 1 [3 bits] | Tinf 2 [3 bits] | argument number 1 [6 bits] | argument number 2 [6 bits] | ask 2 informations | ||||
set 1 argument 0b010 |
argument number [6 bits] | argument value [16 bits] | ask to set an argument | ||||||
set 2 arguments 0b011 |
argumenr number 1 [6 bits] | argument value 1 [8 bits] | argument value 2 [8 bits] | ask to set 2 consecutive argument | |||||
add in the waiting list 0b100 |
number of function [6 bits] | the function parameter [5 bits] | loop function [1 bit] | time unit [2 bits] | time value [10 bits] | ask to add a function to the waiting list | |||
resend a message 0b101 |
the half part of a message [16 bits] | ask to resend a message. request send in 2 messages. Use for the debug development. | |||||||
remove a function from the waiting list 0b110 |
function number [6 bits] | ask to remove a function from the waiting list | |||||||
reset the Arduino 0b111 |
reset the Arduino |
Extra information
The next parts explains some specific point of the HermAs functioning.
Each Arduino has an id coding on 3 bits. For our incubator our Arduinos have these id:
id arduino | Arduino |
---|---|
0b001 | TH control |
0b010 | plate move |
0b011 | measurement control |
The id: 0b111 is kept to send a message to all the Arduinos.
There are 4 argument types, and 1 method type. Each argument type has an id (coding on 2 bits), and a limited number of allocation (define by the length message):
argument type | id | maximum number per type |
---|---|---|
float | 0b00 | 32 (coding on 5 bits) |
uint8_t | 0b01 | 32 (coding on 5 bits) |
uint16_t | 0b10 | 32 (coding on 5 bits) |
uint32_t | 0b11 | 8 (coding on 3 bits) |
Each argument has a number coding on 6 bits. So the operating class can not have more than 64 arguments. Thus the operating class is limited either by the total number of arguments, or by the allocation limit of an argument type.
The number of an is equal to its position in the array pointers of this own argument type, plus the maximum number of the previous types. For example, if the operating class has 3 float arguments, 2 uint8_t arguments, 1 uint16_t argument, and 4 uint32_t arguments.
Thus the first uint8_t argument is numbered 4 = 3 (number of float argument) + 1 (its position in the uint8_t argument) - 1 (because count starts to 0).
The uint16_t argument is numbered 5 = 3 (number of float argument) + 2 (number of uint8_t argument) + 1 (its position in the uint8_t argument) - 1 (because count starts to 0).
The second uint32_t argument is numbered 7 = 3 + 2 +1 + 2 - 1.
The id method type is 0b100. Each method has a number coding on 6 bits. So the operating class can not have more than 64 methods. Because there is only one method type, the numbering is simpler. The number of a method is the method position in the array pointers method.
A float argument is set (with a request message) with 2 decimals. Thus because a float setting message is coded on 16 bits
Because of the length message from the Raspberry, the loop periode is coding on 12 bits: 10 bits for the value, 2 bits for the units:
bit code | unit time |
---|---|
0b00 | milliseconds |
0b01 | seconds |
0b10 | minutes |
0b11 | hours |
The next figure is a class diagram of the 3 classes used in the HermAs protocol. The diagram resumes the principal methods and arguments used for each class.
Use the HermAs protocol
To use the HermAs protocol for your project, you must follow different steps:
- First you have to download the HermAs folder (with the HermAs.cpp and the HermAs.h files), and put it in the Arduino library.
- Secondly you have to create an Arduino library (a .cpp and .h files in the library
Arduino
folder). In this library, you have to create an operating class.
- As explained before, this class must inherit the superclass named:
OperatingClass (this
superclass is defined in the HermAs.h file). Your class must use only these
arguments:
float, uint8_t, uint16_t, uint32_t. And use only methods with this prototype:
uint8_t
MethodName( uint8_t parameter);
, no matter if you use or not the parameter. If you need several parameters you can use the class's arguments as parameters. - Your class must override the 3 pure virtual functions of the OperatingClass:
void setup(): Write in this method the setup lines you need for your Arduino.
For
example the pinMode(); functions, or complex constructors from other libraries.
void SetNbArgMeth(): In this method you must write the number of each argument type, and the number of methods from your class, in the arguments (define in the superclass): nbArgFloat, nbArgInt8, nbArgInt16, nbArgInt32, nbMethod.
void SetArgMeth_inTab(): In this method you set the arguments and the methods in the pointers arrays. To do that you must associate the arguments address to the array element: Tabi8[0] = & Int8Arg1; Tabi8[1] = & Int8Arg2; Tabi16[0] = & Int16Arg; Tabi32[0] = & Int32Arg; TabF[0] = & FloatArg;. For the method you must cast the address method with the pointer type ptrf (define in the HermAs.h file): TabMeth[0] = (OperatingClass::ptrf) & OpClassName::MethodName;
- As explained before, this class must inherit the superclass named:
OperatingClass (this
superclass is defined in the HermAs.h file). Your class must use only these
arguments:
float, uint8_t, uint16_t, uint32_t. And use only methods with this prototype:
uint8_t
- Finally create the .ino file as in the figure XX example. You have to change the opClassTest initialisation with your operating class, and put it in the second parameters of the HermAs constructor. The first parameter is the id Arduino that you want to give to your Arduino.
The files are ready to be uploaded in your Arduino. You can now launch the Pyhtie web server, and use its interface to send requests to your Arduino.
You can find here our sources, in order to use it and have an example of an operating class.
Web-server and Pythie Interface
To use the testing bench, and program experiments, we conceived a GUI (Graphical User Interface) that is both easy to use, and does everything it needs to. This interface is provided by a web server ran on the Raspberry Pi that communicates with the Arduino microcontrollers used to operate the incubator. This web server is already pretty complex for the beginners, thus, we made a simpler, light-weight version of it that is available here to download. Yet event this version needs some explanation, and this is what we'll be doing below.
To fully understand the oncoming explanations, please be advised that you may need to know basics of web development and specifically the usage of "js" language.
Explanation of Pythie, the GUI (Graphical User Interface)
One of the main advantages of using an interface that is a web application is that it is available on a very large amount of devices. From smartphones to computers, as well as tablets and even game consoles, any device that includes a web browser could access the interface. The only requirement to do that is to be connected to the same private network as the testing bench (it is possible to connect the web server to a public domain, but it would expose it to possible hacks if it is not secure enough). On its light-weight version, the GUI is only composed of terminal allowing the used to send messages to the microcontrollers and see their answer(s). Various methods exist to send messages, it is possible to send hexadecimal and binary code, using respectively "HEX:FFFFFFFF" (8 characters) or "BIN:01010101010101010101010101010101" (32 characters). It is also possible to make specific headers to send instructions, for example: "LED:0:0:255:255:255" will light the led at the position (0,0) of the matrix in white (using rgb code).
The advantages of having such way of communicating are numerous, first, it is very versatile, it is very easy to make new headers, and based on its size, they can be very descriptive. Second, the answers received by the arduino can easily be read, if the necessary is done during the step of translation of the messages (see above for the correspondence between a message and its meaning). Finally, scaling such a simple interface to one more complex is very easy, adding buttons that does the equivalent of sending a message is simple, and everything will still be displayed on the terminal.
For the interface to be able to freely send AND receive data from the server,
we use the Websocket
communication protocol, allowing for many users to connect
to the
server at the same time without problems. More informations on this protocol will be given
below as the whole data exchange is based on it.
Brief explanation of the Web Server
The web server has, in the system, the role of the orchestra conductor. Everything goes through it, and it is the one that decides what to do with what it is given. It has the role of interpreting the requests given through the web interface, the answers given by the microcontrollers, and consequently executing specific functions.
In its light-weight version, the web server only has to initialize the communication with the microcontrollers, the Websocket protocol and to actually start the server to deliver the interface. In its more complex version, the server also communicates with a database that holds all of the experiments data, initialize a security protocol allowing the existence of users, admins, and the logging.
Detection and interfacing of Arduino microcontrollers
To detect the microcontrollers connected to the Raspberry Pi, the web server
makes use of various modules (npm packages). The ones used are visible at the start of the
file hermAs.js
situated in the folder of the same name. The init()
function, called in the
main file, is used to browse through all the usb devices connected to the Raspberry. Using
the productId
and the vendorId
of the connected devices, the
server is able to determine
which ones are Arduino or not. This is done by providing the init()
function
the
combinations of corresponding ids of microcontrollers. Those can be found using various
softwares available for free and very easily with a browser research. As you can see in the
screen below, we used different microcontrollers, some being counterfeits as they are
everywhere on the market.
Once identified, the microcontrollers are initialized, the serial ports used
for the communication are configured, and a Parser
is put on this port. The
role of this
last one is to send a signal once data is available on the port, this data generally is an
answer coming from an Arduino. This parser is configured to look for 4 bytes of data as the
HermAs protocol use messages of the same size, thus the communication is easily scalable to
use longer messages by replacing this value to a bigger one.
All of these configurations are stored in an array of variables, holding
information for the connected microcontrollers. This array will then be used by the web
server to send messages. The hermAs file also holds all of the functions used to
communicate, the ones to send requests and the ones to interpret answers following the
protocol. Receiving an answer will trigger an event
that can be processed by
informing the
user using the Pythie interface and also by executing specific functions depending on for
example, the argument received, the function executed, etc...
You can see above, the code in the file requests.js
used to match
an argument received by a specific arduino to a function. The array
keyValuePair
is easily scalable, and clearly specifies what function will be
executed upon receiving an argument. This is done by calling the function
computeArg()
while interpreting the answers in the file
hermAs.js
WebSockets: Exchange of information between client and server
Websockets are used to exchange information between the server and the
possibly many clients connected to it. They work by attributing to every client an id that
is used to know from whom the information comes from and to whom they have to be sent. One
of the advantages of using websockets is that this protocol is symmetric on the way of
coding the exchanges. Using the package socket-io
, the code below shows how, in
the light-weight version, the data is processed.
On the server-side, we need to differentiate two ways of sending messages, the
standard one, used by calling socket.emit()
, and the broadcast one, using
io.emit()
. Using socket
refers to a single client, while the other
refers to all of them. the key-word emit
is used to send data, this data can be
labelled, making it simple for the recipient to interpret it. In the example above,
"consoleInput" and "serverResponse" are used. the key-word on
is used to detect
when data is received, labelled with the subsequent string. A function can then be executed
using the data received, resulting on an action with the microcontrollers and most of the
time, an answer using another label. The symmetry of this protocol makes it so that the
coding on the client-side is very similar, also using the same key-words and corresponding
labels.
Extra information
The explanation above concerns the simpler, light-weight version of the web server, while in reality we used a more complex one. The one that we actually run in our different proof of concept videos has many more possibilities including the presence of a logging page, the usage of a database to store data, extra security to ensure that only authorized users can access the interface, etc...
You can find all the files we used on the following github repository. The software is spread across different folders which names are made to be as explicit as possible for the advanced programmers out there.