Android der Dinge
Lass es blinken
Zum Zeitpunkt der Drucklegung unterstützt Android Things fünf verschiedene Schnittstellen für Drittanbieterhardware: hinter der GPIO-API verbirgt sich die Möglichkeit, auf digitale Ein- und Ausgabepins zurückzugreifen. Analoge Ausgabe wird nur über Pulsbreitenmodulation unterstützt – für die in manchen SoCs enthaltenen Analog-Digital bzw Digital-Analog-Wandler hat Google bisher keine API spendiert.
Ob der Java-inhärenten Probleme mit der Latenz ist Android Things für Bit Banging eher schlecht als recht geeignet: zur Kommunikation mit komplexerer Hardware unterstützt Google deshalb I2C, SPI und UART.
Von besonderem Interesse ist in diesem Zusammenhang die unter https://github.com/androidthings/contrib-drivers bereitstehende Sammlung von User Drivers: es handelt sich dabei um von Google oder von Drittentwicklern angebotene Treiber, die bestimmte Hardwarekomponenten direkt aus Java heraus ansprechbar machen. Momentan finden sich dort rund zwei Dutzend Treiber, die verschiedene Aspekte der Programmierung für Android Things beleuchten.
Google geht bei der Ansprache von Hardwaregeräten unter Android Things von Strings aus: Jedes adressierbare Gerät wird auf Seiten des Entwicklers über einen String angesprochen.
Die genauen Strings können Sie entweder zur Laufzeit unter Nutzung von Methoden des Frameworks ermitteln, oder aber auf die in Bild 4 gezeigte Liste zurückgreifen.
Wer im Bereich Hardware tätig ist, sollte immer eine Gruppe von Leuchtdioden im Haus haben – haben Sie dies nicht, so schafft AliExpress gerne Abhilfe. Das Realisieren eines Lauflichts spricht das innere Kind im Entwickler an, und ist zudem nicht allzu kompliziert. Wir wollen uns deshalb mit der in Bild 5 gezeigten Schaltung begnügen, die vier verschiedenfarbige LEDs an den Prozessrechner anschliesst.
Die Auswahl des Widerstandswerts ist hierbei relativ unkritisch: Richten Sie sich nach der Strommenge, die ihre LEDs benötigen. Die einzelnen Pins des Raspberry Pi können pro Pin bis zu 15 Milliampere liefern, der gesamte Port liefert maximal 50mA. Da das SoC allerdings nicht für klassische Embeddedaufgaben vorgesehen ist, empfiehlt sich an dieser Stelle Zurückhaltung.
Im nächsten Schritt müssen wir dafür sorgen, dass die Pins ansprechbar werden. Als erstes ist hierbei ein Feld von Gpio-Instanzen erforderlich, das als Member der MainActivity entsteht:
public class MainActivity extends Activity {
Gpio myPins[]=new Gpio[4];
Das Einrichten eines Arrays von GPIO-Instanzen lohnt sich an dieser Stelle insofern, als es in Android Things ob der Strings nur schwierig möglich ist, Operationen auf eine Gruppe von Geräten auszuführen. Sind die einzelnen Wrapper in einem Feld, so können Sie den Index zum numerisch gesteuerten Ansprechen einzelner Pins benutzen.
Im nächsten Schritt müssen wir die Gpio-Instanzen initialisieren. Hierzu dient folgender Code, den Sie im Idealfall in onCreate platzieren:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PeripheralManagerService service = new PeripheralManagerService();
try {
myPins[0] = service.openGpio("BCM6");
myPins[0].setDirection(Gpio.DIRECTION_OUT_INITIALLY_HIGH);
myPins[1] = service.openGpio("BCM13");
myPins[1].setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
myPins[2] = service.openGpio("BCM19");
myPins[2].setDirection(Gpio.DIRECTION_OUT_INITIALLY_HIGH);
myPins[3] = service.openGpio("BCM26");
myPins[3].setDirection(Gpio.DIRECTION_OUT_INITIALLY_HIGH);
}
catch (Exception e){}
}
Aufmerksamen Lesern fällt auf, dass Google bei der Realisierung des Gpio-Moduls von Android Things mitgedacht hat. Die Initialisierung beginnt - wie sollte es unter Android anders sein - mit dem Beschaffen einer Instanz eines als PeripheralManagerService bezeichneten Betriebssystemservices, der als Schnittstelle zwischen ihrer Applikation und der unterliegenden Hardware dient. Im nächsten Schritt wird die Methode openGpio aufgerufen, um die jeweiligen Objekte zu erzeugen.
GPIO-Pins können als Eingang oder als Ausgang agieren. Google erlaubt in setDirection das Festlegen des Betriebsmodus. Im Fall eines Ausgangs fragt das Betriebssystem an dieser Stelle ab, welchen Logikpegel die jeweiligen Pins initial einnehmen sollen. Dadurch entfällt der in anderen Betriebssystemen bzw. HALs erforderliche Tanz mit Direction und SetValue ersatzlos.
Während man in einem klassischen Echtzeit-Betriebssystem tun und lassen kann, was man möchte, muss man sich unter Android Things mit strengeren Regeln abgeben: Das zugrunde liegende Betriebssystem ist nach wie vor Android, weshalb Nettigkeiten wie der ANR-Dialog mit von der Partie sind.
Zum Realisieren eines regelmässigen Ein- und Ausschaltens wollen wir hier auf einen Handler zurückgreifen, der samt der zu ihm gehörenden Runnable-Klasse in MainActivity entsteht. Diese Vorgehensweise hat den Vorteil, dass das Runnable als innere Klasse der Activity betrachtet wird und auf das Feld myPins direkt zugreifen kann.:
public class MainActivity extends Activity {
Gpio myPins[]=new Gpio[4];
android.os.Handler myHandler;
private Runnable myRunnable = new Runnable() {
Die eigentliche run-Methode ist dann primitiv. Sie iteriert über alle in myPins enthaltenen Gpio-Objekte, invertiert den Wert und schreibt diesen zurück:
@Override
public void run() {
try {
for(int i=0;i<4;i++){
myPins[i].setValue(!myPins[i].getValue());
}
Nach getaner Arbeit rufen wir abermals postDelayed auf. Der numerische Parameter legt dabei fest, wie viele Millisekunden das Betriebssystem warten soll. Damit ist sichergestellt, dass sich der Handler nach einmaliger Aktivierung immer wieder aktiviert:
myHandler.postDelayed(myRunnable, 500);
} catch (Exception e) {
Log.e("NMG", e.getMessage());
}
}
};
Leider müssen wir den ersten Durchlauf von Hand anstossen, weshalb wir im Try-Catch-Block nach der Initialisierung der GPIO-Wrapperobjekte folgenden Code platzieren:
try {
. . .
myHandler=new Handler();
myHandler.post(myRunnable);
}
Google hielt sich bei der Implementierung der Android-Things-API streng an die Vorgaben aus dem Hause Oracle: wundern sie sich nicht darüber, dass so gut wie alle Funktionen eine Vielzahl von Exceptions werfen.