Bladeren bron

Sonos change saved unstaged files

hmetzner 6 jaren geleden
bovenliggende
commit
9c5f9affcd

+ 522 - 0
fhem/core/FHEM/00_MQTT2_CLIENT.pm

@@ -0,0 +1,522 @@
+##############################################
+# $Id: 00_MQTT2_CLIENT.pm 17725 2018-11-10 17:43:12Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+use DevIo;
+
+sub MQTT2_CLIENT_Read($@);
+sub MQTT2_CLIENT_Write($$$);
+sub MQTT2_CLIENT_Undef($@);
+
+my $keepalive = 30;
+
+# See also:
+# http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html
+
+sub
+MQTT2_CLIENT_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Clients} = ":MQTT2_DEVICE:";
+  $hash->{MatchList}= { "1:MQTT2_DEVICE"  => "^.*" },
+  $hash->{ReadFn}  = "MQTT2_CLIENT_Read";
+  $hash->{DefFn}   = "MQTT2_CLIENT_Define";
+  $hash->{AttrFn}  = "MQTT2_CLIENT_Attr";
+  $hash->{SetFn}   = "MQTT2_CLIENT_Set";
+  $hash->{UndefFn} = "MQTT2_CLIENT_Undef";
+  $hash->{DeleteFn}= "MQTT2_CLIENT_Delete";
+  $hash->{WriteFn} = "MQTT2_CLIENT_Write";
+  $hash->{ReadyFn} = "MQTT2_CLIENT_connect";
+
+  no warnings 'qw';
+  my @attrList = qw(
+    autocreate
+    clientId
+    disable:0,1
+    disabledForIntervals
+    lwt
+    lwtRetain
+    mqttVersion:3.1.1,3.1
+    onConnect
+    rawEvents
+    subscriptions
+    SSL
+    username
+  );
+  use warnings 'qw';
+  $hash->{AttrList} = join(" ", @attrList);
+}
+
+#####################################
+sub
+MQTT2_CLIENT_Define($$)
+{
+  my ($hash, $def) = @_;
+  my ($name, $type, $host) = split("[ \t]+", $def);
+  return "Usage: define <name> MQTT2_CLIENT <hostname>:<tcp-portnr>"
+        if(!$host);
+
+  MQTT2_CLIENT_Undef($hash, undef) if($hash->{OLDDEF}); # modify
+
+  $hash->{DeviceName} = $host;
+  $hash->{clientId} = $hash->{NAME};
+  $hash->{clientId} =~ s/[^0-9a-zA-Z]//g;
+  $hash->{clientId} = "MQTT2_CLIENT" if(!$hash->{clientId});
+  $hash->{connecting} = 1;
+
+  InternalTimer(1, "MQTT2_CLIENT_connect", $hash, 0); # need attributes
+  return undef;
+}
+
+sub
+MQTT2_CLIENT_connect($)
+{
+  my ($hash) = @_;
+  my $disco = (ReadingsVal($hash->{NAME}, "state", "") eq "disconnected");
+  $hash->{connecting} = 1 if($disco && !$hash->{connecting});
+  $hash->{nextOpenDelay} = 5;
+  return DevIo_OpenDev($hash, $disco, "MQTT2_CLIENT_doinit", sub(){})
+                if($hash->{connecting});
+}
+
+sub
+MQTT2_CLIENT_doinit($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  ############################## CONNECT
+  if($hash->{connecting} == 1) {
+    my $usr = AttrVal($name, "username", "");
+    my ($err, $pwd) = getKeyValue($name);
+    if($err) {
+      Log 1, "ERROR: $err";
+      return;
+    }
+    my ($lwtt, $lwtm) = split(" ",AttrVal($name, "lwt", ""),2);
+    my $lwtr = AttrVal($name, "lwtRetain", 0);
+    my $m31 = (AttrVal($name, "mqttVersion", "3.1") eq "3.1");
+    my $msg = 
+        ($m31 ? pack("n",6)."MQIsdp".pack("C",3):
+                pack("n",4)."MQTT"  .pack("C",4)).
+        pack("C", ($usr  ? 0x80 : 0)+
+                  ($pwd  ? 0x40 : 0)+
+                  ($lwtr ? 0x20 : 0)+
+                  ($lwtt ? 0x04 : 0)+2). # clean session
+        pack("n", $keepalive).
+        pack("n", length($hash->{clientId})).$hash->{clientId}.
+        ($lwtt ? (pack("n", length($lwtt)).$lwtt).
+                 (pack("n", length($lwtm)).$lwtm) : "").
+        ($usr ? (pack("n", length($usr)).$usr) : "").
+        ($pwd ? (pack("n", length($pwd)).$pwd) : "");
+    $hash->{connecting} = 2;
+    addToWritebuffer($hash,
+      pack("C",0x10).
+      MQTT2_CLIENT_calcRemainingLength(length($msg)).$msg);
+    RemoveInternalTimer($hash);
+    InternalTimer(gettimeofday()+$keepalive, "MQTT2_CLIENT_keepalive",$hash,0);
+
+  ############################## SUBSCRIBE
+  } elsif($hash->{connecting} == 2) {
+    my $msg = 
+        pack("n", $hash->{FD}). # packed Identifier
+        join("", map { pack("n", length($_)).$_.pack("C",0) } # QOS:0
+                 split(" ", AttrVal($name, "subscriptions", "#")));
+    addToWritebuffer($hash,
+      pack("C",0x80).
+      MQTT2_CLIENT_calcRemainingLength(length($msg)).$msg);
+    $hash->{connecting} = 3;
+
+  }
+  return undef;
+}
+
+sub
+MQTT2_CLIENT_keepalive($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+  return if(ReadingsVal($name, "state", "") ne "opened");
+  Log3 $name, 5, "$name: keepalive $keepalive";
+  my $msg = join("", map { pack("n", length($_)).$_.pack("C",0) } # QOS:0
+                     split(" ", AttrVal($name, "subscriptions", "#")));
+  addToWritebuffer($hash,
+    pack("C",0xC0).pack("C",0));
+  InternalTimer(gettimeofday()+$keepalive, "MQTT2_CLIENT_keepalive", $hash, 0);
+}
+
+sub
+MQTT2_CLIENT_Undef($@)
+{
+  my ($hash, $arg) = @_;
+  DevIo_CloseDev($hash);
+  return undef;
+}
+
+sub
+MQTT2_CLIENT_Delete($@)
+{
+  my ($hash, $arg) = @_;
+  setKeyValue($hash->{NAME}, undef);
+  return undef;
+}
+
+
+sub
+MQTT2_CLIENT_Attr(@)
+{
+  my ($type, $devName, $attrName, @param) = @_;
+  my $hash = $defs{$devName};
+  if($type eq "set" && $attrName eq "SSL") {
+    $hash->{SSL} = $param[0] ? $param[0] : 1;
+  }
+
+  if($attrName eq "clientId") {
+    $hash->{clientId} = $param[0];
+    $hash->{clientId} =~ s/[^0-9a-zA-Z]//g;
+    $hash->{clientId} = "MQTT2_CLIENT" if(!$hash->{clientId});
+  }
+
+  my %h = (clientId=>1,lwt=>1,lwtRetain=>1,subscriptions=>1,SSL=>1,username=>1);
+  if($init_done && $h{$attrName}) {
+    MQTT2_CLIENT_Disco($hash);
+  }
+  return undef;
+} 
+
+sub
+MQTT2_CLIENT_Disco($)
+{
+  my ($hash) = @_;
+  RemoveInternalTimer($hash);
+  $hash->{connecting} = 1;
+  DevIo_Disconnected($hash);
+}
+
+sub
+MQTT2_CLIENT_Set($@)
+{
+  my ($hash, @a) = @_;
+  my %sets = ( password=>2, publish=>2 );
+  my $name = $hash->{NAME};
+  shift(@a);
+
+  return "Unknown argument ?, choose one of ".join(" ", keys %sets)
+        if(!$a[0] || !$sets{$a[0]});
+
+  if($a[0] eq "publish") {
+    shift(@a);
+    my $retain;
+    if(@a>2 && $a[0] eq "-r") {
+      $retain = 1;
+      shift(@a);
+    }
+    return "Usage: set $name publish -r topic [value]" if(@a < 1);
+    my $tp = shift(@a);
+    my $val = join(" ", @a);
+    MQTT2_CLIENT_doPublish($hash, $tp, $val, $retain);
+  }
+
+  if($a[0] eq "password") {
+    return "Usage: set $name password <password>" if(@a < 1);
+    setKeyValue($name, $a[1]);
+    MQTT2_CLIENT_Disco($hash) if($init_done);
+  }
+}
+
+my %cptype = (
+  0 => "RESERVED_0",
+  1 => "CONNECT",
+  2 => "CONNACK", #
+  3 => "PUBLISH", #
+  4 => "PUBACK",  #
+  5 => "PUBREC",
+  6 => "PUBREL",
+  7 => "PUBCOMP",
+  8 => "SUBSCRIBE",
+  9 => "SUBACK",  #
+ 10 => "UNSUBSCRIBE",
+ 11 => "UNSUBACK",
+ 12 => "PINGREQ",
+ 13 => "PINGRESP",  #
+ 14 => "DISCONNECT",#
+ 15 => "RESERVED_15",
+);
+
+#####################################
+sub
+MQTT2_CLIENT_Read($@)
+{
+  my ($hash, $reread) = @_;
+
+  my $name = $hash->{NAME};
+  my $fd = $hash->{FD};
+
+  if(!$reread) {
+    my $buf = DevIo_SimpleRead($hash);
+    return "" if(!defined($buf));
+    $hash->{BUF} .= $buf;
+  }
+
+  my ($tlen, $off) = MQTT2_CLIENT_getRemainingLength($hash);
+  if($tlen < 0 || $tlen+$off<=0) {
+    Log3 $name, 1, "Bogus data from $name, closing connection";
+    MQTT2_CLIENT_Disco($hash);
+    return;
+  }
+  return if(length($hash->{BUF}) < $tlen+$off);
+
+  my $fb = substr($hash->{BUF}, 0, 1);
+  my $pl = substr($hash->{BUF}, $off, $tlen); # payload
+  $hash->{BUF} = substr($hash->{BUF}, $tlen+$off);
+
+  my $cp = ord(substr($fb,0,1)) >> 4;
+  my $cpt = $cptype{$cp};
+  $hash->{lastMsgTime} = gettimeofday();
+
+  # Lowlevel debugging
+  if(AttrVal($name, "verbose", 1) >= 5) {
+    my $pltxt = $pl;
+    $pltxt =~ s/([^ -~])/"(".ord($1).")"/ge;
+    Log3 $name, 5, "$name: received $cpt $pltxt";
+  }
+
+  ####################################
+  if($cpt eq "CONNACK")  {
+    my $rc = ord(substr($pl,1,1));
+    if($rc == 0) {
+      my $onc = AttrVal($name, "onConnect", "");
+      MQTT2_CLIENT_doPublish($hash, split(" ", $onc, 2)) if($onc);
+      MQTT2_CLIENT_doinit($hash);
+
+    } else {
+      my @txt = ("Accepted", "bad proto", "bad id", "server unavailable",
+                  "bad user name or password", "not authorized");
+      Log3 $name, 1, "$name: Connection refused, ".
+        ($rc <= int(@txt) ? $txt[$rc] : "unknown error $rc");
+      MQTT2_CLIENT_Disco($hash);
+      return;
+    }
+  } elsif($cpt eq "PUBACK")   { # ignore it
+  } elsif($cpt eq "SUBACK")   {
+    delete($hash->{connecting});
+
+  } elsif($cpt eq "PINGRESP") { # ignore it
+  } elsif($cpt eq "PUBLISH")  {
+    my $cf = ord(substr($fb,0,1)) & 0xf;
+    my $qos = ($cf & 0x06) >> 1;
+    my ($tp, $val, $pid);
+    ($tp, $off) = MQTT2_CLIENT_getStr($pl, 0);
+    if($qos) {
+      $pid = unpack('n', substr($pl, $off, 2));
+      $off += 2;
+    }
+    $val = substr($pl, $off);
+    addToWritebuffer($hash, pack("CCnC*", 0x40, 2, $pid)) if($qos); # PUBACK
+
+    if(!IsDisabled($name)) {
+      $val = "" if(!defined($val));
+      my $ac = AttrVal($name, "autocreate", undef) ? "autocreate:":"";
+      my $cid = $hash->{clientId};
+      Dispatch($hash, "$ac$cid:$tp:$val", undef, !$ac);
+
+      my $re = AttrVal($name, "rawEvents", undef);
+      DoTrigger($name, "$tp:$val") if($re && $tp =~ m/$re/);
+    }
+
+  } else {
+    Log 1, "M2: Unhandled packet $cpt, disconneting $name";
+    MQTT2_CLIENT_Disco($hash);
+  }
+  return MQTT2_CLIENT_Read($hash, 1);
+}
+
+######################################
+# send topic to client if its subscription matches the topic
+sub
+MQTT2_CLIENT_doPublish($$$$)
+{
+  my ($hash, $topic, $val, $retain) = @_;
+  my $name = $hash->{NAME};
+  return if(IsDisabled($name));
+  $val = "" if(!defined($val));
+  Log3 $name, 5, "$name: sending PUBLISH $topic $val";
+  addToWritebuffer($hash,
+    pack("C",0x30).
+    MQTT2_CLIENT_calcRemainingLength(2+length($topic)+length($val)).
+    pack("n", length($topic)).
+    $topic.$val);
+}
+
+sub
+MQTT2_CLIENT_Write($$$)
+{
+  my ($hash,$topic,$msg) = @_;
+  my $retain;
+  if($topic =~ m/^(.*):r$/) {
+    $topic = $1;
+    $retain = 1;
+  }
+  MQTT2_CLIENT_doPublish($hash, $topic, $msg, $retain);
+}
+
+sub
+MQTT2_CLIENT_calcRemainingLength($)
+{
+  my ($l) = @_;
+  my @r;
+  while($l > 0) {
+    my $eb = $l % 128;
+    $l = int($l/128);
+    $eb += 128 if($l);
+    push(@r, $eb);
+  }
+  return pack("C*", @r);
+}
+
+sub
+MQTT2_CLIENT_getRemainingLength($)
+{
+  my ($hash) = @_;
+  return (2,2) if(length($hash->{BUF}) < 2);
+
+  my $ret = 0;
+  my $mul = 1;
+  for(my $off = 1; $off <= 4; $off++) {
+    my $b = ord(substr($hash->{BUF},$off,1));
+    $ret += ($b & 0x7f)*$mul;
+    return ($ret, $off+1) if(($b & 0x80) == 0);
+    $mul *= 128;
+  }
+  return -1;
+}
+
+sub
+MQTT2_CLIENT_getStr($$)
+{
+  my ($in, $off) = @_;
+  my $l = unpack("n", substr($in, $off, 2));
+  return (substr($in, $off+2, $l), $off+2+$l);
+}
+
+1;
+
+=pod
+=item helper
+=item summary    Connection to an external MQTT server
+=item summary_DE Verbindung zu einem externen MQTT Server
+=begin html
+
+<a name="MQTT2_CLIENT"></a>
+<h3>MQTT2_CLIENT</h3>
+<ul>
+  MQTT2_CLIENT is a cleanroom implementation of an MQTT client (which connects
+  to an external server, like mosquitto) using no perl libraries. It serves as
+  an IODev to MQTT2_DEVICES.
+  <br> <br>
+
+  <a name="MQTT2_CLIENTdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; MQTT2_CLIENT &lt;host&gt;:&lt;port&gt;</code>
+    <br><br>
+    Connect to the server on &lt;host&gt; and &lt;port&gt;. &lt;port&gt; 1883
+    is default for mosquitto.
+    <br>
+    Notes:<br>
+    <ul>
+    <li>only QOS 0 and 1 is implemented</li>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="MQTT2_CLIENTset"></a>
+  <b>Set</b>
+  <ul>
+    <li>publish -r topic value<br>
+      publish a message, -r denotes setting the retain flag.
+      </li><br>
+    <li>password &lt;password&gt; value<br>
+      set the password, which is stored in the FHEM/FhemUtils/uniqueID file.
+      </li>
+  </ul>
+  <br>
+
+  <a name="MQTT2_CLIENTget"></a>
+  <b>Get</b>
+  <ul>N/A</ul><br>
+
+  <a name="MQTT2_CLIENTattr"></a>
+  <b>Attributes</b>
+  <ul>
+
+    <a name="autocreate"></a>
+    <li>autocreate<br>
+      if set, at least one MQTT2_DEVICE will be created, and its readingsList
+      will be expanded upon reception of published messages. Note: this is
+      slightly different from MQTT2_SERVER, where each connection has its own
+      clientId.  This parameter is sadly not transferred via the MQTT protocol,
+      so the clientId of this MQTT2_CLIENT instance will be used.
+      </li></br>
+
+    <a name="clientId"></a>
+    <li>clientId &lt;name&gt;<br>
+      set the MQTT clientId. If not set, the name of the MQTT2_CLIENT instance
+      is used, after deleting everything outside 0-9a-zA-Z
+      </li></br>
+
+    <li><a href="#disable">disable</a><br>
+        <a href="#disabledForIntervals">disabledForIntervals</a><br>
+      disable dispatching of messages.
+      </li><br>
+
+    <a name="lwt"></a>
+    <li>lwt &lt;topic&gt; &lt;message&gt; <br>
+      set the LWT (last will and testament) topic and message, default is empty.
+      </li></br>
+
+    <a name="lwtRetain"></a>
+    <li>lwtRetain<br>
+      if set, the lwt retain flag is set
+      </li></br>
+
+    <a name="mqttVersion"></a>
+    <li>mqttVersion 3.1,3.1.1<br>
+      set the MQTT protocol version in the CONNECT header, default is 3.1
+      </li></br>
+
+    <a name="onConnect"></a>
+    <li>onConnect topic message<br>
+      publish the topic after each connect or reconnect.
+      </li></br>
+
+    <a name="rawEvents"></a>
+    <li>rawEvents &lt;topic-regexp&gt;<br>
+      send all messages as events attributed to this MQTT2_CLIENT instance.
+      Should only be used, if there is no MQTT2_DEVICE to process the topic.
+      </li><br>
+
+    <a name="subscriptions"></a>
+    <li>subscriptions &lt;subscriptions&gt;<br>
+      space separated list of MQTT subscriptions, default is #
+      </li><br>
+
+    <a name="SSL"></a>
+    <li>SSL<br>
+      Enable SSL (i.e. TLS)
+      </li><br>
+
+    <a name="username"></a>
+    <li>username &lt;username&gt;<br>
+      set the username. The password is set via the set command, and is stored
+      separately, see above.
+      </li><br>
+
+  </ul>
+</ul>
+=end html
+
+=cut

+ 859 - 0
fhem/core/FHEM/72_Spritpreis.pm

@@ -0,0 +1,859 @@
+##############################################
+# $Id: 72_Spritpreis.pm 0 2017-01-10 12:00:00Z pjakobs $
+
+# v0.0: inital testing
+# v0.1: basic functionality for pre-configured Tankerkoenig IDs
+
+
+package main;
+
+use strict;
+use warnings;
+
+use Time::HiRes;
+use Time::HiRes qw(usleep nanosleep);
+use Time::HiRes qw(time);
+use JSON::XS;
+use URI::URL;
+use Data::Dumper;
+require "HttpUtils.pm";
+
+$Data::Dumper::Indent = 1;
+$Data::Dumper::Sortkeys = 1;
+
+#####################################
+#
+# fhem skeleton functions
+#
+#####################################
+
+sub
+Spritpreis_Initialize(@) {
+    my ($hash) = @_;
+
+    $hash->{DefFn}          = 'Spritpreis_Define';
+    $hash->{UndefFn}        = 'Spritpreis_Undef';
+    $hash->{ShutdownFn}     = 'Spritpreis_Undef';
+    $hash->{SetFn}          = 'Spritpreis_Set';
+    $hash->{GetFn}          = 'Spritpreis_Get';
+    $hash->{AttrFn}         = 'Spritpreis_Attr';
+    $hash->{NotifyFn}       = 'Spritpreis_Notify';
+    $hash->{ReadFn}         = 'Spritpreis_Read';
+    $hash->{AttrList}       = "lat lon rad IDs type sortby apikey interval address priceformat:2dezCut,2dezRound,3dez"." $readingFnAttributes";
+    #$hash->{AttrList}       = "IDs type interval"." $readingFnAttributes";
+    return undef;
+}
+
+sub
+Spritpreis_Define($$) {
+    #####################################
+    #
+    # how to define this
+    #
+    # for Tankerkönig:
+    # define <myName> Spritpreis Tankerkoenig <TankerkönigAPI_ID>
+    #
+    # for Spritpreisrechner.at
+    # define <myName> Spritpreis Spritpreisrechner
+    #
+    #####################################
+
+
+    my $apiKey;
+    my ($hash, $def)=@_;
+    my @parts=split("[ \t][ \t]*", $def);
+    my $name=$parts[0];
+    if(defined $parts[2]){
+        if($parts[2] eq "Tankerkoenig"){
+            ##
+            if(defined $parts[3]){
+                $apiKey=$parts[3];
+            }else{
+                Log3 ($hash, 2, "$hash->{NAME} Module $parts[1] requires a valid apikey");
+                return undef;
+            }
+
+            my $result;
+            my $url="https://creativecommons.tankerkoenig.de/json/prices.php?ids=12121212-1212-1212-1212-121212121212&apikey=".$apiKey;
+
+            my $param= {
+            url      => $url,
+            timeout  => 1,
+            method   => "GET",
+            header   => "User-Agent: fhem\r\nAccept: application/json",
+            };
+
+            my ($err, $data)=HttpUtils_BlockingGet($param);
+
+            if ($err){
+                Log3($hash,2,"$hash->{NAME}: Error verifying APIKey: $err");
+                return undef;
+            }else{
+                eval {
+                    $result = JSON->new->utf8(1)->decode($data);
+                };
+                if ($@) {
+                    Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
+                } else {
+                    if ($result->{ok} ne "true" && $result->{ok} != 1){
+                        Log3 ($hash, 2, "$hash->{name}: error: $result->{message}");
+                        return undef;
+                    }
+                }
+                $hash->{helper}->{apiKey}=$apiKey;
+                $hash->{helper}->{service}="Tankerkoenig";
+            }
+            if(AttrVal($hash->{NAME}, "IDs","")){
+                #
+                # if IDs attribute is defined, populate list of stations at startup
+                #
+                my $ret=Spritpreis_Tankerkoenig_populateStationsFromAttr($hash);
+            }
+            #
+            # start initial update
+            #
+            Spritpreis_Tankerkoenig_updateAll($hash);
+        } elsif($parts[2] eq "Spritpreisrechner"){
+            $hash->{helper}->{service}="Spritpreisrechner";
+        }
+    }else{
+        Log3($hash,2,"$hash->{NAME} Module $parts[1] requires a provider specification. Currently either \"Tankerkoenig\" (for de) or \"Spritpreisrechner\" (for at)");
+    }
+    return undef;
+}
+
+sub
+Spritpreis_Undef(@){
+    my ($hash,$name)=@_;
+    RemoveInternalTimer($hash);
+    return undef;
+}
+
+sub
+Spritpreis_Set(@) {
+    my ($hash , $name, $cmd, @args) = @_;
+    return "Unknown command $cmd, choose one of update add delete" if ($cmd eq '?');
+    Log3($hash, 3,"$hash->{NAME}: get $hash->{NAME} $cmd $args[0]");
+
+    if ($cmd eq "update"){
+        if(defined $args[0]){
+            if($args[0] eq "all"){
+                # removing the timer so we don't get a flurry of requests
+                RemoveInternalTimer($hash);
+                Spritpreis_Tankerkoenig_updateAll($hash);
+            }elsif($args[0] eq "id"){
+                if(defined $args[1]){
+                    Spritpreis_Tankerkoenig_updatePricesForIDs($hash, $args[1]);
+                }else{
+                    my $r="update id requires an id parameter!";
+                    Log3($hash, 2,"$hash->{NAME} $r");
+                    return $r;
+                }
+            }
+        }else{
+            #
+            # default behaviour if no ID or "all" is given is to update all existing IDs
+            #
+            Spritpreis_Tankerkoenig_updateAll($hash);
+        }
+    }elsif($cmd eq "add"){
+        if(defined $args[0]){
+            Log3($hash, 4,"$hash->{NAME} add: args[0]=$args[0]");
+            if($args[0] eq "id"){
+                #
+                # add station by providing a single Tankerkoenig ID
+                #
+                if(defined($args[1])){
+                    Spritpreis_Tankerkoenig_GetDetailsForID($hash, $args[1]);
+                }else{
+                    my $ret="add by id requires a station id";
+                    return $ret;
+                }
+            }
+        }else{
+            my $ret="add requires id or (some other method here soon)";
+            return $ret;
+        }
+    }elsif($cmd eq "delete"){
+        #
+        # not sure how to "remove" readings through the fhem api
+        #
+    }
+    return undef;
+}
+
+sub
+Spritpreis_Get(@) {
+    my ($hash, $name, $cmd, @args) = @_;
+
+    return "Unknown command $cmd, choose one of search test" if ($cmd eq '?');
+    Log3($hash, 3,"$hash->{NAME}: get $hash->{NAME} $cmd $args[0]");
+
+    if ($cmd eq "search"){
+        my $str='';
+        my $i=0;
+        while($args[$i++]){
+            $str=$str." ".$args[$i];
+        }
+        Log3($hash,4,"$hash->{NAME}: search string: $str");
+        my @loc=Spritpreis_GetCoordinatesForAddress($hash, $str);
+        my ($lat, $lng, $str)=@loc;
+        if($lat==0 && $lng==0){
+            return $str;
+        }else{
+            if($hash->{helper}->{service} eq "Tankerkoenig"){
+                my $ret=Spritpreis_Tankerkoenig_GetStationIDsForLocation($hash, @loc);
+                return $ret;
+            }
+        }
+    }elsif($cmd eq "test"){
+            my $ret=Spritpreis_Tankerkoenig_populateStationsFromAttr($hash);
+            return $ret;
+
+    }else{
+        return undef;
+    }
+    #Spritpreis_Tankerkoenig_GetPricesForLocation($hash);
+    #Spritpreis_GetCoordinatesForAddress($hash,"Hamburg, Elbphilharmonie");
+    # add price trigger here
+    return undef;
+}
+
+sub
+Spritpreis_Attr(@) {
+    my ($cmd, $device, $attrName, $attrVal)=@_;
+    my $hash = $defs{$device};
+
+    if ($cmd eq 'set' and $attrName eq 'interval'){
+        Spritpreis_updateAll($hash);
+    }
+    return undef;
+}
+
+sub
+Spritpreis_Notify(@) {
+    return undef;
+}
+
+sub
+Spritpreis_Read(@) {
+    return undef;
+}
+
+#####################################
+#
+# generalized functions
+# these functions will call the
+# specific functions for the defined
+# provider.
+#
+#####################################
+
+sub
+Spritpreis_GetDetailsForID(){
+}
+
+sub
+Spritpreis_updateAll(@){
+    my ($hash)=@_;
+    if($hash->{helper}->{service} eq "Tankerkoenig"){
+        Spritpreis_Tankerkoenig_updateAll();
+    }elsif($hash->{helper}->{service} eq "Spritpreisrechner"){
+    }
+}
+
+#####################################
+#
+# functions to create requests
+#
+#####################################
+
+sub
+Spritpreis_Tankerkoenig_GetIDsForLocation(@){
+    my ($hash) = @_;
+    my $lat=AttrVal($hash->{'NAME'}, "lat",0);
+    my $lng=AttrVal($hash->{'NAME'}, "lon",0);
+    my $rad=AttrVal($hash->{'NAME'}, "rad",5);
+    my $type=AttrVal($hash->{'NAME'}, "type","diesel");
+    my $apiKey=$hash->{helper}->{apiKey};
+    Log3($hash,4,"$hash->{'NAME'}: apiKey: $apiKey");
+
+    if($apiKey eq "") {
+        Log3($hash,3,"$hash->{'NAME'}: please provide a valid apikey, you can get it from https://creativecommons.tankerkoenig.de/#register. This function can't work without it");
+        my $r="err no APIKEY";
+        return $r;
+    }
+
+    my $url="https://creativecommons.tankerkoenig.de/json/list.php?lat=$lat&lng=$lng&rad=$rad&type=$type&apikey=$apiKey";
+    my $param = {
+        url      => $url,
+        timeout  => 2,
+        hash     => $hash,
+        method   => "GET",
+        header   => "User-Agent: fhem\r\nAccept: application/json",
+        parser   => \&Spritpreis_ParseIDsForLocation,
+        callback => \&Spritpreis_callback
+    };
+    HttpUtils_NonblockingGet($param);
+
+    return undef;
+}
+
+#--------------------------------------------------
+# sub
+# Spritpreis_Tankerkoenig_GetIDs(@){
+#     my ($hash) = @_;
+#     Log3($hash, 4, "$hash->{NAME} called Spritpreis_Tankerkoenig_updatePricesForIDs");
+#     my $IDstring=AttrVal($hash->{NAME}, "IDs","");
+#     Log3($hash,4,"$hash->{NAME}: got ID String $IDstring");
+#     my @IDs=split(",", $IDstring);
+#     my $i=1;
+#     my $j=1;
+#     my $IDList;
+#     do {
+#         $IDList=$IDs[0];
+#         #
+#         # todo hier stimmt was mit den Indizes nicht!
+#         #
+#         do {
+#             $IDList=$IDList.",".$IDs[$i];
+#         }while($j++ < 9 && defined($IDs[$i++]));
+#         Spritpreis_Tankerkoenig_updatePricesForIDs($hash, $IDList);
+#         Log3($hash, 4,"$hash->{NAME}: Set ending at $i IDList=$IDList");
+#         $j=1;
+#     }while(defined($IDs[$i]));
+#     return undef;
+# }
+#--------------------------------------------------
+
+sub
+Spritpreis_Tankerkoenig_populateStationsFromAttr(@){
+    #
+    # This walks through the IDs Attribute and adds the stations listed there to the station readings list,
+    # initially getting full details
+    #
+    my ($hash) =@_;
+    Log3($hash,4, "$hash->{NAME}: called Spritpreis_Tankerkoenig_populateStationsFromAttr ");
+    my $IDstring=AttrVal($hash->{NAME}, "IDs","");
+    Log3($hash,4,"$hash->{NAME}: got ID String $IDstring");
+    my @IDs=split(",", $IDstring);
+    my $i;
+    do{
+        Spritpreis_Tankerkoenig_GetDetailsForID($hash, $IDs[$i]);
+    }while(defined($IDs[$i++]));
+}
+
+sub
+Spritpreis_Tankerkoenig_updateAll(@){
+    #
+    # this walks through the list of ID Readings and updates the fuel prices for those stations
+    # it does this in blocks of 10 as suggested by the Tankerkoenig API
+    #
+    my ($hash) = @_;
+    Log3($hash,4, "$hash->{NAME}: called Spritpreis_Tankerkoenig_updateAll ");
+    my $i=1;
+    my $j=0;
+    my $id;
+    my $IDList;
+    do {
+        $IDList=ReadingsVal($hash->{NAME}, $j."_id", "");
+        while($j++<9 && ReadingsVal($hash->{NAME}, $i."_id", "") ne "" ){
+            Log3($hash, 5, "$hash->{NAME}: i: $i, j: $j, id: ".ReadingsVal($hash->{NAME}, $i."_id", "") );
+            $IDList=$IDList.",".ReadingsVal($hash->{NAME}, $i."_id", "");
+            $i++;
+        }
+        if($IDList ne ""){
+            Spritpreis_Tankerkoenig_updatePricesForIDs($hash, $IDList);
+            Log3($hash, 4,"$hash->{NAME}(update all): Set ending at $i IDList=$IDList");
+        }
+        $j=1;
+    }while(ReadingsVal($hash->{NAME}, $i."_id", "") ne "" );
+    Log3($hash, 4, "$hash->{NAME}: updateAll set timer for ".(gettimeofday()+AttrVal($hash->{NAME},"interval",15)*60)." delay ".(AttrVal($hash->{NAME},"interval", 15)*60));
+    InternalTimer(gettimeofday()+AttrVal($hash->{NAME}, "interval",15)*60, "Spritpreis_Tankerkoenig_updateAll",$hash);
+    return undef;
+}
+
+sub
+Spritpreis_Tankerkoenig_GetDetailsForID(@){
+    #
+    # This queries the Tankerkoenig API for the details for a specific ID
+    # It does not verify the provided ID
+    # The parser function is responsible for handling the response
+    #
+    my ($hash, $id)=@_;
+    my $apiKey=$hash->{helper}->{apiKey};
+    if($apiKey eq "") {
+        Log3($hash,3,"$hash->{'NAME'}: please provide a valid apikey, you can get it from https://creativecommons.tankerkoenig.de/#register. This function can't work without it");
+        my $r="err no APIKEY";
+        return $r;
+    }
+    my $url="https://creativecommons.tankerkoenig.de/json/detail.php?id=".$id."&apikey=$apiKey";
+    Log3($hash, 4,"$hash->{NAME}: called $url");
+    my $param={
+        url     =>  $url,
+        hash    =>  $hash,
+        timeout =>  10,
+        method  =>  "GET",
+        header  =>  "User-Agent: fhem\r\nAccept: application/json",
+        parser  =>  \&Spritpreis_Tankerkoenig_ParseDetailsForID,
+        callback=>  \&Spritpreis_callback
+    };
+    HttpUtils_NonblockingGet($param);
+    return undef;
+}
+
+sub
+Spritpreis_Tankerkoenig_updatePricesForIDs(@){
+    #
+    # This queries the Tankerkoenig API for an update on all prices. It takes a list of up to 10 IDs.
+    # It will not verify the validity of those IDs nor will it check that the number is 10 or less
+    # The parser function is responsible for handling the response
+    #
+    my ($hash, $IDList) = @_;
+    my $apiKey=$hash->{helper}->{apiKey};
+    my $url="https://creativecommons.tankerkoenig.de/json/prices.php?ids=".$IDList."&apikey=$apiKey";
+    Log3($hash, 4,"$hash->{NAME}: called $url");
+    my $param={
+        url     =>  $url,
+        hash    =>  $hash,
+        timeout =>  10,
+        method  =>  "GET",
+        header  =>  "User-Agent: fhem\r\nAccept: application/json",
+        parser  =>  \&Spritpreis_Tankerkoenig_ParsePricesForIDs,
+        callback=>  \&Spritpreis_callback
+    };
+    HttpUtils_NonblockingGet($param);
+    return undef;
+}
+
+sub
+Spritpreis_Spritpreisrechner_updatePricesForLocation(@){
+    #
+    # for the Austrian Spritpreisrechner, there's not concept of IDs. The only method
+    # is to query for prices by location which will make it difficult to follow the
+    # price trend at any specific station.
+    #
+    my ($hash)=@_;
+    my $url="http://www.spritpreisrechner.at/espritmap-app/GasStationServlet";
+    my $lat=AttrVal($hash->{'NAME'}, "lat",0);
+    my $lng=AttrVal($hash->{'NAME'}, "lon",0);
+
+    my $param={
+        url     => $url,
+        timeout => 1,
+        method  => "POST",
+        header  => "User-Agent: fhem\r\nAccept: application/json",
+        data    => {
+            "",
+            "DIE",
+            "15.409674251128",
+            "47.051201316374",
+            "15.489496791403",
+            "47.074588294516"
+        }
+    };
+    my ($err,$data)=HttpUtils_BlockingGet($param);
+    Log3($hash,5,"$hash->{'NAME'}: Dumper($data)");
+    return undef;
+}
+
+sub
+Spritpreis_Tankerkoenig_GetStationIDsForLocation(@){
+   #
+   # This is currently not being used. The idea is to provide a lat/long location and a radius and have
+   # the stations within this radius are presented as a list and, upon selecting them, will be added
+   # to the readings list
+   #
+   my ($hash,@location) = @_;
+
+   # my $lat=AttrVal($hash->{'NAME'}, "lat",0);
+   # my $lng=AttrVal($hash->{'NAME'}, "lon",0);
+   my $rad=AttrVal($hash->{'NAME'}, "rad",5);
+   my $type=AttrVal($hash->{'NAME'}, "type","all");
+   # my $sort=AttrVal($hash->{'NAME'}, "sortby","price");
+   my $apiKey=$hash->{helper}->{apiKey};
+
+   my ($lat, $lng, $formattedAddress)=@location;
+
+   my $result;
+
+   if($apiKey eq "") {
+       Log3($hash,3,"$hash->{'NAME'}: please provide a valid apikey, you can get it from https://creativecommons.tankerkoenig.de/#register. This function can't work without it");
+       my $r="err no APIKEY";
+       return $r;
+   }
+   my $url="https://creativecommons.tankerkoenig.de/json/list.php?lat=$lat&lng=$lng&rad=$rad&type=$type&apikey=$apiKey";
+
+   Log3($hash, 4,"$hash->{NAME}: sending request with url $url");
+
+   my $param= {
+       url      => $url,
+       hash     => $hash,
+       timeout  => 1,
+       method   => "GET",
+       header   => "User-Agent: fhem\r\nAccept: application/json",
+    };
+    my ($err, $data) = HttpUtils_BlockingGet($param);
+
+    if($err){
+        Log3($hash, 4, "$hash->{NAME}: error fetching nformation");
+    } elsif($data){
+        Log3($hash, 4, "$hash->{NAME}: got data");
+        Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
+
+        eval {
+            $result = JSON->new->utf8(1)->decode($data);
+        };
+        if ($@) {
+            Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
+        } else {
+            my @headerHost = grep /Host/, @FW_httpheader;
+            $headerHost[0] =~ s/Host: //g;
+
+            my ($stations) = $result->{stations};
+            my $ret="<html><p><h3>Stations for Address</h3></p><p><h2>$formattedAddress</h2></p><table><tr><td>Name</td><td>Ort</td><td>Straße</td></tr>";
+            foreach (@{$stations}){
+                (my $station)=$_;
+
+                Log3($hash, 2, "Name: $station->{name}, id: $station->{id}");
+                $ret=$ret . "<tr><td><a href=http://" .
+                            $headerHost[0] .
+                            "/fhem?cmd=set+" .
+                            $hash->{NAME} .
+                            "+add+id+" .
+                            $station->{id} .
+                            ">";
+                $ret=$ret . $station->{name} . "</td><td>" . $station->{place} . "</td><td>" . $station->{street} . " " . $station->{houseNumber} . "</td></tr>";
+            }
+            $ret=$ret . "</table>";
+            Log3($hash,2,"$hash->{NAME}: ############# ret: $ret");
+            return $ret;
+        }
+    }else {
+        Log3 ($hash, 4, "$hash->{NAME}: something's very odd");
+    }
+    return $data;
+    # InternalTimer(gettimeofday()+AttrVal($hash->{NAME}, "interval",15)*60, "Spritpreis_Tankerkoenig_GetPricesForLocation",$hash);
+    return undef;
+}
+
+#####################################
+#
+# functions to handle responses
+#
+#####################################
+
+sub
+Spritpreis_callback(@) {
+    #
+    # the generalized callback function. This should check all the general API errors and
+    # handle them centrally, leaving the parser functions to handle response specific errors
+    #
+    my ($param, $err, $data) = @_;
+    my ($hash) = $param->{hash};
+
+    # TODO generic error handling
+    #Log3($hash, 5, "$hash->{NAME}: received callback with $data");
+    # do the result-parser callback
+    if ($err){
+        Log3($hash, 4, "$hash->{NAME}: error fetching information: $err");
+        return undef;
+    }
+    my $parser = $param->{parser};
+    #Log3($hash, 4, "$hash->{NAME}: calling parser $parser with err $err and data $data");
+    &$parser($hash, $err, $data);
+
+    if( $err || $err ne ""){
+        Log3 ($hash, 3, "$hash->{NAME} Readings NOT updated, received Error: ".$err);
+    }
+  return undef;
+ }
+
+sub
+Spritpreis_ParseIDsForLocation(@){
+    return undef;
+}
+
+sub
+Spritpreis_Tankerkoenig_ParseDetailsForID(@){
+    #
+    # this parses the response generated by the query Spritpreis_Tankerkoenig_GetDetailsForID
+    # The response will contain the ID for a single station being, so no need to go through
+    # multiple parts here. It will work whether or not that ID is currently already in the list
+    # of readings. If it is, the details will be updated, if it is not, the new station will be
+    # added at the end of the list
+    #
+    my ($hash, $err, $data)=@_;
+    my $result;
+
+    if($data){
+        Log3($hash, 4, "$hash->{NAME}: got StationDetail reply");
+        Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
+
+        eval { $result = JSON->new->utf8(1)->decode($data); };
+        if ($@) {
+            Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
+        } else {
+            my $i=0;
+            my $station = $result->{station};
+            while(ReadingsVal($hash->{NAME},$i."_id",$station->{id}) ne $station->{id})
+            {
+                #
+                # this loop iterates through the readings until either an id is equal to the current
+                # response $station->{id} or, if no id is, it will come up with the default which is set
+                # to $station->{id}, thus it will be added
+                #
+                $i++;
+            }
+            readingsBeginUpdate($hash);
+
+            readingsBulkUpdate($hash,$i."_name",$station->{name});
+            my @types=("e5", "e10", "diesel");
+            foreach my $type (@types){
+                Log3($hash,4,"$hash->{NAME}: checking type $type");
+                if(defined($station->{$type})){
+
+					if(AttrVal($hash->{NAME}, "priceformat","") eq "2dezCut"){
+						chop($station->{$type});
+					}elsif(AttrVal($hash->{NAME}, "priceformat","") eq "2dezRound"){
+						$station->{$type}=sprintf("%.2f", $station->{$type});
+					}
+                    if(ReadingsVal($hash->{NAME}, $i."_".$type."_trend",0)!=0){
+                        my $p=ReadingsVal($hash->{NAME}, $i."_".$type."_price",0);
+                        Log3($hash,4,"$hash->{NAME}:parseDetailsForID $type price old: $p");
+                        if($p>$station->{$type}){
+                            readingsBulkUpdate($hash,$i."_".$type."_trend","fällt");
+                            Log3($hash,4,"$hash->{NAME}:parseDetailsForID trend: fällt");
+                        }elsif($p < $station->{$type}){
+                            readingsBulkUpdate($hash,$i."_".$type."_trend","steigt");
+                            Log3($hash,4,"$hash->{NAME}:parseDetailsForID trend: konstant");
+                        }else{
+                        }
+                        readingsBulkUpdate($hash,$i."_".$type."_price",$station->{$type})
+                    }
+                }
+            }
+            readingsBulkUpdate($hash,$i."_place",$station->{place});
+            readingsBulkUpdate($hash,$i."_street",$station->{street}." ".$station->{houseNumber});
+            readingsBulkUpdate($hash,$i."_distance",$station->{dist});
+            readingsBulkUpdate($hash,$i."_brand",$station->{brand});
+            readingsBulkUpdate($hash,$i."_lat",$station->{lat});
+            readingsBulkUpdate($hash,$i."_lon",$station->{lng});
+            readingsBulkUpdate($hash,$i."_id",$station->{id});
+            readingsBulkUpdate($hash,$i."_isOpen",$station->{isOpen});
+
+            readingsEndUpdate($hash,1);
+        }
+    }
+}
+
+sub
+Spritpreis_Tankerkoenig_ParsePricesForIDs(@){
+    #
+    # This parses the response to Spritpreis_Tankerkoenig_updatePricesForIDs
+    # this response contains price updates for the requested stations listed by ID
+    # since we don't keep a context between the API request and the response,
+    # in order to update the correct readings, this routine has to go through the
+    # readings list and make sure it does find matching IDs. It will not add new
+    # stations to the list
+    #
+    my ($hash, $err, $data)=@_;
+    my $result;
+
+     if($err){
+        Log3($hash, 4, "$hash->{NAME}: error fetching information");
+    } elsif($data){
+        Log3($hash, 4, "$hash->{NAME}: got PricesForLocation reply");
+        Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
+
+        eval {
+            $result = JSON->new->utf8(1)->decode($data);
+        };
+        if ($@) {
+            Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
+        } else {
+            my ($stations) = $result->{prices};
+            Log3($hash, 5, "$hash->{NAME}: stations:".Dumper($stations));
+            #
+            # the return value is keyed by stations, therefore, I'll have
+            # to fetch the stations from the existing readings and run
+            # through it along those ids.
+            #
+            my $i=0;
+            while(ReadingsVal($hash->{NAME}, $i."_id", "") ne "" ){
+                my $id=ReadingsVal($hash->{NAME}, $i."_id", "");
+                Log3($hash, 4, "$hash->{NAME}: checking ID $id");
+                if(defined($stations->{$id})){
+                    Log3($hash, 4, "$hash->{NAME}: updating readings-set $i (ID $id)" );
+                    Log3($hash, 5, "$hash->{NAME} Update set:\nprice: $stations->{$id}->{price}\ne5: $stations->{$id}->{e5}\ne10: $stations->{$id}->{e10}\ndiesel: $stations->{$id}->{diesel}\n");
+                    readingsBeginUpdate($hash);
+                    my @types=("e5", "e10", "diesel");
+                    foreach my $type (@types){
+                        Log3($hash, 4, "$hash->{NAME} ParsePricesForIDs checking type $type");
+                        if(defined($stations->{$id}->{$type})){
+
+							if(AttrVal($hash->{NAME}, "priceformat","") eq "2dezCut"){
+								chop($stations->{$id}->{$type});
+							}elsif(AttrVal($hash->{NAME}, "priceformat","") eq "2dezRound"){
+								$stations->{$id}->{$type}=sprintf("%.2f", $stations->{$id}->{$type});
+							}
+                            Log3($hash, 4, "$hash->{NAME} ParsePricesForIDs updating type $type");
+                            #if(ReadingsVal($hash->{NAME}, $i."_".$type."_trend","") ne ""){
+                                my $p=ReadingsVal($hash->{NAME}, $i."_".$type."_price",0);
+                                Log3($hash,4,"$hash->{NAME}:parseDetailsForID $type price old: $p");
+                                if($p>$stations->{$id}->{$type}){
+                                    readingsBulkUpdate($hash,$i."_".$type."_trend","fällt");
+                                }elsif($p < $stations->{$id}->{$type}){
+                                    readingsBulkUpdate($hash,$i."_".$type."_trend","steigt");
+                                }else{
+                                }
+                                #}
+                            readingsBulkUpdate($hash,$i."_".$type."_price",$stations->{$id}->{$type})
+                        }
+                    }
+
+
+                    readingsBulkUpdate($hash,$i."_isOpen",$stations->{$id}->{status});
+
+                    readingsEndUpdate($hash, 1);
+                }
+                $i++;
+            }
+        }
+    }
+    return undef;
+}
+
+sub
+Spritpreis_Tankerkoening_ParseStationIDsForLocation(@){
+    my ($hash, $err, $data)=@_;
+    my $result;
+
+    Log3($hash,5,"$hash->{NAME}: ParsePricesForLocation has been called with err $err and data $data");
+
+    if($err){
+        Log3($hash, 4, "$hash->{NAME}: error fetching information");
+    } elsif($data){
+        Log3($hash, 4, "$hash->{NAME}: got PricesForLocation reply");
+        Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
+
+        eval {
+            $result = JSON->new->utf8(1)->decode($data);
+        };
+        if ($@) {
+            Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
+        } else {
+            my ($stations) = $result->{stations};
+            #Log3($hash, 5, "$hash->{NAME}: stations:".Dumper($stations));
+            my $ret="<html><form action=fhem/cmd?set ".$hash->{NAME}." station method='get'><select multiple name='id'>";
+            foreach (@{$stations}){
+                (my $station)=$_;
+
+                #Log3($hash, 5, "$hash->{NAME}: Station hash:".Dumper($station));
+                Log3($hash, 2, "Name: $station->{name}, id: $station->{id}");
+                $ret=$ret."<option value=".$station->{id}.">".$station->{name}." ".$station->{place}." ".$station->{street}." ".$station->{houseNumber}."</option>";
+            }
+            # readingsEndUpdate($hash,1);
+            $ret=$ret."<button type='submit'>submit</button></html>";
+            Log3($hash,2,"$hash->{NAME}: ############# ret: $ret");
+            return $ret;
+        }
+    }else {
+        Log3 ($hash, 4, "$hash->{NAME}: something's very odd");
+    }
+    return $data;
+}
+
+sub
+Spritpreis_ParsePricesForIDs(@){
+}
+#####################################
+#
+# geolocation functions
+#
+#####################################
+
+sub
+Spritpreis_GetCoordinatesForAddress(@){
+    my ($hash, $address)=@_;
+
+    my $result;
+
+    my $url=new URI::URL 'https://maps.google.com/maps/api/geocode/json';
+    $url->query_form("address",$address);
+    Log3($hash, 3, "$hash->{NAME}: request URL: ".$url);
+
+    my $param= {
+    url      => $url,
+    timeout  => 1,
+    method   => "GET",
+    header   => "User-Agent: fhem\r\nAccept: application/json",
+    };
+    my ($err, $data)=HttpUtils_BlockingGet($param);
+
+    if($err){
+        Log3($hash, 4, "$hash->{NAME}: error fetching nformation");
+    } elsif($data){
+        Log3($hash, 4, "$hash->{NAME}: got CoordinatesForAddress reply");
+        Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
+
+        eval {
+            $result = JSON->new->utf8(1)->decode($data);
+        };
+        if ($@) {
+            Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
+        } else {
+            if ($result->{status} eq "ZERO_RESULTS"){
+                return(0,0,"error: could not find address");
+            }else{
+                my $lat=$result->{results}->[0]->{geometry}->{location}->{lat};
+                my $lon=$result->{results}->[0]->{geometry}->{location}->{lng};
+                my $formattedAddress=$result->{results}->[0]->{formatted_address};
+
+                Log3($hash,3,"$hash->{NAME}: got coordinates for address as lat: $lat, lon: $lon");
+                return ($lat, $lon, $formattedAddress);
+            }
+        }
+    }else {
+        Log3 ($hash, 4, "$hash->{NAME}: something is very odd");
+    }
+    return undef;
+}
+
+sub
+Spritpreis_ParseCoordinatesForAddress(@){
+    my ($hash, $err, $data)=@_;
+    my $result;
+
+    Log3($hash,5,"$hash->{NAME}: ParseCoordinatesForAddress has been called with err $err and data $data");
+
+    if($err){
+        Log3($hash, 4, "$hash->{NAME}: error fetching nformation");
+    } elsif($data){
+        Log3($hash, 4, "$hash->{NAME}: got CoordinatesForAddress reply");
+        Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
+
+        eval {
+            $result = JSON->new->utf8(1)->decode($data);
+        };
+        if ($@) {
+            Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
+        } else {
+            my $lat=$result->{results}->[0]->{geometry}->{location}->{lat};
+            my $lon=$result->{results}->[0]->{geometry}->{location}->{lng};
+
+            Log3($hash,3,"$hash->{NAME}: got coordinates for address as lat: $lat, lon: $lon");
+            return ($lat, $lon);
+        }
+    }else {
+        Log3 ($hash, 4, "$hash->{NAME}: something is very odd");
+    }
+    return undef;
+}
+
+#####################################
+#
+# helper functions
+#
+#####################################
+1;

+ 420 - 0
fhem/core/FHEM/72_TA_CMI_JSON.pm

@@ -0,0 +1,420 @@
+##############################################################################
+#
+#     72_TA_CMI_JSON.pm
+#
+#     This file is part of Fhem.
+#
+#     Fhem is free software: you can redistribute it and/or modify
+#     it under the terms of the GNU General Public License as published by
+#     the Free Software Foundation, either version 2 of the License, or
+#     (at your option) any later version.
+#
+#     Fhem is distributed in the hope that it will be useful,
+#     but WITHOUT ANY WARRANTY; without even the implied warranty of
+#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#     GNU General Public License for more details.
+#
+#     You should have received a copy of the GNU General Public License
+#     along with Fhem.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+#  
+# TA_CMI_JSON (c) Martin Gutenbrunner / https://github.com/delmar43/FHEM
+#
+# This module queries the CMI JSON API and allows to map values to readings.
+# Supported devices are UVR1611, UVR16x2, RSM610, CAN-I/O45, CAN-EZ2, CAN-MTx2,
+# and CAN-BC2 by Technische Alternative https://www.ta.co.at/
+#
+# Information in the Wiki: https://wiki.fhem.de/wiki/TA_CMI_UVR16x2_UVR1611
+#
+# Discussed in FHEM Forum:
+# * https://forum.fhem.de/index.php/topic,92740.0.html (official)
+# * https://forum.fhem.de/index.php/topic,41439.0.html (previous discussions)
+# * https://forum.fhem.de/index.php/topic,13534.45.html (previous discussions)
+#
+# $Id: 72_TA_CMI_JSON.pm 17720 2018-11-10 15:21:47Z delmar $
+#
+##############################################################################
+
+package main;
+use strict;
+use warnings;
+use HttpUtils;
+
+my %deviceNames = (
+  '80' => 'UVR1611',
+  '87' => 'UVR16x2',
+  '88' => 'RSM610',
+  '89' => 'CAN-I/O45',
+  '8B' => 'CAN-EZ2',
+  '8C' => 'CAN-MTx2',
+  '8D' => 'CAN-BC2'
+);
+
+my %versions = (
+  1 => '1.25.2 2016-12-12',
+  2 => '1.26.1 2017-02-24',
+  3 => '1.28.0 2017-11-09'
+);
+
+my %units = (
+   0 => '', 1 => '°C', 2 => 'W/m²', 3 => 'l/h', 4 => 'Sek', 5 => 'Min', 6 => 'l/Imp',
+   7 => 'K', 8 => '%', 10 => 'kW', 11 => 'kWh', 12 => 'MWh', 13 => 'V', 14 => 'mA',
+  15 => 'Std', 16 => 'Tage', 17 => 'Imp', 18 => 'kΩ', 19 => 'l', 20 => 'km/h',
+  21 => 'Hz', 22 => 'l/min', 23 => 'bar', 24 => '', 25 => 'km', 26 => 'm', 27 => 'mm',
+  28 => 'm³', 35 => 'l/d', 36 => 'm/s', 37 => 'm³/min', 38 => 'm³/h', 39 => 'm³/d',
+  40 => 'mm/min', 41 => 'mm/h', 42 => 'mm/d', 43 => 'Aus/Ein', 44 => 'Nein/Ja',
+  46 => '°C', 50 => '€', 51 => '$', 52 => 'g/m³', 53 => '', 54 => '°', 56 => '°',
+  57 => 'Sek', 58 => '', 59 => '%', 60 => 'Uhr', 63 => 'A', 65 => 'mbar', 66 => 'Pa',
+  67 => 'ppm'
+);
+
+my %rasStates = (
+  0 => 'Time/auto',
+  1 => 'Standard',
+  2 => 'Setback',
+  3 => 'Standby/frost pr.'
+);
+
+sub TA_CMI_JSON_Initialize {
+  my ($hash) = @_;
+
+  $hash->{GetFn}     = "TA_CMI_JSON_Get";
+  $hash->{DefFn}     = "TA_CMI_JSON_Define";
+  $hash->{UndefFn}   = "TA_CMI_JSON_Undef";
+
+  $hash->{AttrList} = "username password interval readingNamesInputs readingNamesOutputs readingNamesDL-Bus readingNamesLoggingAnalog readingNamesLoggingDigital includePrettyReadings:0,1 includeUnitReadings:0,1 " . $readingFnAttributes;
+
+  Log3 '', 3, "TA_CMI_JSON - Initialize done ...";
+}
+
+sub TA_CMI_JSON_Define {
+  my ( $hash, $def ) = @_;
+  my @a = split( "[ \t][ \t]*", $def );
+ 
+  my $name   = $a[0];
+  my $module = $a[1];
+  my $cmiUrl = $a[2];
+  my $nodeId = $a[3];
+  my $queryParams = $a[4];
+ 
+  if(@a != 5) {
+     my $msg = "TA_CMI_JSON ($name) - Wrong syntax: define <name> TA_CMI_JSON CMI-URL CAN-Node-ID QueryParameters";
+     Log3 undef, 2, $msg;
+     return $msg;
+  }
+
+  $hash->{NAME} = $name;
+  $hash->{CMIURL} = $cmiUrl;
+  $hash->{NODEID} = $nodeId;
+  $hash->{QUERYPARAM} = $queryParams;
+  $hash->{INTERVAL} = AttrVal( $name, "interval", "60" );
+  
+  Log3 $name, 5, "TA_CMI_JSON ($name) - Define done ... module=$module, CMI-URL=$cmiUrl, nodeId=$nodeId, queryParams=$queryParams";
+
+  readingsSingleUpdate($hash, 'state', 'defined', 1);
+
+  TA_CMI_JSON_GetStatus( $hash, 2 );
+
+  return undef;
+}
+
+sub TA_CMI_JSON_GetStatus {
+  my ( $hash, $delay ) = @_;
+  my $name = $hash->{NAME};
+
+  TA_CMI_JSON_PerformHttpRequest($hash);
+}
+
+sub TA_CMI_JSON_Undef {
+  my ($hash, $arg) = @_; 
+  my $name = $hash->{NAME};
+
+  HttpUtils_Close($hash);
+  RemoveInternalTimer($hash);
+
+  return undef;
+}
+
+sub TA_CMI_JSON_PerformHttpRequest {
+    my ($hash, $def) = @_;
+    my $name = $hash->{NAME};
+    my $url = "http://$hash->{CMIURL}/INCLUDE/api.cgi?jsonnode=$hash->{NODEID}&jsonparam=$hash->{QUERYPARAM}";
+    my $username = AttrVal($name, 'username', 'admin');
+    my $password = AttrVal($name, 'password', 'admin');
+
+    my $param = {
+                    url        => "$url",
+                    timeout    => 5,
+                    hash       => $hash,
+                    method     => "GET",
+                    header     => "User-Agent: FHEM\r\nAccept: application/json",
+                    user       => $username,
+                    pwd        => $password,
+                    callback   => \&TA_CMI_JSON_ParseHttpResponse
+                };
+
+    HttpUtils_NonblockingGet($param);
+}
+
+sub TA_CMI_JSON_ParseHttpResponse {
+  my ($param, $err, $data) = @_;
+  my $hash = $param->{hash};
+  my $name = $hash->{NAME};
+  my $return;
+
+  if($err ne "") {
+      Log3 $name, 0, "error while requesting ".$param->{url}." - $err";
+      readingsBeginUpdate($hash);
+      readingsBulkUpdate($hash, 'state', 'ERROR', 0);
+      readingsBulkUpdate($hash, 'error', $err, 0);
+      readingsEndUpdate($hash, 0);      
+  } elsif($data ne "") {
+    my $keyValues = json2nameValue($data);
+
+    my $canDevice = TA_CMI_JSON_extractDeviceName($keyValues->{Header_Device});
+    $hash->{CAN_DEVICE} = $canDevice;
+    $hash->{CMI_API_VERSION} = TA_CMI_JSON_extractVersion($keyValues->{Header_Version});
+    CommandDeleteReading(undef, "$name error");
+
+    readingsBeginUpdate($hash);
+    readingsBulkUpdateIfChanged($hash, 'state', $keyValues->{Status});
+    if ( $keyValues->{Status} eq 'OK' ) {
+      my $queryParams = $hash->{QUERYPARAM};
+
+      TA_CMI_JSON_extractReadings($hash, $keyValues, 'Inputs', 'Inputs') if ($queryParams =~ /I/);
+      TA_CMI_JSON_extractReadings($hash, $keyValues, 'Outputs', 'Outputs') if ($queryParams =~ /O/);
+
+      if ($queryParams =~ /D/) {
+        if ($canDevice eq 'UVR16x2') {
+          TA_CMI_JSON_extractReadings($hash, $keyValues, 'DL-Bus', 'DL-Bus');
+        } else {
+          Log3 $name, 0, "TA_CMI_JSON ($name) - Reading DL-Bus input is not supported on $canDevice";
+        }
+      }
+
+      if ($queryParams =~ /La/) {
+        if ($canDevice eq 'UVR16x2') {
+          TA_CMI_JSON_extractReadings($hash, $keyValues, 'LoggingAnalog', 'Logging_Analog');
+        } else {
+          Log3 $name, 0, "TA_CMI_JSON ($name) - Reading Logging Analog data is not supported on $canDevice";
+        }
+      }
+
+      if ($queryParams =~ /Ld/) {
+        if ($canDevice eq 'UVR16x2') {
+          TA_CMI_JSON_extractReadings($hash, $keyValues, 'LoggingDigital', 'Logging_Digital');
+        } else {
+          Log3 $name, 0, "TA_CMI_JSON ($name) - Reading Logging Digital data is not supported on $canDevice";
+        }
+      }
+    }
+    
+    readingsEndUpdate($hash, 1);
+
+#     Log3 $name, 3, "TA_CMI_JSON ($name) - Device: $keyValues->{Header_Device}";
+  }
+
+  my $functionName = "TA_CMI_JSON_GetStatus";
+  RemoveInternalTimer($hash, $functionName);
+  InternalTimer( gettimeofday() + $hash->{INTERVAL}, $functionName, $hash, 0 );
+
+  return undef;
+}
+
+sub TA_CMI_JSON_extractDeviceName {
+    my ($input) = @_;
+    return (defined($deviceNames{$input}) ? $deviceNames{$input} : 'unknown: ' . $input);
+}
+
+sub TA_CMI_JSON_extractVersion {
+    my ($input) = @_;
+    return (defined($versions{$input}) ? $versions{$input} : 'unknown: ' . $input);
+}
+
+sub TA_CMI_JSON_extractReadings {
+  my ( $hash, $keyValues, $id, $dataKey ) = @_;
+  my $name = $hash->{NAME};
+
+  my $readingNames = AttrVal($name, "readingNames$id", '');
+  Log3 $name, 5, 'readingNames'.$id.": $readingNames";
+  my @readingsArray = split(/ /, $readingNames); #1:T.Kollektor 5:T.Vorlauf
+
+  my $inclUnitReadings =  AttrVal( $name, "includeUnitReadings", 0 );
+  my $inclPrettyReadings = AttrVal( $name, "includePrettyReadings", 0 );
+
+  for my $i (0 .. (@readingsArray-1)) {
+    my ( $idx, $readingName ) = split(/\:/, $readingsArray[$i]);
+    $readingName = makeReadingName($readingName);
+
+    my $jsonKey = 'Data_'.$dataKey.'_'.$idx.'_Value_Value';
+    my $readingValue = $keyValues->{$jsonKey};
+    Log3 $name, 5, "readingName: $readingName, key: $jsonKey, value: $readingValue";
+    readingsBulkUpdateIfChanged($hash, $readingName, $readingValue);
+
+    $jsonKey = 'Data_'.$dataKey.'_'.$idx.'_Value_RAS';
+    my $readingRas = $keyValues->{$jsonKey};
+    if (defined($readingRas)) {
+      readingsBulkUpdateIfChanged($hash, $readingName . '_RAS', $readingRas);
+
+      if ($inclPrettyReadings) {
+        my $ras = (defined($rasStates{$readingRas}) ? $rasStates{$readingRas} : undef);
+        readingsBulkUpdateIfChanged($hash, $readingName . '_RAS_Pretty', $ras) if ($ras);
+      }
+    }
+
+    my $unit;
+    if ($inclUnitReadings || $inclPrettyReadings) {
+      $jsonKey = 'Data_'.$dataKey.'_'.$idx.'_Value_Unit';
+      my $readingUnit = $keyValues->{$jsonKey};
+      $unit = (defined($units{$readingUnit}) ? $units{$readingUnit} : 'unknown: ' . $readingUnit);
+      Log3 $name, 5, "readingName: $readingName . '_Unit', key: $jsonKey, value: $readingUnit, unit: $unit";
+
+      readingsBulkUpdateIfChanged($hash, $readingName . '_Unit', $unit) if ($inclUnitReadings);
+    }
+
+    if ($inclPrettyReadings) {
+      readingsBulkUpdateIfChanged($hash, $readingName . '_Pretty', $readingValue . ' ' . $unit);
+    }
+  }
+
+  return undef;
+}
+
+sub TA_CMI_JSON_Get {
+  my ( $hash, $name, $opt, $args ) = @_;
+
+  if ("update" eq $opt) {
+    TA_CMI_JSON_PerformHttpRequest($hash);
+    return undef;
+  }
+
+#  Log3 $name, 3, "ZoneMinder ($name) - Get done ...";
+  return "Unknown argument $opt, choose one of update";
+
+}
+
+# Eval-Rückgabewert für erfolgreiches
+# Laden des Moduls
+1;
+
+
+# Beginn der Commandref
+
+=pod
+=item [device]
+=item summary Reads values from the Technische Alternative CMI device
+=item summary_DE Werte vom CMI der Firma Technische Alternative auslesen.
+
+=begin html
+
+<a name="TA_CMI_JSON"></a>
+<h3>TA_CMI_JSON</h3>
+<a name="TA_CMI_JSONdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; TA_CMI_JSON  &lt;IP&gt; &lt;CAN-Node-Id&gt; &lt;Query-Params&gt;</code>
+    <br><br>
+    Defines a device that receives values from the CMI at the given IP for the CAN-device with the given CAN-Node-Id.<br/>
+    Query-Param defines, which values you want to read. Allowed values are I,O,D.
+    <br>
+    Example:
+    <ul>
+      <code>defmod cmi TA_CMI_JSON 192.168.4.250 1 I,O,D</code><br>
+    </ul>
+    <br>
+    It's mandatory to define which values should be mapped to readings.<br/>
+    Only mapped values will not be written to readings. (see <a href="#TA_CMI_JSONattr">Attributes</a> for details)
+  </ul>
+  <br><br>
+  
+    <a name="TA_CMI_JSONget"></a>
+  <b>Get</b>
+  <ul>
+    <li><code>update</code><br>Triggers querying of values from the CMI. Please note that the request rate is limited to one query per minute.
+    </li>
+  </ul>
+  
+  <br><br>
+  <a name="TA_CMI_JSONattr"></a>
+  <b>Attributes</b>
+  <br><br>
+  <ul>
+    <li><code>readingNamesDL-Bus {index:reading-name}</code><br>This maps received values from the DL-Bus to readings. eg <code>1:Flowrate_Solar 2:T.Solar_Backflow</code></li>
+    <li><code>readingNamesInputs {index:reading-name}</code><br>This maps received values from the Inputs to readings. eg <code>1:Flowrate_Solar 2:T.Solar_Backflow</code></li>
+    <li><code>readingNamesOutputs {index:reading-name}</code><br>This maps received values from the Outputs to readings. eg <code>1:Flowrate_Solar 2:T.Solar_Backflow</code></li>
+    <li><code>readingNamesLoggingAnalog {index:reading-name}</code><br>This maps received values from Analog Logging to readings. zB eg <code>1:Flowrate_Solar 2:T.Solar_Backflow</code></li>
+    <li><code>readingNamesLoggingDigital {index:reading-name}</code><br>This maps received values from Digital Logging to readings. zB eg <code>1:Flowrate_Solar 2:T.Solar_Backflow</code></li>
+    <li><code>includeUnitReadings [0:1]</code><br>Adds another reading per value, which just contains the according unit of that reading.</li>
+    <li><code>includePrettyReadings [0:1]</code><br>Adds another reading per value, which contains value plus unit of that reading.</li>
+    <li><code>interval</code><br>Query interval in seconds. Minimum query interval is 60 seconds.</li>
+    <li><code>username</code><br>Username for querying the JSON-API. Needs to be either admin or user privilege.</li>
+    <li><code>password</code><br>Password for querying the JSON-API.</li>
+    
+  </ul>
+  <br><br>
+  
+  <a name="TA_CMI_JSONreadings"></a>
+  <b>Readings</b>
+  <br><br>
+  Readings will appear according to the mappings defined in Attributes.
+  
+=end html
+
+=begin html_DE
+
+<a name="TA_CMI_JSON"></a>
+<h3>TA_CMI_JSON</h3>
+Weitere Informationen zu diesem Modul im <a href="https://wiki.fhem.de/wiki/UVR16x2">FHEM-Wiki</a>.
+<a name="TA_CMI_JSONdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; TA_CMI_JSON  &lt;IP&gt; &lt;CAN-Node-Id&gt; &lt;Query-Params&gt;</code>
+    <br><br>
+    Liest Werte vom CMI mit der angegebenen IP für das CAN-Gerät mit der angegebenen Node-Id.<br/>
+    Query-Param definiert, welche Werte ausgelesen werden sollen. Erlaubt sind I,O,D.
+    <br>
+    Beispiel:
+    <ul>
+      <code>defmod cmi TA_CMI_JSON 192.168.4.250 1 I,O,D</code><br>
+    </ul>
+    <br>
+    Daneben muss auch noch das mapping angegeben werden, welche Werte in welches Reading geschrieben werden sollen.<br/>
+    Nur gemappte Werte werden in Readings geschrieben. (siehe <a href="#TA_CMI_JSONattr">Attributes</a>)
+  </ul>
+  <br><br>
+  
+    <a name="TA_CMI_JSONget"></a>
+  <b>Get</b>
+  <ul>
+    <li><code>update</code><br>Hiermit kann sofort eine Abfrage der API ausgef&uuml;hrt werden. Das Limit von einer Anfrage pro Minute besteht trotzdem.
+    </li>
+  </ul>
+  
+  <br><br>
+  <a name="TA_CMI_JSONattr"></a>
+  <b>Attributes</b>
+  <br><br>
+  <ul>
+    <li><code>readingNamesDL-Bus {index:reading-name}</code><br>Hiermit werden erhaltene Werte vom DL-Bus einem Reading zugewiesen. zB <code>1:Durchfluss_Solar 2:T.Solar_RL</code></li>
+    <li><code>readingNamesInput {index:reading-name}</code><br>Hiermit werden erhaltene Werte der Eing&auml;nge einem Reading zugewiesen. zB <code>1:Durchfluss_Solar 2:T.Solar_RL</code></li>
+    <li><code>readingNamesDL-Bus {index:reading-name}</code><br>Hiermit werden erhaltene Werte der Ausg&auml;nge einem Reading zugewiesen. zB <code>1:Durchfluss_Solar 2:T.Solar_RL</code></li>
+    <li><code>readingNamesLoggingAnalog {index:reading-name}</code><br>Hiermit werden erhaltene Werte vom Analog Logging einem Reading zugewiesen. zB <code>1:Durchfluss_Solar 2:T.Solar_RL</code></li>
+    <li><code>readingNamesLoggingDigital {index:reading-name}</code><br>Hiermit werden erhaltene Werte vom Digital Logging einem Reading zugewiesen. zB <code>1:Durchfluss_Solar 2:T.Solar_RL</code></li>
+    <li><code>includeUnitReadings [0:1]</code><br>Definiert, ob zu jedem Reading ein zusätzliches Reading _Name geschrieben werden soll, welches die Einheit enth&auml;lt.</li>
+    <li><code>includePrettyReadings [0:1]</code><br>Definiert, ob zu jedem Reading zusätzlich ein Reading, welches Wert und Einheit enth&auml;lt, geschrieben werden soll.</li>
+    <li><code>interval</code><br>Abfrage-Intervall in Sekunden. Muss mindestens 60 sein.</li>
+    <li><code>username</code><br>Username zur Abfrage der JSON-API. Muss die Berechtigungsstufe admin oder user haben.</li>
+    <li><code>password</code><br>Passwort zur Abfrage der JSON-API.</li>
+    
+  </ul>
+  <br><br>
+  
+  <a name="TA_CMI_JSONreadings"></a>
+  <b>Readings</b>
+  <br><br>
+  Readings werden entsprechend der Definition in den Attributen angelegt.
+=end html
+
+# Ende der Commandref
+=cut

File diff suppressed because it is too large
+ 3355 - 0
fhem/core/FHEM/73_AutoShuttersControl.pm


File diff suppressed because it is too large
+ 1695 - 0
fhem/core/FHEM/98_livetracking.pm


+ 44 - 0
fhem/core/FHEM/99_myUtils.pm

@@ -0,0 +1,44 @@
+##############################################
+# $Id: myUtilsTemplate.pm 7570 2015-01-14 18:31:44Z rudolfkoenig $
+#
+# Save this file as 99_myUtils.pm, and create your own functions in the new
+# file. They are then available in every Perl expression.
+
+package main;
+
+use strict;
+use warnings;
+use POSIX;
+
+sub
+myUtils_Initialize($$)
+{
+  my ($hash) = @_;
+}
+
+# Enter you functions below _this_ line.
+sub checkAllFritzMACpresent($) {
+# Benötigt: nur die zu suchende MAC ($MAC),
+# Es werden alle Instanzen vom Type FRITZBOX abgefragt
+#
+# Rückgabe: 1 = Gerät gefunden
+# 0 = Gerät nicht gefunden
+my ($MAC) = @_;
+# Wird in keiner Instanz die MAC Adresse gefunden bleibt der Status 0
+my $Status = 0;
+$MAC =~ tr/:/_/;
+$MAC = "mac_".uc($MAC);
+my @FBS = devspec2array("TYPE=FRITZBOX");
+foreach( @FBS ) {
+my $StatusFritz = ReadingsVal($_, $MAC, "weg");
+if ($StatusFritz eq "weg") {
+} elsif ($StatusFritz eq "inactive") {
+} else {
+# Reading existiert, Rückgabewert ist nicht "inactive", also ist das Gerät am Netzwerk angemeldet.
+$Status = 1;
+}
+}
+return $Status
+}
+
+1;

+ 767 - 0
fhem/core/FHEM/ABFALL_setUpdate_back.pm

@@ -0,0 +1,767 @@
+# $Id: ABFALL_setUpdate.pm 11021 2017-09-13 00:32:22Z uniqueck $
+# *** WARNING: DO NOT MODIFY *** This is a generated Perl source code!
+#
+# Generated by LF-ET 2.1.5 (170905a), http://www.lohrfink.de/lfet
+# From decision table
+# "/data/github/fhem/fhem-abfall/lfet/ABFALL_setUpdate.lfet"
+# 12.09.2017 22:09
+#
+# Changes to this code resulting from refactorings can be synchronised
+# with LF-ET using the function "Scrapbook Import".
+#
+# Prolog Decision Table ---->
+package main;
+
+use strict;
+use warnings;
+use POSIX;
+use Time::Local;
+use Time::Piece;
+use ABFALL_getEvents;
+
+sub ABFALL_setUpdate_Initialize($$)
+{
+  my ($hash) = @_;
+}
+
+sub ABFALL_setUpdate($) {
+
+    my ($hash) = @_;
+    my $name = $hash->{NAME};
+    Log3 $name, 4, "ABFALL_setUpdate($name) - Start";
+
+    # step counter
+    my $step = 1;
+
+    # array counter
+    my $eventIndex = 0;
+
+    my $lastNow = ReadingsVal($name, "now", "");
+    Log3 $name, 5, "ABFALL_setUpdate($name) - reading lastNow $lastNow";
+
+    # array for events
+    my @events = ();
+
+    my $actualEvent = ();
+
+    # readings
+    my $nowAbfall_tage = -1;
+    my $nowAbfall_text = "";
+    my $nowAbfall_location = "";
+    my $nowAbfall_description = "";
+    my $nowAbfall_datum;
+    my $nowAbfall_weekday;
+    my $now_readingTermin = "";
+
+    my $nextAbfall_tage = -1;
+    my $nextAbfall_text = "";
+    my $nextAbfall_location = "";
+    my $nextAbfall_description = "";
+    my $nextAbfall_datum;
+    my $nextAbfall_weekday;
+    my $next_readingTermin = "";
+
+    # attribute values
+    my $delimiter_text_reading = " " . AttrVal($name,"delimiter_text_reading","und") . " ";
+    my $delimiter_reading = AttrVal($name,"delimiter_reading","|");
+
+    while ($step != -1) {
+
+    # Prolog Decision Table <----
+
+    # Condition B01/01: step / 1 / cleanup step
+    if (
+    $step eq 1
+    )
+    {
+
+        # Condition B02: counting pickups
+        if (
+        AttrVal($name, "enable_counting_pickups", "0")
+        )
+        {
+            # Rule R01 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 1, 21";
+            # Trace <----
+
+            # Action A01: clean readings
+            Log3 $name, 5, "ABFALL_GetUpdate ($name) - delete readings";
+            fhem("deletereading $name next", 1);
+            fhem("deletereading $name now", 1);
+            fhem("deletereading $name .*_tage", 1);
+            fhem("deletereading $name .*_days", 1);
+            fhem("deletereading $name .*_wochentag", 1);
+            fhem("deletereading $name .*_weekday", 1);
+            fhem("deletereading $name .*_text", 1);
+            fhem("deletereading $name .*_datum", 1);
+            fhem("deletereading $name .*_date", 1);
+            fhem("deletereading $name .*_location", 1);
+            fhem("deletereading $name .*_description", 1);
+            fhem("deletereading $name .*_uid", 1);
+            fhem("deletereading $name state", 1);
+
+            # Action A03: getEvents
+            @events = ABFALL_getEvents($hash);
+
+            # Action A17: readingsBeginUpdate
+            readingsBeginUpdate($hash);
+
+            # Action A19/02: step / 2 / event step
+            $step = 2;
+
+            # Rule R01 <----
+        }
+        else
+        {
+            # Rule R02 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 2, 21";
+            # Trace <----
+
+            # Action A01: clean readings
+            Log3 $name, 5, "ABFALL_GetUpdate ($name) - delete readings";
+            fhem("deletereading $name next", 1);
+            fhem("deletereading $name now", 1);
+            fhem("deletereading $name .*_tage", 1);
+            fhem("deletereading $name .*_days", 1);
+            fhem("deletereading $name .*_wochentag", 1);
+            fhem("deletereading $name .*_weekday", 1);
+            fhem("deletereading $name .*_text", 1);
+            fhem("deletereading $name .*_datum", 1);
+            fhem("deletereading $name .*_date", 1);
+            fhem("deletereading $name .*_location", 1);
+            fhem("deletereading $name .*_description", 1);
+            fhem("deletereading $name .*_uid", 1);
+            fhem("deletereading $name state", 1);
+
+            # Action A02: clean pickup readings
+            fhem("deletereading $name .*_pickups", 1);
+            fhem("deletereading $name .*_pickups_used", 1);
+            fhem("deletereading $name .*_abholungen", 1);
+            fhem("deletereading $name .*_abholungen_genutzt", 1);
+
+            # Action A03: getEvents
+            @events = ABFALL_getEvents($hash);
+
+            # Action A17: readingsBeginUpdate
+            readingsBeginUpdate($hash);
+
+            # Action A19/02: step / 2 / event step
+            $step = 2;
+
+            # Rule R02 <----
+        }
+
+    # Condition B01/02: step / 2 / next event step
+    }
+    elsif (
+    $step eq 2
+    )
+    {
+
+        # Condition B05: has events
+        if (
+        scalar(@events) > 0
+        )
+        {
+
+            # Condition B06: has more events
+            if (
+            $eventIndex < scalar(@events)
+            )
+            {
+                # Rule R03 ---->
+
+                # Trace ---->
+                Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 3, 21";
+                # Trace <----
+
+                # Action A04: next event
+                $actualEvent = $events[$eventIndex];
+                $eventIndex++;
+
+                # Action A19/03: step / 3 / event step
+                $step = 3
+
+                # Rule R03 <----
+            }
+            else
+            {
+                # Rule R04 ---->
+
+                # Trace ---->
+                Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 4, 21";
+                # Trace <----
+
+                # Action A18: readingsEndUpdate
+                readingsEndUpdate($hash,1); #end update
+
+                # Action A19/01: step / E / end step
+                $step = -1;
+
+                # Rule R04 <----
+            }
+        }
+        else
+        {
+            # Rule R05 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 5, 21";
+            # Trace <----
+
+            # Action A13: set state no pickups
+            readingsBulkUpdate($hash, "state", "Keine Abholungen");
+
+            # Action A18: readingsEndUpdate
+            readingsEndUpdate($hash,1); #end update
+
+            # Action A19/01: step / E / end step
+            $step = -1;
+
+            # Rule R05 <----
+        }
+
+    # Condition B01/03: step / 3 / event step
+    }
+    elsif (
+    $step eq 3
+    )
+    {
+
+        # Condition B07: event readingName is empty
+        if (
+        $actualEvent->{readingName} eq ""
+        )
+        {
+            # Rule R06 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 6, 21";
+            # Trace <----
+
+            # Action A16: warn empty readingname
+            Log3 $name, 2, "ABFALL_setUpdate($name) - readingName is empty for uid($actualEvent->{uid})";
+
+            # Action A19/02: step / 2 / event step
+            $step = 2;
+
+            # Rule R06 <----
+        }
+        else
+        {
+
+            # Condition B08: event days equals 0
+            if (
+            $actualEvent->{days} eq 0
+            )
+            {
+                # Rule R07 ---->
+
+                # Trace ---->
+                Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 7, 21";
+                # Trace <----
+
+                # Action A05: append as now
+                if($nowAbfall_text eq "") {
+                    $nowAbfall_text = $actualEvent->{summary};
+                } else {
+                    $nowAbfall_text .= $delimiter_text_reading . $actualEvent->{summary};
+                }
+
+                if($nowAbfall_location eq "") {
+                    $nowAbfall_location = $actualEvent->{location};
+                } elsif ($nowAbfall_location ne $actualEvent->{location}) {
+                    # TODO change check to regex expression contains
+                    $nowAbfall_location .= $delimiter_text_reading . $actualEvent->{location};
+                }
+                # check if description reading is the same
+                if($nowAbfall_description eq "") {
+                    $nowAbfall_description = $actualEvent->{description};
+                } elsif ($nowAbfall_description ne $actualEvent->{description}) {
+                    # TODO change check to regex expression contains
+                    $nowAbfall_description .= $delimiter_text_reading . $actualEvent->{description};
+                }
+                $nowAbfall_tage = $actualEvent->{days};
+                $nowAbfall_datum = $actualEvent->{dateFormatted};
+                $nowAbfall_weekday = $actualEvent->{weekday};
+                if ($now_readingTermin eq "") {
+                    $now_readingTermin = $actualEvent->{readingName};
+                } else {
+                    $now_readingTermin .= $delimiter_reading . $actualEvent->{readingName};
+                }
+
+                # Action A08: set as reading
+                my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+                my $readingTermin = $actualEvent->{readingName};
+
+                readingsBulkUpdate($hash, $readingTermin ."_days", $actualEvent->{days});
+                readingsBulkUpdate($hash, $readingTermin ."_text", $actualEvent->{summary});
+                readingsBulkUpdate($hash, $readingTermin ."_date", $actualEvent->{dateFormatted});
+
+                readingsBulkUpdate($hash, $readingTermin ."_weekday", $actualEvent->{weekday});
+                readingsBulkUpdate($hash, $readingTermin ."_location", $actualEvent->{location});
+                readingsBulkUpdate($hash, $readingTermin ."_description", $actualEvent->{description});
+                readingsBulkUpdate($hash, $readingTermin ."_uid", $actualEvent->{uid});
+
+                readingsBulkUpdate($hash, $readingTermin ."_tage", $actualEvent->{days}) if ($enable_old_readingnames);
+                readingsBulkUpdate($hash, $readingTermin ."_wochentag", $actualEvent->{weekday}) if ($enable_old_readingnames);
+                readingsBulkUpdate($hash, $readingTermin ."_datum", $actualEvent->{dateFormatted}) if ($enable_old_readingnames);
+
+                # Action A19/04: step / 4 / counting pickup step
+                $step = 4
+
+                # Rule R07 <----
+            }
+            else
+            {
+
+                # Condition B09: next days initialized
+                if (
+                $nextAbfall_tage > -1
+                )
+                {
+
+                    # Condition B10: event days < next days
+                    if (
+                    $actualEvent->{days} < $nextAbfall_tage
+                    )
+                    {
+                        # Rule R08 ---->
+
+                        # Trace ---->
+                        Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 8, 21";
+                        # Trace <----
+
+                        # Action A07: set next
+                        $nextAbfall_text = $actualEvent->{summary};
+                        $nextAbfall_location = $actualEvent->{location};
+                        $nextAbfall_description = $actualEvent->{description};
+                        $nextAbfall_tage = $actualEvent->{days};
+                        $nextAbfall_datum = $actualEvent->{dateFormatted};
+                        $nextAbfall_weekday = $actualEvent->{weekday};
+                        $next_readingTermin = $actualEvent->{readingName};
+
+                        # Action A08: set as reading
+                        my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+                        my $readingTermin = $actualEvent->{readingName};
+
+                        readingsBulkUpdate($hash, $readingTermin ."_days", $actualEvent->{days});
+                        readingsBulkUpdate($hash, $readingTermin ."_text", $actualEvent->{summary});
+                        readingsBulkUpdate($hash, $readingTermin ."_date", $actualEvent->{dateFormatted});
+
+                        readingsBulkUpdate($hash, $readingTermin ."_weekday", $actualEvent->{weekday});
+                        readingsBulkUpdate($hash, $readingTermin ."_location", $actualEvent->{location});
+                        readingsBulkUpdate($hash, $readingTermin ."_description", $actualEvent->{description});
+                        readingsBulkUpdate($hash, $readingTermin ."_uid", $actualEvent->{uid});
+
+                        readingsBulkUpdate($hash, $readingTermin ."_tage", $actualEvent->{days}) if ($enable_old_readingnames);
+                        readingsBulkUpdate($hash, $readingTermin ."_wochentag", $actualEvent->{weekday}) if ($enable_old_readingnames);
+                        readingsBulkUpdate($hash, $readingTermin ."_datum", $actualEvent->{dateFormatted}) if ($enable_old_readingnames);
+
+                        # Action A19/04: step / 4 / counting pickup step
+                        $step = 4
+
+                        # Rule R08 <----
+                    }
+                    else
+                    {
+
+                        # Condition B11: event days = next days
+                        if (
+                        $actualEvent->{days} == $nextAbfall_tage
+                        )
+                        {
+                            # Rule R09 ---->
+
+                            # Trace ---->
+                            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 9, 21";
+                            # Trace <----
+
+                            # Action A06: append next
+                            if ($nextAbfall_text eq "") {
+                                $nextAbfall_text = $actualEvent->{summary};
+                            } else {
+                                $nextAbfall_text .= $delimiter_text_reading . $actualEvent->{summary};
+                            }
+                            # check if location reading is the same
+                            if ($nextAbfall_location eq "") {
+                                $nextAbfall_location = $actualEvent->{location};
+                            } else {
+                                # TODO check if nextAbfall_location contains $actualEvent->{location}
+                                $nextAbfall_location .= $delimiter_text_reading . $actualEvent->{location};
+                            }
+                            # check if description reading is the same
+                            if ($nextAbfall_description eq "") {
+                                $nextAbfall_description = $actualEvent->{description};
+                            } else {
+                                # TODO check if nextAbfall_location contains $actualEvent->{location}
+                                $nextAbfall_description .= $delimiter_text_reading . $actualEvent->{description};
+                            }
+                            $nextAbfall_tage = $actualEvent->{days};
+                            $nextAbfall_datum = $actualEvent->{dateFormatted};
+                            $nextAbfall_weekday = $actualEvent->{weekday};
+                            if ($next_readingTermin eq "") {
+                                $next_readingTermin = $actualEvent->{readingName};
+                            } else {
+                                $next_readingTermin .= $delimiter_reading . $actualEvent->{readingName};
+                            }
+
+                            # Action A08: set as reading
+                            my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+                            my $readingTermin = $actualEvent->{readingName};
+
+                            readingsBulkUpdate($hash, $readingTermin ."_days", $actualEvent->{days});
+                            readingsBulkUpdate($hash, $readingTermin ."_text", $actualEvent->{summary});
+                            readingsBulkUpdate($hash, $readingTermin ."_date", $actualEvent->{dateFormatted});
+
+                            readingsBulkUpdate($hash, $readingTermin ."_weekday", $actualEvent->{weekday});
+                            readingsBulkUpdate($hash, $readingTermin ."_location", $actualEvent->{location});
+                            readingsBulkUpdate($hash, $readingTermin ."_description", $actualEvent->{description});
+                            readingsBulkUpdate($hash, $readingTermin ."_uid", $actualEvent->{uid});
+
+                            readingsBulkUpdate($hash, $readingTermin ."_tage", $actualEvent->{days}) if ($enable_old_readingnames);
+                            readingsBulkUpdate($hash, $readingTermin ."_wochentag", $actualEvent->{weekday}) if ($enable_old_readingnames);
+                            readingsBulkUpdate($hash, $readingTermin ."_datum", $actualEvent->{dateFormatted}) if ($enable_old_readingnames);
+
+                            # Action A19/04: step / 4 / counting pickup step
+                            $step = 4
+
+                            # Rule R09 <----
+                        }
+                        else
+                        {
+                            # Rule R10 ---->
+
+                            # Trace ---->
+                            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 10, 21";
+                            # Trace <----
+
+                            # Action A08: set as reading
+                            my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+                            my $readingTermin = $actualEvent->{readingName};
+
+                            readingsBulkUpdate($hash, $readingTermin ."_days", $actualEvent->{days});
+                            readingsBulkUpdate($hash, $readingTermin ."_text", $actualEvent->{summary});
+                            readingsBulkUpdate($hash, $readingTermin ."_date", $actualEvent->{dateFormatted});
+
+                            readingsBulkUpdate($hash, $readingTermin ."_weekday", $actualEvent->{weekday});
+                            readingsBulkUpdate($hash, $readingTermin ."_location", $actualEvent->{location});
+                            readingsBulkUpdate($hash, $readingTermin ."_description", $actualEvent->{description});
+                            readingsBulkUpdate($hash, $readingTermin ."_uid", $actualEvent->{uid});
+
+                            readingsBulkUpdate($hash, $readingTermin ."_tage", $actualEvent->{days}) if ($enable_old_readingnames);
+                            readingsBulkUpdate($hash, $readingTermin ."_wochentag", $actualEvent->{weekday}) if ($enable_old_readingnames);
+                            readingsBulkUpdate($hash, $readingTermin ."_datum", $actualEvent->{dateFormatted}) if ($enable_old_readingnames);
+
+                            # Action A19/04: step / 4 / counting pickup step
+                            $step = 4
+
+                            # Rule R10 <----
+                        }
+                    }
+                }
+                else
+                {
+                    # Rule R11 ---->
+
+                    # Trace ---->
+                    Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 11, 21";
+                    # Trace <----
+
+                    # Action A07: set next
+                    $nextAbfall_text = $actualEvent->{summary};
+                    $nextAbfall_location = $actualEvent->{location};
+                    $nextAbfall_description = $actualEvent->{description};
+                    $nextAbfall_tage = $actualEvent->{days};
+                    $nextAbfall_datum = $actualEvent->{dateFormatted};
+                    $nextAbfall_weekday = $actualEvent->{weekday};
+                    $next_readingTermin = $actualEvent->{readingName};
+
+                    # Action A08: set as reading
+                    my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+                    my $readingTermin = $actualEvent->{readingName};
+
+                    readingsBulkUpdate($hash, $readingTermin ."_days", $actualEvent->{days});
+                    readingsBulkUpdate($hash, $readingTermin ."_text", $actualEvent->{summary});
+                    readingsBulkUpdate($hash, $readingTermin ."_date", $actualEvent->{dateFormatted});
+
+                    readingsBulkUpdate($hash, $readingTermin ."_weekday", $actualEvent->{weekday});
+                    readingsBulkUpdate($hash, $readingTermin ."_location", $actualEvent->{location});
+                    readingsBulkUpdate($hash, $readingTermin ."_description", $actualEvent->{description});
+                    readingsBulkUpdate($hash, $readingTermin ."_uid", $actualEvent->{uid});
+
+                    readingsBulkUpdate($hash, $readingTermin ."_tage", $actualEvent->{days}) if ($enable_old_readingnames);
+                    readingsBulkUpdate($hash, $readingTermin ."_wochentag", $actualEvent->{weekday}) if ($enable_old_readingnames);
+                    readingsBulkUpdate($hash, $readingTermin ."_datum", $actualEvent->{dateFormatted}) if ($enable_old_readingnames);
+
+                    # Action A19/04: step / 4 / counting pickup step
+                    $step = 4
+
+                    # Rule R11 <----
+                }
+            }
+        }
+
+    # Condition B01/04: step / 4 / counting pickup step
+    }
+    elsif (
+    $step eq 4
+    )
+    {
+
+        # Condition B02: counting pickups
+        if (
+        AttrVal($name, "enable_counting_pickups", "0")
+        )
+        {
+
+            # Condition B03: reading pickups initialized
+            if (
+            ReadingsVal($name, $actualEvent->{readingName} . "_pickups", "-1") > -1
+            )
+            {
+
+                # Condition B04: reading pickups used initialized
+                if (
+                ReadingsVal($name, $actualEvent->{readingName} . "_pickups_used", "-1") > -1
+                )
+                {
+                    # Rule R12 ---->
+
+                    # Trace ---->
+                    Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 12, 21";
+                    # Trace <----
+
+                    # Action A19/05: step / 5 / now step
+                    $step = 5
+
+                    # Rule R12 <----
+                }
+                else
+                {
+                    # Rule R13 ---->
+
+                    # Trace ---->
+                    Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 13, 21";
+                    # Trace <----
+
+                    # Action A15: initialize pickups used
+                    readingsBulkUpdate($hash, $actualEvent->{readingName} ."_pickups_used", "0");
+
+                    # Action A19/05: step / 5 / now step
+                    $step = 5
+
+                    # Rule R13 <----
+                }
+            }
+            else
+            {
+
+                # Condition B04: reading pickups used initialized
+                if (
+                ReadingsVal($name, $actualEvent->{readingName} . "_pickups_used", "-1") > -1
+                )
+                {
+                    # Rule R14 ---->
+
+                    # Trace ---->
+                    Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 14, 21";
+                    # Trace <----
+
+                    # Action A14: initialize pickups
+                    readingsBulkUpdate($hash, $actualEvent->{readingName} ."_pickups", "0");
+
+                    # Action A19/05: step / 5 / now step
+                    $step = 5
+
+                    # Rule R14 <----
+                }
+                else
+                {
+                    # Rule R15 ---->
+
+                    # Trace ---->
+                    Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 15, 21";
+                    # Trace <----
+
+                    # Action A14: initialize pickups
+                    readingsBulkUpdate($hash, $actualEvent->{readingName} ."_pickups", "0");
+
+                    # Action A15: initialize pickups used
+                    readingsBulkUpdate($hash, $actualEvent->{readingName} ."_pickups_used", "0");
+
+                    # Action A19/05: step / 5 / now step
+                    $step = 5
+
+                    # Rule R15 <----
+                }
+            }
+        }
+        else
+        {
+            # Rule R16 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 16, 21";
+            # Trace <----
+
+            # Action A19/05: step / 5 / now step
+            $step = 5
+
+            # Rule R16 <----
+        }
+
+    # Condition B01/05: step / 5 / now step
+    }
+    elsif (
+    $step eq 5
+    )
+    {
+
+        # Condition B12: now days = 0
+        if (
+        $nowAbfall_tage == 0
+        )
+        {
+
+            # Condition B13: last pickup <> actual pickup
+            if (
+            $lastNow ne $now_readingTermin
+            )
+            {
+                # Rule R17 ---->
+
+                # Trace ---->
+                Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 17, 21";
+                # Trace <----
+
+                # Action A09: set now reading
+                readingsBulkUpdate($hash, "now", $now_readingTermin);
+                readingsBulkUpdate($hash, "now_text", $nowAbfall_text);
+                readingsBulkUpdate($hash, "now_date", $nowAbfall_datum);
+                readingsBulkUpdate($hash, "now_weekday", $nowAbfall_weekday);
+                readingsBulkUpdate($hash, "now_location", $nowAbfall_location);
+                readingsBulkUpdate($hash, "now_description", $nowAbfall_description);
+
+                my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+                readingsBulkUpdate($hash, "now_datum", $nowAbfall_datum) if ($enable_old_readingnames);
+                readingsBulkUpdate($hash, "now_wochentag", $nowAbfall_weekday) if ($enable_old_readingnames);
+
+                # Action A10: increment last pickup
+                Log3 $name, 4, "ABFALL_setUpdate($name) - inc count for pickups for $now_readingTermin";
+                my $now_readingTermin_count =  ReadingsVal($hash, $now_readingTermin . "_pickups", "0");
+                $now_readingTermin_count++;
+                readingsBulkUpdate($hash, $now_readingTermin . "_pickups", $now_readingTermin_count);
+
+                # Action A19/06: step / 6 / next step
+                $step = 6;
+
+                # Rule R17 <----
+            }
+            else
+            {
+                # Rule R18 ---->
+
+                # Trace ---->
+                Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 18, 21";
+                # Trace <----
+
+                # Action A09: set now reading
+                readingsBulkUpdate($hash, "now", $now_readingTermin);
+                readingsBulkUpdate($hash, "now_text", $nowAbfall_text);
+                readingsBulkUpdate($hash, "now_date", $nowAbfall_datum);
+                readingsBulkUpdate($hash, "now_weekday", $nowAbfall_weekday);
+                readingsBulkUpdate($hash, "now_location", $nowAbfall_location);
+                readingsBulkUpdate($hash, "now_description", $nowAbfall_description);
+
+                my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+                readingsBulkUpdate($hash, "now_datum", $nowAbfall_datum) if ($enable_old_readingnames);
+                readingsBulkUpdate($hash, "now_wochentag", $nowAbfall_weekday) if ($enable_old_readingnames);
+
+                # Action A19/06: step / 6 / next step
+                $step = 6;
+
+                # Rule R18 <----
+            }
+        }
+        else
+        {
+            # Rule R19 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 19, 21";
+            # Trace <----
+
+            # Action A19/06: step / 6 / next step
+            $step = 6;
+
+            # Rule R19 <----
+        }
+    }
+    else
+    {
+
+        # Condition B14: next days > 0
+        if (
+        $nextAbfall_tage > 0
+        )
+        {
+            # Rule R20 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 20, 21";
+            # Trace <----
+
+            # Action A11: set next reading
+            readingsBulkUpdate($hash, "next", $next_readingTermin . "_" . $nextAbfall_tage);
+            readingsBulkUpdate($hash, "next_days", $nextAbfall_tage);
+            readingsBulkUpdate($hash, "next_text", $nextAbfall_text);
+            readingsBulkUpdate($hash, "next_date", $nextAbfall_datum);
+            readingsBulkUpdate($hash, "next_weekday", $nextAbfall_weekday);
+            readingsBulkUpdate($hash, "next_location", $nextAbfall_location);
+            readingsBulkUpdate($hash, "next_description", $nextAbfall_description);
+
+            my $enable_old_readingnames = AttrVal($name, "enable_old_readingnames", "0");
+            readingsBulkUpdate($hash, "next_tage", $nextAbfall_tage) if ($enable_old_readingnames);
+            readingsBulkUpdate($hash, "next_datum", $nextAbfall_datum) if ($enable_old_readingnames);
+            readingsBulkUpdate($hash, "next_wochentag", $nextAbfall_weekday) if ($enable_old_readingnames);
+
+            # Action A12: set state next days
+            readingsBulkUpdate($hash, "state", $nextAbfall_tage);
+
+            # Action A19/02: step / 2 / event step
+            $step = 2;
+
+            # Rule R20 <----
+        }
+        else
+        {
+            # Rule R21 ---->
+
+            # Trace ---->
+            Log3 $name, 5, "ABFALL_setUpdate($name) - ABFALL_setUpdate, 20170912.220919, 21, 21";
+            # Trace <----
+
+            # Action A13: set state no pickups
+            readingsBulkUpdate($hash, "state", "Keine Abholungen");
+
+            # Action A19/02: step / 2 / event step
+            $step = 2;
+
+            # Rule R21 <----
+        }
+    }
+
+    # Epilog Decision Table ---->
+} # end while
+}
+
+1;
+# Epilog Decision Table <----
+
+# End of generated Perl source code
+# Generated by LF-ET 2.1.5 (170905a), http://www.lohrfink.de/lfet

+ 421 - 0
fhem/core/FHEM/TradfriIo.pm

@@ -0,0 +1,421 @@
+##############################################
+# $Id$
+package main;
+
+sub DevIo_CloseDev($@);
+sub DevIo_Disconnected($);
+sub DevIo_OpenDev($$$;$);
+sub DevIo_SimpleRead($);
+sub DevIo_SimpleWrite($$$;$);
+
+sub
+DevIo_setStates($$)
+{
+  my ($hash, $val) = @_;
+  $hash->{STATE} = $val;
+  setReadingsVal($hash, "state", $val, TimeNow());
+}
+
+########################
+# Try to read once from the device.
+# "private" function
+sub
+DevIo_DoSimpleRead($)
+{
+  my ($hash) = @_;
+  my ($buf, $res);
+
+  if($hash->{TCPDev}) {
+    $res = sysread($hash->{TCPDev}, $buf, 4096);
+    $buf = "" if(!defined($res));
+  }
+  return $buf;
+}
+
+########################
+# This is the function to read data, to be called in ReadFn.
+# If there is no data, sets the device to disconnected, which results in
+# polling via ReadyFn, trying to open it.
+sub
+DevIo_SimpleRead($)
+{
+  my ($hash) = @_;
+  my $buf = DevIo_DoSimpleRead($hash);
+  ###########
+  # Lets' try again: Some drivers return len(0) on the first read...
+  if(defined($buf) && length($buf) == 0) {
+    $buf = DevIo_SimpleReadWithTimeout($hash, 0.01); # Forum #57806
+  }
+  if(!defined($buf) || length($buf) == 0) {
+    DevIo_Disconnected($hash);
+    return undef;
+  }
+  return $buf;
+}
+
+########################
+# wait at most timeout seconds until the file handle gets ready
+# for reading; returns undef on timeout
+# NOTE1: FHEM can be blocked for $timeout seconds, DO NOT USE IT!
+# NOTE2: This works on Windows only for TCP connections
+sub
+DevIo_SimpleReadWithTimeout($$)
+{
+  my ($hash, $timeout) = @_;
+
+  my $rin = "";
+  vec($rin, $hash->{FD}, 1) = 1;
+  my $nfound = select($rin, undef, undef, $timeout);
+  return DevIo_DoSimpleRead($hash) if($nfound> 0);
+  return undef;
+}
+
+########################
+# Function to write data
+sub
+DevIo_SimpleWrite($$$;$)
+{
+  my ($hash, $msg, $type, $addnl) = @_; # Type: 0:binary, 1:hex, 2:ASCII
+  return if(!$hash);
+
+  my $name = $hash->{NAME};
+  Log3 ($name, 5, $type ? "SW: $msg" : "SW: ".unpack("H*",$msg));
+
+  $msg = pack('H*', $msg) if($type && $type == 1);
+  $msg .= "\n" if($addnl);
+  if($hash->{TCPDev}) {
+    syswrite($hash->{TCPDev}, $msg);
+  }
+  select(undef, undef, undef, 0.001);
+}
+
+########################
+# Open a device for reading/writing data.
+# Possible values for $hash->{DeviceName}:
+# - device@baud[78][NEO][012] => open device, set serial-line parameters
+# - hostname:port => TCP/IP client connection
+# - device@directio => open device without additional "magic"
+# - UNIX:(SEQPACKET|STREAM):filename => Open filename as a UNIX socket
+# - FHEM:DEVIO:IoDev[:IoPort] => Cascade I/O over another FHEM Device
+#
+# callback is only meaningful for TCP/IP (in which case a nonblocking connect
+# is executed) every cases. It will be called with $hash and a (potential)
+# error message. If $hash->{SSL} is set, SSL encryption is activated.
+sub
+DevIo_OpenDev($$$;$)
+{
+  my ($hash, $reopen, $initfn, $callback) = @_;
+  my $dev = $hash->{DeviceName};
+  my $name = $hash->{NAME};
+  my $po;
+  my $baudrate;
+  ($dev, $baudrate) = split("@", $dev);
+  my ($databits, $parity, $stopbits) = (8, 'none', 1);
+  my $nextOpenDelay = ($hash->{nextOpenDelay} ? $hash->{nextOpenDelay} : 60);
+
+  # Call the callback if specified, simply return in other cases
+  my $doCb = sub ($) {
+    my ($r) = @_;
+    Log3 $name, 1, "$name: Can't connect to $dev: $r" if(!$reopen && $r);
+    $callback->($hash,$r) if($callback);
+    return $r;
+  };
+
+  # Call initFn
+  # if fails: disconnect, schedule the next polltime for reopen
+  # if ok: log message, trigger CONNECTED on reopen
+  my $doTailWork = sub {
+    DevIo_setStates($hash, "opened");
+
+    my $ret;
+    if($initfn) {
+      my $hadFD = defined($hash->{FD});
+      $ret = &$initfn($hash);
+      if($ret) {
+        if($hadFD && !defined($hash->{FD})) { # Forum #54732 / ser2net
+          DevIo_Disconnected($hash);
+          $hash->{NEXT_OPEN} = time() + $nextOpenDelay;
+
+        } else {
+          DevIo_CloseDev($hash);
+          Log3 $name, 1, "Cannot init $dev, ignoring it ($name)";
+        }
+      }
+    }
+
+    if(!$ret) {
+      my $l = $hash->{devioLoglevel}; # Forum #61970
+      if($reopen) {
+        Log3 $name, ($l ? $l:1), "$dev reappeared ($name)";
+      } else {
+        Log3 $name, ($l ? $l:3), "$name device opened" if(!$hash->{DevioText});
+      }
+    }
+
+    DoTrigger($name, "CONNECTED") if($reopen && !$ret);
+    return undef;
+  };
+  
+  if($baudrate =~ m/(\d+)(,([78])(,([NEO])(,([012]))?)?)?/) {
+    $baudrate = $1 if(defined($1));
+    $databits = $3 if(defined($3));
+    $parity = 'odd'  if(defined($5) && $5 eq 'O');
+    $parity = 'even' if(defined($5) && $5 eq 'E');
+    $stopbits = $7 if(defined($7));
+  }
+
+  if($hash->{DevIoJustClosed}) {
+    delete $hash->{DevIoJustClosed};
+    return &$doCb(undef);
+  }
+
+  $hash->{PARTIAL} = "";
+  Log3 $name, 3, ($hash->{DevioText} ? $hash->{DevioText} : "Opening").
+       " $name device $dev" if(!$reopen);
+
+  if($dev =~ m/^UNIX:(SEQPACKET|STREAM):(.*)$/) { # FBAHA
+    my ($type, $fname) = ($1, $2);
+    my $conn;
+    eval {
+      require IO::Socket::UNIX;
+      $conn = IO::Socket::UNIX->new(
+        Type=>($type eq "STREAM" ? SOCK_STREAM:SOCK_SEQPACKET), Peer=>$fname);
+    };
+    if($@) {
+      Log3 $name, 1, $@;
+      return &$doCb($@);
+    }
+
+    if(!$conn) {
+      Log3 $name, 1, "$name: Can't connect to $dev: $!" if(!$reopen);
+      $readyfnlist{"$name.$dev"} = $hash;
+      DevIo_setStates($hash, "disconnected");
+      return &$doCb("");
+    }
+    $hash->{TCPDev} = $conn;
+    $hash->{FD} = $conn->fileno();
+    delete($readyfnlist{"$name.$dev"});
+    $selectlist{"$name.$dev"} = $hash;
+
+  } elsif($dev =~ m/^FHEM:DEVIO:(.*)(:(.*))/) {      # Forum #46276
+    my ($devName, $devPort) = ($1, $3);
+    AssignIoPort($hash, $devName);
+    if (defined($hash->{IODev})) {
+      ($dev, $baudrate) = split("@", $hash->{DeviceName});
+      $hash->{IODevPort} = $devPort if (defined($devPort));
+      $hash->{IODevParameters} = $baudrate if (defined($baudrate));
+      if (!CallFn($devName, "IOOpenFn", $hash)) {
+        Log3 $name, 1, "$name: Can't open $dev!";
+        DevIo_setStates($hash, "disconnected");
+        return &$doCb("");
+      }
+    } else {
+      DevIo_setStates($hash, "disconnected");
+      return &$doCb("");
+    }
+  } elsif($dev =~ m/^(.+):([0-9]+)$/) {       # host:port
+
+    # This part is called every time the timeout (5sec) is expired _OR_
+    # somebody is communicating over another TCP connection. As the connect
+    # for non-existent devices has a delay of 3 sec, we are sitting all the
+    # time in this connect. NEXT_OPEN tries to avoid this problem.
+    if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN}) {
+      return &$doCb(undef); # Forum 53309
+    }
+
+    delete($readyfnlist{"$name.$dev"});
+    my $timeout = $hash->{TIMEOUT} ? $hash->{TIMEOUT} : 3;
+
+    
+    # Do common TCP/IP "afterwork":
+    # if connected: set keepalive, fill selectlist, FD, TCPDev.
+    # if not: report the error and schedule reconnect
+    my $doTcpTail = sub($) {
+      my ($conn) = @_;
+      if($conn) {
+        delete($hash->{NEXT_OPEN});
+        $conn->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if(defined($conn));
+
+      } else {
+        Log3 $name, 1, "$name: Can't connect to $dev: $!" if(!$reopen && $!);
+        $readyfnlist{"$name.$dev"} = $hash;
+        DevIo_setStates($hash, "disconnected");
+        $hash->{NEXT_OPEN} = time() + $nextOpenDelay;
+        return 0;
+      }
+
+      $hash->{TCPDev} = $conn;
+      $hash->{FD} = $conn->fileno();
+      $selectlist{"$name.$dev"} = $hash;
+      return 1;
+    };
+
+    if($callback) { # reuse the nonblocking connect from HttpUtils.
+      use HttpUtils;
+      my $err = HttpUtils_Connect({     # Nonblocking
+        timeout => $timeout,
+        url     => $hash->{SSL} ? "https://$dev/" : "http://$dev/",
+        NAME    => $hash->{NAME},
+        noConn2 => 1,
+        callback=> sub() {
+          my ($h, $err, undef) = @_;
+          &$doTcpTail($err ? undef : $h->{conn});
+          return &$doCb($err ? $err : &$doTailWork());
+        }
+      });
+      return &$doCb($err) if($err);
+      return undef;     # no double callback: connect is running in bg now
+
+    } else {    # blocking connect
+      my $conn = $haveInet6 ? 
+          IO::Socket::INET6->new(PeerAddr => $dev, Timeout => $timeout) :
+          IO::Socket::INET ->new(PeerAddr => $dev, Timeout => $timeout);
+      return "" if(!&$doTcpTail($conn)); # no callback: no doCb
+    }
+
+  } elsif($baudrate && lc($baudrate) eq "directio") { # w/o Device::SerialPort
+
+    if(!open($po, "+<$dev")) {
+      return &$doCb(undef) if($reopen);
+      Log3 $name, 1, "$name: Can't open $dev: $!";
+      $readyfnlist{"$name.$dev"} = $hash;
+      DevIo_setStates($hash, "disconnected");
+      return &$doCb("");
+    }
+
+    $hash->{DIODev} = $po;
+
+    if( $^O =~ /Win/ ) {
+      $readyfnlist{"$name.$dev"} = $hash;
+    } else {
+      $hash->{FD} = fileno($po);
+      delete($readyfnlist{"$name.$dev"});
+      $selectlist{"$name.$dev"} = $hash;
+    }
+
+
+  } else {                              # USB/Serial device
+
+    if ($^O=~/Win/) {
+     eval {
+       require Win32::SerialPort;
+       $po = new Win32::SerialPort ($dev);
+     }
+    } else  {
+     eval {
+       require Device::SerialPort;
+       $po = new Device::SerialPort ($dev);
+     }
+    }
+    if($@) {
+      Log3 $name,  1, $@;
+      return &$doCb($@);
+    }
+
+    if(!$po) {
+      return &$doCb(undef) if($reopen);
+      Log3 $name, 1, "$name: Can't open $dev: $!";
+      $readyfnlist{"$name.$dev"} = $hash;
+      DevIo_setStates($hash, "disconnected");
+      return &$doCb("");
+    }
+    $hash->{USBDev} = $po;
+    if( $^O =~ /Win/ ) {
+      $readyfnlist{"$name.$dev"} = $hash;
+    } else {
+      $hash->{FD} = $po->FILENO;
+      delete($readyfnlist{"$name.$dev"});
+      $selectlist{"$name.$dev"} = $hash;
+    }
+
+    if($baudrate) {
+      $po->reset_error();
+      my $p = ($parity eq "none" ? "N" : ($parity eq "odd" ? "O" : "E"));
+      Log3 $name, 3, "Setting $name serial parameters to ".
+                    "$baudrate,$databits,$p,$stopbits" if(!$hash->{DevioText});
+      $po->baudrate($baudrate);
+      $po->databits($databits);
+      $po->parity($parity);
+      $po->stopbits($stopbits);
+      $po->handshake('none');
+
+      # This part is for some Linux kernel versions whih has strange default
+      # settings.  Device::SerialPort is nice: if the flag is not defined for
+      # your OS then it will be ignored.
+
+      $po->stty_icanon(0);
+      #$po->stty_parmrk(0); # The debian standard install does not have it
+      $po->stty_icrnl(0);
+      $po->stty_echoe(0);
+      $po->stty_echok(0);
+      $po->stty_echoctl(0);
+
+      # Needed for some strange distros
+      $po->stty_echo(0);
+      $po->stty_icanon(0);
+      $po->stty_isig(0);
+      $po->stty_opost(0);
+      $po->stty_icrnl(0);
+    }
+
+    $po->write_settings;
+  }
+
+  return &$doCb(&$doTailWork());
+}
+
+########################
+# close the device, remove it from selectlist, 
+# delete DevIo specific internals from $hash
+sub
+DevIo_CloseDev($@)
+{
+  my ($hash,$isFork) = @_;
+  my $name = $hash->{NAME};
+  my $dev = $hash->{DeviceName};
+
+  return if(!$dev);
+  
+  if($hash->{TCPDev}) {
+    $hash->{TCPDev}->close();
+    delete($hash->{TCPDev});
+  }
+  ($dev, undef) = split("@", $dev); # Remove the baudrate
+  delete($selectlist{"$name.$dev"});
+  delete($readyfnlist{"$name.$dev"});
+  delete($hash->{FD});
+  delete($hash->{EXCEPT_FD});
+  delete($hash->{PARTIAL});
+  delete($hash->{NEXT_OPEN});
+}
+
+sub
+DevIo_IsOpen($)
+{
+  my ($hash) = @_;
+  return ($hash->{TCPDev});
+}
+
+# Close the device, schedule the reopen via ReadyFn, trigger DISCONNECTED
+sub
+DevIo_Disconnected($)
+{
+  my $hash = shift;
+  my $dev = $hash->{DeviceName};
+  my $name = $hash->{NAME};
+  my $baudrate;
+  ($dev, $baudrate) = split("@", $dev);
+
+  return if(!defined($hash->{FD}));                 # Already deleted or RFR
+
+  my $l = $hash->{devioLoglevel}; # Forum #61970
+  Log3 $name, ($l ? $l:1), "$dev disconnected, waiting to reappear ($name)";
+  DevIo_CloseDev($hash);
+  $readyfnlist{"$name.$dev"} = $hash;               # Start polling
+  DevIo_setStates($hash, "disconnected");
+  $hash->{DevIoJustClosed} = 1;                     # Avoid a direct reopen
+
+  DoTrigger($name, "DISCONNECTED");
+}
+
+1;

File diff suppressed because it is too large
+ 5426 - 0
fhem/core/FHEM/lib/74_AMADautomagicFlowset_4.2.4.xml


File diff suppressed because it is too large
+ 12677 - 0
fhem/core/FHEM/lib/74_AMADtaskerset_4.2.4.prj.xml


File diff suppressed because it is too large
+ 2148 - 0
fhem/core/Muell-Hassloch-2019.ics


+ 23 - 0
fhem/core/www/gplot/HolgerHandy.gplot

@@ -0,0 +1,23 @@
+# Created by FHEM/98_SVG.pm, 2018-11-23 09:50:39
+set terminal png transparent size <SIZE> crop
+set output '<OUT>.png'
+set xdata time
+set timefmt "%Y-%m-%d_%H:%M:%S"
+set xlabel " "
+set title '<L1>'
+set ytics 
+set y2tics ("present" 1.5,"away" 0)
+set grid
+set ylabel ""
+set y2label ""
+set y2range [0:3]
+
+#DBLogging HolgerHandy:state:::$val=($val=~"present"?2.5:0)
+#DBLogging PitHandy:state:::$val=($val=~"present"?2.0:0)
+#DBLogging JuleHandy:state:::$val=($val=~"present"?1.5:0)
+#DBLogging NeleHandy:state:::$val=($val=~"present"?1.0:0)
+
+plot "<IN>" using 1:2 axes x1y2 title 'Holger' ls l0 lw 1 with lines,\
+     "<IN>" using 1:2 axes x1y2 title 'Pit' ls l1 lw 1 with lines,\
+     "<IN>" using 1:2 axes x1y2 title 'Jule' ls l2 lw 1 with lines,\
+     "<IN>" using 1:2 axes x1y2 title 'Nele' ls l4 lw 1 with lines

+ 22 - 0
fhem/core/www/gplot/st.HeatedWaterPump.gplot

@@ -0,0 +1,22 @@
+# Created by FHEM/98_SVG.pm, 2018-11-26 18:22:09
+set terminal png transparent size <SIZE> crop
+set output '<OUT>.png'
+set xdata time
+set timefmt "%Y-%m-%d_%H:%M:%S"
+set xlabel " "
+set title '<TL>'
+set ytics 
+set y2tics 
+set grid
+set ylabel "Leistung[W]"
+set y2label "Verbrauch[kWh]"
+set yrange [0:25]
+set y2range [0:0.4]
+
+#DBLogging st.HeatedWaterPump:ENERGY_Power
+#DBLogging st.HeatedWaterPump:ENERGY_Today
+#DBLogging st.HeatedWaterPump:ENERGY_Yesterday
+
+plot "<IN>" using 1:2 axes x1y1 title 'Leistung' ls l0 lw 1 with lines,\
+     "<IN>" using 1:2 axes x1y2 title 'Verbrauch' ls l1fill lw 1 with lines,\
+     "<IN>" using 1:2 axes x1y2 title 'Vortag' ls l2 lw 1 with lines

+ 1 - 0
fhem/core/www/images/myFritz.m3u

@@ -0,0 +1 @@
+http://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&prev=input&tl=fr&q=Lirumlaruml%C3%B6ffelstielwerdasnichtkannderkannnichtviel

BIN
fhem/core/www/tablet_dev/images/avia_500x360.png


BIN
fhem/core/www/tablet_dev/images/esso_390x276.png


BIN
fhem/core/www/tablet_dev/images/greenboard-900x400.jpg


BIN
fhem/core/www/tablet_dev/images/real_800x400.png


BIN
fhem/core/www/tablet_dev/images/shell_360x360.png


+ 102 - 0
sonos/Dockerfile

@@ -0,0 +1,102 @@
+FROM debian:stretch
+
+MAINTAINER Matthias Kleine <info@haus-automatisierung.com>
+
+ENV FHEM_VERSION 5.8
+ENV DEBIAN_FRONTEND noninteractive
+ENV TERM xterm
+
+# Install dependencies
+RUN apt-get update && apt-get upgrade -y --force-yes && apt-get install -y --force-yes --no-install-recommends apt-utils
+RUN apt-get -y --force-yes install \
+apt-transport-https \
+build-essential \
+dfu-programmer \
+etherwake \
+git \
+perl \
+snmp \
+snmpd \
+sqlite3 \
+sudo \
+telnet \
+usbutils \
+vim \
+wget
+
+# Install perl packages
+RUN apt-get -y --force-yes --fix-missing install \
+libalgorithm-merge-perl \
+libauthen-oath-perl \
+libavahi-compat-libdnssd-dev \
+libcgi-pm-perl \
+libclass-dbi-mysql-perl \
+libclass-isa-perl \
+libcrypt-cbc-perl \
+libcrypt-ecb-perl \
+libcrypt-urandom-perl \
+libcgi-pm-perl \
+libcommon-sense-perl \
+libconvert-base32-perl \
+libcrypt-urandom-perl \
+libdata-dump-perl \
+libdatetime-format-strptime-perl \
+libdbd-sqlite3-perl \
+libdbi-perl \
+libdevice-serialport-perl \
+libdigest-md5-perl \
+libdpkg-perl \
+liberror-perl \
+libfile-copy-recursive-perl \
+libfile-fcntllock-perl \
+libgd-graph-perl \
+libgd-text-perl \
+libimage-info-perl \
+libimage-librsvg-perl \
+libio-socket-inet6-perl \
+libio-socket-ip-perl \
+libio-socket-multicast-perl \
+libio-socket-ssl-perl \
+libimage-info-perl \
+libimage-librsvg-perl \
+libjson-perl \
+libjson-xs-perl \
+liblist-moreutils-perl \
+libmail-imapclient-perl \
+libmail-sendmail-perl \
+libmime-base64-perl \
+libnet-telnet-perl \
+libsoap-lite-perl \
+libsocket-perl \
+libsocket6-perl \
+libswitch-perl \
+libsys-hostname-long-perl \
+libterm-readkey-perl \
+libterm-readline-perl-perl \
+libtext-csv-perl \
+libtext-diff-perl \
+libtimedate-perl \
+libwww-perl \
+libxml-simple-perl \
+libhtml-tableextract-perl \
+libxml-parser-lite-perl
+
+RUN cpan Crypt::Rijndael_PP
+RUN cpan Net::MQTT::Simple
+RUN cpan Net::MQTT::Constants
+
+# Install fhem
+# Set timezone to Europe/Berlin
+ENV TZ=Europe/Berlin
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+RUN wget https://fhem.de/fhem-${FHEM_VERSION}.deb && dpkg -i fhem-${FHEM_VERSION}.deb
+RUN userdel fhem
+
+WORKDIR "/opt/fhem"
+
+COPY core/start.sh ./
+
+EXPOSE 8083 7072
+
+CMD bash /opt/fhem/start.sh