Aus Raspberry Pi Geek 08/2018

Datenlogger mit Messverstärker (Seite 3)

Listing 9

 

# -*- coding: utf-8 -*-
'''
Daten von Owon B35T Multimeter über Bluetooth BLE einlesen.
Ausgabe:
  outstr: Textstring einschließlich Einheit
  datax: Zeitpunkt der Messung in Sekunden seit Start des Programms
  datay: Messwert als Zahl
'''
import pexpect
import time
device_owon= 'EA:E6:C2:A5:17:35'
logdata = 'datalog.txt'
def formatinput(a, test=False):
  prefixlist = {0: '' , 4: "ov", 8: "Diode", 16: 'M',  32: 'k', 64: 'm', 128: 'u', 2: 'n'}
  unitlist = {1: 'F', 2: '°C',  4: 'F', 8: 'Hz', 16: 'hFE', 32: 'Ohm', 64: 'A', 128: 'V'}
  expolist = {0: 1., 1: 1000., 2: 100., 4: 10.}  # 30, 31, 32, 34-> 0000, 0.000, 00.00, 000.0
  modelist = {48: "DC auto", 40: "AC auto", 16: "DC man", 8: "AC man", 32: "", 0: ""}
  multlist  = {0: 1, 4: 1, 16: 1e6, 32: 1e3, 64: 1e-3, 128: 1e-6, 2: 1e-9}
  data=a.split(b'\r')[0].split(b'value: ')[1][:-1].split(b' ')
  c=[chr(int('0x'+str(x.decode('ascii')),16)) for x in data]
  if len(c) != 14:
    return["Wait for data", 0, 0]
  values=c[0:5]
  expo=int(c[6])
  mode=ord(c[7])
  prefix=ord(c[9])
  unit= ord(c[10])
  val=''.join(values)
  try:
    val1=int(val)
    val1 /=expolist.get(expo,1)
    datay = val1 * multlist.get(prefix, 1)
  except:
    # probably overflow
    val1=9999
    datay= 9999
  # Ohne folgende Korrektur würde "F! statt "nF" als Einheit ausgegeben
  if (unit == 4) and (prefix == 0):
    prefix= 2
  prefixo=prefixlist.get(prefix)
  unito=unitlist.get(unit)
  modesel= modelist.get(mode&56, '')
  datax= round(time.time()-now, 1)
  outstr='{:7.3f}  {:1}{:3}  {:5s}'. format(val1, prefixo, unito, modesel)
  return[outstr, datax, datay]
def init():
  global gattinst, now
  now = time.time()
  print('Initialization')
  gattinst = pexpect.spawn("gatttool -b {} -I".format(device_owon))
  gattinst.sendline("connect")
  # drop first 14 input lines
  for i in range(4):
    gattinst.expect("handle", timeout=3)
    #print("1. ", gattinst.before)
def mloop():
  print('Time in seconds after start, reading and unit')
  print('Write time and reading to file', logdata)
  with open(logdata,'w',encoding='utf8') as f:
    # nur Daten, die sich um mehr als deltay unterscheiden
    # werden gespeichert.
    deltay=0    # Wenn 0, werden alle Daten gespeichert
    oldval=0
    while True:
      try:
        gattinst.expect("handle", timeout=2)
        outp= (formatinput(gattinst.before))
        if outp[1] != 0:
          if abs(outp[2]-oldval) >= deltay:
            print("* {}, {}".format(outp[1], outp[2]))
            f.write("{}, {} <br>\n".format(outp[1], outp[2]))
            f.flush()
            oldval=outp[2]
          else:
            print("  {} {}". format(outp[1], outp[0]))
      except pexpect.exceptions.TIMEOUT:
        pass
        print('timeout')
        break
      except KeyboardInterrupt:
        print("interrupt")
        break
    print('Terminated')
init()
mloop()

Wie schon in Listing 7 initialisiert auch das Python-Skript in Listing 9 das Loggen mit einer kleinen Schleife, um die Synchronisation der Daten zu gewährleisten. Die Schleife mloop() schreibt kontinuierlich in die Datei logdata. Um bei einem Abbruch durch einen Timeout (keine Daten vom Messgerät) oder über [Strg]+[C] keine Werte zu verlieren, fängt die Software solche Ereignisse ab und schließt die Datei ordnungsgemäß. Den Flush-Befehl benötigen Sie nur, wenn Sie Daten während des Messens aus der Datei lesen möchten.

Das Unterprogramm formatinput() definiert zunächst mehrere Dictionaries zum Übersetzen der Werte – teils, um die Anzeige zu dekodieren, teils, um die Werte in eine Zahl mit der richtigen Dimension umzurechnen. Es formatiert den Eingabestring und gibt drei Werte zurück: eine Zeichenkette mit Messwert und Einheiten, den Zeitpunkt der Messung in Sekunden und den Messwert als Zahl (Abbildung 2).

Abbildung 2: Drahtlos überträgt das Multimeter seine Daten entweder über einen modernen Bluetooth-Dongle direkt zum PC oder zunächst an einen Raspberry Pi.

Abbildung 2: Drahtlos überträgt das Multimeter seine Daten entweder über einen modernen Bluetooth-Dongle direkt zum PC oder zunächst an einen Raspberry Pi.

Daten mit dem RasPi erfassen

Das Programm aus Listing 9 gibt seine Daten lediglich live am Bildschirm aus. Komfortabler gelingt das Erfassen der Daten mit einem Rechner, der allerdings am eigentlichen Messort oft im Weg ist. In Abbildung 3 misst das Multimeter die Temperatur eines frisch gebrühten Kaffees. Zwar steht der Rechner dank Bluetooth in einigen Metern Abstand, stört dort aber vermutlich ebenfalls.

Abbildung 3: Beispiel für eine Messung: Das spritzwassergeschützte Messgerät hält ausreichend Abstand von der heißen Kaffeetasse, ein Rechner als Datenlogger steht im Raum nebenan.

Abbildung 3: Beispiel für eine Messung: Das spritzwassergeschützte Messgerät hält ausreichend Abstand von der heißen Kaffeetasse, ein Rechner als Datenlogger steht im Raum nebenan.

Ein RasPi bietet eine Möglichkeit, den Abstand zwischen Mess- und Speicherort zu vergrößern: Im Headless-Mode läuft er ohne Tastatur und Bildschirm und protokolliert die Daten im Speicher. Um das Messen zu beenden, empfiehlt es sich allerdings, ihn kontrolliert herunterzufahren – das jedoch geht schlecht ohne Maus und Display.

Bei einem Verlust der Spannung würde das Programm abbrechen, und die Daten im Zwischenspeicher wären verloren. Davor schützt die Python-Befehlszeile flash(). Noch besser ist es, zunächst das Messgerät auszuschalten: In diesem Fall erkennt die Software, dass das externe Gerät keine Daten mehr sendet, und schließt die Protokolldatei.

Erfreulicherweise gibt es viele Methoden, einen Raspberry Pi im Headless-Modus zu bedienen: auf physikalischer Ebene über Ethernet oder WLAN, auf Transportebene über die Secure Shell oder das VNC-Protokoll, auf Applikationsebene mit den Programmen SSH oder dem Vncviewer (Abbildung 4). Statt das Messprogramm über einen der Zugänge zur Fernwartung zu starten, wäre es alternativ möglich, es automatisch beim Hochfahren des RasPi aufzurufen.

Abbildung 4: Ein Headless-RasPi lässt sich unter anderem über das VNC-Protokoll steuern.

Abbildung 4: Ein Headless-RasPi lässt sich unter anderem über das VNC-Protokoll steuern.

Daten für alle

Beim oben geschilderten Verfahren sieht nur der Messrechner die Ergebnisse, im Headless-Modus liegen sie ausschließlich verborgen in der Logdatei der RasPi. Nur wenige Programmzeilen geben den Zugriff jedoch für alle frei, die sich in demselben WLAN befinden.

Unter Vernachlässigung aller Sicherheitsaspekte rufen Sie dazu im Verzeichnis, in dem das Python-Messprogramm aus Listing 9 läuft, über den Befehl python3 -m http.server 8000 den Python-Webserver auf. Er gibt den Zugriff auf alle Dateien im aktuellen Verzeichnis und den Unterverzeichnissen frei, so auch auf das Logfile.

Nun greifen Sie einfach von jedem Gerät, das in demselben Funknetz eingebucht ist, über einen Webbrowser auf die Daten zu. Als URL verwenden Sie die IP-Adresse des RasPi, die beim Aufruf des Servers verwendete Portnummer sowie den Dateinamen. Angewandt auf das vorliegende Beispiel lautet die Zeile http://192.168.179.57:8000/datalog.txt.

Um die gerade gemessenen Daten alle zehn Sekunden zu aktualisieren, bietet es sich an, diese in ein HTML-Format zu bringen, ergänzt um den Header-Eintrag <meta http-equiv="refresh" content="10">. Wollen Sie die Sache nicht von Hand erledigen, bietet sich dazu die Python-Bibliothek Flask an, über die Sie die Variablen elegant in HTML einbetten.

Der Refresh-Befehl, der die Seite neu lädt, stammt aus der Steinzeit der Webentwicklung. Ohne Ajax (Asynchronous Javascript and XML) wäre die Interaktivität heutiger Webseiten allerdings kaum zu bewerkstelligen. Die grundlegenden Schritte in diese Richtung setzt Listing 10 um.

Listing 10

 

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8"/>
  <title>Ajax Temperaturdemo</title>
</head>
<body>
<h1>Aktuelle Temperatur</h1>
Die Temperatur beträgt 0  Grad Celsius.
<script>
function getData() {
  var xhr = new XMLHttpRequest();
  xhr.onload = function() {
    if(xhr.readyState == 4 && xhr.status === 200) {
      document.getElementById('mydat').innerHTML = xhr.responseText;
    }
  };
  xhr.open("GET", "datalog.txt", true);
  xhr.send(null);
}
setInterval(getData, 3000);
window.onload = getData()
</script>
</body>
</html>

Bei dem Teil außerhalb des Script-Blocks handelt es sich um einfaches HTML. Das entsprechende Anzeigeelement trägt einen Namen; die Javascript-Funktion ersetzt es durch Daten, die es aus der Datei datalog.txt bezieht. Wie häufig das geschieht, regelt der Befehl setInterval. In Listing 10 passiert das alle 3 Sekunden. Da das Messgerät etwa zwei Datenpunkte pro Sekunde sendet, fügt die Routine bei jeder Aktualisierung sechs neue Datensätze ein.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 9 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
RASPBERRY PI GEEK KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Raspberry Pi Geek bei Google Play Readly Logo
Nach oben