micro:bit-Cockpit       

von Ralf Beesner
Elektronik-Labor    Projekte    Microbit 







Überblick 

Meine ersten Gehversuche in der GUI-Programmierung mit Python funktionieren zwar trotz stümperhaften Programmierstils, aber bieten nur das notwendigste. Eigentlich wollte ich mal so weit kommen, eine ansehnliche Oberfläche mit virtuellen Messwerken zu erstellen. Heutzutage befragt man erst mal die Suchmaschine seines geringsten Misstrauens, bevor man das Rad neu erfindet, und da gab es im RaspberryPi-Forum schon eine Lösung, die zwar eher Fans der kohlenwasserstoff-getriebenen Fortbewegung als einen Elektriker erfreut, aber erst mal muss die reichen:

https://www.raspberrypi.org/forums/viewtopic.php?f=38&t=134854,

Unter welcher Lizenz die Software steht, ist nicht ersichtlich, aber wer seine Software in ein Forum postet, wird wohl nichts dagegen haben, dass ein anderer sie aufgreift und verwendet, solange der eigentliche Urheber erkennbar bleibt.

Mit Roger Woolletts Meter.py habe ich drei "Cockpits" mit jeweils drei Messinstrumenten "zusammengepappt", die die Spannungen an drei ADC-Kanälen oder Beschleunigungswerte bzw. magnetische Feldstärken für x-, y-, und z-Achse anzeigen und ggf. in eine Datei schreiben.


Drei micro:bit-Programme

Der uBit-Logger forderte beim micro:bit jeden Messwert einzeln an. Da der uBit-Logger die Abfragen an den micro:bit so schnell wie möglich sendete, kann man eigentlich auch die Abfragerei sein lassen und den micro:bit die Meßwerte kontinuierlich senden lassen. In den folgenden Programmen ist das nun so gelöst.

Stellvertretend ist hier das Programm herausgepickt, das den Kompass-Chip des micro:bit abfragt. Da die Messwerte sehr groß werden können, wenn man mit dem micro:bit z.B. direkt vor einem Lautsprecher herumwedelt, werden sie erst einmal durch eine "runde" Zahl geteilt (durch 1024 - in der Hoffnung, dass MicroPyton so schlau ist, das durch ressourcensparendes bitshifting um 10 bit zu erledigen), dann in ganze Zahlen gewandelt, anschließend in Strings und dann über die serielle Schnittstelle ausgesandt. Die Messwerte sind durch Tabs getrennt und mit "newline" abgeschlossen. So kann man sie mit dem GUI-Programm direkt in ein csv-File schreiben und später mit Excel oder LibreOffice Calc weiterverarbeiten.

Insbesondere beim Magnetfeldsensor ist nach jedem Zyklus eine kleine Wartezeit erforderlich, weil sonst verstümmelte Zeilen auftreten.

# Kompass auslesen

from microbit import *
uart.init(baudrate=115200)

while (1):
    value_x = int (compass.get_x() / 1024)
    value_y = int (compass.get_y() / 1024)
    value_z = int (compass.get_z() / 1024)
    text_x = str(value_x)
    text_y = str(value_y)
    text_z = str(value_z)
    uart.write(text_x + chr(9) + text_y  + chr(9) + text_z + chr(13) + chr(10))
    sleep(12)


Drei GUI-Programme

Roger Woollett fügte seiner Meter-Klasse (meter.py) auch ein Beispiel-Programm (trymeter.py) bei. Es präsentiert zwei Instrumente und zwei Schieberegler, mit denen sich die Instrumentennadeln bewegen lassen. Leider bin ich völlig daran gescheitert, das Gewusel von Klassen und Funktionen, die sich gegenseitig aufrufen und Parameter übergeben, halbwegs zu verstehen und dort die Abfrage der seriellen Schnittstelle und Verteilung der Messwerte auf die drei Instrumente so einzubauen, dass die Zeiger zappeln. Stattdessen gab es nur einen Haufen "interessanter" Fehlermeldungen des Python-Interpreters.

Mein Python-Anfängerbuch erklärt zwar lang und breit das Konzept der Objektorientierung mit Klassen, Vererbung und Mehrfachvererbung anhand eines Beispiels aus dem realen Leben mit Fahrzeugen, PKWs, LKWs usw., das völlig einsichtig ist; dann kommt ein dürres Beispiel mit nur zwei Klassen, und das war es. Damit habe ich den Spagat zu komplexerem realen Code aus der freien Wildbahn jedoch nicht hinbekommen, und kryptische Konstrukte wie " ...,*args,**kwargs", mit denen anscheinend Tupel und listen an die Klasse übergeben werden, kommen da erst gar nicht vor.

Wenn ich etwas nicht verstehe, versuche ich es mit herumprobieren. Wenn das auch scheitert, versuche ich es mit vereinfachen, und so bin ich wieder bei meinem "Stümpercode" des uBitLoggers gelandet, der nun lediglich die Klasse "Meter" benutzt und den Rest mit globalen Variablen hinbiegt.

Da muss ich wohl noch einiges lesen, aber zunächst mal wollte ich ein Erfolgserlebnis, und wie schon anlässlich des uBitLoggers erwähnt: Liebe Kinder, nicht nachmachen, wenn Ihr mal richtige Python-Programmierer werden wollt!

Die serielle Schnittstelle wird in den drei GUI-Programmen nicht automatisch ermittelt, und auch ein Eingabefeld, in das man sie "händisch" eintragen kann, schien mir verzichtbar, weil der micro:bit unter Linux das einzige Gerät sein dürfte, das unter "/dev/ACM0" angesprochen wird. Falls erforderlich, muss man sie direkt im Quellcode ändern. Windows-Nutzer können sicherlich einen weiteren Rahmen mit einem Eingabefeld für die COM-Schnittstelle an das GUI "drantackern".

Die Beschriftung der Instrumente ist auch übelst hin gepfuscht. Zumindest hätte ich drei Subframes unter den Instrumenten anordnen müssen, aber es war einfacher, nur einen einzigen Rahmen vorzusehen und die Beschriftung (innerhalb des einen Rahmens) mit vielen Leerzeichen unter die Instrumente zu schieben.

Die Abfrage der seriellen Schnittstelle war in den bisherigen Programmen über einen separaten Thread gelöst; beim uBit-Cockpit habe ich mal die main.after()-Variante verwendet, mit der man in Tkinter neben der mainloop (die für das GUI zuständig ist) eine weitere Schleife definieren kann, die eine Funktion nach der angegebenen Zeit erneut aufruft.


Download des Quellcodes:  0517-ubit-cockpit.zip

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# license: wtfpl
# http://www.wtfpl.net/txt/copying/

import Tkinter as tk
import meter as m
import serial,time

rangelower = -1000 # untere Bereichsgrenze der Instrumente rangeupper = 1000 # obere Bereichsgrenze der instrumente file_is_open = 0 time_old = 0 def read_adc(): try: ser = serial.Serial("/dev/ttyACM0",115200, timeout=1)
except: print "-----> serielle Schnittstelle nicht vorhanden"
quit() ser_string=ser.readline() # jeweils eine Zeile einlesen ser_liste = ser_string.split(chr(9)) # Strings der ADC-Werte rausangeln try: meter0.set(int(ser_liste[0])) meter1.set(int(ser_liste[1])) meter2.set(int(ser_liste[2])) except: print "Fehler!" ser.flushInput() ## ins File schreiben: global time_old
global file_is_open

if (file_is_open == 1): interval = get_interval() time_new = time.time() if ((time_new - time_old) >= interval): time_old = time_new
try: ltime = time.localtime()
h, m , s = ltime[3:6] time_string = "{0:02d}:{1:02d}:{2:02d}".format(h,m,s) logfile.write (time_string + chr(9) + ser_string) except: print "writing failed" # mit main.after läuft read_adc() als Schleife main.after(10,read_adc) # sollte > 4 sein def get_interval(): get_interval = entry5.get()
try: interval= float(get_interval) except: interval = 1 return interval



def start_log(): global logfile
global file_is_open

filename = "uBitLogger.csv" getfilename= entry6.get() if getfilename != "": filename = getfilename
logfile = open (filename,"a+") # +a: append file_is_open = 1 print "filename=", filename


def stop_log(): global file_is_open
try: logfile.close() file_is_open = 0 except: pass def ende(): print("Ende !") try: thread.stop(get_values, ()) file_is_open = 0 ser.write("e"+ "0") ser.close() logfile.close() except: pass main.destroy() main=tk.Tk() main.title("uBitCockpit") main0 = tk.Frame(main) # main0 für Überschrift main0.pack() main1 = tk.Frame(main) # main1 für Messwerke main1.pack()
main2 = tk.Frame(main) # main2 für Intervall/Dateiname main2.pack() main3 = tk.Frame(main) # main3 für Buttons main3.pack() text0 = tk.Label(main0, text="uBitCockpit - Magnetometer", font="bold") text0.pack(side="top",padx=20,pady=10) # übler Notbehelf für Instrumentenbeschriftung text1 = tk.Label(main1, text=\
"Bx \ By \ Bz") text1.pack(side="bottom") meter0=m.Meter(main1,height=200,width=200,) meter0.setrange(rangelower,rangeupper) meter0.pack(side="left") meter0.blob("black") meter1=m.Meter(main1,height=200,width=200) meter1.setrange(rangelower,rangeupper) meter1.pack(side="left") meter1.blob("black") meter2=m.Meter(main1,height=200,width=200) meter2.setrange(rangelower,rangeupper) meter2.pack(side="left") meter2.blob("black") # Eingabe Intervall frame5 = (tk.Frame(main2)) frame5.pack(side="left",anchor="w") entry5 = tk.Entry(frame5, width=5,text="0.1") entry5.pack(side="left", anchor="w",padx = 10) text5 = tk.Label(frame5, text="Intervall der Logfile-Einträge (in s) ") text5.pack(side="left",pady=10) # Eingabe Logfile-Name frame6 = (tk.Frame(main2)) frame6.pack(sid="left") entry6 = tk.Entry(frame6, width=16) entry6.pack(side="left", padx = 10) text6 = tk.Label(frame6, text = "Name des Logfiles ") text6.pack(side="left", pady=10) # Start/Stop Log frame7 = (tk.Frame(main3)) frame7.pack(side="left") widget_start = tk.Button(frame7, text="Start Log",command=start_log) widget_start.pack(side="left", padx = 10) widget_stop = tk.Button(frame7, text="Stop Log",command=stop_log) widget_stop.pack(side="left", pady=10) # Quit-Button button7 = tk.Button(main3, text="Quit",command=ende) button7.pack(side="right", padx = 10) main.after(1,read_adc()) main.mainloop()




Elektronik-Labor   Projekte   Microbit