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:
- Kanal 1
- Kanal 2
- Kanal 3
- Serial (Ch1 & Ch2)
- 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.