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
|
Title: Adafruit Macropad : multitâche coopératif avec asyncio
|
||||||
Category: linux
|
Category: linux
|
||||||
Tags: Adafruit, CircuitPython, Python
|
Tags: Adafruit, CircuitPython, Python
|
||||||
Date: 2023-07-11 15:10
|
Date: 2023-07-17 18:46
|
||||||
cover: assets/backgrounds/Adafruit_Macropad.jpg
|
cover: assets/backgrounds/Adafruit_Macropad.jpg
|
||||||
status: draft
|
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
|
le Macropad et comment utiliser la connexion série pour envoyer et recevoir des
|
||||||
données depuis un ordinateur.
|
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
|
alors que nous voulions changer la fréquence du clignotement de la DEL : il
|
||||||
fallait attendre que l'instruction `time.sleep` soit terminée.
|
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
|
`await` pour corriger ce problème. Nous essaierons ensuite d'aller plus loin
|
||||||
afin d'appréhender cette bibliothèque.
|
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
|
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
|
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.).
|
ils sont plus difficiles à gérer (concurrences, sections critiques, etc.).
|
||||||
|
|
||||||
Dans le cas d'`asyncio`, nous parlerons [**de coroutines**][l_coroutines] pour
|
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_threads_cp]:https://github.com/adafruit/circuitpython/issues/1124
|
||||||
[l_coroutines]:https://fr.wikipedia.org/wiki/Coroutine
|
[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
|
## Installer `asyncio` sur le Macropad
|
||||||
|
|
||||||
Il est possible que la bibliothèque `asyncio` et ses dépendances ne soient pas
|
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*:
|
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
|
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
|
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`
|
`asyncio.sleep` est définie à 0 afin de laisser l'ordonnanceur du module `asyncio`
|
||||||
interrompre notre fonction et ainsi **empêcher une coroutine à l'exécution
|
interrompre notre fonction et ainsi **empêcher une coroutine de bloquer les
|
||||||
longue bloquer les autres**.
|
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
|
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
|
Ensuite `asyncio.gather()` permet de lancer l'exécution concurrente de nos deux
|
||||||
tâches.
|
tâches.
|
||||||
|
|
||||||
Enfin, notre fonction `main()` est lancée via `asyncio.run` permettant alors de
|
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 fois le fichier sauvegardé, la DEL n°0 devrait se mettre à clignoter avec
|
||||||
une fréquence d'une seconde.
|
une fréquence d'une seconde.
|
||||||
|
@ -139,15 +142,15 @@ une fréquence d'une seconde.
|
||||||
### Oui mais!
|
### Oui mais!
|
||||||
|
|
||||||
Lançons *minicom* sur le port série affichant la console . Nous observons
|
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`
|
ainsi les actions effectuées par notre programme grâce aux différents `print`
|
||||||
disséminées dans le code.
|
disséminés dans le code.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
minicom -D /dev/ttyACM0 -c on
|
minicom -D /dev/ttyACM0 -c on
|
||||||
```
|
```
|
||||||
|
|
||||||
Maintenant, depuis un autre terminal, changeons la fréquence de clignotement de
|
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
|
```shell
|
||||||
printf "time 20\n" > /dev/ttyACM1
|
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
|
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
|
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()`.
|
la modification soit prise en compte par la coroutine `blink()`.
|
||||||
|
|
||||||
## Prendre en compte immédiatement la modification de fréquence
|
## Prendre en compte immédiatement la modification de fréquence
|
||||||
|
|
||||||
Comment pouvons nous faire en sorte que la modification de fréquence soit
|
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()`**
|
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
|
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
|
coroutine `blink()`. C'est bien entendu à nous d'implémenter la gestion de cette
|
||||||
exception.
|
exception.
|
||||||
|
|
||||||
|
|
||||||
Vous pouvez télécharger le fichier `code.py`
|
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
|
```python
|
||||||
from adafruit_macropad import MacroPad
|
from adafruit_macropad import MacroPad
|
||||||
|
@ -242,19 +245,18 @@ async def main():
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
```
|
```
|
||||||
C'est notre fonction `check_serial()` qui lance le `cancel()` à partir de la
|
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 à
|
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
|
recommencer depuis le début du `while`. Notre modification **est donc appliquée
|
||||||
immédiatement**.
|
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
|
Maintenant que nous avons vu le fonctionnement de base d'`asyncio`, prenons un
|
||||||
exemple un peu plus complet.
|
exemple un peu plus complet.
|
||||||
|
|
||||||
|
|
||||||
Vous pouvez télécharger le fichier `code.py`
|
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
|
```python
|
||||||
from adafruit_macropad import MacroPad
|
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*,
|
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`.
|
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
|
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
|
en utilisant `asyncio.done()` afin de vérifier que la tâche testée soit bien
|
||||||
terminée.
|
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
|
* `get_key()` chargée d'écouter les évènements clavier et de lancer les
|
||||||
différents clignotements en fonction de la touche appuyée;
|
différents clignotements en fonction de la touche appuyée ;
|
||||||
* `manage_tasks` chargée de nettoyer le tableau des tâches;
|
* `manage_tasks` chargée de nettoyer le tableau des tâches ;
|
||||||
|
|
||||||
Notre fonction `manage_tasks()` est simple, nous parcourons l'ensemble du
|
Notre fonction `manage_tasks()` est simple, nous parcourons l'ensemble du
|
||||||
tableau et lorsque nous trouvons une coroutine terminée nous supprimons
|
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'é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
|
é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
|
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
|
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
|
Python ici présente ne contient pas de primitive de synchronisation comme les
|
||||||
sémaphores, variables conditions, ... : seul le `Lock` sont présent.
|
sémaphores, variables conditions, ... : seul le `Lock` est présent.
|
||||||
C'est l'implémentation Python des [Mutex][l_mutex]: l'accès à une
|
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
|
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.
|
aura accès à la fois, les autres attendent leur tour une à une.
|
||||||
|
|
||||||
Voici un exemple de code pour notre Macropad utilisant `Lock`, vous pouvez
|
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.
|
en attente sera matérialisée.
|
||||||
|
|
||||||
Nous pouvons observer sur l'écran du *Macropad* différentes informations
|
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 :
|
permettent de comprendre ce qui se passe lors de l'exécution de nos coroutines :
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -437,16 +439,24 @@ Aquire mutex l:6
|
||||||
|
|
||||||
Nous avons vu dans cet article, au fils des différents exemples, comment
|
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
|
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
|
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
|
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.
|
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-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/
|
[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
|
*[DEL]: Diode ÉlectroLuminescente
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue