1

I am trying to control a 3-axis printer using an x-box controller. To get inputs from the x-box I have borrowed code from martinohanlon https://github.com/martinohanlon/XboxController/blob/master/XboxController.py I have also created code that reads a text file line by line (G-code) to move the printer.

I would like to be able to use the X-Box controller to select a G-code file and run it, then as the printer is running continue to listen for a cancel button just in case the print goes wrong. The controller is a threaded class, and my readGcode is a threaded class.

The problem I'm having is that when I use the controller to start the readGcode class I cant communicate with the controller until that thread finished.

My temporary solution is to use the controller to select a file then pass that files path to the readGcode class. In the readGcode class it keeps trying to open a file using a try block and fails until the filepath is acceptable. Then it changes a bool which makes it skip further reading until its done.

Code:

import V2_Controller as Controller
import V2_ReadFile as Read
import time
import sys
# file daialogue
import tkinter as tk
from tkinter import filedialog

# when X is selected on the x-box controller 
def X(xValue):
    if not bool(xValue):
        try:
            f=selectfile()
            rf.setfilename(f)
        except:
            print("failed to select file")

def selectfile():
    try:
        root = tk.Tk()  # opens tkinter
        root.withdraw()  # closes the tkinter window
        return filedialog.askopenfilename()
    except Exception:
        print("no file")

# setup xbox controller
xboxCont = Controller.XboxController(controlCallBack, deadzone=30, 
scale=100, invertYAxis=True)
# init the readfile class
rf = Read.Readfile()

# set the custom function for pressing X
xboxCont.setupControlCallback(xboxCont.XboxControls.X, X)

try:
    # start the controller and readfile threads
    xboxCont.start()
    rf.start()

    xboxCont.join()
    rf.join()

    while True:
        time.sleep(1)

# Ctrl C
except KeyboardInterrupt:
    print("User cancelled")

# error
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

finally:
    # stop the controller
    xboxCont.stop()
    rf.stop()

V2_Readfile

# Main class for reading the script
class Readfile(threading.Thread):

    # supports all variables needed to read a script
    class readfile:
        fileselect = True
        linecount = 0
        currentline = 0
        commands = []

    # setup readfile class
    def __init__(self):
        # setup threading
        threading.Thread.__init__(self)
        # persist values
        self.running = False
        self.reading = False

    def setfilename(self,filename):
        self.filename = filename

    # called by the thread
    def run(self):
        self._start()

    # start reading
    def _start(self):
        self.running = True
        while self.running:
            time.sleep(1)
            if not self.reading:
                try:
                    self.startread()
                except:
                    pass

    def startread(self):
        try:
            with open(self.filename, "r") as f:  # read a local file
                f1 = f.readlines()
                # run through each line and extract the command from each line
                linecount = 0
                line = []
                for x in f1:
                    # read each line into an array
                    line.append(x.split(";")[0])
                    linecount += 1

                # Store the variables for later use
                self.readfile.linecount = linecount
                self.readfile.commands = line
                self.reading = True
        except Exception:
            pass

        i = 0
        while i < self.readfile.linecount and self.reading:
            self.readfile.currentline = i + 1
            self.readline(i)
            i += 1

        # the following stops the code from reading again
        self.reading = False
        self.filename = ""


    def readline(self,line):
        Sort.sortline(self.readfile.commands[line])

    # stops the controller
    def stop(self):
        self.running = False

1 Answers1

0

You could use a syncronization primitive like threading.Event.

To do so, you need to modify your Readfile class like this:

from threading import Event


class Readfile(threading.Thread):

    # setup readfile class
    def __init__(self):
        # setup threading
        threading.Thread.__init__(self)
        # persist values
        self.running = False
        self.reading = False
        self.reading_file = Event()  # Initialize your event here it here

    def setfilename(self,filename):
        self.filename = filename
        self.reading_file.set()  # This awakens the reader

    def _start(self):
        self.running = True
        self.reading_file.wait()  # Thread will be stopped until readfilename is called
        self.startread()

Another syncronization primitive worth exploring is queue.Queue. It could be useful if you want to process more than one filename.

The pattern you describe in your question is called Busy Waiting, and should be avoided when possible.

Marco Lavagnino
  • 1,140
  • 12
  • 31
  • So awesome! it works perfectly, and I understand it. Thank you for helping a newbie. – Alex Mcghee Oct 02 '18 at 00:15
  • (tried to upvote but I'm new). So the idea works because this thread is started and immediately waits. Then when I hit a button it switches the wait off by using .set(). I added a couple lines at the end of the startread() function to clear the event and reload the wait for another script self.reading_file.clear() self._start() – Alex Mcghee Oct 02 '18 at 00:29
  • @AlexMcghee I'm glad it worked! while you can't upvote my answer yet, you can accept my answer (which will award me roughly the same amount of points). To do so, you can click the check mark under the vote buttons. Check [this resource](https://stackoverflow.com/help/someone-answers) about accepting answers. – Marco Lavagnino Oct 02 '18 at 14:15