MODBUS TCP - Write Multiple bits - Error

Bonsoir,
Je fais des essais avec MODBUS TCP et je crois qu’il y a une erreur au niveau de la commande MODBUS Write Multiple Bits.
Cette erreur a déjà été corrigée pour MODBUS RTU mais elle subsiste dans MODBUS TCP.
Je dois envoyer la commande suivante.

function code 15 (write multiple digital output register)
register address 0000 (starting register address)
register number 0008 (write 8 registers - 8 bits ou I/O pour IPX)
byte of data = 01 (1 byte = 8bits)
write 1 byte = 0X55 (valeur binaire a écrire 10101010)

Je crée un objet MODBUS

Port 5022 pour pouvoir envoyer la trame sur un programme python qui fait office de gateway ( récupère les données de l’IPX, les affiche, les renvoi au prérif MODBUS à une autre adresse IP Port 502, reçoit la réponse du périf MODBUS et la renvoi à l’IPX)
En bref ce programme me permet d’observer les trames et éventuellement de les modifier le cas échéant (je peux par exemple changer l’ID du périphérique MODBUS en fonction du port demandé sur l’IPX 5022 → ID2 - 5023 - ID3 etc …)

Résultat (print):
5022 port demandé sur l’IPX:
Trame reçue de l’IPX (j’isole la trame MODBUS): ff 0f 0000 0001 01 55 - PAS DE CRC EN TCP -
Analyse:
ID ff (´forcée pour TCP MODBUS sur IPX pour l’instant) OK
function 0f code 15 (write multiple bits) OK
register address 0000 (starting register address) OK
register number 0001 devrait être 0008 (write 8 registers - 8 bits ou I/O pour IPX) PAS OK
byte of data = 01 (1 byte = 8bits) OK
write 1 byte = 0X55 (valeur binaire a écrire 10101010) OK

Réponse du périf MODBUS: ff 0f 0000 0001
Analyse
ff: ID 255
0F: code 15 (write multiple bits)
0000: starting register address
0001: The number bits written 1 (qui devrait être 8)

En conclusion, il y a sans doute un problème dans la trame « register number » envoyé par l’IPX est 0001 (write 1 bit) quand il devrait être 8 (write 8 bits).
Bonne soirée

1 « J'aime »

Bonjour denisB,

une fois de plus super boulot d’analyse sur le ModBus :wink:

J’ouvre un ticket.

Bonne journée

1 « J'aime »

Bonjour grocrabe,
Merci beaucoup pour ton soutien, je compte beaucoup sur les fonctions MODBUS de l’IPXV5 pour le pilotage de la domotique en relation avec mon installation photovoltaïque.
Bonne journée
:grinning:

Bonjour @denisB ,

Oui super analyse!
Quel programme python utilisez-vous ?

Bonjour Jweb,
Ci-joint le programme (patchwork de code disponible sur le net que j’ai assemblé et modifié)
Attention, je l’utilise exclusivement pour « debugger » reste à implementer les protections nécessaires pour éviter un arrêt brutal en cas d’erreur (try: except: etc)
.

import threading
import socket
import socketserver

HOST = '192.168.1.200'  # Adresse du serveur interface TCP/IP -> RS485 
PORT = 502        # The port used by the server

# handler des tâches de reception de données
class Datahandler(socketserver.BaseRequestHandler):
    def handle(self):
        # Create a TCP/IP socket
        self.data = self.request.recv(2048)
        (host, port) = self.server.server_address           # on récupère l'adresse et port de la tâche qui active le "DataHandler"
        print("{} port demandé sur l'IPX:".format(port))
        print("ID correspondant: {} ".format(port-5020))     # ID modbus en fontion du port demandé par l'IPX 
                                                            # 5022 -> ID 2 / 5023 -> ID 3 / 5028 -> ID8 (pour l'instant 3 périfs MODBUS)
                                                            # derrière une interface ZLAN (T)



        print("Données reçues de l'IPX:",self.data.hex()) # example a1ee00000008 ff0f00000001019f (une entête suivit de la trame MOBUS)
        tmpdata = list(self.data)
        tmpdata[6] = port - 5020   # modif de l'ID MODBUS FF en fonction du port de port demandé par l'IPX (5022)
        data = bytes(tmpdata)
        print("Données reçues de l'IPX après modif ID:",data.hex()) # affichage du resultat a1ee00000008 020f00000001019f
                                                                    # sur cet example on a remplacé FF par 02 
                                                                    # ce qui nous permet d0'adresser un périf MODBUS d'ID 02 en TCP

        if self.data:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # on se connecte sur la passerelle TCP -> RS485
                s.connect((HOST, PORT))
                print("Envoi des données sur: ",HOST," : ", PORT) # envoi des données en provenance de l'IPX avec l'ID MODBUS modifié le cas échéant
                s.sendall(data)
                data2 = s.recv(1024)
                print("Données reçues du périf MODBUS:",data2.hex()) # réception et affichage des données recues via la passerelle
                s.close()
                tmpdata = list(data2)
                tmpdata[6] = 0xff                                    # on remet l'ID FF sinon sl'IPX retourne une erreur
                data2 = bytes(tmpdata)
                print("Données renvoyées à l'IPX:",data2.hex())      # on affiche la trame avant de la renvoyer à l'IPX
                self.request.sendall(data2)

# séquence complète d'affichage 
# 5022 port demandé sur l'IPX:
# ID correspondant: 2
# Données reçues de l'IPX: 860700000008ff0f000000010155
# Données reçues de l'IPX après modif ID: 860700000008020f000000010155
# Envoi des données sur:  192.168.1.200  :  502
# Données reçues du périf MODBUS: 860700000006020f00000001
# Données renvoyées à l'IPX: 860700000006ff0f00000001



class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


def main():
    global TDC_servers
    global TDC_server_threads

    TDC_servers = []
    TDC_server_threads =[]

    # Création de 3 tâches qui permettent de recevoir les données envoyées par l'IPX
    # respectivement sur les ports 5022 5023 5028 (possibilité d'ajouter des ports mais attention à la charge CPU)
    # adresse IP en fonction de la machine hôte 
    TDC_servers.append(ThreadedTCPServer(('192.168.1.122', 5022), Datahandler))
    TDC_servers.append(ThreadedTCPServer(('192.168.1.122', 5023), Datahandler))
    TDC_servers.append(ThreadedTCPServer(('192.168.1.122', 5028), Datahandler))

    for TDC_server in TDC_servers:
        TDC_server_threads.append(threading.Thread(target=TDC_server.serve_forever))

    for TDC_server_thread in TDC_server_threads:
        TDC_server_thread.setDaemon(True)
        TDC_server_thread.start()

    while True:
        continue


if __name__ == '__main__':
    try:
        main()


    finally:
        print('quitting servers')

        for TDC_server in TDC_servers:
            TDC_server.server_close()
4 « J'aime »