A control system framework for personal automation.
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:
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.
All of the code for this example is located in pygestalt/examples/arduino_basicNode/ .
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.
>> cd pyGestalt/examples/arduino_basicNode/
>> python
>>>
>>> import arduino_basicNode as arduino
>>> 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.
>>> myArduino.ledOn()
With any luck, the LED should immediately turn on.
>>> 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.
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.
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:
// ---- INCLUDES ----
#include <gestalt.h>
#include <util/delay.h>
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.
// ---- URL ----
char myurl[] = "http://www.fabunit.com/vn/examples/arduino_basicNode.py";
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.
// ---- GESTALT PORTS ----
#define LEDControlPort 10
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.
//---- IO DEFINITIONS ----
// LED
#define LED_DIR DDRB //Controls the pin direction
#define LED_PORT PORTB //Controls the output state of the pin
#define LED_PIN PINB //Used to read input state
#define LED_pin 5 //PB5. We use just the number b/c Arduino doesn't define the pin.
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.
//---- USER SETUP ----
void userSetup(){
setURL(&myurl[0], sizeof(myurl)); //Registers URL with Gestalt library
LED_DIR |= (1<<LED_pin); //Set the LED pin direction as an output
LED_PORT &= ~(1<<LED_pin); //Turn off the LED
};
Although this method of setting and clearing individual bits might seem complicated, it becomes pretty routine once you get used to the pattern.
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.
//---- USER LOOP ----
void userLoop(){
};
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.
//---- USER PACKET ROUTER ----
void userPacketRouter(uint8_t destinationPort){
switch(destinationPort){
case LEDControlPort: //a message was sent to the LED port
svcControlLED(); //call the LED control service routine
break;
// add additional case statements for new ports here, following the pattern immediately above.
}
};
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.
//---- UTILITY FUNCTIONS ----
void ledOn(){
// Turns on the indicator LED
LED_PORT |= (1<<LED_pin); //Turn on the LED
}
void ledOff(){
// Turns off the indicator LED
LED_PORT &= ~(1<<LED_pin); //Turn off the LED
};
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.
//---- SERVICE ROUTINES ----
// These are functions that get called by the userPacketRouter function
// when a message is received over the gestalt interface.
void svcControlLED(){
uint8_t command = rxBuffer[payloadLocation]; //first byte of payload
if(command){ //check if command is non-zero
ledOn(); //yes; turn LED on
}else{
ledOff(); //no; turn LED off
};
transmitUnicastPacket(LEDControlPort, 0); //transmit an empty (0 payload bytes) unicast packet to the LEDControlPort
};
And that’s it for the physical node firmware! Continue on to learn how to write 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.
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).
The only modules you need to import are a couple pyGestalt sub-modules.
#---- IMPORTS -----
from pygestalt import nodes
from pygestalt import core
from pygestalt import packets
All of the code for your virtual node should live inside a virtualNode class.
class virtualNode(nodes.arduinoGestaltVirtualNode):
"""The arduino_basicNode virtual node."""
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.
def init(self, *args, **kwargs):
"""Initialiation method for the virtual node instance."""
self.crystalFrequency = 16000000 #MHz
self.ADCReferenceVoltage = 5.0 #Volts
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:
def init(self, *args, **kwargs):
"""Initialiation method for the virtual node instance."""
pass
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.
def initPackets(self):
"""Initialize packet types."""
self.LEDControlRequestPacket = packets.template('LEDControlRequest',
packets.unsignedInt('command',1))
The packet template is defined with several arguments:
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.
def initPorts(self):
"""Bind functions to ports and associate with packets."""
self.bindPort(port = 10, outboundFunction = self.LEDControlRequest, outboundTemplate = self.LEDControlRequestPacket)
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.
def ledOn(self):
"""Turns on the LED on the physical node."""
return self.LEDControlRequest(True) #simply calls the virtual node service routine with a True (meaning "on") argument
def ledOff(self):
"""Turns off the LED on the physical node."""
return self.LEDControlRequest(False) #calls the virtual node service routine with a False (meaning "off") argument
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.
class LEDControlRequest(core.actionObject):
"""Controls the state of the node's indicator LED."""
def init(self, ledState):
"""Initializes the actionObject.
ledState -- a boolean value, where True will turn on the LED, and False will turn it off.
Returns True if a confirmation packet was received, or False if not.
"""
if ledState:
self.setPacket(command = 1)
else:
self.setPacket(command = 0)
if self.transmitUntilResponse(): #Transmits multiple times if necessary until a reply is received from the node.
return True #A response was received, so indicate the command was executed by returning True.
else: #No response was received, in spite of multiple attempts.
notice(self.virtualNode, 'got no respone to LED control request') #generate a notice to the user.
return False
At this point, you have written both the Arduino firmware living on the physical node, and also the pyGestalt virtual node. So now, please jump back to Part 1 and take your code for a spin! Or, continue to Part 4 where we teach you how to transmit data back from the physical node.