J'utilise la bibliothèque python gpiozero pour gérer des périphériques GPIO simples sur un Raspberry Pi (j'utilise ici un MotionSensor pour l'exemple):
# will not work because MotionSensor() is not using asyncio motionSensor.when_motion = await self.whenMotion
Mon problème ici est que j'ai essayé de donner une fonction async
a un rappel vers motionSensor.when_motion
.
Donc j'obtiens l'erreur que la fonction whenMotion
est async
mais jamais attend
mais je ne peux pas vraiment l'attendre:
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()
Avez-vous une idée de la façon dont je peux attribuer ma fonction async
à personne? p>
4 Réponses :
Si vous faites cela avec des coroutines, vous devrez récupérer et exécuter la boucle d'événements. Je vais supposer que vous utilisez python 3.7, auquel cas vous pouvez faire quelque chose comme:
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()
Si vous êtes sur python 3.8, vous pouvez simplement utiliser asyncio.run
plutôt que toutes les étapes d'obtention et d'exécution explicites de la boucle d'événements.
Merci pour votre réponse mais dans mon cas, cette loop.run_until_complete ()
est bloquante et donc mon script ne peut pas continuer à fonctionner de manière asynchrone. Avez-vous une idée de ce qu'est le problème?
Étant donné que cela s'exécute dans une boucle et que when_motion
n'a pas besoin d'une valeur de retour, vous pouvez faire:
... motionSensor.when_motion = self.whenMotion def whenMotion(self): asyncio.ensure_future(self.__whenMotionCallback())
Cela planifiera le rappel asynchrone dans la boucle d'événement et gardez le code d'appel synchrone pour la bibliothèque.
Merci pour votre réponse mais je reçois étonnamment cette erreur RuntimeError: Il n'y a pas de boucle d'événement en cours dans le thread 'Thread-1'
. Est-ce que self .__ whenMotionCallback ()
est plus qu'une simple fonction async / wait
?
Donc, après des recherches, j'ai trouvé que je devais créer une nouvelle boucle asyncio pour exécuter un script asynchrone dans une méthode non asynchrone. Alors maintenant, ma méthode whenMotion ()
n'est plus async
mais exécutez-en une en utilisant 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()
p>
Cela fonctionne un peu mais vous créez 2 boucles asyncio et vous aurez maintenant des problèmes de multi-threading si vous utilisez des objets du thread principal. J'ai ajouté une réponse qui vous permet de rappeler la boucle asyncio principale de manière thread-safe.
Lorsque la propriété when_motion est définie, gpiozero crée un nouveau thread qui exécute le rappel (ce n'est pas très bien documenté). Si le rappel doit être exécuté dans la boucle asyncio principale, vous devez renvoyer le contrôle au thread principal.
Le call_soon_threadsafe fait cela pour vous. Essentiellement, il ajoute le rappel à la liste des tâches que la boucle asyncio principale appelle lorsqu'une attente se produit.
Cependant, les boucles asyncio sont locales à chaque thread: voir get_running_loop
Ainsi, lorsque l'objet gpiozero est créé dans le thread asyncio principal, vous devez créer cette boucle objet disponible pour l'objet lorsque le rappel est appelé.
Voici comment procéder pour un PIR qui appelle une méthode asyncio MQTT:
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)
avez-vous déjà une boucle asyncio en cours d'exécution? le
when_motion
a-t-il besoin d'une valeur de retour, ou est-ce que ça va s'il tourne juste une tâche asynchrone?Ce code complet s'exécute dans une autre boucle en utilisant
run_until_complete
et aucunwhen_motion
n'a pas besoin de renvoyer de valeur.