2016-10-31 5 views
0

Я пытаюсь создать поток, который принимает 3 переменные (float), которые постоянно обновляются (cX, cY, angle) в коде python и отправляют его на arduino uno каждые 30 мс.Python threading и связь Arduino

Я никогда не использовал нити раньше и, как новичок, мне очень трудно понять это.

Идея, что кто-то дал мне, - создать кортеж из трех переменных и создать поток в основном потоке питона, который отправляет этот кортеж в arduino (от ПК до arduino через последовательный порт).

Может ли кто-нибудь помочь мне или хотя бы показать мне, как я могу начать.

ответ

1

Темы - это способ запуска кода одновременно. Это означает, что вы можете запустить функцию A, но до ее завершения вы можете запустить функцию B, и они продолжают выполняться, не обязательно оба одновременно (это параллельное программирование). Подумайте, как в данный момент времени, если вы остановите время и посмотрите, что происходит в вашем коде, вы увидите, что функция A выполнила некоторые инструкции, которые еще не закончены, но некоторые операторы функции B также были выполнены.

В потоковом модуле Python каждый поток является функцией (или методом) с некоторыми возможными аргументами и при запуске тела функции запускается. Когда функция возвращается, поток выполняется, а состояние потока изменяется на not alive (см. threading.Thread.is_alive()).

Таким образом, мы создаем поток, передавая функцию/метод ссылки и возможные аргументы:

from threading import Thread 

simple_thread = Thread(target=func_with_no_args) 
thread_with_args = Thread(target=my_function, args=(arg1, arg2)) 

И тогда мы начинаем нить, чтобы запустить тело функции.

simple_thread.start() 
thread_with_args.start() 

Но ток коды продолжается (текущий поток, всегда есть основной поток в программе, которая начинает выполнять его). Поэтому нам не нужно ждать функции (func_with_no_args или my_function), чтобы закончить.

И если нам нужно проверить, если функция, которую мы выполняется в потоке закончена, мы можем проверить, если он жив:

simple_thread.is_alive() # return bool 

И если мы хотим, чтобы ждать, пока эта функция в потоке не закончил работу, мы можем join нить. Также хорошо до join потоки, когда они закончены.

simple_thread.join() 

В основном это полезно для задач ввода-вывода, поскольку в то время как программа ожидает данные, чтобы быть готовыми к/от IO он может делать другие вещи.

Я пытаюсь создать поток, который принимает 3 переменные (с плавающей точкой), которые ...

Я предполагаю, что вы хотите достичь чего-то, и вы используете темы в качестве инструмента. Я имею в виду, что создание потоков не является целью здесь, а способ достижения желаемого. Поскольку детали приложения отсутствуют в этом вопросе, я не могу предоставить точный код/​​образцы, поэтому я собираюсь перейти с абстрактным/общим ответом, надеясь, что вы можете применить эту идею к своему приложению.

Я думаю, что приложение получает данные откуда-то (следовательно, IO), и каждые 30 мс новые данные должны быть отправлены в ardunio (который снова действует как IO). Таким образом, приложение имеет 2 IO-плеча, один для приема данных, а другой - для отправки в ardunio. Поэтому использование потоков может быть оправдано.

У нас есть 2 функции, 1 для чтения данных и 1 для обновления ardunio. Мы запускаем их в 2 потоках, thread_read (для чтения данных) и thread_ardunio (для обновления ardunio).

Если это так, нам нужен способ обмена информацией между потоками. Темы облегчают использование памяти (в памяти процесса, доступной через переменные), поэтому мы можем использовать переменную, доступную обеими функциями. Когда переменная обновляется на 1 поток, в другом потоке также будут отображаться обновленные результаты.

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() 

Это один из способов пойти. ИМХО не так красиво, так как там есть глобальная переменная. Что мы можем сделать, чтобы устранить переменную? Мы можем использовать очередь (см. queue.Queue).

Очереди - хорошие способы общения между потоками, я думаю. Таким образом, поток, содержащий данные, помещает их в очередь (производитель a.k.a), а другой поток выбирает элементы из очереди (пользователь a.k.a).

Что-то вроде этого:

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() 

выглядит лучше.

Теперь нам нужно дождаться выполнения функций. Поэтому мы можем вызвать метод join для потоков. Также на этот раз я взял на себя смелость предположить, что мы можем контролировать, сколько времени потребуется для чтения данных. Если мы должны обновлять ardunio каждые 30 мс новыми данными, то производитель может настроить частоту, и потребитель может просто потреблять без колебаний.

Также нам нужен способ рассказать о потоках, чтобы прекратить производство/потребление. Мы можем использовать Event (см. threading.Event) для этого, или для простоты, по согласованию, данные в очереди будут представлять, что потребитель должен остановиться.

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() 

Код выше предполагает, что производитель (thread_read) будет знать, чтобы остановить производство данных.

Если это не так, мы можем использовать Event для запуска обеих функций, чтобы прекратить производство и потребление.

В конце концов, я столкнулся с небольшим уловом при присоединении потоков. Если основной поток соединяется с другими потоками, он заблокирован и не будет хорошо реагировать на SIGINT. Поэтому, если вы попытаетесь остановить процесс Python (нажав Ctrl + C или отправив SIGINT), он не уйдет.

Однако мы можем попытаться присоединиться к потокам с таймаутом. Поэтому каждый часто основной поток может смотреть на принятый сигнал и обрабатывать их.

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()