Erfreulicherweise gibt es viele Bibliotheken, die die Sache vereinfachen. Für unser Beispiel kommt Python als Programmiersprache zum Einsatz. Ursprünglich war als Grafikbibliothek das moderne Kivy [7] vorgesehen, doch das nutzt das effiziente OpenGL für direktes Rendering, und wie oben beschrieben funktioniert das nicht über das Netzwerk. Deshalb setzt das Projekt jetzt auf wxPython auf, einem Wrapper für die in C++ implementierten wxWidgets.
Für unseren Raspberry Pi erstellen wir ein Programm, das ein mehrzeiliges LC-Display samt Steuerknöpfen simuliert (Abbildung 3). Per Knopfdruck fragen wir diverse Systeminformationen ab, das Ergebnis landet in einer Textbox. Das Ganze lässt sich mit überraschend wenig Codezeilen implementieren.

Abbildung 3: Unser Beispielprogramm auf einem Smartphone mit einer Bildschirmauflösung von 800 x 480 Pixeln.
Vor dem Programmieren steht aber die Installation der notwendigen Python-Bibliothek (Listing 2). Unter Raspbian Lite installiert der Befehl auch alle notwendigen Hilfsbibliotheken für das Ausführen grafischer Anwendungen (also den X-Client) auf dem System – erschrecken Sie also nicht, wenn die Paketliste sehr lang ausfällt.
Listing 2
$ sudo apt-get update $ sudo apt-get -y install python-wxgtk3.0
Bei Bedarf richten Sie darüber hinaus das Paket wx3.0-examples mit Beispielen in C++ ein. Da die Methodenaufrufe in C++ und Python fast identisch ausfallen, lassen sich die Beispiele recht einfach in die Python-Welt übertragen.
wxPython
Von der Grundarchitektur her unterscheidet sich ein wxPython-Programm nicht von anderen Programmen mit grafischer Oberfläche. Der Hauptteil besteht aus einer Endlosschleife, dem sogenannten Event-Loop.
Jede Interaktion mit dem Programm, etwa das Drücken eines Schalters, eine Texteingabe oder das Verschieben des Fensters löst ein Ereignis aus. Der Event-Loop verarbeitet das Ereignis, indem er es an entsprechende Python-Methoden weitergibt. Unser Beispielprogramm verfügt über drei Buttons, entsprechend gibt es drei Methoden für die Verarbeitung.
In Listing 3 sehen Sie die wichtigsten Abschnitte des Programms WxVlcdApp.py, den kompletten Code laden Sie aus dem Github-Repo des Autors herunter [8]. Der beschriebene Event-Loop findet sich in der letzten Zeile. Dort ruft das Programm die Methode MainLoop() der darüber definierten Applikationsklasse WxVlcdApp auf.
Sie erweitert die Klasse wx.App und besteht hier nur aus der einzelnen Methode OnInit(). Diese generiert die Oberfläche der Anwendung. Dafür gibt es wiederum eine eigene, ab Zeile 22 definierte Klasse AppFrame, die den Hauptteil des Programms ausmacht.
Der Konstruktor von AppFrame erzeugt als Erstes in der Methode create_controls() die grafischen Elemente (ab Zeile 34). Dazu verwenden wir einen BoxSizer(), wie man ihn aus anderen Toolkits unter dem Namen BoxLayout() oder ähnlich kennt.
Dieses Layoutelement richtet seine Kinder vertikal aus; im Beispiel sind das konkret die Textbox für die Ausgabe und die Schalterleiste. Die Parameter der Methode Add() steuern, welches Kind sich wie ausbreiten darf und wie es sich ausrichtet. Weitere Details dazu finden Sie in der Dokumentation zu wxPython. Analog dazu erzeugt die Methode create_buttons() (ab Zeile 44) die Schalterleiste. Hier landen drei Buttons in einem horizontalen BoxSizer().
Zusätzlich legen die Zeilen 50 bis 53 fest, welche Methoden das Programm beim Drücken der einzelnen Knöpfe ausführt. Die Erweiterung um zusätzliche Buttons erfolgt hier einfach durch Kopieren und Einfügen der relevanten Zeilen.
Listing 3
#!/usr/bin/python
import os, time, threading, subprocess, re, socket
import wx
[...]
# --- Hilfsklasse für asynchrones Update der Textbox
SYSCMD_EVENT_TYPE = wx.NewEventType()
EVT_SYSCMD = wx.PyEventBinder(SYSCMD_EVENT_TYPE, 1)
class SysCmdFinishEvent(wx.PyCommandEvent):
""" wrap result of a system-command into an event """
def __init__(self, ev_id, value=None):
wx.PyCommandEvent.__init__(self, SYSCMD_EVENT_TYPE, ev_id)
self._value = value
def get_output(self):
return self._value
# --- Hauptapplikationsrahmen
class AppFrame(wx.Frame):
""" Application toplevel frame """
# --- Constructor
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(800, 480))
#self.SetMenuBar(self.create_menu())
#self.CreateStatusBar()
self.create_controls()
self.Bind(EVT_SYSCMD, self.on_syscmd_result)
self.on_uptime(None)
# --- Widgets erstellen
def create_controls(self):
""" create panel with all controls """
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(self.create_outputarea(self), 1, wx.ALL|wx.EXPAND, 10)
vbox.Add(self.create_buttons(self), 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 10)
self.SetSizer(vbox)
[...]
# --- Buttonleiste erstellen
def create_buttons(self, parent):
button_panel = wx.Panel(parent)
sys_btn = wx.Button(button_panel, -1, "System")
disk_btn = wx.Button(button_panel, -1, "Disk")
net_btn = wx.Button(button_panel, -1, "Network")
self.Bind(wx.EVT_BUTTON, self.on_uptime, sys_btn)
self.Bind(wx.EVT_BUTTON, self.on_df, disk_btn)
self.Bind(wx.EVT_BUTTON, self.on_ip, net_btn)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(sys_btn, 0, wx.ALL, 10)
hbox.Add(disk_btn, 0, wx.ALL, 10)
hbox.Add(net_btn, 0, wx.ALL, 10)
button_panel.SetSizer(hbox)
return button_panel
# --- Textbox für Ausgabe erstellen
def create_outputarea(self, parent):
text_panel = wx.Panel(parent)
text_panel.SetBackgroundColour(wx.BLUE)
self._output_text = wx.StaticText(text_panel, -1, "")
self._output_text.SetForegroundColour(wx.WHITE)
self._output_text.SetFont(wx.Font(14, wx.MODERN, wx.NORMAL, wx.BOLD))
self._output_text.SetSize(self._output_text.GetBestSize())
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox.Add(self._output_text, 1, wx.EXPAND, 0)
text_panel.SetSizer(hbox)
return text_panel
# --- Handler für den "uptime"-Button
def on_uptime(self, evt):
""" start asynchronous command """
threading.Thread(target=self.get_uptime).start()
[...]
# --- Handler für asynchrones Update der Textbox
def on_syscmd_result(self, evt):
self._output_text.SetLabelText(evt.get_output())
# --- Exit-Handler
def on_exit(self, evt):
""" exit-handler """
self.Close()
# --- Abfrage uptime (asynchron ausgeführt)
def get_uptime(self):
hostname = self.read_hostname()
tm = time.strftime("%H:%M:%S")
(uptime, idle) = self.read_uptime()
load = self.read_loadavg()
output = """Hostname: %s
Time: %s
Uptime: %s
Idle: %s
Load: %s""" % (hostname, tm, uptime, idle, load)
evt = SysCmdFinishEvent(-1, output)
wx.PostEvent(self, evt)
[...]
# --- Hostname auslesen
def read_hostname(self):
""" read hostname from /proc/sys/kernel/hostname """
with open("/proc/sys/kernel/hostname") as f:
hostname = f.read().split()
f.close()
return hostname[0]
[...]
# --- Hauptanwendungsklasse
class WxVlcdApp(wx.App):
def OnInit(self):
frame = AppFrame(None, "Virtual LCD")
self.SetTopWindow(frame)
frame.Show(True)
return True
# --- Hauptprogramm
if __name__ == '__main__':
wait_for_X11()
app = WxVlcdApp(redirect=False)
app.MainLoop()
Event-Verarbeitung
Unsere Button-Methoden rufen letztlich Systeminformationen ab und schreiben das Ergebnis in das Textfeld. Das erfolgt aber nicht direkt, weil während der Laufzeit der Methode die Anwendung quasi “einfriert”: MainLoop() kann in dieser Zeit auf Ereignisse der Oberfläche (Fenster verschieben, Button drücken und so weiter) nicht reagieren.





