Python线程和Arduino通信

Python threading and Arduino communication

我正在尝试创建一个线程,该线程采用 (cX, cY, angle)python 代码中不断更新的 3 个变量(浮动),并每 30 毫秒将其发送到 arduino uno。

我以前从未使用过线程,作为初学者,我真的很难理解它。

有人给我的想法是创建一个包含三个变量的元组,并在主 python 流中创建一个线程,将那个元组发送到 arduino(PC 通过串口发送到 arduino)。

任何人都可以帮助我或至少告诉我如何开始。

线程是 运行 并发编码的一种方式。这意味着你可以 运行 函数 A,但在它完成之前,你可以启动函数 B 并且它们继续执行,不一定同时执行(这就是并行编程)。想像在给定的时间点,如果您停止时间并查看代码中发生的事情,您会看到函数 A 执行了一些尚未完成的语句,但函数 B 的一些语句也已执行。

在Python线程模块中,每个线程都是一个函数(或方法),带有一些可能的参数,当它启动函数体时运行s。当函数returns时,线程完成运行ning,线程状态变为not alive(参见threading.Thread.is_alive())。

因此我们通过传递 function/method 引用和可能的参数来创建线程:

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_argsmy_function)完成。

如果我们需要检查我们在线程中执行的函数是否已经完成,我们可以检查它是否还活着:

simple_thread.is_alive()  # return bool

而如果我们想等到线程中的函数执行完毕,我们可以join线程。线程完成后 join 也很好。

simple_thread.join()

这对 IO 任务很有帮助,因为当程序等待数据准备就绪时 to/from IO 它可以做其他事情。

I am trying to create a thread that takes 3 variables (float) that are ...

我假设您想有所作为并且正在使用线程作为工具。我的意思是创建线程不是这里的目标,而是实现您想要的目标的一种方式。 由于问题中缺少应用程序的详细信息,我无法提供确切的 code/samples,因此我将使用 abstract/generic 答案希望您可以将这个想法应用到您的应用程序中。

我认为应用程序正在从某个地方接收数据(因此是 IO),并且每 30 毫秒应将新数据发送到 ardunio(它再次充当 IO)。所以应用程序有 2 个 IO 臂,一个用于接收数据,另一个用于发送到 ardunio。所以使用线程可能是合理的。

我们可以有 2 个函数,1 个用于读取数据,1 个用于更新 ardunio。我们 运行 它们在 2 个线程中,thread_read(读取数据)和 thread_ardunio(更新 ardunio)。

如果是这样,我们需要一种在线程之间共享信息的方法。线程使使用内存变得容易(在进程内存中,可通过变量访问),因此我们可以使用两个函数都可以访问的变量。当一个线程更新变量时,另一个线程也会看到更新后的结果。

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 方法。这次我还冒昧地假设我们可以控制读取数据所需的时间。如果我们应该每 30 毫秒用新数据更新 ardunio,那么生产者可以调整频率,消费者可以毫不犹豫地消费。

我们还需要一种方法来告诉线程停止 producing/consuming。 为此,我们可以使用 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()