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()