Reputation: 3
I am trying to create a thread that takes 3 variables (float) that are constantly updated (cX, cY, angle)
in a python
code and send it to an arduino uno every 30ms.
I never used threads before and as a beginner it is really hard for me to understand it.
The idea that someone gave me is to create a tuple of the three variables and create a thread in the main python flow that sends that tuple to the arduino (PC to arduino via serial).
Can anyone help me or at least show me how I could start.
Upvotes: 0
Views: 2277
Reputation: 8855
Threads are a way to run code concurrently. Meaning you can run function A, but before it finishes, you can start function B and they continue executing, not necessarily both at the same time (that's parallel programming). Think like at a given point in time, if you stop time and look at what's going on in your code, you'll see that function A has executed some statements yet not finished, but some statements of function B have also been executed.
In Python threading module, each thread is a function (or method) with some possible arguments, and when it starts the body of the function runs. When the function returns, the thread is done running, and the state of the thread changes to not alive
(see threading.Thread.is_alive()
).
So we create a thread by passing a function/method reference and possible arguments:
from threading import Thread
simple_thread = Thread(target=func_with_no_args)
thread_with_args = Thread(target=my_function, args=(arg1, arg2))
And then we start the thread, to run the body of the function.
simple_thread.start()
thread_with_args.start()
But current flow of code continues (the current thread, there is always the main thread in the program that starts executing it). So we don't need to wait for the function (func_with_no_args
or my_function
) to finish.
And if we need to check if the function we executed in a thread has finished, we can check if it's alive:
simple_thread.is_alive() # return bool
And if we want to wait until the function in the thread finished executing, we can join
the thread. It's also good to join
the threads when they're finished.
simple_thread.join()
This is mostly helpful for IO tasks, because while the program is waiting for data to be ready to/from IO it can do other stuff.
I am trying to create a thread that takes 3 variables (float) that are ...
I assume you want to achieve something and you're using threads as a tool. I mean creating the threads is not the goal in here, but a way to achieve what you want. Because the details of the application is missing from the question, I can't provide exact code/samples, so I'm going to go with an abstract/generic answer hoping that you can apply the idea to your application.
I think the application is receiving the data from somewhere (hence IO), and every 30ms the new data should be sent to ardunio (which again acts like an IO). So the application has 2 IO arms, one to receive the data, and another to send to ardunio. So using threads may justify.
We can have 2 functions, 1 to read data and 1 to update ardunio. We run them in 2 threads, thread_read
(to read data) and thread_ardunio
(to update ardunio).
If so we need a way to share information between threads. Threads make it easy to use memory (in process memory, accessible via variables), so we can use a variable accessible by both functions. When the variable is updated by 1 thread, the other thread will see the updated results as well.
storage = None
def read_data():
global storage
# read data from DB, web API, user input, etc.
# maybe in a loop to keep receiving data. Or generate the data from here
storage = (cX, cY, angle)
def send_to_ardunio():
global storage
# send storage data to ardunio, maybe in a loop
# sleeping 30ms after each update
thread_read = Thread(target=read_data)
thread_ardunio = Thread(target=send_to_ardunio)
thread_read.start()
thread_ardunio.start()
That's one way to go. IMHO not so pretty, as there is a global variable hanging out there. What can we do to eliminate the variable? We can use a queue (see queue.Queue
).
Queues are good ways to communicate between threads, I think. So a thread that has the data puts them on the queue (a.k.a producer), and the other thread picks items from the queue (a.k.a consumer).
Something like this:
def read_data(queue_):
# read data from DB, web API, user input, etc.
# maybe in a loop to keep receiving data, or generate the data from here
data = (cX, cY, angle)
queue_.put(data)
def send_to_ardunio(queue_):
# send data to ardunio, maybe in a loop
# sleeping 30ms after each update
data = queue_.get()
cX, cY, angle = data
queue_ = Queue() # this will be used to transfer data
thread_read = Thread(target=read_data, args=(queue_,))
thread_ardunio = Thread(target=send_to_ardunio, args=(queue_,))
thread_read.start()
thread_ardunio.start()
looks better.
Now we need to wait for the functions to run. So we can call the join
method on the threads. Also this time I took the liberty of assuming we can control how much time it'll take to read the data. If we're supposed to update the ardunio every 30ms with new data, then the producer can adjust the frequency and the consumer can just consume without any hesitation.
Also we need a way to tell the threads to stop producing/consuming.
We can use an Event
(see threading.Event
) for this, or for simplicity just by agreement a data on the queue will represent the consumer should stop.
def read_data(queue_):
while True:
# calculate/get cX, cY, angle
queue_.put((cX, cY, angle))
# sleep 30ms
# when we finished producing data, put something to the queue to tell the consumer there is no more data. I'll assume None is good option here
queue_.put(None)
def send_to_ardunio(queue_):
while True:
data = queue_.get()
if data is None:
break
cX, cY, angle = data
# update ardunio, because the data is updated every 30ms on the producer, we don't need to do anything special. We can just wait when the data is ready, we'll update.
queue_ = Queue()
thread_read = Thread(target=read_data, args=(queue_,))
thread_ardunio = Thread(target=send_to_ardunio, args=(queue_,))
thread_read.start()
thread_ardunio.start()
thread_read.join()
thread_ardunio.join()
The code above assumes that the producer (thread_read
) will know to stop producing the data.
If it's not the case then we can use an Event
to trigger both functions to stop producing and consuming.
In the end, I've experienced a small catch when joining threads though. If the main thread is joining other threads, it's blocked and won't respond well to SIGINT. So if you try to stop the Python process (by pressing Ctrl+C or sending SIGINT) it won't quit.
We can however try to join the threads with a timeout. So every often the main thread can take a look at received signal and handle them.
def read_data(queue_, should_stop):
while not should_stop.is_set():
# calculate/get cX, cY, angle
queue_.put((cX, cY, angle))
# sleep 30ms
def send_to_ardunio(queue_, should_stop):
while not should_stop.is_set():
data = queue_.get()
cX, cY, angle = data
# update ardunio
def tell_when_to_stop(should_stop):
# detect when to stop, and set the Event. For example, we'll wait for 10 seconds and then ask all to stop
time.sleep(10)
should_stop.set()
queue_ = Queue()
should_stop = Event()
thread_stop_decider = Thread(target=tell_when_to_stop, args=(should_stop,))
thread_read = Thread(target=read_data, args=(queue_, should_stop))
thread_ardunio = Thread(target=send_to_ardunio, args=(queue_, should_stop))
thread_read.start()
thread_ardunio.start()
thread_stop_decider.start()
try:
while thread_read.is_alive():
thread_read.join(1)
except KeyboardInterrupt:
should_stop.set()
thread_read.join()
thread_ardunio.join()
thread_stop_decider.join()
Upvotes: 4