3.5. CncShieldGUI Example

This module provides a graphical interface to an Arduino running the CNCShieldServer sketch. It is built on top of the ArduinoGUI module.

3.5.2. CncShieldGUI.py

CncShieldGUI.py

GUI controller for the CNC_Shield_Server Arduino sketch.

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

class CncShieldGUI.CncShieldGUI.CncShieldGUIController(client, port=None, **kwargs)[source]

Application-specific control object to manage the GUI for the CNC_Shield_Server. This class creates a generic ArduinoConsole GUI, adds application-specific GUI controls, and manages basic I/O using a protocol object.

Parameters:
  • client – a protocol object to receive commands from the GUI
  • port – the name of the serial port device
  • kwargs – collect any unused keyword arguments
disable_button_pressed()[source]

Callback function activated to disable the motor drivers when the GUI button is pressed.

enable_button_pressed()[source]

Callback function activated to enable the motor drivers when the GUI button is pressed.

home_button_pressed()[source]

Callback function activated to move to the origin when the GUI button is pressed.

port_data_ready(fd)[source]

Callback function activated when data is received from the Arduino. This just prints a message, but would usually process status messages as part of a command protocol.

start_button_pressed()[source]

Callback function activated to start a user script.

stop_button_pressed()[source]

Callback function activated to interrupt a user script.

x_slider_moved(value)[source]

Callback function activated when the X slider is moved.

y_slider_moved(value)[source]

Callback function activated when the Y slider is moved.

z_slider_moved(value)[source]

Callback function activated when the Z slider is moved.

3.5.3. CncShieldProtocol.py

CncShieldProtocol.py : communications logic to control an Arduino running CNC_Shield_Server

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

class CncShieldGUI.CncShieldProtocol.CncShieldClient(port=None, verbose=False, debug=False, **kwargs)[source]

Class to manage a connection to a CNC_Shield_Server running on a serial-connected Arduino.

Parameters:
  • port – the name of the serial port device
  • verbose – flag to increase console output
  • debug – flag to print raw inputs on sconsole
  • 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.

distance_to_target()[source]

Return the estimated total number of microsteps remaining to reach the target position, based on the most recently received position status.

flush_serial_input()[source]

Clear the input buffer.

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.

issue_move(position)[source]

Issue a command to move to a [x, y, z] absolute position (specified in microsteps). Returns immediately.

Parameters:position – a list or tuple with at least three elements
motor_enable(value=True)[source]

Issue a command to enable or disable the stepper motor drivers.

open_serial_port()[source]

Open the serial connection to the Arduino and initialize communciations.

position_updated()[source]

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

send_command(string)[source]

Issue a command string to the Arduino.

set_serial_port_name(name)[source]

Set the name of the serial port device.

wait_for_input()[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.

wait_for_position_update()[source]

Block until a position status message has been received.

3.5.4. run_gui.py

This script launches the GUI. It also includes a small custom subclass of the protocol to add scripting behaviors. This demonstrates layering in a state machine into the GUI event system.

  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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python

"""\
run_gui.py

Sample script to create and operate a graphical user interface for an Arduino
program.  This interface shell communicates over a serial port to an Arduino
running a sketch which includes line-oriented text input and output.

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

from __future__ import print_function
import os, sys, argparse

# This requires the PyQt4 module to interface to the Qt GUI toolkit.  For
# documentation on the PyQt4 API, see
# http://pyqt.sourceforge.net/Docs/PyQt4/index.html
from PyQt4 import QtGui

# Make sure that the Python libraries also contained within this course package
# are on the load path.  This adds the parent folder to the load path, assuming that this
# script is still located within a subfolder of the Python library tree.
sys.path.insert(0, os.path.abspath(".."))

from CncShieldGUI.CncShieldGUI      import CncShieldGUIController
from CncShieldGUI.CncShieldProtocol import CncShieldClient

################################################################
class ScriptClient(CncShieldClient):
    """A custom subclass of CncShieldClient to implement the Arduino control
    protocol with additional methods to support scripted movement.
    """

    def __init__(self, *args, **kwargs):
        # initialize the parent class
        CncShieldClient.__init__(self, *args, **kwargs)

        # define a simple pose animation system which moves in a sequence from target to target
        self.next_pose = None
        self.pose_sequence = [ [1000, 2000, 3000],
                               [0, 0, 0],
                               [1000, 2000, 3000],
                               [0, 0, 0],
                               [3000, 2000, 1000],
                               [0, 0, 0],
                               [3000, 2000, 1000],
                               [0, 0, 0] ]
        return

    # additional methods specialized to this class
    def start_script(self):
        print("Restarting script state machine.")
        self.next_pose = 0

        # Issue a move command to stop at the most recent position in case the
        # target is currently set far away.
        self.issue_move( self.position )

        return

    def stop_script(self):
        print("Stopping script state machine.")
        self.next_pose = None             # stop sequencer
        self.issue_move( self.position )  # stop motors here
        return

    def position_updated(self):
        """Receive notification calls when new data is received from the Arduino."""

        # wait until the most recent target was reached
        if self.distance_to_target() == 0:

            # if sequencer is running
            if self.next_pose is not None:

                # issue another pose if available
                if self.next_pose < len(self.pose_sequence):
                    print("Issuing pose %d in sequence." % self.next_pose)
                    self.issue_move(self.pose_sequence[self.next_pose])
                    self.next_pose += 1

################################################################
# Main script follows.  This sequence is executed when the script is initiated from the command line.
if __name__ == "__main__":
    # process command line arguments
    parser = argparse.ArgumentParser(description = """Run CncShieldGUI.""")
    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).')
    args = parser.parse_args()

    # Convert the command line arguments from a Namespace to a set of keyword
    # arguments and initialize the client protocol controller.  Note that this
    # will ignore extraneous values, e.g., from additional command argument
    # inputs unneeded by this object.
    keyword_args = vars(args)
    client = ScriptClient(**keyword_args)

    # initialize the Qt system itself
    app = QtGui.QApplication(sys.argv)

    # Create the GUI controller and connect it to the protocol controller.  It
    # will create a generic console window and extend the interface window
    # controls to customize it for this particular application.
    gui = CncShieldGUIController( client, **keyword_args )

    print("""Starting up CncShieldGUI.
Toggle the 'Connected' switch to open the Arduino serial port.
Click 'Enable' while connected to enable motor drivers.
Click 'Start' to begin the script sequence.
""")
    
    # run the event loop until the user is done
    sys.exit(app.exec_())

3.5.5. movement_test.py

This is a console-only script (no GUI) to demonstrate scripted motion using the same underlying protocol class.

 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
86
87
88
#!/usr/bin/env python

"""\
movement_test.py

Test the protocol for the CNC_Shield_Server without a GUI by moving through a sequence of positions.

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

from __future__ import print_function
import os, sys, time, argparse

# Make sure that the Python libraries also contained within this course package
# are on the load path.  This adds the parent folder to the load path, assuming that this
# script is still located within a subfolder of the Python library tree.
sys.path.insert(0, os.path.abspath(".."))

from CncShieldGUI.CncShieldProtocol import CncShieldClient

################################################################
def move_to( client, position ):

    """Issue a command to move to a [x, y, z] absolute position (specified in
    microsteps).  Waits until completion.
    
    :param position: a list or tuple with at least three elements
    """
    
    client.issue_move(position)
    while True:
        client.wait_for_position_update()
        # wait until all motors have reached their targets
        if client.distance_to_target() == 0:
            return

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

    # Initialize the command parser.
    parser = argparse.ArgumentParser( description = """Simple test client to send data to the CNC_Shield_Server on an Arduino.""")
    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()

    # Convert the Namespace to a set of keyword arguments and initialize the
    # client object.  Note that it will ignore extraneous values, e.g., from
    # additional command argument inputs unneeded by this object.
    client = CncShieldClient(**vars(args))

    print("Connecting to Arduino.")
    client.open_serial_port()

    print("Sleeping briefly while Arduino boots...")
    while client.is_startup_delay_complete() is False:
        time.sleep(0.5)

    # throw away any extraneous input
    print("Flushing Arduino input...")
    client.port.flushInput()
    
    # Begin the motion sequence.  This may be safely interrupted by the user pressing Control-C.
    try:
        print("Beginning movement sequence.")
        client.motor_enable()
        move_to(client, [1000, 2000, 3000])
        move_to(client, [0, 0, 0])
        move_to(client, [1000, 2000, 3000])
        move_to(client, [0, 0, 0])
        move_to(client, [3000, 2000, 1000])
        move_to(client, [0, 0, 0])
        move_to(client, [3000, 2000, 1000])
        move_to(client, [0, 0, 0])

    except KeyboardInterrupt:
        print("User interrupted motion.")

    # Issue a command to turn off the drivers, then shut down the connection.
    client.motor_enable(False)
    client.close_serial_port()