Pneumatics Animation Example

This module provides a basic template for a command-line Python script to communicate with an Arduino running the ValveControl Arduino Sketch. The script itself just reads and writes raw I/O data, but could be extended to implement application logic. The script uses just one Arduino but is designed to be extended for multiple.

The full code can be found in several files in pneumatics_animation or downloaded as a single zip file pneumatics_animation.zip

animation_example.py

This application script can be run from the command line to interact with an Arduino over a serial port.

  1#!/usr/bin/env python
  2"""\
  3animation_example.py : example command-line program to communicate with an Arduino over a serial port and execute timed logic.
  4
  5Copyright (c) 2017, Garth Zeglin.  All rights reserved. Licensed under the terms of the BSD 3-clause license.
  6"""
  7
  8from __future__ import print_function
  9import os, sys, time, argparse, select
 10
 11from ValveControlProtocol import ValveControlProtocol
 12
 13################################################################
 14def update_animation_state(now):
 15    """Stub for a function called periodically to evaluate the sensor inputs and generate the next cycle of animation outputs.
 16    
 17    :param now: current wall clock time
 18    """
 19    
 20    pass
 21
 22################################################################
 23def event_loop(arduinos):
 24    """Prototype for an event-processing loop which manages I/O on multiple serial ports.
 25
 26    :param arduinos: list of Arduino protocol objects
 27    """
 28
 29    polling_interval = 0.5
 30    polling_timer = polling_interval
 31    last_time = time.time()
 32    
 33    while True:
 34        # Check which Arduinos have data available, using select with a short timeout.
 35        ready = select.select(arduinos,[], [], max(polling_timer, 0.0))
 36
 37        # Process messages on each Arduino port with available data. 
 38        for arduino in ready[0]: arduino.wait_for_message()
 39          
 40        # Check whether enough time has elapsed to run a polling cycle, and recalculate the next timeout.
 41        now = time.time()
 42        polling_timer -= (now - last_time) # subtract elapsed time
 43        last_time = now
 44        
 45        # When the polling interval has expired:
 46        if polling_timer <= 0:
 47            polling_timer += polling_interval
 48
 49            # Apply any polled logic here; this will be called at a constant average rate.
 50
 51            # Print a debugging message stream.
 52            for arduino in arduinos:
 53                print("arduino status: awake: %s, joints: %s" % (arduino.awake_time, arduino.joints))
 54
 55            # Callback to update the animation based on any new data.
 56            update_animation_state(now)
 57
 58################################################################
 59# The following section is run when this is loaded as a script.
 60if __name__ == "__main__":
 61
 62    # Initialize the command parser.
 63    parser = argparse.ArgumentParser( description = """Example command line program to interface with an Arduino sketch.""")
 64    parser.add_argument( '-v', '--verbose', action='store_true', help='Enable more detailed output.' )
 65    parser.add_argument( '--debug', action='store_true', help='Enable debugging output.' )
 66    parser.add_argument( '-p', '--port', default='/dev/tty.usbmodem1411', help='Specify the name of the Arduino serial port device (default is /dev/tty.usbmodem1411).')
 67
 68    # Parse the command line, returning a Namespace.
 69    args = parser.parse_args()
 70
 71    # Extract specific command line options into a keyword argument dictionary.
 72    kwargs = { 'port' : args.port, 'debug' : args.debug, 'verbose' : args.verbose }
 73    
 74    # Create a protocol object to manage the serial port.
 75    arduino = ValveControlProtocol(**kwargs)
 76
 77    # Connect to the Arduino, which will also reset it.
 78    print("Connecting to Arduino.")
 79    arduino.open_serial_port()
 80
 81    # issue a few test commands
 82    time.sleep(2)
 83    arduino.set_flow(50, extend=1, retract=2)
 84    time.sleep(1)
 85    arduino.set_flow(-50, extend=1, retract=2)
 86    arduino.set_flow(50, extend=3, retract=4)
 87    time.sleep(1)
 88    arduino.set_flow(-50, extend=3, retract=4)
 89    time.sleep(1)
 90    arduino.set_flow(0, extend=1, retract=2)
 91    arduino.set_flow(0, extend=3, retract=4)        
 92    
 93    # Now run the event loop until it exits or control-C is pressed.
 94    try:
 95        event_loop([arduino])
 96            
 97    except KeyboardInterrupt:
 98        print("User interrupted operation.")
 99
100    # Shut down the connection.
101    arduino.close_serial_port()
102        

four_joint_example.py

This application script can be run from the command line to interact with a pair of Arduinos driving up to four actuators.

  1#!/usr/bin/env python
  2"""\
  3four_joint_example.py : example command-line program to communicate with two Arduinos over serial ports and execute timed logic.
  4
  5Copyright (c) 2017, Garth Zeglin.  All rights reserved. Licensed under the terms of the BSD 3-clause license.
  6"""
  7
  8from __future__ import print_function
  9import os, sys, time, argparse, select
 10
 11from ValveControlProtocol import ValveControlProtocol
 12from SensorProtocol import SensorProtocol
 13
 14################################################################
 15def polling_wait(arduinos, duration=0.0):
 16    """Poll all Arduinos and process pending data for a given interval.
 17
 18    :param arduinos: list of Arduino protocol objects
 19    :param duration: time in seconds to poll before returning (default to zero)
 20    """
 21    last_time = time.time()
 22    pending = True
 23
 24    while pending or duration > 0.0:
 25        # Check which Arduinos have data available, using select with a short timeout.
 26        ready = select.select(arduinos,[], [], max(duration, 0.0))
 27        pending = False
 28        
 29        # Process messages on each Arduino port with available data.  If any message
 30        # is received, reflag pending to ensure checking again.
 31        for arduino in ready[0]:
 32            arduino.wait_for_message()
 33            pending = True
 34          
 35        # Recalculate the maximum timeout.
 36        if duration > 0.0:
 37            now = time.time()
 38            duration -= (now - last_time) # subtract elapsed time
 39            last_time = now
 40
 41################################################################
 42# The following section is run when this is loaded as a script.
 43if __name__ == "__main__":
 44
 45    # Initialize the command parser.
 46    parser = argparse.ArgumentParser( description = """Example command line program to interface with an Arduino sketch.""")
 47    parser.add_argument( '-v', '--verbose', action='store_true', help='Enable more detailed output.' )
 48    parser.add_argument( '--debug', action='store_true', help='Enable debugging output.' )
 49
 50    # Parse the command line, returning a Namespace.
 51    args = parser.parse_args()
 52
 53    # Extract specific command line options into a keyword argument dictionary.
 54    kwargs = { 'debug' : args.debug, 'verbose' : args.verbose }
 55    
 56    # Create protocol objects to manage the serial ports.
 57    valve1 = ValveControlProtocol(port = "/dev/tty.usbmodem141431", **kwargs)
 58    valve2 = ValveControlProtocol(port = "/dev/tty.usbmodem141441", **kwargs)
 59    sensors = SensorProtocol(     port = "/dev/tty.usbmodem141421", **kwargs)
 60    arduinos = [valve1, valve2, sensors]
 61    
 62    # Connect to the Arduinos, which will also reset them.
 63    print("Connecting to Arduinos.")
 64    
 65    for arduino in arduinos:
 66        arduino.open_serial_port()
 67
 68    # issue a few test commands
 69    time.sleep(2)
 70
 71    # Begin an animation sequence until it finishes or control-C is pressed..
 72    try:
 73        valve1.set_flow(50, extend=1, retract=2)
 74        polling_wait(arduinos, duration=1.0)
 75        
 76        valve1.set_flow(-50, extend=1, retract=2)
 77        valve1.set_flow(50, extend=3, retract=4)
 78        polling_wait(arduinos, duration=1.0)
 79
 80        valve1.set_flow(-50, extend=3, retract=4)
 81        polling_wait(arduinos, duration=1.0)
 82
 83        valve1.set_flow(0, extend=1, retract=2)
 84        valve1.set_flow(0, extend=3, retract=4)
 85        polling_wait(arduinos, duration=1.0)
 86                
 87        valve2.set_flow(50, extend=1, retract=2)
 88        polling_wait(arduinos, duration=1.0)
 89        
 90        valve2.set_flow(-50, extend=1, retract=2)
 91        valve2.set_flow(50, extend=3, retract=4)
 92        polling_wait(arduinos, duration=1.0)
 93
 94        valve2.set_flow(-50, extend=3, retract=4)
 95        polling_wait(arduinos, duration=1.0)
 96
 97        valve2.set_flow(0, extend=1, retract=2)
 98        valve2.set_flow(0, extend=3, retract=4)        
 99        polling_wait(arduinos, duration=1.0)
100            
101    except KeyboardInterrupt:
102        print("User interrupted operation.")
103
104    # Shut down the connections.
105    for arduino in arduinos:    
106        arduino.close_serial_port()

ValveControlProtocol.py

ValveControlProtocol.py : application-specific class to manage serial communication with an Arduino ValveControl sketch.

Copyright (c) 2015-2017, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license.

class pneumatics_animation.ValveControlProtocol.ValveControlProtocol(**kwargs)[source]

Example class to communicate with the ValveControlASCII Arduino sketch.

awake_time

The clock time of the most recent Arduino awake message.

joints

Dictionary of most recent joint status data.

message_received(tokens)[source]

Application-specific message processing to parse lists of tokens received from the Arduino sketch. This implementation just stores values in instance attributes. This method overrides the null implementation in the parent class.

Parameters:

tokens – list of string tokens

set_flow(flow, extend=1, retract=2, verbose=False)[source]

Issue an open-loop set of flow commands for a valve pair attached to an actuator. Returns immediately. No return value.

Parameters:
  • flow – net flow from -100 full retraction rate to 100 full extension rate

  • extend – channel number for extension valve pair

  • retract – channel number for retract valve pair

  • verbose – flag to enable printed debugging output

Returns:

None

SensorProtocol.py

SensorProtocol.py : application-specific class to manage serial communication with an Arduino sketch reporting sensor data.

Copyright (c) 2015-2017, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license.

class pneumatics_animation.SensorProtocol.SensorProtocol(**kwargs)[source]

Example class to communicate with an Arduino sketch which reports lists of numbers.

message_received(tokens)[source]

Application-specific message processing to parse lists of tokens received from the Arduino sketch. This implementation just converts every token to a number and stores them in a list in an instance attribute. This method overrides the null implementation in the parent class.

Parameters:

tokens – list of string tokens

values

List of the most recent values received.

ArduinoProtocol.py

ArduinoProtocol.py : abstract class to manage serial communication with an Arduino sketch.

Copyright (c) 2015-2017, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license.

class pneumatics_animation.ArduinoProtocol.ArduinoProtocol(port=None, verbose=False, debug=False, rate=115200, **kwargs)[source]

Abstract class to manage a serial connection to an Arduino sketch.

This class is sufficient to send and receive structured data messages as line-delimited text, but is more useful as a parent class for an application-specific class which manages the specific data types in the stream.

Parameters:
  • port – the name of the serial port device

  • verbose – flag to increase console output

  • debug – flag to print raw inputs on console

  • rate – serial baud rate

  • kwargs – collect any unused keyword arguments

close_serial_port()[source]

Shut down the serial connection to the Arduino, after which this object may no longer be used.

fileno()[source]

Return the file descriptor for the input port, useful for blocking on available input with select.

flush_serial_input()[source]

Clear the input buffer.

input

Input port object (usually same as output).

is_connected()[source]

Return true if the serial port device is open.

is_startup_delay_complete()[source]

Check whether the boot time interval has completed after opening the serial port.

message_received(tokens)[source]

Method called whenever a new message is received from the Arduino. The default implentation is null but this may be overridden in subclasses as a hook for this event.

Parameters:

tokens – list of strings representing whitespace-delimited tokens in received message

messages_received

Count of total lines received.

messages_sent

Count of total lines sent.

open_serial_port()[source]

Open the serial connection to the Arduino and initialize communications.

output

Output port object (usually same as input).

send_message(tokens)[source]

Issue a message to the Arduino sketch.

Parameters:

tokens – message as list of values

send_message_string(string)[source]

Issue a message string to the Arduino.

Parameters:

string – message as single string without terminating newline

set_serial_port_name(name)[source]

Set the name of the serial port device.

wait_for_message()[source]

Wait for a line of input from the Arduino and process it, updating status variables. This will block if data is not ready; if using in an event-driven system, it is important to check the port for available data prior to calling.