1

I have a program that is supposed to send a few data points over a serial connection to an arduino which will control some motors to move. I can send the control signals individually as well as by txt file which will run repeatedly until the file is complete. While running a txt file, I want to be able to exit the loop like a pause or stop button. I think the best way to do that is via a thread that I can close. I have never done any threading before and my rudimentary attempts have not worked. Here is the function that sends the file data.

def send_file():
    # Global vars
    global moto1pos
    global motor2pos
    # Set Ready value
    global isready
    # Get File location
    program_file_name = file_list.get('active')
    file_path = "/home/evan/Documents/bar_text_files/"
    program_file = Path(file_path + program_file_name)
    file = open(program_file)
    pos1 = []
    pos2 = []
    speed1 = []
    speed2 = []
    accel1 = []
    accel2 = []
    for each in file:
        vals = each.split()
        pos1.append(int(vals[0]))
        pos2.append(int(vals[1]))
        speed1.append(int(vals[2]))
        speed2.append(int(vals[3]))
        accel1.append(int(vals[4]))
        accel2.append(int(vals[5]))
    # Send file values
    try:
        while isready == 1:
            for i in range(len(pos1)):
                print("Step: " + str(i+1))
                data = struct.pack("!llhhhh", pos1[i], pos2[i], speed1[i], speed2[i], accel1[i], accel2[i])
                ser.write(data)

                try:
                    pos1time = abs(pos1[i]/speed1[i])
                except:
                    pos1time = 0
                try:
                    pos2time = abs(pos2[i]/speed2[i])
                except:
                    pos2time = 0
            time_array = (pos1time, pos2time)
            time.sleep(max(time_array))
            motor1pos = ser.readline()
            motor2pos = ser.readline()
            if i < (len(pos1)-1):
                isready = ord(ser.read(1))
            else:
                isready = 0
except:
    print("Error: data not sent. Check serial port is open")

Here is the threading command which I want the sendfile command to work from.

def thread():
    try:
        global isready
        isready = 1
        t = threading.Thread(name='sending_data', target=command)
        t.start()
    except:
        print("Threading Error: you don't know what you are doing")

And here is the stop function I want the thread to be killed by:

def stop():
    try:
        global isready
        isready = 0
        t.kill()
    except:
        print("Error: thread wasn't killed")

I know you aren't supposed to kill a thread but the data isn't very important. Whats more important is to stop the motors before something breaks.

The button in tkinter is:

run_file_butt = tk.Button(master = file_frame, text = "Run File", command = thread)

When I click the button, the program runs but the stop function does nothing to stop the motion.

evan
  • 169
  • 3
  • 12
  • Read [Why is Button parameter “command” executed when declared?](https://stackoverflow.com/questions/5767228/why-is-button-parameter-command-executed-when-declared) and [A: tkinter: RuntimeError: threads can only be started once](https://stackoverflow.com/questions/54395358/tkinter-runtimeerror-threads-can-only-be-started-once/54405046?r=SearchResults&s=1|40.2089#54405046) and [A: Python close a thread on multithreading](https://stackoverflow.com/questions/43683257/python-close-a-thread-on-multithreading/43686996?r=SearchResults&s=2|36.7817#43686996) – stovfl Aug 27 '19 at 14:53
  • Okay so I get that I cannot pass a command with arguments and when I remove the argument and make the commands run, nothing happens. It doesn't send the file and it doesn't give an error for at least the 30 seconds I have waited on it before manually closing the gui. – evan Aug 27 '19 at 15:04
  • You don't define the variable `t` in the definition of your `stop()` function. – jjramsey Aug 27 '19 at 16:18
  • Re, "I think the best way to do that is via a thread." I would not. Assuming you are talking about some kind of a GUI application running on a desktop PC or on a mobile device, then I would use a _[Timer](https://stackoverflow.com/q/2400262/801894)_ to periodically send whatever needs to be sent. – Solomon Slow Aug 27 '19 at 17:54

2 Answers2

0

Question: run and kill a thread on a button press

There is no such a thing called .kill(....
Start making your def send_file(... a Thread object which is waiting your commands.

Note: As it stands, your inner while isready == 1: will not stop by using m.set_state('stop').
It's mandatory to start the Thread object inside:

if __name__ == '__main__':
    m = MotorControl()
import threading, time

class MotorControl(threading.Thread):
    def __init__(self):
        super().__init__()
        self.state = {'is_alive'}
        self.start()

    def set_state(self, state):
        if state == 'stop':
            state = 'idle'

        self.state.add(state)

    def terminate(self):
        self.state = {}

    # main function in a Thread object
    def run(self):
        # Here goes your initalisation
        # ...

        while 'is_alive' in self.state:

            if 'start' in self.state:
                isready = 1
                while isready == 1:
                    # Here goes your activity
                    # Simulate activity
                    print('running')
                    time.sleep(2)
                    isready = 0

                self.state = self.state - {'start'}
                self.state.add('idle')

            elif 'idle' in self.state:
                print('idle')
                time.sleep(1)

if __name__ == '__main__':
    m = MotorControl()

    time.sleep(2)
    m.set_state('start')
    time.sleep(3)
    m.set_state('stop')
    time.sleep(3)
    m.set_state('start')

    time.sleep(4)
    m.terminate()
    print('EXIT __main__')

Your tk.Button should look like:

tk.Button(text = "Run File", command = lambda:m.set_state('start'))
tk.Button(text = "Stop File", command = lambda:m.set_state('stop'))
tk.Button(text = "Terminate", command = m.terminate)
stovfl
  • 14,998
  • 7
  • 24
  • 51
0

The answer I have gone with is simple due to my simple understanding of threading and unique circumstances with which I am using the threading. Instead of terminating the thread in a way I was hoping, I added another conditional statement to the sending line of the send_file function.

    while isready == 1:
        for i in range(len(pos1)):
            if motorstop == False:
                print("Step: " + str(i+1))
                #data = struct.pack('!llllhhhhhhhh', pos1[i], pos2[i], pos3[i], pos4[i], speed1[i], speed2[i], speed3[i], speed[4], accel1[i], accel2[i], accel3[i], accel4[i])
                data = struct.pack("!llhhhh", pos1[i], pos2[i], speed1[i], speed2[i], accel1[i], accel2[i])
                ser.write(data)
            else:
                isready = 0
                break

and I have updated my stop() func to the following:

def stop():
    try:
        global motorstop
        global t
        motorstop = True
        t.join()
    except:
        print("Error: thread wasn't killed")

I'm not exactly sure how it works but it is much simpler than what was mentioned by @stovefl.

With this code, since the function is mostly just sleeping, it can run but it won't send any new information and then will .join() after the next iteration.

evan
  • 169
  • 3
  • 12