Funksteckdosen über den Status des Smartphones (Akku, Wlan etc.) steuern – Teil 1

Wenn ich über das Steuern der Funksteckdosen geschrieben habe, war immer die Rede von selbst ausgelösten Funktionen. Man musste die Steckdose über das Smartphone selbst ein – und ausschalten. Für viele Einsatzzwecke reicht diese Methode vollkommen aus und ist sogar wünschenswert. Aber es gibt Situation wo eine Automatisierung des Vorgangs angebrachter wäre. Was zuerst bei solchen Ideen rauskommt ist eine zeitlich gesteuerte Lösung. Jeder von uns kennt ja die Zeitschaltuhren oder digital umgesetzte Timer. Hier wird die Aktion zeitlich ausgelöst und kann nicht durch andere Faktoren beeinflusst werden. Der Nachteil hierbei ist, dass man eben nur zeitlich agieren kann. Warum also nicht die gewünschten „Events“ abfangen und dann passend die entsprechende Steckdose schalten? Genau das werden wir machen. Für den Anfang habe ich an zwei Einsatzmöglichkeiten gedacht. Ich lasse mein Smartphone immer über Nacht laden. Egal wie viel Akku ich noch habe, das Smartphone wird drangehängt. Jetzt soll das Smartphone bei 100% Akku die Steckdose automatisch ausschalten. So wird nicht nur Strom gespart (wobei die Funksteckdose auch im „ausgeschaltetem“ Zustand bestimmt etwas über 1 Watt zieht) sondern auch das Smartphone „hängt“ nicht ständig am Strom. Der andere Einsatzzweck ist eine Aquariumpumpe. Diese soll nur dann laufen, wenn ich das Haus verlasse. Komme ich nach der Arbeit nach Hause wäre eine automatische Abschaltung nicht schlecht. Hier ist der Wechsel des WLAN’s der entsprechende Trigger für das Ereignis. Nun gut genug geschrieben. Lass uns anfangen zu programmieren.

Eine kleine Anmerkung noch. Das ganze Projekt entsteht live. Das heißt es können und
"bestimmt" werden Programmierfehler entstehen die wir im Nachhinein ausbessern.

Teil 1 – BroadcastReceiver

Um bestimmte Statusmeldungen des Smartphones abzufangen, setzt Android auf die sogenannten Intents. Ein Intent kann die Verbindung zwischen zwei unterschiedlichen Komponenten im Android herstellen. Wenn wir ein Anruf bekommen dann können wir auf dieses Ereignis mithilfe eines Intents zugreifen um z.B die Telefonnummer des Anrufers auszulesen. Was wir nicht wissen ist, das Android ständig Events (also die Intents) durch die Gegend schiebt. Mal wurde ein neues WLAN gefunden, es gibt eine neue SMS, die Batterie wird geladen oder die Uhrzeit ändert sich. Alle diese Events schwirren durch die Gegend und können von uns abgefangen werden. Für diesen Zweck wurde der BroadcastReceiver entwickelt. Dieser fängt die Events ab und meldet uns die Informationen die gerade das Smartphone absetzt. Mit diesem kleinem Helfer können wir also auch auf den Status der Batterie und des Netzwerkes zugreifen.
Wir erstellen also ein neues Android Projekt. In meinem Fall heißt das Projekt „PlugySwitcher“. Wie üblich erstelle ich ein Projekt für die API Version 15 vom Android, das entspricht Android 4.0.3. Ihr könnt aber auch gerne für eine andere Version entwickeln. Die Klasse für die Activity lassen wir zuerst ruhig leer. Bei mir sieht die so aus:

public class MainActivity extends android.app.Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);
    }
}

Das Layout für die Activity können wir erstmal auch leer lassen. Das erste worum wir uns kümmern werden ist eine neue Klasse die unseren BroadcastReceiver verwaltet. Wir erstellen also eine neue Klasse „MainReceiver“. Diese wird um die Superklasse „BroadcastReceiver“ erweitert. Ich zeige euch erstmal die ganze Klasse und dann gehen wir diese Schritt für Schritt durch.

public class MainReceiver extends BroadcastReceiver{

    private boolean battery_trigger = false;
    private boolean wlan_trigger = false;
  
    private final String SSID = "\"SSID\"";
    private final int BATTERYLEVEL = 100;
  
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
    
        //Batterie wird mit AC geladen + 100 + !trigger
        if (action.equals(Intent.ACTION_BATTERY_CHANGED)){
            int chargeType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
            int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            if (chargeType == BatteryManager.BATTERY_PLUGGED_AC 
                && level == BATTERYLEVEL && battery_trigger == false){
                Toast.makeText(context, "Power On - AC and Battery full", Toast.LENGTH_LONG).show();
                battery_trigger = true;
            }
        }
        //Batterie wird nicht mehr geladen
        if (action.equals(Intent.ACTION_POWER_DISCONNECTED)){
            Toast.makeText(context, "Power disconnected", Toast.LENGTH_SHORT).show();
            battery_trigger = false;
        }
    
        //WLAN Netzwerk hat sich verändert
        if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)){
            NetworkInfo netInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
            if (netInfo.isConnected()){
                WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                if (wifiMan.getConnectionInfo().getSSID().equals(SSID) && wlan_trigger == false){
                    Toast.makeText(context,"Richtiges WLAN:" + SSID, Toast.LENGTH_LONG).show();
                    wlan_trigger = true;
                }else{
                    wlan_trigger = false;
                }
            }
        }    
    }
}

Zuerst brauchen wir zwei Variablen vom Typ boolean „battery_trigger“ und „wlan_trigger“. Diese dienen als Trigger und verhindern, dass die Events mehrfach unsere Funktion auslösen . Diese setzen wir zuerst Default auf „false“. Danach erstellen wir noch eine final String Variable „SSID“ wo wir die SSID des WLAN’s eintragen auf welche „gehorcht“ werden soll. Die andere final Variable ist vom Typ int „BATTERYLEVEL“ und gibt den Level der Batterie an ab welchem der Event auslösen soll. Laut der Konvention werden final und static Variablen immer groß geschrieben. Danach folgt schon die Methode „onReceive“ die wir so oder so aus der Klasse „BroadcastReceiver“ automatisch implementieren. Diese wird dann ausgelöst wenn der Receiver im System registriert und ein entsprechendes Intent ausgelöst wurde, aber dazu später mehr. Als Parameter nimmt die Methode die Objekte Context und Intent entgegen.  Der Context ist ein Interface also eine Schnittstelle zwischen der Applikation und dem Android System. Das Intent Objekt beinhaltet das ausgelöste Event. Wird ein solches Event ausgelöst, müssen wir zuerst wissen was für ein Event das überhaupt ist. Dafür nehmen wir die Methode  „getAction()“ und speichern das Ergebnis in eine String Variable „action“. Danach folgt die erste Abfrage, die überprüft ob die ausgelöste Aktion irgendwas mit der Veränderung der Batterie zu tun hat. Dafür hat die Klasse „Intent“ mehrere solcher Auslöser. In unserem Fall brauchen wir das Event „ACTION_BATTERY_CHANGED„. Dieser beinhaltet mehrere Informationen über die Batterie wie zum Beispiel den Ladestatus oder den Akkustand. Nun müssen wir zuerst wissen ob die Batterie über die Steckdose geladen wird und nicht über USB am Rechner. Dafür erstellen wir eine int Variable und holen uns über die Methode „getIntExtra“ die gewünschte Information aus dem Intent mithilfe der Klasse „BatteryManager“ und der Zahl hinter der Variable „EXTRA_PLUGGED“. Die „-1“ in der Methode ist nur ein Defaultwert. Die Variable „EXTRA_PLUGGED“ kann drei Werte enthalten 0 = Batterie , 1 (BATTERY_PLUGGED_AC) = Steckdose, 2 (BATTERY_PLUGGED_USB) = USB und 4 (BATTERY_PLUGGED_WIRELESS) = kapazitives laden. Den Wert den wir erhalten speichern wir uns in die Variable „chargeType“. Die zweite int Variable „level“ holt die Zahl hinter der Variable „EXTRA_LEVEL“. Diese beinhaltet den Wert zwischen 0 und der Variable „EXTRA_SCALE“ im Normalfall 100 also den maximalen Stand der Batterie. Nun können wir abfragen ob alles stimmt um unsere Funktion auszulösen. Die IF Abfrage schaut ob das Smartphone an der Steckdose hängt, das Level unserem „BATTERYLEVEL“ (100) entspricht und der Batterie-Trigger auf „false“ steht. Sind alle Bedienungen erfüllt wird zuerst eine kleine Info auf dem Bildschirm angezeigt. Hier werden wir natürlich im weiteren Verlauf des Tutorials die gewünschte Steckdose über das Netzwerk schalten. Zusätzlich wird noch der Trigger auf „true“ gesetzt. Das ist wichtig, weil wir die Aktion hinter der IF Abfrage nur einmal auslösen wollen. Aber wann setzen wir dann den Trigger wieder auf „false“? Ganz einfach wenn wir das Telefon vom Strom abziehen. Dafür schauen wir ob die Variable „action“ also das Intent selbst die Aktion „ACTION_POWER_DISCONNECTED“ meldet. Dann wird der Trigger wieder auf „false“ gesetzt bis das Smartphone irgendwann wieder am Strom hängt und voll geladen wurde. Damit hätten wir den ersten Teil mit der Batterie fertig. Jetzt kümmern wir uns um das WLAN. Dafür schauen wir ob das Intent die Aktion „NETWORK_STATE_CHANGED_ACTION“ meldet. Dieser Intent kommt von der Klasse „WifiManger“ und wird ausgelöst wenn das Netzwerk sich ändern sollte. Das reicht uns aber nicht. Deshalb holen wir uns aus dem Intent über die Methode „getParcelableExtra“ mit dem Wert „EXTRA_NETWORK_INFO“ ein NetworkInfo Objekt „netInfo“. Damit können wir auf die Methode „isConnected()“ zugreifen. Diese gibt uns „true“ zurück wenn eine Netzwerk Verbindung besteht und Daten gesendet und empfangen werden können. Das fragen wir in der ersten IF Abfrage ab. Danach brauchen wir ein Objekt vom Typ „WifiManager„. Das Objekt selbst bekommen wir über den Context und dessen Methode „getSystemService“. Als Service geben wir den WIFI_SERVICE an. Damit haben wir Zugriff auf mehrere Funktionen der WLAN Verbindung. In der nächsten Abfrage vergleichen wir ob die bestehende WLAN Verbindung der von uns angegebenen (SSID) entspricht. Das erfolgt durch die Methoden „getConnectionInfo()“ und „getSSID()“. Da das Ergebnis immer in Anführungszeichen („testwlan“) ausgegeben wird müssen wir in unserer Variable „SSID“ die Anführungszeichen escapen. Zusätzlich wird überprüft ob der Wlan-Trigger auf „false“ steht. Das dient auch nur dazu um das Ereignis nur einmal auszuführen. Wurde die Bedienung erfüllt und befinden wir uns im richtigen WLAN dann erscheint auch hier „erstmal“ nur eine kleine Nachricht und der Trigger wird auf „true“ gesetzt. Befinden wir uns nicht mehr im richtigen WLAN oder weichen auf das mobile Internet aus wird der Trigger wieder auf „false“ gesetzt. Damit hätten wir den ersten Schritt gemacht. Aber der Receiver muss doch noch irgendwie gestartet werden, oder? Damit wir den Receiver einmal testen können brauchen wir nochmal die MainActivity.

public class MainActivity extends android.app.Activity {
    private BroadcastReceiver br;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);

        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    
        br = new MainReceiver();
        registerReceiver(br,filter);
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(br);
        super.onDestroy();
    }
}

Bevor wir den Receiver registrieren brauchen wir einen sogenannten IntentFilter. Da es unzählige Intents gibt und diese ständig durch das ganze System jagen, würde unser BroadcastReceiver ständig überprüfen ob die passenden Intents dabei sind. Wenn wir aber ein neues Objekt vom Typ „IntentFilter“ erstellen „filter“ und diesem über die Methode „addAction“ die Intents mitgeben auf welche der BroadcastReceiver horchen soll, ist es überhaupt kein Problem mehr. Nach der Registrierung würde der BroadcastReceiver nur dann „anspringen“ wenn eins von den drei oben angegebenen Intents ausgelöst worden sind. Um den Receiver zu registrieren erstellen wir zuerst ein neues Objekt „br“ vom Typ BroadcastReceiver den wir zuvor oben in der Klasse deklarieren. Über die Methode „registerReceiver“ können wir ihn dann über das Context der Applikation registrieren zusammen mit dem Filter. Wird die Methode „onDestroy“ aufgerufen wenn die Anwendung beendet wird so wird auch der Receiver aus dem System entfernt. Das würde auch dann passieren wenn wir es nicht explizit angegeben hätten. Bevor wir jetzt die Applikation ausführen, brauchen wir noch die entsprechenden Permissions für das auslesen des Netzwerkes.  Einfach folgendes in das Manifest eintragen:

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Starten wir die Anwendung dann wird auch der Receiver im System gestartet. Diesen sehen wir nicht und er läuft solange wie unsere Anwendung. Um zu testen ob alles Funktioniert kann man natürlich erstmal in der Klasse „MainReceiver“ die IF – Abfragen vereinfachen:

if (chargeType == BatteryManager.BATTERY_PLUGGED_USB 
    &&  battery_trigger == false){
    ...
}

Hier würden wir jedes mal wenn wir das Smartphone am USB des Rechners anschließen eine Meldung bekommen. So aber sind wir jetzt eigentlich fertig? Nein noch lange nicht. Das Hauptproblem ist das unsere Anwendung ständig laufen muss damit der BroadcastReceiver funktioniert. Aber ich habe keine Lust die Anwendung zu starten wenn ich mein Telefon an das Ladegerät anschließe oder ich das Haus verlasse. Damit wäre die Automatisierung futsch. In diesem Fall ist ein Service also ein Dienst im Hintergrund die Lösung. Der soll sich um unseren Receiver kümmern ohne das wir diesen extra über die Anwendung starten müssen. Auch ein automatischer Start des Dienstes nach dem hochfahren des Telefons wäre von Vorteil. Aber das behandeln wir erst im Teil 2.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.