Nach sehr langer Pause hat mich wieder einmal die Lust am Basteln und Experimentieren gepackt… Also, auf geht's:
Inhalt
Was ist Geofencing?
Im Zuge des Vormarschs von Internet of Things (IoT) Produkten kommt vermehrt auch Geofencing zum Einsatz. Geofencing ist ein Kunstwort, zusammengesetzt aus den Begriffen "Geo" (Erde oder auch abgekürzt für Geographie etc.) und "Fence" (engl.: Zaun). Ein Geofence ist ein virtueller Zaun um einen bestimmten Punkt herum, bei dessen Überschreiten in die eine oder andere Richtung ein Ereignis (Heizungssteuerung, Licht an/aus, Garagentor auf/zu etc.) ausgelöst wird.
Beispielsweise bietet Tado smarte Heizkörper Thermostate an, die beim Verlassen deiner Wohnung die Temperatur senken und beim Annähern die Temperatur hochstellen. Clever! Ebenso bietet z.B. Viessmann eine kostenpflichtige Erweiterung seiner Vicare Lösung an, die Geofencing beinhaltet. Natürlich gibt es noch viele andere Anwendungsfälle.
In der Regel ist dafür eine App auf dem Smartphone installiert, die laufend den eigenen Standort abfragt und bei Überschreiten des virtuellen Zauns ein Kommando auslöst. Das Kommando wird in der Regel über die Cloud des Anbieters geleitet. Das hat den Vorteil, dass ein – hoffentlich – gut geschützter Server existiert, der 24/7 empfangsbereit ist und der seinerseits die empfangenen Kommandos verschlüsselt und abgesichert an die IoT Steuerung zu hause bei dir weiterleitet. Ich persönlich mag solche Clouds nicht besonders, da vor allem Nicht-EU Anbieter die Daten meist weiterverkaufen.
Für mich selbst habe ich eine einfache Lösung gesucht, die meine Außenbeleuchtung einschaltet, wenn ich abends nach Hause komme – und das möglichst ohne irgend eine Cloud eines mehr oder minder obskuren Anbieters zu nutzen.
Was benötige ich dazu?
- ein iPhone oder iPad mit installierter kostenloser Locative App, Zugriff auf Standort muss auf 'immer' eingestellt sein
- einen Raspberry Pi mit irgend einem Webserver und php 7.4
- eine aus dem Internet erreichbare Webadresse
- Alternativ kann man auch einen anderen aktiven Webserver (z.B. bei einem Provider) nehmen, sofern man dort php hinzugebucht hat. Ein reiner Webspace reicht nicht aus. Bei so einer Lösung kann man natürlich nicht innerhalb seines privaten Netzwerks arbeiten sondern muss alles übers Internet abwickeln.
Zielgruppe – Disclaimer
Dieser Artikel wendet sich an wohlmeinende Amateure, die gerne mehr über Geofencing wissen wollen und eventuell selbst eine Lösung bauen möchten. Da ich beileibe kein professioneller Programmierer bin, werden manche Profis und Experten sicher über den einen oder anderen Punkt schmunzeln oder die Stirn runzeln – ich erhebe keinerlei Anspruch auf Professionalität oder "Wasserdichtigkeit" meiner vorgestellten Experimente.
Die Locative App
Im Apple App Store habe ich die kostenlose App Locative von Bearologics gefunden, die genau das tut was ich will. Sie kann auf einem iPhone oder iPad bis zu 20 virtuelle Zäune aufspannen und entsprechend Kommandos an einen beliebigen Empfänger (Server) schicken. Das kann ein bei einem Webhoster liegender Server sein oder, wie in meinem Fall, ein selbst betriebener Server auf einem Raspberry Pi. Dieser Server sollte allerdings aus dem Internet erreichbar sein, sonst bringt die ganze Chose nichts.
Das Besondere an Locative ist, dass es komplett ohne "company in the middle" auskommt. Außerdem erhebt Bearologic lt. Auskunft des App Stores auch keinerlei Nutzerdaten.
Die kostenfreie App ist weitgehend ohne Dokumentation, erschließt sich aber im Laufe der Experimente von selbst. Auch das Userinterface erscheint manchmal etwas unausgereift – die wichtigsten Punkte erkläre ich aber hier.
Es scheint auch eine ältere Version für Android zu geben (APK Download aus 2018), inwieweit diese funktioniert, kann ich nicht sagen. Wer Android nutzt, hat m.E. sowieso weniger Bedenken, seine Daten an Datenkraken wie Google abzutreten; also ist eine datensparsame Lösung wie Locative möglicherweise nicht so relevant.
Wichtig zu wissen: Für viele fertige IoT Hausautomatisierungssysteme wie FHEM, Home Assistant etc. gibt es entsprechende Locative Adapter. Wer so ein Teil sein eigen nennt, braucht nicht unbedingt weiterlesen.
Im nächsten Beitrag erkläre ich, wie man Locative mit Node-Red zum Fliegen bringt.
Einrichten der App
Die App hat insgesamt nur vier Screens:
- Den Orte Screen zum Einrichten und Konfigurieren der maximal 20 Geofences
- Den Benachrichtigungen Screen, auf dem dann im Echtbetrieb die Ereignisse (kommen/gehen) angezeigt werden. Beim Testen hat dieser Screen keine Funktion.
- Den Einstelllungen Screen zum Aktivierung von Benachrichtigungen, globale Einstellungen, die auch beim Testen verwendet werden. Die globalen Einstellungen werden als Default auch im Orte Screen übernommen. Ganz wichtig ist noch die Funktion Debugging bei der Testrequests generiert und verschickt werden.
- Last but not least den Info Screen, dessen wichtigste Funktion die Diagnose mit Logging ist.
Geofence
Das setzen des Bereichs-Mittelpunktes erfolgt unter "Orte" über die Eingabe der Adresse. Nachteilig: Will man einen Punkt im freien Gelände setzen, muss man sich dort vor Ort befinden und "Benutze den aktuellen Standort" auswählen.
Der Umfang des "Zauns" kann von 50m bis 226km eingestellt werden.
Die Location ID kann frei vergeben werden oder man lässt Locative eine ID vergeben
Webhook
Ein Webhook ist nichts Anderes als die Empfängeradresse mit einem entsprechenden Programm, dass die von Locative übertragenen Daten empfängt, weiterverarbeitet und ggf. darauf antwortet. Zum Experimentieren reicht es völlig, sich innerhalb des eigenen privaten Netzwerks zu bewegen. Im Echtbetrieb würde hier natürlich eine aus dem Internet erreichbare URL stehen.
Es gibt zwei verschiedene Trigger-Zustände, nämlich "enter" und "exit" also Ankunft und Verlassen des Geofences.
GET/POST
Es gibt zwei verschiedene Arten, die Daten an den Empfänger zu übertragen: GET und POST. GET ist etwas einfacher aber unsicher, POST ist etwas komplizierter aber leistungsfähiger und sicherer. Dazu nachher mehr.
Testrequest
Da ja wohl kaum jemand dauernd physisch den Geofence überschreiten möchte, um die Funktion zu testen, gibt es netterweise die Möglichkeit, einen "Ankunft"-Testrequest auszulösen. Der Testreqest befindet sich auf dem Tab "Einstellungen".
Es empfiehlt sich, alle 3 Benachrichtigungsschalter zu aktivieren.
Die URL bei "Globale HTTP Einstellungen" muss unbedingt eingetragen werden, es werden keine Werte aus dem Geofence Screen übernommen. Wichtig ist auch, vor die URL ein http:// oder https:// zu schreiben, sonst funktioniert der Aufruf nicht.
Je nach Serveranwendung muss hier ein GET oder POST gewählt werden. Siehe auch weiter unten.
Die Parameter des Requests sind bei Github dokumentiert.
Für wenig Geld gibt es auch die Professional Version, welche u.a. das Schreiben eigener Request Records ermöglicht (Request Designer) und ferner eine bessere Auswertung der Geofencing Aktionen erlaubt. Hier geht es aber um die kostenfreie Version der Locative App.
HTTP Basic Authentifizierung
Will man die Anwendung produktiv aus dem Internet betreiben, sollte unbedingt eine Authentifizierung vorgenommen werden. Sonst könnte jeder, der die URL kennt, das Licht ein- oder ausschalten oder schlimmeres anrichten. Mehr dazu weiter unter Absicherung. Für unsere ersten Experimente innerhalb des eigenen Netwerks brauchen wir ers einmal keine Authentifizierung.
Request Designer
Hat bei der kostenlosen Version keine Relevanz. Für uns genügt der Standardrequest, der den Absender, die verwendete Hardware, den Zeitstempel, die aktuellen Längen- und Breitengrade sowie die Richtung (rein oder raus) übermittelt.
Protokoll
Es erleichtert das Verständnis ungemein, wenn man unter dem Info Tab, die Protokollfunktion einschaltet. Schaltet man das Protokoll wieder aus, wird es dabei gelöscht.
Manuelles Auslösen
Hat man später einmal eine Anwendung beisammen, kann man die Events "enter" und "exit" auch von Hand auslösen indem man im Orte Tab etwas länger auf den gewünschten Ort tippt. Anschließend kann man "Ankunft" oder "Verlassen" auswählen, um die gewünschte Routine zu starten, ohne dass eine virtuelle Grenze überschritten wird.
PHP Teststub mit GET
Für einfachste Anwendungen ohne jeglichen Sicherheitsanspruch kann der GET Request genutzt werden. Ich rate in Bezug auf Geofencing davon ab. Wer mehr über POST und GET sowie andere Methoden lernen will, dem seien die englischen W3Schools Seiten zum Thema empfohlen.
Beim GET Request werden die Anfrageparameter im Klartext an die aufgerufene URL angehängt. Das sieht dann z.B. so aus: /test/demo_form.php?name1=value1&name2=value2
Hier der Teststub dazu:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php echo "läuft mit GET! "; // collect value of input field $value = $_GET['trigger']; if (empty($value)) { echo "Value field is empty";} else { echo "Trigger was " . $value; $datei = fopen("record.dat","w"); fwrite($datei, $value); fclose($datei); } ?> |
Ein Stub ist ein Stummel oder Stumpf. Ein kleines Progrämmchen, das ein Interface darstellt, das funktioniert, aber noch erweitert werden muss, um richtigen Nutzen zu stiften.
Damit GET funktioniert, muss beim Locative Testrequest noch der Schalter "Body als URL-Query senden, wenn GET-Methode benutzt wird" aktiviert werden.
Es passiert nicht viel: Der Trigger des Requests wird ausgewertet (hier immer "enter") , in eine Datei record.dat geschrieben und außerdem werden mit echo Meldungen an die Locative App zurückgegeben.
Im Benachrichtigungen Screen erscheint beim Testrequest keine Mitteilung, das erfolgt erst, wenn "richtige " Requests verarbeitet werden.
PHP Teststub mit POST
Für die POST Methode habe ich folgenden Code geschrieben:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php //debug setting error_reporting (E_ALL | E_STRICT); ini_set ('display_errors' , 1); // Ende Debug echo "läuft mit POST "; // collect value of input field $request = file_get_contents('php://input'); $value = $_POST['trigger']; $fp = file_put_contents( 'request.log', $request ); echo "Trigger was " . $value; ?> |
Anders als vorhin wird der komplette Request in die Datei request.log geschrieben.
Der Debug Teil oben im Code ist hilfreich, wenn man einen Fehler einprogrammiert hat. PHP ist unheimlich empfindlich. Die Fehlermeldung wird im Locative Meldungsfenster ausgegeben. Manchmal ist es auch hilfreich, das Programm direkt im Terminal mit php nameDesProgramms.php aufzurufen. Solange kein Input erwartet wird, sieht man sehr schön, welche Fehlermeldungen ausgegeben werden.
Das wäre es schon gewesen. Der Trigger ist übertragen, gelesen und kann jetzt in einer eigenen Anwendung verarbeitet werden. Denkbar wäre z.B. ein Skript in Python, das die Datei regelmäßig abgreift und daraus irgendwelche Aktionen ableitet.
Ist das Ganze auf einem Minirechner zuhause installiert (z.B. einem Raspberry Pi) und ist dieser Rechner bzw. der Webserver aus dem Internet erreichbar, hat man aber ein größeres Sicherheitsproblem. Also…
Absichern
Wie oben erwähnt, beherrscht Locative die http Basic Authorization. Das hat übrigens nichts mit einem passwortgeschützten Verzeichnis zu tun. So etwas ist eher für browserbasierte Anwendungen gedacht. Locative wird damit nicht funktionieren.
Die Basic Authorization – zumindest so wie ich sie verstanden habe – transportiert Username und Passwort in gehashter Form im Header des Requests. Der Header muss vom Empfänger dementsprechend analysiert, die Informationen ausgelesen und decodiert und anschließend mit den hinterlegten Credentials verglichen werden. Erst wenn das erfolgt ist, kann weitergearbeitet werden.
PHP ist für Laien wie mich nicht ganz einfach, weshalb ich mir verschiedene Code Snippets im Internet zusammengesucht habe, um eine funktionierende Anwendung zu bauen.
Sehr hilfreich war der Artikel PHP A primer on the basic authorization header, von dem ich den ersten Codeschnipsel zum Auslesen von Username und Passwort verwendet habe.
Ferner der Artikel How to encrypt and decrypt passwords using PHP ? in dem die Behandlung der beim Server hinterlegten Credentials sehr gut beschrieben ist.
Aus grundsätzlichen Überlegungen heraus habe ich bewusst kein Framework wie z.B. Symfonie verwendet.
Unseren Stub POST_hook.php habe ich wie folgt erweitert (die hervorgehobenen Teile sind aus dem alten Code übernommen):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?php //debug setting error_reporting (E_ALL | E_STRICT); ini_set ('display_errors' , 1); // Ende Debug //check if exists basic auth in request if (isset($_SERVER["HTTP_AUTHORIZATION"])) { $auth = $_SERVER["HTTP_AUTHORIZATION"]; $auth_array = explode(" ", $auth); $un_pw = explode(':', base64_decode($auth_array[1])); $un = $un_pw[0]; $pw = $un_pw[1]; // check Uname & PW $fh = file_get_contents('.password/pwds.env'); $pline = explode(':', $fh); $encpw = preg_replace('/\s+/', '', $pline[1]); $verify = password_verify($pw, $encpw); // if password is correct, continue if ($un == $pline[0] && $verify) { //receiving part $request = file_get_contents('php://input'); //$req_dump = print_r( $request, true ); $fp = file_put_contents( 'request.log', $request );} else { echo "UNAUTHORIZED!!!!!";} } else { echo "Missing credentials";} ?> |
In Zeile 18 wird mit preg_replace der aus der Datei übernommene Passworteintrag noch gesäubert, sonst wird noch ein Steuerzeichen (Zeilenende) mit ausgelesen, was den Vergleich fehlschlagen lässt.
Die Passwortdatei befindet sich in einem versteckten Verzeichnis – das erzeugst du im Linux Terminal, indem du es im Verzeichnis des PHP Skripts mit sudo mkdir .passwords anlegst.
Das Passwort wird mit PHP verschlüsselt. Hierzu folgende Routine anpassen, als hash.php speichern und mit php hash.php starten.
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php // The plain text password to be hashed $plaintext_password = "deinSupergeheimesBasicAuthPasswort"; // The hash of the password that // can be stored in the database $hash = password_hash($plaintext_password, PASSWORD_DEFAULT); // Print the generated hash echo "Generated hash: ".$hash; ?> |
Das Programm gibt dein Passwort in verschlüsselter Form aus – ich meine, das Sicherheitsniveau reicht für unsere Zwecke.
Das Passwort und den Usernamen packst du wie folgt in die Datei pwds.env als eine einzige Zeile. Da wir es ja mit einem einzigen Maschinenuser (z.B. "locative") zu tun haben, brauchen wir keine User- und Passwortverwaltung. Vorne der username dann ein Doppelpunkt und dann das verschlüsselte Passwort mit der Länge 60.
locative:$2y$10$zz1KkB0Ieo/Pb5lL51/oyuTF2KBLiON2t0c7pBAUtGZJOVm42lV06
Im Verzeichnis .passwords abspeichern und fertig.
Noch sicherer wird es, wenn du deinen Webserver mit SSL bzw. https: absicherst. Dann aber aufpassen, dass die URLs in Locative entsprechend angepasst werden.
Wie gesagt, du kannst den Output beliebig weiterverwenden und mit selbst entwickelten Routinen verarbeiten. Zum Beispiel GPIO Pins im Raspberry Pi ansteuern oder Shellies schalten oder wie ich Node-Red damit verküpfen. Dazu mehr im Teil 2.
Achtung Apache: Im Zuge eines Komplett-Updates meines Servers bin ich auf Apache2 als Webserver umgestiegen – vorher hatte ich lighttpd verwendet. Apache2 hat aus irgend welchen Gründen die Autorisierung über den html Header deaktiviert. $_SERVER["HTTP_AUTHORIZATION"] funktioniert also von Haus aus nicht.
Abhilfe erfolgt durch den Eintrag CGIPassAuth On in der Datei .htaccess im Verzeichnis unseres PHP Programms.
Eventuell muss .htaccess auch noch aktiviert werden: Dazu die Datei /etc/apache2/apache2.conf editieren und bei bereits vorhandenen Einträgen der Art
1 2 3 4 5 |
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> |
folgenden Eintrag hinzufügen:
1 2 3 4 5 |
<Directory /var/www/html> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> |
Anschließend den Webserver mit sudo service apache2 restart neu starten.