Le Macropad Adafruit est un petit clavier de 12 touches rétroéclairées avec un écran OLED et un sélecteur. Il est motorisé par un Raspberry Pi RP2040.
Dans cet article, nous allons voir comment utiliser ce clavier et communiquer avec via l’utilisation du port série.
Circuitpython
Comme beaucoup de carte électronique à base de micro controlleurs, ce clavier est compatible avec l’écosystème Arduino, il est donc possible de le programmer en C. Il existe aussi un firmware embarqant CircuitPython, couplé aux diférentes bibliothèques fournies par Adafuit, il est possible d’utiliser Python pour le programmer. C’est ce que nous allons utiliser ici.
La documentation sur l’installation du firmware CyrcuitPython pour le Macropad est disponible sur le site d’Adafuit
Préparer l’environnement
Sur notre ordinateur nous allons préparer un environnement virtuel Python pour
y installer circup
, module Python permettant d’installer le nécessaire sur
le Macropad:
python -m venv ~/venv_circup
source ~/venv_circup/bin/activate
pip install circup
Puis après avoir branché le Macropad et monté l’espace de stockage :
circup install adafruit_macropad
Installer minicom
Nous allons maintenant préparer notre système pour tester la connexion série. Tout d’abord installons un logiciel pour communiquer via le port série pour Interagir avec le Macropad. Personnellement j’ai choisi minicom disponible dans les dépôts Debian, Archlinux et sûrement d’autres distributions:
pacman -S minicom
Il est aussi nécessaire d’ajouter votre utilisateur dans le groupe uucp
en
utilisant la commande suivante (avec root
ou sudo
):
gpasswd -a ephase uucp
Initialiser un périphérique série sur la Macropad
Afin d’initialiser un périphérique série sur le Macropad, il est nécessaire de
créer un fichier boot.py
à la racine de votre lecteur CIRCUITPY et d’y ajouter
le code suivant:
import usb_cdc
try:
"""
Initialisation du port série pour la console REPL mais
aussi pour un périphérique série, Sous Linux il sera
disponible dans /dev/ttyACM
"""
usb_cdc.enable(console=True, data=True)
except Exception as e:
print(e)
Lors de l’enregistrement du fichier, le Macropad devrait se relancer et prendre
en compte les modifications. Plus besoin de toucher à ce fichier, on le laissera
tranquille tout au long de cet article. Nous modifierons maintenant le fichier
code.py
toujours à la racine de notre lecteur CIRCUITPY
.
Vous pouvez télécharger le fichier boot.py
ici.
Premier script, faire clignoter une DEL
Nous allons maintenant tester notre premier code. Celui-ci va simplement faire
clignoter une DEL sous une touche de notre clavier. Nous pourrons changer la
fréquence en envoyant la commande time
via une connexion série. Voici le
code en question:
from adafruit_macropad import MacroPad
import usb_cdc
import time
macropad = MacroPad()
serial = usb_cdc.data
def exec_command (data):
global timer
command,option = data.split()
print("cmd: {} | opt: {}".format(command, option))
if command == 'time':
timer = float(option)
print("new timer: {}".format(timer))
def blink(led, light):
print('light: {}'.format(light))
if light:
macropad.pixels[led] = (33, 45, 230)
else:
macropad.pixels[led] = (0, 0, 0)
print('c: {}'.format(macropad.pixels[led]))
return False if light else True
timer=2
light=False
in_data=bytearray()
while True:
light = blink(1, light)
time.sleep(timer)
if serial.in_waiting > 0:
while(serial.in_waiting>0):
byte = serial.read(1)
if byte == b'\r':
exec_command(in_data.decode("utf-8"))
in_data = bytearray()
else:
in_data += byte
Vous pouvez télécharger le fichier code.py
ici.
Le code est plutôt simple, nous initialisons notre Macropad et la connexion série avec le début de notre fichier:
from adafruit_macropad import MacroPad
import usb_cdc
import time
macropad = MacroPad()
serial = usb_cdc.data
Ensuite nous définissons notre fonction exec_command
chargée d’interpréter les
commandes envoyées depuis la connexion série puis notre fonction blink
changer
de faire clignoter notre DEL.
Après avoir initialiser les variables timer
light
et in_data
— cette
dernière recevra les données de la connexion série le programme commence. La
partie la plus intéressante démarre avec if serial.is_wainting
.
À ce moment, si des données sont en attente sur le port série, alors nous les
ajoutons à notre variable in_data
. Lors de la réception d’un retour chariot
\r
, alors nous passons les données reçues à notre fonction exec_command
.
Test de notre code
Le Test démarre une fois le fichier code.py
écrit sur notre Macropad.
Vous devriez voir la DEL sous la touche 2 clignoter toute les deux secondes.
L’écran du Macropad affiche aussi les informations données par les instructions
print
de notre code.
À partir de de moment nous allons pouvoir utiliser minicon
pour se connecter à
la partie REPL du Macropad et ainsi visualiser aussi les print
. Ici la console
REPL est accessible via le périphérique /dev/ttyACM0
:
$ minicom -D /dev/ttyACM0
Welcome to minicom 2.8
OPTIONS: I18n
Compiled on Jan 9 2021, 12:42:45.
Port /dev/ttyACM0, 18:28:08
Press CTRL-A Z for help on special keys
light: False
c: (0, 0, 0)
light: True
c: (33, 45, 230)
La connexion série initiée par notre code est disponible sur le périphérique
/dev/ttyACM1
. Pour modifier le timer, alors nous pouvons entrer la commande
suivante depuis un autre terminal:
printf "time 10\r" > /dev/ttyACM1
Sur notre terminal de contrôle avec minicom
, vous voyons apparaitre alors:
light: True
c: (33, 45, 230)
cmd: time | opt: 10
new timer: 10.0
light: False
c: (0, 0, 0)
Notre timer est bien pris en compte et le clignotement se fait maintenant toutes les dix secondes.
Ce code est bien rudimentaire : il n’y a aucun contrôle et un rien — comme
juste envoyer time toto
avec notre printf
— le fait planter. Pas de panique,
le Macropad redémarrera alors tout seul.
Autre problème, il faut attendre que notre instruction time.sleep(timer)
soit
finie avant que la modification de notre timer
soit prise en compte. Il est
possible d’utiliser la librairie asyncio
pour contourner ce problème, mais
nous ne traiterons pas de ce cas dans cet article.
Envoyer des données depuis le Macroad
Maintenant que nous avons réussi à envoyer des données depuis notre ordinateur
vers le Macropad, nous allons en envoyer en sens inverse. Nous allons modifier
le fichier code.py
, plus précisément la fonction exec_command
comme ci-dessous:
# [...]
def exec_command (data):
global timer
try:
command,option = data.split()
except:
command = ""
option = ""
print("cmd: {} | opt: {}".format(command, option))
if command == 'time':
timer = float(option)
print("new timer: {}".format(timer))
response = "nouveau timer : {}\r\n".format(option)
buffer = bytearray(response)
serial.write(buffer)
# [...]
Vous pouvez télécharger ce fichier code.py
ici.
La modification est relativement simple et tient en 3 instructions. Pour tester
son fonctionnement, nous allons ouvrir deux terminaux avec minicom
:
- sur notre périphérique
/dev/ttyACM0
afin d’avoir un accès à la console du Macropad; - sur
/dev/ttyACM1
afin d’interagir avec notre programme dans le Macropad;
Sur cette dernière fenêtre de terminal, nous allons lancer minicom avec la
commande minicom -D /dev/ttyACM1 -c on
puis une fois lancé nous allons
activer l’affichage des commandes que l’on saisit avec Ctrl + A
puis E
. Cette
étape est facultative mais il est plus agréable de voir ce que nous saisissons.
À partir de là nous pouvons changer la fréquence de clignotement avec la
commande time
saisie directement dans minicom, notre Macropad nous répond
alors directement en envoyant la confirmation via la connexion série:
Welcome to minicom 2.8
OPTIONS: I18n
Compiled on Jan 9 2021, 12:42:45
Port /dev/ttyACM1, 17:16:49
Press CTRL-A Z for help on special keys
time 1
nouveau timer : 1
time 2
nouveau timer : 2
time 4
nouveau timer : 4
time 1
nouveau timer : 1
Vous devriez obtenir un fonctionnement similaire à ce que montre la capture d’écran ci-dessus.
Nous avons maintenant obtenu une communication série bidirectionnelle entre notre Macropad et notre ordinateur. Mais cet exemple est plutôt succinct, que pouvons nous en faire concrètement?
Un (début) d’exemple “réel”
Nous allons utiliser ce que nous avons vu jusqu’ici pour mettre en place un bouton du clavier pour mettre en sourdine le microphone de notre ordinateur.
Nous allons programmer le bouton N°1 du Macropad pur envoyer une instruction mute. Sa DEL sera rouge si le microphone est coupé et verte s’il est actif. Un script Python sur notre ordinateur se chargera de recevoir les ordres les exécutera et enverra l’état du microphone en retour.
Sur le Macropad
Voici le code à mettre dans code.py
:
from adafruit_macropad import MacroPad
import usb_cdc
import time
macropad = MacroPad()
serial = usb_cdc.data
def exec_command (data):
global muted
try:
command,option = data.split()
except:
command = ""
option = ""
print("cmd: {} | opt: {}".format(command, option))
if command == 'mute':
muted = True if option == 'yes' else False
timer=2
in_data=bytearray()
muted=False
while True:
# Define muted button color
if muted:
macropad.pixels[1] = (255,0,0)
else:
macropad.pixels[1] = (0,255,0)
# Get key event
key_event = macropad.keys.events.get()
if key_event:
if key_event.pressed:
if key_event.key_number == 1:
serial.write(bytearray('mute\r\n'))
# Receive serial data
if serial.in_waiting > 0:
while(serial.in_waiting > 0):
byte = serial.read(1)
if byte == b'\r':
exec_command(in_data.decode("utf-8"))
in_data = bytearray()
else:
in_data += byte
Comme vous pouvez le constater nous utilisons largement le code présent dans les deux premières parties.
Ce fichier code.py
est disponible ici
Le script Python sur notre ordinateur
Il utilise pactl
pour piloter le micro et obtenir son état. Voici le code à
mettre dans le fichier serial_daemon.py
:
#!/usr/bin/env python
import serial
import subprocess
ser = serial.Serial(port='/dev/ttyACM1')
ser.flushInput()
print('Begin loop')
while True:
line = ser.readline()
try:
command = (line.decode()).split()
print('command received: {}'.format(command[0]))
except:
print('no valid command received')
command = ""
try:
if command[0] == "mute":
# First subprocess for toggle mote the microphone
subprocess.run(
['pactl', 'set-source-mute', '@DEFAULT_SOURCE@', 'toggle'],
)
# Second one for check the states of microphone
result = subprocess.run(
['pactl', 'get-source-mute', '@DEFAULT_SOURCE@'],
capture_output=True
)
message = "mute {}\r".format(result.stdout.split()[1].decode())
ser.write(message.encode())
print('command sent: {}'.format(message))
except Error as e:
print('Error in command: {}'.format(e))
Ce fichier serial_daemon.py
est dispoible ici
Le script commence par initialiser le périphérique série, puis vide l’ensemble
des données présente dans le buffer du port série avec ser.flushInput()
afin
de repartie de zéro. Commence ensuite une boucle infinie (notre script reste
résident en mémoire).
line = ser.readline()
permet de mettre notre processus en attente passive tant
qu’une fin de ligne de type \n\r
n’a pas été reçue. A partir de là le script
traite la linge reçue.
Le script lance la commande pour changer l’état du microphone puis la commande
pour en vérifier l’état. L’instruction ser.write()
transmet l’état de ce
dernier au Macropad qui agira en conséquence.
Ici encore le script reste minimal, il suffit maintenant de le lancer et d’observer les différents messages ainsi que l’état du microphone :
chmod +x serial_daemon.py
./serial_daemon.py
En appuyant sur le bouton adéquat du clavier, notre script serial_daemon
devrait réagir comme ci-dessous dans la console:
Begin loop
command received: mute
command sent: mute yes
command received: mute
command sent: mute no
command received: mute
command sent: mute yes
Et la lumière sous la touche devrait changer de couleur en fonction de l’activation de la sourdine.
En conclusion
Nous avons donc vu comment utiliser les ports série — que se soit pour la connexion REPL ou pour les données — sur notre Macropad Adafruit. Personnellement j’adore ce petit périphérique qui permet une infinité de possibilité et se programme relativement facilement grâce à CircuitPython.
Bien sût cet article n’a pas pour vocation d’aller en profondeur, que se soit dans les paramétrages des connexions séries que dans les possibilités offertes. Mais si le sujet vous intéresse une petite recherche sur les Internets vous donnera de quoi faire. Je ne peux cependant que vous conseiller l’excellent article de Carlos Olmos qui a utilisé le Macropad pour se créer un jukebox. Je m’en suis inspiré pour l’écriture de cet article.
Credits
Les photos proviennent du site Adafuit, prisent par Kattni Rembor et sous licence Creative Common By-Sa.