12.1. OneInOneOutConsole Example

This module provides a basic template for a command-line Python script to communicate with an Arduino running the OneInOneOutASCII 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 definition of client-server or peer-to-peer relationships depend on the specific system; the Python program could include high-level services for an Arduino program, or be considered a primary application using an Arduino for I/O and hardware control.

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

12.1.1. peering_example.py

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/env python
"""\
peering_example.py : example command-line program to communicate with an Arduino over a serial port and execute timed logic.

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

# This script is compatible with Python 2.7 or 3.5.
from __future__ import print_function, absolute_import, unicode_literals
import os, sys, time, argparse, select

# Quick fix for keeping this script in the same folder as the package it
# imports.  Please note that the import will need to change if the folder is
# renamed.
sys.path.insert(0, "..")
from OneInOneOutConsole.OneInOneOutProtocol import OneInOneOutProtocol
                      
################################################################
def event_loop(arduinos):
    """Prototype for an event-processing loop which manages I/O on multiple serial ports.

    :param arduinos: list of Arduino protocol objects
    """

    polling_interval = 0.5
    polling_timer = polling_interval
    last_time = time.time()
    
    while True:
        # Check which Arduinos have data available, using select with a short timeout.
        ready = select.select(arduinos,[], [], max(polling_timer, 0.0))

        # Process messages on each Arduino port with available data. 
        for arduino in ready[0]: arduino.wait_for_message()
          
        # Check whether enough time has elapsed to run a polling cycle, and recalculate the next timeout.
        now = time.time()
        polling_timer -= (now - last_time) # subtract elapsed time
        last_time = now
        
        # When the polling interval has expired:
        if polling_timer <= 0:
            polling_timer += polling_interval

            # Apply any polled logic here; this will be called at a constant average rate.
            for arduino in arduinos:
                print("arduino status: clock: %d, analog: %s, digital: %s" % (arduino.arduino_time, arduino.analog_inputs, arduino.digital_inputs))

                # send a message to update the LED state based on the time
                led = int(now) % 2
                arduino.send_message(['led', led])

################################################################
# The following section is run when this is loaded as a script.
if __name__ == "__main__":

    # Initialize the command parser.
    parser = argparse.ArgumentParser( description = """Example command line program to interface with an Arduino sketch.""")
    parser.add_argument( '-v', '--verbose', action='store_true', help='Enable more detailed output.' )
    parser.add_argument( '--debug', action='store_true', help='Enable debugging output.' )
    parser.add_argument( '-p', '--port', default='/dev/tty.usbmodem1411', help='Specify the name of the Arduino serial port device (default is /dev/tty.usbmodem1411).')

    # Parse the command line, returning a Namespace.
    args = parser.parse_args()

    # Extract specific command line options into a keyword argument dictionary.
    kwargs = { 'port' : args.port, 'debug' : args.debug, 'verbose' : args.verbose }
    
    # Create a protocol object to manage the serial port.
    arduino = OneInOneOutProtocol(**kwargs)

    # Connect to the Arduino, which will also reset it.
    print("Connecting to Arduino.")
    arduino.open_serial_port()
    
    # Now run the event loop until it exits or control-C is pressed.
    try:
        event_loop([arduino])
            
    except KeyboardInterrupt:
        print("User interrupted operation.")

    # Shut down the connection.
    arduino.close_serial_port()
        

12.1.2. OneInOneOutProtocol.py

OneInOneOutProtocol.py : application-specific class to manage serial communication with an Arduino OneInOneOutASCII sketch.

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

class OneInOneOutConsole.OneInOneOutProtocol.OneInOneOutProtocol(**kwargs)[source]

Example class to communicate with the OneInOneOutASCII Arduino sketch.

analog_inputs = None

Dictionary of most recent analog inputs. Elements are channel:value.

arduino_time = None

The most recent Arduino microsecond clock value (integer).

digital_inputs = None

Dictionary of most recent digital inputs. Elements are pin:value.

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

12.1.3. 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 OneInOneOutConsole.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 = None

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 = None

Count of total lines received.

messages_sent = None

Count of total lines sent.

open_serial_port()[source]

Open the serial connection to the Arduino and initialize communications.

output = None

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.