Daten aus einem ASCII-Array of Byte zerlegen

kors

Level-1
Beiträge
7
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Community,

Zunaechst, ich arbeite mit e!cockpit, also Codesys 3.5.
ich habe folgendes Anliegen/Problem.
Ich mache eine Abfrage eines Smartmeters und bekomme als Antwort ein ASCII Array [irgendwas mit 150 aufwaerts] of Byte zurueck.
Zur Visualisierung uebersetze ich das Array in einen zusammenhaengenden Text wie folgt:

Code:
FUNCTION_BLOCK Buffer_Uebersetzen
VAR_INPUT
    Buffer: ARRAY [0..3000] OF BYTE;
    ByteCount: UDINT;
END_VAR
VAR_OUTPUT
    sText: STRING(255);
END_VAR
VAR
    i: INT;
    
    sBuffer: STRING;
    ByteCount_INT: INT;
END_VAR
Code:
ByteCount_INT := UDINT_TO_INT(ByteCount);
sText := '';

FOR i := 0 TO ByteCount_INT DO
    sBuffer := WagoSysString.ASCIIByte_To_String(b:= Buffer[i]);            // Wandelt den ASCII-Byte-Array zu einzelnen Strings um
    sText := CONCAT(String_1:=sText , String_2:=sBuffer );            // Setzt die einzelnen Strings zu einem String zusammen (Concat-Befehl)    
END_FOR

Dabei kommt dann etwas nach dem Schema raus.
Code:
{"status":"ok","result":[{"time":1524146846301,"energy":2787704584452000,"power":-22614000,"power1":-7422000,"power2":-8088600,"power3":-7103400,"energyOut":553326052714800}]}
Wenn ich jetzt die einzelnen Leistungen der Phasen haben will, kann ich mit dem "Find-Befehl" diese aus dem Ergebnis raussuchen.

Soweit so gut.

Wenn ich jetzt jedoch eine Abfrage ueber einen laengeren Zeitraum machen will, dann wird das ASCII Array laenger und die Ausgabe von time bis energyOut wiederholt sich n-fach. Da klappt das nicht mehr so schoen mit dem "Find", weil wenn er z.B. das erste "time" gefunden hat, nicht die folgenden ausgelesen werden koennen.
Jetzt koennte ich mit dem "Right-Befehl" natuerlich immer nach dem "energyOut" abschneiden und dann wirder neu suchen.

Was ich aber eleganter faende, waere das direkt in dem FB zu machen. Dabei wuerde ich die Daten gerne in einem Array speichern, was eine laenge von 7 hat (fuer time, energy, power,...).
Ich hab mir sowas vorgestellt wie:
Code:
VAR
Buffer: ARRAY [0..3000] OF BYTE;
DatenArray: ARRAY [0..6] OF INT;
ArrayCounter: INT;
END_VAR
Code:
FOR i := 0 TO ByteCount_INT DO
IF Buffer[i] := eineZahl THEN
DatenArray[ArrayCounter] := DatenArray[ArrayCounter] * 10 + Buffer[i];
ELSE ArrayCounter = ArrayCounter +1;
END_IF
END_FOR
Dabei soll der ArrayCounter immer nach einer Nicht-Zahlenfolge hochgesetzt werden.
Leider funktioniert das mit dem IF nicht so, da man nur BOOL's oder STRING's verwenden kann und ich nicht (wenn man das wieder in STRING's umwandelt) fuer die Zahlen 0-9 jeweils ein IF oder ELSIF machen will. Ein "Case-Befehl" ist auch nicht optimal (es muessen INT sein), da man die ASCII-Byte in STRING's konvertieren muss und Buchstaben dann ebenfalls "0" ergeben.

WÃaee fuer Ansaetze oder Tipp's zur Loesung sehr dankbar.
Und Tschuldigung fuer den kleinen Abendroman :)

MfG

kors
 
Zuletzt bearbeitet:
Wenn ich jetzt jedoch eine Abfrage ueber einen laengeren Zeitraum machen will, dann wird das ASCII Array laenger und die Ausgabe von time bis energyOut wiederholt sich n-fach.

Ich kenne dieses EnergieMeter nicht ... aber Frage : wie fragst du das denn ab, dass dein Array "immer länger" wird ?
Bzw. kann man dem Ding nicht mitteilen, dass man seine aktuellen Daten schon hat (und es seinen Puffer löschen soll) ?

Gruß
Larry
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich kenne dieses EnergieMeter nicht ... aber Frage : wie fragst du das denn ab, dass dein Array "immer länger" wird ?
Der Smartmeter ist von Discovergy. Die Daten bekomme ich mit einer https-Post Abfrage, da er an einem anderen Standort ist. Die API-Dokumentation ist hier: https://my.discovergy.com/json/Api/help
(Ist die alte API da die neue noch nicht funktioniert)
Da benutze ich die "Api.getRawWithPower" Abfrage. Um die Daten zu bekommen muss man die Unix-Zeit bzw. ein Intervall angeben, zwischen denen man die Werte bekommen will. Ich hätte gerne möglichst aktuelle Werte. Wenn ich dabei die aktuelle Zeit angebe und die Zeit vor 2 Sekunden (Smartmeter gibt Daten in 2 Sekunden Intervall aus) bekomme ich
Code:
{"status":"ok","result":[{"time":1524146846301,"energy":2787704584452000,"power":-22614000,"power1":-7422000,"power2":-8088600,"power3":-7103400,"energyOut":553326052714800}]}
raus.
Da es jedoch Latenz-Probleme gibt, kommt oft die Ausgabe
Code:
{"status":"ok","result":[]}
. Das kommt auch vor wenn ich die Abfrage vom Intervall Zeit 1 von vor 2 Sekunden und Zeit 2 von vor 4 Sekunden mache. Wenn ich das Intervall jedoch größer mache, tritt der Fehler nicht mehr auf.
Bzw. kann man dem Ding nicht mitteilen, dass man seine aktuellen Daten schon hat (und es seinen Puffer löschen soll) ?
Ich mache die Abfrage zu einem Intervall und bekomme dann die Daten zurück. Dabei sind leider die "ältesten" Daten links und die "aktuellsten" Daten rechts (wenn man den STRING betrachtet). Was ich auf jeden Fall brauche sind die aktuellen Daten da ich diese weiterverarbeiten will. Ideal wäre es wenn ich jedes Datenpaket (von "time" bis "energyOut") in ein Array of INT packen könnte und die dann in einen Datalogger einfügen kann.
Aber am wichtigsten sind die Daten ganz "rechts" vom String bzw. am Ende vom Array of Byte.

Ich hoffe du weißt was ich meine und kannst weiter helfen.

MfG
kors
 
... ich habe jetzt zumindestens teilweise dein Problem verstanden ... 8)

Was mir noch nicht so ganz klar ist :
Du fragst einen Zeitraum ab und erhältst als Antwort einen String, der die Informationen des Zeitraums beinhaltet - verstanden !
Nun willst du ja irgendetwas mit den Daten anfangen - dabei muß aber m.E. berücksichtigt werden, ob du von den gelieferten Daten schon ganz oder teilweise "Notiz genommen" hast. Sonst könnte man ja einfach einen FindRevers programmieren und nur den letzten Datensatz des Strings lesen. Was aber ist, wenn in dem String 2 oder 3 Zeitstempel von dir noch nicht verwertet worden sind ?

Prinzipiell ist das, was du da machen willst, etwas, dass deine SPS schon ganz schön beschäftigen kann. Bis du sicher, dass das in der SPS richtig aufgehoben ist ?

Gruß
Larry
 
Nun willst du ja irgendetwas mit den Daten anfangen - dabei muß aber m.E. berücksichtigt werden, ob du von den gelieferten Daten schon ganz oder teilweise "Notiz genommen" hast.
Nur nochmal zum Verständnis. Ich bekomme als Antwort von der https-Abfrage einen ASCII-Array of Byte, wo bereits alle Daten vorhanden sind. Das Intervall gebe ich bereits bei der https-Abfrage an. Also wenn ich ein Intervall von Sekunden eingebe, dann bekomme ich 5 mal den Datenblock von "time" bis "energyOut". Diese Daten sind alle im Array of Byte. Den Array konvertiere ich dann zu dem String, wobei er (wie oben im FB zu sehen ist) das Array einzelnt abfährt und die einzelnen char dann immer an den String hängt und diesen mit jedem Durchgang der FOR-Schleife aufbaut.
Da ist meine Überlegung vom ersten Post, dass ich die Daten da schon "sortiere" in einem (oder mehreren) anderen Array's, da ich das komplette Array of Byte sowieso schon einmal durchlaufe.
Was aber ist, wenn in dem String 2 oder 3 Zeitstempel von dir noch nicht verwertet worden sind ?
Wie gesat, die Daten werden alle auf einmal in dem Array of Byte angegeben. Da sind dann alle Daten in dem Zeitintervall vorhanden.
Sonst könnte man ja einfach einen FindRevers programmieren und nur den letzten Datensatz des Strings lesen
Sowas wäre auch möglich, nur weiß ich nicht wie lang der "letzte Datensatz" ist, da sich die Leistung der einzelnen Phasen ständig verändern.
Prinzipiell ist das, was du da machen willst, etwas, dass deine SPS schon ganz schön beschäftigen kann. Bis du sicher, dass das in der SPS richtig aufgehoben ist ?
Das wäre nicht so gut, wenn eine Abfrage die SPS schon zum rauchen bringt, da ich vorhab mehrere Smartmeter abzufragen. Hättest du einen anderen Ansatz die Daten zu verarbeiten? Der Array of Byte ist die Basis.

Vielen Dank schonmal für deine Hilfe!

MfG
kors
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Deine Latenz-Probleme kommen aus meiner Sicht nicht von der SPS (noch nicht) sondern von der Kommunikation - ich denke, aus dieser Nummer kommst du bei dem Ansatz nicht heraus. Die Frage ist aber, ob das wirklich schlimm ist wenn dir ja am Ende nichts verloren geht ...

Ich würde das Ganze durch einen PC (.Net-Applikation) erledigen lassen - gerade, wenn es dir um viele SmartMeter geht, die du abfragen willst.
Du hast dann einmal den Vorteil, dass du hier Speichertechnisch viel entspannter herangehen kannst (dynamische Speicherverwaltung) und es kann dir dann wirklich egal sein, wie lange die Auswertung dauert (wobei eine PC-Applikation mit Strings überhaupt keine Probleme hat - hier gibt es ganz andere Mechanismen).

Gruß
Larry
 
Eigentlich wuerde ich es schon gerne über die SPS machen, sammle jedoch gerade noch Erfahrung was die Kopplung mit Smartmeter angeht. Falls es bei mehreren Smartmeter dann groeßere Latenz-Probleme geben wird, ist das natuerlich auch nicht richtig zielfuehrend...
Ich vertraue da mal deiner Aussage.
Ich lasse mir das mal durch den Kopf gehen, wie ich das dann angehe, ob weiter mit der SPS oder ueber eine .Net-Applikation.

Danke dir auf jeden Fall für deine Hilfe!

MfG
kors
 
Code:
{"status":"ok","result":[{"time":1524146846301,"energy":2787704584452000,"power":-22614000,"power1":-7422000,"power2":-8088600,"power3":-7103400,"energyOut":553326052714800}]}
Moin kors!
Tüftele gerade daran, Deinen BeispielDatensatz (s.o.) per Excel-VBA zu zerlegen.
Hättest Du mal ein Beispiel für mich, in dem 2 oder mehr "DatenPakete" (Messungen zu mehreren Zeiten) enthalten sind? Damit ich mal sehen und testen kann, wann, was, wo, wie die "geschwiffenen" und die eckigen Klammern die Daten "zerteilen"?
Gruss, Heinileini
Power.jpg
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Heinileini

hier ein Beispiel mit 2 "Datenpaketen". Die Daten sind jeweils in den geschweiften Klammern mit einem Komma getrennt.

Code:
{"status":"ok","result":[{"time":1524665369470,"energy":7636641783000,"power":3690,"power1":3690,"power2":0,"power3":0,"energyOut":0},{"time":1524665371470,"energy":7636641804000,"power":3710,"power1":3710,"power2":0,"power3":0,"energyOut":0}]}

MfG
kors
 
Danke kors!

In Excel-VBA habe ich's jetzt, d.h. die Werte erscheinen im TabellenBlatt.
Muss mir noch mal den Thread ansehen, wie Deine Vorstellungen waren.
Habe noch keine Vorstellung, was wir mit dem "Vorspann" ("status ok result") machen - zumindest das ok wirst Du auswerten wollen.
Für den Rest schwebt mir ein 3-D string-array vor (für die Texte und die Zahlenwerte).
1. Index : 1 ... n - DatenPaketNr
2. Index : 0 ... 6 - für die 7 Messwerte eines Pakets
3. Index : 0 - Bezeichner (z.B. "power2"); 1 - ZahlenWert
alternativ:
1 x 2-D string-array für die Bezeichner (dieses array evtl. verzichtbar!?)
1 x 2-D numeric-array für die Zahlenwerte - welcher DatenTyp ist gefragt?
1. und 2. Index wie zuvor.

Gruss, Heinileini

PS:
Deine Zahlen sind aber ganz schön riesig! In den Beispielen bis zu 16 Stellen.
Mit wie vielen Stellen müssen wir maximal bei den Daten rechnen?
Und wieviele kann CoDeSys maximal (sorry, bin CoDeSys-Laie!)?
Typ Real hilft uns wohl auch nicht weiter?
 
Zuletzt bearbeitet:
Im VBA-Slang formuliert - muss noch ein CoDeSys-Experte was draus machen:
Code:
Dim a$(20, 6, 1)
' Idx1 : 0 - Vorspann (status)
'        1 bis n - DatenPaketNr
' Idx2 : 0 - time
'        1 - energy
'   ...
'        6 - energyOut
' Idx3 : 0 - Bezeichner (entbehrlich? ggfs nach PlausibilitätsPrüfung in die Tonne treten?)
'        1 - Zahlenwert (oder bei "status" ein Text z.B. ok)
'xTmp$ = "{"status":"ok","result":[{"time":1524665369470,"energy":7636641783000,"power":3690,"power1":3690,"power2":0,"power3":0,"energyOut":0},{"time":1524665371470,"energy":7636641804000,"power":3710,"power1":3710,"power2":0,"power3":0,"energyOut":0}]}"
xIdx1& = -1
For xi& = 1 To Len(xTmp$)
    xChr$ = Mid$(xTmp$, xi&, 1) ' <===<<< der Datensatz in xTmp$ - den es bei Dir nicht gibt! - wird hier in einzelne Bytes zerlegt
                                ' entspricht Deinem Auslesen von "Buffer[i]"
    xCas& = InStr("{}[]:," & Chr$(34), xChr$) ' hier werden bestimmte Zeichen in eine Zahl 1 ... 7 umgewandelt, 0 bei allen anderen Zeichen
    Select Case xCas&
    Case 1 ' " { " - - - - - geschweifte Klammer auf
        If xBez$ <> "" Then ' wenn Bezeichner, dann zuerst ausgeben
            If xIdx1& = 0 Then xIdx2& = xIdx2& + 1
            a$(xIdx1&, xIdx2&, 0) = xBez$
            xBez$ = ""
        ElseIf xDat$ <> "" Then ' wenn Zahlenwert, dann zuerst ausgeben
            a$(xIdx1&, xIdx2&, 1) = xDat$
            xDat$ = ""
            End If
        xIdx1& = xIdx1& + 1
        xIdx2& = -1
    Case 2 ' " } " - - - - - geschweifte Klammer zu
    Case 3 ' " [ " - - - - - eckige Klammer auf
        xIdx3& = 0
    Case 4 ' " ] " - - - - - eckige Klammer zu
    Case 5 ' " : " - - - - - DoppelPunkt
        xIdx3& = 1
    Case 6 ' " , " - - - - - Komma
        xIdx3& = 0
    Case 7 ' " " " - - - - - Gänsebeinchen werden ignoriert
    Case Else ' - - - - - sonstige Zeichen
        If xIdx3& Then ' - - - Bezeichner
            If xBez$ <> "" Then ' wenn Bezeichner, dann zuerst ausgeben
                xIdx2& = xIdx2& + 1
                a$(xIdx1&, xIdx2&, 0) = xBez$
                xBez$ = ""
                End If
            xDat$ = xDat$ & xChr$ ' "concat"
        Else ' - - - ZahlenWert (oder "ok" beim status)
            If xDat$ <> "" Then ' wenn ZahlenWert, dann zuerst ausgeben
                a$(xIdx1&, xIdx2&, 1) = xDat$
                xDat$ = ""
                End If
            xBez$ = xBez$ & xChr$ ' "concat"
            End If
    End Select
    Next xi&
If xBez$ <> "" Then ' wenn Bezeichner, dann zuerst ausgeben
    a$(xIdx1&, xIdx2&, 0) = xBez$
    xIdx2& = xIdx2& + 1
    xBez$ = ""
ElseIf xDat$ <> "" Then ' wenn ZahlenWert, dann zuerst ausgeben
    a$(xIdx1&, xIdx2&, 1) = xDat$
    xDat$ = ""
    End If


Array im LokalFenster nach Abarbeitung des 1. TestDatenSatzes:

LokalFenster-Test1-18426.jpg

Array im LokalFenster nach Abarbeitung des 2. TestDatenSatzes (wegen der Länge auf 2 Bilder aufgeteilt):

LokalFenster-Test2a-18426.jpg
LokalFenster-Test2b-18426.jpg

Wegen des "Abrauchens" der SPS würde ich mir keine Gedanken machen.
Notfalls kannst Du die For-Schleife so umkonstruieren, dass sie nicht mehr als solche in Erscheinung tritt, sondern in jedem Zykklus nur 1 Byte (Buffer) abgearbeitet wird.
Du hast viele Sekunden Zeit, während ein anderes Exemplar von Buffer bereits wieder mit dem nächsten DatenSatz "überflutet" werden könnte ;o)

Gruss, Heinileini

PS:
Deinen Satz ...
Ein "Case-Befehl" ist auch nicht optimal (es muessen INT sein), da man die ASCII-Byte in STRING's konvertieren muss und Buchstaben dann ebenfalls "0" ergeben.

... habe ich nicht so richtig verstanden. Er war aber der Grund dafür, dass ich die CASE-Selektion etwas unkonventionell aufgesplittet habe in
1. Umwandlung von 7 ("Sonder-")Zeichen in Zahlen per instr()
2. CASE-Abfragen von Zahlen
statt direkt die Zeichen in den CASE-Abfragen zu schreiben. In VBA hätte ich mir diesen "Umweg" ersparen können.
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
@Heinilein:
Da geht noch Einiges mehr - z.B. mit dem Befehl "Split", mit dem man zunächst die Datenblöcke zerlegen könnte und anschließend jeden Block in seine Einzel-Elemente.
Mein einer Schleife würde ich nur nach dem jeweiligen Split arbeiten und ein CASE entfällt dann aus meiner Sicht komplett.
Ich denke aber mal das Kors das im Grunde schon weis, da er seine Strings ja schon zerlegt bekommt.
Mit wäre bei einer Bearbeitung (bzw. Weiterverarbeitung) der Zeitstempel wichtig - hier würde ich versuchen daraus ein sinnvolles Date- oder DateTime-Objekt zu machen - das bringt dann möglicherweise noch weitere Benefits.

Gruß
Larry
 
@Larry:
Da geht noch Einiges mehr ...
[/QUOTE]
Da geht noch vieles mehr! Alle Wege führen noch Rom. Und zusätzlich noch die vielen Umwege ...
Ich hätte es für mich in VBA auch ganz anders gemacht und einige meiner eigenen "StandardFunktionen" dafür verwendet.
Da ich aber nicht nur von Tuten und Blasen keine Ahnung habe, sondern auch von CoDeSys, habe ich diesen Weg gewählt, der sich vermutlich relativ problemlos in CoDeSys umformulieren lässt. Ach, und von dem "Umfeld" der Aufgabenstellung habe ich auch ganz viel keine Ahnung.
[QUOTE]
... Mit wäre bei einer Bearbeitung (bzw. Weiterverarbeitung) der Zeitstempel wichtig - hier würde ich versuchen daraus ein sinnvolles Date- oder DateTime-Objekt zu machen - das bringt dann möglicherweise noch weitere Benefits.
Zeitstempel ist mit drin, alles im StringFormat und dennoch unlesbar. Die Ent-Unixung habe ich mir erspart ... vielleicht gibt's hier mal einen Thread, wo das gut (bene) hin passen (fit) würde.
Gruss, Heinileini

PS:
Dieser ForumsEditor war mal relativ gut zu beherrschen. Heute hat bzw. macht er Schwierigkeiten mit der [ QUOTE ]nRegelung ;o)
Wie sage ich dem Editor, dass das oben zwei (2) Quotes sind, zwischen denen ich meinen Senf eingegeben habe?

Ich kopiere den Quark mal in einen richtigen Editor und wieder zurück ...

@Larry:
Da geht noch Einiges mehr ...
Da geht noch vieles mehr! Alle Wege führen noch Rom. Und zusätzlich noch die vielen Umwege ...
Ich hätte es für mich in VBA auch ganz anders gemacht und einige meiner eigenen "StandardFunktionen" dafür verwendet.
Da ich aber nicht nur von Tuten und Blasen keine Ahnung habe, sondern auch von CoDeSys, habe ich diesen Weg gewählt, der sich vermutlich relativ problemlos in CoDeSys umformulieren lässt. Ach, und von dem "Umfeld" der Aufgabenstellung habe ich auch ganz viel keine Ahnung.
... Mit wäre bei einer Bearbeitung (bzw. Weiterverarbeitung) der Zeitstempel wichtig - hier würde ich versuchen daraus ein sinnvolles Date- oder DateTime-Objekt zu machen - das bringt dann möglicherweise noch weitere Benefits.
Zeitstempel ist mit drin, alles im StringFormat und dennoch unlesbar. Die Ent-Unixung habe ich mir erspart ... vielleicht gibt's hier mal einen Thread, wo das gut (bene) hin passen (fit) würde.

 
Zuletzt bearbeitet:
... wenn man weiß was er darstellt (und welches Format er hat - es wird dazu sicher eine Doku geben) dann wird er wohl lesbar sein ... denk z.B. mal an das Siemens-DT-Format - wenn du da nicht weißt, wie sich das aufschlüsselt, dann kannst du mit der Zahl, die daraus resultieren würde, auch nichts anfangen ...

Gruß
Larry
 
Zuviel Werbung?
-> Hier kostenlos registrieren
@Larry:
Wie bereits angedeutet: ich weiss aber von nix. Mit ohne Gewähr habe ich aus den TestDaten ...
2018-04-19 14:07:26
2018-04-25 14:09:29
2018-04-25 14:09:31
... gelesen.
Mit ...
Code:
If InStr(" unix date time ", " " & LCase$(xBez$) & " ") Then xDat$ = DateSerial(1970, 1, 1) + Left$(xDat$, Len(xDat$) - 3) / 86400

Gruss, Heinileini
 
Zuletzt bearbeitet:
VIelen Dank Heinileini!

Ich werde mir das mal in einer ruhigen Minute genauer anschauen und in Codesys übersetzen.

MfG
Kors
 
Zuletzt bearbeitet:
Hi,

da das JSON ist, könnte man auch eine der vielen JSON Bibliotheken nehmen: https://www.json.org/json-de.html

Das hilft aber erst einmal direkt unter Codesys nicht viel. Da aber mit e!Cockpit gearbeitet wird, kommt entweder ein PFC100 oder PFC200 von WAGO zum Einsatz.

Da gibt es die Möglichkeit direkt ein eigenes Modul im Linux Subsystem zu entwickeln und dann in Codesys einzubinden. Dann könnte man auch eine der Bibliotheken vom Link verwenden. Informationen findet man dazu bei Wago unter BSP bzw. Board Support Package

Gruß
 
Zurück
Oben