pyGestalt

Logo

A control system framework for personal automation.

View the Project on GitHub imnp/pygestalt

My First Arduino pyGestalt Node

Introduction

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:

  1. Turn on and off the built-in LED from a Python program.
  2. Do some arithmatic on the Arduino, and return the result.

Materials

Prerequisites

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.

  1. Open up the arduino_basicNode.ino file in your Arduino IDE.
  2. Plug in your Arduino.
  3. 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.

  1. Launch a terminal window.
  2. Navigate to the example directory (which contains arduino_basicNode.py). On my computer, it looks like this:
     >> cd pyGestalt/examples/arduino_basicNode/
    
  3. Start up the Python interpreter. This puts you into an interactive Python environment.
     >> python
     >>>
    
  4. Import the arduino virtual node module. We’ll use import as syntax to make the name simpler to work with.
     >>> import arduino_basicNode as arduino
    
  5. 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.

  6. Turn the LED on!
     >>> myArduino.ledOn()
    

    With any luck, the LED should immediately turn on.

  7. 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.

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:

// ---- INCLUDES ----
#include <gestalt.h>
#include <util/delay.h>

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.

// ---- URL ----
char myurl[] = "http://www.fabunit.com/vn/examples/arduino_basicNode.py";

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.

// ---- GESTALT PORTS ----
#define LEDControlPort    10 

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.

//---- 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.

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.

//---- 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.

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.

//---- USER LOOP ----
void userLoop(){
};

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.

//---- 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.
  }
};

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.

//---- 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
};

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.

//---- 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.

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.

#---- IMPORTS -----
from pygestalt import nodes
from pygestalt import core
from pygestalt import packets

3.3 virtualNode Class

All of the code for your virtual node should live inside a virtualNode class.

class virtualNode(nodes.arduinoGestaltVirtualNode):
	"""The arduino_basicNode virtual node."""

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.

    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

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.

    def initPackets(self):
        """Initialize packet types."""
        self.LEDControlRequestPacket = packets.template('LEDControlRequest',
                                                           packets.unsignedInt('command',1))

The packet template is defined with several arguments:

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.

    def initPorts(self):
        """Bind functions to ports and associate with packets."""
        self.bindPort(port = 10, outboundFunction = self.LEDControlRequest, outboundTemplate = self.LEDControlRequestPacket)

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.

    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

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.

    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.