Diamex Temp-Sensor-Tester für DS18B20 unter Ubuntu Linux 20.04

Hier mal nix mit Foto­gra­fie son­dern was ganz Spe­zi­el­les zum The­ma 1‑wire Tem­pe­ra­tur­sen­so­ren (DS18B20) und wie man die­se an PCs mit USB Schnitt­stel­le mit Hil­fe des Diamex Temp-Sen­sor Tes­ter unter Ubun­tu Linux 20.04 abfra­gen kann.

Was habe ich damit vor?

Hin­ter­grund ist, dass ich die Ser­ver­raum­tem­pe­ra­tu­ren bei uns in der Fir­ma mes­sen, pro­to­kol­lie­ren und über­wa­chen möch­te und ich dafür nicht unnö­tig viel Strom ver­brau­chen will. Dazu hat­te ich schon vor eini­gen Jah­ren erfolg­reich einen Raspber­ry Pi ver­wen­det, bei dem man 1‑wire Tem­pe­ra­tur­sen­so­ren (DS18B20) ein­fach per GPIO anschlie­ßen kann. Lei­der ging nach rela­tiv kur­zer Zeit die SD-Kar­te kaputt, es waren wohl zuviel Mess­da­ten bzw. Schreib­vor­gän­ge im Lau­fe der Zeit.
Danach habe ich es mit einem Bana­na­Pi ver­sucht, die­ser hat die Mög­lich­keit per SATA Schnitt­stel­le eine SSD als robus­te­ren Daten­spei­cher anzu­bin­den. Das ging auch eine län­ge­re Zeit gut, aber letz­tes Jahr hat sich dann die SATA Schnitt­stel­le ver­ab­schie­det, also wie­der nix dau­er­haf­tes.
Zuletzt habe ich ver­sucht mein Moni­to­ring-Sys­tem auf Basis eines strom­spa­ren­den MiniPC (Ter­ra Black Dwarf) auf­zu­bau­en. Lei­der hat die­ser eine x86 (i386) Archi­tek­tur und ich habe dann letzt­lich  auf­ge­ge­ben, da es eini­ge für mein Pro­jekt not­wen­di­ge Linux Soft­ware­pak­te (wie Graf­a­na) nicht fer­tig für die x86 Archi­tek­tur gab. Die Pake­te aus den Quel­len sel­ber zu bau­en und zukünf­tig zu pfle­gen war mit ein­fach zu viel Aufwand.

Soft­ware­ba­sis für mein Moni­to­ring-Sys­tem war stets ein Linux (Raspian/Debian) mit Nagios bzw. spä­ter Icinga2 (Icin­ga-Direc­tor) samt Gra­phi­te bzw. Graf­a­na, um die Daten gra­fisch dar­zu­stel­len. Für die ARM-Archi­tek­tur des Raspi bzw. Bana­na­pi habe ich die Pake­te noch irgend­wo­her gefun­den, lei­der bin ich beim x86 Sys­tem zuletzt dar­an ver­zei­felt, dass es kei­ne fer­ti­gen Pake­te für x86 von Graf­a­na gab.
Da fiel der Ent­schluß ein klei­nes x64 Sys­tem mit Ubun­tu Ser­ver 20.04 auf­zu­setz­ten und Nagios bzw. Graf­a­na dort aus den ver­füg­ba­ren Paket­re­po­si­to­ries zu instal­lie­ren. Das hat auch gut geklappt, nun muss­te ich aber noch eine Lösung fin­den, die 1‑wire Sen­so­ren an die­sen PC anzu­bin­den. Mei­ne Wahl fiel auf einen USB 1‑wire Adap­ter und ganz spe­zi­ell auf den Adap­ter von Diamex. Die­ser besteht aus einem klei­nen STM32F030, wel­cher die Sen­so­ren aus­liest und die Daten per USB HID Events zur Ver­fü­gung stellt. Soll­te also auch unter Linux kein Pro­blem sein, aber es war für mich dann doch etwas schwie­ri­ger bzw. ich habe kei­ne gute Anlei­tung dazu gefun­den und daher stel­le ich hier mei­ne Erfah­run­gen ins Netz und hof­fe, dass es ande­ren schnel­ler weiterhilft.

Der Diamex Adapter und seine Software

Diamex stellt zwei Down­loads zur Ver­fü­gung, ein­mal ein fer­ti­ges Win­dows Tool und dann ein soge­nann­tes „MSYS Paket“. Ers­te­res kann man unter Win­dows neh­men, um mal eben zu tes­ten, ob der Adap­ter samt ange­schlos­se­ner Sen­so­ren funk­tio­niert. Ging in mei­nem Fall pro­blem­los, somit wuss­te ich, dass Pro­ble­me unter Linux nur an der Soft­ware lie­gen können.

Das zwei­te Paket läuft auch unter Win­dows, am bes­ten den Ord­ner „msys“ ins Root von C: ent­pa­cken. Dann in den Unter­ord­ner „1.0“ wech­seln und „mysys.bat“ mit admi­nis­tra­ti­ven Rech­ten (!) star­ten. Man lan­det dann ein einer Bash, ja ich schrei­be bewusst Bash, da MSYS eine auf Cyg­win basie­ren­de Linux Umge­bung für Win­dows ist, spe­zi­ell zum com­pi­lie­ren von Anwen­dun­gen mit gcc. Der Quell­code für das Bei­spiel­pro­jekt liegt unter /ho­me/­temp-sen­sor. Dort fin­det man mit „main.c“ den eigent­lich Quell­code, eini­ge Datei­en, um HID Gerä­te unter Windows/Linux/MacOSX anspre­chen zu kön­nen, das „Make­file“ (brau­chen wir spä­ter unter Linux) und einen ver­steck­ten Ord­ner „.deps“. Hier schon mal ein ers­ter Tipp, wenn es spä­ter mal Pro­ble­me beim kom­pi­lie­ren gibt, die Datei­en in die­sem Ord­ner löschen („make clean“ macht das nicht immer 100%ig).

So, wenn wir in die­sem Ord­ner sind, als ers­tes ein „make clean“, um Res­te des letz­ten Builds zu ent­fer­nen, dann ein „make“ und es soll­te erfolg­reich eine Datei namens „TempCmd.exe“ erstellt werden:

$ make
gcc.exe -Wall -g -O2 -DOS_WINDOWS -D_WIN32 -fno-strict-aliasing -MD -MP -MF ./.deps/hid_WINDOWS.o.d -c hid_WINDOWS.c -o hid_WINDOWS.o
gcc.exe -Wall -g -O2 -DOS_WINDOWS -D_WIN32 -fno-strict-aliasing -MD -MP -MF ./.deps/main.o.d -c main.c -o main.o
gcc.exe -Wall -g -O2 -DOS_WINDOWS -D_WIN32 -fno-strict-aliasing -MD -MP -MF ./.deps/TempCmd.d hid_WINDOWS.o main.o -lhid -lsetupapi -o TempCmd
text data bss dec hex filename
5388 1580 244 7212 1c2c TempCmd.exe

Die­se Datei kann aus der Bash her­aus mit den Linux typi­schen Vor­an­stel­len von „./“ gestar­tet werden:

$ ./TempCmd.exe
No Temp-Sensor found

Da mein Adap­ter hier nicht ein­ge­steckt war, bekom­me ich die Mel­dung „No Temp-Sen­sor found“, aber man sieht, die exe läuft. Wenn der Adap­ter ange­schlos­sen ist, liest er die Sen­so­ren aus und schickt so ca. 1x pro Sekun­de die Mess­wer­te eines Sen­sors per HID Event raus. So bekommt man so nach und nach den Mess­wert von jedem Sen­sor und die klei­ne TempCmd.exe zeigt die­se ein­fach an. Für mich ist das okay, ich brau­che Mess­wer­te von zwei (oder mehr) Sen­so­ren nicht zeit­gleich. Per Druck auf „ESC“ lässt sich das Pro­gramm been­den. Auch „STRG+C“ geht!

Endlich: Wie läuft der Adapter unter Linux?

Okay, ich gehe davon aus, das Ubun­tu Ser­ver 20.04 x64 bereits instal­liert und die Anmel­dung an der Kon­so­le erfolgt ist. Ich wechs­le per „sudo bash“ bei sol­chen Din­gen ger­ne zum „root“ user, dann brem­sen mich erst­mal Rech­te­ein­schrän­kun­gen nicht aus. Man kann aber auch als Stan­dard­be­nut­zer ange­mel­det blei­ben und per vor­an­ge­stell­tem „sudo“ die Befeh­le abschi­cken, das bleibt jedem selbst über­las­sen.
Ich schrei­be die Befeh­le im Fol­gen­den stets ohne „sudo“!

Als ers­tes schau­en wir, ob der Adap­ter grund­sätz­lich erkannt wird. Dazu per „dmesg ‑w“ sich die Ker­nel­mel­dun­gen anzei­gen las­sen und dann den Diamex Adap­ter in einen USB Port ein­ste­cken. Es soll­ten nun Mel­dun­gen auf­tau­chen, wel­che auf ein USB Device mit idVendor=16c0 und idProduct=0480 hinweisen:

[ 959.564613] usb 2-2: new full-speed USB device number 3 using uhci_hcd
[ 959.751360] usb 2-2: New USB device found, idVendor=16c0, idProduct=0480, bcdDevice= 1.00
[ 959.751367] usb 2-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 959.751372] usb 2-2: Product: Temp-Sensor-Tester
[ 959.751376] usb 2-2: Manufacturer: DIAMEX GmbH
[ 959.761449] hid-generic 0003:16C0:0480.0003: hiddev0,hidraw2: USB HID v1.11 Device [DIAMEX GmbH Temp-Sensor-Tester] on usb-0000:00:10.0-2/input0

Kompilieren der Software

Als ers­tes muss der Quell­code auf den Linux Ser­ver. Dazu ein­fach das Ver­zeich­nis „temp-sen­sor“ aus dem ZIP-Archiv „msys.zip“ auf das Ubun­tu Sys­tem kopie­ren. Am bes­ten ins Ver­zeich­nis „/usr/src/1‑wire“, wel­ches wir vor­her erstel­len. Zum Kopie­ren kann z.B. das Tool Win­SCP ver­wen­det wer­den. Damit kommt man aber von Remo­te nor­ma­ler­wei­se nicht schrei­bend ins Ver­zeich­nis „/usr/src“ („root“ hat kein Recht per SSH zuzu­grei­fen), also am bes­ten alles nach „/home/<username>“ kopie­ren und dann auf dem Ubun­tu Ser­ver als user „root“ das Ver­zeich­nis nach „usr/src/1‑wire“ kopieren.

Auf dem Ubun­tu Ser­ver braucht man zum Kom­pi­lie­ren eini­ge Pake­te, die­se bekommt man zusam­men am ein­fachs­ten mit:

apt install build-essential

Zusätz­lich braucht man die Ent­wick­ler­da­tei­en für die Biblio­the­ken USB und Udev:

apt install libusb-dev
apt install libudev-dev

So, nun funk­tio­niert aber ein „make“ noch nicht! Man muss erst das „Make­file“ für Linux und spe­zi­ell für Ubun­tu 20.04 umbauen:

#--------------------------------------------------------------------------
OS = LINUX
#OS = WINDOWS
#--------------------------------------------------------------------------
DEP = ./.deps
#--------------------------------------------------------------------------
PROG = Read1Wire
#--------------------------------------------------------------------------
ifeq ($(OS), LINUX)
TPATH     = linux
TARGET    = $(TPATH)/$(PROG)
CC        = gcc
SIZE      = size
NM        = nm
STRIP     = strip
AFLAGS    = -DOS_$(OS)
CFLAGS    = -Wall -g -O2 -DOS_$(OS)
CFLAGS   += -fno-strict-aliasing
CFLAGS   += -MD -MP -MF $(DEP)/$(@F).d
LIBS      = `pkg-config libusb libudev --libs`
INCLUDES  = `pkg-config libusb --cflags`
else ifeq ($(OS), WINDOWS)
TPATH     = windows
TARGET    = $(TPATH)/$(PROG).exe
CC        = gcc.exe     #i586-mingw32msvc-gcc
STRIP     = strip.exe   #i586-mingw32msvc-strip
SIZE      = size.exe    #i586-mingw32msvc-size
NM        = nm.exe      #i586-mingw32msvc-nm
AFLAGS    = -DOS_$(OS)
CFLAGS    = -Wall -g -O2 -DOS_$(OS) -D_WIN32
CFLAGS   += -fno-strict-aliasing
CFLAGS   += -MD -MP -MF $(DEP)/$(@F).d
LIBS      = -lhid -lsetupapi 
INCLUDES  = 
endif

#--------------------------------------------------------------------------
REMOVE = rm -f
#--------------------------------------------------------------------------
COBJ      = hid_$(OS).o \
            main.o
#AOBJ      = data.o
#--------------------------------------------------------------------------
all: $(PROG)
#	cp $(PROG) $(TARGET)

$(PROG): $(COBJ) $(AOBJ)
	$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LIBS) -o $(PROG)
	@$(SIZE) -B $(PROG)
	@$(NM) -n $(PROG) > $(PROG).sym
	@$(STRIP) $(PROG)

$(PROG).exe: $(PROG)
	@cp $(PROG) $(PROG).exe
	
$(COBJ) : %.o: %.c
	$(CC) $(CFLAGS) -c $(INCLUDES) $< -o $@

$(AOBJ) : %.o : %.S
	$(CC) -c $(AFLAGS) $< -o $@

clean:
	$(REMOVE) *.o
	$(REMOVE) $(PROG)
	$(REMOVE) $(PROG).sym
	$(REMOVE) $(PROG).exe
	$(REMOVE) $(TARGET)
	$(REMOVE) $(DEP)/*.d
#--------------------------------------------------------------------------
# Include the dependency files.
include $($(DEP) 2 > /dev/null) $(wildcard $(DEP)/*)
#--------------------------------------------------------------------------
.PHONY : all clean
#--------------------------------------------------------------------------

Klar, oben erst­mal den Kom­men­tar # vor „Linux“ weg und bei „Win­dows“ hin.

Wenn man möch­te, kann man den Namen des erzeug­ten Pro­gram­mes bei „PROG =“ ändern.

Unter Ubun­tu 20.04 heißt die libusb nicht „libusb‑1.0“, son­dern ein­fach „libusb“, das muss bei „LIBS“ und „INCLUDES“ geän­dert werden.

Und dann, obwohl es oben im Make­file eine Unter­schei­dung zwi­schen „Linux“ und „Win­dows“ gibt, gibt es unten eini­ge Befeh­le, die davon aus­ge­hen, dass eine „exe“ raus­kommt. Damit das Make­file unter Linux sau­ber läuft ein­fach alle „.exe“ (bis auf Eines!, sie­he Code oben) raus­lö­schen. Auch im Abschnitt „clean:“, dann wird sau­ber aufgeräumt.

So, und hier zum Schluß mein Kom­pi­lier­er­geb­niss, ich habe das Pro­gram übri­gens „Read1Wire“ genannt:

root@ubuntu-server:/usr/src/1-Wire USB/temp-sensor# make
gcc -Wall -g -O2 -DOS_LINUX -fno-strict-aliasing -MD -MP -MF ./.deps/hid_LINUX.o.d -c `pkg-config libusb --cflags` hid_LINUX.c -o hid_LINUX.o
gcc -Wall -g -O2 -DOS_LINUX -fno-strict-aliasing -MD -MP -MF ./.deps/main.o.d -c `pkg-config libusb --cflags` main.c -o main.o
gcc -Wall -g -O2 -DOS_LINUX -fno-strict-aliasing -MD -MP -MF ./.deps/Read1Wire.d hid_LINUX.o main.o `pkg-config libusb libudev --libs` -o Read1Wire
text data bss dec hex filename
5845 388 12 6245 1865 Read1Wire

Funk­tio­nert wun­der­bar als „root“, aber nicht als nor­ma­ler Nut­zer! War­um? Weil der nor­ma­le Nut­zer kei­nen Zugriff auf das HID Device hat! Man kann das ändern, indem man für jeden Nut­zer den Zugriff auf das spe­zi­el­le HID Device per udev-Regel zulässt. Das fol­gen­de Bei­spiel gibt JEDEM Nut­zer das Recht den Diamex Adpa­ter per HID aus­zu­le­sen. Aus mei­ner Sicht ist das sicher­heits­tech­nisch okay. Man könn­te die udev-Regel auch so gestal­ten, dass man das Recht einer bestimm­ten Grup­pe gibt und dann nur bestimm­te Nut­zer in die­se Grup­pe auf­nimmt. Das darf jeder selbst raus­fin­den, wie das mit­tels udev-Regeln geht. Hier nun die ein­fa­che­re Variante.

Man legt eine Datei „85-usb.rules“ im Ver­zeich­nis „/etc/udev/rules.d“ mit fol­gen­dem Inhalt an:

#
# pure data udev rules
#
# Put me in "/etc/udev/rules.d", I am named based on the debian udev rules format
#
# "add" actions are device insertions and device attributes are used to match the device
#
# "remove" actions are matched using ENV variables since the SYSFS node for the device is gone
# and thus the attributes have been deleted
#
# rules built using:
# - udevadm info -a -p $(udevadm info -q path -n /dev/*device*) : attributes
# - sudo udevadm monitor --env /dev/*device* : events and env vars
#
# Documentation is your friend: http://reactivated.net/writing_udev_rules.html
#
################################################################################################


# input devices
#
SUBSYSTEM=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="0480", MODE="0666"

Anschlie­ßend wen­det man die­se Regel an mit fol­gen­dem Befehl bzw. per Reboot:

udevadm control --reload-rules && udevadm trigger

Die ver­wen­de­ten Attri­bu­te „idVen­dor“ und „idPro­duct“ wei­sen die­se Regel NUR dem Diamex Adap­ter zu.

Anpassungen am Quellcode für die Verwendung in Nagios/Icinga

Der mit­ge­lie­fer­te Quell­code läuft in einer End­los­schlei­fe und gibt die Wer­te samt Zusatz­in­fos ein­fach aus. Das passt so nicht zur Ver­wen­dung als Plug­in in Nagios/Icinga. Ein­mal soll­ten soge­nann­te „Per­for­man­ce­da­ten“ aus­ge­ge­ben wer­den, damit Nagos/Icinga die Wer­te ver­ar­bei­ten kann. Und das Pro­gramm soll­te star­ten, den Wert eines bestimm­ten Sen­sors aus­le­sen, aus­ge­ben und sich dann wie­der been­den. Außer­dem wäre es wün­schens­wert, wenn man die Num­mer des Sen­sors, des­sen Wert man haben will, ans Pro­gramm über­ge­ben könn­te und dabei auch gleich Wer­te für „War­ning“ und „Cri­ti­cal“, die dann wie­der­um als Per­for­man­ce­da­ten aus­ge­ge­ben werden.

Mit dem fol­gen­den Quell­code wird genau dies rea­li­siert. Man muss dem Pro­gramm drei Wer­te mit­ge­ben, der Ers­te ist die Sen­sor­num­mer, der Zwei­te der Wert für War­ning und der Drit­te der Wert für Critical:

Read1Wire <sensor> <warn> >critical>

Da der Adap­ter die Wer­te der ein­zel­nen Sen­so­ren nach­ein­an­der im Sekun­den­takt per HID Event aus­gibt, muss das Pro­gramm so lan­ge war­ten, bis der Adap­ter den Wert des gewünsch­ten Sen­sors geschickt hat. Dann gibt es den Wert, samt wei­te­ren Infos und den Per­for­man­ce­da­ten, aus:

root@ubuntu-server:/usr/src/1-Wire USB/temp-sensor# ./Read1Wire 1 25 30
Sensor #1 of 2: +28.6°C Power: Extern ID: 284B2FB9262001DC
| value=28.6;25;30

Man sieht in der Aus­ga­be, dass inge­samt zwei Sen­so­ren am Adap­ter ange­schlos­sen sind, abge­fragt habe ich die Num­mer 1.

Und hier der Quell­code (main.c):

# version date 15.01.2021 13:00

#include 
#include 
#include 

#if defined(OS_LINUX) || defined(OS_MACOSX)
  #include 
  #include 
#elif defined(OS_WINDOWS)
  #include 
#endif

#include "hid.h"
//*----------------------------------------------------------------------------
// static char getkey(void);
//*----------------------------------------------------------------------------
int main(int argc, char* argv[])
{
                int i, r, num, sensor=1, temp=0, warn=30, crit=40;
                char buf[64], *pwr;
                
                if (argc == 4) {
                               sensor = atoi(argv[1]);
                               warn = atoi(argv[2]);
                               crit = atoi(argv[3]);
                } else {
                               fprintf(stdout, "Usage: %s   \n", *argv);
                               return -1;
                }
                
                
                r = rawhid_open(1, 0x16C0, 0x0480, 0xFFAB, 0x0200);
                if (r <= 0) {
                               fprintf(stdout, "No Temp-Sensor(s) found\n");
                               return 3;
                }
                // fprintf(stdout, "Found Temp-Sensor(s)\n");

                while (1) {
                               //....................................
                               // check if any Raw HID packet has arrived
                               //....................................
                               num = rawhid_recv(0, buf, 64, 220);
                               if (num < 0) {
                                               fprintf(stdout, "\nError Reading\n");
                                               rawhid_close(0);
                                               return 3;
                               }
                               
                               if (num == 64) {
                                               // exit, if sensor parameter value is greater then number of connected sensors
                                               if (sensor > buf[0]){
                                                               fprintf(stdout, "Only %d Temp-Sensors found!\n", buf[0]);
                                                               return 3;
                                               }
                                               // print values of sensor
                                               if (buf[1] == sensor){
                                                               temp = *(short *)&buf[4];
                                                               if (buf[2]) { pwr = "Extern"; }
                                                                               else { pwr = "Parasite"; }
                                                               fprintf(stdout, "Sensor #%d of %d: %+.1f°C Power: %-10s ID: ", 
                                                                               buf[1], buf[0], temp / 10.0, pwr);
                                                                 
                                                                               for (i = 0x08; i < 0x10; i++) {
                                                                                              fprintf(stdout, "%02X", (unsigned char)buf[i]);
                                                                               }
                                                 
                                                               fprintf(stdout, "\n");
                                                               // print nagios/icinga performance values
                                                               fprintf(stdout, "|");
                                                               fprintf(stdout, " value=%.1f;%d;%d", temp / 10.0, warn, crit);
                                                               fprintf(stdout, "\n");

                                                               if (temp >= crit*10) {
                                                                               return 2;
                                                               }
                                                               if (temp >= warn*10) {
                                                                               return 1;
                                                               }
                                                               return 0;
                                               }
                               }
                }
} 

His­to­rie:

19.12.2020 – 16:15: Ers­tes Release
15.01.2021 – 13:00: Rück­ga­be­wer­te für Nagios hinzugefügt

Ihr könnt Euch als wei­te­res Bei­spiel, wie man Wer­te aus­liest auch noch die­ses Pro­jekt bei Git­hub ansehen.

Pro­ble­me oder Anre­gun­gen? Schreibt mir unter onewire@docollipics.de