Introduction
Souhaitant me tourner un peu vers la domotique, je me suis procuré un relai électrique commandable via une communication série. Avec quelques expériences en électricité, démarrer automatiquement certains équipements de son domicile devient possible. Les luminaires dès que l’on rentre chez soi, la fameuse machine à café à l’aube d’une belle journée,… Voire les guirlandes de Noël. J’ai donc développé une petite bibliothèque dédiée à l’utilisation de ce relai. Je décrirai dans un futur article ce qu’il adviendra de lui. Pour le moment, voyons plus en détails cette petite bibliothèque.
⚠ Petite note au préalable sur les dangers de l’électricité, même à de faibles tensions. Dans le doute, pas de doute : on ne fait pas.
Description du relai
L’ensemble des commandes est défini sur des valeurs entières1 d’un octet. Chaque valeur correspond donc à une commande, par exemple la valeur102
à mettre en position active le relai numéro 2
. On pourra envoyer six commandes différentes pour activer ou désactiver les contacteurs. Une commande particulière reste celle de demande d’état qui reste très pratique pour savoir comment sont configurés les contacteurs. N’oublions pas non plus la commande 90
qui informe sur l’identifiant du relai et sa version logicielle.Du point de vue électrique, suivre les abaques2 fournies détermine la plage d’utilisation du relai – qui sera ici jusqu’à 16 A à 230 VAC.
Prise en main
Branchement
Au branchement USB sur un PC GNU/Linux — suis basé sur Debian, le kernel 5.19.0
fait son travail et identifie le composant, diffuse un uevent
capté par udev
3 — via une socket NETLINK_ KOBJECT_UEVENT
— et son service systemd
associé.
[595331.721754] usb 3-1: new full-speed USB device number 5 using xhci_hcd
[595331.872896] usb 3-1: New USB device found, idVendor=04d8, idProduct=ffee, bcdDevice= 1.00
[595331.872908] usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[595331.872911] usb 3-1: Product: USB-RLY02.
[595331.872914] usb 3-1: Manufacturer: Devantech Ltd.
[595331.872917] usb 3-1: SerialNumber: 00008749
[595331.872919] cdc_acm 3-4:1.0: ttyACM0: USB ACM device
C’est le module kernel cdc_adm
4 qui a la tâche de créer le device ttyACM0
. Et c’est via le service systemd-udevd.service
que le relai devient accessible. Le device /dev/ttyACM0
apparait alors dans le filesystem, conformément aux règles préétablies dans /etc/udev/rules.d/
, ou dans le répertoire par défaut /lib/udev/rules.d/
. La règle udev
exécutée est celle dédiée aux appareils série.
Voyons ce qu’il se passe dans les logs d’udev
. Changeons temporairement le niveau de logging :
$ sudo udevadm control --log-priority=debug
Et voici ce qu’il nous raconte :
[...]
systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:8 Importing properties from results of builtin command 'hwdb --subsystem=usb'
systemd-udevd[178638]: ttyACM0: hwdb modalias key: "usb:v04D8pFFEEd0100dc02dsc00dp00ic02isc02ip01in00"
systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:15 Importing properties from results of builtin command 'path_id'
systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:16 LINK 'serial/by-path/pci-0000:00:14.0-usb-0:4:1.0'
systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:19 Skipping builtin 'usb_id' in IMPORT key
systemd-udevd[178638]: ttyACM0: /usr/lib/udev/rules.d/60-serial.rules:23 LINK 'serial/by-id/usb-Devantech_Ltd._USB-RLY02._00008749-if00'
systemd-udevd[178637]: 3-4:1.1: sd-device: Created db file '/run/udev/data/+usb:3-4:1.1' for '/devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.1'
systemd-udevd[178637]: 3-4:1.1: Device processed (SEQNUM=9933, ACTION=bind)
systemd-udevd[178638]: ttyACM0: Setting permissions /dev/ttyACM0, uid=0, gid=20, mode=0660
systemd-udevd[178638]: ttyACM0: Handling device node '/dev/ttyACM0', devnum=c166:0
systemd-udevd[178638]: ttyACM0: /run/udev/links/serial\x2fby-path\x2fpci-0000:00:14.0-usb-0:4:1.0 is modified, but its timestamp is not changed, updating timestamp after 10ms.
systemd-udevd[178637]: 3-4:1.1: sd-device-monitor: Passed 523 byte to netlink monitor
systemd-udevd[178638]: ttyACM0: /run/udev/links/serial\x2fby-id\x2fusb-Devantech_Ltd._USB-RLY02._00008749-if00 is modified, but its timestamp is not changed, updating timestamp after 10ms.
systemd-udevd[178638]: ttyACM0: sd-device: Created db file '/run/udev/data/c166:0' for '/devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.0/tty/ttyACM0'
systemd-udevd[178638]: ttyACM0: Failed to get watch handle, ignoring: No such file or directory
systemd-udevd[178638]: ttyACM0: Device processed (SEQNUM=9930, ACTION=add)
systemd-udevd[178638]: ttyACM0: sd-device-monitor: Passed 1081 byte to netlink monitor
[...]
Et on retrouve bien le chemin d’accès /dev/ttyACM0
qu’udev
nous donne, via la règle 60-serial.rules
.
$ cat /lib/udev/rules.d/60-serial.rules
# do not edit this file, it will be overwritten on update
ACTION=="remove", GOTO="serial_end"
SUBSYSTEM!="tty", GOTO="serial_end"
SUBSYSTEMS=="pci", ENV{ID_BUS}="pci", ENV{ID_VENDOR_ID}="$attr{vendor}", ENV{ID_MODEL_ID}="$attr{device}"
SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci"
SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb"
# /dev/serial/by-path/, /dev/serial/by-id/ for USB devices
KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end"
SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}"
IMPORT{builtin}="path_id"
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="", SYMLINK+="serial/by-path/$env{ID_PATH}"
ENV{ID_PATH}=="?*", ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-path/$env{ID_PATH}-port$env{.ID_PORT}"
IMPORT{builtin}="usb_id"
ENV{ID_SERIAL}=="", GOTO="serial_end"
SUBSYSTEMS=="usb", ENV{ID_USB_INTERFACE_NUM}="$attr{bInterfaceNumber}"
ENV{ID_USB_INTERFACE_NUM}=="", GOTO="serial_end"
ENV{.ID_PORT}=="", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}"
ENV{.ID_PORT}=="?*", SYMLINK+="serial/by-id/$env{ID_BUS}-$env{ID_SERIAL}-if$env{ID_USB_INTERFACE_NUM}-port$env{.ID_PORT}"
LABEL="serial_end"
On peut toutefois directement appeler l’utilitaire udevadm
pour demander les informations du périphérique en question.
$ udevadm info --name /dev/ttyACM0 --query all
P: /devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.0/tty/ttyACM0
N: ttyACM0
L: 0
S: serial/by-path/pci-0000:00:14.0-usb-0:4:1.0
S: serial/by-id/usb-Devantech_Ltd._USB-RLY02._00008749-if00
E: DEVPATH=/devices/pci0000:00/0000:00:14.0/usb3/3-4/3-4:1.0/tty/ttyACM0
E: DEVNAME=/dev/ttyACM0
E: MAJOR=166
E: MINOR=0
E: SUBSYSTEM=tty
E: USEC_INITIALIZED=186532534818
E: ID_BUS=usb
E: ID_VENDOR_ID=04d8
E: ID_MODEL_ID=ffee
E: ID_PCI_CLASS_FROM_DATABASE=Serial bus controller
E: ID_PCI_SUBCLASS_FROM_DATABASE=USB controller
E: ID_PCI_INTERFACE_FROM_DATABASE=XHCI
E: ID_VENDOR_FROM_DATABASE=Microchip Technology, Inc.
E: ID_AUTOSUSPEND=1
E: ID_MODEL_FROM_DATABASE=Devantech USB-ISS
E: ID_VENDOR=Devantech_Ltd.
E: ID_VENDOR_ENC=Devantech\x20Ltd.
E: ID_MODEL=USB-RLY02.
E: ID_MODEL_ENC=USB-RLY02.
E: ID_REVISION=0100
E: ID_SERIAL=Devantech_Ltd._USB-RLY02._00008749
E: ID_SERIAL_SHORT=00008749
E: ID_TYPE=generic
E: ID_USB_INTERFACES=:020201:0a0000:
E: ID_USB_INTERFACE_NUM=00
E: ID_USB_DRIVER=cdc_acm
E: ID_USB_CLASS_FROM_DATABASE=Communications
E: ID_PATH=pci-0000:00:14.0-usb-0:4:1.0
E: ID_PATH_TAG=pci-0000_00_14_0-usb-0_4_1_0
E: ID_MM_CANDIDATE=1
E: DEVLINKS=/dev/serial/by-path/pci-0000:00:14.0-usb-0:4:1.0 /dev/serial/by-id/usb-Devantech_Ltd._USB-RLY02._00008749-if00
E: TAGS=:systemd:
E: CURRENT_TAGS=:systemd:
➡ La reconnaissance de ce périphérique est automatique sur cette machine, et on peut d’ores et déjà communiquer avec le relai via le chemin /dev/ttyACM0
.
Communication série
Les données de communication série transitent sous forme de séquences, dont les structures sont prédéterminées5.
Dans notre cas, le nombre de bits de données est de 8
, il n’y aura pas de bit de parité, et on terminera avec un stop bit à 1
. La vitesse de transmission est de 115200
bauds.
Spécifications logicielles
Voici une liste très biaisée et personnelle de ce que cette bibliothèque doit fournir comme fonctionnalités.
- Langage : pour développer, nous nous tournerons vers le langage Python.
- Communication série : les quatre fonctionnalités primordiales de communication série pour nous sont les suivantes : ouverture et fermeture de port série, transfert et réception de données. On souhaitera s’assurer de la bonne réception des commandes série au fur et à mesure.
- Pouvoir exécuter ces opérations dans un thread dédié.
- Commander les fonctionnalités de la bibliothèque de manière thread safe : que depuis un autre thread, l’on puisse envoyer des données et en récupérer.
- Proposer une configuration par défaut.
- Fournir une documentation.
- Tester un minimum.
Développement
Solutions
La bibliothèque PySerial6 offre des fonctionnalités d’interfaçage série. Les commandes de version et d’état nécessitent une lecture supplémentaire pour récupérer la réponse du micro contrôleur. Cela signifie un envoi de commande et une lecture de données à la suite. Pour s’assurer de la bonne activation des contacteurs, on peut vérifier que l’envoi vers le port série s’est bien passé en analysant le retour de la fonction write()
7. Et si nécessaire, vérifier l’état du contacteur en interrogeant le relai avec la commande d’état.
Exécuter les opérations dans un thread sera intégré par la bibliothèque Threading8 du langage Python, tandis que la communication thread safe sera assurée par des Queues de la bibliothèque du même nom9. On utilisera les docstrings 10 pour documenter le code avec Pdoc 11.
Résultat
Rappelons-nous qu’il s’agit d’un petit projet, l’objectif est bien d’avoir une stabilité minimale et de pouvoir facilement, d’ici quelques mois, se replonger dans le code. Les sources de cette bibliotèque ne seront pas proposées dans l’article, mais plutôt ici. La documentation est consultable une fois le dépôt téléchargé, ou à cet endroit.
Conclusion
Cette bibliothèque n’est pas en soi très complexe, mais cherche à respecter les étapes minimales de stabilité et de partage sur le long terme,… Pour pouvoir l’intégrer dans de futurs projets de domotique !
https://www.robot-electronics.co.uk/htm/usb_rly02tech.htm ↩︎
https://www.hcc-embedded.com/products/usb-overview/host-class-drivers/cdc-acm#:~:text=The%20CDC%2DACM%20class%20provides,as%20a%20remote%20serial%20port. ↩︎
https://pyserial.readthedocs.io/en/latest/pyserial_api.html ↩︎
https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.Serial.write ↩︎