Hier mal nix mit Fotografie sondern was ganz Spezielles zum Thema 1‑wire Temperatursensoren (DS18B20) und wie man diese an PCs mit USB Schnittstelle mit Hilfe des Diamex Temp-Sensor Tester unter Ubuntu Linux 20.04 abfragen kann.
Was habe ich damit vor?
Hintergrund ist, dass ich die Serverraumtemperaturen bei uns in der Firma messen, protokollieren und überwachen möchte und ich dafür nicht unnötig viel Strom verbrauchen will. Dazu hatte ich schon vor einigen Jahren erfolgreich einen Raspberry Pi verwendet, bei dem man 1‑wire Temperatursensoren (DS18B20) einfach per GPIO anschließen kann. Leider ging nach relativ kurzer Zeit die SD-Karte kaputt, es waren wohl zuviel Messdaten bzw. Schreibvorgänge im Laufe der Zeit.
Danach habe ich es mit einem BananaPi versucht, dieser hat die Möglichkeit per SATA Schnittstelle eine SSD als robusteren Datenspeicher anzubinden. Das ging auch eine längere Zeit gut, aber letztes Jahr hat sich dann die SATA Schnittstelle verabschiedet, also wieder nix dauerhaftes.
Zuletzt habe ich versucht mein Monitoring-System auf Basis eines stromsparenden MiniPC (Terra Black Dwarf) aufzubauen. Leider hat dieser eine x86 (i386) Architektur und ich habe dann letztlich aufgegeben, da es einige für mein Projekt notwendige Linux Softwarepakte (wie Grafana) nicht fertig für die x86 Architektur gab. Die Pakete aus den Quellen selber zu bauen und zukünftig zu pflegen war mit einfach zu viel Aufwand.
Softwarebasis für mein Monitoring-System war stets ein Linux (Raspian/Debian) mit Nagios bzw. später Icinga2 (Icinga-Director) samt Graphite bzw. Grafana, um die Daten grafisch darzustellen. Für die ARM-Architektur des Raspi bzw. Bananapi habe ich die Pakete noch irgendwoher gefunden, leider bin ich beim x86 System zuletzt daran verzeifelt, dass es keine fertigen Pakete für x86 von Grafana gab.
Da fiel der Entschluß ein kleines x64 System mit Ubuntu Server 20.04 aufzusetzten und Nagios bzw. Grafana dort aus den verfügbaren Paketrepositories zu installieren. Das hat auch gut geklappt, nun musste ich aber noch eine Lösung finden, die 1‑wire Sensoren an diesen PC anzubinden. Meine Wahl fiel auf einen USB 1‑wire Adapter und ganz speziell auf den Adapter von Diamex. Dieser besteht aus einem kleinen STM32F030, welcher die Sensoren ausliest und die Daten per USB HID Events zur Verfügung stellt. Sollte also auch unter Linux kein Problem sein, aber es war für mich dann doch etwas schwieriger bzw. ich habe keine gute Anleitung dazu gefunden und daher stelle ich hier meine Erfahrungen ins Netz und hoffe, dass es anderen schneller weiterhilft.
Der Diamex Adapter und seine Software
Diamex stellt zwei Downloads zur Verfügung, einmal ein fertiges Windows Tool und dann ein sogenanntes „MSYS Paket“. Ersteres kann man unter Windows nehmen, um mal eben zu testen, ob der Adapter samt angeschlossener Sensoren funktioniert. Ging in meinem Fall problemlos, somit wusste ich, dass Probleme unter Linux nur an der Software liegen können.
Das zweite Paket läuft auch unter Windows, am besten den Ordner „msys“ ins Root von C: entpacken. Dann in den Unterordner „1.0“ wechseln und „mysys.bat“ mit administrativen Rechten (!) starten. Man landet dann ein einer Bash, ja ich schreibe bewusst Bash, da MSYS eine auf Cygwin basierende Linux Umgebung für Windows ist, speziell zum compilieren von Anwendungen mit gcc. Der Quellcode für das Beispielprojekt liegt unter /home/temp-sensor. Dort findet man mit „main.c“ den eigentlich Quellcode, einige Dateien, um HID Geräte unter Windows/Linux/MacOSX ansprechen zu können, das „Makefile“ (brauchen wir später unter Linux) und einen versteckten Ordner „.deps“. Hier schon mal ein erster Tipp, wenn es später mal Probleme beim kompilieren gibt, die Dateien in diesem Ordner löschen („make clean“ macht das nicht immer 100%ig).
So, wenn wir in diesem Ordner sind, als erstes ein „make clean“, um Reste des letzten Builds zu entfernen, dann ein „make“ und es sollte erfolgreich 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
Diese Datei kann aus der Bash heraus mit den Linux typischen Voranstellen von „./“ gestartet werden:
$ ./TempCmd.exe
No Temp-Sensor found
Da mein Adapter hier nicht eingesteckt war, bekomme ich die Meldung „No Temp-Sensor found“, aber man sieht, die exe läuft. Wenn der Adapter angeschlossen ist, liest er die Sensoren aus und schickt so ca. 1x pro Sekunde die Messwerte eines Sensors per HID Event raus. So bekommt man so nach und nach den Messwert von jedem Sensor und die kleine TempCmd.exe zeigt diese einfach an. Für mich ist das okay, ich brauche Messwerte von zwei (oder mehr) Sensoren nicht zeitgleich. Per Druck auf „ESC“ lässt sich das Programm beenden. Auch „STRG+C“ geht!
Endlich: Wie läuft der Adapter unter Linux?
Okay, ich gehe davon aus, das Ubuntu Server 20.04 x64 bereits installiert und die Anmeldung an der Konsole erfolgt ist. Ich wechsle per „sudo bash“ bei solchen Dingen gerne zum „root“ user, dann bremsen mich erstmal Rechteeinschränkungen nicht aus. Man kann aber auch als Standardbenutzer angemeldet bleiben und per vorangestelltem „sudo“ die Befehle abschicken, das bleibt jedem selbst überlassen.
Ich schreibe die Befehle im Folgenden stets ohne „sudo“!
Als erstes schauen wir, ob der Adapter grundsätzlich erkannt wird. Dazu per „dmesg ‑w“ sich die Kernelmeldungen anzeigen lassen und dann den Diamex Adapter in einen USB Port einstecken. Es sollten nun Meldungen auftauchen, welche 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 erstes muss der Quellcode auf den Linux Server. Dazu einfach das Verzeichnis „temp-sensor“ aus dem ZIP-Archiv „msys.zip“ auf das Ubuntu System kopieren. Am besten ins Verzeichnis „/usr/src/1‑wire“, welches wir vorher erstellen. Zum Kopieren kann z.B. das Tool WinSCP verwendet werden. Damit kommt man aber von Remote normalerweise nicht schreibend ins Verzeichnis „/usr/src“ („root“ hat kein Recht per SSH zuzugreifen), also am besten alles nach „/home/<username>“ kopieren und dann auf dem Ubuntu Server als user „root“ das Verzeichnis nach „usr/src/1‑wire“ kopieren.
Auf dem Ubuntu Server braucht man zum Kompilieren einige Pakete, diese bekommt man zusammen am einfachsten mit:
apt install build-essential
Zusätzlich braucht man die Entwicklerdateien für die Bibliotheken USB und Udev:
apt install libusb-dev
apt install libudev-dev
So, nun funktioniert aber ein „make“ noch nicht! Man muss erst das „Makefile“ für Linux und speziell für Ubuntu 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 erstmal den Kommentar # vor „Linux“ weg und bei „Windows“ hin.
Wenn man möchte, kann man den Namen des erzeugten Programmes bei „PROG =“ ändern.
Unter Ubuntu 20.04 heißt die libusb nicht „libusb‑1.0“, sondern einfach „libusb“, das muss bei „LIBS“ und „INCLUDES“ geändert werden.
Und dann, obwohl es oben im Makefile eine Unterscheidung zwischen „Linux“ und „Windows“ gibt, gibt es unten einige Befehle, die davon ausgehen, dass eine „exe“ rauskommt. Damit das Makefile unter Linux sauber läuft einfach alle „.exe“ (bis auf Eines!, siehe Code oben) rauslöschen. Auch im Abschnitt „clean:“, dann wird sauber aufgeräumt.
So, und hier zum Schluß mein Kompilierergebniss, ich habe das Program übrigens „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
Funktionert wunderbar als „root“, aber nicht als normaler Nutzer! Warum? Weil der normale Nutzer keinen Zugriff auf das HID Device hat! Man kann das ändern, indem man für jeden Nutzer den Zugriff auf das spezielle HID Device per udev-Regel zulässt. Das folgende Beispiel gibt JEDEM Nutzer das Recht den Diamex Adpater per HID auszulesen. Aus meiner Sicht ist das sicherheitstechnisch okay. Man könnte die udev-Regel auch so gestalten, dass man das Recht einer bestimmten Gruppe gibt und dann nur bestimmte Nutzer in diese Gruppe aufnimmt. Das darf jeder selbst rausfinden, wie das mittels udev-Regeln geht. Hier nun die einfachere Variante.
Man legt eine Datei „85-usb.rules“ im Verzeichnis „/etc/udev/rules.d“ mit folgendem 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 wendet man diese Regel an mit folgendem Befehl bzw. per Reboot:
udevadm control --reload-rules && udevadm trigger
Die verwendeten Attribute „idVendor“ und „idProduct“ weisen diese Regel NUR dem Diamex Adapter zu.
Anpassungen am Quellcode für die Verwendung in Nagios/Icinga
Der mitgelieferte Quellcode läuft in einer Endlosschleife und gibt die Werte samt Zusatzinfos einfach aus. Das passt so nicht zur Verwendung als Plugin in Nagios/Icinga. Einmal sollten sogenannte „Performancedaten“ ausgegeben werden, damit Nagos/Icinga die Werte verarbeiten kann. Und das Programm sollte starten, den Wert eines bestimmten Sensors auslesen, ausgeben und sich dann wieder beenden. Außerdem wäre es wünschenswert, wenn man die Nummer des Sensors, dessen Wert man haben will, ans Programm übergeben könnte und dabei auch gleich Werte für „Warning“ und „Critical“, die dann wiederum als Performancedaten ausgegeben werden.
Mit dem folgenden Quellcode wird genau dies realisiert. Man muss dem Programm drei Werte mitgeben, der Erste ist die Sensornummer, der Zweite der Wert für Warning und der Dritte der Wert für Critical:
Read1Wire <sensor> <warn> >critical>
Da der Adapter die Werte der einzelnen Sensoren nacheinander im Sekundentakt per HID Event ausgibt, muss das Programm so lange warten, bis der Adapter den Wert des gewünschten Sensors geschickt hat. Dann gibt es den Wert, samt weiteren Infos und den Performancedaten, 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 Ausgabe, dass ingesamt zwei Sensoren am Adapter angeschlossen sind, abgefragt habe ich die Nummer 1.
Und hier der Quellcode (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;
}
}
}
}
Historie:
19.12.2020 – 16:15: Erstes Release
15.01.2021 – 13:00: Rückgabewerte für Nagios hinzugefügt
Ihr könnt Euch als weiteres Beispiel, wie man Werte ausliest auch noch dieses Projekt bei Github ansehen.
Probleme oder Anregungen? Schreibt mir unter onewire@docollipics.de