UDP3305S Daten Recording

Posted on Do 04 Januar 2024 in Computer & Electronics

In einem früheren Post hatte ich mein neues Labornetzteil vorgestellt. Kürzlich bin ich durch einen Post im EEVblog Forum auf eine (neue?) undokumentierte Funktion aufmerksam geworden: Unter Utility > Recorder kann man offenbar automatisch aufzeichnen, was das Netzteil so treibt:

Das klingt praktisch. Leider ist das aber im Handbuch nicht beschrieben und das *.REC File, das ich am Ende bekomme ist in einem undokumentierten Binärformat.

Im User-Interface des Netzteils selbst konnte ich auch keine Möglichkeit finden, die aufgezeichneten Werte irgendwie anzuzeigen oder in einem benutzbaren Format zu exportieren. Das ist schade, denn im Grunde ist das eine durchaus nützliche Funktion und ich würde mir wünschen, ich könnte ein CSV File bekommen, um die Daten extern auszuwerten.

Analyse

Aber eigentlich kann es doch nicht so irre schwer sein, das Format zu knacken und einen kleinen Konverter zu bauen, damit das nützlicher wird. Also schauen wir uns das mal näher an.

Grundstruktur

Als erstes mal ganz simpel: Kanal 1, 2 und 3 jeweils auf 5V und max. 1A stellen. Wir starten das recording (1/s), tun aber sonst nichts, d.h. alle Kanäle bleiben abgeschaltet. Das lassen wir ein paar Sekunden laufen und schauen uns das Ergebnis als Hexdump an:

00000000  10 06 54 00 68 00 69 00  73 00 20 00 69 00 73 00  |..T.h.i.s. .i.s.|
00000010  20 00 61 00 20 00 52 00  45 00 43 00 4f 00 52 00  | .a. .R.E.C.O.R.|
00000020  44 00 20 00 66 00 69 00  6c 00 65 00 2e 00 00 00  |D. .f.i.l.e.....|
00000030  00 00 00 00 00 00 00 00  18 12 16 20 ff ff ff ff  |........... ....|
00000040  01 00 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000070  00 00 00 00 00 00 00 00  18 12 04 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000a0  00 00 00 00 18 12 04 00  00 00 00 00 00 00 00 00  |................|
000000b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  18 12 04 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000f0  00 00 00 00 00 00 00 00  00 00 00 00 18 12 04 00  |................|
00000100  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000120  00 00 00 00 00 00 00 00  18 12 04 00 00 00 00 00  |................|
00000130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000140  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000150  00 00 00 00 18 12 04 00  00 00 00 00 00 00 00 00  |................|
00000160  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000170  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000180  18 12 04 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000190  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001a0  00 00 00 00 00 00 00 00  00 00 00 00 18 12 04 00  |................|
000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001d0  00 00 00 00 00 00 00 00  18 12 04 00              |............|

Wie es scheint, beginnt das File mit einem Header von 80 Bytes, der uns mitteilt, dass es ein *.rec File ist und dann folgen hintereinander die aufgezeichneten Datensätze. Jeder davon scheint 44 Bytes zu umfassen. Außerdem fällt auf, dass entweder zu Beginn oder am Ende jedes Datensatzes immer wieder 18 12 04 00 steht. Das könnte z.B. eine Art Magic Number sein, die einen neuen Record einleitet. Diese Sequenz ist das letzte bevor das File endet. Also markiert es offenbar das Ende, nicht den Anfang eines Records. Könnte dann z.B. eine Prüfsumme enthalten oder sowas.

Zudem fällt auf, dass da im Header viel mehr Bytes sind, als nötig. Teils mit 0xff Padding. Und auch dort gibt es eine 18 12 Sequenz. Hm – vielleicht stehen da globale Daten drin? Z.B. die Logging Periode? In den einzelnen Datensätzen stehen ja ganz offensichtlich keine Timestamps oder sowas drin. Also ausprobieren: wir setzten die Logging-Periode auf 1s, 10s, 50000s und 99999s. Und so sehen dann die Header aus:

# 1s
00000000  10 06 54 00 68 00 69 00  73 00 20 00 69 00 73 00  |..T.h.i.s. .i.s.|
00000010  20 00 61 00 20 00 52 00  45 00 43 00 4f 00 52 00  | .a. .R.E.C.O.R.|
00000020  44 00 20 00 66 00 69 00  6c 00 65 00 2e 00 00 00  |D. .f.i.l.e.....|
00000030  00 00 00 00 00 00 00 00  18 12 16 20 ff ff ff ff  |........... ....|
00000040  01 00 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

# 10s
00000000  10 06 54 00 68 00 69 00  73 00 20 00 69 00 73 00  |..T.h.i.s. .i.s.|
00000010  20 00 61 00 20 00 52 00  45 00 43 00 4f 00 52 00  | .a. .R.E.C.O.R.|
00000020  44 00 20 00 66 00 69 00  6c 00 65 00 2e 00 00 00  |D. .f.i.l.e.....|
00000030  00 00 00 00 00 00 00 00  18 12 16 20 ff ff ff ff  |........... ....|
00000040  0a 00 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

# 50000s
00000000  10 06 54 00 68 00 69 00  73 00 20 00 69 00 73 00  |..T.h.i.s. .i.s.|
00000010  20 00 61 00 20 00 52 00  45 00 43 00 4f 00 52 00  | .a. .R.E.C.O.R.|
00000020  44 00 20 00 66 00 69 00  6c 00 65 00 2e 00 00 00  |D. .f.i.l.e.....|
00000030  00 00 00 00 00 00 00 00  18 12 16 20 ff ff ff ff  |........... ....|
00000040  50 c3 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |P...............|

# 99999s
00000000  10 06 54 00 68 00 69 00  73 00 20 00 69 00 73 00  |..T.h.i.s. .i.s.|
00000010  20 00 61 00 20 00 52 00  45 00 43 00 4f 00 52 00  | .a. .R.E.C.O.R.|
00000020  44 00 20 00 66 00 69 00  6c 00 65 00 2e 00 00 00  |D. .f.i.l.e.....|
00000030  00 00 00 00 00 00 00 00  18 12 16 20 ff ff ff ff  |........... ....|
00000040  9f 86 01 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

Treffer: die Logging Period steht an Adresse 0x40 und umfasst 4 Bytes (=32 Bits). Das Speicherformat ist Little Endian:

0x00000001 = 1
0x0000000a = 10
0x0000c350 = 50000
0x0001869f = 99999

Um die weitere Analyse einfacher zu machen, verwenden wir ab jetzt ein kleines Python Skript, das wir schrittweise weiterentwicklen. Als erstes soll es mal den Header einlesen und dann so lange 44-Byte records konsumieren, bis wir am Ende angekommen sind. Und wo wir schon dabei sind, geben wir auch gleich die Logging Period aus:

def main(infile, headlen=80, reclen=44):
    header = infile.read(headlen)
    logperiod = int.from_bytes(header[0x40:0x44], byteorder='little')
    print("Logging period =", logperiod, "s")
    i = 0
    while(True):
        rec = infile.read(44)
        if len(rec) == 0:
            break
        print(f"{i:6}  ", " ".join([f"{i:02x}" for i in rec]))
        i += 1

Kanaldaten

Als nächstes machen wir eine neue Aufzeichnung und aktivieren die drei Kanäle der Reihe nach. Der Readout der Kanäle beträgt meist nicht exakt 5.000V, sondern:

  • Ch1: 5.001 V
  • Ch2: 5.000 V
  • Ch3: 4.999 V

Und so schaut nun unser Output des Eigenbau-Decoders aus:

❯ ./decode.py foo.REC
Logging period = 1 s
     0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 04 00
     1   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 04 00
     2   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 04 00
     3   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 04 00
     4   5a c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 0c 00
     5   5a c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 0c 00
     6   5a c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 0c 00
     7   5a c3 00 00 00 00 00 00 3a 7f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 17 00
     8   5a c3 00 00 00 00 00 00 50 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 12 00
     9   5a c3 00 00 00 00 00 00 50 c3 00 00 00 00 00 00 46 c3 00 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 1b 00
    10   5a c3 00 00 00 00 00 00 50 c3 00 00 00 00 00 00 46 c3 00 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 1b 00
    11   5a c3 00 00 00 00 00 00 50 c3 00 00 00 00 00 00 46 c3 00 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 1b 00

Cool – also scheint jeder Kanal je 8 Byte zu bekommen. Dabei wäre es naheliegend, wenn es je 4 für Spannung und Strom wären. Und man darf vermuten, dass wir wieder einen Little-Endian 32-Bit Int vor uns haben:

  • 0x0000c35a = 50010
  • 0x0000c350 = 50000
  • 0x0000c346 = 49990

Also dürfte das die Readout-Spannung sein, nur dass man noch durch 10000 teilen muss, um Volt zu bekommen.

Nun schließen wir zusätzlich einen 12Ω Widerstand an, damit auch wirklich Strom fließt und zeichnen auch das auf:

Logging period = 1 s
     0   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 04 00
     1   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 04 00
     2   5a c3 00 00 4a 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 10 00
     3   5a c3 00 00 4a 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 10 00
     4   5a c3 00 00 4a 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 10 00
     5   5a c3 00 00 4a 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 12 10 00

Perfekt: 0x0000104a = 4170. Und genau das (0.417A) hatte ich am Display abgelesen. Also bauen wir das gleich in unser Programm ein.

Ein wenig weiteres Experimentieren ergab, dass wir es insgesamt mit 5 "Kanälen" zu tun haben, die je 8 Byte bekommen:

  1. Kanal 1
  2. Kanal 2
  3. Kanal 3
  4. Serial (Ch1 & Ch2)
  5. Parallel (Ch 1 & Ch2)

Somit bleiben nur noch die 4 Byte am Ende zu klären – und evtl. noch weitere globale Info im Header, falls sie existiert.

Implementierung

Dieses Wissen habe ich nun in mein Skript einfließen lassen und das Ganze auf Github gestellt.

Fazit

Warum UNI-T sich dazu entschieden hat den Recorder in diesem Binärformat zu implementieren ist mir schleierhaft. CSV wäre viel sinnvoller. Aber zum Glück war das Format nicht komplex und so kann ich mein Skript verwenden, um mit den aufgezeichneten Daten zu arbeiten.

Parallel habe ich auch an UNI-T geschrieben und nach Dokumentation des Formats gefragt, aber reverse Engineering war schneller. Auch habe ich dem Support gesagt, dass CSV viel besser wäre. Vielleicht kommt das ja irgendwann mit einem Firmware-Update und mein Skript wird obsolet.