Long time lurker here who has benefited a huge amount from this forum. I’ve seen lots of posts with questions on this topic and I have learned from some of them. I thought I would provide my solution for a working setup. This setup uses the mqtt binding for grabbing the location data and Owntracks/Mqttitude binding for geofence switches. It utilizes google maps for the display of the locations.
I have a “notify on motion” switch setup which when turned on, stores the users location in a variable, then every time the users location is updated it compares the new location with the stored location. if the calculated distance is greater than 300 meters it sends me a push notification via pushover(not covered in this tutorial) then turns off the switch. I don’t use this much but I could see it coming in very handy when my daughter gets older.
Screenshots:
I’m sure there are probably more elegant ways to accomplish this but it has been working well for me. Feel free to offer suggestions.
Step1
Install the mqtt broker mosquitto. I’m running openhabian so this was activated in openhabian-config.
Step2
Install and setup Owntracks app on your smartphone. I’m using TLS encryption (which I advise) but will not be covering here. It will work with or without the encryption, however the default port will be different. I advise you get it working without encryption, then take the time to set it up. Important note here is that you will need to setup port forwarding on whichever port you are using. Here are some screenshots from my Samsung S8.
Enter your ddns name or router ip address and port under the Host connection setting.
Enter your mosquitto credentials under the Identification connection setting. What you enter as device_id will be correlated to the mqtt topic which you will setup openhab to subscribe too.
This is a good point to test whether the mqtt broker (mosquitto) is receiving messages published by your owntracks app. In a terminal window type the following command to subscribe to a topic. We are going to use the ‘#’ wildcard to view anything received by the broker.
Without encryption
sudo mosquitto_sub -h localhost -p 1883 -u openhabian -P <yourPW> -t "#"
With encryption:
sudo mosquitto_sub -h localhost -p 8883 --cafile <path to certificate> -u <username> -P <password> -t "#"
Now in your owntracks app on your phone, publish your location and you should see something like this in your terminal window:
Google Map for display
you will need to register as a google developer and get an api key.
on your openhab box create a file at html/Map.html. You will need to modify this file in several places to suit your location. I grabbed this example from the internet somewhere and left some of the variable names unchanged. It displays 2 locations and home and zooms the map relative to those 3 points but could be modified to your liking. Its sort of pieced together but it works for me. YMMV
Map.html:
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<style type="text/css">
.Flexible-container {
position: relative;
padding-bottom: 0px;
padding-top : 0px;
height : 340px ;
overflow: hidden;
}
.Flexible-container iframe,
.Flexible-container object,
.Flexible-container embed {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=<yourkeyhere>&libraries=places,drawing,geometry"></script>
<script type="text/javascript">
////////////////////////////////////////////////////////////////////////
// Google Maps JavaScript API:
// https://developers.google.com/maps/documentation/javascript/?hl=de
// Marker Icons:
// https://sites.google.com/site/gmapsdevelopment/
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// JQuery
////////////////////////////////////////////////////////////////////////
var map = null;
//make an empty bounds variable
var bounds = new google.maps.LatLngBounds();
// LatLng's we want to show
var latlngHome = new google.maps.LatLng("xx.571369", "-xx.253659");
var latlngPatrik = new google.maps.LatLng("xx.571369", "-xx.253659"); // initialize to home ...
var latlngKarin = new google.maps.LatLng("xx.571369", "-xx.253659"); // initialize to home ...
var map_options = { center : latlngHome,
zoom : 11,
mapTypeId : google.maps.MapTypeId.ROADMAP };
$( "#map_canvas" ).ready($(function() {
var map_canvas = document.getElementById('map_canvas');
map = new google.maps.Map(map_canvas, map_options)
var marker = new google.maps.Marker({
position : latlngHome,
map : map,
icon : 'http://maps.google.com/mapfiles/kml/pal2/icon10.png',
title : "Home"
})
var circle = new google.maps.Circle({
center : latlngHome,
radius : 450,
map : map,
strokeColor : '#050505',
strokeOpacity : 0.5,
strokeWeight : 1,
fillColor : '#000000',
fillOpacity : 0,
}); // end of [Circle]
bounds.extend(latlngHome);
}))
$( document ).ready($(function() {
// ******************************************************************************
$.ajax({
url : "../rest/items/locationBecca/state/",
data : {},
success : function( data ) {
if ( map == null) { return; }
if ( data == "Uninitialized") { return; }
var coords = data.split(',');
var latlngPatrik = new google.maps.LatLng(coords[0],coords[1]);
var marker = new google.maps.Marker({
position : latlngPatrik,
map : map,
icon : 'http://maps.google.com/mapfiles/ms/icons/pink-dot.png',
title : "Becca"
}) // end of [Marker]
$.ajax({
url : "../rest/items/mqttBeccaAccuracy/state/",
data : {},
success : function( data ) {
if ( data == "Uninitialized") { return; }
var accuracy = parseInt(data);
var circle = new google.maps.Circle({
center : latlngPatrik,
radius : accuracy,
map : map,
strokeColor : '#f442e2',
strokeOpacity : 0.8,
strokeWeight : 2,
fillColor : '#f442e2',
fillOpacity : 0.35,
}); // end of [Circle]
bounds.extend(latlngPatrik);
map.fitBounds(bounds);
} // end of [function]
}) // end of [$.ajax]
} // end of [function]
}) // end of [$.ajax]
// ******************************************************************************
$.ajax({
url : "../rest/items/locationJosh/state/",
data : {},
success : function( data ) {
if ( map == null) { return; }
if ( data == "Uninitialized") { return; }
var coords = data.split(',');
var latlngKarin = new google.maps.LatLng(coords[0],coords[1]);
var marker = new google.maps.Marker({
position : latlngKarin,
map : map,
icon : 'http://maps.google.com/mapfiles/ms/icons/green-dot.png',
title : "Josh"
}) // end of [Marker]
$.ajax({
url : "../rest/items/mqttJoshAccuracy/state/",
data : {},
success : function( data ) {
if ( data == "Uninitialized") { return; }
var accuracy = parseInt(data);
var circle = new google.maps.Circle({
center : latlngKarin,
radius : accuracy,
map : map,
strokeColor : '#00FF00',
strokeOpacity : 0.8,
strokeWeight : 2,
fillColor : '#00FF00',
fillOpacity : 0.35,
}); // end of [Circle]
bounds.extend(latlngKarin);
map.fitBounds(bounds);
} // end of [function]
}) // end of [$.ajax]
} // end of [function]
}) // end of [$.ajax]
// map.fitBounds(bounds);
}))
</script>
</head>
<body>
<div id="map_canvas" class="Flexible-container" />
</body>
</html>
Step 3
A. Install the mqtt and Owntracks(formerly mqttitude) bindings via your preferred method. I used PaperUI.
Step 4
The OwnTracks binding does not need a config file but you will need to uncomment a few lines in services/mqtt.cfg Here are the uncommented lines in my setup:
mosquitto.url=ssl://localhost:8883
mosquitto.user=openhabian
mosquitto.pwd=YourPasswordHere
mosquitto.qos=1
Step 5
Add a region in the owntracks app for any geofences you want. Note that you will want the share switch on. I have a region for my house and my work.
Setup OpenHAB
add a file in the transform directory called mqttitude-tstamp.js which will be used to transform the timestamp from owntracks. Note that I am in US Eastern timezone so I am subtracting the time by 5 hours.
(function(json){ var otdata = JSON.parse(json);
var this_date = new Date(otdata.tst * 1000);
this_date.setHours(this_date.getHours() - 5);
return this_date.toISOString();
})
(input)
Items
Duplicate this for any devices you may have. Your mqtt topics may be slightly different depending on usernames, device names, and region names.
//Owntracks/mqttitude Binding
String josh "Josh [%s]" <josh> (gPresenceChart,gDashboard)
Switch phone_Josh "Josh [MAP(presence.map):%s]" <present> (gPresence,gPhones) {mqttitude="mosquitto:owntracks/openhabian/phone_Josh/event:Home"}
Switch phone_Josh_Work "Josh_Work [MAP(presence_work.map):%s]" <ecu> { mqttitude="mosquitto:owntracks/openhabian/phone_Josh/event:work" }
//MQTT Binding
String mqttPositionJoshRaw "Josh's Raw Data [%s]" { mqtt="<[mosquitto:owntracks/openhabian/phone_Josh:state:default]" }
DateTime mqttJoshLastUpdated "Josh's last update [%1$tm/%1$td/%1$tY %1$tH:%1$tM:%1$tS]" <clock> { mqtt="<[ mosquitto:owntracks/openhabian/phone_Josh:state:JS(mqttitude-tstamp.js)]" }
Location locationJosh
String mqttJoshLatitude
String mqttJoshLongitude
String mqttJoshAccuracy "Josh's Accuracy [%s]"
String mqttJoshBattery "Josh's Android Battery [%s%%]" <battery> (Phone,MQTT,Battery)
Switch josh_moving "Notify on motion" <siren>
Number joshDistanceFromHome "Miles from home [%.1f]"
Add file transform/presence.map:
//--------------presence.map------------------------
ON=Home
OFF=Away
-=Undefined
add file transform/presence_work.map:
ON=At Work
OFF=Off
-=Undefined
Rules
Duplicate for as more devices.
var PointType becca_moving_location
var PointType josh_moving_location
var PointType home = new PointType("XX.571367,-XX.253829")//no spaces after comma
rule "Josh Moving switch on"
when Item josh_moving received update ON
then
josh_moving_location = locationJosh.state
logInfo("josh_moving_location updated","josh_moving_location set to" + josh_moving_location)
end
rule "MqttPostionParseJosh"
when
Item mqttPositionJoshRaw changed
then
val String json = (mqttPositionJoshRaw.state as StringType).toString
val String type = transform("JSONPATH", "$._type", json)
if (type == "location") {
val String lat = transform("JSONPATH", "$.lat", json)
val String lon = transform("JSONPATH", "$.lon", json)
val String acc = transform("JSONPATH", "$.acc", json)
val String batt = transform("JSONPATH", "$.batt", json)
postUpdate(mqttJoshLatitude,lat)
postUpdate(mqttJoshLongitude,lon)
locationJosh.postUpdate(new PointType(lat + "," + lon))
mqttJoshAccuracy.postUpdate(acc)
mqttJoshBattery.postUpdate(batt)
joshDistanceFromHome.postUpdate((home.distanceFrom(locationJosh.state))*0.000621)//meters to miles
}
if (josh_moving.state == ON){
if (josh_moving_location.distanceFrom(locationJosh.state) > 300){
pushover("Josh is on the move")
postUpdate(josh_moving, OFF)
}
}
end
rule "System Start"
when
System started
then
if(phone_Josh.state == ON){
postUpdate(josh, "Home")
}
else if (phone_Josh_Work.state == ON)
postUpdate(josh, "Work")
else postUpdate(josh, "Away")
end
rule "Daddy's Home"
when
Item phone_Josh changed from OFF to ON
then
postUpdate(josh, "Home")
end
rule "Josh leaves home"
when Item phone_Josh received update OFF
then
postUpdate(josh, "Away"
end
rule "Josh gets to work"
when Item phone_Josh_Work received update ON
then postUpdate(josh, "Work")
end
rule "Josh leaves work in Afternoon"
when
Item phone_Josh_Work received update OFF
then
postUpdate(josh, "Away")
end
sitemap
Frame label="Presence"{
Text item=josh icon="josh"{
Switch item=phone_Josh valuecolor=[phone_Josh==Uninitialized="lightgray", ==ON="red", ==OFF="lightgray"]
Switch item=phone_Josh_Work valuecolor=[phone_Josh_Work==Uninitialized="lightgray", ==ON="red", ==OFF="lightgray"]
Text item=mqttJoshLastUpdated
Text item=mqttJoshBattery
Switch item=josh_moving
Default item=joshDistanceFromHome
Webview url="http://192.168.1.140:8080/static/Map.html" height=10
Chart item=phone_Josh period=d refresh=10000
}
Webview url="http://<yourIPaddress>:8080/static/Map.html" height=10 visibility=[phone_Josh==OFF]
}
I probably forgot something. Let me know if you have any questions and I’ll answer as soon as I can.