In this tutorial, we will walk you step-by-step through the process of creating an Arduino-based physical node and a complementary Python-based virtual node. By the end of the tutorial, you will be able to write firmware for your own custom nodes, and then import and interact with them from any Python program.
We’re going to start with two basic examples that touch on various aspects of the framework:
Turn on and off the built-in LED from a Python program.
Do some arithmatic on the Arduino, and return the result.
Install / register the Gestalt firmware library with your Ardunio IDE. You can check Arduino’s Library Installation Instructions for instructions, but an easy way to do this is to copy the gestalt.h and gestalt.cpp files from the ‘firmware’ folder into a folder you name ‘Gestalt’ your Arduino libraries folder (e.g. ~/yourarduinosketchbook/libraries/Gestalt/)
Part 1: Blink an LED – Quick-Start
We’re going to show you how to write your own custom nodes on both the firmware and Python sides. But first, we’d like to give you the chance to jump to the end and use our pre-written example code to get a flavor of the end goal. This will also help you make sure you have all of the elements configured correctly, including the Arduino IDE, your Python environment, and the pyGestalt library.
1.1 Navigate to the Arduino example code
All of the code for this example is located in pygestalt/examples/arduino_basicNode/ .
1.2 Load the Arduino sketch onto your Arduino.
Open up the arduino_basicNode.ino file in your Arduino IDE.
Plug in your Arduino.
Click the “Upload” button to compile and load the firmware onto the Arduino.
Note: If the code fails to compile, be sure that you have the Gestalt firmware library registered with the Arduino environment.
1.3 Control the LED!
We are going to show you how to do this using the Terminal. However, you can also create a new python script using the exact same commands, with the same effect.
Launch a terminal window.
Navigate to the example directory (which contains arduino_basicNode.py). On my computer, it looks like this:
>>cd pyGestalt/examples/arduino_basicNode/
Start up the Python interpreter. This puts you into an interactive Python environment.
>> python
>>>
Import the arduino virtual node module. We’ll use import as syntax to make the name simpler to work with.
>>>importarduino_basicNodeasarduino
Create an instance of the virtual node.
>>>myArduino=arduino.virtualNode()
Python will think for a few moments and then display something like this:
[tty.usbmodem1411] Successfully connected to port /dev/tty.usbmodem1411
>>>
We’ve now connected to the Arduino over a USB -> serial connection, performed a handshake, and are ready to start issuing commands.
Turn the LED on!
>>>myArduino.ledOn()
With any luck, the LED should immediately turn on.
Turn the LED off!
>>>myArduino.ledOff()
And it turns off. Note that True will display after each command is run. This is the value returned by the virtual node, to indicate that the physical node has confirmed the requested action was completed successfully.
Now that you’ve gotten a flavor of how easy it is to import external hardware and start controlling it from within Python, we’re ready to walk step-by-step through the underlying code for the physical and virtual nodes.
Part 2: Blink an LED – Writing the Firmware
2.1 Fire up the Arduino IDE
This part is self-explanatory, but go ahead and initialize a new sketch in the Arduino IDE.
We typically write nodes with a certain structure, so that we don’t forget any steps. In this tutorial we’ll walk you thru each section individually.
2.2 Specify Includes
The includes section is where you’ll tell the IDE which libraries to include when compiling your code. We only need two for this tutorial:
gestalt.h is the header file for the Gestalt library, and is required for every node.
delay is the standard Arduino delay library
2.3 Assign URL
Every Gestalt node has a complementary Python virtual node. It is possible to publish these Python files online, and pyGestalt will automatically download and import them. This is similar to automatically downloading drivers for new hardware. In the URL section, we define a custom URL where the virtual node can be found. For now, we’ll just use something imaginary. Keep in mind that we take no responsibility for security issues that might arise when you automatically download and execute Python files from the internet.
2.4 Define Gestalt Ports
Virtual and physical node functions are connected by ports. These are simply mutually-agreed upon internal addresses assigned to each Gestalt function, where messages can be sent and received. Ports 0-9 and 255 are reserved by the Gestalt firmware library, but you are free to assign ports 10-254 as you wish.
2.5 IO Definitions
It can be cleaner to define all pin definitions in one place, which we will do here. Note that we’re going to use the bare-metal AVR C pin definitions. Bear with us! We’ll show you how to set pin directions and state without using the somewhat bloated Arduino functions.
DDRB is an internal register that controls pin direction
PORTB is an internal register that controls pin output state.
PINB is an internal register that reads pin state.
PB5 is the IO pin to which Arduino has an LED already pre-wired.
2.6 User Setup
The Gestalt library commandeers Arduino’s setup() function, but instead provides us with userSetup(). This is called once, towards the beginning of program execution. It’s where you’ll put any code needed to configure the node.
First, we explicitly register the URL with the Gestalt library
Next, we set the direction of the LED pin to an output. All we’re doing is saying “set the bit corresponding to the LED_pin, while not altering anything else.” This is accomplished by bit-shifting a 1 into the LED_pin position, and then writing it to the port with an OR-EQUALS operator.
Finally, we make sure the LED starts out being off. This is accomplished similarly to the above, but since we’re clearing the bit instead of setting it, we first invert the byte and then use the AND-EQUALS operator.
Although this method of setting and clearing individual bits might seem complicated, it becomes pretty routine once you get used to the pattern.
2.7 Main User Loop
Just like the standard Arduino setup() function, loop() is needed by the Gestalt library. You can put any code that runs in a loop inside userLoop(). Our userLoop() will be empty for these examples.
2.8 User Packet Router
When messages arrive at the node, they need to be routed to the correct service routine based on the port to which they are addressed. userPacketRouter() is called whenever a message arrives, with the target port number as the only argument. This is really the switch-board that connects your physical node to its virtual counterpart.
2.9 Utility Functions
Typically there’s some tasks that are repetitive or error-prone to reproduce throughout the code, so we write small “utility” functions to handle them. For example, turning on or off an LED, stepping a motor (or changing it’s direction), etc. For this example, we have utility functions for turning on and off the LEDs.
2.10 Service Routines
Messages arrive from the virtual node and must be received, interpreted, and executed upon by the physical node. We call the function that does this a service routine. Most service routines not only receive a message, but also respond as well; maybe something as simple as an empty packet to acknowledge receipt, or a payload laden with e.g. a sensor reading. Here we write a simple service routine to turn on or off the LED, depending on the received payload. After receipt, an “empty” paycket – meaning one with no data payload – is transmitted in reply.
First, we read the first payload (data) byte from the Receive Buffer. This buffer is a reserved area of memory used to record message bytes as they arrive. The data section of the message – meaning the actual content of the message rather than the preamble or closing bytes – starts at the keyword payloadLocation that is defined by the Gestalt firmware library. In our case, the payload is a single byte instructing the node whether to turn on or off the LED. When we write the pyGestalt virtual node, we’ll show you how to build this packet.
Next, we simply check whether the LED command byte is zero or non-zero, indicating whether to turn off or on the LED, respectively. Using our utility functions, we then make it happen.
Finally, we send a return message to the virtual node (VN). This indicates receipt, and can also be used to pause program execution on the VN side until the command is executed. If turning on or off the LED required communicating with an external controller, you might consider returning a single “success” byte as part of the response message.
And that’s it for the physical node firmware! Continue on to learn how to write the virtual node.
Part 3: Blink an LED – Writing the Virtual Node
Here we will show you how to write a corresponding Python module – what we call the *virtual node* – that can be imported into your Python scripts in order to directly control the physical node. We will follow a similar format to writing the physical node firmware; each relevant section of the code will be handled individually. By the end, you’ll have a complete end-to-end control system for turning on and off an LED.
3.1 Fire up a Python code editor
Create a new Python file in your favorite text editor. We use Eclipse (so passe, we know!), but you could use really anything. Sublime Text is also quite nice and does helpful syntax highlighting. You’ll need to name the file “arduino_basicNode.py” so that it matches the filename specified in the physical node’s URL (or adjust per Section 2.3 above to your own taste).
3.2 Imports Section
The only modules you need to import are a couple pyGestalt sub-modules.
pygestalt.nodes gives you access to the pyGestalt virtual node base classes
pygestalt.core provides access to the actionObject class for creating service routines
pygestalt.packets for access to the packet templates and encoding types
3.3 virtualNode Class
All of the code for your virtual node should live inside a virtualNode class.
the class must be called “virtualNode” in order for consistency, and for it to be automatically imported correctly within the context of a virtual machine.
The base class of this particular node should be nodes.arduinoGestaltVirtualNode. This takes care of a lot of configuration for you, including setting the correct commnications baud rate.
3.4 init Function
The virtual node base class overrides the standard __init__() function, and instead provides init(). This is called when the virtual node is first initialized, and gets passed any (non-reserved) arguments provided on initialization of the virtual node. This is a good place to define any parameters relevant to the specific node.
The above are just examples of parameters you might typically put in the init() function, although are not immediately relevant to the LED control example. If you don’t want to put anything in init(), simply don’t declare it, or make this the first and only line in the function:
3.5 initPackets Function
As a communications framework, one of the primary tasks of pyGestalt is to make it easier to pass various data types back and forth between the virtual node and the firmware running on the physical node. Data is transmitted via packets, which are sequences of bytes that follow a specific format. Gestalt packets contain a fixed number of framing bytes, and then a variable number of payload bytes. The packets sub-module helps you define and encode these packet payloads with a rich set of data types. For the purposes of our example, we’ll define a simple packet for transmitting an on/off control signal to the Arduino via a single data byte.
The packet template is defined with several arguments:
A name for the template, used for logging and reporting errors
An unlimited number of additional arguments, each containing packetTokens from the pygestalt.packets submodule. The sequence of these tokens defines how they are ordered in the packet payload. Each token has a mandatory name - used to reference the token inside the packet - and any additional parameters needed to set up the token. In our example, we’re transmitting a single unsigned value to the physical node, encoded within one byte of payload data.
3.6 initPorts Function
In order for service routine functions in the virtual node to communicate with the service routines on physical node, they must both have a commonly shared identifier (referred to in pyGestalt as a port), and a common understanding of how transmitted information is encoded. self.bindPort() “binds” virtual node service routines to port numbers, and then associated them with the packet templates that are used to encode and decode transmitted communication packets. With these associations in place, pyGestalt is able to automatically assist in shuttling messages in the correct format between virtual and physical nodes.
self.bindPort is an internal function of the virtualNode base class
the port argument specifies a port number. This must match the associated port number provided to userPacketRouter in the physical node firmware.
outboundFunction is the virtual node service routine responsible for controlling the LED. We haven’t written this yet, but will be doing so in a few steps.
outboundTemplate is the packet template - defined in the prior section - that is responsible for encoding the message to the physical node.
Note that we don’t specify an inbound template, because the response from the physical node is an “empty” acknowledgement containing no payload data.
3.7 Public User Functions
This is the section where we write functions that are explicitly intended for the user to call. These are useful in situations like our LED demo, where the user might want to simply call ledOn() or ledOff(), rather than calling the service routine that actually transmits the command to the physical node. (If this doesn’t make sense yet, read ahead to the next section). Note that these functions should be indented so that they are at the same level as all of the init functions we just wrote.
3.8 Service Routines
Service routines are functions that are responsible for communicating with complementary service routines on the physical node. These special functions are actually implemented as classes with an actionObject base class. We won’t go into all of the details here, but pyGestalt follows a pattern where calls to service routines do not simply generate encoded packets. Rather, an actionObject is generated and preserved until the very moment before transmission, at which point it spits out an encoded packet. The short reason for this is that for more complicated controls applications, such as those requiring motion planning, the final output of the function might change well after it is first called.
First, we create a service routine “action object” class, with actionObject as its base class. The name of the service routine must match the name we used in the bindPort and public user functions. Note that we’re creating a class, not a function. This is important, as a call to the service routine will generate an instance of our own custom actionObject.
Next, we start working inside the init() function of the actionObject. This is called whenever a new class instance is initialized. So, for example, a user might call LEDControlRequest(True), and this will result in the call: init(True). Any arguments you want your service routine to accept should be parameters of the init() function. In our case, we want to know whether the LED should be on or off, which we’ll accept as a parameter called ledState.
The following several lines are responsible for “setting” the outgoing packet, meaning configuring the parameters of the packet before it is encoded. In our packet definition in 3.5, we defined the entire packet payload as just one single-byte unsigned integer called command. Now, we are setting the value of command. If the user wants to turn on the LED (a True argument to the service routine), the value of command should be “1”. Otherwise, “0”.
Lastly, we call the internal helper function self.transmitUntilResponse(). This function not only transmits the encoded packet to the physical node, but it waits for a reply (and even re-transmits several times if necessary). If a response is eventually received, the function returns True. If, after numerous attempts, no return packet is received, self.transmitUntilResponse() returns False.