2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-20 01:06:04 +00:00

98_livetracking: added OsmAnd server for Traccar iOS client

git-svn-id: https://svn.fhem.de/fhem/trunk@17717 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
moises 2018-11-09 22:26:18 +00:00
parent 7bae973520
commit e4f9259a8b
2 changed files with 256 additions and 19 deletions

View File

@ -1,5 +1,6 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
# Do not insert empty lines here, update check depends on it. # Do not insert empty lines here, update check depends on it.
- feature: 98_livetracking: added OsmAnd server for Traccar iOS client
- new: 73_AutoShuttersControl: Module for the automated control of - new: 73_AutoShuttersControl: Module for the automated control of
shutters based on certain properties shutters based on certain properties
- feature: 93_DbRep: V8.8.0, attribute valueFilter applied to more functions, - feature: 93_DbRep: V8.8.0, attribute valueFilter applied to more functions,

View File

@ -27,6 +27,7 @@ use JSON;
use Time::Local; use Time::Local;
use URI::Escape; use URI::Escape;
use Data::Dumper; use Data::Dumper;
use Encode qw(encode_utf8 decode_utf8);
use utf8; use utf8;
@ -79,6 +80,10 @@ sub livetracking_Initialize($) {
"zonename_8 ". "zonename_8 ".
"zonename_9 ". "zonename_9 ".
"batteryWarning:5,10,15,20,25,30,35,40 ". "batteryWarning:5,10,15,20,25,30,35,40 ".
"addressLanguage:de,en,fr,es,it,nl ".
"addressReading:0,1 ".
"osmandServer:0,1 ".
"osmandId ".
$readingFnAttributes; $readingFnAttributes;
@ -91,7 +96,7 @@ sub livetracking_Define($$$) {
return "syntax: define <name> livetracking <openpaths_key> <openpaths_secret> <swarm_token>" if(int(@a) < 2 || int(@a) > 7 ); return "syntax: define <name> livetracking <openpaths_key> <openpaths_secret> <swarm_token>" if(int(@a) < 2 || int(@a) > 7 );
my $name = $hash->{NAME}; my $name = $hash->{NAME};
$hash->{OAuth_exists} = $libcheck_hasOAuth if($libcheck_hasOAuth); #$hash->{OAuth_exists} = $libcheck_hasOAuth if($libcheck_hasOAuth);
if(int(@a) == 4 ) { if(int(@a) == 4 ) {
$hash->{helper}{openpaths_key} = $a[2];# if($hash->{OAuth_exists}); $hash->{helper}{openpaths_key} = $a[2];# if($hash->{OAuth_exists});
@ -213,8 +218,6 @@ sub livetracking_Get($@) {
return $usage if $command eq '?'; return $usage if $command eq '?';
RemoveInternalTimer($hash);
if(AttrVal($name, "disable", 0) eq 1) if(AttrVal($name, "disable", 0) eq 1)
{ {
return "livetracking $name is disabled. Aborting..."; return "livetracking $name is disabled. Aborting...";
@ -248,9 +251,10 @@ sub livetracking_Get($@) {
if($parameter =~ /,/){ if($parameter =~ /,/){
@location = split(",",$parameter); @location = split(",",$parameter);
} }
my $lang = AttrVal($name,"addressLanguage","en");
if(defined($location[1])) { if(defined($location[1])) {
my($err,$data) = HttpUtils_BlockingGet({ my($err,$data) = HttpUtils_BlockingGet({
url => "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=".$location[0]."&lon=".$location[1]."&addressdetails=1&limit=1", url => "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=".$location[0]."&lon=".$location[1]."&addressdetails=1&accept-language=$lang",
noshutdown => 1, noshutdown => 1,
}); });
return "data error" if($err); return "data error" if($err);
@ -258,30 +262,42 @@ sub livetracking_Get($@) {
my $json = eval { JSON->new->utf8(0)->decode($data) }; my $json = eval { JSON->new->utf8(0)->decode($data) };
return "invalid json evaluation" if($@); return "invalid json evaluation" if($@);
if( $parameter eq "short" && defined($json->{display_name}) ) { if( $parameter eq "short" && defined($json->{display_name}) ) {
return $json->{display_name}; readingsSingleUpdate($hash,"address",livetracking_utf8clean($json->{display_name}),1) if(AttrVal($name,"addressReading",0));
return livetracking_utf8clean($json->{display_name});
} elsif( defined($json->{address}) ) { } elsif( defined($json->{address}) ) {
my $addr = ""; my $addr = "";
if($parameter eq "long"){
$addr .= $json->{address}->{housename}."\n" if(defined($json->{address}->{housename}));
$addr .= $json->{address}->{parking}."\n" if(defined($json->{address}->{parking}));
}
$addr .= $json->{address}->{road}." " if(defined($json->{address}->{road})); $addr .= $json->{address}->{road}." " if(defined($json->{address}->{road}));
$addr .= $json->{address}->{path}." " if(defined($json->{address}->{path}) && !defined($json->{address}->{road})); $addr .= $json->{address}->{path}." " if(defined($json->{address}->{path}) && !defined($json->{address}->{road}));
$addr .= $json->{address}->{bridleway}." " if(defined($json->{address}->{bridleway}) && !defined($json->{address}->{road}) && !defined($json->{address}->{path}) && !defined($json->{address}->{path})); $addr .= $json->{address}->{bridleway}." " if(defined($json->{address}->{bridleway}) && !defined($json->{address}->{road}) && !defined($json->{address}->{path}));
$addr .= $json->{address}->{footway}." " if(defined($json->{address}->{footway}) && !defined($json->{address}->{road}) && !defined($json->{address}->{path}) && !defined($json->{address}->{bridleway})); $addr .= $json->{address}->{footway}." " if(defined($json->{address}->{footway}) && !defined($json->{address}->{road}) && !defined($json->{address}->{path}) && !defined($json->{address}->{bridleway}));
$addr .= $json->{address}->{neighbourhood}." " if(defined($json->{address}->{neighbourhood}) && !defined($json->{address}->{road}) && !defined($json->{address}->{path}) && !defined($json->{address}->{bridleway}) && !defined($json->{address}->{footway}));
$addr .= $json->{address}->{house_number} if(defined($json->{address}->{house_number})); $addr .= $json->{address}->{house_number} if(defined($json->{address}->{house_number}));
$addr .= "\n".$json->{address}->{neighbourhood} if(defined($json->{address}->{neighbourhood}) && $parameter eq "long"); #$addr .= "\n".$json->{address}->{neighbourhood} if(defined($json->{address}->{neighbourhood}) && $parameter eq "long");
$addr .= "\n".$json->{address}->{suburb} if(defined($json->{address}->{suburb}) && $parameter eq "long"); if($parameter eq "long"){
$addr .= "\n" if(defined($json->{address}->{postcode}) || defined($json->{address}->{city}) || defined($json->{address}->{town})); $addr .= "\n".$json->{address}->{suburb} if(defined($json->{address}->{suburb}));
}
$addr .= "\n" if(defined($json->{address}->{postcode}) || defined($json->{address}->{city}) || defined($json->{address}->{town}) || defined($json->{address}->{village}));
$addr .= $json->{address}->{postcode}." " if(defined($json->{address}->{postcode})); $addr .= $json->{address}->{postcode}." " if(defined($json->{address}->{postcode}));
$addr .= $json->{address}->{city} if(defined($json->{address}->{city})); $addr .= $json->{address}->{city} if(defined($json->{address}->{city}));
$addr .= $json->{address}->{town}." " if(defined($json->{address}->{town}) && !defined($json->{address}->{city})); $addr .= $json->{address}->{town}." " if(defined($json->{address}->{town}) && !defined($json->{address}->{city}));
$addr .= $json->{address}->{village}." " if(defined($json->{address}->{village}) && !defined($json->{address}->{city}) && !defined($json->{address}->{town})); $addr .= $json->{address}->{village}." " if(defined($json->{address}->{village}) && !defined($json->{address}->{city}) && !defined($json->{address}->{town}));
$addr .= "\n".$json->{address}->{county} if(defined($json->{address}->{county}) && $parameter eq "long"); if($parameter eq "long"){
$addr .= "\n" if((defined($json->{address}->{state_district}) || defined($json->{address}->{state})) && $parameter eq "long"); $addr .= "\n".$json->{address}->{county} if(defined($json->{address}->{county}));
$addr .= $json->{address}->{state_district}." " if(defined($json->{address}->{state_district}) && $parameter eq "long"); $addr .= "\n" if((defined($json->{address}->{state_district}) || defined($json->{address}->{state})));
$addr .= $json->{address}->{state} if(defined($json->{address}->{state}) && $parameter eq "long"); $addr .= $json->{address}->{state_district}." " if(defined($json->{address}->{state_district}));
$addr .= $json->{address}->{state} if(defined($json->{address}->{state}));
}
$addr .= "\n".$json->{address}->{country} if(defined($json->{address}->{country})); $addr .= "\n".$json->{address}->{country} if(defined($json->{address}->{country}));
#Log3 ($name, 3, "$name: ".Dumper($json)); Log3 ($name, 4, "$name: address received\n".Dumper($json));
return $addr; readingsSingleUpdate($hash,"address",livetracking_utf8clean($addr),1) if(AttrVal($name,"addressReading",0));
return livetracking_utf8clean($addr);
} elsif( defined($json->{display_name}) ) { } elsif( defined($json->{display_name}) ) {
return $json->{display_name}; readingsSingleUpdate($hash,"address",livetracking_utf8clean($json->{display_name}),1) if(AttrVal($name,"addressReading",0));
return livetracking_utf8clean($json->{display_name});
} else { } else {
return "no data"; return "no data";
} }
@ -309,6 +325,13 @@ sub livetracking_Attr(@) {
elsif ($attr && $attr =~ /^(beacon_)([0-9]+)/) { elsif ($attr && $attr =~ /^(beacon_)([0-9]+)/) {
fhem( "deletereading $name beacon_".$2.".*" ); fhem( "deletereading $name beacon_".$2.".*" );
} }
elsif ($attr && $attr eq 'osmandServer') {
if($command eq "set" && $val == 1){
livetracking_addExtension($hash);
} else {
livetracking_removeExtension($hash);
}
}
return undef; return undef;
} }
@ -360,6 +383,7 @@ sub livetracking_GetOpenPaths($) {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#RemoveInternalTimer($hash); #RemoveInternalTimer($hash);
RemoveInternalTimer($hash, "livetracking_GetOpenPaths");
if(AttrVal($name, "disable", 0) eq 1) if(AttrVal($name, "disable", 0) eq 1)
{ {
@ -412,7 +436,7 @@ sub livetracking_GetOpenPaths($) {
my $interval = AttrVal($hash->{NAME}, "interval", 1800); my $interval = AttrVal($hash->{NAME}, "interval", 1800);
#RemoveInternalTimer($hash); #RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + $interval, "livetracking_GetAll", $hash, 0); InternalTimer( gettimeofday() + $interval, "livetracking_GetOpenPaths", $hash, 0);
$hash->{UPDATED} = FmtDateTime(time()); $hash->{UPDATED} = FmtDateTime(time());
return undef; return undef;
@ -425,6 +449,7 @@ sub livetracking_GetSwarm($) {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
#RemoveInternalTimer($hash); #RemoveInternalTimer($hash);
RemoveInternalTimer($hash, "livetracking_GetSwarm");
if(AttrVal($name, "disable", 0) eq 1) if(AttrVal($name, "disable", 0) eq 1)
{ {
@ -453,7 +478,7 @@ sub livetracking_GetSwarm($) {
my $interval = AttrVal($hash->{NAME}, "interval", 1800); my $interval = AttrVal($hash->{NAME}, "interval", 1800);
#RemoveInternalTimer($hash); #RemoveInternalTimer($hash);
InternalTimer( gettimeofday() + $interval, "livetracking_GetAll", $hash, 0); InternalTimer( gettimeofday() + $interval, "livetracking_GetSwarm", $hash, 0);
$hash->{UPDATED} = FmtDateTime(time()); $hash->{UPDATED} = FmtDateTime(time());
return undef; return undef;
@ -922,6 +947,59 @@ sub livetracking_Notify($$)
} }
} }
} }
if(defined($dataset->{t}))
{
my $trigger = "unknown";
$trigger = "ping" if($dataset->{t} eq "p");
$trigger = "region" if($dataset->{t} eq "c");
$trigger = "beacon" if($dataset->{t} eq "b");
$trigger = "request" if($dataset->{t} eq "r");
$trigger = "manual" if($dataset->{t} eq "u");
$trigger = "timer" if($dataset->{t} eq "t");
$trigger = "frequent" if($dataset->{t} eq "v");
readingsBulkUpdate($hash, "trigger",$trigger);
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($dataset->{tst});
} else {
readingsBulkUpdate($hash, "trigger","automatic");
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($dataset->{tst});
}
if(defined($dataset->{inregions}))
{
my @placenumbersactive;
my @placenumbersinactive;
for(my $i=9;$i>=0;$i--)
{
next if(!defined($attr{$name}{"zonename_$i"}));
my $active = 0;
foreach my $regionname ( @{$dataset->{inregions}} )
{
$active = 1 if($regionname =~ m/^($attr{$name}{"zonename_$i"})$/);
}
if($active){
Log3 ($name, 4, "$name OwnTracks region active: ".Dumper($attr{$name}{"zonename_$i"}));
push @placenumbersactive, $i;
} else {
Log3 ($name, 4, "$name OwnTracks region inactive: ".Dumper($attr{$name}{"zonename_$i"}));
push @placenumbersinactive, $i;
}
}
foreach my $placenumber (@placenumbersactive)
{
readingsBulkUpdate($hash, "zone_".$placenumber,"active");
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($dataset->{tst});
}
foreach my $placenumber (@placenumbersinactive)
{
readingsBulkUpdate($hash, "zone_".$placenumber,"inactive");
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($dataset->{tst});
}
}
if(defined($attr{$name}{home}) and $accurate) if(defined($attr{$name}{home}) and $accurate)
{ {
#$hash->{".updateTimestamp"} = FmtDateTime($dataset->{tst}); #$hash->{".updateTimestamp"} = FmtDateTime($dataset->{tst});
@ -1042,6 +1120,142 @@ sub livetracking_getHistory($$$$$)
#########################
sub livetracking_addExtension($) {
my ($hash) = @_;
my $name = $hash->{NAME};
#livetracking_removeExtension() ;
my $url = "/osmand";
delete $data{FWEXT}{$url} if($data{FWEXT}{$url});
Log3 $name, 1, "Enabling livetracking url for $name";
$data{FWEXT}{$url}{deviceName} = $name;
$data{FWEXT}{$url}{FUNC} = "livetracking_Webcall";
$data{FWEXT}{$url}{LINK} = "livetracking";
$modules{"livetracking"}{defptr}{"webcall".AttrVal($name, "osmandId", "")} = $hash;
}
#########################
sub livetracking_removeExtension($) {
my ($hash) = @_;
my $url = "/osmand";
my $name = $data{FWEXT}{$url}{deviceName};
$name = $hash->{NAME} if(!defined($name));
Log3 $name, 3, "Disabling livetracking url for $name";
delete $data{FWEXT}{$url};
delete $modules{"livetracking"}{defptr}{"webcall".AttrVal($name, "osmandId", "")};
}
#########################
sub livetracking_Webcall() {
my ($request) = @_;
my ($id) = $request =~ /id=(.*?)(&|$)/ || "";
my $hash = $modules{"livetracking"}{defptr}{"webcall".$id};
$hash = $modules{"livetracking"}{defptr}{"webcall"} if(!defined($hash));
if(!defined($hash)){
Log3 "livetracking", 1, "OsmAnd webcall hash not defined!";
return ( "text/plain; charset=utf-8",
"undefined" );
}
my $name = $hash->{NAME};
my $osmandid = AttrVal($name, "osmandId", undef);
return undef if(defined($osmandid) && $osmandid ne $id);
Log3 $name, 5, "OsmAnd webcall request:\n".$request;
my ($tst) = $request =~ /tamp=(.*?)(&|$)/;
my ($hdop) = $request =~ /hdop=(.*?)(&|$)/ || 0;
my ($lat) = $request =~ /lat=(.*?)(&|$)/;
my ($lon) = $request =~ /lon=(.*?)(&|$)/;
my ($speed) = $request =~ /speed=(.*?)(&|$)/;
my ($bearing) = $request =~ /bearing=(.*?)(&|$)/;
my ($altitude) = $request =~ /altitude=(.*?)(&|$)/;
my ($battery) = $request =~ /batt=(.*?)(&|$)/;
if(!defined($tst))
{
return ( "text/plain; charset=utf-8",
"timestamp missing" );
}
my $accurate = 1;
$accurate = 0 if(defined($attr{$name}{filterAccuracy}) and defined($hdop) and $attr{$name}{filterAccuracy} < $hdop);
my $changeindex = 0;
readingsBeginUpdate($hash); # Start update readings
$hash->{".updateTimestamp"} = FmtDateTime($tst);
Log3 ($name, 4, "$name OsmAnd Server: ".FmtDateTime($tst));
if($accurate && defined($lat) && defined($lon))
{
readingsBulkUpdate($hash, "location", $lat.",".$lon);
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
}
else
{
Log3 ($name, 3, "$name OsmAnd: Inaccurate reading ignored: ".$lat.",".$lon." (".$hdop.")");
}
if($accurate && defined($speed) && $speed >= 0)
{
readingsBulkUpdate($hash, "velocity", $speed);
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
}
if($accurate && defined($bearing) && $bearing >= 0)
{
readingsBulkUpdate($hash, "heading", $bearing);
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
}
if($accurate && defined($altitude) and $altitude != 0)
{
my $altitudeRound = AttrVal($hash->{NAME}, "roundAltitude", 1);
my $newaltitude = livetracking_roundfunc($altitude/$altitudeRound)*$altitudeRound;
readingsBulkUpdate($hash, "altitude", $newaltitude);
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
}
if(defined($hdop) && $hdop > 0)
{
readingsBulkUpdate($hash, "accuracy", $hdop);
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
}
if(defined($battery))
{
readingsBulkUpdate($hash, "batteryPercent", $battery);
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
readingsBulkUpdate($hash, "batteryState", (int($battery) <= int(AttrVal($name, "batteryWarning" , "20")))?"low":"ok");
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
}
if($accurate && defined($attr{$name}{home}) and defined($lat) && defined($lon))
{
readingsBulkUpdate($hash, "distance", livetracking_distance($hash,$lat.",".$lon,$attr{$name}{home}));
$hash->{CHANGETIME}[$changeindex++] = FmtDateTime($tst);
}
readingsEndUpdate($hash, 1);
readingsSingleUpdate($hash,".lastOsmAnd",$tst,1);
$hash->{helper}{lastOsmAnd} = $tst;
if(defined($lat) && defined($lon))
{
return ( "text/plain; charset=utf-8",
"OK" );
} else {
return ( "text/plain; charset=utf-8",
"no data" );
}
return undef;
}
########################## ##########################
sub livetracking_DbLog_splitFn($) sub livetracking_DbLog_splitFn($)
@ -1179,6 +1393,11 @@ sub livetracking_rad2deg($) {
sub livetracking_utf8clean($) { sub livetracking_utf8clean($) {
my ($string) = @_; my ($string) = @_;
my $log = ""; my $log = "";
return $string if(utf8::is_utf8($string));
return encode_utf8($string);
if($string !~ m/^[\w\.,!@#$%^&*()\\|<>"' _:;\/?=+-]+$/) if($string !~ m/^[\w\.,!@#$%^&*()\\|<>"' _:;\/?=+-]+$/)
{ {
$log .= $string."(standard) "; $log .= $string."(standard) ";
@ -1323,7 +1542,7 @@ sub livetracking_utf8clean($) {
<br/> <br/>
Request steps data from OwnTracks Request steps data from OwnTracks
</li><br> </li><br>
<li><a name="#address">address [short/long/lat,lng]</a> <li><a name="#address">address [&lt;lat,lng&gt;/short/long]</a>
<br/> <br/>
Get an address from coordinates<br/> Get an address from coordinates<br/>
</li><br> </li><br>
@ -1452,6 +1671,23 @@ sub livetracking_utf8clean($) {
<br> <br>
OwnTracks MQTT device to look for notifies from OwnTracks MQTT device to look for notifies from
</li><br> </li><br>
<li><a name="addressLanguage">addressLanguage</a> (de/en/fr/es/it/nl)
<br>
Preferred language used to return reverse geocoding results
</li><br>
<li><a name="addressReading">createAddressReading</a> (0/1)
<br>
Write reverse geocoding results to address reading
</li><br>
<li><a name="osmandServer">osmandServer</a> (0/1)
<br>
Starts an OsmAnd compatible listener on FHEM which can be entered into traccar-client directly:<br/>
<code>https://user:pass@your.fhem.ip/fhem/osmand</code> (The Android client does not support user:pass authentication)
</li><br>
<li><a name="osmandId">osmandId</a>
<br>
The device identifier that is set in the OsmAnd client and transmitted in the request as <i>id</i>
</li><br>
</ul> </ul>
</ul> </ul>