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 valeur 102 à 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.

Commandes

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 udev3 — 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_adm4 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.

Documentation

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 !