Prise en compte FC23 (Read/Write Multiple Registers)

Bonjour,

j’ai préparé quelque chose, voilà une procédure pour tester sachant que ça risque fortement de ne pas fonctionner puisque le script n’a jamais été testé.

1. Créer le script

Dans Réglages / Système / Editeur de fichiers, créer un fichier dans le répertoire tests avec cette icone :
image

Renommer ce fichier fc23.py
Double cliquer sur ce fichier et coller le script suivant :

#!/usr/bin/env python3

import logging
import argparse

from pymodbus import pymodbus_apply_logging_config

from pymodbus.client import ModbusSerialClient
from pymodbus.exceptions import ModbusException
from pymodbus.pdu import ExceptionResponse
from pymodbus import FramerType


pymodbus_apply_logging_config(logging.ERROR)
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)s %(message)s')
_logger = logging.getLogger(__file__)


def parse_args():
    parser = argparse.ArgumentParser(description="Script de test de la fonctionnalité de lecture/écriture avec FC23")
    parser.add_argument("--port", type=str, default="/dev/serial0", help="Interface série à utiliser")
    parser.add_argument("--address", type=int, default=1, help="Device ID. Numéro de l'esclave")
    parser.add_argument("--idb", type=str, default="0217C1", help="Bus ID IDB (18 bit dans 3 octets)")
    parser.add_argument("--idal", type=str, default="0E", help="Bus ID IDAL (5 bit dans 1 octet)")
    parser.add_argument("--tabNr", type=int, default=0, help="Tab number")
    parser.add_argument("--index", type=int, default=1, help="Index")
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    _logger.info("### Début du programme")

    assert 0 <= args.address <= 247, "L'adresse doit être entre 0 et 247"  
    assert len(args.idb) == 6, "IDB doit être long de 6 caractères pour représenter 3 octets"
    assert len(args.idal) == 2, "IDAL doit être long de 2 caractères pour représenter 1 octet"
    assert 0 <= args.tabNr <= 4, "Tab number doit être entre 0 et 4"
    assert 0 <= args.index <= 255, "Index doit être entre 0 et 255"

    # Construction des registres à envoyer
    registers = idx2reg(args.idal, args.idb)
    registers.append(args.tabNr)
    registers.append(args.index)
    
    # valeur à utiliser d'une manière ou d'une autre
    value = 0
    write_value = ModbusSerialClient.convert_to_registers(value, ModbusSerialClient.DATATYPE.FLOAT32)
    registers.extend(write_value) # valeur à écrire

    _logger.info("### Connexion au client")
    client = ModbusSerialClient(
        port=args.port,
        parity="E",
        framer=FramerType.RTU,
        timeout=5,
    )
    client.connect()
    _logger.info("### Client connecté")
    
    error = False

    try:
        # write_address => 0: read, 1: write d'après la documentation
        rr = client.readwrite_registers(read_address=0, read_count=0, write_address=0, values=registers, slave=args.address)
    except ModbusException as exc:
        _logger.error(f"Exception Modbus : {exc!s}")
        error = True
    if not error and rr.isError():
        _logger.error(f"Erreur")
        error = True
    if not error and isinstance(rr, ExceptionResponse):
        _logger.error(f"Response exception : {rr!s}")
        error = True
    
    if not error:
        _logger.info(f"Réponse: {rr.registers}")
        value = client.convert_from_registers(rr.registers[-4], client.DATATYPE.FLOAT32)
        _logger.info(f"Valeur lue: {value}")
    else:
        _logger.error("Une erreur est survenue")

    client.close()
    _logger.info("### Fin du programme")

def idx2reg(idal, idb) -> list[int]:
    registers = bytearray()
    registers.extend(bytes.fromhex(idal))
    registers.extend(bytes.fromhex(idb))
    ret = []
    for i in range(0, len(registers), 2):
        ret.append(int.from_bytes(registers[i:i+2], byteorder='big', signed=False))
    return ret

if __name__ == "__main__":
    main()

Cliquer sur le bouton Sauvegarder & Fermer :

2. Préparer le terrain

Ouvrir une session terminal (ssh ou console locale sur la VM) et se placer dans le répertoire tests et rendre le script exécutable :

cd ~www-data/html/tests
chmod +x fc23.py

2.1. Installer pymodbus

Pour éviter de perturber le système avec d’éventuelles dépendances supplémentaires à gérer on va faire ça dans un venv isolé. Dans le shell :

python3 -m venv modbus

Cette commande va créer un venv nommé modbus. Dans l’éditeurs de fichiers, si tu réactualises l’affichage, tu verras qu’un répertoire modbus est créé.
Pour activer ce venv :

source modbus/bin/activate

Maintenant on peut installer pymodbus et les dépendances pour les liaisons série dont tu as besoin dans le venv actif :

pip install pymodbus[serial]

3. Exécuter le script

Le script prend les paramètres suivants :

  • port : le port série à utiliser /dev/ttyUSB1 par exemple
  • address : device ID tel que défini dans la documentation chapitre 3 : 1 par exemple
  • idb : la valeur idb telle que définie dans la doc (en héxadécimal mais sans 0x devant) mais sur 2 caractères impérativement : 0E par exemple (pour reprendre l’exemple de la doc)
  • idal : la valeur idal telle que définie dans la doc (en héxadécimal mais sans 0x devant) mais sur 6 caractères impérativement : 0217C1 par exemple (pour reprendre l’exemple de la doc)
  • tabNr : la valeur tabNr telle que définie dans la doc : 0 pour monitoring
  • index : la valeur index telle que définie dans la doc : 1 pour reprendre l’exemple de la doc

Test avec la commande suivante. C’est à toi de renseigner les bons paramètres :

./fc23.py --port /dev/ttyUSB1 --address 1 --idb 0E --idal 0217C1 --tabNr 0 --index 1

Et à partir de là je ne peux plus tester chez moi, à toi de me dire ce qu’il se passe.

Si tout va bien tu vas avoir des log de retour dont une ligne avec Valeur lue : xxx. Si tu as ça du premier coup, je serai très étonné, un script ne fonctionne jamais du premier coup.

→ A toi de jouer !