Raspberry Pi – Bewegungsmelder

Raspberry Pi – Bewegungsmelder

Zu einer modernen und gelungenen Hausautomation und/oder Alarmanlage gehört heutzutage, dass man an seinem Smartphone über jede Änderung benachrichtigt wird.
Projekte mit Bewegungsmeldern an einem Raspberry Pi gibt es viele, doch wer so technikvernarrt ist wie ich, der hat bestimmt schon einmal über die Konnektivität zwischen Raspberry und Smartphone nachgedacht.

Wir (StaticFloat und www.tutorials-raspberrypi.de) haben nun ein kleines Projekt auf die Beine gestellt, welches diese Verbindung herstellt.
Lasse dich ab jetzt von deinem Android Smartphone benachrichtigen, sobald der Bewegungsmelder an deinem Raspberry Pi eine Bewegung registriert.

Als kleine Erläuterung zu diesem Projekt.
Uns ist bewusst, dass die Wenigsten eine eigene Webseite/einen eigenen Server besitzen. Aus diesem Grund benutzen wir den Google Cloud Messaging Dienst (kurz GCM).
Dieser sendet, bei Benachrichtigung vom Raspberry Pi an alle verbundenen Android Geräte eine Nachricht, mit Ort und Zeit der Bewegung. So sparen wir uns die Programmierung eines eigenen Webservices und vor allem Akku, denn Google Benachrichtigt uns und wir fragen nicht nach.
Auf www.tutorials-raspberrypi.de findet ihr alle nötigen Anleitungen, um euren Raspberry Pi einzurichten und zu programmieren. Dieser Benachrichtigt, bei Bewegung, Google.
Auf StaticFloat findet ihr die nötigen Anleitungen, um eine Benachrichtigung unter Android anzuzeigen, die wir von Google bekommen.

Der SourceCode steht unter der Apache 2.0 Lizenz,  ihr könnt die App also benutzen/verändern/veröffentlichen, wie es euch beliebt.
Bitte achtet darauf, dass ihr die google-services.json Datei, welche ihr in diesem Artikel erstellt, mit einfügt. Wie das geht ist unter dem Punkt “Vorarbeit” erklärt.

Raspberry Pi einrichten:

Wie bereits angesprochen findet ihr den Artikel zur Einrichtung und Programmierung eures Raspberry Pi nicht bei StaticFloat.
Bitte folgt also dieser Anleitung -> Raspberry Pi + Android: Benachrichtigung vom Bewegungmelder

Android App erstellen:

Solltet ihr zum ersten mal eine Android App programmieren, dann folgt bitte diesen 3 Artikeln:

Habt ihr Android Studio installiert, dann erstellt eine neue App. Dieser gebt ihr einen Namen und einen Package Namen.
Der Package Name besteht aus einem Ländercode (z. B. de), einem Entwicklernamen und dem Appnamen. Jeweils mit einem “.” getrennt.

Als minimale Version benutzt ihr 9 und ihr wählt “Blank Activity” (ab Android Studio 2.1 heißt es “Empty Activity”) aus.
Android Studio wird nun ein erstes Grundgerüst erstellen.

Vorarbeit:

Bitte folgt den Anleitungen in diesem Artikel Raspberry Pi – Google Cloud API-Key, um eure App für GCM vorzubereiten. Am Ende dieses Vorgangs wird euch der Download einer google-services.json Datei angeboten.

Diese müssen wir nun in unsere App einfügen. Klickt dazu in der linken Leiste mittels Rechtsklick auf “App” und dann auf “Im Explorer anzeigen” oder wie bei mir “Reveal in Finder”. Es öffnet sich der Ordner, in den wir die Datei einfach verschieben. Der Ordner kann danach wieder geschlossen werden.

Nun müssen wir die Dateien “gradle.build (Module: app)” und “gradle.build (Project: ????)” anpassen. Diese sind unter der Kategorie “Gradle” ebenfalls in der linken Dateiliste zu finden.

In die Datei “gradle.build (Project: ????)” fügen wir innerhalb der {}-Klammern von dependecies ein:

classpath 'com.google.gms:google-services:3.0.0'


// Das sieht in etwa so aus:
dependencies {
 classpath 'com.android.tools.build:gradle:3.0.1'
 classpath 'com.google.gms:google-services:3.0.0'
}

In die Datei “gradle.build (Module: app)” müssen wir 2 Stellen bearbeiten.
Als Erstes fügen wir ganz am Ende der Datei ein:

apply plugin: 'com.google.gms.google-services'

Und wieder fügen wir innerhalb der {}-Klammern von dependencies ein:

compile "com.google.android.gms:play-services-gcm:9.4.0"

// Das sieht dann in etwas so aus:
dependencies {
 compile fileTree(dir: 'libs', include: ['*.jar'])
 testCompile 'junit:junit:4.12'
 compile 'com.android.support:appcompat-v7:24.2.0'
 compile "com.google.android.gms:play-services-gcm:9.4.0"
}

Damit ist unsere Vorarbeit auch schon geleistet.
Wir haben Android Studio mit diesen kleinen Anpassungen nun beauftragt zur Erstellung der App die Google Play Services mit einzubinden. Dadurch bekommen wir die Möglichkeit diese zu benutzen. Das Google Cloud Messaging ist ein Teil der Google Play Services.

AndroidManifest.xml:

Wir müssen nun vor der Programmierung noch ein paar wichtige Informationen an Android weitergeben.

Im Raspberry Pi Teil dieses Projektes habt ihr auf dem Raspberry Pi mit einem Python Skript programmiert. Dieses Skript läuft, wie jedes Programm, von oben nach unten ab.
Java auf Android auch, mit dem Unterschied, dass Java eine Objektorientierte Programmiersprache ist. Dies bedeutet, dass wir Objekte erstellen können. Objekte sind hierbei im Groben und Ganzen nichts anderes als unterschiedliche Java Dateien, die in Variablen geschrieben werden können und parallel zu dem Hauptprogramm ablaufen. Wir können somit mehrere Java Dateien in eine Java Datei importieren oder mehrere Java Dateien in unserer App parallel und unabhängig von einander ausführen.

Genau bei diesem Punkt kommt die AndroidManifest.xml-Datei mit ins Spiel.
In dieser Datei steht nämlich drin, wann Android welche Klasse aufrufen und benutzen soll.
Zum Beispiel könnten wir sagen, dass Android beim Start der App die Klasse/Datei “MainActivity” öffnen soll, aber wenn Android gerade neu gestartet hat, dann soll die Klasse/Datei “Restarted” geöffnet werden.

Wir nutzen diese AndroidManifest.xml-Datei, um zu sagen, welche Berechtigungen unsere App bekommen soll und welche Klasse/Datei geöffnet werden soll, wenn unsere App eine Nachricht vom GCM Dienst bekommt.

Die AndroidManifest.xml-Datei ist in der linken Liste unter app->manifest zu finden.

Wir fügen vorerst die benötigten Berechtigungen ein.
Dazu schreiben wir folgende Zeilen innerhalb des -Tags, aber überhalb von :

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name=".permission.C2D_MESSAGE" tools:replace="android:name" android:protectionLevel="signature" />
<uses-permission android:name=".permission.C2D_MESSAGE" tools:replace="android:name" />

[EDIT]: Zu zwei der Tags muss ab Android Studio 3 nun die Info “tools:replace=”android:name”” hinzugefügt werden. Dank an Lenny.

Anschließend fügen wir folgende Zeilen innerhalb des -Tags ein:

<!-- Regelt den Empfang von Nachrichten durch Google -->
<receiver android:name="com.google.android.gms.gcm.GcmReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
 <intent-filter android:priority="1000">
 <action android:name="com.google.android.c2dm.intent.RECEIVE"></action>
 <category android:name="[Euer Packagename]"></category>
 </intent-filter>
</receiver>

<!-- Regelt den Empfang von Nachrichten durch Google -->
<service android:name=".GcmListener" android:exported="false">
 <intent-filter android:priority="1000">
 <action android:name="com.google.android.c2dm.intent.RECEIVE"></action>
 </intent-filter>
</service>

<!-- Regelt den Empfang von einer ID für unsere App durch Google -->
<service android:name=".InstanceListener" android:exported="false">
 <intent-filter>
 <action android:name="com.google.android.gms.iid.InstanceID"></action>
 </intent-filter>
</service>

<!-- Regelt die Registrierung unserer App von Nachrichten durch Google -->
<service android:name=".RegistrationIntentService" android:exported="false">
</service>


Programmierung:

Schlussendlich haben wir nun alles vorbereitet, was wir benötigen, um den Empfang von GCM Nachrichten zu regeln.
Nun gilt es noch die nötigen Klassen/Dateien zu programmieren, die wir soeben in der AndroidManifest.xml-Datei definiert haben.

RegistrationIntentService.class:

Fangen wir mit der Registrierung unserer App beim Google Server an. Diese Klasse müssen wir bereitstellen, damit Google weiß, dass unsere App die Nachrichten von unserem Raspberry erhalten kann und welche Nachrichten an uns versendet werden sollen.
Ihr erstellt diese Datei, indem ihr in der linken Liste auf app->java->(Euer Package Name ohne Test oder ähnliches) geht und mit einem Rechtsklick “new” und dann “Java class” auswählt.
Gebt hier “RegistrationIntentService” ein.

Ersetzt den Inhalt der Datei mit dem gleich folgenden Code, aber achtet drauf, dass ihr die Zeile oberste mit “package ?????;” erhaltet!

import android.app.IntentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.google.android.gms.gcm.GcmPubSub;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.google.android.gms.iid.InstanceID;

import java.io.IOException;

public class RegistrationIntentService extends IntentService {

 private static final String TAG = "RegistrationIntentService";
 private static final String[] TOPICS = {"bewegung"};

 public RegistrationIntentService() {
 super(TAG);
 }

 // Diese Funktion wird von Android aufgerufen, wenn Google unsere App über GCM registriert hat.
 @Override
 protected void onHandleIntent(Intent intent) {
 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

 try {
 InstanceID instanceID = InstanceID.getInstance(this);
 String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId),
 GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);

 subscribeTopics(token);

 sharedPreferences.edit().putBoolean(Einstellungen.SENT_TOKEN_TO_SERVER, true).apply();
 } catch (Exception e) {
 sharedPreferences.edit().putBoolean(Einstellungen.SENT_TOKEN_TO_SERVER, false).apply();
 }

 Intent registrationComplete = new Intent(Einstellungen.REGISTRATION_COMPLETE);
 LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
 }
 // Mit dieser Funktion registrieren wir unsere App für das Thema "bewegung".
 // Alle Push Nachrichten, die unser Pi über GCM mit dem Thema/topic "bewegung" sendet werden ab jetzt von Google an diese App weitergeleitet.
 private void subscribeTopics(String token) throws IOException {
 GcmPubSub pubSub = GcmPubSub.getInstance(this);
 for (String topic : TOPICS) {
 pubSub.subscribe(token, "/topics/" + topic, null);
 }
 }

}

InstanceListener.class:

Diese Klasse müssen wir bereitstellen, damit unsere App die Registrierung auf dem Google Server als bestätigt ansehen kann.
Ihr erstellt diese Datei, indem ihr in der linken Liste auf app->java->(Euer Package Name ohne Test oder ähnliches) geht und mit einem Rechtsklick “new” und dann “Java class” auswählt.
Gebt hier “InstanceListener” ein.

Ersetzt den Inhalt der Datei mit dem gleich folgenden Code, aber achtet drauf, dass ihr die oberste Zeile mit “package ?????;” erhaltet!

import android.content.Intent;
import com.google.android.gms.iid.InstanceIDListenerService;

public class InstanceListener extends InstanceIDListenerService {

 // Diese Funktion wird aufgerufen, falls sich der Token unserer Registrierung bei GCM ändert und speichert den neuen Token wieder über die Klasse "RegisterIntentService".
 @Override
 public void onTokenRefresh() {
 Intent intent = new Intent(this, RegistrationIntentService.class);
 startService(intent);
 }
}

GcmListener.class:

Diese Klasse müssen wir bereitstellen, damit unsere App alle Nachrichten auch empfangen kann.
Ihr erstellt diese Datei, indem ihr in der linken Liste auf app->java->(Euer Package Name ohne Test oder ähnliches) geht und mit einem Rechtsklick “new” und dann “Java class” auswählt.
Gebt hier “GcmListener” ein.

Ersetzt den Inhalt der Datei mit dem gleich folgenden Code, aber achtet drauf, dass ihr die oberste Zeile mit “package ?????;” erhaltet!

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import com.google.android.gms.gcm.GcmListenerService;

import org.json.JSONException;
import org.json.JSONObject;

public class GcmListener extends GcmListenerService {

 @Override
 public void onMessageReceived(String from, Bundle data) {
 String jsonString = data.getString("message");

 String ort = "Unbekannt";
 Long zeitUnix = 0L;
 DateFormat formatter ;
 Date date = null;
 formatter = new SimpleDateFormat("HH:mm dd.MM.yyy");

 try {

 // JSON einlesen
 JSONObject message = new JSONObject(jsonString);

 // Lese die Zeit und den Ort aus und erstelle aus der Unix Zeit ein lesbares Datum.
 ort = message.getString("O");
 zeitUnix = message.getInt("T") * 1000L;
 date = new java.util.Date(zeitUnix);

 // Prüfe, ob es sich bei der Benachrichtigung um das richtige Thema handelt.
 if (from.startsWith("/topics/bewegung")) {
 sendNotification(ort + " - " + formatter.format(date), getIdentifier(ort));
 }

 } catch (JSONException e) {
 e.printStackTrace();
 }

 }

 // Erstelle aus jedem Ort eine eindeutige ID, damit es für jeden Ort eine eigene Banchrichtigung gibt.
 public int getIdentifier(String str) {
 try {
 int id = 0;
 for (Byte t : str.getBytes("UTF-8")) {
 id += t.intValue();
 }
 return id;
 } catch (UnsupportedEncodingException e) {
 return 0;
 }
 }

 // Gib eine Benachrichtigung aus.
 private void sendNotification(String message, int id) {
 Intent intent = new Intent(this, MainActivity.class);
 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
 PendingIntent.FLAG_ONE_SHOT);

 Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
 NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
 .setSmallIcon(R.mipmap.ic_launcher)
 .setContentTitle("Bewegungsmelder")
 .setContentText(message)
 .setAutoCancel(true)
 .setSound(defaultSoundUri)
 .setContentIntent(pendingIntent);

 NotificationManager notificationManager =
 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

 notificationManager.notify(id, notificationBuilder.build());
 }
}

[EDIT]: Der Import lautet ab Android Studio 3 android.support.v4.app.NotificationCompat und nicht mehr android.support.v7.app.NotificationCompat. Dank an Lenny.

Einstellungen.class:

Diese Klasse ist nicht unbedingt notwendig. Sie enthält einige Einstellungsnamen, die wir in verschiedenen Klassen aufrufen. Wir benutzen die Klasse Einstellungen, damit wir bei Änderungen der Einstellungsnamen nur diese Klasse ändern müssen und nicht in jeder verwendenden Klasse. Wir sparen uns also Arbeit.
Ihr erstellt diese Datei, indem ihr in der linken Liste auf app->java->(Euer Package Name ohne Test oder ähnliches) geht und mit einem Rechtsklick “new” und dann “Java class” auswählt.
Gebt hier “Einstellungen” ein.

Ersetzt den Inhalt der Datei mit dem gleich folgenden Code, aber achtet drauf, dass ihr die oberste Zeile mit “package ?????;” erhaltet!

public class Einstellungen {

 public static final String SENT_TOKEN_TO_SERVER = "Token";
 public static final String REGISTRATION_COMPLETE = "registrationComplete";

}

Erster Start:

Beim ersten Start der App muss die App sich nun bei Google registrieren. Ersetzt den Inhalt der Datei “MainActivity” mit dem gleich folgenden Code, aber achtet drauf, dass ihr die oberste Zeile mit “package ?????;” erhaltet!

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {
 
 private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
 private boolean isReceiverRegistered = false;
 private BroadcastReceiver mRegistrationBroadcastReceiver;

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

 // Prüfe, ob auf dem Gerät der Google PlayService installiert ist.
 // Dieser wird für den Empfang der GCM Nachrichten benötigt.
 if(checkPlayServices()) {

 // Erstelle einen Listener, der immer dann ausgeführt wird, wenn die App bei GCM registriert wurde oder ein Fehler bei der Registrierung auftrat.
 mRegistrationBroadcastReceiver = new BroadcastReceiver() {
 @Override
 public void onReceive(Context context, Intent intent) {
 SharedPreferences sharedPreferences =
 PreferenceManager.getDefaultSharedPreferences(context);
 boolean sentToken = sharedPreferences
 .getBoolean(Einstellungen.SENT_TOKEN_TO_SERVER, false);
 }
 };

 // Starte den oben beschriebenen Listener.
 registerReceiver();

 // Starte einen IntentService, um die App bei GCM zu registrieren.
 Intent intent = new Intent(this, RegistrationIntentService.class);
 startService(intent);

 }


 }

 private boolean checkPlayServices() {
 GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
 int resultCode = apiAvailability.isGooglePlayServicesAvailable(this);
 if (resultCode != ConnectionResult.SUCCESS) {
 if (apiAvailability.isUserResolvableError(resultCode)) {
 apiAvailability.getErrorDialog(this, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST)
 .show();
 } else {
 finish();
 }
 return false;
 }
 return true;
 }

 // Ab hier folgen Fehlerkorrekturen, damit die App auch beim Neustart funktioniert.
 @Override
 protected void onResume() {
 super.onResume();
 registerReceiver();
 }

 @Override
 protected void onPause() {
 LocalBroadcastManager.getInstance(this).unregisterReceiver(mRegistrationBroadcastReceiver);
 isReceiverRegistered = false;
 super.onPause();
 }

 private void registerReceiver(){
 if(!isReceiverRegistered) {
 LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationBroadcastReceiver, new IntentFilter(Einstellungen.REGISTRATION_COMPLETE));
 isReceiverRegistered = true;
 }
 }
}

Schlusswort:

Nun könnt ihr die App von Android Studio erstellen lassen und beim ersten Start der App registriert sich diese bei Google.
Probiert nun einfach aus, was passiert, wenn der Bewegungsmelder etwas registriert.
Die Benachrichtigungen erfolgen auch bei einem Neustart von Android.

Wir werden versuchen in näherer Zukunft auch eine einfache Android App im Google PlayStore zu veröffentlichen, mit der die Einrichtung einfacher wird.

Weitere Raspberry Pi + Android Smartphone Projekte findet ihr auf StaticFloat und www.tutorials-raspberrypi.de.

[DM id=”644″]

Marvin

Ich bin 23 Jahre jung und studiere zurzeit Wirtschaftsinformatik an der Georg-August-Universität in Göttingen. Ich bin ein Mensch, der sich neben der Programmierung noch für tausend andere Dinge interessiert, die mal mehr und mal weniger verrückt sind. Vor allem aber bin ich Feuer und Flamme mit der Programmierung von eigenen kleinen Apps und Programmen, die mein Leben bereichern.

Kommentar hinzufügen

*Pflichtfeld

Beim Punkt AndroidManifest.xml findet sich kein Code im Codefenster. Was sollte da stehen? Danke im Voraus, David