Python Forum
Whats wrong with my thread? - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: General Coding Help (https://python-forum.io/forum-8.html)
+--- Thread: Whats wrong with my thread? (/thread-4227.html)



Whats wrong with my thread? - tony1812 - Aug-01-2017

Hello. I just started learning Python
I wrote this code. The idea is to let the main thread to hadle the UI tasks and leave the relay control to a new thread. The relay should be on x seconds and off y sends. User can set to on/off duration wiyj the sliders. How ever the sliders never up date During runtime. I susppect I didn't do it right. Please advice. Thanks.

import serial, time, _thread #random
from tkinter import *
from time import sleep

#Prepare GPIO
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False) #disable annoying warning messages
GPIO.setup(40,GPIO.OUT)
#initially is on
GPIO.output(40,GPIO.HIGH)

g_on_slider_val = 0
g_off_slider_val = 0



# Define a function for the thread 
def relay_control( threadName, delay):
    while True:
        #on_time = float(g_on_slider_val) #g_slder_val is a global var
        #off_time = float(g_off_slider_val)
        
        relay_on()
        time.sleep(g_on_slider_val)
        relay_off()
        time.sleep(g_off_slider_val)
        ui.update_idletasks()
        ui.update()
      
# Define main thread functions
def read_data():
    #data = "Random number {}".format(random.randint(1, 99)) #dummy data
    data = ser.readline()
    #time.sleep(1)
    return data
    
def relay_on():
	GPIO.output(40,GPIO.HIGH)
	
def relay_off():
	GPIO.output(40,GPIO.LOW)

def update_value(string_var, ui_window):
    data = read_data()
    string_var.set(data)
    ui_window.after(1000, update_value, string_var, ui_window)
    
#Slider function
def get_on_slider_value(on_val):
	g_on_slider_val = on_val
	print (g_on_slider_val) 

def get_off_slider_value(off_val):
	g_off_slider_val = off_val
	print (g_off_slider_val) 
    		
ui = Tk()
ui.geometry("800x400+0+0")
ui.title("Command Center")
ser = serial.Serial('/dev/ttyUSB0', 9600)
var = StringVar()
var.set('Gather Sensor data.')

#slider	
on_slider = Scale(ui, orient = HORIZONTAL, length = 300, width = 10, sliderlength = 60, from_ = 0, to = 100, command = get_on_slider_value)
on_slider.place(x=250, y=100)

off_slider = Scale(ui, orient = HORIZONTAL, length = 300, width = 10, sliderlength = 60, from_ = 0, to = 100, command = get_off_slider_value)
off_slider.place(x=250, y=140)

data_label = Label(ui, textvariable = var)
data_label.place(x=300, y=300)

ui.after(1000, update_value, var, ui)

# Create a threads 
_thread.start_new_thread( relay_control, ("Thread-1", 2, ) )



ui.mainloop()
#END

    



RE: Whats wrong with my thread? - Larz60+ - Aug-01-2017

Once the thread is started, it won't communicate with the main process without coaxing.
You need to provide communications between the two processes
Watch this video: http://pyvideo.org/pycon-us-2015/python-concurrency-from-the-ground-up-live.html


RE: Whats wrong with my thread? - tony1812 - Aug-01-2017

Sorry for a dumb newbie question, In the above code how to share the slider number in Python during runtime. Can you be more specific? Thanks.


RE: Whats wrong with my thread? - Larz60+ - Aug-01-2017

It's not simple to explain, but basically you can use a pipe as in os.pipe: https://docs.python.org/3/library/os.html#os.pipe
or a semiphore: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Semaphore
or for messaging, a queue: https://docs.python.org/3/library/multiprocessing.html#pipes-and-queues
Doug Hellman (The python standard Library by example) does an excellent tutorial here: https://pymotw.com/3/multiprocessing/communication.html

But I think you would like David Beazley's video, it's quite enjoyable to watch.


RE: Whats wrong with my thread? - DeaD_EyE - Aug-01-2017

A minimal example with a class without any queues, using locks instead.
With queues is a better approach, but I'm to tired to wrap my mind around queues.

import time
import threading

class Foo:
    def __init__(self, on_duration, off_duration):
        self._on_duration = on_duration
        self._off_duration = off_duration
        self.lock = threading.Lock()
        
    @property
    def off_duration(self):
        return self._off_duration
    
    @off_duration.setter
    def off_duration(self, value):
        with self.lock: # BLOCKING
            # in this block no other thread
            # is allowed to aquire the lock
            self._off_duration = value
    @property
    def on_duration(self):
        return self._on_duration
    
    @on_duration.setter
    def on_duration(self, value):
        with self.lock: # BLOCKING
            self._on_duration = value
            # in this block no other thread
            # is allowed to aquire the lock
            
    def start(self):
        thread = threading.Thread(target=self.run)
        thread.setDaemon(True)
        thread.start()
        
    def run(self):
        while True:
            with self.lock: # BLOCKING
                # here we assign the two values from
                # self.on_duration and self.off_duration
                # during this time, the other lock will
                # block, also the other assigments, which
                # are realized with @porperty.setter
                # will block this lock here.
                # this will prevent race conditions
                on_duration = self.on_duration
                off_duration = self.off_duration
                # during this time all other locks with
                # using the same lock object have to wait
                # a assigment is very short, so this
                # will not affect your gui
            # outside the lock block
            # here we use the new variables, we have
            # assigned before
            # no blocking here
            # and the sleep function won't block
            # because it's outside of the lock
            print('On', on_duration)
            time.sleep(on_duration)
            print('Off', off_duration)
            time.sleep(on_duration)

foo = Foo(5, 5)
foo.start()
time.sleep(10)
foo.on_duration = 2
foo.off_duration = 2
time.sleep(10)
# program ends here, main thread is killd
# because there are no non-daemon threads left



RE: Whats wrong with my thread? - tony1812 - Aug-02-2017

Oh this is the code I have atfer incooperated yor class

import serial, time, threading #random
from tkinter import *
from time import sleep

#Prepare GPIO
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False) #disable annoying warning messages
GPIO.setup(40,GPIO.OUT)
GPIO.setup(38,GPIO.OUT)
#initially is off
GPIO.output(40,GPIO.LOW)
GPIO.output(38,GPIO.LOW)

# create a Relay_1 class
class Relay_1:
    def __init__(self, on_duration, off_duration):
        self._on_duration = on_duration
        self._off_duration = off_duration
        self.lock = threading.Lock()
         
    @property
    def off_duration(self):
        return self._off_duration
     
    @off_duration.setter
    def off_duration(self, value):
        with self.lock: # BLOCKING
            # in this block no other thread
            # is allowed to aquire the lock
            self._off_duration = value
    @property
    def on_duration(self):
        return self._on_duration
     
    @on_duration.setter
    def on_duration(self, value):
        with self.lock: # BLOCKING
            self._on_duration = value
            # in this block no other thread
            # is allowed to aquire the lock
             
    def start(self):
        thread = threading.Thread(target=self.run)
        thread.setDaemon(True)
        thread.start()
         
    def run(self):
        while True:
            with self.lock: # BLOCKING
                on_duration = self.on_duration
                off_duration = self.off_duration
            r1_relay_on()
            #print('On', on_duration)
            time.sleep(on_duration)
            #print('Off', off_duration)
            r1_relay_off()
            time.sleep(off_duration)
# End Relay_1 class

# create a Relay_2 class
class Relay_2:
    def __init__(self, on_duration, off_duration):
        self._on_duration = on_duration
        self._off_duration = off_duration
        self.lock = threading.Lock()
         
    @property
    def off_duration(self):
        return self._off_duration
     
    @off_duration.setter
    def off_duration(self, value):
        with self.lock: # BLOCKING
            # in this block no other thread
            # is allowed to aquire the lock
            self._off_duration = value
    @property
    def on_duration(self):
        return self._on_duration
     
    @on_duration.setter
    def on_duration(self, value):
        with self.lock: # BLOCKING
            self._on_duration = value
            # in this block no other thread
            # is allowed to aquire the lock
             
    def start(self):
        thread = threading.Thread(target=self.run)
        thread.setDaemon(True)
        thread.start()
         
    def run(self):
        while True:
            with self.lock: # BLOCKING
                on_duration = self.on_duration
                off_duration = self.off_duration
            r2_relay_on()
            #print('On', on_duration)
            time.sleep(on_duration)
            #print('Off', off_duration)
            r2_relay_off()
            time.sleep(off_duration)
# End Relay_2 class


def read_data():
    #data = "Random number {}".format(random.randint(1, 99)) #Test dummy data
    data = ser.readline()
    #time.sleep(1)
    return data
    
def r1_relay_on():
	GPIO.output(40,GPIO.HIGH)
	
def r1_relay_off():
	GPIO.output(40,GPIO.LOW)

def r2_relay_on():
	GPIO.output(38,GPIO.HIGH)
	
def r2_relay_off():
	GPIO.output(38,GPIO.LOW)
	

def update_value(string_var, ui_window):
    data = read_data()
    string_var.set(data)
    ui_window.after(1000, update_value, string_var, ui_window)
    
#Slider function
'''
def get_on_slider_value(val):
    #g_on_duration = val
    print ("")#g_on_duration)

def get_off_slider_value(val):
    #g_off_duration = val
    print ("")#g_off_duration)
'''
    		
ui = Tk()
ui.geometry("800x400+0+0")
ui.title("Command Center")
ser = serial.Serial('/dev/ttyUSB0', 9600)
var = StringVar()
var.set('Gather Sensor data.')

#slider	
on_slider = Scale(ui, orient = HORIZONTAL, length = 300, width = 10)#, sliderlength = 60, from_ = 0, to = 100, command = get_on_slider_value)
on_slider.place(x=250, y=100)

off_slider = Scale(ui, orient = HORIZONTAL, length = 300, width = 10)#, sliderlength = 60, from_ = 0, to = 100, command = get_off_slider_value)
off_slider.place(x=250, y=140)

data_label = Label(ui, textvariable = var)
data_label.place(x=300, y=300)

r1 = Relay_1(1, 1)
r1.start()
r1.on_duration = 4 #float(g_on_duration)
r1.off_duration = 4 #float(g_off_duration)

r2 = Relay_2(1, 1)
r2.start()
r2.on_duration = 2 #float(g_on_duration)
r2.off_duration = 2 #float(g_off_duration)



ui.after(500, update_value, var, ui)




ui.mainloop()
#END



RE: Whats wrong with my thread? - DeaD_EyE - Aug-03-2017

I think my suggestion maybe tends into a wrong direction.

What do you want? Change the sliders and the thread should acquire the new values? No button to submit the changes?

The thread itself cat get the values out of the slider objects with variables. Also a Button with a command can submit the changes to the thread.


RE: Whats wrong with my thread? - tony1812 - Aug-04-2017

Thank you so muuh for taking the time to hejp a dumb newbie like me. :) Your code actually works very well. does actulaay what I intend to do, it was a very good exercise for a python starter.