TIA Lesen vom SQL Server: Byte Array -> DWord -> Real - Umwandeln aber wie?

tomlei

Level-2
Beiträge
137
Reaktionspunkte
11
Ich nutze die Bibliothek von Siemens LSQL zum Schreiben und Lesen von Daten aus einem SQL Server.
Die Verbindung habe ich hinbekommen und ich kann Daten schreiben und lesen.

Für das Lesen der Daten gibt es in der Bib den FB DeserializeRows. Dort gibt es Beispiele für das Lesen und Verarbeiten von Integer und String Variablen. Jetzt habe ich versucht dieses zu Erweitern um die Umwandlung von Real Daten (Float Zahlen positiv und negativ).

Vorhandener Code für Integer und String:

Code:
REGION --> use case deserializaition
            
            //IF - ELSIF case selection on the columName
            
            IF #columns[#tempColumnLoopCounter].columnName = 'entityName' THEN
                REGION example String
                    //move Bytes to temporary array
                    MOVE_BLK(IN := #tdsTelegramArray[#statByteAddressCounter],
                             COUNT := UDINT_TO_UINT(#tempValueLength),
                             OUT => #tempByteCharArray[0]);
                    
                    //convert BYTE array to STRING
                    #rows[#tempRowLoopCounter].entityName := "LSql_BytesToString"(columnType := #columns[#tempColumnLoopCounter].columnType, byteArray := #tempByteCharArray, numberOfBytes := UDINT_TO_UINT(#tempValueLength));
                END_REGION example String
              
               
            ELSIF #columns[#tempColumnLoopCounter].columnName = 'entityValueInteger' THEN
                REGION example INT
                    //merge next two BYTE to WORD and then convert to INT
                    #rows[#tempRowLoopCounter].entityValueInteger := WORD_TO_INT("LGF_MergeBytesToWord"(byte0 := #tdsTelegramArray[#statByteAddressCounter], byte1 := #tdsTelegramArray[#statByteAddressCounter + 1]));
                END_REGION example Int
                
                
            ELSIF #columns[#tempColumnLoopCounter].columnName = 'entityValueString' THEN
                REGION example String
                        //move Bytes to temporary array
                        MOVE_BLK(IN := #tdsTelegramArray[#statByteAddressCounter],
                                 COUNT := UDINT_TO_UINT(#tempValueLength),
                                 OUT => #tempByteCharArray[0]);
                        
                        //convert BYTE array to STRING
                        #rows[#tempRowLoopCounter].entityValueString := "LSql_BytesToString"(columnType := #columns[#tempColumnLoopCounter].columnType, byteArray := #tempByteCharArray, numberOfBytes := UDINT_TO_UINT(#tempValueLength));
                    END_REGION example String
                    
                    
              
            ELSE
              //column name is not handled by user program case
                #statWarning := TRUE;
              
              //check if there has not been any other major error yet
              IF (#statStatus = #ERR_NOERROR) THEN
                #statStatus := #ERR_COULMNS_COLUMNNOTEXISTING;
              END_IF;
              
            END_IF;
          END_REGION --> use case deserializaition



Den Code habe ich jetzt versucht damit zu ergänzen:

Code:
ELSIF #columns[#tempColumnLoopCounter].columnName = 'entityValueFloat' THEN
                REGION example REAL
                    //merge next two BYTE to DWORD and then convert to REAL
              
                    #rows[#tempRowLoopCounter].entityValueFloat := DWORD_TO_REAL("LGF_MergeBytesToDWord"(byte3:=#tdsTelegramArray[#statByteAddressCounter+3], byte2:=#tdsTelegramArray[#statByteAddressCounter + 2], byte1:=#tdsTelegramArray[#statByteAddressCounter + 1], byte0:=#tdsTelegramArray[#statByteAddressCounter]));
                    
                END_REGION example REAL

Allerdings bekomme ich da kein sinnvolles Ergebnis. Als DWord erhalte ich schon Ergebnisse, die nicht dem in der SQL Datenbank gespeicherten Float Wert entspricht. Also ich bekomme irgendwelche "sinnlosen" negativen Werte.

Testweise habe ich in einem anderen FC eine REAL Zahl in ein DWord und wieder zurück umgewandelt - ohne Probleme. Ich gehe momentan davon aus, dass das Problem schon bei der Umwandlung der Bytes vom Array zum DWord besteht. Aber wieso funktioniert die Umwandlung der Byte Daten des Arrays nach Integer und String?

Ich komme hier nicht weiter. Kann mir jemand einen Hinweis geben?
 
Ich habe da einen Verdacht - könntest du vielleicht mal 4 der Bytewerte posten, die nach deiner Ansicht zu einem REAL werden sollten und vielleicht auch welchen Wert du da erwartest ...?
 
Testweise habe ich in einem anderen FC eine REAL Zahl in ein DWord und wieder zurück umgewandelt - ohne Probleme.
Vielleicht ist das Problem, dass zwischen DWORD und REAL nicht konvertiert werden darf, sondern jeweils das 32-Bit Bitmuster unverändert in den Zieldatentyp übernommen werden muss. Falls beim Konvertieren das DWORD als Integer/Ganzzahl interpretiert wird, dann könnte eine ungewollte Konvertierung zuschlagen.
 
Ich habe da einen Verdacht - könntest du vielleicht mal 4 der Bytewerte posten, die nach deiner Ansicht zu einem REAL werden sollten und vielleicht auch welchen Wert du da erwartest ...?
Danke an Euch beide, dass Ihr Euch damit beschäftigt!

Ich habe den Code mit ein paar Testzeilen ergänzt und folgende Werte erhalten:

Code:
ELSIF #columns[#tempColumnLoopCounter].columnName = 'entityValueFloat' THEN
                REGION example REAL
                    //merge next two BYTE to DWORD and then convert to REAL
                    
                    "global_TestWert".ArrByte0 := #tdsTelegramArray[#statByteAddressCounter];
                    "global_TestWert".ArrByte1 := #tdsTelegramArray[#statByteAddressCounter+1];
                    "global_TestWert".ArrByte2 := #tdsTelegramArray[#statByteAddressCounter+2];
                    "global_TestWert".ArrByte3 := #tdsTelegramArray[#statByteAddressCounter+3];
                    "global_TestWert".DWord_of_MergedBytes := "LGF_MergeBytesToDWord"(
                                                                                      byte3 := "global_TestWert".ArrByte3,
                                                                                      byte2 := "global_TestWert".ArrByte2,
                                                                                      byte1 := "global_TestWert".ArrByte1,
                                                                                      byte0 := "global_TestWert".ArrByte0);
                    "global_TestWert".Real_of_DWord := DWORD_TO_REAL("global_TestWert".DWord_of_MergedBytes);
                    
                    #rows[#tempRowLoopCounter].entityValueFloat := DWORD_TO_REAL("LGF_MergeBytesToDWord"(byte3:=#tdsTelegramArray[#statByteAddressCounter+3], byte2:=#tdsTelegramArray[#statByteAddressCounter + 2], byte1:=#tdsTelegramArray[#statByteAddressCounter + 1], byte0:=#tdsTelegramArray[#statByteAddressCounter]));
                    
                END_REGION example REAL

Aus der DB wird der Wert 2,55 gelesen (siehe Testwert in der BT)

1718877671316.png

Ergibt das für Euch einen Sinn?
 
Vielleicht ist das Problem, dass zwischen DWORD und REAL nicht konvertiert werden darf, sondern jeweils das 32-Bit Bitmuster unverändert in den Zieldatentyp übernommen werden muss. Falls beim Konvertieren das DWORD als Integer/Ganzzahl interpretiert wird, dann könnte eine ungewollte Konvertierung zuschlagen.
Kannst Du mir das etwas mehr erklären? Ich bin etwas überfordert...
 
Noch eine Zusatzfrage:

Hier wird die #TDS_COLUMN_SIZEFIELDSIZE abgefragt. Sollte die Länge bei Real nicht 4 sein (byte0-byte3)?
Mir wird die Länge 1 für dieses Feld aber angezeigt. siehe unten...

Code:
//iteration over rows of the row array
    FOR #tempRowLoopCounter := (#tempTokenRowsLowerBound + UDINT_TO_DINT(#statRowsDeserialized)) TO #tempRowLoopCounterUpper BY 1
    DO
      //check if the array place will be occupied by a received row
      IF #tempRowLoopCounter - #tempTokenRowsLowerBound < #rowCount THEN
        
        //each row token is started by one byte '0xd1' identifying it as a TokenType
        #statByteAddressCounter += 1;
        
        //iteration over all columns given from ColumnMetaData
        FOR #tempColumnLoopCounter := 0 TO #columnCount - 1 BY 1
        DO
          //datatypes TEXT and NTEXT bring additional Data: 'Data Textptr Len', 'Data Textptr' and 'Data Text timestamp'
          IF (#columns[#tempColumnLoopCounter].columnType = #TDS_TOKENROW_COLUMNTYPE_TEXT OR #columns[#tempColumnLoopCounter].columnType = #TDS_TOKENROW_COLUMNTYPE_NTEXT) THEN
            #tempRowDataTextptrLen := BYTE_TO_USINT(#tdsTelegramArray[#statByteAddressCounter]);
            
            #tempRowDataTextTotalLength := 1 + #tempRowDataTextptrLen + #TDS_TOKENROW_DATATEXTTIMESTAMP_LENGTH;
            #statByteAddressCounter += UDINT_TO_DINT(#tempRowDataTextTotalLength);
          ELSE
            #tempRowDataTextTotalLength := 0;
          END_IF;
          
          //check if there is a field for the column size
          IF (#columns[#tempColumnLoopCounter].columnSizeFieldSize > 0) THEN
            
            #tempValueLength := 0;
            
            //read out the actual length
            CASE #columns[#tempColumnLoopCounter].columnSizeFieldSize OF
              #TDS_COLUMN_SIZEFIELDSIZE_1:
                #tempValueLength := DWORD_TO_UDINT("LGF_MergeBytesToDWord"(byte0 := #tdsTelegramArray[#statByteAddressCounter], byte1 := #BYTE_EMPTY, byte2 := #BYTE_EMPTY, byte3 := #BYTE_EMPTY));
              #TDS_COLUMN_SIZEFIELDSIZE_2:
                #tempValueLength := DWORD_TO_UDINT("LGF_MergeBytesToDWord"(byte0 := #tdsTelegramArray[#statByteAddressCounter], byte1 := #tdsTelegramArray[#statByteAddressCounter + 1], byte2 := #BYTE_EMPTY, byte3 := #BYTE_EMPTY));
              #TDS_COLUMN_SIZEFIELDSIZE_4:
                #tempValueLength := DWORD_TO_UDINT("LGF_MergeBytesToDWord"(byte0 := #tdsTelegramArray[#statByteAddressCounter], byte1 := #tdsTelegramArray[#statByteAddressCounter + 1], byte2 := #tdsTelegramArray[#statByteAddressCounter + 2], byte3 := #tdsTelegramArray[#statByteAddressCounter + 3]));
              ELSE
                #tempValueLength := #columns[#tempColumnLoopCounter].columnSize;
            END_CASE;
            
            IF (#tempValueLength < 0 OR #tempValueLength > #columns[#tempColumnLoopCounter].columnSize) THEN
              #tempValueLength := 0;
            END_IF;
            
          ELSE
            #tempValueLength := #columns[#tempColumnLoopCounter].columnSize;
          END_IF;
          
          //increment address counter
          #statByteAddressCounter += #columns[#tempColumnLoopCounter].columnSizeFieldSize;


1718878965048.png

Bei columns[2] (Feld mit dem Float Wert) steht bei columnsSizeFieldSize aber 1 und nicht 4 ...?



Weitere Zusatzfrage:
Kann es sein, dass hier der Dezimaltrenner ein Problem darstellt?
Im SQL Server ist es das Komma und bei Siemens ist es der Punkt.
 
HEUREKA!!

Nachdem ich mich einen ganzen Tag mit der Bibliothek von Siemens beschäftigt habe und ich dabei versucht habe herauszufinden, an welcher Stelle der Implementierung dieser Bibliothek ich zu blöd war, habe ich mich mit dem SQL Server beschäftigt und herausgefunden, dass ich zu blöd war die Tabellenstruktur richtig zu definieren...

Ich hatte den Datentyp Float genutzt, weil ich das von VBA so gewöhnt war. Nachdem ich den Datentyp zu REAL geändert hatte, durfte ich feststellen, dass ich 8h sinnlos Zeit vergeudet habe.

Jetzt funktioniert auch die Umwandlung einwandfrei. Danke an alle, die sich mit meinem Thema befasst haben!
 
Hallo,
ich hole dieses Thema einmal wieder hoch....

Wir müssen aus einer SQL Tabelle ein paar Variablen des Typs NUMERIK (10,3) auslesen und nach REAL wandeln.
Wie geht das ?? String und INT bzw. REAL funktionieren. Aber ich komme nicht darauf, wie ich den Wert den ich auslese, interpretieren soll.
der Wert in der SQL-Tabelle hat den Wert 100.000, lesend in TIA bekomme ich einen negativen REAL-Wert (-1,xxx E38)..

Kann da einer helfen ??
 
Numeric scheint ein Festpunkt- Datentyp zu sein (wenn ich es auf die schnelle richtig verstanden habe bei numeric(10,3): 7 Stellen vor dem Komma und 3 dahinter). Am einfachsten könnte sein, wenn ihr mit dem Genauigkeitsverlust zurechtkommt einfach den Server die Umwandlung vornehmen zu lassen und mit einem CAST die Werte als Real abzufragen.
 
Hallo,

- leider dürfen wir keine Änderungen auf Seite SQL-Server vornehmen...
- ich habe schon alle möglichen byte-Reihenfolgen gesetzt (0.1.2.3, 3.2.1.0, 1.0.3.2, usw.)

bin gleich wieder auf der Baustelle, dann mache ich noch ein paar Bilder und weitere Versuche!
 
Hallo,

- leider dürfen wir keine Änderungen auf Seite SQL-Server vornehmen...
- ich habe schon alle möglichen byte-Reihenfolgen gesetzt (0.1.2.3, 3.2.1.0, 1.0.3.2, usw.)

bin gleich wieder auf der Baustelle, dann mache ich noch ein paar Bilder und weitere Versuche!
Das wäre ja keine Änderung auf der Serverseite sondern nur eine andere Abfrage
 
Bei Microsoft gibt es die Dokumentation zu dem Datentyp:

[MS-TDS]: Tabular Data Stream Protocol | Microsoft Learn

In Kapitel 2.2.5.5.1.6 (Seite 43) ist der Aufbau erklärt.

Kurz auf einer SQL Server 2022 Express Instanz getestet (Numeric(10,3)) passt das:
1723797596752.png
Bei 10.100 speichert der Server dann 10100, das entspricht 0x2774, aber eben mit anderer Endianness und ein Byte für das Vorzeichen davor.
 
Hallo,
Nachdem ich mir jetzt die Zeit für das Problem genommen habe, ist die Lösung relativ simpel. Word einlesen Bereich byte 1+2, word to dint, dint ro real….., das ganze durch 1000 -> fertig!

Dankeschön für eure Zeit !
 

Anhänge

  • IMG_0738.jpeg
    IMG_0738.jpeg
    1 MB · Aufrufe: 16
Zurück
Oben