Some corrections, thanks my wife and heuzef
This commit is contained in:
parent
5c089947e7
commit
febf52d368
1 changed files with 47 additions and 37 deletions
|
@ -1,7 +1,7 @@
|
|||
Title: Adafruit Macropad : multitâche coopératif avec asyncio
|
||||
Category: linux
|
||||
Tags: Adafruit, CircuitPython, Python
|
||||
Date: 2023-07-11 15:10
|
||||
Date: 2023-07-17 18:46
|
||||
cover: assets/backgrounds/Adafruit_Macropad.jpg
|
||||
status: draft
|
||||
|
||||
|
@ -10,7 +10,7 @@ Nous avons découvert dans mon
|
|||
le Macropad et comment utiliser la connexion série pour envoyer et recevoir des
|
||||
données depuis un ordinateur.
|
||||
|
||||
Dans la première partie de cet article, nous avions été confronté à un problème
|
||||
Dans la première partie de cet article, nous avions été confrontés à un problème
|
||||
alors que nous voulions changer la fréquence du clignotement de la DEL : il
|
||||
fallait attendre que l'instruction `time.sleep` soit terminée.
|
||||
|
||||
|
@ -18,7 +18,7 @@ Nous allons vois ici comment utiliser `asyncio` et l'implémentation de `async`
|
|||
`await` pour corriger ce problème. Nous essaierons ensuite d'aller plus loin
|
||||
afin d'appréhender cette bibliothèque.
|
||||
|
||||
## Qu'est-ce que `asyncio`
|
||||
## Qu'est-ce qu'`asyncio`?
|
||||
|
||||
C'est une bibliothèque de *Python* qui permet de réaliser de la **programmation
|
||||
asynchrone**. Le principe ici est d'éviter que le programme attende sans rien
|
||||
|
@ -32,7 +32,7 @@ s'en charge. Mais d'abord *CircuitPython* ne [les supporte pas](l_threads_cp) et
|
|||
ils sont plus difficiles à gérer (concurrences, sections critiques, etc.).
|
||||
|
||||
Dans le cas d'`asyncio`, nous parlerons [**de coroutines**][l_coroutines] pour
|
||||
des fonctions déclarée avec le mot clef `async`.
|
||||
des fonctions déclarées avec le mot clef `async`.
|
||||
|
||||
[l_threads_cp]:https://github.com/adafruit/circuitpython/issues/1124
|
||||
[l_coroutines]:https://fr.wikipedia.org/wiki/Coroutine
|
||||
|
@ -40,7 +40,7 @@ des fonctions déclarée avec le mot clef `async`.
|
|||
## Installer `asyncio` sur le Macropad
|
||||
|
||||
Il est possible que la bibliothèque `asyncio` et ses dépendances ne soient pas
|
||||
installées, il est alors possible de les installer avec `circup`. Commençons pas
|
||||
installées, il est alors possible de les installer avec `circup`. Commençons par
|
||||
créer un *environnement virtuel Python*:
|
||||
|
||||
```
|
||||
|
@ -121,17 +121,20 @@ ce module qui se charge maintenant de l'attente, ainsi il pourra gérer
|
|||
|
||||
La partie du code utilisé pour la gestion des entrées par le port série est elle
|
||||
aussi placée dans la fonction asynchrone `check_serial()`. Cette fois
|
||||
`asyncio.sleep` est définis à 0 afin de laisser l'ordonnanceur du module `asyncio`
|
||||
interrompre notre fonction et ainsi **empêcher une coroutine à l'exécution
|
||||
longue bloquer les autres**.
|
||||
`asyncio.sleep` est définie à 0 afin de laisser l'ordonnanceur du module `asyncio`
|
||||
interrompre notre fonction et ainsi **empêcher une coroutine de bloquer les
|
||||
autres en ne rendant jamais la main**[^coroutine].
|
||||
|
||||
[^coroutine]: Cette technique permet aussi de rendre une fonction compatible
|
||||
asynchrone
|
||||
|
||||
Une nouvelle fonction asynchrone fait son apparition : `main()`. C'est ici que
|
||||
nous définissions nos tâches (contenants nos *coroutines*) `t_led` et`t_serial`.
|
||||
nous définissons nos tâches (contenants nos *coroutines*) `t_led` et`t_serial`.
|
||||
Ensuite `asyncio.gather()` permet de lancer l'exécution concurrente de nos deux
|
||||
tâches.
|
||||
|
||||
Enfin, notre fonction `main()` est lancée via `asyncio.run` permettant alors de
|
||||
de l'exécuter jusqu'à sa fin.
|
||||
l'exécuter jusqu'à sa fin.
|
||||
|
||||
Une fois le fichier sauvegardé, la DEL n°0 devrait se mettre à clignoter avec
|
||||
une fréquence d'une seconde.
|
||||
|
@ -139,15 +142,15 @@ une fréquence d'une seconde.
|
|||
### Oui mais!
|
||||
|
||||
Lançons *minicom* sur le port série affichant la console . Nous observons
|
||||
ainsi les actions effectuées par notre programme grâce au différents `print`
|
||||
disséminées dans le code.
|
||||
ainsi les actions effectuées par notre programme grâce aux différents `print`
|
||||
disséminés dans le code.
|
||||
|
||||
```shell
|
||||
minicom -D /dev/ttyACM0 -c on
|
||||
```
|
||||
|
||||
Maintenant, depuis un autre terminal, changeons la fréquence de clignotement de
|
||||
la DEL pour la placer à 20 secondes puis le remettre à 1 seconde:
|
||||
la DEL à 20 secondes pour la remettre aussitôt à 1 seconde:
|
||||
|
||||
```shell
|
||||
printf "time 20\n" > /dev/ttyACM1
|
||||
|
@ -176,20 +179,20 @@ l:0 c:0xad20b4 t:1.0
|
|||
|
||||
Les lignes `serial data received` et `cmd:time opt:<>` sont apparues
|
||||
immédiatement, mais lors du changement de fréquence de 20 à 1, il a fallu
|
||||
**attendre que les 20 secondes définies précédement soient ecoulées** pour que
|
||||
**attendre que les 20 secondes définies précédemment soient écoulées** pour que
|
||||
la modification soit prise en compte par la coroutine `blink()`.
|
||||
|
||||
## Prendre en compte immédiatement la modification de fréquence
|
||||
|
||||
Comment pouvons nous faire en sorte que la modification de fréquence soit
|
||||
immédiatement répercutée? Tout simplement en **utilisant la méthode `cancel()`**
|
||||
sur notre tache `t_led`. Celle-ci lève l'exception `CancelError` dans la
|
||||
immédiatement répercutée ? Tout simplement en **utilisant la méthode `cancel()`**
|
||||
sur notre tâche `t_led`. Celle-ci lève l'exception `CancelError` dans la
|
||||
coroutine `blink()`. C'est bien entendu à nous d'implémenter la gestion de cette
|
||||
exception.
|
||||
|
||||
|
||||
Vous pouvez télécharger le fichier `code.py`
|
||||
[ici]({attach}./files/async_blink/code.py), voici le code:
|
||||
[ici]({attach}./files/async_blink/code.py), voici le code :
|
||||
|
||||
```python
|
||||
from adafruit_macropad import MacroPad
|
||||
|
@ -242,19 +245,18 @@ async def main():
|
|||
asyncio.run(main())
|
||||
```
|
||||
C'est notre fonction `check_serial()` qui lance le `cancel()` à partir de la
|
||||
tache `t_led` que nous lui passons en paramètre. L'effet est alors immédiat :
|
||||
tâche `t_led` que nous lui passons en paramètre. L'effet est alors immédiat :
|
||||
l'exception `CancelError` est lancée dans `blink()` forçant cette dernière à
|
||||
recommencer depuis le début du `while`. Notre modification **est donc appliquée
|
||||
immédiatement**.
|
||||
|
||||
## Gestions dynamique des tâches
|
||||
## Gestion dynamique des tâches
|
||||
|
||||
Maintenant que nous avons vu le fonctionnement de base d'`asyncio`, prenons un
|
||||
exemple un peu plus complet.
|
||||
|
||||
|
||||
Vous pouvez télécharger le fichier `code.py`
|
||||
[ici]({attach}./files/async_array/code.py), voici le code:
|
||||
[ici]({attach}./files/async_array/code.py), voici le code :
|
||||
|
||||
```python
|
||||
from adafruit_macropad import MacroPad
|
||||
|
@ -302,27 +304,27 @@ asyncio.run(main())
|
|||
```
|
||||
|
||||
Lorsque nous appuyons sur une des douze touches du clavier de notre *MacroPad*,
|
||||
la DEL en dessous clignote cinq fois. Vous l'aurez compris chaque clignotement
|
||||
la DEL en dessous clignote cinq fois. Vous l'aurez compris, chaque clignotement
|
||||
est en fait une coroutine gérée par `asyncio`.
|
||||
|
||||
Les différentes tâche sont répertoriée dans un tableau qui est passé à la
|
||||
Les différentes tâches sont répertoriées dans un tableau qui est passé à la
|
||||
fonction `asyncio.gather()`. Plus intéressant, afin de supprimer de notre
|
||||
tableau les coroutines terminée, nous passons par la fonction `manage_tasks()`
|
||||
tableau les coroutines terminées, nous passons par la fonction `manage_tasks()`
|
||||
en utilisant `asyncio.done()` afin de vérifier que la tâche testée soit bien
|
||||
terminée.
|
||||
|
||||
|
||||
Nous avons donc deux tâches lancées dès le départ:
|
||||
Nous avons donc deux tâches lancées dès le départ :
|
||||
|
||||
* `get_key()` changée d'écouter les évènements clavier et de lancer les
|
||||
différents clignotements en fonction de la touche appuyée;
|
||||
* `manage_tasks` chargée de nettoyer le tableau des tâches;
|
||||
* `get_key()` chargée d'écouter les évènements clavier et de lancer les
|
||||
différents clignotements en fonction de la touche appuyée ;
|
||||
* `manage_tasks` chargée de nettoyer le tableau des tâches ;
|
||||
|
||||
Notre fonction `manage_tasks()` est simple, nous parcourons l'ensemble du
|
||||
tableau et lorsque nous trouvons une coroutine terminée nous supprimons
|
||||
l'élément du tableau. Remarquez le `break` lorsque notre fonction supprime un
|
||||
élément de notre `taskslist`, c'est nécessaire afin d'éviter une erreur d'index
|
||||
dut au fait que **notre tableau contiendra un élément de moins**;
|
||||
dû au fait que **notre tableau contiendra un élément de moins**.
|
||||
|
||||
|
||||
Voici l'affichage des informations sur l'écran du *Macropad* (ou sur la console
|
||||
|
@ -345,9 +347,9 @@ leur destruction dans `manage_tasks()` -- merci les `print()`.
|
|||
|
||||
Contrairement à sa grande [sœur *CPython*][l_cpython], notre implémentation de
|
||||
Python ici présente ne contient pas de primitive de synchronisation comme les
|
||||
sémaphores, variables conditions, ... : seul le `Lock` sont présent.
|
||||
C'est l'implémentation Python des [Mutex][l_mutex]: l'accès à une
|
||||
partie du code protégée par un *Mutex* sera sérialisé. Une seule coroutine y
|
||||
sémaphores, variables conditions, ... : seul le `Lock` est présent.
|
||||
C'est l'implémentation Python des [Mutex][l_mutex] : l'accès à une
|
||||
partie du code protégé par un *Mutex* sera sérialisé. Une seule coroutine y
|
||||
aura accès à la fois, les autres attendent leur tour une à une.
|
||||
|
||||
Voici un exemple de code pour notre Macropad utilisant `Lock`, vous pouvez
|
||||
|
@ -413,7 +415,7 @@ Lors de l'appui sur une touche, la fonction `blink()` DEL passe au vert, si le
|
|||
en attente sera matérialisée.
|
||||
|
||||
Nous pouvons observer sur l'écran du *Macropad* différentes informations
|
||||
relatives à l'attente / acquisitions de notre `blink_mutex`. Ces informations
|
||||
relatives à l'attente / acquisition de notre `blink_mutex`. Ces informations
|
||||
permettent de comprendre ce qui se passe lors de l'exécution de nos coroutines :
|
||||
|
||||
```shell
|
||||
|
@ -437,16 +439,24 @@ Aquire mutex l:6
|
|||
|
||||
Nous avons vu dans cet article, au fils des différents exemples, comment
|
||||
utiliser `asyncio` pour gérer des coroutines, permettant d'implémenter dans nos
|
||||
programme pour notre Macropad du multitĉhe coopératif.
|
||||
programmes pour notre Macropad du multitâche coopératif.
|
||||
|
||||
Un petit tous sur [la documentation][L-circuip_async] spécifique à `asyncio`
|
||||
Un petit tour sur [la documentation][L-circuip_async] spécifique à `asyncio`
|
||||
dans CircuitPython vous permettra d'aller plus loin. Elle est cependant très
|
||||
succincte et manque cruellement d'exemples. Si vous voulez en apprendre plus
|
||||
succincte et manque cruellement d'exemples[^doc]. Si vous voulez en apprendre plus
|
||||
sur la programmation asynchrone en Python, vous avez aussi l'excellent tutoriel
|
||||
de nohar sur [Zeste de savoir][l_zeste], il m'a été d'une grande aide pour la
|
||||
de *nohar* sur [Zeste de savoir][l_zeste], il m'a été d'une grande aide pour la
|
||||
rédaction de cet article.
|
||||
|
||||
[^doc] La dernière mise à jour date de juin 2022 au moment de la rédaction de
|
||||
cet article
|
||||
|
||||
[L-circuip_async]:https://docs.circuitpython.org/projects/asyncio/en/latest/index.html
|
||||
[l_zeste]:https://zestedesavoir.com/articles/1568/decouvrons-la-programmation-asynchrone-en-python/
|
||||
|
||||
## Credits
|
||||
|
||||
Les photos proviennent du site Adafuit, prisent par [Kattni Rembor] et sous licence
|
||||
Creative Common By-Sa.
|
||||
|
||||
*[DEL]: Diode ÉlectroLuminescente
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue