Jenseits des Smartphones: Android Things

Seite 3: Benutzeroberflächen & Fazit

Inhaltsverzeichnis

Ein klassischer Anwendungsfall für Android Things ist das Realisieren aufwendiger grafischer Benutzeroberflächen. Der Android-GUI-Stack ist in der Lage, das Rendering der meisten Steuerelemente in ein Bitmap umzuwandeln, das Entwickler daraufhin Schritt für Schritt auslesen und beispielsweise an ein Display schicken können, das per SPI mit dem Prozessrechnersystem verbunden ist. Im weiter oben genannten GitHub-Repository ist ein Treiber für die weit verbreiteten API-Displays zu finden. Die Dokumentation bietet ausreichende Informationen zum Prozess des Umleitens.

Interessanter ist die Frage, wie Entwickler Sensorinformationen für den Rest des Betriebssystems bereitstellen. Knöpfe gehören zu den am meisten verwendeten Eingabegeräten im Embedded-Bereich, und viele Prozessrechner besitzen Sensoren und GPS-Empfänger. Mit den Elementen wüsste auch der Rest von Android Things etwas anzufangen.

Als "Meisterstück" folgt ein System, in dem einige Knöpfe auf einer Steckplatine Elemente am Bildschirm beeinflussen. Aus schaltungstechnischer Sicht ist die Aufgabe nicht sonderlich komplex: Folgende Abbildung zeigt einen Weg, um den Prozessrechner mit einigen Knöpfen bekannt zu machen.

Wo ein Knopf ist, ist ein Vorwiderstand meist nicht fern.

Da das Programm mit vier GPIO-Pins arbeitet, ist die Aufteilung der Initialisierung empfehlenswert. Dazu legen Entwickler im ersten Schritt eine Funktion an, die den jeweiligen Pin parametriert:

private Gpio setupGPIO(PeripheralManagerService service,
String _x){
try{
Gpio myGpio;
myGpio=service.openGpio(_x);
myGpio.setDirection(Gpio.DIRECTION_IN);
return myGpio;
}
catch(Exception x){return null;}
}

Der Code beschafft eine Instanz des GPIO-Objekts und deklariert den Pin als Input. Der Konstruktor bevölkert ein Array mit den vier Pin-Instanzen und stößt einen Task an, der für das eigentliche Verarbeiten der Informationen zuständig ist:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PeripheralManagerService service =
new PeripheralManagerService();
myGPIO[0]=setupGPIO(service, "BCM6");
myGPIO[1]=setupGPIO(service, "BCM13");
myGPIO[2]=setupGPIO(service, "BCM19");
myGPIO[3]=setupGPIO(service, "BCM26");
ButtonSniffer myThread=new ButtonSniffer(myGPIO);
new Thread( myThread ).start();

An dieser Stelle zeigt sich ein kleines Problem: Wer Knöpfe drückt, macht mit einem als Prellen bezeichneten Phänomen Bekanntschaft. Es handelt sich dabei um eine Art Oszillation, die beim Wechsel zwischen gedrückt und nicht gedrückt auftritt.

In der Praxis gibt es eine Vielzahl von Umgehungsmöglichkeiten: Ein Klassiker wäre beispielsweise das Parallelschalten eines Kondensators oder die Nutzung einer Software. Das Beispiel fragt der Einfachheit halber den Knopf nur alle 250 Millisekunden ab:

@Override
public void run() {
try {
while(1==1) {
checkGpio(0);
checkGpio(1);
checkGpio(2);
checkGpio(3);
Thread.sleep(250);
}
}
catch (Exception e){
Log.e("Heise", e.getMessage());
}
}

Interessant ist in dem Zusammenhang, dass die oben abgebildete Schaltung ein wenig Logik voraussetzt: Ein nicht gedrückter Knopf liefert von Haus aus nämlich den Spannungspegel High, was die Methode CheckGPIO berücksichtigt:

private void checkGpio(int _p) throws Exception
{
int myCode=0;
if(_p==0)myCode= KeyEvent.KEYCODE_DPAD_UP;
if(_p==1)myCode= KeyEvent.KEYCODE_DPAD_DOWN;
if(_p==2)myCode= KeyEvent.KEYCODE_DPAD_LEFT;
if(_p==3)myCode= KeyEvent.KEYCODE_DPAD_RIGHT;
if(myGpio[_p].getValue()== false && isPressed[_p]==false)
{
isPressed[_p]=true;
myInputDriver.triggerEvent(true, myCode);
}
else if(myGpio[_p].getValue()== true
&& isPressed[_p]==true)
{
isPressed[_p]=false;
myInputDriver.triggerEvent(false, myCode);
}
}

CheckGPIO realisiert zudem über die durch Thread.Sleep erzeugte Trägheit eine Art primitives Debouncing. Pro Durchlauf der Methode ist nämlich nur eine Zustandsänderung möglich: Auch wenn der Schalter noch so viel prellt, wirkt es sich nicht weiter auf das Systemverhalten aus.

Damit stellt sich allerdings die Frage, wo beziehungsweise wie die Interaktion zwischen Betriebssystem und Knopf erfolgt. In der offiziellen Dokumentation empfiehlt Google das Nutzen eines Service, aber für die vorliegenden Situation kommt der Einfachheit halber eine Klasse namens HeiseButtonService zum Einsatz, die sich beim Erstellen folgendermaßen beim Betriebssystem anmeldet:

public HeiseButtonService() {
myDriver =
InputDriver.builder(InputDevice.SOURCE_CLASS_BUTTON)
.setName("HeiseKnopf")
.setVersion(1)
.setKeys(new int[] {KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT})
.build();
UserDriverManager manager =
UserDriverManager.getManager();
manager.registerInputDriver(myDriver);
}

Im ersten Schritt kommt die Klasse InputDriver zum Einsatz, um eine neue Treiberinstanz anzulegen, die der Code anschließend im Framework anmeldet. Folgender Ausschnitt zweigt das Auslösen eines Ereignisses in triggerEvent:

public  void triggerEvent(boolean pressed, int KEY_CODE) {
int action = pressed ? KeyEvent.ACTION_DOWN :
KeyEvent.ACTION_UP;
KeyEvent[] events =
new KeyEvent[] {new KeyEvent(action, KEY_CODE)};
if (!myDriver.emit(events)) {
Log.w("Heise", "FEHLER!");
}
}

Damit ist die Integration zwischen Betriebssystem und Ereignisquelle abgeschlossen. Zum Test soll an dieser Stelle ein kleines Benutzer-Interface dienen. Das von Google vorgegebene Beispiel enthält keine XML-Layouts. Mann muss daher im ersten Schritt in Android Studio das /res-Verzeichnis mit der rechten Maustaste anklicken und die Option New | XML | XML Layout File wählen. Dabei kann man die von der Entwicklungsumgebung vorgegebenen Werte im Großen und Ganzen übernehmen, sofern dabei eine neue XML-Datei herauskommt, die sich nach folgendem Schema in die Activity einbinden lässt:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout);

Anschließend erfolgt das Erstellen eines mehr oder weniger komplexen Layouts: Ein Klassiker wären neun unter Hilfe verschachtelter Linear-Layouts nebeneinander angeordnete Buttons. Schließlich wird die Manifestdatei zum Prozessrechner gesendet: Bei sorgfältiger Betrachtung fällt auf, dass sich der von Google TV bekannte Cursor über die Steuerelemente verschieben lässt.

Leider ist er im vorgegebenen Theme schwer
erkennbar, was sich durch Anpassung beispielsweise
mit folgendem Theme ändern lässt:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@android:style/Theme.Black">

Android Things erfüllt die Erwartung und schafft das, woran frühere Ansätze wie der von ST Micron zusammen mit dem französischen Beratungsunternehmen MicroEJ, das damals noch IS2T hieß, scheiterten : Mit so geringem Aufwand lässt sich eine attraktive Applikation für das Internet der Dinge sonst nur mit Windows 10 IoT zusammenstellen.

Wer Android-Applikationen mit einem Hardware-Add-on ausstattenmöchte, sollte einen Versuch mit Android Things wagen. Dass der Verkauf von Hardware eine sinnvolle Geschäftsstrategie sein kann, zeigt unter anderem das Hardwareportfolio des österreichischen Unternehmens Runtastic.

Tam Hanna
befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handheldcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenews-Dienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.
(rme)