1

我正在使用gpiozero python 库来处理 Raspberry Pi 上的简单 GPIO 设备(我在这里使用 MotionSensor 作为示例):

import asyncio
from gpiozero import MotionSensor


class MotionSensorHandler():
    __whenMotionCallback = None

    def __init__(self, pin, whenMotionCallback):
        # whenMotionCallback is an async function
        self.__whenMotionCallback = whenMotionCallback

        # Just init the sensor with gpiozero lib
        motionSensor = MotionSensor(pin)

        # Method to call when motion is detected
        motionSensor.when_motion = self.whenMotion

    async def whenMotion(self):
        await self.__whenMotionCallback()

我的问题是我试图给一个async函数回调到motionSensor.when_motion.

所以我得到了这个whenMotion函数的错误,async但从来没有await,但我实际上不能等待它:

# will not work because MotionSensor() is not using asyncio
motionSensor.when_motion = await self.whenMotion

您知道如何将我的async功能分配给 none one 吗?

4

4 回答 4

3

鉴于这是在循环中运行并且when_motion不需要返回值,您可以执行以下操作:

        ...
        motionSensor.when_motion = self.whenMotion

    def whenMotion(self):
        asyncio.ensure_future(self.__whenMotionCallback())

这将在事件循环中安排异步回调并保持库的调用代码同步。

于 2019-12-18T15:05:04.587 回答
2

如果您使用协程执行此操作,则需要获取并运行事件循环。我假设您使用的是 python 3.7,在这种情况下,您可以执行以下操作:

import asyncio
from gpiozero import MotionSensor


class MotionSensorHandler():
    __whenMotionCallback = None

    def __init__(self, pin, whenMotionCallback):
        # whenMotionCallback is an async function
        self.__whenMotionCallback = whenMotionCallback

        # Just init the sensor with gpiozero lib
        motionSensor = MotionSensor(pin)

        # Method to call when motion is detected
        loop = asyncio.get_event_loop()
        motionSensor.when_motion = loop.run_until_complete(self.whenMotion())
        loop.close()

    async def whenMotion(self):
        await self.__whenMotionCallback()

如果您使用的是 python 3.8,则可以只使用asyncio.run而不是所有显式获取和运行事件循环。

于 2019-12-18T12:22:33.810 回答
1

当设置 when_motion 属性时,gpiozero 创建一个执行回调的新线程(这没有很好地记录)。如果回调应该在主异步循环中执行,那么您需要将控制权传递回主线程。

call_soon_threadsafe方法可以为您做到这一点。本质上,当等待发生时,它将回调添加到主异步循环调用的任务列表中。

但是异步循环对于每个线程都是本地的:请参阅get_running_loop

因此,当在主异步线程中创建 gpiozero 对象时,您需要在调用回调时使该循环对象对对象可用。

以下是我对调用 asyncio MQTT 方法的 PIR 执行此操作的方法:

class PIR:
    def __init__(self, mqtt, pin):
        self.pir = MotionSensor(pin=pin)
        self.pir.when_motion = self.motion
        # store the mqtt client we'll need to call
        self.mqtt = mqtt
        # This PIR object is created in the main thread
        # so store that loop object
        self.loop = asyncio.get_running_loop()

    def motion(self):
        # motion is called in the gpiozero monitoring thread
        # it has to use our stored copy of the loop and then
        # tell that loop to call the callback:
        self.loop.call_soon_threadsafe(self.mqtt.publish,
                                       f'sensor/gpiod/pir/kitchen', True)
于 2020-12-05T10:08:49.317 回答
1

所以经过研究我发现我必须创建一个新的 asyncio 循环来以非异步方法执行异步脚本。所以现在我的whenMotion()方法不再async是使用ensure_future().

import asyncio
from gpiozero import MotionSensor


class MotionSensorHandler():
    __whenMotionCallback = None

    def __init__(self, pin, whenMotionCallback):
        # whenMotionCallback is an async function
        self.__whenMotionCallback = whenMotionCallback

        # Just init the sensor with gpiozero lib
        motionSensor = MotionSensor(pin)

        # Method to call when motion is detected
        motionSensor.when_motion = self.whenMotion

    def whenMotion(self):
        # Create new asyncio loop
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        future = asyncio.ensure_future(self.__executeWhenMotionCallback()) # Execute async method
        loop.run_until_complete(future)
        loop.close()

    async def __executeWhenMotionCallback(self):
        await self.__whenMotionCallback()
于 2019-12-26T19:36:18.523 回答