Open Protocol - socket buffer Problem..

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
Epics
User
Beiträge: 2
Registriert: Mittwoch 23. August 2017, 14:28
Wohnort: Austria

Mittwoch 23. August 2017, 19:04

Hallo zusammen,

ich arbeite zuzeit an der Kommunikation zwischen einen Schauber von Atlas Copco und Python. Die Aufgabe ist die Daten der Schraubvorgänge mittels Open Protocol in eine CSV zu exportieren.
Nun die Kommunikation steht soweit aber ich habe immer wieder das Problem dass sich die Nachrichten die ich mit recv() empfange, verschieben und es kann sein dass wenn ich das Programm neu starte noch alte Nachrichten ankommen.. (Auf den Bildern ist es sehr gut zu sehen)

So sollte es aussehen:
Bild
Hier nach dem zweiten mal ausführen:
Bild
Und hier nach dem dritten mal:
Bild

Nun der code.

main.py

Code: Alles auswählen

import OpenProtocol, dataExport, networking

op = OpenProtocol.opCon()

export = dataExport.dataExport()

sock = networking.opProtocol()

sock.IP = "192.168.1.136"
sock.PORT = 4545
sock.BUFFER = 1024
sock.connect(sock.IP,sock.PORT)
#a = sock.sendMID(op.message('Enable_tool'))
#print(a)
#b = sock.sendMID(op.message('Last_tightening_result_data_subscribe'))
#print(b)
#c = sock.recvData()
#print(c)
sock.close()
pass
OpenProtocol.py

Code: Alles auswählen

class opCon:
    MID = {'Communication_start': '0001',
           'Communication_start_acknowledge': '0002',
           'Communication_stop': '0003',
           'Command_error': '0004',
           'Command_accepted': '0005',
           'Generic_subscription': '0008',
           'Generic_unsubscribe': '0009',
           'Pset_ID_upload_request': '0010',
           'Pset_ID_upload_reply': '0011',
           'Pset_data_upload_request': '0012',
           'Pset_data_upload_reply': '0013',
           'Pset_selected_subscribe': '0014',
           'Pset_selected': '0015',
           'Pset_selected_acknowledge': '0016',
           'Pset_selected_unsubscribe': '0017',
           'Select_Parameter_set': '0018',
           'Set_Pset_batch_size': '0019',
           'Reset_Pset_batch_counter': '0020',
           'Job_ID_upload_request': '0030',
           'Job_ID_upload_reply': '0031',
           'Tool_data_upload_request': '0040',
           'Tool_data_upload_reply': '0041',
           'Disable_tool': '0042',
           'Enable_tool': '0043',
           'Vehicle_ID_Nr_download_request': '0050',
           'VIN_subscribe': '0051',
           'VIN': '0052',
           'VIN_acknowledge': '0053',
           'VIN_unsubscribe': '0054',
           'Last_tightening_result_data_subscribe': '0060',
           'Last_tightening_result_data': '0061',
           'Last_tightening_result_data_acknowledge': '0062',
           'Last_tightening_result_data_unsubscribe': "0063",
           'Old_tightening_result_upload_request': '0064',
           'Old_tightening_result_upload_reply': '0065',
           'Read_time_upload_request': '0080',
           'Read_time_upload_reply': '0081',
           'Set_time': '0082',
           'Result_traces_curve': '0900',
           'Result_traces_curve_plot_data': '0901',
           'Keep_Alive': '9999'}

    def message(self,mid, rev="   ", Nof=" ", stID="  ", spID="  ", seqNr="  ", msgPrt=" ", msgNr=" ", data="", NULL="\x00"):
        x = self.MID[mid]
        lng = str(len("0000" + x + rev + Nof + stID + spID + seqNr + msgPrt + msgNr + data))
        sd = lng.zfill(4) + x + rev + Nof + stID + spID + seqNr + msgPrt + msgNr + data + NULL
        return sd.encode()  # lng.zfill(4)+x+rev+Nof+stID+spID+seqNr+msgNr+data+NULL

    def decode(self,msg):

        decoded = msg#.decode()
        mid = decoded[4:8]
        if mid == self.MID['Last_tightening_result_data']:
            '''Decode MID 0061 REV 001'''
            torque_controller_name = "Torque Controller Name: "+decoded[32:57]
            job_id = "Job ID: "+decoded[86:88]
            parameter_set_id = "PSet ID: "+decoded[90:93]
            if decoded[107:108] == "1":
                tightening_status = "Tightening Status: "+"OK"
            elif decoded[107:108] == "0":
                tightening_status = "Tightening Status: "+"NOK"

            if decoded[110:111] == "0":
                torque_status = "Torque Status: LOW"
            elif decoded[110:111] == "1":
                torque_status = "Torque Status: OK"
            elif decoded[110:111] == "2":
                torque_status = "Torque Status: HIGH"

            if decoded[113:114] == "0":
                angle_status = "Angle Status: LOW"
            elif decoded[113:114] == "1":
                angle_status = "Angle Status: OK"
            elif decoded[113:114] == "2":
                angle_status = "Angle Status: HIGH"

            torque = "Torque: "+str(int(decoded[140:146])/100)+" Nm"
            angle = "Angle: "+decoded[169:174]+" deg"
            time_stamp = "Time Stamp: "+decoded[176:195]
            tightening_id = "Tightening ID: "+decoded[221:231]
            data = (torque_controller_name,job_id,parameter_set_id,tightening_status,torque_status,angle_status,torque,angle,time_stamp,tightening_id)
            '''
            0 Torque Controller Name
            1 Job ID
            2 Pset ID
            3 Tightening Status
            4 Torque Status
            5 Angle Status
            6 Torque
            7 Angle
            ....
            '''

        return data
networking.py

Code: Alles auswählen

import socket, OpenProtocol, time, threading

op = OpenProtocol.opCon()



class opProtocol:
    IP = None
    PORT = None
    BUFFER = None

    def connect(self, IP, PORT):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            self.s.connect((IP,PORT))
            self.s.send(op.message('Communication_start'))
        except:
            print("Something's wrong with %s:%s." % (IP,PORT))
        print("Socket Connected")
        try:
            response = self.s.recv(1024)

            print("Communication Start: %s" % (response))
            x = str(response[4:8])
            if x == op.MID['Communication_start_acknowledge']:
                print("dsdwqe")
            #if response[4:8] == op.MID['Communication_start_acknowledge']:
                #print("Connected to %s:%s" % (IP,PORT))
        except:
            print("Recieve Error %s:%s." % (IP, PORT))


    def sendMID(self,msg):
        try:
            self.s.send(msg)
        except:
            pass
        o = self.s.recv(1024)
        return o

    def recvData(self):
        try:
            recieve = self.s.recv(1024)
        except:
            print("Error Recieve %s" % (recieve))
        return recieve


    def keepAlive(self,INTERVAL=8):
        while True:
            try:
                self.s.send(op.message('Keep_Alive'))
                response_keepalive = self.s.recv(self.BUFFER)
                if response_keepalive == op.message('Keep_Alive'):
                    break
                else:
                    print("Keep Alive Error: %s" % (response_keepalive))
            except:
                print("Keep Alive Except Error: %s" % (response_keepalive))

            time.sleep(INTERVAL)



    def close(self):
        try:
            self.s.send(op.message('Communication_stop'))
            communication_stop = self.s.recv(1024)
            print("Communication Stop:  %s" % (communication_stop))
            #if communication_stop[4:8] == op.MID['Command_accepted']:
                #print("Connection Stopped")
            self.s.close()
            #print(communication_stop[4:8],op.MID['Command_accepted'])
        except:
            print("Error closing Socket: %s" % (communication_stop))

    t = threading.Thread(target=keepAlive)
dataExport.py

Code: Alles auswählen

import csv

class dataExport:
    file = 'export.csv'
    def writeData(self,data):
        fileObj = open(self.file,'a')
        csvWriter = csv.writer(fileObj)
        for row in [data]:
            csvWriter.writerow(row)
        fileObj.close()
'''
data = "test"
data1 = "dest"
with open("./export/export.csv", "a", newline='') as exportFile:
    exportFileWriter = csv.writer(exportFile)
    exportFileWriter.writerow([data, data1])
exportFile.close()
'''
__deets__
User
Beiträge: 3087
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mittwoch 23. August 2017, 19:15

Willkommen bei der Netzwerkprogrammierung. Bei der Anfänger denken, sockets würden Nachrichten verschicken, obwohl sie Ströme von Bytes ohne eingebaute Segmentierung darstellen :D

Du musst schon selbst dafür sorgen das du empfangene Daten aufteilst und an durch das Protokoll vorgegebenen Grenzen zerteilst.

Ich habe auf die Schelle nicht viel finden können wie das Protocol definiert ist. Aber https://www.sps-forum.de/simatic/31839- ... ernet.html suggeriert das du nach Verbindungsaufbau immer 4 Bytes lesen musst, und danach die in diesen Bytes definiert Datagrammlänge. Achtung! Dabei musst du auf endianess achten.
Sirius3
User
Beiträge: 8095
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 23. August 2017, 19:54

@Epics: in main.py Zeile 3: op wird nicht benutzt. Zeile 9-11: Komplett großgeschriebene Namen bezeichnen Konstanten, dass Du denen Werte zuweist, ist unüblich. Warum sind das überhaupt Klassenattribute? Und warum wandelst Du sie in Instanzattribute, und warum sind die überhaupt da, da Du IP und PORT ja der connect-Methode übergibst? Das sollten also wirkliche Instanzattribute (kleingeschrieben) werden, die Du beim Erzeugen der Instanz von opProtocol übergeben solltest, so dass connect gar keine Argumente mehr braucht.

In OpenProtocol.py Zeile 44ff: Du generierst zwei Mal einen ziemlich langen String, da kann es leicht zu Schreibfehlern kommen, mach das nur einmal. Was passiert, wenn eine der vielen Argumente nicht die erwartete Länge hat? Ist NULL wirklich ein Argument?

Code: Alles auswählen

    def message(self,mid, rev="   ", Nof=" ", stID="  ", spID="  ", seqNr="  ", msgPrt=" ", msgNr=" ", data=""):
        mid = self.MID[mid]
        length = 4 + 4 + 3 + 1 + 2 + 2 + 2 + 1 + 1 + len(data)
        msg = "{:04d}{:4.4s}{:3.3s}{:1.1s}{:2.2s}{:2.2s}{:2.2s}{:1.1s}{:1.1s}{}\0".format(
            length, mid, rev, Nof, stID, spID, seqNr, msgPrt, msgNr, data)
        return msg.encode()
In networking.py Zeile 3: globale Variablen vermeiden. Wenn op gebraucht wird, dann erzeuge es in __init__ von opProtocol oder übergebe es als Argument. Zeile 16: send sendet nicht unbedingt alle Bytes. Benutze sendall. Zeile 17: benutze niemals nackte excepts. Die Fehlerbehandlung ist sowieso unsinnig, weil der eigentliche Fehler überdeckt wird und das Programm nach der Fehlerbehandlung nicht mehr in einem sinnvollen Zustand ist. Zeile 21: recv empfängt von einem bis zu 1024 Zeichen. Das ist sicher nicht das, was Du möchtest. Zeile 29: siehe andere excepts. Zeile 26: noch schlimmer, da wird der Fehler ohne Meldung ignoriert. Zeile 77: ein Klassenattribut ist mit Sicherheit falsch. An dieser Stelle kann keepAlive gar nicht die richtigen Argumente bekommen, weil sie noch nicht existieren.
Antworten