ITSkillsCenter
Intelligence Artificielle

Lire et écrire des registres Modbus TCP en Python avec pymodbus

12 min de lecture
📍 Guide principal de la série : Robotique industrielle open source : ROS 2, Modbus, OPC-UA et automates. Ce tutoriel s’attaque au protocole le plus répandu de l’industrie.

Derrière la quasi-totalité des automates, variateurs, capteurs et compteurs d’énergie de la planète se cache le même protocole, vieux de plus de quarante ans et toujours roi : Modbus. Savoir lire et écrire ses registres, c’est tenir la clé d’accès à la majorité des équipements industriels. Dans ce tutoriel, vous allez monter un automate simulé qui parle Modbus, puis écrire le programme de supervision qui l’interroge et le commande — exactement ce que fait un système de contrôle réel.

🎯 Ce que vous allez apprendre

  • Comprendre les quatre espaces de données de Modbus et le piège classique de l’adressage.
  • Lancer un serveur Modbus TCP de test sur votre machine, sans matériel.
  • Lire des registres et des bits depuis un client Python avec pymodbus.
  • Écrire des consignes et basculer des sorties à distance.
  • Reconstituer une grandeur sur 32 bits répartie sur deux registres.

🛠️ Ce que vous allez construire

Deux programmes qui dialoguent. Le premier joue le rôle d’un automate : un serveur Modbus TCP dont la mémoire contient une température mesurée, une consigne réglable et l’état d’un moteur. Le second est votre superviseur : un client qui lit la température, écrit une nouvelle consigne et démarre le moteur à distance. C’est la boucle fondamentale de toute supervision industrielle, reproduite en quelques dizaines de lignes.

Prérequis

  • Python 3.10 ou plus récent, et l’outil pip.
  • Des bases de Python (fonctions, listes). Test express : si vous savez écrire un script qui boucle et affiche des valeurs, vous êtes prêt.
  • Aucun matériel : tout tourne en local.
  • ⏱️ Temps estimé : 40 minutes.

Comprendre l’adressage Modbus avant de coder

Modbus organise les données en quatre espaces. Les coils sont des bits en lecture/écriture (sorties tout-ou-rien, comme « moteur en marche »). Les discrete inputs sont des bits en lecture seule (entrées). Les input registers sont des mots de 16 bits en lecture seule (mesures). Les holding registers sont des mots de 16 bits en lecture/écriture (consignes et paramètres). C’est l’espace le plus utilisé.

Le piège qui fait perdre des heures à tout débutant : la différence entre l’adressage « humain » de la documentation (qui commence souvent à 40001 pour le premier holding register) et l’adressage « protocole » réellement envoyé sur le réseau (qui commence à 0). Quand un manuel dit « la consigne est au registre 40011 », l’adresse à passer à votre programme est en réalité 10. Gardez cette règle en tête : l’adresse sur le fil démarre à zéro, et le préfixe 4xxxx n’est qu’une convention de lecture.

Étape 1 — Installer pymodbus

pymodbus est la bibliothèque Python de référence pour Modbus, à la fois côté client et côté serveur. On l’installe dans un environnement virtuel pour ne pas polluer le système.

python3 -m venv ~/modbus_env
source ~/modbus_env/bin/activate
pip install pymodbus

L’activation de l’environnement modifie votre invite de commande (un préfixe (modbus_env) apparaît). Vérifiez la version installée, car l’interface a évolué au fil des versions : pip show pymodbus. Ce tutoriel cible la série 3 (au moment d’écrire, la 3.13). Si vous utilisez une version 3.8 ou antérieure, retenez que l’argument device_id des appels s’appelait alors slave — le seul changement à connaître.

Étape 2 — Démarrer l’automate simulé (le serveur)

Plutôt que d’acheter un automate pour apprendre, on en simule un. pymodbus permet de créer un serveur dont la mémoire est un simple tableau de valeurs. Créez automate.py :

from pymodbus.server import StartTcpServer
from pymodbus.datastore import (
    ModbusSequentialDataBlock,
    ModbusDeviceContext,
    ModbusServerContext,
)

# Valeurs initiales des holding registers : on prépare 100 cases à zéro,
# puis on fixe la température (index 0) et la consigne (index 10).
hr_init = [0] * 100
hr_init[0] = 235    # holding register 0 = temperature x10 -> 23,5 C
hr_init[10] = 250   # holding register 10 = consigne x10  -> 25,0 C

# Mémoire de l'automate : 100 emplacements par espace de données.
memoire = ModbusDeviceContext(
    co=ModbusSequentialDataBlock(0, [False] * 100),  # coils (sorties bit)
    di=ModbusSequentialDataBlock(0, [False] * 100),  # entrées bit
    hr=ModbusSequentialDataBlock(0, hr_init),        # holding registers
    ir=ModbusSequentialDataBlock(0, [0] * 100),      # input registers
)

contexte = ModbusServerContext(devices=memoire, single=True)

print("Automate Modbus en ecoute sur 127.0.0.1:5020")
StartTcpServer(context=contexte, address=("127.0.0.1", 5020))

Décortiquons. On crée une mémoire à quatre espaces, chacun de 100 cases. Les valeurs initiales sont déposées directement dans le bloc de holding registers : on stocke la température en dixièmes de degré (235 pour 23,5 °C) car un registre Modbus ne contient qu’un entier — une astuce universelle pour transporter des décimales. La classe ModbusDeviceContext représente la mémoire d’un équipement (dans les versions plus anciennes de pymodbus, elle s’appelait ModbusSlaveContext, et le serveur attendait l’argument slaves= au lieu de devices= — à adapter selon votre version). Enfin, StartTcpServer démarre l’écoute sur le port 5020 (on évite le port standard 502 qui exige les droits administrateur). Lancez ce programme : python automate.py. Il affiche son message et reste en écoute. Laissez-le tourner dans son terminal.

Étape 3 — Lire la mesure depuis le superviseur (le client)

Dans un second terminal (avec l’environnement virtuel activé), on écrit le superviseur. Première mission : se connecter et lire la température. Créez superviseur.py :

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("127.0.0.1", port=5020)
client.connect()

# Lire 1 holding register à l'adresse 0 (la température).
reponse = client.read_holding_registers(0, count=1, device_id=1)
if reponse.isError():
    print("Erreur de lecture :", reponse)
else:
    temperature = reponse.registers[0] / 10.0
    print("Temperature mesuree :", temperature, "C")

client.close()

On crée un client pointant vers l’adresse et le port du serveur, on se connecte, puis on lit un holding register à l’adresse 0 avec read_holding_registers. L’argument count dit combien de registres lire, device_id identifie l’esclave (ici ignoré car le serveur est en mode unique). On vérifie systématiquement isError() : une lecture peut échouer pour mille raisons réseau, et un programme robuste le contrôle toujours. La valeur brute est dans reponse.registers ; on la divise par dix pour retrouver les degrés. Lancez python superviseur.py : il doit afficher « Temperature mesuree : 23.5 C ».

Point d’étape — Si le superviseur lit 23,5 °C, la connexion Modbus fonctionne de bout en bout. Si vous obtenez une erreur de connexion, vérifiez que l’automate tourne bien et que le port (5020) est identique des deux côtés.

Étape 4 — Écrire une consigne et commander une sortie

Lire ne suffit pas : un superviseur agit. On va modifier la consigne (un holding register) et démarrer le moteur (un coil). Complétez superviseur.py avant le client.close() :

# Écrire une nouvelle consigne : 26,0 C -> 260 dans le registre 10.
client.write_register(10, 260, device_id=1)
print("Consigne mise a jour.")

# Démarrer le moteur : mettre le coil 0 à vrai.
client.write_coil(0, True, device_id=1)

# Relire le coil pour confirmer.
etat = client.read_coils(0, count=1, device_id=1)
print("Moteur en marche :", etat.bits[0])

write_register écrit une valeur unique dans un holding register : on y dépose 260 pour une consigne de 26 °C. write_coil bascule un bit de sortie : True démarre le moteur. On relit ensuite le coil avec read_coils pour confirmer — les bits se trouvent dans l’attribut bits, pas registers. Relancez le superviseur : il met à jour la consigne et affiche « Moteur en marche : True ». Vous venez de commander un équipement à distance par Modbus.

Étape 5 — Reconstituer une grandeur sur 32 bits

Un registre Modbus ne contient que 16 bits, soit des entiers jusqu’à 65535. Pour transporter une grandeur plus grande (un compteur d’énergie, une mesure flottante précise), les équipements la répartissent sur deux registres consécutifs. Le superviseur doit alors les recombiner. Voici comment lire un entier 32 bits stocké aux registres 20 et 21 :

paire = client.read_holding_registers(20, count=2, device_id=1)
if not paire.isError():
    haut, bas = paire.registers[0], paire.registers[1]
    valeur_32 = (haut << 16) | bas   # combine les deux mots de 16 bits
    print("Valeur 32 bits :", valeur_32)

On lit deux registres d’un coup, puis on assemble : le premier registre fournit les 16 bits de poids fort (décalés de 16 positions vers la gauche), le second les 16 bits de poids faible, combinés par un OU binaire. Attention : l’ordre des deux mots dépend du fabricant (certains placent le poids fort en premier, d’autres l’inverse). En cas de valeur aberrante, essayez d’inverser haut et bas — c’est la cause numéro un des mesures fantaisistes en Modbus.

Point d’étape final — Vous savez maintenant lire des mesures, écrire des consignes, commander des sorties et décoder des grandeurs sur 32 bits. C’est l’essentiel du vocabulaire Modbus dont a besoin une supervision.

Comprendre les codes de fonction

Sous les méthodes commodes de pymodbus se cachent les codes de fonction du protocole, qu’il est utile de connaître pour lire une documentation d’équipement ou diagnostiquer un échange. Chaque opération Modbus correspond à un numéro normalisé : le code 1 lit des coils, le code 2 lit des entrées discrètes, le code 3 lit des holding registers, le code 4 lit des input registers, le code 5 écrit un coil unique, le code 6 écrit un registre unique, le code 15 écrit plusieurs coils d’un coup et le code 16 écrit plusieurs registres. Quand vous appelez read_holding_registers, pymodbus envoie en réalité une trame portant le code de fonction 3.

Pourquoi s’en soucier ? Parce que les fiches techniques des équipements industriels raisonnent dans ce langage. Un manuel dira « la consigne de vitesse s’écrit au registre 4xxxx, fonction 16 » : vous savez maintenant traduire en un appel write_registers (au pluriel) à la bonne adresse. De même, comprendre qu’un équipement n’accepte que la lecture sur certains espaces (input registers, fonction 4) évite de chercher en vain à y écrire. Le protocole impose aussi des limites : on ne peut lire qu’environ 125 registres ou 2000 bits en une seule requête. Au-delà, il faut découper en plusieurs lectures — une contrainte que pymodbus ne masque pas toujours.

Enfin, quand une requête échoue côté équipement, Modbus renvoie une réponse d’exception avec un code qui précise la cause : fonction non supportée, adresse illégale, valeur illégale, défaillance de l’équipement. C’est exactement ce que détecte isError(), et inspecter l’objet d’erreur renvoyé indique souvent la nature exacte du problème. Lire ces codes, c’est passer du tâtonnement au diagnostic méthodique — la marque d’un intégrateur qui sait ce qu’il fait.

🐞 Pièges fréquents

Symptôme / erreur Cause probable Correctif
Connection refused Le serveur n’est pas lancé ou le port diffère Démarrer l’automate, aligner le port des deux côtés
Valeur lue décalée d’une case Confusion adressage 40001 (humain) / 0 (protocole) Soustraire l’offset : 40011 dans la doc → adresse 10
Mesure 32 bits aberrante Ordre des deux registres inversé Échanger poids fort et poids faible
AttributeError sur l’argument Version pymodbus avec slave au lieu de device_id Adapter le nom de l’argument à votre version
Permission denied sur le port 502 Le port 502 exige les droits administrateur Utiliser un port haut comme 5020 en développement

✅ Récapitulatif

Vous avez monté un automate simulé qui parle Modbus TCP, puis écrit un superviseur qui lit sa température, modifie sa consigne, démarre son moteur et décode une grandeur sur 32 bits. Vous maîtrisez maintenant les quatre espaces de données, le piège de l’adressage et la vérification d’erreur — le socle de toute intégration Modbus. C’est précisément ce protocole qu’exposera l’automate ouvert d’un tutoriel ultérieur, et que la passerelle finale reliera à ROS 2.

🧾 Aide-mémoire

Appel Rôle
read_holding_registers(adr, count=n, device_id=1) Lire n registres 16 bits R/W
read_input_registers(...) Lire des registres en lecture seule
write_register(adr, valeur, device_id=1) Écrire un registre
read_coils(adr, count=n, device_id=1) Lire des bits (attribut .bits)
write_coil(adr, True/False, device_id=1) Basculer une sortie bit
.isError() Tester l’échec d’une requête

💪 À vous de jouer

Transformez le superviseur en boucle de régulation simple : toutes les deux secondes, lisez la température ; si elle dépasse la consigne, coupez le moteur (coil à False), sinon démarrez-le. Pour aller plus loin, faites évoluer la température dans l’automate en fonction de l’état du moteur, afin d’obtenir un véritable système bouclé.

Voir la piste pour la boucle

Entourez la lecture et la décision d’un while True: avec un time.sleep(2). Lisez le holding register 0 (température) et le 10 (consigne), comparez-les, puis appelez write_coil(0, ...) selon le résultat. Vous obtenez un thermostat : la logique de base de l’automatisation, en Python.

Tutoriels frères

Pour aller plus loin

  • 🔝 Retour au guide principal : Robotique industrielle open source
  • Spécifications publiques du protocole : modbus.org. Documentation de la bibliothèque : pymodbus.readthedocs.io.

FAQ

Modbus TCP ou Modbus RTU : quelle différence ?
RTU circule sur une liaison série (RS-485), TCP sur un réseau Ethernet/IP. La logique des registres est identique ; seul le transport change. pymodbus gère les deux ; on a choisi TCP car il ne demande aucun matériel série.

Pourquoi vérifier isError() à chaque appel ?
Parce qu’une requête Modbus traverse un réseau et un équipement qui peuvent défaillir : équipement absent, adresse invalide, délai dépassé. Sans contrôle, votre programme exploiterait une réponse vide et planterait plus loin, là où la cause est difficile à trouver.

Comment sécuriser du Modbus ?
Modbus n’a ni authentification ni chiffrement : on ne le sécurise pas en lui-même, on l’isole. Le réseau qui le porte doit être segmenté et protégé par un pare-feu, et l’on ne franchit ses frontières qu’avec un protocole sécurisé comme OPC-UA. Ne jamais exposer un port Modbus sur Internet.

Puis-je interroger plusieurs équipements ?
Oui. Chaque équipement a un identifiant (device_id) ; un même client peut interroger plusieurs esclaves en variant cet argument, ou ouvrir plusieurs connexions vers des adresses différentes.

Partager
Service ITSkillsCenter

Application mobile Android et iOS

Création d'application mobile Android et iOS. À partir de 350 000 FCFA.

Démarrer mon projet
Publicité