hmetzner 7 vuotta sitten
vanhempi
commit
7469cb6c2c
40 muutettua tiedostoa jossa 37389 lisäystä ja 0 poistoa
  1. 94 0
      fhem-compose.yml
  2. 379 0
      fhem/core/FHEM/37_NotifyAndroidTV.pm
  3. 2243 0
      fhem/core/FHEM/39_Talk2Fhem.pm
  4. 833 0
      fhem/core/FHEM/50_MOBILEALERTSGW.pm
  5. 1187 0
      fhem/core/FHEM/51_MOBILEALERTS.pm
  6. 473 0
      fhem/core/FHEM/52_I2C_EMC1001.pm
  7. 2976 0
      fhem/core/FHEM/72_XiaomiDevice.pm
  8. 669 0
      fhem/core/FHEM/74_UnifiVideo.pm
  9. 1059 0
      fhem/core/FHEM/74_XiaomiBTLESens.pm
  10. 2632 0
      fhem/core/FHEM/88_HMCCURPCPROC.pm
  11. 785 0
      fhem/core/FHEM/88_xs1Bridge.pm
  12. 548 0
      fhem/core/FHEM/88_xs1Dev.pm
  13. 2256 0
      fhem/core/FHEM/95_Babble.pm
  14. 1701 0
      fhem/core/FHEM/97_PiXtendV2.pm
  15. 1104 0
      fhem/core/FHEM/98_freezemon.pm
  16. 5278 0
      fhem/core/FHEM/lib/74_AMADautomagicFlowset_4.2.0.xml
  17. 11136 0
      fhem/core/FHEM/lib/74_AMADtaskerset_4.2.0.prj.xml
  18. 287 0
      fhem/core/FHEM/lib/EnO_ReCom_Device_Descr.xml
  19. 30 0
      fhem/core/db.conf
  20. 83 0
      fhem/core/www/images/fhemSVG/logic.svg
  21. 59 0
      fhem/core/www/images/fhemSVG/mqtt.svg
  22. 81 0
      fhem/core/www/images/fhemSVG/mqtt_broker.svg
  23. 68 0
      fhem/core/www/images/fhemSVG/mqtt_device.svg
  24. 30 0
      fhem/core/www/images/fhemSVG/sonos_play1.svg
  25. 332 0
      fhem/core/www/pgm2/babble.js
  26. 662 0
      fhem/core/www/pgm2/f18.js
  27. 68 0
      fhem/core/www/pgm2/f18style.css
  28. 178 0
      fhem/core/www/tablet/ftui_check.html
  29. 37 0
      fhem/core/www/tablet/js/widget_include.js
  30. 46 0
      fhem/core/www/tablet/js/widget_theme.js
  31. 1 0
      fhem/core/www/tablet/lib/farbtastic.min.js
  32. 1 0
      fhem/core/www/tablet/lib/fhemSVG.min.css
  33. 1 0
      fhem/core/www/tablet/lib/genericons.min.css
  34. 1 0
      fhem/core/www/tablet/lib/jquery.circlemenu.min.js
  35. 1 0
      fhem/core/www/tablet/lib/jquery.datetimepicker.min.css
  36. 1 0
      fhem/core/www/tablet/lib/jquery.datetimepicker.min.js
  37. 66 0
      fhem/core/www/tablet/lib/jquery.unveil.js
  38. 1 0
      fhem/core/www/tablet/lib/jquery.unveil.min.js
  39. 1 0
      fhem/core/www/tablet/lib/jquery.wakeup.min.js
  40. 1 0
      fhem/core/www/tablet/lib/openautomation.min.css

+ 94 - 0
fhem-compose.yml

@@ -0,0 +1,94 @@
+version: '2'
+
+services:
+    fhem:
+        restart: unless-stopped
+        container_name: fhem-core
+        expose:
+            - "8083"
+            - "7072"
+        ports:
+            - "8083:8083"
+            - "7072:7072"
+        build: fhem
+        privileged: true
+        volumes:
+            - ./fhem/core/:/opt/fhem/
+        networks:
+            - fhem-network
+        depends_on:
+            - "mysql"
+            - "mqtt"
+
+#    homebridge:
+#        restart: always
+#        image: oznu/homebridge
+#        volumes:
+#            - ./homebridge/config.json:/homebridge/config.json
+#            - ./homebridge/package.json:/homebridge/package.json
+#        environment:
+#            - TZ=Europe/Berlin
+#            - PGID=1000
+#            - PUID=1000
+#        network_mode: host
+#        depends_on:
+#            - "fhem"
+
+#    habridge:
+#        restart: always
+#        build: habridge
+#        network_mode: host
+
+    mysql:
+        restart: unless-stopped
+        container_name: fhem-mysql
+        expose:
+            - "3306"
+            - "33060"
+        ports:
+            - "3306:3306"
+            - "33060:33060"
+        image: mysql/mysql-server:5.7
+        volumes:
+            - ./mysql/init.sql:/docker-entrypoint-initdb.d/fhem-init.sql
+            - ./mysql/data:/var/lib/mysql
+        environment:
+            - MYSQL_RANDOM_ROOT_PASSWORD=yes
+        networks:
+            - fhem-network
+
+    mqtt:
+        restart: unless-stopped
+        container_name: fhem-mqtt
+        expose:
+            - "1883"
+            - "9001"
+        ports:
+            - "1883:1883"
+            - "9001:9001"
+        image: toke/mosquitto
+        networks:
+            - fhem-network
+        volumes:
+            - ./mqtt/config/:/mqtt/config/
+            - ./mqtt/log/:/mqtt/log/
+            - ./mqtt/data/:/mqtt/data/
+
+    nodered:
+        restart: unless-stopped
+        container_name: fhem-nodered
+        expose:
+            - "1880"
+        ports:
+            - "1880:1880"
+        image: nodered/node-red-docker:0.18.4
+        volumes:
+            - ./nodered/data/:/data/
+        networks:
+            - fhem-network
+        depends_on:
+            - "mqtt"
+
+networks:
+    fhem-network:
+        driver: bridge

+ 379 - 0
fhem/core/FHEM/37_NotifyAndroidTV.pm

@@ -0,0 +1,379 @@
+# $Id: 37_NotifyAndroidTV.pm 16138 2018-02-09 20:36:36Z justme1968 $
+
+package main;
+
+use strict;
+use warnings;
+
+use HttpUtils;
+
+use vars qw($FW_icondir);
+
+use Data::Dumper;
+
+my $options = { position => { 'bottom-right' => 0,
+                              'bottom-left' => 1,
+                              'top-right' => 2,
+                              'top-left' => 3,
+                              'center' => 4,
+                            },
+                width => { 'default' => 0,
+                          'narrow' => 1,
+                          'small' => 2,
+                          'wide' => 3,
+                          'extrawide' => 4,
+                        },
+                transparency => { 'default' => 0,
+                                  '0%' => 1,
+                                  '25%' => 2,
+                                  '50%' => 3,
+                                  '75%' => 4,
+                                  '100%' => 5,
+                                },
+                interrupt => { 'true' => 1,
+                               'false' => 0,
+                             },
+                bkgcolor => { 'grey' => '#607d8b',
+                              'black' => '#000000',
+                              'indigo' => '#303F9F',
+                              'green' => '#4CAF50',
+                              'red' => '#F44336',
+                              'cyan' => '#00BCD4',
+                              'teal' => '#009688',
+                              'amber' => '#FFC107',
+                              'pink' => '#E91E63',
+                            },
+                type => { 'complete' => 0,
+                          'titleonly' => 1,
+                          'nameonly' => 2,
+                          'icononly' => 3,
+                          'noimage' => 4,
+                          'short' => 5,
+                        },
+                duration => undef,
+                offset => undef,
+                offsety => undef,
+                icon => undef,
+                image => undef,
+                title => undef,
+                imageurl => undef,
+             };
+
+sub
+NotifyAndroidTV_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{DefFn}    = "NotifyAndroidTV_Define";
+  $hash->{UndefFn}  = "NotifyAndroidTV_Undefine";
+  $hash->{SetFn}    = "NotifyAndroidTV_Set";
+  $hash->{AttrFn}   = "NotifyAndroidTV_Attr";
+  #$hash->{AttrList} = "defaultIcon";
+
+  foreach my $option (keys %{$options}) {
+    $hash->{AttrList} .= ' ' if( $hash->{AttrList} );
+    #$hash->{AttrList} .= "NotifyAndroidTV#$option";
+    $hash->{AttrList} .= "default". ucfirst $option;
+    if( $options->{$option} ) {
+      $hash->{AttrList} .= ":select,". join( ',', sort keys %{$options->{$option}} )
+    }
+  }
+  #delete $hash->{AttrList};
+}
+
+#####################################
+
+sub
+NotifyAndroidTV_Define($$)
+{
+  my ($hash, $def) = @_;
+
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "Usage: define <name> NotifyAndroidTV <host>" if(@a != 3);
+
+  my $name = $a[0];
+  my $host = $a[2];
+
+  $hash->{NAME} = $name;
+  $hash->{host} = $host;
+
+
+  $hash->{STATE} = 'INITIALIZED';
+
+  return undef;
+}
+
+sub
+NotifyAndroidTV_Undefine($$)
+{
+  my ($hash, $arg) = @_;
+
+  return undef;
+}
+
+sub
+NotifyAndroidTV_addFormField($$;$)
+{
+  my ($name,$data,$extra) = @_;
+
+  return "--boundary\r\n".
+         "Content-Disposition: form-data; name=\"$name\"\r\n".
+         ($extra?"$extra\r\n":"").
+         "\r\n".
+         $data."\r\n"
+}
+
+sub
+NotifyAndroidTV_parseHttpAnswer($$$)
+{
+  my ($param, $err, $data) = @_;
+  my $hash = $param->{hash};
+  my $name = $hash->{NAME};
+
+  if( $err ) {
+    Log3 $name, 2, "NotifyAndroidTV ($name) - got error while sending notificaton $err";
+    readingsSingleUpdate($hash,"lastError", $err, 1);
+    if( $param->{cl} && $param->{cl}{canAsyncOutput} ) {
+      asyncOutput( $param->{cl}, "got error while sending notification $err\n" );
+    }
+    return;
+  }
+}
+
+sub
+NotifyAndroidTV_Set($$@)
+{
+  my ($hash, $name, $cmd, @params) = @_;
+
+  $hash->{".triggerUsed"} = 1;
+
+  my $list = 'msg';
+
+  if( $cmd && ($cmd eq 'msg' || $cmd eq 'notify') ) {
+    my ($param_a, $param_h) = parseParams(\@params);
+
+    my $txt = join( ' ', @{$param_a} );
+    $txt =~ s/\n/<br>/g;
+
+    my $error;
+    foreach my $option (keys %{$param_h}) {
+      if( $options->{$option} ) {
+        if( defined( $options->{$option}{$param_h->{$option}}) ) {
+          $param_h->{$option} = $options->{$option}{$param_h->{$option}};
+        } elsif( grep {$_==$param_h->{$option}} values %{$options->{$option}} )  {
+          $param_h->{$option} = $param_h->{$option};
+        } else {
+          $param_h->{$option} = undef;
+        }
+
+        if( !defined($param_h->{$option}) ) {
+          $error .= "\n";
+          $error .= "$option value must be one of: ". join( ' ', sort keys %{$options->{$option}} );
+        }
+      }
+    }
+
+    return "error: $error" if( $error );
+
+    foreach my $option (keys %{$options}) {
+      if( !defined($param_h->{$option}) ) {
+        if( my $default = AttrVal($name, "default". ucfirst $option, undef) ) {
+          $param_h->{$option} = $default;
+        }
+      }
+    }
+
+    $param_h->{offset} = 0 if( !$param_h->{offset} );
+    $param_h->{offsety} = 0 if( !$param_h->{offsety} );
+    $param_h->{transparency} = 0 if( !$param_h->{transparency} );
+    if( $param_h->{duration} && $param_h->{duration} eq 'unlimited' ) {
+      $param_h->{duration} = 15;
+      $param_h->{interrupt} = 1;
+    }
+
+    if( $txt && $txt eq '?' ) {
+      my $usage = "usage: set $name msg";
+      foreach my $option (sort keys %{$options}) {
+        if( $options->{$option} ) {
+          $usage .= " [$option=". join( '|', sort keys %{$options->{$option}} ). "]";
+        } else {
+          $usage .= " [$option=<$option>]";
+        }
+      }
+      $usage .= " <message>";
+
+      return $usage;
+    }
+
+
+    $param_h->{icon} = 'fhemicon.png' if( !$param_h->{icon} );
+    $param_h->{icon} .= ".png" if( $param_h->{icon} !~ '\.' );
+    $param_h->{icon} = "$FW_icondir/default/$param_h->{icon}" if( $param_h->{icon} !~ '^/' );
+
+    Log3 $name, 5, "$name: using icon $param_h->{icon}";
+
+    my $icon;
+    local( *FH ) ;
+    if( open( FH, $param_h->{icon} ) ) {
+      $icon = do { local( $/ ) ; <FH> } ;
+      close( FH );
+    }
+
+    return "icon not found: $param_h->{icon}" if( !$icon );
+    delete $param_h->{icon};
+
+
+    my $image;
+    if( $param_h->{image} && $param_h->{image} =~ m/^{.*}$/ ) {
+      $image = eval $param_h->{image};
+      if( $@ ) {
+        Log3 $name, 5, "$name: $@";
+        return $@;
+      }
+      return "empty image returned from perl code" if(!$image);
+
+    } elsif( $param_h->{image} ) {
+      #$param_h->{image} .= ".jpg" if( $param_h->{image} !~ '\.' );
+      #$param_h->{image} = "$FW_icondir/default/$param_h->{image}" if( $param_h->{image} !~ '^/' );
+
+      Log3 $name, 5, "$name: using image $param_h->{image}";
+
+      local( *FH ) ;
+      if( open( FH, $param_h->{image} ) ) {
+        $image = do { local( $/ ) ; <FH> } ;
+        close(FH);
+      }
+
+      return "image not found: $param_h->{image}" if( !$image );
+      delete $param_h->{image};
+    }
+
+
+    my $param;
+    $param->{url}        = "http://$hash->{host}:7676/";
+    $param->{callback}   = \&NotifyAndroidTV_parseHttpAnswer;
+    $param->{hash}       = $hash;
+    $param->{cl} = $hash->{CL} if( ref($hash->{CL}) eq 'HASH' );
+    $param->{noshutdown} = 1;
+    $param->{timeout}    = 5;
+    $param->{loglevel}   = 5;
+    $param->{method}     = "POST";
+    $param->{header}     = "Content-Type: multipart/form-data; boundary=boundary";
+
+    $param->{data} .= NotifyAndroidTV_addFormField('msg', $txt);
+    foreach my $option (keys %{$param_h}) {
+      $param->{data} .= NotifyAndroidTV_addFormField($option, $param_h->{$option})
+    }
+    Log3 $name, 5, $param->{data};
+
+    $param->{data} .= NotifyAndroidTV_addFormField('filename', $icon, "filename=\"fhemicon.png\"\r\nContent-Type: application/octet-stream");
+    $param->{data} .= NotifyAndroidTV_addFormField('filename2', $image, "filename=\"image.png\"\r\nContent-Type: application/octet-stream") if( $image );
+    $param->{data} .= "--boundary--";
+
+    Log3 $name, 4, "NotifyAndroidTV ($name) - send notification ";
+    #Log3 $name, 5, $param->{data};
+
+    my ($err, undef) = HttpUtils_NonblockingGet($param);
+
+    Log3 $name, 5, "NotifyAndroidTV ($name) - received http response code ".$param->{code} if(exists($param->{code}));
+
+    if ($err) {
+      Log3 $name, 3, "NotifyAndroidTV ($name) - got error while sending notificaton $err";
+      return "got error while sending notification $err";
+    }
+    return;
+  }
+
+  $list =~ s/ $//;
+  return "Unknown argument $cmd, choose one of $list";
+}
+
+sub
+NotifyAndroidTV_Get($$@)
+{
+  my ($hash, $name, $cmd, @params) = @_;
+
+  my $list = '';
+
+  $list =~ s/ $//;
+  return "Unknown argument $cmd, choose one of $list";
+}
+
+sub
+NotifyAndroidTV_Attr($$$)
+{
+  my ($cmd, $name, $attrName, $attrVal) = @_;
+
+  my $orig = $attrVal;
+  $attrVal = int($attrVal) if($attrName eq "interval");
+
+  my $hash = $defs{$name};
+  if( $attrName eq 'disable' ) {
+  }
+
+
+  if( $cmd eq "set" ) {
+    if( $attrName =~ m/default(.*)/ ) {
+      my $option = lcfirst $1;
+      if($options->{$option} && !defined($options->{$option}{$attrVal})) {
+        return "$attrName value must be one of: ". join( ' ', sort keys %{$options->{$option}} );
+      }
+    }
+  }
+
+  return;
+}
+
+1;
+
+=pod
+=item summary    Notifications for Android TV/Fire TV module
+=item summary_DE Notifications for Android TV/Fire TV Modul
+=begin html
+
+<a name="NotifyAndroidTV"></a>
+<h3>NotifyAndroidTV</h3>
+<ul>
+  This module allows you to send notifications to the
+  <a href ='https://play.google.com/store/apps/details?id=de.cyberdream.androidtv.notifications.google'>
+  Notifications for Android TV</a> and
+  <a href ='https://www.amazon.de/Christian-Fees-Notifications-for-Fire/dp/B00OESCXEK'>
+  Notifications for Fire TV</a> apps.
+  <br><br>
+  <code>set &lt;name&gt; msg bkgcolor=amber interrupt=true position=top-left transparency=0% duration=2 offset=10 icon=fhemicon title="der titel" das ist ein test</code>
+
+  <br><br>
+
+
+  <a name="NotifyAndroidTV_Define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; NotifyAndroidTV &lt;host&gt;</code>
+    <br><br>
+  </ul>
+
+  <a name="NotifyAndroidTV_Set"></a>
+  <b>Set</b>
+  <ul>
+    <li>msg [options] &lt;message&gt;<br>
+    possible options are: bkgcolor, interrupt, position, transparency, duration, offset, offsety, width, type, icon, image, title, imageurl. use <code>set &lt;name&gt; notify</code> to see valid values.<br>
+    <code>set nb msg ?</code> shows a help text<br>
+    it is better to use imageurl instad of image as it is non blocking!<br>
+    image can be given as <code>image={&lt;perlCode&gt;}</code></li>
+  </ul><br>
+
+  <a name="NotifyAndroidTV_Get"></a>
+  <b>Get</b>
+  <ul>none
+  </ul><br>
+
+  <a name="NotifyAndroidTV_Attr"></a>
+  <b>Attr</b>
+  <ul>none
+  </ul>
+
+</ul><br>
+
+=end html
+=cut

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2243 - 0
fhem/core/FHEM/39_Talk2Fhem.pm


+ 833 - 0
fhem/core/FHEM/50_MOBILEALERTSGW.pm

@@ -0,0 +1,833 @@
+##############################################
+# $Id: 50_MOBILEALERTSGW.pm 16104 2018-02-06 19:11:13Z MarkusF $
+# Written by Markus Feist, 2017
+package main;
+
+use strict;
+use warnings;
+use TcpServerUtils;
+use HttpUtils;
+use IO::Socket;
+
+use constant MA_PACKAGE_LENGTH => 64;
+
+my $MA_wname;
+my $MA_chash;
+my $MA_cname;
+my @MA_httpheader;
+my %MA_httpheader;
+
+sub MOBILEALERTSGW_Initialize($) {
+    my ($hash) = @_;
+
+    $hash->{ReadFn}        = "MOBILEALERTSGW_Read";
+    $hash->{GetFn}         = "MOBILEALERTSGW_Get";
+    $hash->{SetFn}         = "MOBILEALERTSGW_Set";
+    $hash->{AttrFn}        = "MOBILEALERTSGW_Attr";
+    $hash->{DefFn}         = "MOBILEALERTSGW_Define";
+    $hash->{UndefFn}       = "MOBILEALERTSGW_Undef";
+    $hash->{Clients}       = "MOBILEALERTS";
+    $hash->{MatchList}     = { "1:MOBILEALERTS" => "^.*" };
+    $hash->{Write}         = "MOBILEALERTSGW_Write";
+    $hash->{FingerprintFn} = "MOBILEALERTSGW_Fingerprint";
+
+    #$hash->{NotifyFn}= ($init_done ? "FW_Notify" : "FW_SecurityCheck");
+    #$hash->{AsyncOutputFn} = "MOBILEALERTSGW_AsyncOutput";
+    #$hash->{ActivateInformFn} = "MOBILEALERTSGW_ActivateInform";
+    $hash->{AttrList} = "forward:0,1 " . $readingFnAttributes;
+    Log3 "MOBILEALERTSGW", 5, "MOBILEALERTSGW_Initialize finished.";
+}
+
+sub MOBILEALERTSGW_Define($$) {
+    my ( $hash, $def ) = @_;
+    my ( $name, $type, $port ) = split( "[ \t]+", $def );
+    return "Usage: define <name> MOBILEALERTSGW <tcp-portnr>"
+      if ( $port !~ m/^\d+$/ );
+
+    my $ret = TcpServer_Open( $hash, $port, "global" );
+
+    return $ret;
+}
+
+sub MOBILEALERTSGW_GetUDPSocket($$) {
+    my ( $hash, $name ) = @_;
+    my $socket;
+    if ( defined( $hash->{UDPHASH} ) ) {
+        $socket = $hash->{UDPHASH}->{UDPSOCKET};
+    }
+    else {
+        #IO::Socket::INET geht leider nicht
+        Log3 $name, 3, "$name MOBILEALERTSGW: Create UDP Socket.";
+        unless ( socket( $socket, AF_INET, SOCK_DGRAM, getprotobyname('udp') ) )
+        {
+            Log3 $name, 1, "$name MOBILEALERTSGW: Could not create socket: $!";
+            return undef;
+        }
+        unless ( setsockopt( $socket, SOL_SOCKET, SO_BROADCAST, 1 ) ) {
+            Log3 $name, 1, "$name MOBILEALERTSGW: Could not setsockopt: $!";
+            return undef;
+        }
+        my $cname = "${name}_UDPPORT";
+        my %nhash;
+        $nhash{NR}                  = $devcount++;
+        $nhash{NAME}                = $cname;
+        $nhash{FD}                  = fileno($socket);
+        $nhash{UDPSOCKET}           = $socket;
+        $nhash{TYPE}                = $hash->{TYPE};
+        $nhash{STATE}               = "Connected";
+        $nhash{SNAME}               = $name;
+        $nhash{TEMPORARY}           = 1;                 # Don't want to save it
+        $nhash{HASH}                = $hash;
+        $attr{$cname}{room}         = "hidden";
+        $defs{$cname}               = \%nhash;
+        $selectlist{ $nhash{NAME} } = \%nhash;
+        $hash->{UDPHASH}            = \%nhash;
+    }
+    return $socket;
+}
+
+sub MOBILEALERTSGW_Get ($$@) {
+    my ( $hash, $name, $cmd, @args ) = @_;
+
+    return "\"get $name\" needs at least one argument" unless ( defined($cmd) );
+
+    if ( $cmd eq "config" ) {
+        my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) );
+        my $gateway = $args[0];
+        my $destpaddr;
+        my $command;
+
+        if ( $gateway =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/ ) {
+            $destpaddr = sockaddr_in( 8003, inet_aton($gateway) );
+            $gateway   = "000000000000";
+            $command   = 1;
+        }
+        elsif ( $gateway =~ /([0-9A-F]{12})/ ) {
+            if ( @gateways == 0 ) {
+                $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST );
+                $command = 2;
+            }
+            elsif ( !grep( /^$gateway$/, @gateways ) ) {
+                $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST );
+                $command = 2;
+            }
+            else {
+                my $ip = ReadingsVal( $name, "GW_" . $gateway . "_ip", "" );
+                if ( length($ip) == 0 ) {
+                    $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST );
+                }
+                else {
+                    $destpaddr = sockaddr_in( 8003, inet_aton($ip) );
+                }
+                $command = 2;
+            }
+        }
+        else {
+            $gateway   = "000000000000";
+            $destpaddr = sockaddr_in( 8003, INADDR_BROADCAST );
+            $command   = 1;
+        }
+        my $socket = MOBILEALERTSGW_GetUDPSocket( $hash, $name );
+        if ( !defined($socket) ) {
+            return "Could not create socket.";
+        }
+        my $data = pack( "nH[12]n", $command, $gateway, 10 );
+        Log3 $name, 5,
+          "$name MOBILEALERTSGW: Send GetConfig " . unpack( "H*", $data );
+        send( $socket, $data, 0, $destpaddr );
+        return undef;
+    }
+    else {
+        return "Unknown argument $cmd, choose one of config";
+    }
+}
+
+sub MOBILEALERTSGW_Set ($$@) {
+    my ( $hash, $name, $cmd, @args ) = @_;
+
+    return "\"set $name\" needs at least one argument" unless ( defined($cmd) );
+
+    if ( $cmd eq "clear" ) {
+        if ( $args[0] eq "readings" ) {
+            for ( keys %{ $hash->{READINGS} } ) {
+                readingsDelete($hash, $_) if ( $_ ne 'state' );
+            }
+            return undef;
+        }
+        else {
+            return "Unknown value $args[0] for $cmd, choose one of readings";
+        }
+    }
+    elsif ( $cmd eq "initgateway" ) {
+        my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) );
+        my $gateway = $args[0];
+
+        if ( @gateways == 0 ) {
+            return
+              "No gateway known. Find with 'get $name findgateways' first.";
+        }
+        if ( !grep( /^$gateway$/, @gateways ) ) {
+            return "Unknown $gateway for $cmd, choose one of "
+              . join( ",", @gateways );
+        }
+        my $ip     = ReadingsVal( $name, "GW_" . $gateway . "_ip",     "" );
+        my $config = ReadingsVal( $name, "GW_" . $gateway . "_config", "" );
+        if ( length($ip) == 0 ) {
+            return
+"IP of gateway unknown. Find with 'get $name findgateways' first.";
+        }
+        if ( length($config) == 0 ) {
+            return
+"Config of gateway unknown. Find with 'get $name findgateways' first.";
+        }
+        my $sock = IO::Socket::INET->new(
+            Proto    => 'udp',
+            PeerPort => 8003,
+            PeerAddr => $ip
+        ) or return "Could not create socket: $!\n";
+        my $myip   = $sock->sockhost;
+        my $myport = $hash->{PORT};
+
+        Log3 $name, 4,
+"$name MOBILEALERTSGW: Config gateway $gateway $ip Proxy auf $myip:$myport";
+        $config = pack( "H*", $config );
+        $config =
+            "\0\x04"
+          . substr( $config, 2, 6 )
+          . "\0\xB5"
+          . substr( $config, 15, 1 + 4 + 4 + 4 + 21 + 65 ) . "\x01"
+          . pack( "a65n", $myip, $myport )
+          . substr( $config, 182, 4 );
+        Log3 $name, 5, "$name MOBILEALERTSGW: Send " . unpack( "H*", $config );
+        $sock->send($config) or return "Could not send $!";
+        $sock->close();
+        return undef;
+    }
+    elsif ( $cmd eq "rebootgateway" ) {
+        my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) );
+        my $gateway = $args[0];
+
+        if ( @gateways == 0 ) {
+            return
+              "No gateway known. Find with 'get $name findgateways' first.";
+        }
+        if ( !grep( /^$gateway$/, @gateways ) ) {
+            return "Unknown $gateway for $cmd, choose one of "
+              . join( ",", @gateways );
+        }
+        my $ip = ReadingsVal( $name, "GW_" . $gateway . "_ip", "" );
+        if ( length($ip) == 0 ) {
+            return
+"IP of gateway unknown. Find with 'get $name findgateways' first.";
+        }
+        my $sock = IO::Socket::INET->new(
+            Proto    => 'udp',
+            PeerPort => 8003,
+            PeerAddr => $ip
+        ) or return "Could not create socket: $!\n";
+        Log3 $name, 4,
+          "$name MOBILEALERTSGW: Reboot gateway $gateway auf $ip:8003";
+        my $data = pack( "nH[12]n", 5, $gateway, 10 );
+        Log3 $name, 5, "$name MOBILEALERTSGW: Send " . unpack( "H*", $data );
+        $sock->send($data) or return "Could not send $!";
+        $sock->close();
+        return undef;
+    }
+    elsif ( $cmd eq "debuginsert" ) {
+        my $data = pack( "H*", $args[0] );
+        my ( $packageHeader, $timeStamp, $packageLength, $deviceID ) =
+          unpack( "CNCH12", $data );
+        Log3 $name, 4,
+            "$name MOBILEALERTSGW: Debuginsert PackageHeader: "
+          . $packageHeader
+          . " Timestamp: "
+          . scalar( FmtDateTimeRFC1123($timeStamp) )
+          . " PackageLength: "
+          . $packageLength
+          . " DeviceID: "
+          . $deviceID;
+        Log3 $name, 5, "$name MOBILEALERTSGW: Debuginsert for $deviceID: "
+          . unpack( "H*", $data );
+        Dispatch( $hash, $data, undef );
+        return undef;
+    }
+    else {
+        my $gateways = ReadingsVal( $name, "Gateways", "" );
+        return
+            "Unknown argument $cmd, choose one of clear:readings rebootgateway:"
+          . $gateways
+          . " initgateway:"
+          . $gateways;
+    }
+}
+
+sub MOBILEALERTSGW_Undef($$) {
+    my ( $hash, $name ) = @_;
+
+    if ( defined( $hash->{UDPHASH} ) ) {
+        my $cname = "${name}_UDPPORT";
+        delete( $selectlist{$cname} );
+        delete $attr{$cname};
+        delete $defs{$cname};
+        close( $hash->{UDPHASH}->{UDPSOCKET} );
+        delete $hash->{UDPHASH};
+    }
+
+    my $ret = TcpServer_Close($hash);
+    return $ret;
+}
+
+sub MOBILEALERTSGW_Attr($$$$) {
+    my ( $cmd, $name, $attrName, $attrValue ) = @_;
+
+    if ( $cmd eq "set" ) {
+        if ( $attrName eq "forward" ) {
+            if ( $attrValue !~ /^[01]$/ ) {
+                Log3 $name, 3,
+"$name MOBILEALERTSGW: Invalid parameter attr $name $attrName $attrValue";
+                return "Invalid value $attrValue allowed 0,1";
+            }
+        }
+    }
+    return undef;
+}
+
+sub MOBILEALERTSGW_Fingerprint($$$) {
+    my ( $io_name, $message ) = @_;
+
+#PackageHeader + UTC Timestamp + Package Length + Device ID + tx counter (3 bytes)
+    my $fingerprint = unpack( "H30", $message );
+    return ( $io_name, $fingerprint );
+}
+
+sub MOBILEALERTSGW_Write ($$) {
+
+    #Dummy, because it is not possible to send to device.
+    my ( $hash, @arguments ) = @_;
+    return undef;
+}
+
+sub MOBILEALERTSGW_Read($$);
+
+sub MOBILEALERTSGW_Read($$) {
+    my ( $hash, $reread ) = @_;
+    my $name    = $hash->{NAME};
+    my $verbose = GetVerbose($name);
+
+    if ( exists $hash->{UDPSOCKET} ) {
+        my $phash = $hash->{HASH};
+        $name = $phash->{NAME};
+        Log3 $name, 5, "$name MOBILEALERTSGW: Data from UDP received";
+        my $srcpaddr = recv( $hash->{UDPSOCKET}, my $udpdata, 186, 0 );
+        MOBILEALERTSGW_DecodeUDP( $phash, $udpdata, $srcpaddr );
+        return;
+    }
+
+    if ( $hash->{SERVERSOCKET} ) {    # Accept and create a child
+        my $nhash = TcpServer_Accept( $hash, "MOBILEALERTSGW" );
+        return if ( !$nhash );
+        my $wt = AttrVal( $name, "alarmTimeout", undef );
+        $nhash->{ALARMTIMEOUT} = $wt if ($wt);
+        $nhash->{CD}->blocking(0);
+        return;
+    }
+
+    $MA_chash = $hash;
+    $MA_wname = $hash->{SNAME};
+    $MA_cname = $name;
+    $verbose  = GetVerbose($MA_wname);
+
+    #$FW_subdir = "";
+
+    my $c = $hash->{CD};
+
+    if ( !$reread ) {
+
+        # Data from HTTP Client
+        my $buf;
+        my $ret = sysread( $c, $buf, 1024 );
+
+        if ( !defined($ret) && $! == EWOULDBLOCK ) {
+            $hash->{wantWrite} = 1
+              if ( TcpServer_WantWrite($hash) );
+            return;
+        }
+        elsif ( !$ret ) {    # 0==EOF, undef=error
+            CommandDelete( undef, $name );
+            Log3 $MA_wname, 4,
+              "$MA_wname MOBILEALERTSGW: Connection closed for $name: "
+              . ( defined($ret) ? 'EOF' : $! );
+            return;
+        }
+        $hash->{BUF} .= $buf;
+    }
+
+    if ( !$hash->{HDR} ) {
+        return if ( $hash->{BUF} !~ m/^(.*?)(\n\n|\r\n\r\n)(.*)$/s );
+        $hash->{HDR} = $1;
+        $hash->{BUF} = $3;
+        if ( $hash->{HDR} =~ m/Content-Length:\s*([^\r\n]*)/si ) {
+            $hash->{CONTENT_LENGTH} = $1;
+        }
+    }
+
+    @MA_httpheader = split( /[\r\n]+/, $hash->{HDR} );
+    %MA_httpheader = map {
+        my ( $k, $v ) = split( /: */, $_, 2 );
+        $k =~ s/(\w+)/\u$1/g;    # Forum #39203
+        $k => ( defined($v) ? $v : 1 );
+    } @MA_httpheader;
+
+    my $POSTdata = "";
+    if ( $hash->{CONTENT_LENGTH} ) {
+        return if ( length( $hash->{BUF} ) < $hash->{CONTENT_LENGTH} );
+        $POSTdata = substr( $hash->{BUF}, 0, $hash->{CONTENT_LENGTH} );
+        $hash->{BUF} = substr( $hash->{BUF}, $hash->{CONTENT_LENGTH} );
+    }
+    delete( $hash->{HDR} );
+    if ( $verbose >= 5 ) {
+        Log3 $MA_wname, 5,
+          "$MA_wname MOBILEALERTSGW: Headers: " . join( ", ", @MA_httpheader );
+        Log3 $MA_wname, 5, "$MA_wname MOBILEALERTSGW: Receivebuffer: "
+          . unpack( "H*", $POSTdata );
+    }
+
+    my ( $method, $url, $httpvers ) = split( " ", $MA_httpheader[0], 3 )
+      if ( $MA_httpheader[0] );
+    $method = "" if ( !$method );
+
+    #if($method !~ m/^(GET|POST)$/i){
+    if ( $method !~ m/^(PUT|POST)$/i ) {
+        TcpServer_WriteBlocking( $MA_chash,
+                "HTTP/1.1 405 Method Not Allowed\r\n"
+              . "Content-Length: 0\r\n\r\n" );
+        delete $hash->{CONTENT_LENGTH};
+        MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} );
+        Log3 $MA_wname, 3,
+"$MA_wname MOBILEALERTSGW: $MA_cname: unsupported HTTP method $method, rejecting it.";
+        MOBILEALERTSGW_closeConn($hash);
+        return;
+    }
+
+    if ( $url !~ m/.*\/gateway\/put$/i ) {
+        TcpServer_WriteBlocking( $MA_chash,
+            "HTTP/1.1 400 Bad Request\r\n" . "Content-Length: 0\r\n\r\n" );
+        delete $hash->{CONTENT_LENGTH};
+        MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} );
+        Log3 $MA_wname, 3,
+"$MA_wname MOBILEALERTSGW: $MA_cname: unsupported URL $url, rejecting it.";
+        MOBILEALERTSGW_closeConn($hash);
+        return;
+    }
+    if ( !exists $MA_httpheader{"HTTP_IDENTIFY"} ) {
+        TcpServer_WriteBlocking( $MA_chash,
+            "HTTP/1.1 400 Bad Request\r\n" . "Content-Length: 0\r\n\r\n" );
+        delete $hash->{CONTENT_LENGTH};
+        MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} );
+        Log3 $MA_wname, 3,
+"$MA_wname MOBILEALERTSGW: $MA_cname: not Header http_identify, rejecting it.";
+        MOBILEALERTSGW_closeConn($hash);
+        return;
+    }
+    Log3 $MA_wname, 5, "Header HTTP_IDENTIFY" . $MA_httpheader{"HTTP_IDENTIFY"};
+    my ( $gwserial, $gwmac, $actioncode ) =
+      split( /:/, $MA_httpheader{"HTTP_IDENTIFY"} );
+    my @gateways = split( ",", ReadingsVal( $MA_wname, "Gateways", "" ) );
+    readingsBeginUpdate( $defs{$MA_wname} );
+    if ( !grep( /^$gwmac$/, @gateways ) ) {
+        push( @gateways, $gwmac );
+        readingsBulkUpdate( $defs{$MA_wname}, "Gateways",
+            join( ",", @gateways ) );
+    }
+    readingsBulkUpdate( $defs{$MA_wname}, "GW_" . $gwmac . "_lastSeen",
+        TimeNow() );
+    readingsBulkUpdateIfChanged( $defs{$MA_wname}, "GW_" . $gwmac . "_ip",
+        $hash->{PEER} );
+    readingsBulkUpdateIfChanged( $defs{$MA_wname}, "GW_" . $gwmac . "_serial",
+        $gwserial );
+    readingsEndUpdate( $defs{$MA_wname}, 1 );
+    if ( $actioncode eq "00" ) {
+        Log3 $MA_wname, 4,
+"$MA_wname MOBILEALERTSGW: $MA_cname: Initrequest from $gwserial $gwmac";
+        MOBILEALERTSGW_DecodeInit( $hash, $POSTdata );
+        MOBILEALERTSGW_DefaultAnswer($hash);
+    }
+    elsif ( $actioncode eq "C0" ) {
+        Log3 $MA_wname, 4,
+          "$MA_wname MOBILEALERTSGW: $MA_cname: Data from $gwserial $gwmac";
+        MOBILEALERTSGW_DecodeData( $hash, $POSTdata );
+        MOBILEALERTSGW_DefaultAnswer($hash);
+    }
+    else {
+        TcpServer_WriteBlocking( $MA_chash,
+            "HTTP/1.1 400 Bad Request\r\n" . "Content-Length: 0\r\n\r\n" );
+        delete $hash->{CONTENT_LENGTH};
+        MOBILEALERTSGW_Read( $hash, 1 ) if ( $hash->{BUF} );
+        Log3 $MA_wname, 3,
+          "$MA_wname MOBILEALERTSGW: $MA_cname: unknown Actioncode $actioncode";
+        Log3 $MA_wname, 4,
+"$MA_wname MOBILEALERTSGW: $MA_cname: unknown Actioncode $actioncode Postdata: "
+          . unpack( "H*", $POSTdata );
+        MOBILEALERTSGW_closeConn($hash);
+        return;
+    }
+    MOBILEALERTSGW_closeConn($hash);    #No Keep-Alive
+
+    #Send to Server
+    if ( AttrVal( $MA_wname, "forward", 0 ) == 1 ) {
+        my $httpparam = {
+            url         => "http://www.data199.com/gateway/put",
+            timeout     => 20,
+            httpversion => "1.1",
+            hash        => $hash,
+            method      => "PUT",
+            header      => "HTTP_IDENTIFY: "
+              . $MA_httpheader{"HTTP_IDENTIFY"}
+              . "\r\nContent-Type: application/octet-stream",
+            data     => $POSTdata,
+            callback => \&MOBILEALERTSGW_NonblockingGet_Callback
+        };
+        HttpUtils_NonblockingGet($httpparam);
+    }
+    return;
+}
+
+sub MOBILEALERTSGW_NonblockingGet_Callback($$$) {
+    my ( $param, $err, $data ) = @_;
+    my $hash = $param->{hash};
+    my $name = $hash->{NAME};
+    my $code = $param->{code};
+    Log3 $name, 4, "$name MOBILEALERTSGW: Callback";
+    if ( $err ne "" ) {
+        Log3 $name, 3,
+            "$name MOBILEALERTSGW: error while forward request to "
+          . $param->{url}
+          . " - $err";
+    }
+    elsif ( $code != 200 ) {
+        Log3 $name, 3,
+            "$name MOBILEALERTSGW: http-error while forward request to "
+          . $param->{url} . " - "
+          . $param->{code};
+        Log3 $name, 5,
+          "$name MOBILEALERTSGW: http-header: " . $param->{httpheader};
+        Log3 $name, 5, "$name MOBILEALERTSGW: http-data: " . $data;
+    }
+    else {
+        Log3 $name, 5, "$name MOBILEALERTSGW: forward successfull";
+        Log3 $name, 5,
+          "$name MOBILEALERTSGW: http-header: " . $param->{httpheader};
+        Log3 $name, 5,
+          "$name MOBILEALERTSGW: http-data: " . unpack( "H*", $data );
+    }
+    HttpUtils_Close($param);
+}
+
+sub MOBILEALERTSGW_closeConn($) {
+    my ($hash) = @_;
+
+    # Kein Keep-Alive noetig
+    TcpServer_Close( $hash, 1 );
+}
+
+sub MOBILEALERTSGW_DefaultAnswer($) {
+    my ($hash) = @_;
+    my $buf;
+
+    $buf = pack( "NxxxxNxxxxNN", 420, time, 0x1761D480, 15 );
+
+    TcpServer_WriteBlocking( $MA_chash,
+            "HTTP/1.1 200 OK\r\n"
+          . "Content-Type: application/octet-stream\r\n"
+          . "Content-Length: 24\r\n\r\n"
+          . $buf );
+}
+
+sub MOBILEALERTSGW_DecodeInit($$) {
+    my ( $hash, $POSTdata ) = @_;
+    my ( $packageLength, $upTime, $ID, $unknown1, $unknown50 ) =
+      unpack( "CNH12nn", $POSTdata );
+
+    Log3 $MA_wname, 4,
+      "$MA_wname MOBILEALERTSGW: Uptime (s): " . $upTime . " ID: " . $ID;
+}
+
+sub MOBILEALERTSGW_DecodeData($$) {
+    my ( $hash, $POSTdata ) = @_;
+    my $verbose = GetVerbose($MA_wname);
+
+    for ( my $pos = 0 ; $pos < length($POSTdata) ; $pos += MA_PACKAGE_LENGTH ) {
+        my $data = substr $POSTdata, $pos, MA_PACKAGE_LENGTH;
+        my ( $packageHeader, $timeStamp, $packageLength, $deviceID ) =
+          unpack( "CNCH12", $data );
+        Log3 $MA_wname, 4,
+            "$MA_wname MOBILEALERTSGW: PackageHeader: "
+          . $packageHeader
+          . " Timestamp: "
+          . scalar( FmtDateTimeRFC1123($timeStamp) )
+          . " PackageLength: "
+          . $packageLength
+          . " DeviceID: "
+          . $deviceID
+          if ( $verbose >= 4 );
+        Log3 $MA_wname, 5,
+          "$MA_wname MOBILEALERTSGW: Data for $deviceID: "
+          . unpack( "H*", $data )
+          if ( $verbose >= 5 );
+        my $found = Dispatch( $defs{$MA_wname}, $data, undef );
+    }
+}
+
+sub MOBILEALERTSGW_DecodeUDP($$$) {
+    my ( $hash, $udpdata, $srcpaddr ) = @_;
+    my ( $port, $ipaddr ) = sockaddr_in($srcpaddr);
+    my $name = $hash->{NAME};
+    Log3 $name, 4,
+      "$name MOBILEALERTSGW: Data from " . inet_ntoa($ipaddr) . ":" . $port;
+    Log3 $name, 5, "$name MOBILEALERTSGW: Data: " . unpack( "H*", $udpdata );
+
+    if ( length $udpdata == 186 ) {
+        my @ip;
+        my @fip;
+        my @netmask;
+        my @gateway;
+        my @dnsip;
+        (
+            my $command,
+            my $gatewayid,
+            my $length,
+            @ip[ 0 .. 3 ],
+            my $dhcp,
+            @fip[ 0 .. 3 ],
+            @netmask[ 0 .. 3 ],
+            @gateway[ 0 .. 3 ],
+            my $devicename,
+            my $dataserver,
+            my $proxy,
+            my $proxyname,
+            my $proxyport,
+            @dnsip[ 0 .. 3 ]
+        ) = unpack( "nH12nxCCCCCCCCCCCCCCCCCa21a65Ca65nCCCC", $udpdata );
+
+        if ( $command != 3 ) {
+            Log3 $name, 3,
+              "$name MOBILEALERTSGW: Unknown Command $command: "
+              . unpack( "H*", $udpdata );
+            return;
+        }
+        Log3 $name, 4,
+            "$name MOBILEALERTSGW: Command: "
+          . $command
+          . " Gatewayid: "
+          . $gatewayid
+          . " length: "
+          . $length . " IP: "
+          . join( ".", @ip )
+          . " DHCP: "
+          . $dhcp
+          . " fixedIP: "
+          . join( ".", @fip )
+          . " netmask: "
+          . join( ".", @netmask )
+          . " gateway: "
+          . join( ".", @gateway )
+          . " devicename: "
+          . $devicename
+          . " dataserver: "
+          . $dataserver
+          . " proxy: "
+          . $proxy
+          . " proxyname: "
+          . $proxyname
+          . " proxyport: "
+          . $proxyport
+          . " dnsip: "
+          . join( ".", @dnsip );
+        $gatewayid = uc $gatewayid;
+        readingsBeginUpdate($hash);
+        readingsBulkUpdate( $hash, "GW_" . $gatewayid . "_lastSeen",
+            TimeNow() );
+        readingsBulkUpdateIfChanged( $hash, "GW_" . $gatewayid . "_ip",
+            inet_ntoa($ipaddr) );
+        readingsBulkUpdateIfChanged(
+            $hash,
+            "GW_" . $gatewayid . "_config",
+            unpack( "H*", $udpdata )
+        );
+        readingsBulkUpdateIfChanged(
+            $hash,
+            "GW_" . $gatewayid . "_proxy",
+            $proxy == 1 ? "on" : "off"
+        );
+        readingsBulkUpdateIfChanged( $hash, "GW_" . $gatewayid . "_proxyname",
+            $proxyname );
+        readingsBulkUpdateIfChanged( $hash, "GW_" . $gatewayid . "_proxyport",
+            $proxyport );
+        my @gateways = split( ",", ReadingsVal( $name, "Gateways", "" ) );
+
+        if ( !grep( /^$gatewayid$/, @gateways ) ) {
+            push( @gateways, $gatewayid );
+            readingsBulkUpdate( $hash, "Gateways", join( ",", @gateways ) );
+        }
+        readingsEndUpdate( $hash, 1 );
+    }
+    elsif ( length $udpdata == 118 ) {
+        Log3 $name, 5,
+          "$name MOBILEALERTSGW: Package was defect, this seems to be normal.";
+    }
+    else {
+        Log3 $name, 3,
+          "$name MOBILEALERTSGW: Unknown Data: " . unpack( "H*", $udpdata );
+    }
+    return;
+}
+
+# Eval-Rückgabewert für erfolgreiches
+# Laden des Moduls
+1;
+
+=pod
+=item device
+=item summary    IO device for german MobileAlerts
+=item summary_DE IO device für deutsche MobileAlets
+=begin html
+
+<a name="MOBILEALERTSGW"></a>
+<h3>MOBILEALERTSGW</h3>
+<ul>
+  The MOBILEALERTSGW is a fhem module for the german MobileAlerts Gateway and TFA WEATHERHUB.
+  <br><br>
+  The fhem module makes simulates as http-proxy to intercept messages from the gateway.
+  In order to use this module you need to configure the gateway to use the fhem-server with the defined port as proxy.
+  You can do so with the command initgateway or by app.
+  It automatically detects devices. The other devices are handled by the <a href="#MOBILEALERTS">MOBILELAERTS</a> module,
+  which uses this module as its backend.<br>
+  <br>
+
+  <a name="MOBILEALERTSGWdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; MOBILEALERTSGW &lt;port&gt;</code><br>
+    <br>
+    port is the port where the proxy server listens. The port must be free.
+  </ul>
+  <br>
+
+  <a name="MOBILEALERTSGWreadings"></a>
+  <b>Readings</b>
+  <ul>
+    <li>Gateways<br>List of known gateways</li>
+    <li>GW_&lt;Gateway-MAC&gt;_lastSeen<br>Time when last message was received from gateway</li>
+    <li>GW_&lt;Gateway-MAC&gt;_ip<br>IP-Adresse of gateway</li>
+    <li>GW_&lt;Gateway-MAC&gt;_serial<br>Serialnumber of gateway</li>
+    <li>GW_&lt;Gateway-MAC&gt;_proxy<br>on, off: setting of proxy (only after get config)</li>
+    <li>GW_&lt;Gateway-MAC&gt;_proxyname<br>Name/IP of proxy (only after get config)</li>
+    <li>GW_&lt;Gateway-MAC&gt;_proxyport<br>Port of proxy (only after get config)</li>
+    <li>GW_&lt;Gateway-MAC&gt;_config<br>Complete configuration as hex-values (only after get config)</li>
+  </ul>
+  <br>     
+
+  <a name="MOBILEALERTSGWset"></a>
+  <b>Set</b>
+  <ul>
+    <li><code>set &lt;name&gt; clear &lt;readings&gt;</code><br>
+    Clears the readings. </li>
+    <li><code>set &lt;name&gt; initgateway &lt;gatewayid&gt;</code><br>
+    Sets the proxy in the gateway to the fhem-server. A reboot of the gateway may be needed in order to take effect.</li>    
+    <li><code>set &lt;name&gt; rebootgateway &lt;gatewayid&gt;</code><br>
+    Reboots the gateway.</li>        
+  </ul>
+  <br>
+
+  <a name="MOBILEALERTSGWget"></a>
+  <b>Get</b>
+  <ul>
+    <li><code>get &lt;name&gt; config &lt;IP or gatewayid&gt; </code><br>
+    Gets the config of a gateway or all gateways. IP or gatewayid are optional. 
+    If not specified it will search for alle Gateways in the local lan (Broadcast).</li>
+  </ul>
+  <br>
+  <br>
+
+  <a name="MOBILEALERTSGWattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li>forward<br>
+      If value 1 is set, the data will be forwarded to the MobileAlerts Server http://www.data199.com/gateway/put .
+    </li>
+  </ul>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="MOBILEALERTSGW"></a>
+<h3>MOBILEALERTSGW</h3>
+<ul>
+  MOBILEALERTSGW ist ein FHEM-Modul f&uuml;r das deutsche MobileAlerts Gateway und TFA WEATHERHUB.
+  <br><br>
+  Dieses FHEM-Modul simuliert einen http-proxy, um Nachrichten vom Gateway abzufangen.
+  Um dies zu erreichen, muss das Gateway so konfiguriert werden, dass es den FHEM-Server mit dem definierten Port als
+  Proxy nutzt. Sie k&ouml;nnen dies entweder mit der App oder dem Kommando initgateway erreichen.
+  Es erkennt automatisch Ger&auml;te. Die Ger&auml; werden durch das <a href="#MOBILEALERTS">MOBILELAERTS</a> Modul
+  bereitgestellt. MOBILEALERTS nutzt dieses Modul als Backend.<br>
+  <br>
+
+  <a name="MOBILEALERTSGWdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; MOBILEALERTSGW &lt;port&gt;</code><br>
+    <br>
+    port ist der Port auf dem der Proxy-Server h&ouml;rt. Der Port muss frei sein.
+  </ul>
+  <br>
+
+  <a name="MOBILEALERTSGWreadings"></a>
+  <b>Readings</b>
+  <ul>
+    <li>Gateways<br>Liste der bekannten Gateways</li>
+    <li>GW_&lt;Gateway-MAC&gt;_lastSeen<br>Zeitpunkt when zuletzt eine Nachricht empfangen wurde</li>
+    <li>GW_&lt;Gateway-MAC&gt;_ip<br>IP-Adresse des Gateways</li>
+    <li>GW_&lt;Gateway-MAC&gt;_serial<br>Seriennummer des Gateways</li>
+    <li>GW_&lt;Gateway-MAC&gt;_proxy<br>on, off: Einstellung des Proxies (nur verf&uuml;nach einem get config)</li>
+    <li>GW_&lt;Gateway-MAC&gt;_proxyname<br>Name/IP der Proxy (nur verf&uuml;nach einem get config)</li>
+    <li>GW_&lt;Gateway-MAC&gt;_proxyport<br>Port der Proxy (nur verf&uuml;nach einem get config)</li>
+    <li>GW_&lt;Gateway-MAC&gt;_config<br>Komplette Konfiguration als HEX-Wert (nur verf&uuml;nach einem get config)</li>
+  </ul>
+  <br>   
+
+  <a name="MOBILEALERTSGWset"></a>
+  <b>Set</b>
+  <ul>
+    <li><code>set &lt;name&gt; clear &lt;readings&gt;</code><br>
+    L&ouml;scht die Readings. </li>
+    <li><code>set &lt;name&gt; initgateway &lt;gatewayid&gt;</code><br>
+    Setzt den Proxy im Gateway auf dem FHEM-Server. Es kann ein Neustart (reboot) des Gateways n&ouml;tig sein, damit die
+    Einstellung wirksam wird.</li>    
+    <li><code>set &lt;name&gt; rebootgateway &lt;gatewayid&gt;</code><br>
+    Startet das Gateway neu.</li>        
+  </ul>
+  <br>
+
+  <a name="MOBILEALERTSGWget"></a>
+  <b>Get</b>
+  <ul>
+    <li><code>get &lt;name&gt; config &lt;IP or gatewayid&gt; </code><br>
+    Holt die Konfiguration eines oder aller Gateways im lokalen Netz. IP bzw. die GatewayId sind optional. 
+    Wenn keines von beiden angegeben ist, werden alle Gateways im lokalen Netz gesucht (Broadcast).</li>
+  </ul>
+  <br>
+  <br>
+
+  <a name="MOBILEALERTSGWattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li>forward<br>
+      Wenn dieser Wert auf 1 gesetzt ist, werden die Daten zus&auml;tzlich zum MobileAlerts Server http://www.data199.com/gateway/put gesendet.
+    </li>
+  </ul>
+</ul>
+
+=end html_DE
+=cut

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1187 - 0
fhem/core/FHEM/51_MOBILEALERTS.pm


+ 473 - 0
fhem/core/FHEM/52_I2C_EMC1001.pm

@@ -0,0 +1,473 @@
+##############################################
+#
+# Modul for reading a EMC1001 digital temperature sensor via I2C
+# (see http://ww1.microchip.com/downloads/en/DeviceDoc/20005411A.pdf)
+#
+# Copyright (C) 2018 Stephan Eisler
+#
+#     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/>.
+#
+# $Id: 52_I2C_EMC1001.pm 16082 2018-02-04 14:49:06Z eisler $
+#
+##############################################
+
+package main;
+
+use strict;
+use warnings;
+
+use constant {
+	EMC1001_I2C_ADDRESS 	=> 0x48,    # EMC1001 I2C ADDRESS
+	Reg_TMP_HB		=> 0x00,    # R temperature value high byte
+	Reg_STATUS    		=> 0x01,    # RC Status
+	Reg_TMP_LB    		=> 0x02,    # R low byte containing 1/4 deg fraction
+	Reg_Config    		=> 0x03,    # R/W Configuration
+	Reg_Cnv_Rate 		=> 0x04,    # R/W Conversion Rate
+	Reg_THL_HB    		=> 0x05,    # R/W Temperature High Limit High Byte
+	Reg_THL_LB    		=> 0x06,    # R/W Temperature High Limit Low Byte
+	Reg_TLL_HB    		=> 0x07,    # R/W Temperature Low Limit High Byte
+	Reg_TLL_LB    		=> 0x08,    # R/W Temperature Low Limit Low Byte
+	Reg_One_Sht   		=> 0x0f,    # R One-Shot
+	Reg_THM_LMT   		=> 0x20,    # R/W THERM Limit
+	Reg_THM_HYS   		=> 0x21,    # R/W THERM Hysteresis
+	Reg_SMB_TO    		=> 0x22,    # R/W SMBus Timeout Enable
+	Reg_Prd_ID    		=> 0xfd,    # R Product ID Register
+	Reg_Mnf_ID    		=> 0xfe,    # R Manufacture ID
+	Reg_Rev_No    		=> 0xff     # R Revision Number
+};
+
+##################################################
+# Forward declarations
+#
+sub I2C_EMC1001_I2CRec ($$);
+sub I2C_EMC1001_GetReadings ($$);
+sub I2C_EMC1001_GetTemp ($@);
+sub I2C_EMC1001_calcTrueTemperature($$);
+
+
+my %sets = (
+	'readValues' => 1,
+);
+
+sub I2C_EMC1001_Initialize($) {
+	my ($hash) = @_;
+
+	$hash->{DefFn}    = 'I2C_EMC1001_Define';
+	$hash->{InitFn}   = 'I2C_EMC1001_Init';
+	$hash->{AttrFn}   = 'I2C_EMC1001_Attr';
+	$hash->{SetFn}    = 'I2C_EMC1001_Set';
+	#$hash->{GetFn}    = 'I2C_EMC1001_Get';
+	$hash->{UndefFn}  = 'I2C_EMC1001_Undef';
+	$hash->{I2CRecFn} = 'I2C_EMC1001_I2CRec';
+	$hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' .
+	          					'roundTemperatureDecimal:0,1,2 ' .
+											$readingFnAttributes;
+  $hash->{DbLog_splitFn} = "I2C_EMC1001_DbLog_splitFn";
+}
+
+sub I2C_EMC1001_Define($$) {
+	my ($hash, $def) = @_;
+	my @a = split('[ \t][ \t]*', $def);
+	$hash->{STATE} = 'defined';
+
+	my $name = $a[0];
+
+	my $msg = '';
+	if((@a < 2)) {
+		$msg = 'wrong syntax: define <name> I2C_EMC1001 [I2C-Address]';
+	}
+	if ($msg) {
+		Log3 ($hash, 1, $msg);
+		return $msg;
+	}
+	if ($main::init_done) {
+		eval { I2C_EMC1001_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
+		return I2C_EMC1001_Catch($@) if $@;
+	}
+}
+
+sub I2C_EMC1001_Init($$) {					# wird bei FHEM start Define oder wieder
+	my ( $hash, $args ) = @_;
+	my $name = $hash->{NAME};
+
+	if (defined (my $address = shift @$args)) {
+		$hash->{I2C_Address} = $address =~ /^0x.*$/ ? oct($address) : $address;
+		return "$name: I2C Address not valid" unless ($hash->{I2C_Address} < 128 && $hash->{I2C_Address} > 3);
+	} else {
+		$hash->{I2C_Address} = EMC1001_I2C_ADDRESS;
+	}
+	my $msg = '';
+	# create default attributes
+	#if (AttrVal($name, 'poll_interval', '?') eq '?') {
+    #	$msg = CommandAttr(undef, $name . ' poll_interval 5');
+    #	if ($msg) {
+    #  		Log3 ($hash, 1, $msg);
+    #  		return $msg;
+    #	}
+	#}
+	eval {
+				AssignIoPort($hash, AttrVal($hash->{NAME},"IODev",undef));
+				I2C_EMC1001_i2cread($hash, Reg_Prd_ID, 1);		#get Prd Id
+				I2C_EMC1001_i2cread($hash, Reg_Mnf_ID, 1);		#get Mnf Id
+				I2C_EMC1001_i2cread($hash, Reg_Rev_No, 1);		#get Reg Rev No
+			};
+    return I2C_EMC1001_Catch($@) if $@;
+}
+
+sub I2C_EMC1001_Catch($) {					# Fehlermeldungen formattieren
+	my $exception = shift;
+	if ($exception) {
+		$exception =~ /^(.*)( at.*FHEM.*)$/;
+		return $1;
+	}
+	return undef;
+}
+
+sub I2C_EMC1001_Attr (@) {					# Wird beim Attribut anlegen/aendern aufgerufen
+	my ($command, $name, $attr, $val) =  @_;
+	my $hash = $defs{$name};
+	my $msg = '';
+
+	if (defined $command && $command eq "set" && $attr eq "IODev") {
+		eval {
+			if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) {
+				main::AssignIoPort($hash,$val);
+				my @def = split (' ',$hash->{DEF});
+				I2C_EMC1001_Init($hash,\@def) if (defined ($hash->{IODev}));
+			}
+        };
+		$msg = I2C_EMC1001_Catch($@) if $@;
+	} elsif ($attr eq 'poll_interval') {
+		if (defined($val)) {
+			if ($val =~ m/^(0*[1-9][0-9]*)$/) {
+				RemoveInternalTimer($hash);
+				I2C_EMC1001_Poll($hash) if ($main::init_done);
+				#InternalTimer(gettimeofday() + 5, 'I2C_EMC1001_Poll', $hash, 0) if ($main::init_done);
+			} else {
+				$msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
+			}
+		} else {
+			RemoveInternalTimer($hash);
+		}
+	} elsif ($attr =~ m/^round(Temperature)Decimal$/ && defined($val)) {
+		$msg = 'Wrong value: $val for $attr defined. value must be a one of 0,1,2' unless ($val =~ m/^(0*[0-2])$/);
+	}
+	return ($msg) ? $msg : undef;
+}
+
+sub I2C_EMC1001_Poll($) {					# Messwerte regelmaessig anfordern
+	my ($hash) =  @_;
+	my $name = $hash->{NAME};
+
+	I2C_EMC1001_Set($hash, ($name, 'readValues'));						# Read values
+	my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
+	if ($pollInterval > 0) {
+		InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_EMC1001_Poll', $hash, 0);
+	}
+}
+
+sub I2C_EMC1001_Set($@) {					# Messwerte manuell anfordern
+	my ($hash, @a) = @_;
+
+	my $name = $a[0];
+	my $cmd =  $a[1];
+
+	if(!defined($sets{$cmd})) {
+		return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)  . ":noArg"
+	}
+
+	if ($cmd eq 'readValues') {
+			I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1);
+			I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1);
+			RemoveInternalTimer($hash);
+			InternalTimer(gettimeofday() + 1, 'I2C_EMC1001_UpdateReadings', $hash, 0);
+	}
+	return undef
+}
+
+sub I2C_EMC1001_Get($@) {					# Messwerte manuell anfordern
+	my ($hash, @a) = @_;
+	my $name = $a[0];
+	my $cmd =  $a[1];
+
+	if (defined($cmd) && $cmd eq 'readValues') {
+			I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1);
+			I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1);
+			RemoveInternalTimer($hash);
+			InternalTimer(gettimeofday() + 1, 'I2C_EMC1001_UpdateReadings', $hash, 0);
+	} else {
+		return 'Unknown argument ' . $cmd . ', choose one of readValues:noArg';
+	}
+	return undef
+}
+
+sub I2C_EMC1001_UpdateReadings($) {			# Messwerte auslesen
+	my ($hash) = @_;
+	I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1);
+	I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1);
+	my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);	#poll_interval Timer wiederherstellen
+	InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_EMC1001_Poll', $hash, 0) if ($pollInterval > 0);
+}
+
+sub I2C_EMC1001_Undef($$) {					# Device loeschen
+	my ($hash, $arg) = @_;
+	RemoveInternalTimer($hash);
+	return undef;
+}
+
+sub I2C_EMC1001_I2CRec ($$) {				# wird vom IODev aus aufgerufen wenn I2C Daten vorliegen
+	my ($hash, $clientmsg) = @_;
+	my $name = $hash->{NAME};
+	my $pname = undef;
+	my $phash = $hash->{IODev};
+	$pname = $phash->{NAME};
+	while ( my ( $k, $v ) = each %$clientmsg ) { 																#erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen
+		$hash->{$k} = $v if $k =~ /^$pname/ ;
+	}
+
+	if ( $clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) {
+		if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) {
+			Log3 $hash, 5, "$name Rx, Reg: $clientmsg->{reg}, Data: $clientmsg->{received}";
+			I2C_EMC1001_GetProdId ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Prd_ID && $clientmsg->{nbyte} == 1;
+			I2C_EMC1001_GetMnfId ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Mnf_ID && $clientmsg->{nbyte} == 1;
+			I2C_EMC1001_GetRevN ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Rev_No && $clientmsg->{nbyte} == 1;
+			I2C_EMC1001_GetReadingsTemperatureValueHighByte ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_TMP_HB && $clientmsg->{nbyte} == 1;
+			I2C_EMC1001_GetReadingsTemperatureValueLowByte ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_TMP_LB && $clientmsg->{nbyte} == 1;
+		}
+	}
+
+	return undef
+}
+
+sub I2C_EMC1001_GetProdId ($$) {
+	my ($hash, $rawdata) = @_;
+	if ($rawdata == hex("00")) {
+		$hash->{DeviceType} = "EMC1001";
+	} elsif ($rawdata == hex("01")) {
+		$hash->{DeviceType} = "EMC1001-1";
+	}
+	readingsSingleUpdate($hash, 'DeviceType', $hash->{DeviceType}, 1);
+	$hash->{STATE} = 'Initialized';
+	I2C_EMC1001_Poll($hash) if defined(AttrVal($hash->{NAME}, 'poll_interval', undef));			# wenn poll_interval definiert -> timer starten 
+}
+
+sub I2C_EMC1001_GetMnfId ($$) {
+	my ($hash, $rawdata) = @_;
+	readingsSingleUpdate($hash, 'DeviceManufactureId', sprintf("0x%X", $rawdata), 1);
+}
+
+sub I2C_EMC1001_GetRevN ($$) {
+	my ($hash, $rawdata) = @_;
+	readingsSingleUpdate($hash, 'DeviceRevisionNumber', sprintf("%d", $rawdata), 1);
+}
+
+sub I2C_EMC1001_GetReadingsTemperatureValueHighByte ($$) {			# empfangenes Temperature High Byte verarbeiten
+		my ($hash, $rawdata) = @_;
+		Log3 $hash, 5, "ReadingsTemperatureValueHighByte: $rawdata";
+		$hash->{TemperatureValueHighByte} = $rawdata;
+}
+
+sub I2C_EMC1001_GetReadingsTemperatureValueLowByte ($$) {			# empfangenes Temperature Low Byte verarbeiten
+		my ($hash, $rawdata) = @_;
+		Log3 $hash, 5, "ReadingsTemperatureValueLowByte: $rawdata";
+		$hash->{TemperatureValueLowByte} = $rawdata;
+		I2C_EMC1001_GetTemp($hash, $rawdata);
+
+		my $tem = ReadingsVal($hash->{NAME},"temperature", undef);
+		readingsSingleUpdate(
+			$hash,
+			'state',
+			(defined $tem ? "T: $tem " : ""),
+			1
+		);
+}
+
+sub I2C_EMC1001_GetTemp($@) {				# Temperatur Messwerte verarbeiten
+	my ($hash, @raw) = @_;
+
+	my $temp= $hash->{TemperatureValueHighByte};
+	my $templo= $hash->{TemperatureValueLowByte};
+
+	$templo = $templo >> 6;
+
+	if ($temp < 0) {
+	        $templo = 3-$templo;
+	}
+		my $temperature = sprintf(
+			'%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f',
+			sprintf("%d.%d", $temp, $templo*25)
+		);
+		readingsSingleUpdate($hash, 'temperature', $temperature, 1);
+}
+
+sub I2C_EMC1001_i2cread($$$) {				# Lesebefehl an Hardware absetzen (antwort kommt in I2C_*****_I2CRec an)
+	my ($hash, $reg, $nbyte) = @_;
+	if (defined (my $iodev = $hash->{IODev})) {
+		Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} read $nbyte Byte from Register $reg";
+		CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
+		direction  => "i2cread",
+		i2caddress => $hash->{I2C_Address},
+		reg => 				$reg,
+		nbyte => 			$nbyte
+		});
+	} else {
+		return "no IODev assigned to '$hash->{NAME}'";
+	}
+}
+
+sub I2C_EMC1001_i2cwrite($$$) {				# Schreibbefehl an Hardware absetzen
+	my ($hash, $reg, @data) = @_;
+	if (defined (my $iodev = $hash->{IODev})) {
+		Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} write " . join (' ',@data) . " to Register $reg";
+		CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
+		direction  => "i2cwrite",
+		i2caddress => $hash->{I2C_Address},
+		reg => $reg,
+		data => join (' ',@data),
+		});
+	} else {
+		return "no IODev assigned to '$hash->{NAME}'";
+	}
+}
+
+sub I2C_EMC1001_DbLog_splitFn($) {  			# Einheiten
+    my ($event) = @_;
+    Log3 undef, 5, "in DbLog_splitFn empfangen: $event";
+    my ($reading, $value, $unit) = "";
+
+    my @parts = split(/ /,$event);
+    $reading = shift @parts;
+    $reading =~ tr/://d;
+    $value = $parts[0];
+    $unit = "\xB0C" if(lc($reading) =~ m/temp/);
+    return ($reading, $value, $unit);
+}
+
+1;
+
+=pod
+=item device
+=item summary reads temperature from an via I2C connected EMC1001
+=item summary_DE lese Temperatur eines &uuml;ber I2C angeschlossenen EMC1001
+=begin html
+
+<a name="I2C_EMC1001"></a>
+<h3>I2C_EMC1001</h3>
+(en | <a href="commandref_DE.html#I2C_EMC1001">de</a>)
+<ul>
+  <a name="I2C_EMC1001"></a>
+    Provides an interface to the digital temperature sensor EMC1001
+    The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
+	or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
+	<b>attribute IODev must be set</b><br>
+  <b>Define</b>
+  <ul>
+    <code>define EMC1001 I2C_EMC1001 [&lt;I2C Address&gt;]</code><br><br>
+    without defined <code>&lt;I2C Address&gt;</code> 0x48 will be used as address<br>
+    <br>
+    Examples:
+    <pre>
+      define EMC1001 I2C_EMC1001 0x48
+      attr EMC1001 poll_interval 5
+			attr roundTemperatureDecimal 2
+    </pre>
+  </ul>
+
+  <a name="I2C_EMC1001set"></a>
+  <b>Set</b>
+  <ul>
+    <code>set EMC1001 &lt;readValues&gt;</code>
+    <br><br>
+    Reads current temperature values from the sensor.<br>
+    Normaly this execute automaticly at each poll intervall. You can execute
+    this manually if you want query the current values.
+    <br><br>
+  </ul>
+
+	<a name="I2C_EMC1001attr"></a>
+	<b>Attributes</b>
+	<ul>
+		<li>poll_interval<br>
+			Set the polling interval in minutes to query the sensor for new measured
+			values.<br>
+			Default: 5, valid values: any whole number<br><br>
+		</li>
+		<li>roundTemperatureDecimal<br>
+			Round temperature values to given decimal places.<br>
+			Default: 1, valid values: 0, 1, 2<br><br>
+		</li>
+		<li><a href="#IODev">IODev</a></li>
+		<li><a href="#do_not_notify">do_not_notify</a></li>
+		<li><a href="#showtime">showtime</a></li>
+	</ul><br>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="I2C_EMC1001"></a>
+<h3>I2C_EMC1001</h3>
+(<a href="commandref.html#I2C_EMC1001">en</a> | de)
+<ul>
+  <a name="I2C_EMC1001"></a>
+    Erm&ouml;glicht die Verwendung eines digitalen Temperatur EMC1001 &uuml;ber den I2C Bus des Raspberry Pi.<br><br>
+	I2C-Botschaften werden &uuml;ber ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
+	oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
+	<b>Das Attribut IODev muss definiert sein.</b><br>
+
+	<b>Define</b>
+	<ul>
+		<code>define EMC1001 &lt;EMC1001_name&gt; [&lt;I2C Addresse&gt;]</code><br><br>
+		Fehlt <code>&lt;I2C Address&gt;</code> wird 0x48 verwendet<br>
+		<br>
+		Beispiel:
+		<pre>
+			define EMC1001 I2C_EMC1001 0x48
+			attr EMC1001 poll_interval 5
+			attr roundTemperatureDecimal 2
+		</pre>
+	</ul>
+
+	<a name="I2C_EMC1001set"></a>
+	<b>Set</b>
+	<ul>
+		<code>set EMC1001 readValues</code>
+		<br><br>
+		<code>set &lt;name&gt; readValues</code><br>
+		Aktuelle Temperatur Werte vom Sensor lesen.<br><br>
+	</ul>
+
+	<a name="I2C_EMC1001attr"></a>
+	<b>Attribute</b>
+		<ul>
+		<li>poll_interval<br>
+			Definiert das Poll Intervall in Minuten f&uuml;r das Auslesen einer neuen Messung.<br>
+			Default: 5, g&uuml;ltige Werte: 1, 2, 5, 10, 20, 30<br><br>
+		</li>
+		<li>roundTemperatureDecimal<br>
+			Rundet jeweils den Temperaturwert mit den angegebenen Nachkommastellen.<br>
+			Standard: 1, g&uuml;ltige Werte: 0, 1, 2<br><br>
+		</li>
+		<li><a href="#IODev">IODev</a></li>
+		<li><a href="#do_not_notify">do_not_notify</a></li>
+		<li><a href="#showtime">showtime</a></li>
+		</ul><br>
+</ul>
+
+=end html_DE
+=cut
+

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2976 - 0
fhem/core/FHEM/72_XiaomiDevice.pm


+ 669 - 0
fhem/core/FHEM/74_UnifiVideo.pm

@@ -0,0 +1,669 @@
+# $Id: 74_UnifiVideo.pm 16158 2018-02-12 15:39:47Z justme1968 $
+
+package main;
+
+use strict;
+use warnings;
+
+use JSON;
+use Data::Dumper;
+
+use HttpUtils;
+
+use vars qw(%modules);
+use vars qw(%defs);
+use vars qw(%attr);
+use vars qw($readingFnAttributes);
+sub Log($$);
+sub Log3($$$);
+
+sub
+UnifiVideo_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{ReadFn}   = "UnifiVideo_Read";
+
+  $hash->{DefFn}    = "UnifiVideo_Define";
+  $hash->{NotifyFn} = "UnifiVideo_Notify";
+  $hash->{UndefFn}  = "UnifiVideo_Undefine";
+  $hash->{SetFn}    = "UnifiVideo_Set";
+  $hash->{GetFn}    = "UnifiVideo_Get";
+  $hash->{AttrFn}   = "UnifiVideo_Attr";
+  $hash->{AttrList} = "disable filePath apiKey ".
+                      "sshUser ".
+                      $readingFnAttributes;
+
+  $hash->{FW_detailFn}  = "UnifiVideo_detailFn";
+}
+
+#####################################
+
+
+sub
+UnifiVideo_Define($$)
+{
+  my ($hash, $def) = @_;
+
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "Usage: define <name> UnifiVideo <ip> [<apiKey>]"  if(@a < 3);
+
+  my $name = $a[0];
+  my $host = $a[2];
+  $hash->{NAME} = $name;
+
+  my $d = $modules{$hash->{TYPE}}{defptr};
+  return "$hash->{TYPE} device already defined as $d->{NAME}." if( defined($d) && $name ne $d->{NAME} );
+  $modules{$hash->{TYPE}}{defptr} = $hash;
+
+  $hash->{NOTIFYDEV} = "global";
+
+  $hash->{HOST} = $host;
+  $hash->{DEF} = $host;
+
+  $hash->{STATE} = 'active';
+
+  CommandAttr(undef,"$name apiKey $a[3]" ) if( defined($a[3]) );
+
+  if( $init_done ) {
+    UnifiVideo_Connect($hash);
+  } else {
+    readingsSingleUpdate($hash, 'state', 'initialized', 1 );
+  }
+
+  return undef;
+}
+
+sub
+UnifiVideo_Notify($$)
+{
+  my ($hash,$dev) = @_;
+
+  return if($dev->{NAME} ne "global");
+  return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
+
+  UnifiVideo_Connect($hash);
+
+  return undef;
+}
+
+sub
+UnifiVideo_Undefine($$)
+{
+  my ($hash, $arg) = @_;
+
+  UnifiVideo_killLogWatcher($hash);
+  RemoveInternalTimer($hash, "UnifiVideo_Connect");
+
+  delete $modules{$hash->{TYPE}}{defptr};
+
+  return undef;
+}
+
+sub
+UnifiVideo_detailFn()
+{
+  my ($FW_wname, $d, $room, $extPage) = @_; # extPage is set for summaryFn.
+  my $hash = $defs{$d};
+
+  return UnifiVideo_2html($hash);
+}
+
+sub
+UnifiVideo_2html($;$$)
+{
+  my ($hash,$cams,$width) = @_;
+  $hash = $defs{$hash} if( ref($hash) ne 'HASH' );
+  return undef if( !defined($hash) );
+  $width = 200 if( !$width );
+  my $name = $hash->{NAME};
+
+  my @cams = split(',', $cams) if( defined($cams) );
+
+  my $apiKey = AttrVal($name, 'apiKey', undef);
+  return undef if( !$apiKey );
+  $apiKey = UnifiVideo_decrypt( $apiKey );
+
+  my $json = $hash->{helper}{json};
+  return undef if( !$json );
+
+  my $javascriptText = "<script type='text/javascript'>
+    function loadImages() {
+      var tags = document.getElementsByClassName('unifiSnap');
+      for(var i = 0;i < tags.length; i++) {
+          var img = tags[i];
+        var nvrIp = img.getAttribute('nvrIp');
+        var cameraId = img.getAttribute('cameraId');
+        var apiKey = img.getAttribute('apiKey');
+        var width = img.width;
+        tags[i].src='http://'+ nvrIp +':7080/api/2.0/snapshot/camera/'+cameraId+'?force=true&width='+width+'&apiKey='+apiKey+'&'+Date.now();
+      }
+
+      setTimeout( function() {loadImages()}, 1000 );
+    }
+  </script>";
+  $javascriptText =~ s/\n/ /g;
+  $javascriptText =~ s/ +/ /g;
+  my $html = "$javascriptText<div onload='loadImages()'>";
+  $html .= "\n" if( $html );
+  $html .= '<iframe style="display:none" onload="loadImages()"></iframe>';
+  my $i = 0;
+  foreach my $entry (@{$json->{data}}) {
+    next if( $entry->{deleted} );
+    next if( $entry->{state} eq 'DISCONNECTED' );
+    if( defined($cams) ) {
+      foreach my $cam (@cams) {
+        if( ( $cam =~ m/[0-9]+/ && int($cam) == $i )
+            || $entry->{_id} eq $cam
+            || $entry->{name} =~ m/$cam/ ) {
+          $html .= "\n" if( $html );
+          $html .= "  <img width='$width' class='unifiSnap' nvrIp='$hash->{HOST}' apiKey='$apiKey' cameraId='$entry->{_id}'>";
+        }
+      }
+    } else {
+      $html .= "\n" if( $html );
+      $html .= "  <img width='200' class='unifiSnap' nvrIp='$hash->{HOST}' apiKey='$apiKey' cameraId='$entry->{_id}'>";
+    }
+
+    ++$i;
+  }
+  $html .= "\n" if( $html );
+  $html .= "</div>";
+
+#Log 1, $html;
+  return $html;
+}
+
+sub
+UnifiVideo_Set($$@)
+{
+  my ($hash, $name, $cmd, @args) = @_;
+
+  my $list = "reconnect:noArg snapshot apiKey";
+
+  if( $cmd eq 'reconnect' ) {
+    $hash->{".triggerUsed"} = 1;
+
+    UnifiVideo_Connect($hash);
+
+    return undef;
+
+  } elsif( $cmd eq 'snapshot' ) {
+    my $json = $hash->{helper}{json};
+    return "not jet connected" if( !$json );
+
+    my ($param_a, $param_h) = parseParams(\@args);
+    my $cam = $param_h->{cam};
+    my $width = $param_h->{width};
+    return "usage: snapshot cam=<cam> [width=<width>] [fileName=<fileName>]" if( !defined($cam) );
+    my $i = 0;
+    foreach my $entry (@{$json->{data}}) {
+      next if( $entry->{deleted} );
+      next if( $entry->{state} eq 'DISCONNECTED' );
+      if( ( $cam =~ m/[0-9]+/ && int($cam) == $i )
+          || $entry->{_id} eq $cam
+          || $entry->{name} =~ m/$cam/ ) {
+        $cam = $entry->{_id};
+        #Log 1, "$i $entry->{name}: $entry->{_id}";
+        last;
+      }
+      ++$i;
+    }
+
+    return "no such cam: $cam" if( $i >= $json->{meta}{totalCount} );
+
+    my $apiKey = AttrVal($name, 'apiKey', undef);
+    $apiKey = UnifiVideo_decrypt( $apiKey );
+
+    my $url = "http://$hash->{HOST}:7080/api/2.0/snapshot/camera/$cam?force=true&apiKey=$apiKey";
+    $url .= "&width=$width" if( $width );
+    my $param = {
+      url => $url,
+      method => 'GET',
+      timeout => 5,
+      noshutdown => 0,
+      hash => $hash,
+      key => 'snapshot',
+      cam => $cam,
+      fileName => $param_h->{fileName} ,
+      index => $i,
+    };
+
+    Log3 $name, 4, "$name: fetching data from $url";
+
+    $param->{callback} = \&UnifiVideo_parseHttpAnswer;
+    HttpUtils_NonblockingGet( $param );
+
+    return undef;
+
+  } elsif( $cmd eq 'apiKey' ) {
+
+    return CommandAttr(undef,"$name apiKey $args[0]" );
+  }
+
+  return "Unknown argument $cmd, choose one of $list";
+}
+
+
+sub
+UnifiVideo_Get($$@)
+{
+  my ($hash, $name, $cmd) = @_;
+
+  my $list = "apiKey:noArg";
+
+  if( $cmd eq 'apiKey' ) {
+    my $apiKey = AttrVal($name, 'apiKey', undef);
+    return 'no apiKey set' if( !$apiKey );
+
+    $apiKey = UnifiVideo_decrypt( $apiKey );
+
+    return "apiKey: $apiKey";
+  }
+
+  return "Unknown argument $cmd, choose one of $list";
+}
+
+sub
+UnifiVideo_Parse($$;$)
+{
+  my ($hash,$data,$peerhost) = @_;
+  my $name = $hash->{NAME};
+}
+sub
+UnifiVideo_parseHttpAnswer($$$)
+{
+  my ($param, $err, $data) = @_;
+  my $hash = $param->{hash};
+  my $name = $hash->{NAME};
+
+  if( $err ) {
+    Log3 $name, 2, "$name: http request ($param->{url}) failed: $err";
+
+    return undef;
+  }
+
+  return undef if( !$data );
+
+  Log3 $name, 5, "$name: received $data";
+  if( $param->{key} eq 'json' ) {
+    my $json = eval { decode_json($data) };
+    Log3 $name, 2, "$name: json error: $@ in $json" if( $@ );
+
+    #Log 1, Dumper $json;
+    $hash->{helper}{json} = $json;
+
+    if( !defined( $json->{meta} ) ) {
+      Log3 $name, 2, "$name: received unknown data";
+      return undef;
+    }
+
+    my $apiKey = AttrVal($name, 'apiKey', undef);
+    $apiKey = UnifiVideo_decrypt( $apiKey );
+
+    my $totalCount = $json->{meta}{totalCount};
+    readingsBeginUpdate($hash);
+    readingsBulkUpdate($hash, 'state', $hash->{PID}?'watching':'running', 1 );
+    readingsBulkUpdateIfChanged($hash, 'totalCount', $totalCount, 1);
+    my $i = 0;
+    foreach my $entry (@{$json->{data}}) {
+      if( !$entry->{deleted} ) {
+      #Log 1, Dumper $entry->{_id};
+        readingsBulkUpdateIfChanged($hash, "cam${i}name", $entry->{name}, 1);
+        readingsBulkUpdateIfChanged($hash, "cam${i}id", $entry->{_id}, 1);
+        readingsBulkUpdateIfChanged($hash, "cam${i}state", $entry->{state}, 1);
+        #readingsBulkUpdateIfChanged($hash, "cam${i}snapshotURL", "http://$hash->{HOST}:7080/api/2.0/snapshot/camera/$entry->{_id}?force=true&apiKey=$apiKey" , 1);
+      }
+      ++$i;
+    }
+    readingsEndUpdate($hash,1);
+
+    RemoveInternalTimer($hash, "UnifiVideo_Connect");
+    InternalTimer(gettimeofday() + 900, "UnifiVideo_Connect", $hash);
+
+  } elsif( $param->{key} eq 'snapshot' ) {
+    my $modpath = $attr{global}{modpath};
+    my $filePath = AttrVal($name, 'filePath', "$modpath/www/snapshots" );
+
+    if(! -d $filePath) {
+      my $ret = mkdir "$filePath";
+      if($ret == 0) {
+        Log3 $name, 1,  "Error while creating filePath $filePath $!";
+        return undef;
+      }
+    }
+
+    my $fileName = $param->{fileName};
+    $fileName = $param->{cam} if( !$fileName );
+    $fileName .= '.jpg';
+
+    if(!open(FH, ">$filePath/$fileName")) {
+      Log3 $name, 1, "Can't write $filePath/$fileName $!";
+      return undef;
+    }
+    print FH $data;
+    close(FH);
+    Log3 $name, 4, "snapshot $filePath/$fileName written.";
+
+    DoTrigger( $name, "newSnapshot: $param->{index} $filePath/$fileName" );
+
+  } else {
+    Log3 $name, 2, "parseHttpAnswer: unhandled key";
+
+  }
+
+  return undef;
+}
+
+sub
+UnifiVideo_Read($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  my $buf;
+  my $ret = sysread($hash->{FH}, $buf, 65536 );
+  my $err = int($!);
+
+  if(!defined($ret) && $err == EWOULDBLOCK) {
+    return;
+  }
+
+#Log 1, $ret;
+#Log 1, $buf;
+#Log 1, $err;
+
+  if( $ret == 0 && !defined($hash->{PARTIAL}) ) {
+    UnifiVideo_killLogWatcher($hash);
+  }
+
+  my $data = $hash->{PARTIAL};
+  $data .= $buf;
+
+  while($data =~ m/\n/) {
+    my $line;
+    ($line,$data) = split("\n", $data, 2);
+
+    if( $line =~ m/password/ ) {
+      UnifiVideo_killLogWatcher($hash);
+
+    } elsif( $line =~ m/Camera\[([^\]]+)\].*type:([^\s]+)/ ) {
+      my $cam = $1;
+      my $type = $2;
+
+      if( $type eq 'start' ) {
+        my $json = $hash->{helper}{json};
+        $json = [] if( !$json );
+        my $i = 0;
+        foreach my $entry (@{$json->{data}}) {
+          last if( $entry->{mac} eq $cam );
+          ++$i;
+          }
+          if( $i >= $json->{meta}{totalCount} ) {
+            Log3 $name, 2, "$name: got motion event for unknown cam: $cam";
+
+          } else {
+            readingsSingleUpdate($hash, "cam${i}motion", $type, 1);
+          }
+
+        } elsif( $type eq 'stop' ) {
+        } else {
+          Log3 $name, 2, "$name: got unknown event type from cam: $cam";
+        }
+
+    } else {
+      Log3 $name, 2, "$name: got unknown event: $line";
+    }
+  }
+
+  $hash->{PARTIAL} = $data
+
+  #UnifiVideo_Parse($hash, $buf, $hash->{CD}->peerhost);
+}
+
+sub
+UnifiVideo_killLogWatcher($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  kill( 9, $hash->{PID} ) if( $hash->{PID} );
+
+  close($hash->{FH}) if($hash->{FH});
+  delete($hash->{FH});
+  delete($hash->{FD});
+
+  return if( !$hash->{PID} );
+  delete $hash->{PID};
+
+  readingsSingleUpdate($hash, 'state', 'running', 1 );
+  Log3 $name, 3, "$name: stopped logfile watcher";
+
+  delete $hash->{PARTIAL};
+  delete($selectlist{$name});
+Log 1, "4";
+}
+sub
+UnifiVideo_startLogWatcher($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  UnifiVideo_killLogWatcher($hash);
+
+  my $user = AttrVal($name, "sshUser", undef);
+  return if( !$user );
+  my $logfile = AttrVal($name, "logfile", "/var/log/unifi-video/motion.log" );
+  my $cmd = qx(which ssh);
+  chomp( $cmd );
+  $cmd .= ' -q ';
+  $cmd .= $user."\@" if( defined($user) );
+  $cmd .= $hash->{HOST};
+  $cmd .= " tail  -n 0 -F $logfile";
+  #my $cmd = "tail -f /tmp/x";
+  Log3 $name, 3, "$name: using $cmd to watch logfile";
+  if( my $pid = open( my $fh, '-|', $cmd ) ) {
+    $fh->blocking(0);
+    $hash->{FH}  = $fh;
+    $hash->{FD}  = fileno($fh);
+    $hash->{PID} = $pid;
+
+    $selectlist{$name} = $hash;
+
+    readingsSingleUpdate($hash, 'state', 'watching', 1 );
+
+    Log3 $name, 3, "$name: started logfile watcher";
+  } else {
+    Log3 $name, 2, "$name: failed to start logfile watcher";
+
+  }
+}
+
+
+sub
+UnifiVideo_Connect($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  return if( IsDisabled($name) );
+
+  my $apiKey = AttrVal($name, 'apiKey', undef);
+  if( !$apiKey ) {
+    $hash->{STATE} = 'disconnected';
+    Log3 $name, 2, "$name: can't connect without apiKey";
+    return undef;
+  }
+
+  $apiKey = UnifiVideo_decrypt( $apiKey );
+
+  my $url = "http://$hash->{HOST}:7080/api/2.0/camera?apiKey=$apiKey";
+  my $param = {
+    url => $url,
+    method => 'GET',
+    timeout => 5,
+    noshutdown => 0,
+    hash => $hash,
+    key => 'json',
+  };
+
+  Log3 $name, 4, "$name: fetching data from $url";
+
+  $param->{callback} = \&UnifiVideo_parseHttpAnswer;
+  HttpUtils_NonblockingGet( $param );
+
+  UnifiVideo_startLogWatcher( $hash ) if( !$hash->{PID} );
+
+  return undef;
+}
+
+sub
+UnifiVideo_encrypt($)
+{
+  my ($decoded) = @_;
+  my $key = getUniqueId();
+  my $encoded;
+
+  return $decoded if( $decoded =~ m/^crypt:(.*)/ );
+
+  for my $char (split //, $decoded) {
+    my $encode = chop($key);
+    $encoded .= sprintf("%.2x",ord($char)^ord($encode));
+    $key = $encode.$key;
+  }
+
+  return 'crypt:'. $encoded;
+}
+sub
+UnifiVideo_decrypt($)
+{
+  my ($encoded) = @_;
+  my $key = getUniqueId();
+  my $decoded;
+
+  $encoded = $1 if( $encoded =~ m/^crypt:(.*)/ );
+
+  for my $char (map { pack('C', hex($_)) } ($encoded =~ m/(..)/g)) {
+    my $decode = chop($key);
+    $decoded .= chr(ord($char)^ord($decode));
+    $key = $decode.$key;
+  }
+
+  return $decoded;
+}
+
+sub
+UnifiVideo_Attr($$$)
+{
+  my ($cmd, $name, $attrName, $attrVal) = @_;
+
+  my $orig = $attrVal;
+
+  my $hash = $defs{$name};
+  if( $attrName eq 'disable' ) {
+    if( $cmd eq "set" && $attrVal ) {
+      UnifiVideo_killLogWatcher($hash);
+      readingsSingleUpdate($hash, 'state', 'disabled', 1 );
+
+    } else {
+      readingsSingleUpdate($hash, 'state', 'running', 1 );
+      $attr{$name}{$attrName} = 0;
+      UnifiVideo_Connect($hash);
+
+    }
+
+  } elsif( $attrName eq 'sshUser' ) {
+    if( $cmd eq "set" && $attrVal ) {
+      $attr{$name}{$attrName} = $attrVal;
+    } else {
+      delete $attr{$name}{$attrName};
+      UnifiVideo_killLogWatcher($hash);
+    }
+
+    UnifiVideo_Connect($hash);
+
+  } elsif( $attrName eq 'apiKey' ) {
+    if( $cmd eq "set" && $attrVal ) {
+
+      return if( $attrVal =~ m/^crypt:/ );
+
+      $attrVal = UnifiVideo_encrypt($attrVal);
+
+      if( $orig ne $attrVal ) {
+        $attr{$name}{$attrName} = $attrVal;
+
+        UnifiVideo_Connect($hash);
+
+        return "stored obfuscated apiKey";
+      }
+    }
+  }
+
+
+  if( $cmd eq 'set' ) {
+
+  } else {
+    delete $attr{$name}{$attrName};
+  }
+
+  return;
+}
+
+
+1;
+
+=pod
+=item summary    Module to integrate FHEM with UnifiVideo
+=item summary_DE Modul zur Integration von FHEM mit UnifiVideo
+=begin html
+
+<a name="UnifiVideo"></a>
+<h3>UnifiVideo</h3>
+<ul>
+  Module to integrate UnifiVideo devices with FHEM.<br><br>
+
+  define &lt;name&gt; UnifiVideo &lt;ip&gt; [&lt;apiKey&gt;] <br><br>
+
+  Notes:
+  <ul>
+    <li>JSON has to be installed on the FHEM host.</li>
+    <li>create nvr api key: admin-&gt;my account-&gt;api access</li>
+    <li><code>define <name> webLink htmlCode {UnifiVideo_2html('&lt;nvr&gt;','&lt;cam&gt;[,&lt;cam2&gt;,..]'[,&lt;width&gt;])}</code></li>
+  </ul><br>
+
+  <a name="UnifiVideo_Set"></a>
+  <b>Set</b>
+  <ul>
+    <li>snapshot cam=&lt;cam&gt; width=&lt;width&gt; fileName=&lt;fileName&gt;<br>
+      takes a snapshot from &lt;cam&gt; with optional &lt;width&gt; and stores it with the optional &lt;fileName&gt;<br>
+      &lt;cam&gt; can be the number of the cammera, its id or a regex that is matched against the name.
+      </li>
+    <li>reconnect<br>
+      </li>
+  </ul>
+
+  <a name="UnifiVideo_Get"></a>
+  <b>Get</b>
+  <ul>
+    <li>apiKey<br>
+      shows the configured apiKey.</li>
+  </ul>
+
+  <a name="UnifiVideo_Attr"></a>
+  <b>Attr</b>
+  <ul>
+    <li>filePath<br>
+      path to store the snapshot images to. default: .../www/snapshots
+      </li>
+    <li>apiKey<br>
+      apiKey to use for nvr access
+      </li>
+    <li>ssh_user<br>
+      ssh user for nvr logfile access. used to fhem events after motion detection.
+      </li>
+  </ul>
+</ul><br>
+
+=end html
+=cut

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1059 - 0
fhem/core/FHEM/74_XiaomiBTLESens.pm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2632 - 0
fhem/core/FHEM/88_HMCCURPCPROC.pm


+ 785 - 0
fhem/core/FHEM/88_xs1Bridge.pm

@@ -0,0 +1,785 @@
+#################################################################
+# $Id: 88_xs1Bridge.pm 16394 2018-03-12 23:09:47Z HomeAuto_User $
+#################################################################
+# physisches Modul - Verbindung zur Hardware
+#
+# note / ToDo´s / Bugs:
+# - Port Check
+# 
+# 
+# 
+#################################################################
+
+package main;
+
+# Laden evtl. abhängiger Perl- bzw. FHEM-Module
+use HttpUtils;					# um Daten via HTTP auszutauschen https://wiki.fhem.de/wiki/HttpUtils
+use strict;				
+use warnings;					# Warnings
+use Net::Ping;
+
+my $missingModul		= "";
+my $xs1_ConnectionTry 	= 1;	# disable Funktion sobald 10x keine Verbindung (Schutzabschaltung)
+
+eval "use Encode qw(encode encode_utf8 decode_utf8);1" or $missingModul .= "Encode ";
+eval "use JSON;1" or $missingModul .= "JSON ";
+eval "use Net::Ping;1" or $missingModul .= "Net::Ping ";
+
+#$| = 1;		#Puffern abschalten, Hilfreich für PEARL WARNINGS Search
+
+sub xs1Bridge_Initialize($) {
+	my ($hash) = @_;
+	
+	$hash->{WriteFn}    = "xs1Bridge_Write";
+	$hash->{Clients}    = ":xs1Dev:";
+	$hash->{MatchList}  = { "1:xs1Dev"   =>	'[x][s][1][D][e][v][#][A][k][t][o][r]#[0-6][0-9].*|[x][s][1][D][e][v][#][S][e][n][s][o][r]#[0-6][0-9].*' };	## https://regex101.com/ Testfunktion
+	
+	$hash->{DefFn}		=	"xs1Bridge_Define";
+	$hash->{AttrFn}  	= 	"xs1Bridge_Attr";  
+	$hash->{UndefFn}	=	"xs1Bridge_Undef";
+	$hash->{AttrList}	=	"debug:0,1 ".
+							"disable:0,1 ".
+							"ignore:0,1 ".
+							"interval:30,60,180,360 ".
+							"update_only_difference:0,1 ".
+							"view_Device_name:0,1 ".
+							"view_Device_function:0,1 ".
+							"xs1_control:0,1 ";
+							##$readingFnAttributes;		## die Standardattribute von FHEM
+							
+	foreach my $d(sort keys %{$modules{xs1Bridge}{defptr}}) {
+        my $hash = $modules{xs1Bridge}{defptr}{$d};
+    }							
+}
+
+sub xs1Bridge_Define($$) {
+	my ($hash, $def) = @_;
+	my @arg = split("[ \t][ \t]*", $def);
+	my $name = $hash->{NAME};							## Der Definitionsname, mit dem das Gerät angelegt wurde.
+	my $typ = $hash->{TYPE};							## Der Modulname, mit welchem die Definition angelegt wurde.
+	my $debug = AttrVal($hash->{NAME},"debug",0);
+	my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0);
+	my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0);
+	my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0);
+
+					#		0		1	 2
+	return "Usage: define <NAME> $name <IP>"  if(@arg != 3);
+	#return "Usage: define <NAME> $name <IP> <PORT>"  if(@arg != 4);
+	return "Your IP is not valid. Please Check!" if not($arg[2] =~ /[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}/s);
+	#return "Your PORT is not valid. Please Check!"  if not($arg[3] =~ /[0-9]{2,5}/s);
+	return "Cannot define xs1Bridge device. Perl modul ${missingModul}is missing." if ( $missingModul );
+
+	my $xs1check = 0;
+	if(!defined $modules{xs1Bridge}) {
+		my $p = Net::Ping->new("tcp", 2);
+		if(!($p->ping("$arg[2]", 2))) {
+			$xs1check = 1;
+		}
+		$p->close();
+		return "Your IP is not reachable. Please Check!" if ($xs1check == 1);
+	}
+	
+	# Parameter Define
+	my $xs1_ip = $arg[2];				## Zusatzparameter 1 bei Define - ggf. nur in Sub
+	$hash->{xs1_ip} = $xs1_ip;
+	
+	$hash->{STATE} = "Initialized";		## Der Status des Modules nach Initialisierung.
+	$hash->{TIME} = time();				## Zeitstempel, derzeit vom anlegen des Moduls
+	$hash->{VERSION} = "1.16";			## Version
+	$hash->{BRIDGE}	= 1;
+	
+	# Attribut gesetzt
+	$attr{$name}{disable}		= "0";
+	$attr{$name}{interval}		= "60";
+	$attr{$name}{room}			= "xs1"	if( not defined( $attr{$name}{room} ) );
+	$attr{$name}{xs1_control}	= "0";
+	
+	$modules{xs1Bridge}{defptr}{BRIDGE} = $hash;
+	
+	InternalTimer(gettimeofday()+$attr{$name}{interval}, "xs1Bridge_GetUpDate", $hash);		## set Timer
+
+	Log3 $name, 3, "$typ: Modul defined with xs1_ip: $xs1_ip";
+	
+	if(!defined($defs{'FileLog_xs1Bridge'})) {												## Logfile existent check
+		fhem("define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log ".$arg[0]);		## Logfile define
+		fhem("attr FileLog_xs1Bridge room xs1");											## Logfile in xs1 room
+	}
+
+	return undef;
+}
+
+sub xs1Bridge_Attr(@) {
+	my ($cmd,$name,$attrName,$attrValue) = @_;
+	my $hash = $defs{$name};
+	my $typ = $hash->{TYPE};
+	my $interval = 0;
+	my $debug = AttrVal($hash->{NAME},"debug",0);
+	my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0);
+	my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0);
+	my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0);
+	my $xs1_control = AttrVal($hash->{NAME},"xs1_control",0);
+	
+	# $cmd  - Vorgangsart - kann die Werte "del" (löschen) oder "set" (setzen) annehmen
+	# $name - Gerätename
+	# $attrName/$attrValue sind Attribut-Name und Attribut-Wert
+   
+	#### Handling bei set .. attribute
+	if ($cmd eq "set") {
+		RemoveInternalTimer($hash);									## Timer löschen
+		Debug " $typ: Attr | Cmd:$cmd | RemoveInternalTimer" if($debug);
+		if ($attrName eq "interval") {								## Abfrage Attribute
+			if (($attrValue !~ /^\d*$/) || ($attrValue < 10))
+			{
+			return "$typ: Interval is too small. Please define new Interval | (at least: 10 seconds)";
+			}
+			my $interval = $attrValue;
+		}
+		elsif ($attrName eq "disable") {
+			if ($attrValue eq "1") {								## Handling bei attribute disable 1
+			readingsSingleUpdate($hash, "state", "deactive", 1);
+			}
+			elsif ($attrValue eq "0") {								## Handling bei attribute disable 0
+			$xs1_ConnectionTry = 1;
+			readingsSingleUpdate($hash, "state", "active", 1);
+			}
+		}elsif ($attrName eq "view_Device_function") {
+			if ($attrValue eq "1") {								## Handling bei attribute view_Device_function 1
+			Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue";
+			}
+			elsif ($attrValue eq "0") {								## Handling bei attribute view_Device_function 0
+			Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue";
+			}
+		}elsif ($attrName eq "view_Device_name") {
+			if ($attrValue eq "1") {								## Handling bei attribute view_Device_name 1
+			Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue";
+			}
+			elsif ($attrValue eq "0") {								## Handling bei attribute view_Device_name 0
+				Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue";
+				for my $i (0..64) {
+				delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
+				delete $hash->{READINGS}{"Sensor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
+				}
+			}
+		}elsif ($attrName eq "update_only_difference") {
+			if ($attrValue eq "1") {								## Handling bei attribute update_only_difference 1
+			Log3 $name, 3, "$typ: Attribut update_only_difference $cmd to $attrValue";
+			}
+			elsif ($attrValue eq "0") {								## Handling bei attribute update_only_difference 0
+				Log3 $name, 3, "$typ: Attribut update_only_difference $cmd to $attrValue";
+				for my $i (0..64) {
+				delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
+				}
+			}
+		}elsif ($attrName eq "xs1_control") {
+			if ($attrValue eq "1") {								## Handling bei attribute xs1_control 1
+				if(! $modules{xs1Dev}) {							## Check Modul vorhanden
+					$attr{$name}{xs1_control}	= "0";
+					return "Module xs1Dev is non-existent or still under development. Please wait"
+				}
+			}
+		}
+	}
+	
+	#### Handling bei del ... attribute
+	if ($cmd eq "del") {
+		if ($attrName eq "disable" && !defined $attrValue) {
+		readingsSingleUpdate($hash, "state", "active", 1);
+		Debug " $typ: Attr | Cmd:$cmd | $attrName=$attrValue" if($debug);
+		}
+		elsif ($attrName eq "interval") {
+		RemoveInternalTimer($hash);
+		Debug " $typ: Attr | Cmd:$cmd | $attrName" if($debug);
+		}
+		elsif ($attrName eq "view_Device_function") {
+			Log3 $name, 3, "$typ: Attribut view_Device_function delete";
+			for my $i (0..64) {
+				for my $i2 (1..4) {
+					delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_function_".$i2} if($hash->{READINGS});
+				}
+			}
+		}
+		elsif ($attrName eq "view_Device_name") {
+			Log3 $name, 3, "$typ: Attribut view_Device_name delete";
+			for my $i (0..64) {
+				delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
+				delete $hash->{READINGS}{"Sensor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
+			}
+		}
+		elsif ($attrName eq "update_only_difference") {
+			Log3 $name, 3, "$typ: Attribut update_only_difference delete";
+		}
+	}
+
+	#### Handling bei state active
+	if ($hash->{STATE} eq "active") {
+		RemoveInternalTimer($hash);
+		InternalTimer(gettimeofday()+$interval, "xs1Bridge_GetUpDate", $hash);
+		Debug " $typ: Attr | RemoveInternalTimer + InternalTimer" if($debug);
+	}
+	return undef;
+}
+
+sub xs1Bridge_GetUpDate() {
+	my ($hash) = @_;
+	my $name = $hash->{NAME};
+	my $typ = $hash->{TYPE};
+	my $state = $hash->{STATE};
+	my $xs1_ip = $hash->{xs1_ip};
+	
+	my $xs1_uptimeStart = $hash->{helper}{xs1_uptimeStart};
+	my $xs1_uptimeOld = $hash->{helper}{xs1_uptimeOld};
+	my $xs1_uptimeNew = $hash->{helper}{xs1_uptimeNew};
+	my $def;
+	
+	#http://x.x.x.x/control?callback=cname&cmd=...
+	#get_list_actuators        - list all actuators			i0
+	#get_list_sensors          - list all sensors			i1
+	#get_list_timers           - list all timers				i3
+	#get_config_info           - list all device info´s	i2
+	#get_protocol_info         - list protocol info´s
+   
+	my $cmd = "/control?callback=cname&cmd=";
+	my @cmdtyp = ("get_list_actuators","get_list_sensors","get_config_info","get_list_timers","get_list_actuators");
+	my @arrayname = ("actuator","sensor","info","timer","function");
+	my @readingsname = ("Aktor","Sensor","","Timer","");
+   
+	my $debug = AttrVal($hash->{NAME},"debug",0);
+	my $disable = AttrVal($name, "disable", 0);
+	my $interval = AttrVal($name, "interval", 60);
+	my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0);
+	my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0);
+	my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0);
+	my $xs1_control = AttrVal($hash->{NAME},"xs1_control",0);
+	
+	#### xs1Bridge disable Option = 0 -> aktiviert zum auslesen
+	if (AttrVal($hash->{NAME},"disable",0) == 0 && $xs1_ConnectionTry <= 5) {
+		RemoveInternalTimer($hash);									## Timer löschen
+		InternalTimer(gettimeofday()+$interval, "xs1Bridge_GetUpDate", $hash);
+		Debug " -------------- ERROR CHECK - START --------------" if($debug);
+		Debug " $typ: GetUpDate | RemoveInternalTimer + InternalTimer" if($debug);
+		#Log3 $name, 3, "$typ: xs1Bridge_GetUpDate | RemoveInternalTimer + InternalTimer";
+
+		if ($state eq "Initialized") {
+			readingsSingleUpdate($hash, "state", "active", 1);
+		}
+
+		my $xs1Dev_check = "ERROR";
+
+		#if($modules{xs1Dev} && $modules{xs1Dev}{LOADED}) {		## Check Modul vorhanden + geladen
+		if($modules{xs1Dev}) {									## Check Modul vorhanden
+			$xs1Dev_check = "ok";
+			Debug " $typ: GetUpDate | Modul xs1Dev_check = $xs1Dev_check" if($debug);
+		} else {
+			Debug " $typ: GetUpDate ERROR | Modul xs1Dev not existent! Please check it to be available!" if($debug);
+			#Log3 $name, 3, "$typ: GetUpDate | xs1Dev_check = $xs1Dev_check";
+		}
+
+		#### JSON Abfrage - Schleife
+		for my $i (0..3) {
+			#### HTTP Requests #### Start ####
+			my $connection;
+			my $Http_err 	= "";
+			my $Http_data 	= "";
+			my $param 		= 	{
+									url        => "http://".$xs1_ip.$cmd.$cmdtyp[$i],
+									timeout    => 3,
+									method     => "GET",		# Lesen von Inhalten
+								};
+
+			HttpUtils_BlockingGet($param);
+			($Http_err, $Http_data) = HttpUtils_BlockingGet($param);
+			#### HTTP Requests #### END ####	
+		
+			my $adress = "http://".$xs1_ip.$cmd.$cmdtyp[$i];
+			my $json;
+			my $json_utf8;
+			my $decoded;			
+
+			Debug " $typ: GetUpDate | Adresse: $adress | xs1_ConnectionTry=$xs1_ConnectionTry" if($debug && $Http_err eq "");
+			Debug " $typ: GetUpDate | HTTP request: ".$Http_err."| xs1_ConnectionTry=$xs1_ConnectionTry" if($debug && $Http_err ne "");
+
+			#### HTTP Requests, ERROR
+			if ($Http_err ne "") {
+				# ERROR Message
+				# http://192.168.2.5/control?callback=cname&cmd=get_list_actuators: Can't connect(1) to http://192.168.2.5:80: IO::Socket::INET: connect: No route to host
+				# http://192.168.2.5/control?callback=cname&cmd=get_config_info: empty answer received
+				# http://192.168.2.5/control?callback=cname&cmd=get_config_info: Select timeout/error: 
+				
+				#($Http_err) = $Http_err =~ /[:]\s.*/g;
+				Log3 $name, 3, "$typ: GetUpDate | Try=$xs1_ConnectionTry loop=$i | Error: ".$Http_err;
+				$xs1_ConnectionTry++;
+				last;											## Abbruch Schleife
+			}
+			#### HTTP Requests, OK dann ARRAY Verarbeitung	
+			elsif ($Http_data ne "") {						
+				($json) = $Http_data =~ /[^(]*[}]/g;			## cut cname( + ) am Ende von Ausgabe -> ARRAY Struktur als Antwort vom xs1
+				$json_utf8 = eval {encode_utf8( $json )};		## UTF-8 character Bearbeitung, da xs1 TempSensoren ERROR
+				$decoded = eval {decode_json( $json_utf8 )};
+				$xs1_ConnectionTry 	= 1;			
+
+				#### xs1 Aktoren / Sensoren als Readings
+				if ($i <= 1 ) {
+					my $xs1_data;
+					my @array;
+			
+					if (defined $decoded->{$arrayname[$i]}) {
+						@array = @{ $decoded->{$arrayname[$i]} };
+					} else {
+						Log3 $name, 3, "$typ: GetUpDate | ARRAY-ERROR xs1 -> no Data in loop $i";
+						last;
+					}
+
+					my $i3 = 0;		## Counter für real Werte in xs1, da sonst Verschiebungen wenn User ID´s verschiebt, NOTWENDIG!
+					
+					foreach my $f ( @array ) {
+						$i3++;
+						if ($f->{"type"} ne "disabled") {
+							my $xs1Dev = "xs1Dev";
+
+							#### Aktoren spezifisch
+							my $xs1_function1 = "-";
+							my $xs1_function2 = "-";
+							my $xs1_function3 = "-";
+							my $xs1_function4 = "-";
+
+							if ($i == 0) {
+								#### xs1 Aktoren nur update bei differenten Wert
+								if ($update_only_difference == 1) {
+									my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3), "unknown");	## Readings Wert
+									my $newState = sprintf("%.1f" , $f->{"value"});														## ARRAY Wert xs1 aktuell
+
+									Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." oldState=$oldState newState=$newState" if($debug);
+									
+									if ($oldState ne $newState) {
+										readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3) , $newState, 0);
+									}
+								}
+
+								#### xs1 Aktoren / Funktion != disable
+								my @array2 = @{ $decoded->{'actuator'}->[$i3-1]->{$arrayname[4]} };
+								my $i2 = 0;		## Funktionscounter
+
+								foreach my $f2 ( @array2 ) {
+									$i2++;
+
+									#### xs1 Option - Ansicht Funktionsname
+									if ($viewDeviceFunction == 1) {
+										my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3)."_".$arrayname[4]."_".$i2, "unknown");		## Readings Wert
+										my $newState = $f2->{'type'};		## ARRAY Wert xs1 aktuell
+
+										if ($oldState ne "unknown" && $newState eq "disabled") { 	## FunktionReading del bei disable
+											Debug " $typ: "."Aktor_".sprintf("%02d", $i3)."_function_".$i2." are disabled" if($debug);
+											delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i3)."_function_".$i2} if($hash->{READINGS});
+										}
+
+										if ($f2->{"type"} ne "disabled") {  ## Funktion != function -> type disable
+											if ($oldState ne $newState) {
+												readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3)."_".$arrayname[4]."_".$i2 , $f2->{"type"} , 1);
+											}
+										}
+									}
+
+									#### Funktion != function -> type disable
+									if ($f2->{"type"} ne "disabled") {
+										if ($i2 == 1) {
+											$xs1_function1 = $f2->{"type"};
+										}elsif ($i2 == 2) {
+											$xs1_function2 = $f2->{"type"};
+										}elsif ($i2 == 3) {
+											$xs1_function3 = $f2->{"type"};
+										}elsif ($i2 == 4) {
+											$xs1_function4 = $f2->{"type"};
+										}
+									}
+								}
+							}
+
+							#### Value der Aktoren | Sensoren
+							if ($i == 1 || $i == 0 && $update_only_difference == 0) {		# Aktoren | Sensoren im intervall - Format 0.0 bzw. 37.0 wie aus xs1
+								readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3) , sprintf("%.1f" , $f->{"value"}), 0);
+								$xs1_data = $xs1Dev."#".$readingsname[$i]."#".sprintf("%02d", $i3)."#".$f->{"type"}."#".sprintf("%.1f" , $f->{"value"})."#"."$xs1_function1"."#"."$xs1_function2"."#"."$xs1_function3"."#"."$xs1_function4"."#".$f->{"name"};
+							} elsif ($i == 0 && $update_only_difference == 1){				# Aktoren | nur bei DIFF - Format 0.0 bzw. 37.0 wie aus xs1
+								$xs1_data = $xs1Dev."#".$readingsname[$i]."#".sprintf("%02d", $i3)."#".$f->{"type"}."#".sprintf("%.1f" , $f->{"value"})."#"."$xs1_function1"."#"."$xs1_function2"."#"."$xs1_function3"."#"."$xs1_function4"."#".$f->{"name"};
+							}
+
+							#### Ausgaben je Typ unterschiedlich !!!
+							Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." | ".$f->{"type"}." | ".$f->{"name"}." | ". $f->{"value"}." | "."F1 $xs1_function1 | F2 $xs1_function2 | F3 $xs1_function3 | F4 $xs1_function4" if($debug == 1 && $i == 0);
+							Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." | ".$f->{"type"}." | ".$f->{"name"}." | ". $f->{"value"} if($debug == 1 && $i != 0);
+
+							### Ansicht Namen der Aktoren | Sensoren als Readings
+							if ($viewDeviceName == 1) {
+								my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3)."_name", "unknown");		## Readings Wert
+								my $newState = $f->{"name"};		## ARRAY Wert xs1 aktuell
+								
+								if ($oldState ne $newState) {		## Namen nur bei Änderung schreiben
+									#Log3 $name, 3, "$typ: GetUpDate | newState=$newState ne oldState=$oldState";
+									readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3)."_name" , $f->{"name"} , 1);
+								}
+							}
+							
+							### Dispatch an xs1Device Modul						
+							if ($xs1Dev_check eq "ok" && $xs1_control == 1) {
+								Debug " $typ: GetUpDate | Dispatch: $xs1_data" if($debug);
+								Dispatch($hash,$xs1_data,undef) if($xs1_data);
+							}
+						} else {
+						#### ID bzw. Speicherplatz xs1 ist disabled | Reading are delete
+							delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)} if($hash->{READINGS});
+							delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)."_name"} if($hash->{READINGS});
+							
+							if ($i == 0) {
+								for my $count (1..4) {
+									delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)."_function_".$count} if($hash->{READINGS});
+								}
+							}
+						}
+					}
+				} 
+				#### xs1 Info´s nur bei uptime Änderung als Readings
+				elsif ($i == 2) {
+					my $features;
+					my $features_i=0;
+
+					my @xs1_readings = ("xs1_start","xs1_devicename","xs1_bootloader","xs1_hardware","xs1_features","xs1_firmware","xs1_mac","xs1_dhcp");
+					my @xs1_decoded = (FmtDateTime(time()-($decoded->{'info'}{'uptime'})) , $decoded->{'info'}{'devicename'} , $decoded->{'info'}{'bootloader'} , $decoded->{'info'}{'hardware'} , $features , $decoded->{'info'}{'firmware'} , $decoded->{'info'}{'mac'} , $decoded->{'info'}{'autoip'});
+				
+					my $oldState = ReadingsVal($name, $xs1_readings[0], "2000-01-01 03:33:33");	## Readings Wert
+					my @oldstate = split (/[-,:,\s\/]/, $oldState); 							## Split $year, $month, $mday, $hour, $min, $sec
+					$oldState = fhemTimeGm($oldstate[5], $oldstate[4], $oldstate[3], $oldstate[2], $oldstate[1]-1, $oldstate[0]-1900); 	## Verarbeitung $sec, $min, $hour, $mday, $month-1, $year-1900
+				
+					my $newState = FmtDateTime(time()-($decoded->{'info'}{'uptime'}));			## ARRAY uptime Wert xs1 aktuell
+					my @newState = split (/[-,:,\s\/]/, $newState); 							## Split $year, $month, $mday, $hour, $min, $sec
+					$newState = fhemTimeGm($newState[5], $newState[4], $newState[3], $newState[2], $newState[1]-1, $newState[0]-1900); 	## Verarbeitung $sec, $min, $hour, $mday, $month-1, $year-1900
+				
+					#### Vergleich mit 5 Sekunden Tolleranz je Verarbeitungszeit Netzwerk | DLAN | CPU
+					if (abs($oldState - $newState) > 5 || $debug == 1) {
+						readingsBeginUpdate($hash);
+						for my $i2 (0..7) {
+							if ($i2 == 4) {
+								while (defined $decoded->{'info'}{'features'}->[$features_i]) {
+									$features.= $decoded->{'info'}{'features'}->[$features_i]." ";
+									$features_i++;
+								}
+								$xs1_decoded[4] = $features;	## ARRAY Wert xs1_decoded wird definiert
+							}
+							if (defined $xs1_decoded[$i2]) {
+								readingsBulkUpdate($hash, $xs1_readings[$i2] , $xs1_decoded[$i2]);
+								Debug " $typ: ".$xs1_readings[$i2].": ".$xs1_decoded[$i2] if($debug);
+							} else {
+								Log3 $name, 3, "$typ: GetUpDate | ARRAY-ERROR xs1 -> no Data in loop $i|$i2";
+								last;
+							}
+						}
+						readingsEndUpdate($hash, 1);
+					}
+				} 
+				#### xs1 Timers als Readings
+				elsif ($i == 3) {
+					my @array = @{ $decoded->{$arrayname[$i]} };
+					foreach my $f ( @array ) {
+						my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $f->{"id"}), "unknown");	## Readings Wert
+						my $newState = FmtDateTime($f->{"next"});			## ARRAY Wert xs1 aktuell
+					
+						if ($f->{"type"} ne "disabled") {
+							if ($oldState ne $newState) {					## Update Reading nur bei Wertänderung
+								readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $f->{"id"}) , FmtDateTime($f->{"next"}), 1);
+							}
+							Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $f->{"id"})." | ".$f->{"name"}." | ".$f->{"type"}." | ". $f->{"next"} if($debug);
+						} elsif ($oldState ne "unknown") {					## deaktive Timer mit Wert werden als Reading entfernt
+							Log3 $name, 3, "$typ: GetUpDate | ".$readingsname[$i]."_".sprintf("%02d", $f->{"id"})." is deactive in xs1";
+							delete $defs{$name}{READINGS}{$readingsname[$i]."_".sprintf("%02d", $f->{"id"})};
+						}
+					}
+				}
+	 
+				if ($i < 3) {
+					Debug " --------------- ERROR CHECK - SUB --------------- " if($debug);
+				}
+				### Schleifen Ende ###
+			}
+		}
+
+		Debug " ------------- ERROR CHECK - ALL END -------------\n " if($debug);
+	}
+	
+	if ($xs1_ConnectionTry == 6) {								## Abschaltung xs1 nach 10 Verbindungsversuchen
+		$attr{$name}{disable}	= "1";
+		readingsSingleUpdate($hash, "state", "deactive", 1);
+		RemoveInternalTimer($hash);								## Timer löschen
+		Log3 $name, 3, "$typ: GetUpDate | connection ERROR -> xs1 set to disable! Device not reachable after 10 attempts";
+	}
+}
+
+sub xs1Bridge_Write($)			## Zustellen von Daten via IOWrite() vom logischen zum physischen Modul 
+{
+	my ($hash, $Aktor_ID, $xs1_typ, $cmd, $cmd2) = @_;
+	my $name = $hash->{NAME};
+	my $typ = $hash->{TYPE};
+	my $xs1_ip = $hash->{xs1_ip};
+   
+   ## Anfrage (Client -> XS1): http://192.168.1.242/control?callback=cname&cmd=set_state_actuator&number=1&value=100
+	
+	## Aktor Typen aus xs1: (notwendig zur Verarbeitung)
+	## -------------------------------------------------
+	## blind - Jalousie				|	dimmer - Dimmer				|	door - Tür			|	disabled - deaktivert
+	## switch - Schalter				|	shutter - Rolladen			|	sound - Ton			|	sun-blind - Markise
+	## temperature - Temperatur	|	timerswitch - Zeitschalter	|	window - Fenster
+
+	## Sensor Typen (Auswahl) aus xs1: (nur Info)
+	## ------------------------------------------
+	## alarmmat - Alarmmatte			|	disabled - deaktivert
+	## gas_butan - Gasmelder Butan	|	gas_peak - Gas Spitzenwert
+	## mail - Briefmelder				|	motion - Bewegung
+	## other - Andere						|	presence - Anwesenheit
+	## pwr_consump - Energiezähler	|	pwr_peak - Energie Spitzenwert
+	## soilmoisture - Bodenfeuchte	|	soiltemp -  Bodentemperatur
+	## leafwetness - Blattfeuchte		|	remotecontrol - Fernbedienung
+	## windowopen - Fenstermelder	...
+
+	$Aktor_ID = substr($Aktor_ID, 1,2);
+
+	my $xs1cmd;
+	#### xs1 Typ switch || shutter || timerswitch - Anpassung Sendebefehl
+	if ($xs1_typ eq "switch" || $xs1_typ eq "shutter" || $xs1_typ eq "timerswitch") {
+		$xs1cmd = "http://$xs1_ip/control?callback=cname&cmd=set_state_actuator&number=$Aktor_ID&$cmd2";
+	} elsif ($xs1_typ eq "dimmer") {
+		if ($cmd eq "off") {
+			$cmd = 0;
+		}
+		$xs1cmd = "http://$xs1_ip/control?callback=cname&cmd=set_state_actuator&number=$Aktor_ID&value=$cmd";
+	} else {
+	#### keine Verarbeitung zum senden ####
+	Log3 $name, 3, "$typ: Write | $xs1_typ not control xs1. Please inform me!";
+	last;
+	}
+
+	### HTTP Requests #### Start ####
+	my $connection;
+	my $Http_err 	= "";
+	my $Http_data;
+	my $param 	= 	{
+							url        => "$xs1cmd",
+							timeout    => 3,
+							method     => "GET",		# Lesen von Inhalten
+						};
+
+	HttpUtils_BlockingGet($param);
+	($Http_err, $Http_data) = HttpUtils_BlockingGet($param);
+	### HTTP Requests #### END ####	
+	
+	if ($Http_err ne "") {
+		($Http_err) = $Http_err =~ /[:]\s.*/g;
+		Log3 $name, 3, "$typ: Write | no Control possible | Error".$Http_err;
+		return undef;
+	} elsif ($Http_data ne "") {
+		Log3 $name, 4, "$typ: Write | Send to xs1 -> $xs1cmd";		## Kontrolle Sendebefehl
+	}
+}
+
+sub xs1Bridge_Undef($$)
+{
+	my ( $hash, $name) = @_;
+	my $typ = $hash->{TYPE};
+
+	RemoveInternalTimer($hash);
+
+	delete $modules{xs1Bridge}{defptr}{BRIDGE} if( defined($modules{xs1Bridge}{defptr}{BRIDGE}) );
+	
+	foreach my $d (sort keys %defs) {
+		if(defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash) {
+			Log3 $name, 3, "$typ: deleting IODev for $d";
+			delete $defs{$d}{IODev};
+		}
+	}
+	
+	Log3 $name, 3, "$typ: deleting Device with Name $name";
+	return undef;
+}
+
+# Eval-Rückgabewert für erfolgreiches
+# Laden des Moduls
+1;
+
+
+# Beginn der Commandref
+
+=pod
+=item summary    Connection of the device xs1 from EZControl
+=item summary_DE Anbindung des Ger&auml;tes xs1 der Firma EZControl
+=begin html
+
+<a name="xs1Bridge"></a>
+<h3>xs1Bridge</h3>
+<ul>
+	With this module you can read out the device xs1 from EZcontrol. There will be actors | Sensors | Timer | Information read from xs1 and written in readings. With each read only readings are created or updated, which are also defined and active in xs1. Actor | Sensor or timer definitions which are deactivated in xs1 are NOT read.
+	<br><br>
+
+	The module was developed based on the firmware version v4-Beta of the xs1. There may be errors due to different adjustments within the manufacturer's firmware.<br>
+	Testet firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser<br><br>
+
+	<a name="xs1Bridge_define"></a>
+	<b>Define</b><br>
+		<ul>
+		<code>define &lt;NAME&gt; xs1Bridge &lt;IP&gt;</code>
+		<br><br>
+
+		The module can not create without the IP of the xs1. If the IP can not be reached during module definition, the Define process is aborted.
+			<ul>
+			<li><code>&lt;IP&gt;</code> is IP address in the local network.</li>
+			</ul><br>
+		example:
+		<ul>
+		define EZcontrol_xs1 xs1Bridge 192.168.1.45
+		</ul>	
+		</ul><br>
+	<b>Set</b>
+	<ul>N/A</ul><br>
+	<b>Get</b><br>
+	<ul>N/A</ul><br>
+	<a name="xs1_attr"></a>
+	<b>Attributes</b>
+	<ul>
+		<li>debug (0,1)<br>
+		This brings the module into a very detailed debug output in the logfile. Program parts can be checked and errors checked.<br>
+		(Default, debug 0)
+		</li><br>
+		<li>disable (0,1)<br>
+		This function deactivates the interval. With disable 1 no readings are updated.<br>
+		(Default, disable 0)
+		</li><br>
+		<li>interval (30,60,180,360)<br>
+		This is the interval in seconds at which readings are read from xs1<br>
+		<i>For actuators, only different states are updated in the set interval.</i><br>
+		<i>Sensors are always updated in intervals, regardless of the status.</i><br>
+		(Default, interval 60)
+		</li><br>
+		<li>update_only_difference (0,1)<br>
+		The actuators defined in xs1 are only updated when the value changes.<br>
+		(Default, update_only_difference 0)</li><br>
+		<li>view_Device_name (0,1)<br>
+		The actor names defined in xs1 are read as Reading.<br>
+		(Default, view_Device_name 0)<br>
+		</li><br>
+		<li>view_Device_function (0,1)<br>
+		The actuator functions defined in xs1 are read out as Reading.<br>
+		(Default, view_Device_function 0)<br>
+		</li><br>
+		<li>xs1_control (0,1)<br>
+		Option to control the xs1. After activating this, the xs1Dev module creates each actuator and sensor in FHEM.<br>
+		(Default, xs1_control 0)<br>
+		</li><br><br>
+	</ul>
+	<b>explanation:</b>
+	<ul>
+		<li>various Readings:</li>
+		<ul>
+		<li>Aktor_(01-64)</li> defined actuator in the device<br>
+		<li>Aktor_(01-64)_name</li> defined actor name in the device<br>
+		<li>Aktor_(01-64)_function(1-4)</li> defined actuator function in the device<br>
+		<li>Sensor_(01-64)</li> defined sensor in the device<br>
+		<li>Sensor_(01-64)_name</li> defined sensor name in the device<br>
+		<li>Timer_(01-128)</li> defined timer in the device<br>
+		<li>xs1_bootloader</li> version of bootloader<br>
+		<li>xs1_dhcp</li> DHCP on/off<br>
+		<li>xs1_features</li> purchased feature when buying (A = send | B = receive | C = Skripte/Makros | D = Media Access)<br>
+		<li>xs1_firmware</li> firmware number<br>
+		<li>xs1_start</li> device start<br>
+		</ul><br>
+		<li>The message "<code>... Can't connect ...</code>" or "<code>ERROR: empty answer received</code>" in the system logfile says that there was no query for a short time.<br>
+		(This can happen more often with DLAN.)<br><br></li>
+		<li>If the device has not been connected after 5 connection attempts, the module will switch on < disable > !</li><br>
+		<li>Create logfile automatically after define | scheme: <code>define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log &lt;name&gt;</code><br>
+			The following values ​​are recorded in logfile: Timer | xs1-status information</li>
+	</ul>
+</ul>
+=end html
+=begin html_DE
+
+<a name="xs1Bridge"></a>
+<h3>xs1Bridge</h3>
+<ul>
+	Mit diesem Modul k&ouml;nnen Sie das Gerät xs1 der Firma <a href="http://www.ezcontrol.de/">EZcontrol</a> auslesen. Das Modul ruft die Daten des xs1 via der Kommunikationsschnittstelle ab. Mit einem HTTP GET Requests erh&auml;lt man die Antworten in Textform welche im Datenformat JSON (JavaScript Object Notation) ausgegeben werden. 
+	Es werden Aktoren | Sensoren | Timer | Informationen vom xs1 ausgelesen und in Readings geschrieben. Bei jedem Auslesen werden nur Readings angelegt bzw. aktualisiert, welche auch im xs1 definiert und aktiv sind. Aktor | Sensor bzw. Timer Definitionen welche deaktiviert sind im xs1, werden NICHT ausgelesen.
+	<br><br>
+
+	Das Modul wurde entwickelt basierend auf dem Firmwarestand v4-Beta des xs1. Es kann aufgrund von unterschiedlichen Anpassungen innerhalb der Firmware des Herstellers zu Fehlern kommen.<br>
+	Getestete Firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser<br><br>
+
+	<a name="xs1Bridge_define"></a>
+	<b>Define</b><br>
+		<ul>
+		<code>define &lt;NAME&gt; xs1Bridge &lt;IP&gt;</code>
+		<br><br>
+
+		Ein anlegen des Modules ohne Angabe der IP vom xs1 ist nicht m&ouml;glich. Sollte die IP bei der Moduldefinierung nicht erreichbar sein, so bricht der Define Vorgang ab.
+			<ul>
+			<li><code>&lt;IP&gt;</code> ist IP-Adresse im lokalen Netzwerk.</li>
+			</ul><br>
+		Beispiel:
+		<ul>
+		define EZcontrol_xs1 xs1Bridge 192.168.1.45
+		</ul>	
+		</ul><br>
+	<b>Set</b>
+	<ul>N/A</ul><br>
+	<b>Get</b><br>
+	<ul>N/A</ul><br>
+	<a name="xs1_attr"></a>
+	<b>Attribute</b>
+	<ul>
+		<li>debug (0,1)<br>
+		Dies bringt das Modul in eine sehr ausf&uuml;hrliche Debug-Ausgabe im Logfile. Somit lassen sich Programmteile kontrollieren und Fehler &uuml;berpr&uuml;fen.<br>
+		(Default, debug 0)
+		</li><br>
+		<li>disable (0,1)<br>
+		Diese Funktion deaktiviert den Interval. Mit <code>disable 1</code> werden keine Readings aktualisiert.<br>
+		(Default, disable 0)
+		</li><br>
+		<li>interval (30,60,180,360)<br>
+		Das ist der Intervall in Sekunden, in dem die Readings neu gelesen werden vom xs1.<br>
+		<i>Bei Aktoren werden nur unterschiedliche Zust&auml;nde aktualisiert im eingestellten Intervall.</i><br>
+		<i>Sensoren werden unabhängig vom Zustand immer im Intervall aktualisiert.</i><br>
+		(Default, interval 60)
+		</li><br>
+		<li>update_only_difference (0,1)<br>
+		Die Aktoren welche im xs1 definiert wurden, werden nur bei Wert&auml;nderung aktualisiert.<br>
+		(Default, update_only_difference 0)</li><br>
+		<li>view_Device_name (0,1)<br>
+		Die Aktor Namen welche im xs1 definiert wurden, werden als Reading ausgelesen.<br>
+		(Default, view_Device_name 0)<br>
+		</li><br>
+		<li>view_Device_function (0,1)<br>
+		Die Aktor Funktionen welche im xs1 definiert wurden, werden als Reading ausgelesen.<br>
+		(Default, view_Device_function 0)<br>
+		</li><br>
+		<li>xs1_control (0,1)<br>
+		Die Freigabe zur Steuerung des xs1. Nach Aktivierung dieser, wird durch das xs1Dev Modul jeder Aktor und Sensor in FHEM angelegt.<br>
+		(Default, xs1_control 0)<br>
+		</li><br><br>
+	</ul>
+	<b>Erl&auml;uterung:</b>
+	<ul>
+		<li>Auszug Readings:</li>
+		<ul>
+		<li>Aktor_(01-64)</li> definierter Aktor mit jeweiligem Zustand im Ger&auml;t<br>
+		<li>Aktor_(01-64)_name</li> definierter Aktorname im Ger&auml;t<br>
+		<li>Aktor_(01-64)_function(1-4)</li> definierte Aktorfunktion im Ger&auml;t<br>
+		<li>Sensor_(01-64)</li> definierter Sensor im Ger&auml;t<br>
+		<li>Sensor_(01-64)</li> definierter Sensorname im Ger&auml;t<br>
+		<li>Timer_(01-128)</li> definierter Timer im Ger&auml;t<br>
+		<li>xs1_bootloader</li> Firmwareversion des Bootloaders<br>
+		<li>xs1_dhcp</li> DHCP an/aus<br>
+		<li>xs1_features</li> erworbene Feature beim Kauf (A = SENDEN | B = EMPFANGEN | C = Skripte/Makros | D = Speicherkartenzugriff)<br>
+		<li>xs1_firmware</li> Firmwareversion<br>
+		<li>xs1_start</li> Ger&auml;testart<br>
+		</ul><br>
+		<li>Die Meldung "<code>Error: Can't connect ...</code>" oder "<code>ERROR: empty answer received</code>" im System-Logfile, besagt das kurzzeitig keine Abfrage erfolgen konnte.<br>
+		(Das kann h&auml;ufiger bei DLAN vorkommen.)<br><br></li>
+		<li>Sollte das Ger&auml;t nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !</li><br>
+		<li>Logfile Erstellung erfolgt automatisch nach dem definieren. | Schema: <code>define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log &lt;Name&gt;</code><br>
+			Folgende Werte werden im Logfile erfasst: Timer | xs1-Statusinformationen</li><br>
+		<li>Sollte das Ger&auml;t nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !</li><br>
+	</ul>
+</ul>
+=end html_DE
+=cut

+ 548 - 0
fhem/core/FHEM/88_xs1Dev.pm

@@ -0,0 +1,548 @@
+#################################################################
+# $Id: 88_xs1Dev.pm 16394 2018-03-12 23:09:47Z HomeAuto_User $
+#################################################################
+# logisches Modul - einzelnes Gerät, über das mit physikalisches
+#					Modul kommuniziert werden kann
+# 
+# note / ToDo´s:
+# 
+# 
+# 
+#################################################################
+
+package main;
+
+# Laden evtl. abhängiger Perl- bzw. FHEM-Module
+use strict;				
+use warnings;					# Warnings
+use POSIX;
+use Time::Local;
+use SetExtensions;
+
+
+sub xs1Dev_Initialize($) {
+	my ($hash) = @_;
+	
+	$hash->{Match}			= 	"[x][s][1][D][e][v][_][A][k][t][o][r]_[0-6][0-9].*|[x][s][1][D][e][v][_][S][e][n][s][o][r]_[0-6][0-9].*";				## zum testen - https://regex101.com/
+	
+	$hash->{DefFn}			=	"xs1Dev_Define";
+	$hash->{AttrFn}			= 	"xs1Dev_Attr";
+	$hash->{ParseFn}		= 	"xs1Dev_Parse";
+	$hash->{SetFn}			=	"xs1Dev_Set";
+	$hash->{UndefFn}		=	"xs1Dev_Undef";
+	$hash->{AttrList}		=	"debug:0,1 ".
+								"IODev ".
+								"useSetExtensions:0,1 ".
+								$readingFnAttributes;
+
+	$hash->{AutoCreate}	= { "xs1Dev_Sensor_.*" => { GPLOT => "temp4hum4:Temp/Hum,", FILTER=>"%NAME",  } };
+	
+}
+
+sub xs1Dev_Define($$) {
+	my ($hash, $def) = @_;
+	my @arg = split("[ \t][ \t]*", $def);
+
+   			#				0		1	  2		3     4
+	return "Usage: define <NAME> xs1Dev <Typ> <ID> IODev=  |  wrong number of arguments" if( @arg != 5);
+	return "Usage: define <NAME> xs1Dev <Typ> <ID> IODev=  |  wrong IODev argument" if not ( $arg[4] =~ m/IODev=([^\s]*)[a-zA-Z0-9]/);
+	return "Usage: define <NAME> xs1Dev <Typ> <ID>  |  wrong ID, must be 1-64" if ( $arg[3] <1 || $arg[3] >64);
+	return "Usage: define <NAME> xs1Dev <Typ> <ID>  |  wrong Typ, must be A or S" if ( $arg[2] ne "A" && $arg[2] ne "S");
+
+	splice( @arg, 1, 1 );
+	my $iodev;
+	my $i = 0;
+
+	#### Schleife (Durchlauf der Argumente @arg) wo IODev= gefiltert wird aus define | Dispatch
+	foreach my $param ( @arg ) {
+		if( $param =~ m/IODev=([^\s]*)/ ) {
+				$iodev = $1;
+            splice( @arg, $i, 3 );
+            last;
+        }
+        $i++;
+	}
+	###########################################################################################
+
+	my $name = $hash->{NAME};							## Der Definitionsname, mit dem das Gerät angelegt wurde.
+	my $typ = $hash->{TYPE};							## Der Modulname, mit welchem die Definition angelegt wurde.
+   
+	#Log3 $name, 3, "$typ: Define arguments 0:$arg[0] | 1:$arg[1] | 2:$arg[2] | 3:$arg[3]";
+
+	# Parameter Define
+	my $xs1_ID = $arg[2];			## Zusatzparameter 1 bei Define - ggf. nur in Sub
+	my $xs1_typ1 = $arg[1];			## A || S
+	
+	my $Device = $xs1_typ1.$xs1_ID;						## A02 || S05
+	my $Device_count = 0;
+	my $Device_exist;
+	
+	### Check A02 || S05 bereits definiert
+	foreach my $d (sort keys %defs) {
+		if(defined($defs{$d}) && defined($defs{$d}{ID}) && $defs{$d}{ID} eq $Device) {
+			$Device_count++;
+			$Device_exist = $d;
+			Log3 $name, 3, "$typ: $d $Device_count";
+      }
+	}
+	
+	return "The xs1 <ID> $Device is already definded: $Device_exist" if ($Device_count != 0);
+
+	$hash->{ID} = $xs1_typ1.$xs1_ID;						## A02 || S05
+	$modules{xs1Dev}{defptr}{$xs1_typ1.$xs1_ID} = $hash;	## !!! Adresse rückwärts dem Hash zuordnen (für ParseFn)
+
+	my $debug = AttrVal($hash->{NAME},"debug",0);
+	AttrVal($hash->{NAME},"useSetExtensions",0);
+	
+	$hash->{STATE}			= "Defined";					## Der Status des Modules nach Initialisierung.
+	$hash->{TIME}			= time();						## Zeitstempel, derzeit vom anlegen des Moduls
+	$hash->{VERSION}		= "1.16";						## Version
+	
+	$hash->{xs1_name}		= "undefined";					## Aktor | Sensor Name welcher def. im xs1
+	$hash->{xs1_typ}		= "undefined";					## xs1_Typ switch | hygrometer | temperature ...
+	
+	if ($xs1_typ1 eq "A"){
+		$hash->{xs1_function1}	= "undefined";			## xs1_Funktion zugeordnete Funktion 1
+		$hash->{xs1_function2}	= "undefined";			## xs1_Funktion zugeordnete Funktion 2
+		$hash->{xs1_function3}	= "undefined";			## xs1_Funktion zugeordnete Funktion 3
+		$hash->{xs1_function4}	= "undefined";			## xs1_Funktion zugeordnete Funktion 4
+	}
+
+	# Attribut gesetzt
+	$attr{$name}{room}				= "xs1"	if( not defined( $attr{$name}{room} ) );
+
+	
+	AssignIoPort($hash,$iodev) if( !$hash->{IODev} );		## sucht nach einem passenden IO-Gerät (physikalische Definition)
+	
+	if(defined($hash->{IODev}->{NAME})) {
+		Log3 $name, 4, "xs1Dev: $name - I/O device is " . $hash->{IODev}->{NAME};
+    } 
+	
+	# GENERELL in FHEM auf LOG1 genommen von Rudi
+	# else {
+		# Log3 $name, 3, "xs1Dev: $name - no I/O device";
+    # }
+
+	# if(defined($hash->{IODev}->{xs1_ip})) {				## IP von xs1Bridge - Device aus HASH
+        # $hash->{xs1_ip} = $hash->{IODev}->{xs1_ip};
+	# }
+	return undef;
+}
+
+sub xs1Dev_Attr()
+{
+	my ($cmd,$name,$attrName,$attrValue) = @_;
+	my $hash = $defs{$name};
+	my $typ = $hash->{TYPE};
+	my $debug = AttrVal($hash->{NAME},"debug",0);
+	
+	Debug " $name: Attr | Attributes $attrName = $attrValue" if($debug);
+}
+
+sub xs1Dev_Set ($$@)
+{
+	my ( $hash, $name, @args ) = @_;
+	my $xs1_ID = $hash->{ID};
+	#my $name = $hash->{NAME};
+	my $cmd = $args[0];
+	
+	my $debug = AttrVal($hash->{NAME},"debug",0);
+	my $xs1_typ = $hash->{xs1_typ};
+	my $Aktor_ID = substr($xs1_ID,1,2);			## A01 zu 01
+	my $cmd2;											## notwendig für Switch Funktionsplatz xs1
+	
+	return "no set value specified" if(int(@args) < 1);
+	
+	if ($xs1_typ ne "temperature" && $xs1_typ ne "hygrometer") {
+		my @xs1_function =();		## Funktionen in ARRAY schreiben
+		push (@xs1_function, $hash->{xs1_function1});
+		push (@xs1_function, $hash->{xs1_function2});
+		push (@xs1_function, $hash->{xs1_function3});
+		push (@xs1_function, $hash->{xs1_function4});
+
+		my $cmdList = "";
+		my $cmdListNew = "";
+		my $SetExtensionsReady = 0;
+		
+		foreach (@xs1_function) {			## cmdList aus ARRAY xs1_function zusammenstellen
+			($cmdList)=split(/;/);
+			$cmdListNew .= " ".$cmdList if ($cmdList ne "-");
+			$SetExtensionsReady++ if ($cmdList eq "on" || $cmdList eq "off");
+		}
+	
+		Debug " -------------- ERROR CHECK - START --------------" if($debug);
+		
+		#### Set cmdList bei switch || dimmer || shutter || timerswitch
+		$cmdList = $cmdListNew if($xs1_typ eq "switch" || $xs1_typ eq "dimmer" || $xs1_typ eq "shutter" || $xs1_typ eq "timerswitch");
+		#$cmdList .= "dim:slider,0,6.25,100 dimup dimdown" if ($xs1_typ eq "dimmer");
+		my $cmdFound = index($cmdListNew, $cmd);	## check cmd in cmdListNew
+	
+		Debug " $name: Set | xs1_typ=$xs1_typ SetExtensionsReady=$SetExtensionsReady cmdList=$cmdList" if($debug);
+	
+		if ($cmdList ne "") {			## Set nur bei definierten Typ
+			if(AttrVal($name,"useSetExtensions",undef) || AttrVal($name,"useSetExtensions","0" && $SetExtensionsReady > 0)) {
+				$cmd =~ s/([.?*])/\\$1/g;
+				if($cmdList !~ m/\b$cmd\b/) {
+					unshift @args, $name;
+					return SetExtensions($hash, $cmdList, @args);
+				}
+				SetExtensionsCancel($hash);
+			} else {
+				############## Funktion switch ##############
+				if($xs1_typ eq "switch") {
+					Debug " $name: Set | xs1_function 1=$xs1_function[0] 2=$xs1_function[1] 3=$xs1_function[2] 4=$xs1_function[3]" if($debug);
+					if ($cmdFound >= 0) {	## cmdFound in welchem Funktionsplatz xs1
+						for my $i (0 .. 3) {
+							if ($xs1_function[$i] eq $cmd) {
+								$cmd2 = "function=".($i+1);
+								Debug " $name: Set | cmd=$cmd cmd2=$cmd2 on xs1_function place".($i+1) if($debug);
+							}
+						}
+					}
+					return "Wrong set argument, choose one of $cmdList" if($cmdFound < 0);
+				} 
+				############## Funktion dimmer ##############
+				elsif ($xs1_typ eq "dimmer") {
+
+					Debug " $name: Set | xs1_typ=$xs1_typ cmd=$cmd" if ( not defined ($args[0]) );
+					Debug " $name: Set | xs1_typ=$xs1_typ cmd=$cmd args0=".$args[0] if ( defined ($args[0]) && $cmd ne "?");
+	
+					#return "Unknown argument ?, choose one of $cmdList" if($args[0] eq "?");			### geht - ALT
+					return SetExtensions($hash, $cmdList, $name, $cmd, @args);							### TEST - NEU
+
+					if($cmd eq "dim") {			## dim
+						return "Please value between 0 to 100" if($args[0] !~ /^([0-9]{1,2}+$|^[1][0][0]$|[0-9]{1,2}\.[0-9]{1}$)/);	# 0-100 mit einer Kommastelle
+		
+						$cmd = $cmd.sprintf("%02d", $args[0])."%" if ($args[0] >= 1 && $args[0] <= 9);
+						$cmd = $cmd.$args[0]."%" if (length $args[0] != 1);
+						$cmd = "off" if ($args[0] == 0);				## dim00% als off
+					} elsif ($cmd eq "dimup" || $cmd eq "dimdown") {	## dimup + dimdown
+			
+						if (defined $args[0]) {
+							if ($args[0] >= 0 && $args[0] <= 100) {
+								$cmd = $cmd." ".$args[0];
+							} else {
+								return "value not in range | 0-100";
+							}
+						} else {						## OLD - NEW State auslesen einbauen mit ReadVal - XS! Kontrollieren !!!
+							my $oldState = ReadingsVal($name, "state" , "unknown");
+							(my $TempState) = $oldState =~ /[0-9]{1,2}/g ;
+							my $newState;
+				
+							if ($cmd eq "dimdown" && $TempState >= 1) {
+								$newState = $TempState - 1 ;
+							} elsif ($cmd eq "dimdown" && $TempState <= 99) {
+								$newState = $TempState + 1 if ($cmd eq "dimup");
+							}
+
+							$cmd = $cmd." $newState";
+						}
+					
+					}
+					
+				} 
+				############## Funktion shutter || timerswitch ##############
+				elsif ($xs1_typ eq "shutter" || $xs1_typ eq "timerswitch") {
+					Debug " $name: Set | xs1_function 1=$xs1_function[0] 2=$xs1_function[1] 3=$xs1_function[2] 4=$xs1_function[3]" if($debug);
+					if ($cmdFound >= 0) {	## cmdFound in welchem Funktionsplatz xs1
+						for my $i (0 .. 3) {
+							if ($xs1_function[$i] eq $cmd) {
+								$cmd2 = "function=".($i+1);
+								Debug " $name: Set | cmd=$cmd cmd2=$cmd2 on xs1_function place".($i+1) if($debug);
+							}
+						}
+					}
+					return "Wrong set argument, choose one of $cmdList" if($cmdFound < 0);
+				} 
+				############## alles Andere ##############
+				elsif ($xs1_typ ne "undefined") {
+					Log3 $name, 2, "$name: Set | xs1_typ=$xs1_typ are not supported. Please inform me!";
+					return "xs1_typ=$xs1_typ are not supported. Please inform me!";
+				}
+			}
+		}
+	
+		if(defined($hash->{IODev}->{NAME})) {
+			if ($xs1_typ eq "switch" || $xs1_typ eq "dimmer" || $xs1_typ eq "shutter" || $xs1_typ eq "timerswitch") {
+				Debug " $name: Set IOWrite | xs1_ID=$xs1_ID xs1_typ=$xs1_typ cmd=$cmd cmd2=$cmd2" if($debug && $xs1_typ ne "temperature" && $xs1_typ ne "hygrometer");
+				IOWrite($hash, $xs1_ID, $xs1_typ, $cmd, $cmd2);
+				readingsSingleUpdate($hash, "state", $cmd , 1);			
+			}
+			#else { 
+			#Log3 $name, 2, "$name: Device NOT SUPPORTED for Dispatch. In xs1 disabled.";
+			#}
+		} else {
+			return "no IODev define. Please define xs1Bridge.";
+		}
+
+		Debug " $name: Set | xs1_ID=$xs1_ID xs1_typ=$xs1_typ" if($debug);
+		Debug " -------------- ERROR CHECK - END --------------" if($debug);
+	}
+	
+	return undef;
+}
+
+sub xs1Dev_Parse($$)				## Input Data from 88_xs1Bridge
+{
+	my ( $io_hash, $data) = @_;		## $io_hash = ezControl -> def. Name von xs1Bridge
+
+	my ($xs1Dev,$xs1_readingsname,$xs1_ID,$xs1_typ2,$xs1_value,$xs1_f1,$xs1_f2,$xs1_f3,$xs1_f4,$xs1_name) = split("#", $data);
+	my $xs1_typ1 = substr($xs1_readingsname,0,1);		## A || S
+
+	my $def = $modules{xs1Dev}{defptr}{$xs1_typ1.$xs1_ID};
+	$def = $modules{xs1Dev}{defptr}{$xs1_typ1.$xs1_ID} if(!$def);		## {xs1Dev}{defptr}{A02}
+	
+	my $hash = $def;
+	$hash->{xs1_typ} = $xs1_typ2;
+	$hash->{xs1_name} = $xs1_name;
+	my $IODev = $io_hash->{NAME};
+
+	my $name = $hash->{NAME};			## xs1Dev_Aktor_01
+	my $typ = $hash->{TYPE};			## xs1Dev
+	$typ = "xs1Dev" if (!$def);			## Erstanlegung
+	
+	###### Define and values update ######
+	#Log3 $typ, 3, "$typ: Parse | Data: $xs1Dev | $xs1_readingsname | $xs1_ID | $xs1_typ2 | $xs1_value | $xs1_typ1 | $IODev" if (!$def);
+
+	if(!$def) {
+			# "UNDEFINED xs1Dev_Aktor_12 xs1Dev A 12"
+			Log3 $name, 3, "$typ: Unknown device ".$xs1Dev."_".$xs1_readingsname."_"."$xs1_ID $xs1_ID $xs1_typ1 IODev=$IODev, please define it";
+			return "UNDEFINED xs1Dev"."_".$xs1_readingsname."_"."$xs1_ID xs1Dev $xs1_typ1 $xs1_ID IODev=$IODev";
+	} else {
+		#Log3 $name, 3, "$typ: device $xs1_readingsname"."_"."$xs1_ID xs1_value:$xs1_value xs1_typ2:$xs1_typ2";
+		
+		AssignIoPort($hash, $io_hash);			## sucht nach einem passenden IO-Gerät (physikalische Definition)
+		
+		if ($xs1_readingsname eq "Aktor") {		## zugeordnete xs1_Funktionen
+			$hash->{xs1_function1} = $xs1_f1;
+			$hash->{xs1_function2} = $xs1_f2;
+			$hash->{xs1_function3} = $xs1_f3;
+			$hash->{xs1_function4} = $xs1_f4;
+		}
+		
+		#### Typ switch  | on | off mod for FHEM Default
+		if ($xs1_typ2 eq "switch") {
+			if ($xs1_value == 0) { $xs1_value = "off"; }
+				elsif ($xs1_value == 100) { $xs1_value = "on"; }
+			readingsSingleUpdate($hash, "state", $xs1_value ,1);	# Aktor | Sensor Update value
+			
+			## RegEx devStateIcon da Symbole nicht gleich benannt -> dim_up | dim_down
+			if ($hash->{xs1_function1} eq "dim_up" || $hash->{xs1_function2} eq "dim_up" || $hash->{xs1_function3} eq "dim_up" || $hash->{xs1_function4} eq "dim_up" || 
+				$hash->{xs1_function1} eq "dim_down" || $hash->{xs1_function2} eq "dim_down" || $hash->{xs1_function3} eq "dim_down" || $hash->{xs1_function4} eq "dim_down" ) {
+				$attr{$name}{devStateIcon} = "dim_up:dimup dim_down:dimdown" if( not defined( $attr{$name}{devStateIcon} ) );
+			}
+			
+		} 
+		#### Typ temperature
+		elsif ($xs1_typ2 eq "temperature") {
+			my $xs1_value_new = "T: ".$xs1_value;	## temperature mod for FHEM Default
+
+			readingsBeginUpdate($hash);
+			readingsBulkUpdate($hash, "state", $xs1_value_new);
+			readingsBulkUpdate($hash, "temperature", $xs1_value);
+			readingsEndUpdate($hash, 1);
+		} 
+		#### Typ hygrometer
+		elsif ($xs1_typ2 eq "hygrometer") {
+			my $xs1_value_new = "H: ".$xs1_value;	## hygrometer mod for FHEM Default
+
+			readingsBeginUpdate($hash);
+			readingsBulkUpdate($hash, "state", $xs1_value_new);
+			readingsBulkUpdate($hash, "humidity", $xs1_value);
+			readingsEndUpdate($hash, 1);
+		} 
+		#### Typ dimmer
+		elsif ($xs1_typ2 eq "dimmer") {
+			
+			## RegEx devStateIcon da Symbole nicht durchweg von 0 - 100 | dim_up | dim_down
+			$attr{$name}{devStateIcon} = 	"dim0[1-6]\\D%:dim06% dim[7-9]\\D|dim[1][0-2]%:dim12% dim[1][3-8]%:dim18% \n"
+											."dim[1][9]|dim[2][0-5]%:dim25% dim[2][6-9]|dim[3][0-1]%:dim31% dim[3][2-7]%:dim37% \n"
+											."dim[3][8-9]|dim[4][0-3]%:dim43% dim[4][4-9]|dim[5][0]%:dim50% dim[5][1-6]%:dim56% \n"
+											."dim[5][7-9]|dim[6][0-2]%:dim62% dim[6][3-8]%:dim68% dim[6][9]|dim[7][0-5]%:dim75% \n"
+											."dim[7][6-9]|dim[8][0-1]%:dim81% dim[8][2-7]%:dim87% dim[8][8-9]|dim[9][0-3]%:dim93% \n"
+											."dim[9][4-9]|dim[1][0][0]%:dim100% dim[_][u][p]:dimup dim[_][d][o]:dimdown" if( not defined( $attr{$name}{devStateIcon} ) );
+
+			if ($xs1_value ne "0.0") {
+			$xs1_value = "dim".sprintf("%02d", $xs1_value)."%";
+			} elsif ($xs1_value eq "0.0") {
+			$xs1_value = "off";
+			}
+			
+			readingsSingleUpdate($hash, "state", $xs1_value ,1);
+		}  
+		#### Typ shutter | on | off mod for FHEM Default
+		elsif ($xs1_typ2 eq "shutter") {
+			if ($xs1_value == 0) { $xs1_value = "off"; }
+				elsif ($xs1_value == 100) { $xs1_value = "on"; }
+			readingsSingleUpdate($hash, "state", $xs1_value ,1);
+		}
+		#### Typ timerswitch | on | off mod for FHEM Default
+		elsif ($xs1_typ2 eq "timerswitch") {
+			if ($xs1_value == 0) { $xs1_value = "off"; }
+				elsif ($xs1_value == 100) { $xs1_value = "on"; }
+			readingsSingleUpdate($hash, "state", $xs1_value ,1);
+		}
+	}
+	
+	return $name;
+}
+
+sub xs1Dev_Undef($$)    
+{                     
+	my ( $hash, $name) = @_;
+	my $typ = $hash->{TYPE};
+	
+	delete($modules{xs1Dev}{defptr}{$hash->{ID}});
+	Log3 $name, 3, "$typ: Device with Name $name delete";
+	return undef;
+}
+
+# Eval-Rückgabewert für erfolgreiches
+# Laden des Moduls
+1;
+
+
+# Beginn der Commandref
+
+=pod
+=item summary    Control of the devices which defined in xs1
+=item summary_DE Steuerung des Ger&auml;te welche im xs1 definiert sind
+=begin html
+
+<a name="xs1Dev"></a>
+<h3>xs1Dev</h3>
+<ul>
+	This module works with the xs1Bridge module. (The <code>xs1_control</code> attribute in the xs1Bridge module must be set to 1!) <br>
+	It communicates with this and creates all actuators of the xs1 as a device in FHEM. So you can control the actuators of the xs1 from the FHEM. <br><br>
+	The module was developed based on the firmware version v4-Beta of the xs1. There may be errors due to different adjustments within the manufacturer's firmware.
+	<br><br>
+
+	<a name="xs1Dev_define"></a>
+	<b>Define</b><br>
+		<ul>
+		<code>define &lt;name&gt; xs1Dev &lt;Typ&gt; &lt;ID&gt; IODev=&lt;NAME&gt;</code>
+		<br><br>
+
+		It is not possible to create the module without specifying type and ID of xs1.
+			<ul>
+			<li><code>&lt;ID&gt;</code> is internal id in xs1.</li>
+			</ul>
+			<ul>
+			<li><code>&lt;Typ&gt;</code> is the abbreviation A for actuators or S for sensors.</li>
+			</ul><br>
+		example:
+		<ul>
+		define xs1Dev_Aktor_02 xs1Dev A 02 IODev=ezControl
+		</ul>	
+		</ul><br>
+	<b>Set</b>
+	<ul><code>set &lt;name&gt; &lt;value&gt; </code></ul><br>
+	in which <code>value</code> one of the following values:<br>
+	<ul><code>
+      dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%<br>
+      dimdown<br>
+      dimup<br>
+      dimupdown<br>
+      off<br>
+      off-for-timer<br>
+      on<br>
+      on-for-timer<br>
+    </code></ul><br>
+	<b>Get</b><br>
+	<ul>N/A</ul><br>
+	<a name="xs1_attr"></a>
+	<b>Attributes</b>
+	<ul>
+		<li>debug (0,1)<br>
+		This brings the module into a very detailed debug output in the logfile. Thus, program parts can be controlled and errors can be checked.<br>
+		(Default, debug 0)
+		</li>
+		<li>useSetExtensions (0,1)<br>
+		Toggles the SetExtensions on or off.<br>
+		(Default, useSetExtensions 0)
+		</li>
+	</ul><br>
+	<b>Explanation:</b>
+	<ul>
+		<li>abstract Internals:</li>
+		<ul>
+		xs1_function(1-4): defined function in the device<br>
+		xs1_name: defined name in the device<br>
+		xs1_typ: defined type in the device<br>
+		</ul><br>
+	
+	<li>The following xs1 device types are already integrated: dimmer | shutter | switch | timerswitch</li>
+	</ul>
+</ul>
+=end html
+=begin html_DE
+
+<a name="xs1Dev"></a>
+<h3>xs1Dev</h3>
+<ul>
+	Dieses Modul arbeitet mit dem Modul xs1Bridge zusammen. (Das Attribut <code>xs1_control</code> im Modul xs1Bridge muss auf 1 gestellt sein!) <br>
+	Es kommuniziert mit diesem und legt sämtliche Aktoren des xs1 als Device im FHEM an. So kann man vom FHEM aus, die Aktoren der xs1 steuern.
+	<br><br>
+	Das Modul wurde entwickelt basierend auf dem Firmwarestand v4-Beta des xs1. Es kann aufgrund von unterschiedlichen Anpassungen innerhalb der Firmware des Herstellers zu Fehlern kommen.
+	<br><br>
+
+	<a name="xs1Dev_define"></a>
+	<b>Define</b><br>
+		<ul>
+		<code>define &lt;name&gt; xs1Dev &lt;Typ&gt; &lt;ID&gt; IODev=&lt;NAME&gt;</code>
+		<br><br>
+
+		Ein anlegen des Modules ohne Angabe des Typ und der ID vom xs1 ist nicht möglich.
+			<ul>
+			<li><code>&lt;ID&gt;</code> ist interne ID im xs1.</li>
+			</ul>
+			<ul>
+			<li><code>&lt;Typ&gt;</code> ist der Kürzel A für Aktoren oder S für Sensoren.</li>
+			</ul><br>
+		Beispiel:
+		<ul>
+		define xs1Dev_Aktor_02 xs1Dev A 02 IODev=ezControl
+		</ul>	
+		</ul><br>
+	<b>Set</b>
+	<ul><code>set &lt;name&gt; &lt;value&gt; </code></ul><br>
+	Wobei <code>value</code> einer der folgenden Werte sein kann:<br>
+	<ul><code>
+      dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50% dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%<br>
+      dimdown<br>
+      dimup<br>
+      dimupdown<br>
+      off<br>
+      off-for-timer<br>
+      on<br>
+      on-for-timer<br>
+    </code></ul><br>
+	<b>Get</b><br>
+	<ul>N/A</ul><br>
+	<a name="xs1_attr"></a>
+	<b>Attribute</b>
+	<ul>
+		<li>debug (0,1)<br>
+		Dies bringt das Modul in eine sehr ausf&uuml;hrliche Debug-Ausgabe im Logfile. Somit lassen sich Programmteile kontrollieren und Fehler &uuml;berpr&uuml;fen.<br>
+		(Default, debug 0)
+		</li>
+		<li>useSetExtensions (0,1)<br>
+		Schaltet die SetExtensions ein bzw. aus.<br>
+		(Default, useSetExtensions 0)
+		</li>
+	</ul><br>
+	<b>Erl&auml;uterung:</b>
+	<ul>
+		<li>Auszug Internals:</li>
+		<ul>
+		xs1_function(1-4): definierte Funktion im Ger&auml;t<br>
+		xs1_name: definierter Name im Ger&auml;t<br>
+		xs1_typ: definierter Typ im Ger&auml;t<br>
+		</ul><br>
+	
+	<li>Folgende xs1-Ger&aumltetypen sind bereits integriert: dimmer | shutter | switch | timerswitch</li>
+	</ul>
+		
+</ul>
+=end html_DE
+=cut

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 2256 - 0
fhem/core/FHEM/95_Babble.pm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1701 - 0
fhem/core/FHEM/97_PiXtendV2.pm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1104 - 0
fhem/core/FHEM/98_freezemon.pm


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 5278 - 0
fhem/core/FHEM/lib/74_AMADautomagicFlowset_4.2.0.xml


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 11136 - 0
fhem/core/FHEM/lib/74_AMADtaskerset_4.2.0.prj.xml


+ 287 - 0
fhem/core/FHEM/lib/EnO_ReCom_Device_Descr.xml

@@ -0,0 +1,287 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Enocean_Devices xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" schemaVersion="1.0"
+   xsi:noNamespaceSchemaLocation="./ReCom_Device_Description_Schema.xsd">
+  <Device Product_ID="0x004900000000">
+		<Version>0.0.0.7</Version>
+    <Name>Micropelt MVA004(U)</Name>
+    <Description>Intelligent Thermostatic Radiator Valve</Description>
+    <Frequency>868</Frequency>
+    <TX>
+      <!-- The Profiles we send based on our chip id -->
+      <ChipIDBased>
+        <EEP>
+          <Rorg>0xA5</Rorg>
+          <Func>0x20</Func>
+          <Type>0x01</Type>
+        </EEP>
+      </ChipIDBased>
+    </TX>
+    <RX>
+      <!-- The Profiles we send based on our chip id -->
+      <ChipIDBased>
+        <EEP>
+          <Rorg>0xA5</Rorg>
+          <Func>0x20</Func>
+          <Type>0x01</Type>
+        </EEP>
+      </ChipIDBased>
+    </RX>
+    <!-- ReMan and ReComm commands -->
+    <ReMan>
+    	<Cmd CmdId="0x0001">
+    		<Description>Unlock - RM_FN_UNLOCK</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0002">
+    		<Description>Lock - RM_FN_LOCK</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0003">
+    		<Description>Set security - RM_FN_SET_CODE</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0004">
+    		<Description>Query ID - RM_FN_QUERY_ID</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0006">
+    		<Description>Ping - RM_FN_PING_COMMAND</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0007">
+    		<Description>Query supported RPC functions - RM_FN_QUERY_FUNCTION_COMMAND</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0008">
+    		<Description>Query last Status - RM_FN_QUERY_STATUS</Description>
+    	</Cmd>
+    </ReMan>
+    <ReComm>
+    	<Cmd CmdId="0x0210">
+    		<Description>Get Link Table Metadata Query</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0211">
+    		<Description>Get Link Table Query</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0212">
+    		<Description>Set Link Table Content</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0227">
+    		<Description>Get Product Id Query</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0230">
+    		<Description>Get Device Configuration Query</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0231">
+    		<Description>Set Device Configuration Query</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0310">
+    		<Description>Get Firmware Version Query</Description>
+    	</Cmd>
+    	<Cmd CmdId="0x0224">
+    		<Description>Reset Device Defaults</Description>
+    	</Cmd>
+    </ReComm>
+    <!-- The Recom Device Parameters -->
+    <Device_Parameters>
+      <!-- Here we list the different paramaters,  -->
+      <Parameters>
+        <!-- XML/XSD can not check if we use a index only once, this is up to the user! -->
+        <Param index="0">
+          <Description>Ambient to target temp offset [K]</Description>
+          <Enum>
+	        <Length_In_Bytes>1</Length_In_Bytes>
+            <Default_Value>0</Default_Value>
+            <!--  The description of the enum -->
+            <EnumList>
+              <Enum_Value index="0">
+                <Description>Auto</Description>
+              </Enum_Value>
+              <Enum_Value index="1">
+                <Description>-3K</Description>
+              </Enum_Value>
+              <Enum_Value index="2">
+                <Description>-2K</Description>
+              </Enum_Value>
+              <Enum_Value index="3">
+                <Description>-1K</Description>
+              </Enum_Value>
+              <Enum_Value index="4">
+                <Description>0K</Description>
+              </Enum_Value>
+              <Enum_Value index="5">
+                <Description>+1K</Description>
+              </Enum_Value>
+              <Enum_Value index="6">
+                <Description>+2K</Description>
+              </Enum_Value>
+              <Enum_Value index="7">
+                <Description>+3K</Description>
+              </Enum_Value>
+              <Enum_Value index="8">
+                <Description>+4K</Description>
+              </Enum_Value>
+              <Enum_Value index="9">
+                <Description>+5K</Description>
+              </Enum_Value>
+              <Enum_Value index="10">
+                <Description>+6K</Description>
+              </Enum_Value>
+              <Enum_Value index="11">
+                <Description>+7K</Description>
+              </Enum_Value>
+              <Enum_Value index="12">
+                <Description>+8K</Description>
+              </Enum_Value>
+              <Enum_Value index="13">
+                <Description>+9K</Description>
+              </Enum_Value>
+              <Enum_Value index="14">
+                <Description>+10K</Description>
+              </Enum_Value>
+              <Enum_Value index="15">
+                <Description>+11K</Description>
+              </Enum_Value>
+            </EnumList>
+          </Enum>
+        </Param>
+        <Param index="1">
+          <Description>Radio communication interval [s/min]</Description>
+          <Enum>
+	        <Length_In_Bytes>1</Length_In_Bytes>
+            <Default_Value>4</Default_Value>
+            <!--  The description of the enum -->
+            <EnumList>
+              <Enum_Value index="0">
+                <Description>Auto</Description>
+              </Enum_Value>
+              <Enum_Value index="2">
+                <Description>2min</Description>
+              </Enum_Value>
+              <Enum_Value index="3">
+                <Description>5min</Description>
+              </Enum_Value>
+              <Enum_Value index="4">
+                <Description>10min</Description>
+              </Enum_Value>
+            </EnumList>
+          </Enum>
+        </Param>
+        <Param index="2">
+          <Description>Safe Mode Setting [%]</Description>
+          <Scaled>
+           <Length_In_Bytes>1</Length_In_Bytes>
+           <Default_Value>50</Default_Value>
+           <Range>
+            <Min>0</Min>
+            <Max>100</Max>
+           </Range>
+           <Scale>
+            <Min>0</Min>
+            <Max>100</Max>
+           </Scale>
+           <Unit></Unit>
+          </Scaled>
+        </Param>
+        <Param index="5">
+          <Description>Safe mode communication period [h]</Description>
+          <Enum>
+	        <Length_In_Bytes>1</Length_In_Bytes>
+            <Default_Value>1</Default_Value>
+            <!--  The description of the enum -->
+            <EnumList>
+              <Enum_Value index="0"> <Description>0.5</Description> </Enum_Value>
+              <Enum_Value index="1"> <Description>1</Description> </Enum_Value>
+              <Enum_Value index="2"> <Description>2</Description> </Enum_Value>
+              <Enum_Value index="3"> <Description>4</Description> </Enum_Value>
+              <Enum_Value index="4"> <Description>8</Description> </Enum_Value>
+              <Enum_Value index="5"> <Description>24</Description> </Enum_Value>
+              <Enum_Value index="6"> <Description>48</Description> </Enum_Value>
+              <Enum_Value index="7"> <Description>96</Description> </Enum_Value>
+           </EnumList>
+          </Enum>
+        </Param>
+        <Param index="9">
+          <Description>EnOcean transmitt ID (Base ID)</Description>
+          <Scaled>
+           <Length_In_Bytes>4</Length_In_Bytes>
+           <!--  Default value is the already scaled value, makes it easier readable/editable -->
+           <Default_Value>0xFFFFFFFF</Default_Value>
+           <Range>
+            <Min>0xFF800000</Min>
+            <Max>0xFFFFFFFF</Max>
+           </Range>
+           <Scale>
+            <Min>0xFF800000</Min>
+            <Max>0xFFFFFFFF</Max>
+           </Scale>
+           <Unit></Unit>
+          </Scaled>
+        </Param>
+        <Param index="11">
+          <Description>Offset parameter</Description>
+          <Scaled>
+           <Length_In_Bytes>1</Length_In_Bytes>
+           <Default_Value>91</Default_Value>
+           <Range>
+            <Min>0</Min>
+            <Max>255</Max>
+           </Range>
+           <Scale>
+            <Min>0</Min>
+            <Max>255</Max>
+           </Scale>
+           <Unit></Unit>
+          </Scaled>
+        </Param>
+        <Param index="12">
+          <Description>p-Parameter int. regulator</Description>
+          <Scaled>
+           <Length_In_Bytes>1</Length_In_Bytes>
+           <Default_Value>30</Default_Value>
+           <Range>
+            <Min>0</Min>
+            <Max>255</Max>
+           </Range>
+           <Scale>
+            <Min>0</Min>
+            <Max>255</Max>
+           </Scale>
+           <Unit></Unit>
+          </Scaled>
+        </Param>
+        <Param index="13">
+          <Description>Battery open-circuit voltage (Read Only)</Description>
+          <Scaled>
+           <Length_In_Bytes>1</Length_In_Bytes>
+           <Default_Value>0</Default_Value>
+           <Range>
+            <Min>0</Min>
+            <Max>255</Max>
+           </Range>
+           <Scale>
+            <Min>0</Min>
+            <Max>255</Max>
+           </Scale>
+           <Unit></Unit>
+          </Scaled>
+        </Param>
+      </Parameters>
+    </Device_Parameters>
+  </Device>
+</Enocean_Devices>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 30 - 0
fhem/core/db.conf

@@ -0,0 +1,30 @@
+#
+# database configuration file
+#
+#
+## for MySQL
+################################################################
+%dbconfig= (
+	connection => "mysql:database=fhem;host=fhem-mysql;port=3306",
+	user => "fhemuser",
+	password => "2jRHnEi3WuNSQAcX7",
+);
+################################################################
+#
+## for PostgreSQL
+################################################################
+#%dbconfig= (
+#        connection => "Pg:database=fhem;host=localhost",
+#        user => "fhemuser",
+#        password => "fhempassword"
+#);
+################################################################
+#
+## for SQLite (username and password stay empty for SQLite)
+################################################################
+#%dbconfig= (
+#        connection => "SQLite:dbname=/opt/fhem/fhem.db",
+#        user => "",
+#        password => ""
+#);
+################################################################

+ 83 - 0
fhem/core/www/images/fhemSVG/logic.svg

@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1"
+	 id="icon" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:svg="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:ns1="http://sozi.baierouge.fr"
+	 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="831.14px" height="802px"
+	 viewBox="6 85.445 831.14 802" enable-background="new 6 85.445 831.14 802" xml:space="preserve">
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M550.233,240.119l-0.09-0.328c-6.715-5.197-14.14-9.143-22.459-12.489l0.091,0.327c-4.396,5.789-8.7,11.905-12.679,17.931
+	c-2.652-0.679-4.887-1.12-7.122-1.563c-2.98-0.588-5.961-1.177-9.088-1.021c-1.072-0.058-2.144-0.114-3.126,0.156
+	c-2.638-7.024-5.274-14.048-8.241-20.981c-3.454,0.246-6.729,1.147-10.421,1.811l-5.238,1.442c-3.186,1.228-6.132,2.039-8.898,3.505
+	c0.909,7.146,2.237,14.531,3.892,21.825c-1.31,0.361-2.201,0.958-3.422,1.646c-0.328,0.09-0.655,0.181-0.893,0.598
+	c-2.439,1.376-4.697,3.406-6.957,5.437c-2.022,1.614-3.717,3.136-5.321,4.987c-6.828-3.053-13.238-5.867-20.066-8.92l-0.418-0.236
+	c-1.039,1.341-2.079,2.686-2.791,3.938c-1.841,2.267-3.266,4.772-4.362,7.188c-2.226,3.431-3.617,7.336-5.337,11.332l0.329-0.09
+	c5.788,4.396,11.814,8.373,17.932,12.679c-0.771,2.325-1.449,4.978-1.893,7.212c-0.588,2.979-0.758,6.197-0.693,8.997
+	c-0.293,1.49-0.262,2.89,0.01,3.873c-7.024,2.637-14.048,5.274-20.654,8.148c0.246,3.456,0.82,6.821,1.811,10.422
+	c1.353,4.912,3.031,9.734,5.276,14.048c7.057-1.237,14.112-2.475,21.497-3.802c0.36,1.31,1.048,2.529,1.974,3.332
+	c1.465,2.767,3.169,5.116,5.29,7.702c1.94,1.933,3.555,3.955,5.732,5.469c-3.055,6.828-6.197,13.33-8.92,20.067
+	c6.804,5.524,14.318,9.797,22.548,12.816l-0.092-0.329c4.067-5.698,8.374-11.814,12.353-17.84l0.327-0.09
+	c1.907,0.532,4.144,0.973,6.051,1.505c3.307,0.499,6.941,0.908,10.069,0.751c1.398-0.034,2.473,0.024,3.544,0.081
+	c2.549,6.697,5.185,13.72,8.151,20.655c3.035-0.483,6.073-0.967,9.348-1.868l4.912-1.352c3.602-0.991,7.113-2.31,10.535-3.957
+	c-1.237-7.056-2.565-14.44-3.893-21.825c0.892-0.598,1.785-1.195,2.677-1.793c2.768-1.466,5.116-3.17,7.375-5.2
+	c2.023-1.614,3.954-3.555,5.796-5.821c0.09,0.326,0.09,0.326,0.418,0.236c6.5,3.143,13.329,6.196,20.065,8.921
+	c0.804-0.926,1.605-1.85,2.646-3.194c1.514-2.177,2.938-4.684,4.034-7.099c2.136-3.757,4.183-7.842,5.81-12.166
+	c-6.115-4.306-12.231-8.61-18.259-12.588l-0.089-0.327c0.533-1.909,0.973-4.143,1.416-6.377c0.262-2.89,0.85-5.87,0.783-8.67
+	c-0.09-0.327-0.18-0.655,0.147-0.745c-0.36-1.31-0.303-2.382-0.155-3.128c7.023-2.637,14.047-5.274,20.743-7.822
+	c-0.246-3.455-1.147-6.729-1.72-10.094l-1.531-5.567c-1.23-3.185-2.131-6.459-3.688-9.553c-7.057,1.236-14.44,2.564-21.825,3.892
+	c-0.598-0.893-1.195-1.785-1.466-2.767c-1.794-2.677-3.587-5.353-5.708-7.941c-1.523-1.694-3.464-3.626-4.987-5.32
+	C544.128,253.775,547.181,246.947,550.233,240.119L550.233,240.119z M470.281,291.357c4.183-7.844,10.575-12.773,18.853-14.699
+	c8.423-2.67,16.111-1.616,24.043,2.895c7.519,4.272,12.447,10.665,14.7,18.852c2.344,8.513,1.289,16.201-2.895,24.044
+	c-4.601,7.606-10.665,12.446-19.18,14.788c-8.186,2.253-16.2,1.288-23.717-2.984c-7.606-4.599-12.445-10.664-14.789-19.179
+	C465.045,306.888,466.01,298.874,470.281,291.357L470.281,291.357z"/>
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M418.226,361.482c-1.45-0.455-2.51-0.628-3.516-0.466c-4.187-0.356-8.318-0.378-12.449-0.401
+	c-2.229,9.647-3.842,18.852-5.454,28.056l-6.035,0.973c-3.97,0.984-7.604,1.914-11.185,3.179c-2.964,0.821-5.819,2.313-8.674,3.806
+	c-6.609-6.848-13.553-13.641-20.105-20.152l-0.39-0.282c-1.903,0.995-3.808,1.99-5.321,3.266c-3.138,1.882-5.885,4.044-8.578,6.543
+	c-4.315,3.103-8.188,6.823-11.617,11.16l0.054,0.335c5.375,7.735,10.414,15.522,15.789,23.256c-1.97,2.726-3.657,5.062-5.235,8.067
+	c-2.251,3.115-4.057,6.846-5.528,10.523c-0.79,1.504-1.244,2.953-1.645,4.738c-9.659-0.164-19.317-0.327-28.922-0.156
+	c-1.028,4.295-2.056,8.59-2.694,13.164c-0.26,6.923-0.575,13.51-0.219,19.987l27.775,5.844l0.756,4.694
+	c0.596,3.688,1.578,7.658,3.233,11.52c0.876,3.299,2.422,6.49,3.968,9.68c-6.847,6.608-13.359,13.162-20.152,20.106
+	c5.646,9.409,12.858,17.878,20.97,25.517l0.335-0.055c7.734-5.374,15.521-10.414,23.592-15.844c2.336,1.688,4.617,3.041,6.954,4.729
+	c3.45,2.196,7.517,3.948,11.919,5.646c1.113,0.509,2.563,0.963,4.066,1.753c-0.218,9.323-0.046,18.928,0.127,28.532
+	c3.958,1.082,7.863,1.829,12.049,2.186c1.06,0.174,3.18,0.52,6.641,0.651c4.91,0.584,9.712,0.497,14.18,0.465
+	c2.229-9.646,4.177-18.905,5.735-28.444l4.358-0.702c4.023-0.648,7.658-1.578,11.238-2.845c3.245-1.211,6.436-2.757,9.627-4.303
+	c0,0,0.054,0.335,0.389,0.281c6.608,6.847,13.552,13.64,20.159,20.487c1.515-1.277,3.083-2.217,4.315-3.104
+	c3.137-1.882,5.885-4.044,8.632-6.207c4.543-3.828,8.697-7.938,12.461-12.329c-5.093-8.123-10.468-15.857-15.896-23.926
+	c1.688-2.337,3.039-4.618,4.728-6.955c1.86-3.396,3.667-7.127,5.192-10.469c-0.054-0.336,0.228-0.726,0.173-1.061
+	c0.509-1.113,0.963-2.563,1.472-3.677c9.658,0.164,19.264-0.009,28.922,0.155c1.028-4.295,1.721-8.534,2.359-13.109
+	c0.173-1.06,0.347-2.121,0.465-3.516c0.174-1.06,0.012-2.065,0.466-3.515c-0.032-4.467,0.271-8.987-0.042-13.066
+	c-9.312-2.282-18.852-3.842-28.11-5.789l-0.648-4.023c-0.648-4.023-1.686-8.328-3.34-12.189c-0.822-2.964-2.314-5.82-3.472-8.729
+	c6.847-6.607,13.641-13.551,20.099-20.44l0.335-0.054c-5.645-9.41-12.523-17.933-21.305-25.463
+	c-8.124,5.094-15.857,10.469-23.591,15.843c-2.726-1.968-5.451-3.938-8.123-5.571c-3.396-1.859-6.792-3.72-10.415-4.856
+	c-1.168-0.844-2.617-1.299-3.731-1.807c-0.172-9.605-0.008-19.263-0.181-28.868c-4.294-1.028-8.923-2.002-13.497-2.641
+	C420.626,361.439,419.231,361.32,418.226,361.482L418.226,361.482z M376.073,496.937c-7.269-8.806-10.23-18.648-8.942-29.864
+	c0.953-11.162,6.027-20.236,14.832-27.503c8.523-6.879,18.702-9.895,29.528-8.888c11.216,1.288,20.571,5.972,27.503,14.831
+	c7.268,8.806,10.23,18.647,8.941,29.864c-0.952,11.161-6.026,20.235-14.55,27.113c-9.14,7.32-18.982,10.285-29.863,8.942
+	C392.36,510.479,382.95,505.459,376.073,496.937L376.073,496.937z"/>
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M376.833,863.947c-10.201-3.136-20.891-5.212-30.506-9.625
+	c-22.279-10.224-36.49-28.065-44.682-51.021c-0.931-2.61-3.305-5.401-5.753-6.662c-34.416-17.73-47.451-51.527-33.91-88.079
+	c0.851-2.302,0.854-5.518-0.059-7.778c-6.891-17.027-7.165-34.028-0.852-51.246c0.699-1.908,1.37-4.635,0.543-6.167
+	c-6.043-11.214-6.807-23.569-8.372-35.772c-3.642-28.387-13.557-54.593-27.073-79.676c-11.207-20.791-22.753-41.487-32.446-62.992
+	c-15.659-34.743-19.983-71.724-18.435-109.594c1.542-37.64,11.259-73.181,29.143-106.045
+	c26.167-48.083,63.902-84.512,113.679-108.25c28.371-13.53,58.114-21.196,89.299-23.836c1.367-0.115,2.705-0.569,4.055-0.866
+	c9.952,0,19.905,0,29.856,0c7.634,1.15,15.263,2.324,22.899,3.444c41.795,6.138,79.315,22.335,112.902,47.852
+	c44.536,33.831,74.981,77.611,89.911,131.377c16.295,58.676,13.143,116.656-11.918,172.582
+	c-8.104,18.087-17.945,35.439-27.72,52.717c-17.301,30.576-27.993,63.047-31.002,98.134c-0.53,6.172-3.736,12.075-5.229,18.219
+	c-0.739,3.043-1.367,6.697-0.373,9.475c6.047,16.922,5.435,33.489-0.943,50.16c-1.002,2.616-1.029,6.268-0.041,8.887
+	c13.279,35.21,0.305,69.574-33.239,86.721c-3.876,1.98-5.784,4.518-7.271,8.521c-12.346,33.272-36.241,52.708-71.217,58.595
+	c-0.739,0.125-1.418,0.61-2.127,0.928C442.915,863.947,409.874,863.947,376.833,863.947z M425.711,633.434c1.99,0,3.979,0,5.971,0
+	c32.042-0.005,64.084,0.076,96.125-0.051c13.539-0.053,20.356-6.591,22.758-19.486c3.633-19.514,6.979-39.163,12.19-58.277
+	c7.488-27.465,22.471-51.674,36.355-76.231c16.237-28.718,27.922-58.746,30.283-92.081c2.455-34.736-1.783-68.37-15.683-100.363
+	c-14.109-32.472-35.193-59.769-63.387-81.407c-25.683-19.712-54.416-32.742-86.193-38.803c-37.412-7.137-74.046-3.965-109.756,9.318
+	c-33.911,12.61-62.529,32.956-85.612,60.654c-19.972,23.963-33.825,51.227-41.264,81.739c-7.532,30.895-7.609,61.834-1.978,92.926
+	c5.341,29.484,19.597,55.269,34.135,80.882c11.797,20.784,23.546,41.707,29.654,64.908c5.046,19.169,7.813,38.931,12.22,58.283
+	c1.072,4.705,3.604,10.312,7.29,12.873c4.527,3.146,10.955,4.835,16.588,4.906C358.839,633.654,392.277,633.435,425.711,633.434z
+	 M426.163,714.722c-32.41,0-64.82-0.025-97.23,0.034c-3.359,0.008-6.804,0.134-10.062,0.864
+	c-14.221,3.188-20.275,19.666-11.872,31.554c5.502,7.786,13.467,9.113,22.093,9.111c64.422-0.004,128.845,0.012,193.267-0.04
+	c3.358-0.002,6.787-0.219,10.061-0.916c14.288-3.052,20.644-19.342,12.361-31.381c-5.038-7.323-12.386-9.284-20.79-9.265
+	C491.382,714.769,458.774,714.719,426.163,714.722z M425.789,653.686c-30.449,0-60.896-0.003-91.343,0.003
+	c-3.78,0-7.568-0.089-11.342,0.079c-9.064,0.409-16.65,6.19-19.18,14.456c-2.537,8.301,0.289,17.883,8.137,22.449
+	c4.366,2.54,10.106,3.771,15.233,3.791c65.471,0.237,130.942,0.171,196.414,0.096c3.543-0.003,7.208-0.295,10.605-1.221
+	c11.586-3.156,17.647-17.172,12.178-27.859c-4.233-8.274-11.135-11.873-20.408-11.839
+	C492.652,653.761,459.219,653.689,425.789,653.686z M344.797,776.297c-1.517,20.658,15.722,39.937,37.497,40.611
+	c29.192,0.896,58.445,0.73,87.646-0.021c18.265-0.47,33.318-14.789,36.858-32.702c1.496-7.582,1.013-8.188-6.771-8.188
+	c-49.503-0.008-99.006-0.008-148.507,0.011C349.368,776.008,347.218,776.187,344.797,776.297z"/>
+</svg>

+ 59 - 0
fhem/core/www/images/fhemSVG/mqtt.svg

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="562px"
+	 height="514px" viewBox="0 0 562 514" enable-background="new 0 0 562 514" xml:space="preserve">
+<g id="Ebene_1">
+	<g>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M74.828,332.363c0,0-0.214-0.322-0.281-0.438
+			c-11.177-19.108-19.123-39.393-24.77-60.692c-7.089-26.744-8.035-54.223-6.096-81.668c1.094-15.489,3.336-31.045,8.29-45.85
+			c4.974-14.867,10.394-29.595,17.816-43.549C77.813,85.08,87.417,71.132,97.94,57.758c4.512-5.734,10.142-10.482,14.485-16.401
+			c1.706-2.325,3.867-1.675,5.952,0.236c11.232,10.296,22.543,20.506,33.848,30.722c6.882,6.219,13.762,12.442,20.723,18.572
+			c3.097,2.727,3.243,4.625,0.008,7.843c-17.571,17.476-30.428,37.872-38.795,61.38c-6.863,19.283-9.444,38.985-8.704,59.158
+			c0.61,16.625,3.577,32.928,9.95,48.472c2.75,6.706,5.358,13.453,9.037,19.734c2.767,4.725,3.25,4.894,7.276,1.396
+			c4.01-3.484,7.835-7.2,12.018-10.46c3.107-2.421,2.176-5.207,0.897-7.563c-12.321-22.689-15.232-47.173-13.156-72.324
+			c1.958-23.725,10.21-45.309,24.506-64.567c4.758-6.41,9.923-12.39,15.402-18.139c4.19-4.397,4.881-4.249,9.277-0.291
+			c16.749,15.078,33.562,30.084,50.393,45.069c3.501,3.117,3.425,4.351-0.245,7.156c-10.582,8.087-17.463,18.317-20.735,31.577
+			c-3.401,13.782-1.167,26.419,4.944,38.499c5.524,10.919,14.373,18.947,25.589,23.997c4.219,1.9,6.683,4.143,6.205,9.222
+			c-0.467,4.97,0.895,9.904,1.446,14.856c0.566,5.091-1.093,6.559-6.009,4.944c-13.014-4.276-24.989-10.291-35.044-20.001
+			c-11.146-10.764-19.146-23.474-22.857-38.278c-2.634-10.511-3.57-21.372-1.688-32.583c1.944-11.586,5.962-21.964,11.972-31.804
+			c1.09-1.785,1.165-3.313-0.593-4.812c-3.909-3.334-7.841-6.655-11.548-10.207c-2.281-2.185-3.846-1.356-5.239,0.695
+			c-9.489,13.968-15.2,29.426-16.831,46.167c-0.788,8.086-0.788,16.385,0.045,24.464c1.722,16.698,7.429,32.119,17.066,45.982
+			c0.847,1.218,1.573,2.522,2.453,3.714c4.5,6.096,4.457,6.032-0.766,11.104c-16.64,16.161-35.656,29.614-52.199,45.884
+			c-2.762,2.716-5.276,2.104-7.553-0.794c-15.891-20.23-28.115-42.32-34.954-67.291c-4.542-16.585-7.1-33.353-6.313-51.784
+			c-1.261-15.059,0.99-31.102,5.065-46.688c4.564-17.453,11.417-34.158,21.262-49.491c3.645-5.677,6.223-12.024,10.673-17.214
+			c1.902-2.218,1.409-4.24-0.634-6.038c-4.092-3.601-8.316-7.06-12.278-10.798c-2.667-2.517-4.819-1.938-6.615,0.664
+			c-5.82,8.431-11.865,16.706-16.941,25.644c-6.2,10.917-11.634,22.233-16.115,33.9c-4.258,11.087-7.344,22.582-9.338,34.42
+			c-3.124,18.552-2.648,37.127-1.924,55.746c0.598,15.367,4.501,30.037,9.012,44.695c5.344,17.367,13.141,33.436,22.512,48.877
+			c1.354,2.23,2.746,4.434,4.19,6.595c0.129,0.193,0.343,0.525,0.473,0.718L74.828,332.363z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M490.984,332.363c0,0,0.222-0.388,0.311-0.542
+			c4.755-8.291,9.066-16.859,13.014-25.672c7.9-17.638,13.271-36.002,16.461-54.938c2.177-12.923,3.125-26.074,2.294-37.845
+			c0.553-21.081-1.361-40.483-6.016-59.646c-5.469-22.514-14.152-43.692-25.812-63.654c-10.313-17.659-23.046-33.502-37.043-48.361
+			c-2.174-2.308-3.937-2.024-6.284,0.109c-17.786,16.158-35.7,32.176-53.558,48.255c-4.534,4.083-4.647,5.335-0.253,9.552
+			c8.646,8.295,15.929,17.648,22.421,27.688c11.198,17.316,18.662,36.274,22.207,56.387c2.755,15.631,3.566,31.504,1.254,47.633
+			c-2.835,19.776-8.68,38.257-17.961,55.791c-2.367,4.472-3.779,4.778-7.517,1.542c-4.139-3.584-8.112-7.358-12.227-10.971
+			c-2.028-1.781-1.946-3.709-0.719-5.782c7.654-12.923,11.135-27.244,13.026-41.861c2.498-19.299,1.347-38.621-4.378-57.239
+			c-6.927-22.528-19.378-41.888-36.472-58.228c-2.481-2.372-4.336-2.366-6.766-0.11c-6.437,5.975-12.983,11.832-19.503,17.716
+			c-10.831,9.774-21.66,19.55-32.527,29.283c-2.436,2.182-2.933,3.475,0.434,5.67c8.633,5.631,14.373,13.658,18.635,23.113
+			c12.669,28.105-2.076,60.651-28.274,71.926c-3.226,1.388-5.517,3.28-5.756,7.647c-0.272,4.964-0.596,9.95-0.786,14.927
+			c-0.213,5.602,1.309,6.628,6.744,5.003c14.044-4.199,26.188-11.546,36.262-22.181c14.798-15.622,22.104-34.622,22.341-55.782
+			c0.167-15.016-4.104-29.494-12.189-42.415c-1.997-3.19-1.383-5.173,0.914-7.292c3.642-3.36,7.419-6.574,11.049-9.947
+			c1.866-1.734,3.477-1.049,4.587,0.558c9.536,13.801,15.829,29.121,17.76,45.655c3.364,28.809-3.721,55.165-21.68,78.232
+			c-2.353,3.022-1.588,4.866,0.837,7.032c17.865,15.957,35.723,31.923,53.52,47.958c2.888,2.602,4.969,2.794,7.658-0.472
+			c10.023-12.168,18.794-25.114,25.445-39.433c5.31-11.432,8.383-23.553,11.374-35.757c3.182-12.984,4.715-26.128,5.086-39.392
+			c0.462-16.486-1.423-32.757-5.1-48.901c-3.032-13.315-8.007-25.803-13.801-38.043c-5.392-11.387-12.113-21.974-19.891-31.873
+			c-1.654-2.105-1.714-3.608,0.371-5.389c3.382-2.89,6.678-5.888,9.906-8.949c6.958-6.595,6.939-6.629,12.972,0.801
+			c1.254,1.545,2.502,3.105,3.624,4.746c10.857,15.872,19.95,32.574,26.446,50.814c4.915,13.799,9.241,27.749,10.784,42.188
+			c1.667,15.598,3.592,31.27,1.219,47.153c-1.49,9.975-2.215,20.025-4.464,29.944c-4.49,19.809-11.69,38.535-21.443,56.25
+			c-3.121,5.668-6.468,11.209-10.102,16.556c-0.132,0.193-0.342,0.515-0.342,0.515L490.984,332.363z"/>
+	</g>
+	<g>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M127.784,408.252l34.856-40.213h23.483V476.49h-27.016v-67.694l-29.299,34.002h-4.057l-29.292-34.002v67.694H69.443
+			V368.039H92.9L127.784,408.252z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M421.087,368.039v19.874h-39.281v88.578h-27.017v-88.578h-39.281v-19.874H421.087z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M521.447,368.039v19.874h-39.281v88.578h-27.017v-88.578h-39.281v-19.874H521.447z"/>
+	</g>
+	
+		<rect x="241" y="441.498" transform="matrix(0.7071 0.7071 -0.7071 0.7071 403.2676 -66.5775)" fill-rule="evenodd" clip-rule="evenodd" fill="#000000" width="82" height="24"/>
+	<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M305,476.498H196v-109h109V476.498z M282,390.499h-63v63h63V390.499z"/>
+</g>
+</svg>

+ 81 - 0
fhem/core/www/images/fhemSVG/mqtt_broker.svg

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="562px"
+	 height="514px" viewBox="0 0 562 514" enable-background="new 0 0 562 514" xml:space="preserve">
+<g id="Ebene_1">
+	<g>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M74.828,332.363c0,0-0.214-0.322-0.281-0.438
+			c-11.177-19.108-19.123-39.393-24.77-60.692c-7.089-26.744-8.035-54.223-6.096-81.668c1.094-15.489,3.336-31.045,8.29-45.85
+			c4.974-14.867,10.394-29.595,17.816-43.549C77.813,85.08,87.417,71.132,97.94,57.758c4.512-5.734,10.142-10.482,14.485-16.401
+			c1.706-2.325,3.867-1.675,5.952,0.236c11.232,10.296,22.543,20.506,33.848,30.722c6.882,6.219,13.762,12.442,20.723,18.572
+			c3.097,2.727,3.243,4.625,0.008,7.843c-17.571,17.476-30.428,37.872-38.795,61.38c-6.863,19.283-9.444,38.985-8.704,59.158
+			c0.61,16.625,3.577,32.928,9.95,48.472c2.75,6.706,5.358,13.453,9.037,19.734c2.767,4.725,3.25,4.894,7.276,1.396
+			c4.01-3.484,7.835-7.2,12.018-10.46c3.107-2.421,2.176-5.207,0.897-7.563c-12.321-22.689-15.232-47.173-13.156-72.324
+			c1.958-23.725,10.21-45.309,24.506-64.567c4.758-6.41,9.923-12.39,15.402-18.139c4.19-4.397,4.881-4.249,9.277-0.291
+			c16.749,15.078,33.562,30.084,50.393,45.069c3.501,3.117,3.425,4.351-0.245,7.156c-10.582,8.087-17.463,18.317-20.735,31.577
+			c-3.401,13.782-1.167,26.419,4.944,38.499c5.524,10.919,14.373,18.947,25.589,23.997c4.219,1.9,6.683,4.143,6.205,9.222
+			c-0.467,4.97,0.895,9.904,1.446,14.856c0.566,5.091-1.093,6.559-6.009,4.944c-13.014-4.276-24.989-10.291-35.044-20.001
+			c-11.146-10.764-19.146-23.474-22.857-38.278c-2.634-10.511-3.57-21.372-1.688-32.583c1.944-11.586,5.962-21.964,11.972-31.804
+			c1.09-1.785,1.165-3.313-0.593-4.812c-3.909-3.334-7.841-6.655-11.548-10.207c-2.281-2.185-3.846-1.356-5.239,0.695
+			c-9.489,13.968-15.2,29.426-16.831,46.167c-0.788,8.086-0.788,16.385,0.045,24.464c1.722,16.698,7.429,32.119,17.066,45.982
+			c0.847,1.218,1.573,2.522,2.453,3.714c4.5,6.096,4.457,6.032-0.766,11.104c-16.64,16.161-35.656,29.614-52.199,45.884
+			c-2.762,2.716-5.276,2.104-7.553-0.794c-15.891-20.23-28.115-42.32-34.954-67.291c-4.542-16.585-7.1-33.353-6.313-51.784
+			c-1.261-15.059,0.99-31.102,5.065-46.688c4.564-17.453,11.417-34.158,21.262-49.491c3.645-5.677,6.223-12.024,10.673-17.214
+			c1.902-2.218,1.409-4.24-0.634-6.038c-4.092-3.601-8.316-7.06-12.278-10.798c-2.667-2.517-4.819-1.938-6.615,0.664
+			c-5.82,8.431-11.865,16.706-16.941,25.644c-6.2,10.917-11.634,22.233-16.115,33.9c-4.258,11.087-7.344,22.582-9.338,34.42
+			c-3.124,18.552-2.648,37.127-1.924,55.746c0.598,15.367,4.501,30.037,9.012,44.695c5.344,17.367,13.141,33.436,22.512,48.877
+			c1.354,2.23,2.746,4.434,4.19,6.595c0.129,0.193,0.343,0.525,0.473,0.718L74.828,332.363z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M490.984,332.363c0,0,0.222-0.388,0.311-0.542
+			c4.755-8.291,9.066-16.859,13.014-25.672c7.9-17.638,13.271-36.002,16.461-54.938c2.177-12.923,3.125-26.074,2.294-37.845
+			c0.553-21.081-1.361-40.483-6.016-59.646c-5.469-22.514-14.152-43.692-25.812-63.654c-10.313-17.659-23.046-33.502-37.043-48.361
+			c-2.174-2.308-3.937-2.024-6.284,0.109c-17.786,16.158-35.7,32.176-53.558,48.255c-4.534,4.083-4.647,5.335-0.253,9.552
+			c8.646,8.295,15.929,17.648,22.421,27.688c11.198,17.316,18.662,36.274,22.207,56.387c2.755,15.631,3.566,31.504,1.254,47.633
+			c-2.835,19.776-8.68,38.257-17.961,55.791c-2.367,4.472-3.779,4.778-7.517,1.542c-4.139-3.584-8.112-7.358-12.227-10.971
+			c-2.028-1.781-1.946-3.709-0.719-5.782c7.654-12.923,11.135-27.244,13.026-41.861c2.498-19.299,1.347-38.621-4.378-57.239
+			c-6.927-22.528-19.378-41.888-36.472-58.228c-2.481-2.372-4.336-2.366-6.766-0.11c-6.437,5.975-12.983,11.832-19.503,17.716
+			c-10.831,9.774-21.66,19.55-32.527,29.283c-2.436,2.182-2.933,3.475,0.434,5.67c8.633,5.631,14.373,13.658,18.635,23.113
+			c12.669,28.105-2.076,60.651-28.274,71.926c-3.226,1.388-5.517,3.28-5.756,7.647c-0.272,4.964-0.596,9.95-0.786,14.927
+			c-0.213,5.602,1.309,6.628,6.744,5.003c14.044-4.199,26.188-11.546,36.262-22.181c14.798-15.622,22.104-34.622,22.341-55.782
+			c0.167-15.016-4.104-29.494-12.189-42.415c-1.997-3.19-1.383-5.173,0.914-7.292c3.642-3.36,7.419-6.574,11.049-9.947
+			c1.866-1.734,3.477-1.049,4.587,0.558c9.536,13.801,15.829,29.121,17.76,45.655c3.364,28.809-3.721,55.165-21.68,78.232
+			c-2.353,3.022-1.588,4.866,0.837,7.032c17.865,15.957,35.723,31.923,53.52,47.958c2.888,2.602,4.969,2.794,7.658-0.472
+			c10.023-12.168,18.794-25.114,25.445-39.433c5.31-11.432,8.383-23.553,11.374-35.757c3.182-12.984,4.715-26.128,5.086-39.392
+			c0.462-16.486-1.423-32.757-5.1-48.901c-3.032-13.315-8.007-25.803-13.801-38.043c-5.392-11.387-12.113-21.974-19.891-31.873
+			c-1.654-2.105-1.714-3.608,0.371-5.389c3.382-2.89,6.678-5.888,9.906-8.949c6.958-6.595,6.939-6.629,12.972,0.801
+			c1.254,1.545,2.502,3.105,3.624,4.746c10.857,15.872,19.95,32.574,26.446,50.814c4.915,13.799,9.241,27.749,10.784,42.188
+			c1.667,15.598,3.592,31.27,1.219,47.153c-1.49,9.975-2.215,20.025-4.464,29.944c-4.49,19.809-11.69,38.535-21.443,56.25
+			c-3.121,5.668-6.468,11.209-10.102,16.556c-0.132,0.193-0.342,0.515-0.342,0.515L490.984,332.363z"/>
+	</g>
+		<rect x="241" y="441.498" transform="matrix(0.7071 0.7071 -0.7071 0.7071 403.2676 -66.5775)" display="none" fill-rule="evenodd" clip-rule="evenodd" fill="#000000" width="82" height="24"/>
+</g>
+<g id="Ebene_4">
+	<g>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M15.16,371.338h38.045c9.07,0,16.198,2.182,21.385,6.545c5.186,4.364,7.779,9.524,7.779,15.482
+			c0,7.638-4.011,13.49-12.031,17.561c6.047,1.597,10.677,4.413,13.889,8.446c3.213,4.034,4.819,8.74,4.819,14.118
+			c0,7.647-2.625,13.866-7.874,18.656c-5.25,4.791-12.976,7.186-23.18,7.186H15.16V371.338z M37.08,387.464v18.833h11.464
+			c3.527,0,6.277-0.902,8.251-2.708c1.973-1.806,2.96-4.073,2.96-6.803c0-2.688-0.987-4.914-2.96-6.678
+			c-1.974-1.764-4.724-2.645-8.251-2.645H37.08z M37.08,422.422v20.786h12.346c5.836,0,10.036-0.808,12.598-2.425
+			c2.561-1.616,3.842-4.419,3.842-8.409c0-3.064-1.218-5.49-3.653-7.275c-2.436-1.784-5.69-2.677-9.763-2.677H37.08z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M105.233,371.338h39.305c8.524,0,15.453,2.465,20.786,7.396c5.333,4.931,8,10.901,8,17.908
+			c0,9.694-5.039,16.87-15.117,21.527c4.417,2.01,8.616,7.422,12.596,16.234c3.98,8.813,7.822,17.123,11.529,24.929h-24.054
+			c-1.436-2.897-3.938-8.419-7.505-16.566c-3.569-8.146-6.623-13.374-9.163-15.684c-2.542-2.309-5.26-3.465-8.157-3.465h-6.299
+			v35.715h-21.92V371.338z M127.153,387.464v20.029h11.59c3.695,0,6.624-0.87,8.787-2.613c2.162-1.742,3.244-4.23,3.244-7.465
+			c0-6.634-4.179-9.951-12.535-9.951H127.153z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M237.257,370.205c14.991,0,27.126,4.525,36.407,13.574c9.279,9.05,13.92,19.58,13.92,31.589
+			c0,12.179-4.703,22.739-14.109,31.683c-9.406,8.945-21.479,13.417-36.218,13.417c-14.908,0-27.043-4.45-36.407-13.354
+			c-9.365-8.902-14.046-19.484-14.046-31.746c0-12.052,4.65-22.591,13.952-31.62C210.056,374.719,222.223,370.205,237.257,370.205z
+			 M237.257,442.2c8.313,0,15.013-2.467,20.093-7.401c5.08-4.934,7.621-11.41,7.621-19.432c0-8.063-2.541-14.55-7.621-19.464
+			c-5.08-4.912-11.779-7.369-20.093-7.369c-8.357,0-15.085,2.457-20.188,7.369c-5.102,4.914-7.653,11.401-7.653,19.464
+			c0,8.021,2.551,14.498,7.653,19.432C222.171,439.733,228.9,442.2,237.257,442.2z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M377.342,371.338l-28.663,41.385l38.868,46.609h-27.399l-34.896-41.258v41.258h-21.92v-87.995h21.92v39.62l27.979-39.62
+			H377.342z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M461.369,443.208v16.125h-64.878v-87.995h63.996v16.125h-42.076v18.455h40.123v16.125h-40.123v21.164H461.369z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M478.124,371.338h39.305c8.524,0,15.453,2.465,20.786,7.396c5.332,4.931,7.999,10.901,7.999,17.908
+			c0,9.694-5.039,16.87-15.117,21.527c4.418,2.01,8.616,7.422,12.596,16.234c3.98,8.813,7.823,17.123,11.529,24.929h-24.054
+			c-1.436-2.897-3.938-8.419-7.506-16.566c-3.568-8.146-6.622-13.374-9.162-15.684c-2.541-2.309-5.26-3.465-8.157-3.465h-6.299
+			v35.715h-21.92V371.338z M500.044,387.464v20.029h11.59c3.694,0,6.624-0.87,8.787-2.613c2.162-1.742,3.244-4.23,3.244-7.465
+			c0-6.634-4.18-9.951-12.535-9.951H500.044z"/>
+	</g>
+</g>
+</svg>

+ 68 - 0
fhem/core/www/images/fhemSVG/mqtt_device.svg

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="562px"
+	 height="514px" viewBox="0 0 562 514" enable-background="new 0 0 562 514" xml:space="preserve">
+<g id="Ebene_1">
+	<g>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M74.828,332.363c0,0-0.214-0.322-0.281-0.438
+			c-11.177-19.108-19.123-39.393-24.77-60.692c-7.089-26.744-8.035-54.223-6.096-81.668c1.094-15.489,3.336-31.045,8.29-45.85
+			c4.974-14.867,10.394-29.595,17.816-43.549C77.813,85.08,87.417,71.132,97.94,57.758c4.512-5.734,10.142-10.482,14.485-16.401
+			c1.706-2.325,3.867-1.675,5.952,0.236c11.232,10.296,22.543,20.506,33.848,30.722c6.882,6.219,13.762,12.442,20.723,18.572
+			c3.097,2.727,3.243,4.625,0.008,7.843c-17.571,17.476-30.428,37.872-38.795,61.38c-6.863,19.283-9.444,38.985-8.704,59.158
+			c0.61,16.625,3.577,32.928,9.95,48.472c2.75,6.706,5.358,13.453,9.037,19.734c2.767,4.725,3.25,4.894,7.276,1.396
+			c4.01-3.484,7.835-7.2,12.018-10.46c3.107-2.421,2.176-5.207,0.897-7.563c-12.321-22.689-15.232-47.173-13.156-72.324
+			c1.958-23.725,10.21-45.309,24.506-64.567c4.758-6.41,9.923-12.39,15.402-18.139c4.19-4.397,4.881-4.249,9.277-0.291
+			c16.749,15.078,33.562,30.084,50.393,45.069c3.501,3.117,3.425,4.351-0.245,7.156c-10.582,8.087-17.463,18.317-20.735,31.577
+			c-3.401,13.782-1.167,26.419,4.944,38.499c5.524,10.919,14.373,18.947,25.589,23.997c4.219,1.9,6.683,4.143,6.205,9.222
+			c-0.467,4.97,0.895,9.904,1.446,14.856c0.566,5.091-1.093,6.559-6.009,4.944c-13.014-4.276-24.989-10.291-35.044-20.001
+			c-11.146-10.764-19.146-23.474-22.857-38.278c-2.634-10.511-3.57-21.372-1.688-32.583c1.944-11.586,5.962-21.964,11.972-31.804
+			c1.09-1.785,1.165-3.313-0.593-4.812c-3.909-3.334-7.841-6.655-11.548-10.207c-2.281-2.185-3.846-1.356-5.239,0.695
+			c-9.489,13.968-15.2,29.426-16.831,46.167c-0.788,8.086-0.788,16.385,0.045,24.464c1.722,16.698,7.429,32.119,17.066,45.982
+			c0.847,1.218,1.573,2.522,2.453,3.714c4.5,6.096,4.457,6.032-0.766,11.104c-16.64,16.161-35.656,29.614-52.199,45.884
+			c-2.762,2.716-5.276,2.104-7.553-0.794c-15.891-20.23-28.115-42.32-34.954-67.291c-4.542-16.585-7.1-33.353-6.313-51.784
+			c-1.261-15.059,0.99-31.102,5.065-46.688c4.564-17.453,11.417-34.158,21.262-49.491c3.645-5.677,6.223-12.024,10.673-17.214
+			c1.902-2.218,1.409-4.24-0.634-6.038c-4.092-3.601-8.316-7.06-12.278-10.798c-2.667-2.517-4.819-1.938-6.615,0.664
+			c-5.82,8.431-11.865,16.706-16.941,25.644c-6.2,10.917-11.634,22.233-16.115,33.9c-4.258,11.087-7.344,22.582-9.338,34.42
+			c-3.124,18.552-2.648,37.127-1.924,55.746c0.598,15.367,4.501,30.037,9.012,44.695c5.344,17.367,13.141,33.436,22.512,48.877
+			c1.354,2.23,2.746,4.434,4.19,6.595c0.129,0.193,0.343,0.525,0.473,0.718L74.828,332.363z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M490.984,332.363c0,0,0.222-0.388,0.311-0.542
+			c4.755-8.291,9.066-16.859,13.014-25.672c7.9-17.638,13.271-36.002,16.461-54.938c2.177-12.923,3.125-26.074,2.294-37.845
+			c0.553-21.081-1.361-40.483-6.016-59.646c-5.469-22.514-14.152-43.692-25.812-63.654c-10.313-17.659-23.046-33.502-37.043-48.361
+			c-2.174-2.308-3.937-2.024-6.284,0.109c-17.786,16.158-35.7,32.176-53.558,48.255c-4.534,4.083-4.647,5.335-0.253,9.552
+			c8.646,8.295,15.929,17.648,22.421,27.688c11.198,17.316,18.662,36.274,22.207,56.387c2.755,15.631,3.566,31.504,1.254,47.633
+			c-2.835,19.776-8.68,38.257-17.961,55.791c-2.367,4.472-3.779,4.778-7.517,1.542c-4.139-3.584-8.112-7.358-12.227-10.971
+			c-2.028-1.781-1.946-3.709-0.719-5.782c7.654-12.923,11.135-27.244,13.026-41.861c2.498-19.299,1.347-38.621-4.378-57.239
+			c-6.927-22.528-19.378-41.888-36.472-58.228c-2.481-2.372-4.336-2.366-6.766-0.11c-6.437,5.975-12.983,11.832-19.503,17.716
+			c-10.831,9.774-21.66,19.55-32.527,29.283c-2.436,2.182-2.933,3.475,0.434,5.67c8.633,5.631,14.373,13.658,18.635,23.113
+			c12.669,28.105-2.076,60.651-28.274,71.926c-3.226,1.388-5.517,3.28-5.756,7.647c-0.272,4.964-0.596,9.95-0.786,14.927
+			c-0.213,5.602,1.309,6.628,6.744,5.003c14.044-4.199,26.188-11.546,36.262-22.181c14.798-15.622,22.104-34.622,22.341-55.782
+			c0.167-15.016-4.104-29.494-12.189-42.415c-1.997-3.19-1.383-5.173,0.914-7.292c3.642-3.36,7.419-6.574,11.049-9.947
+			c1.866-1.734,3.477-1.049,4.587,0.558c9.536,13.801,15.829,29.121,17.76,45.655c3.364,28.809-3.721,55.165-21.68,78.232
+			c-2.353,3.022-1.588,4.866,0.837,7.032c17.865,15.957,35.723,31.923,53.52,47.958c2.888,2.602,4.969,2.794,7.658-0.472
+			c10.023-12.168,18.794-25.114,25.445-39.433c5.31-11.432,8.383-23.553,11.374-35.757c3.182-12.984,4.715-26.128,5.086-39.392
+			c0.462-16.486-1.423-32.757-5.1-48.901c-3.032-13.315-8.007-25.803-13.801-38.043c-5.392-11.387-12.113-21.974-19.891-31.873
+			c-1.654-2.105-1.714-3.608,0.371-5.389c3.382-2.89,6.678-5.888,9.906-8.949c6.958-6.595,6.939-6.629,12.972,0.801
+			c1.254,1.545,2.502,3.105,3.624,4.746c10.857,15.872,19.95,32.574,26.446,50.814c4.915,13.799,9.241,27.749,10.784,42.188
+			c1.667,15.598,3.592,31.27,1.219,47.153c-1.49,9.975-2.215,20.025-4.464,29.944c-4.49,19.809-11.69,38.535-21.443,56.25
+			c-3.121,5.668-6.468,11.209-10.102,16.556c-0.132,0.193-0.342,0.515-0.342,0.515L490.984,332.363z"/>
+	</g>
+		<rect x="241" y="441.498" transform="matrix(0.7071 0.7071 -0.7071 0.7071 403.2676 -66.5775)" display="none" fill-rule="evenodd" clip-rule="evenodd" fill="#000000" width="82" height="24"/>
+</g>
+<g id="Ebene_4">
+	<g>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M37.033,369.338h38.801c14.991,0,26.843,4.105,35.557,12.314c8.713,8.21,13.07,18.824,13.07,31.841
+			c0,13.647-4.42,24.366-13.259,32.155c-8.84,7.79-21.448,11.685-37.825,11.685H37.033V369.338z M58.953,385.464v55.744h14.298
+			c9.28,0,16.377-2.551,21.29-7.653c4.913-5.102,7.37-11.831,7.37-20.188c0-8.65-2.488-15.464-7.464-20.439
+			c-4.977-4.977-12.125-7.464-21.448-7.464H58.953z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M205.716,441.208v16.125h-64.878v-87.995h63.996v16.125h-42.076v18.455h40.124v16.125h-40.124v21.164H205.716z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M305.442,369.338l-40.669,89.128h-9.511l-42.633-89.128h23.878l23.284,50.281l22.04-50.281H305.442z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M337.929,369.338v87.995h-21.92v-87.995H337.929z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M440.221,430.5v19.212c-11.002,5.837-22.529,8.755-34.581,8.755c-15.621,0-28.061-4.314-37.32-12.944
+			c-9.259-8.629-13.889-19.264-13.889-31.903c0-12.639,4.882-23.368,14.645-32.187c9.764-8.819,22.519-13.228,38.266-13.228
+			c12.556,0,23.18,2.583,31.872,7.748v19.59c-10.667-6.005-20.766-9.008-30.298-9.008c-9.364,0-17.018,2.51-22.959,7.527
+			c-5.942,5.019-8.912,11.411-8.912,19.18c0,7.811,2.929,14.257,8.786,19.338c5.858,5.081,13.363,7.621,22.519,7.621
+			c4.535,0,8.902-0.599,13.102-1.795C425.648,437.208,431.906,434.573,440.221,430.5z"/>
+		<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M522.799,441.208v16.125h-64.878v-87.995h63.996v16.125h-42.076v18.455h40.123v16.125h-40.123v21.164H522.799z"/>
+	</g>
+</g>
+</svg>

+ 30 - 0
fhem/core/www/images/fhemSVG/sonos_play1.svg

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 width="566.929px" height="566.93px" viewBox="0 0 566.929 566.93" enable-background="new 0 0 566.929 566.93"
+	 xml:space="preserve">
+<g id="Ebene_1">
+	<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M493.714,438.5c0,0.802-0.486,4.065-1.714,6.917
+		c-0.942,2.188-2.514,3.954-3.478,5.268c-0.751,1.026-1.592,16.444-2.524,17.646C464.288,496.267,344.237,543,291,543
+		s-174.358-46.564-195.48-75.011c-0.954-1.284-1.814-13.483-2.583-14.584c-2.536-3.641-4.063-6.222-4.649-7.364
+		c-0.133-0.26-1.514-3.419-1.787-4.791S86,435,86,435V104.333v-2.646c0-0.855,0-1.633,0-2.332c0-0.918,0.311-1.407,0.908-2.2
+		c13.532-17.979,174.403-65.292,206.967-65.292c34,0,199.845,47.085,199.839,62.136c0,0.997,0,16.667,0,16.667V427.25
+		C493.714,427.25,493.714,437.083,493.714,438.5z M474.334,190.396c0.5,0.625,0.416,1.146,0.416,1.146l-0.008,242.7
+		c0,0,0.037,1.944-0.242,3.758c-0.229,1.493-0.729,2.838-1.151,3.44C463.146,456.013,342.146,515,291.24,515
+		c-50.907,0-173.983-56.307-184.24-73c-1.826-2.973-1.488-6.369-1.627-8.011l0.001-0.988L105.292,192.5c0,0-0.071-1.594,0.802-2.438
+		c0.625-0.604,2.228-0.104,2.393-0.04C135.729,200.54,255.71,243.375,292,243.375c50.474,0,142.435-38.142,180.574-53.021
+		c0.229-0.09,0.636-0.249,0.636-0.249S473.834,189.771,474.334,190.396z M292,223.421c-41.663,0-184.333-49.921-186.465-54.672
+		c-0.861-1.918,0.03-46.373,0.03-46.373S106.05,121.199,107,122c12.051,10.158,150.283,57.757,183.158,57.757
+		s164.541-45.834,181.348-59.82c0.91-0.758,2.057-0.586,2.557-0.187c0.582,0.468,0.438,1.25,0.438,1.25s1.502,44.843,0.243,47.167
+		C470.5,176,333.662,223.421,292,223.421z M111,101c0,14,150.742,58.757,178.283,58.757c21.05,0,45.731-8.236,79.064-18.601
+		c0.301-0.094,0.902-0.281,0.902-0.281S296,110.833,296,108.5S329.666,94,334.333,94s80.167,30.5,80.167,30.5
+		s1.251-0.457,1.872-0.685C447.659,112.331,468,102.862,468,98c0-12-123.334-50.291-176-50.136C246,48,111,91,111,101z"/>
+</g>
+<g id="Ebene_2">
+	<path fill-rule="evenodd" clip-rule="evenodd" fill="#000000" d="M126.542,219.167c0.708-0.417,0.62-0.085,2.754,0.665
+		c19.669,6.905,123.991,45.502,162.112,45.502c39.606,0,146.965-39.641,160.538-44.199c0.097-0.032,0.549-0.28,1.007,0.053
+		s0.381,0.729,0.381,0.729V229v192.5c0,0,0.25,6.792-0.521,7.75s-0.865,1.227-3.409,2.661
+		c-19.71,11.118-108.854,59.756-159.995,59.756c-51.253,0-140.267-50.425-159.623-61.758c-2.451-1.435-2.722-1.921-3.223-3.284
+		S126,421.5,126,421.5V231v-9.666C126,221.334,125.833,219.583,126.542,219.167z"/>
+</g>
+</svg>

+ 332 - 0
fhem/core/www/pgm2/babble.js

@@ -0,0 +1,332 @@
+//########################################################################################
+// babble.js
+// Version 1.25
+// See 95_Babble for licensing
+//########################################################################################
+//# Prof. Dr. Peter A. Henning
+
+//------------------------------------------------------------------------------------------------------
+// Determine csrfToken
+//------------------------------------------------------------------------------------------------------
+
+var req = new XMLHttpRequest();
+req.open('GET', document.location.href, false);
+req.send(null);
+var csrfToken = req.getResponseHeader('X-FHEM-csrfToken');
+if( csrfToken == null ){
+    csrfToken = "null";
+}
+
+//------------------------------------------------------------------------------------------------------
+// encode Parameters for URL
+//------------------------------------------------------------------------------------------------------
+
+function encodeParm(oldval) {
+    var newval;
+    newval = oldval.replace(/\$/g, '\\%24');
+    newval = newval.replace(/"/g, '%27');
+    newval = newval.replace(/#/g, '%23');
+    newval = newval.replace(/\+/g, '%2B');
+    newval = newval.replace(/&/g, '%26');
+    newval = newval.replace(/'/g, '%27');
+    newval = newval.replace(/=/g, '%3D');
+    newval = newval.replace(/\?/g, '%3F');
+    newval = newval.replace(/\|/g, '%7C');
+    newval = newval.replace(/\s/g, '%20');
+    return newval;
+};
+
+//------------------------------------------------------------------------------------------------------
+// Add and remove places and verbs
+//------------------------------------------------------------------------------------------------------
+
+
+function dialog1(message) {
+    $('<div></div>').appendTo('body').html('<div><h6>' + message + '</h6></div>').dialog({
+        modal: true, title: 'Babble', zIndex: 10000, autoOpen: true,
+        width: 'auto', resizable: false,
+        buttons: {
+            OK: function () {
+                location.reload();
+                $(this).dialog("close");
+            }
+        },
+        close: function (event, ui) {
+            $(this).remove();
+        }
+    });
+};
+
+function babble_addplace(name) {
+    var place = document.getElementById('b_newplace').value;
+    var location = document.location.pathname;
+    if (location.substr(location.length -1, 1) == '/') {
+        location = location.substr(0, location.length -1);
+    }
+    var url = document.location.protocol + "//" + document.location.host + location;
+    FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '={Babble_ModPlace("' + name + '","' + encodeParm(place) + '",1)}');
+    dialog1(tt_place + ' ' + place + ' ' + tt_added);
+};
+
+function babble_modplace(name, place, num) {
+    var btn = document.getElementById('b_addplace');
+    var divm = document.getElementById('b_chgplacediv');
+    var fld = document.getElementById('b_newplace');
+    fld.value = place;
+    btn.value = tt_remove;
+    btn.setAttribute("onclick", "babble_remplace('" + name + "','" + place + "'," + num + ")");
+    var btnm = '<input type="button" id="b_canplace" onclick="babble_cancelplace(';
+    btnm += "'" + name + "')";
+    btnm += '" value="' + tt_cancel + '" style="height:20px; width:100px;"/>';
+    divm.innerHTML = btnm;
+};
+
+function babble_remplace(name, place, num) {
+    var location = document.location.pathname;
+    if (location.substr(location.length -1, 1) == '/') {
+        location = location.substr(0, location.length -1);
+    }
+    var url = document.location.protocol + "//" + document.location.host + location;
+    FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '={Babble_ModPlace("' + name + '",' + num + ',0)}');
+    dialog1(tt_place + ' ' + place + ' ' + tt_removed);
+};
+
+function babble_cancelplace(name) {
+    var btn = document.getElementById('b_addplace');
+    var fld = document.getElementById('b_newplace');
+    var divm = document.getElementById('b_chgplacediv');
+    fld.value = "";
+    btn.value = tt_add;
+    btn.setAttribute("onclick", "babble_addplace('" + name + "')");
+    divm.innerHTML = '';
+}
+
+function babble_addverb(name) {
+    var verbi = document.getElementById('b_newverbi').value;
+    var verbc = document.getElementById('b_newverbc').value;
+    var location = document.location.pathname;
+    if (location.substr(location.length -1, 1) == '/') {
+        location = location.substr(0, location.length -1);
+    }
+    1
+    var url = document.location.protocol + "//" + document.location.host + location;
+    FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '={Babble_ModVerb("' + name + '","' + verbi + '","' + verbc + '",1)}');
+    dialog1(tt_verb + ' ' + verbi + ' ' + tt_added);
+};
+
+function babble_modverb(name, verbi, verbc, num) {
+    var btna = document.getElementById('b_addverb');
+    var divm = document.getElementById('b_chgverbdiv');
+    var fldi = document.getElementById('b_newverbi');
+    var fldc = document.getElementById('b_newverbc');
+    fldi.value = verbi;
+    fldc.value = verbc;
+    btna.value = tt_remove;
+    btna.setAttribute("onclick", "babble_remverb('" + name + "','" + verbi + "'," + num + ")");
+    var btnm = '<input type="button" id="b_chgverb" onclick="babble_chgverb(';
+    btnm += "'" + name + "'," + num + ")";
+    btnm += '" value="' + tt_modify + '" style="height:20px; width:100px;"/>';
+    var btnm2 = '<input type="button" id="b_canverb" onclick="babble_cancelverb(';
+    btnm2 += "'" + name + "')";
+    btnm2 += '" value="' + tt_cancel + '" style="height:20px; width:100px;"/>';
+    divm.innerHTML = btnm + btnm2;
+};
+function babble_remverb(name, verbi, num) {
+    var location = document.location.pathname;
+    if (location.substr(location.length -1, 1) == '/') {
+        location = location.substr(0, location.length -1);
+    }
+    var url = document.location.protocol + "//" + document.location.host + location;
+    FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '={Babble_ModVerb("' + name + '",' + num + ',"",0)}');
+    dialog1(tt_verb + ' ' + verbi + ' ' + tt_removed);
+};
+
+function babble_chgverb(name, num) {
+    var verbi = document.getElementById('b_newverbi').value;
+    var verbc = document.getElementById('b_newverbc').value;
+    var location = document.location.pathname;
+    if (location.substr(location.length -1, 1) == '/') {
+        location = location.substr(0, location.length -1);
+    }
+    1
+    var url = document.location.protocol + "//" + document.location.host + location;
+    FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '={Babble_ModVerb("' + name + '",' + num + ',"' + verbc + '",2)}');
+    dialog1(tt_verb + ' ' + verbi + ' ' + tt_modified);
+};
+
+function babble_cancelverb(name) {
+    var btna = document.getElementById('b_addverb');
+    var divm = document.getElementById('b_chgverbdiv');
+    var fldi = document.getElementById('b_newverbi');
+    var fldc = document.getElementById('b_newverbc');
+    fldi.value = "";
+    fldc.value = "";
+    btna.value = tt_add;
+    btna.setAttribute("onclick", "babble_addverb('" + name + "')");
+    divm.innerHTML = '';
+}
+
+function babble_addrow(name, devx, rowx) {
+    var table = document.getElementById("devstable");
+    var rown = 2;
+    for (i = 0; i < devx-1; i++) {
+        rown += devrows[i];
+    }
+    var row = table.insertRow(rown)
+    devrows[devx -1] = devrows[devx -1] + 1;
+    var cell0 = row.insertCell(0);
+    var cell1 = row.insertCell(1);
+    var cell2 = row.insertCell(2);
+    var cell3 = row.insertCell(3);
+    var cell4 = row.insertCell(4);
+    var cell5 = row.insertCell(5);
+    var cell6 = row.insertCell(6);
+    var cell7 = row.insertCell(7);
+    //copy from existing row if such a row exists
+    cell2.innerHTML = newplace;
+    cell3.innerHTML = newverbs;
+    cell4.innerHTML = newtargs;
+    cell5.innerHTML = newfield;
+    cell6.innerHTML = newcheck;
+}
+
+function babble_remrow(name, devx, rowx) {
+    var table = document.getElementById("devstable");
+    var url = document.location.protocol + "//" + document.location.host + "/fhem";
+    var rown = rowx;
+    var rowdev = 1;
+    for (i = 0; i < devx -1; i++) {
+        rowdev += devrows[i];   
+    }
+    devrows[devx -1] = devrows[devx -1] -1;
+    var bdev = table.rows[rowdev].cells[1].textContent;
+    var place = '';
+    var verb = '';
+    var target = '';
+    var cmd = '';
+    var selector;
+    var selectedanswer;
+    //
+    selector = table.rows[rown].cells[2].getElementsByTagName("select")[0];
+    if( selector ){
+      selectedanswer = selector.selectedIndex;
+      place = selector.getElementsByTagName("option")[selectedanswer].value;
+    }else{
+      place = "none";
+    }
+    //
+    selector = table.rows[rown].cells[3].getElementsByTagName("select")[0];
+    if( selector ){
+      selectedanswer = selector.selectedIndex;
+      verb = selector.getElementsByTagName("option")[selectedanswer].value;
+    }else{
+      verb = "none";
+    }
+    //
+    selector = table.rows[rown].cells[4].getElementsByTagName("select")[0];
+    if( selector ){
+      selectedanswer = selector.selectedIndex;
+      target = selector.getElementsByTagName("option")[selectedanswer].value;
+    }else{
+      target = "none";
+    }
+    //
+    cmd = '{Babble_RemCmd("' + name + '","' + bdev + '","' + place + '","' + verb + '","' + target + '")}';
+    FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '=' + cmd);
+    table.deleteRow(rown);
+}
+
+function babble_savedevs(name) {
+    var table = document.getElementById("devstable");
+    var url = document.location.protocol + "//" + document.location.host + "/fhem";
+    for (idev = 1; idev <= devrows.length; idev++) {
+        var rowdev = 1;
+        for (ip = 0; ip < idev -1; ip++) {
+          rowdev += devrows[ip];   
+        }
+        var fhemdev = table.rows[rowdev].cells[0].textContent;
+        var bdev = table.rows[rowdev].cells[1].textContent;
+        var place = '';
+        var verb = '';
+        var target = '';
+        var cmd = '';
+        var selector;
+        var selectedanswer;
+        var field;
+        // Help text
+        field = table.rows[rowdev].cells[3].getElementsByTagName("input")[0];
+        cmd = field.value;
+        cmd = '{Babble_ModHlp("' + name + '","' + bdev + '","' + encodeParm(cmd) + '")}';
+        FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '=' + cmd);
+        // Command lines
+        for (j = 1; j < devrows[idev -1]; j++) {
+            //
+            selector = table.rows[rowdev + j].cells[2].getElementsByTagName("select")[0];
+            if( selector ){ 
+              selectedanswer = selector.selectedIndex;
+              place = selector.getElementsByTagName("option")[selectedanswer].value;
+            }else{
+              place = "none";
+            }
+            //
+            selector = table.rows[rowdev + j].cells[3].getElementsByTagName("select")[0];
+            if( selector ){
+              selectedanswer = selector.selectedIndex;
+              verb = selector.getElementsByTagName("option")[selectedanswer].value;
+            }else{
+              verb = "none";
+            }  
+            //
+            selector = table.rows[rowdev + j].cells[4].getElementsByTagName("select")[0];
+            if( selector ){
+              selectedanswer = selector.selectedIndex;
+              target = selector.getElementsByTagName("option")[selectedanswer].value;
+            }else{
+              target = "none"
+            }
+            //
+            field = table.rows[rowdev + j].cells[5].getElementsByTagName("input")[0];
+            cmd = field.value;
+            //
+            field = table.rows[rowdev + j].cells[6].getElementsByTagName("input")[0];
+            if( field.checked == true ){
+                cmd = cmd + ";;$CONFIRM";
+            }
+            //
+            cmd = '{Babble_ModCmd("' + name + '","' + bdev + '","' + place + '","' + verb + '","' + target + '","' + encodeParm(cmd) + '")}';
+            FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '=' + cmd);
+        }
+    };
+    FW_cmd(url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '={Babble_savename("' + name + '")}');
+}
+
+var httpRequest;
+
+function babble_testit(name) {
+    var sentence = document.getElementById("d_testcommand").value;
+    var exec =  document.getElementById("b_execit").checked;
+    var exflag;
+    if( exec ){
+       exflag=1;
+    }else{
+       exflag=0;
+    }
+    var url = document.location.protocol + "//" + document.location.host + "/fhem";
+    var cmd = '{Babble_DoIt("' + name + '","' + encodeParm(sentence) + '","testit",' + exflag + ')}';
+  
+    httpRequest = new XMLHttpRequest();
+    httpRequest.open("GET", url + '?XHR=1&fwcsrf=' + csrfToken + '&cmd.' + name + '=' + cmd, true);
+    httpRequest.onreadystatechange = parse;
+    httpRequest.send(null);
+};
+
+function parse () {
+    if (httpRequest.readyState != 3) {
+        return;
+    }
+    var lines = httpRequest.responseText.split("\n");
+    //Pop the last (maybe empty) line after the last "\n"
+    //We wait until it is complete, i.e. terminated by "\n"
+    lines.pop();
+    document.getElementById("d_testresult").innerHTML = lines[1];
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 662 - 0
fhem/core/www/pgm2/f18.js


+ 68 - 0
fhem/core/www/pgm2/f18style.css

@@ -0,0 +1,68 @@
+@import url("defaultCommon.css");
+
+#logo {
+  position:absolute; top:10px; left:52px; width:32px; height:32px; z-index:10;
+  background-image:url(../images/default/fhemicon.png);
+  background-size: contain; background-repeat: no-repeat;
+}
+#menuBtn { position:absolute; top:10px; left:10px; width:32px; height:32px; }
+#hdr     { position:absolute; top:10px; right:10px; }
+#content { position:absolute; top:50px; left:10px; bottom:10px; right:10px; }
+#menu {
+  position: absolute;
+  top:50px; left:-120%;
+  z-index:20;
+  display:inline-block;
+  padding:0 0.5em 0.5em 0;
+}
+#menu.visible  { left:10px!important; }
+table.room,table.block.wide,table.fileList {
+  border:0;
+  border-radius:0;
+  border-spacing:0;
+  border-top:1px solid gray;
+  border-bottom:1px solid gray;
+}
+table.block.wide td > div { padding:0 4px; }
+
+#menuScrollArea { display:none; } /* commandref */
+body[fw_id] #menuScrollArea { display:block; } /* not commandref */
+#right { top:10px; left:10px; }
+
+input[type=submit] { border:none; background:none; }
+select { background:none; }
+#menu img.icon { width:18px; height:18px; }/* Firefox assumes 100px bef.load */
+
+table.roomoverview  { border-spacing:0; }
+div#menu > table { border-spacing:0; }
+tr.devTypeTr td { padding:0px; }
+tr.column > td { padding-right:10px; }
+
+.animated  { transition: all .1s ease-in; }
+a { text-decoration:none; }
+
+div.dist { padding-top:0.3em; }
+button.dist { margin:10px; background:transparent; border:0px; cursor:pointer; }
+select,input[type=submit] { cursor:pointer; }
+
+div.pin, div.dragger { float:right; width:1em; height:1em; margin-left:0.5em; }
+div.pinHeader { height:1em; padding:2px; }
+
+body.touch a { font-size: 20px; }
+body.touch #menu { font-size: 20px; } /* for the menuTree icon */
+body.touch div.col1, body.touch #menu table.room div { padding:0.25em 0; }
+
+@media screen and (orientation: portrait) {
+  body.small table.block tr td:nth-child(n+3) { width: 0px; display: none; }
+  body.small #content > table { width: 100%; }
+}
+
+body.pinnedMenu #menuBtn { display:none; }
+body.pinnedMenu #menu { left:10px; }
+body.pinnedMenu #logo { left:10px; }
+body.small.rightMenu #menuBtn { right:10px; left:auto; }
+body.small.rightMenu #logo { right:52px; left:auto; }
+body.small.rightMenu #hdr { left:10px; right:auto; }
+body { background-repeat: no-repeat; background-size:cover; }
+
+div.SVGlabel { display:inline-block; }

+ 178 - 0
fhem/core/www/tablet/ftui_check.html

@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <!--
+     /* FHEM tablet ui */
+     /*
+     * UI builder framework for FHEM
+     *
+     * Version: 2.6.*
+     * URL: https://github.com/knowthelist/fhem-tablet-ui
+     *
+     * Copyright (c) 2018 Mario Stephan <mstephan@shared-files.de>
+     * Under MIT License (http://www.opensource.org/licenses/mit-license.php)
+     *
+    -->
+    <link rel="icon" href="favicon.ico" type="image/x-icon" />
+    <link rel="stylesheet" href="css/fhem-tablet-ui.css" />
+
+    <script src="lib/jquery.min.js"></script>
+    <style>
+        body {
+            background-color: #efefef;
+            color: #222222;
+            font: normal 16px/18px "Helvetica Neue", Helvetica, sans-serif;
+        }
+
+        h2 {
+            text-align: left;
+            padding-left: 20px;
+        }
+
+        .check>div {
+            background-color: #fff;
+            padding: 25px;
+            border-radius: 30px
+        }
+
+        td {
+            border-bottom: 1px solid #bbb;
+        }
+
+    </style>
+    <script>
+        function parseISOLocal(s) {
+            var b = new Date(s);
+            return b.toLocaleString();
+        }
+
+        function update() {
+
+            var ftui = {
+                    poll: {},
+                    version: JSON.parse(localStorage.getItem('ftui.version')),
+                    subscriptions: JSON.parse(localStorage.getItem('ftui.subscriptions')),
+                    config: JSON.parse(localStorage.getItem('ftui.config')),
+                    deviceStates: JSON.parse(localStorage.getItem('ftui.deviceStates')),
+                    poll: {
+                        short: JSON.parse(localStorage.getItem('ftui.poll.short')),
+                        long: JSON.parse(localStorage.getItem('ftui.poll.long'))
+                    }
+                },
+                modules = JSON.parse(localStorage.getItem('modules'));
+
+            var strSubs = '<table>',
+                strMods = '<table>',
+                strGeneral = '<table>',
+                strShort = '<table>',
+                strLong = '<table>',
+                strVersion = '<table>',
+                i = 0;
+
+            // Subscriptions
+            for (var key in ftui.subscriptions) {
+                i++;
+                var mDev = ftui.subscriptions[key].device;
+                var mRead = ftui.subscriptions[key].reading;
+                var mValid = "--unknown--";
+                if (ftui.deviceStates[mDev]) {
+                    var params = ftui.deviceStates[mDev];
+                    if (params[mRead]) {
+                        mValid = (params[mRead].valid) ? "'" + params[mRead].val + "'" : '--known-but-invalid--';
+                    }
+                }
+                var subClass = (mValid === "--unknown--" || mValid === "--known-but-invalid--") ? 'red' : '';
+                strSubs = strSubs + "<tr class='" + subClass + "'><td>" + i + "</td><td>" + mDev + "</td><td>" + mRead + "</td><td>" + mValid + "</td></tr>";
+            }
+
+            strSubs = strSubs + "</table>";
+
+            // Modules
+            i = 0;
+            for (var key in modules) {
+                strMods = strMods + "<tr class=''><td>" + i + "</td><td>" + modules[i].name + "</td><td>" + modules[i].area + "</td></tr>";
+                i++;
+            }
+
+            strMods = strMods + "</table>";
+
+            // Short
+            strShort = strShort + "<tr><td>lastTimestamp</td><td class=''>" + parseISOLocal(ftui.poll.short.lastTimestamp); + "</td></tr>";
+            strShort = strShort + "<tr><td>StatusText</td><td class=''>" + ftui.poll.short.request.statusText + "</td></tr>";
+            strShort = strShort + "<tr><td>Duration</td><td class=''>" + ftui.poll.short.duration + "</td></tr>";
+
+            strShort = strShort + "</table>";
+            strShort = strShort + "<h3>Filter</h3>" + ftui.poll.short.filter;
+
+            // Long
+            strLong = strLong + "<tr><td>longPollType</td><td class=''>" + ftui.config.longPollType + "</td></tr>";
+            if (ftui.poll.long.xhr) {
+                strLong = strLong + "<tr><td>currLine</td><td class=''>" + ftui.poll.long.currLine + "</td></tr>";
+                strLong = strLong + "<tr><td>readyState</td><td class=''>" + ftui.poll.long.request.readyState + "</td></tr>";
+            } else if (ftui.poll.long.websocket) {
+                strLong = strLong + "<tr><td>ready</td><td class=''>true</td></tr>";
+            } else {
+                strLong = strLong + "<tr><td>readyState</td><td class=''>no longpoll object created</td></tr>";
+            }
+
+            strLong = strLong + "<tr><td>lastEventTimestamp</td><td class=''>" + parseISOLocal(ftui.poll.long.lastEventTimestamp) + "</td></tr>";
+            strLong = strLong + "<tr><td>lastUpdateTimestamp</td><td class=''>" + parseISOLocal(ftui.poll.long.lastUpdateTimestamp) + "</td></tr>";
+            strLong = strLong + "<tr><td>LastDevice</td><td class=''>" + ftui.poll.long.lastDevice + "</td></tr>";
+            strLong = strLong + "<tr><td>LastReading</td><td class=''>" + ftui.poll.long.lastReading + "</td></tr>";
+            strLong = strLong + "<tr><td>LastValue</td><td class=''>" + ftui.poll.long.lastValue + "</td></tr>";
+
+            strLong = strLong + "</table>";
+            strLong = strLong + "<h3>Filter</h3>" + ftui.poll.long.filter;
+
+            // Config
+            $.each(ftui.config, function(index, key) {
+                strGeneral = strGeneral + "<tr><td>" + index + "</td><td class=''>" + key + "</td></tr>"
+            });
+            strGeneral = strGeneral + "</table>";
+
+            // Version
+            strVersion = strVersion + "<tr><td>Version</td><td class=''>" + ftui.version + "</td></tr></table>"
+
+            // render 
+            $('.version').html(strVersion);
+            $('.general').html(strGeneral);
+            $('.subs').html(strSubs);
+            $('.mods').html(strMods);
+            $('.short').html(strShort);
+            $('.long').html(strLong);
+
+        };
+
+        $(document).ready(function() {
+
+            $(window).on('storage', function(e) {
+                update();
+            });
+
+            update();
+        });
+
+    </script>
+
+    <title>FTUI-Check</title>
+</head>
+
+<body>
+    <div class="check">
+        <h1>FTUI Health Check</h1>
+        <div class="version left-align events"></div>
+        <h2>Settings</h2>
+        <div class="general left-align events"></div>
+        <h2>Subscriptions</h2>
+        <div class="subs left-align events"></div>
+        <h2>Modules</h2>
+        <div class="mods left-align events"></div>
+        <h2>ShortPoll</h2>
+        <div class="short left-align events wordwrap"></div>
+        <h2>LongPoll</h2>
+        <div class="long left-align events wordwrap"></div>
+    </div>
+</body>
+
+</html>

+ 37 - 0
fhem/core/www/tablet/js/widget_include.js

@@ -0,0 +1,37 @@
+/* FTUI Plugin
+ * Copyright (c) 2017 Mario Stephan <mstephan@shared-files.de>
+ * originally created 2017 Thomas Gödicke <tgoedicke89@gmail.com>
+ * Under MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+/* global ftui:true, Modul_widget:true */
+
+"use strict";
+
+var Modul_include = function () {
+    
+    function init() {
+
+        me.elements = $('div[data-type="' + me.widgetname + '"]:not([data-ready])', me.area);
+        me.elements.each(function (index) {
+            var elem = $(this);
+            elem.attr("data-ready", "");
+
+            $.get(elem.data('url'), {}, function (data) {
+                var parValues = elem.data('parameter');
+                for (var key in parValues) {
+                    data = data.replace(new RegExp(key, 'g'), parValues[key]);
+                }
+                elem.html(data);
+                ftui.initPage('[data-wgid="' + elem.wgid() + '"]');
+            });
+        });
+    }
+    
+    var me = $.extend(new Modul_widget(), {
+        widgetname: 'include',
+        init: init,
+    });
+    
+    return me;
+};

+ 46 - 0
fhem/core/www/tablet/js/widget_theme.js

@@ -0,0 +1,46 @@
+/* FTUI Plugin
+ * Copyright (c) 2018 Mario Stephan <mstephan@shared-files.de>
+ * Under MIT License (http://www.opensource.org/licenses/mit-license.php)
+ * https://github.com/knowthelist/fhem-tablet-ui
+ */
+
+/* global ftui:true, Modul_widget:true */
+
+"use strict";
+
+// usage: <link rel="stylesheet" href="css/fhem-darkblue-ui.css" data-device="ftuitest" 
+//          data-type="theme" data-get="theme" data-get-on="night" data-get-off="!night" />
+
+var Modul_theme = function () {
+
+    function init_ui(elem) {}
+
+    function update(dev, par) {
+
+        me.elements.filterDeviceReading('get', dev, par)
+            .each(function (index) {
+                var elem = $(this);
+                var state = elem.getReading('get').val;
+                if (elem.matchingState('get', state) === 'on') {
+                    elem.removeAttr('disabled');
+                    //ftui.loadStyleSchema();  
+                    // ToDo: support ftui.getStyle functions  
+                }
+                if (elem.matchingState('get', state) === 'off') {
+                    elem.attr('disabled', 'disabled');
+                    //ftui.loadStyleSchema(); 
+                }
+            });
+    }
+
+    // public
+    // inherit all public members from base class
+    var me = $.extend(new Modul_widget(), {
+        //override or own public members
+        widgetname: 'theme',
+        init_ui: init_ui,
+        update: update
+    });
+
+    return me;
+};

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/farbtastic.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/fhemSVG.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/genericons.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/jquery.circlemenu.min.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/jquery.datetimepicker.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/jquery.datetimepicker.min.js


+ 66 - 0
fhem/core/www/tablet/lib/jquery.unveil.js

@@ -0,0 +1,66 @@
+/**
+ * jQuery Unveil
+ * A very lightweight jQuery plugin to lazy load images
+ * inspired from http://luis-almeida.github.com/unveil (2013 Luís Almeida)
+ *
+ * Copyright (c) 2017 Mario Stephan <mstephan@shared-files.de>
+ * Under MIT License (http://www.opensource.org/licenses/mit-license.php)
+ * https://github.com/knowthelist/fhem-tablet-ui
+ */
+
+;(function($) {
+
+$.fn.unveil = function (options) {
+
+    var opts = $.extend({}, $.fn.unveil.defaults, options);
+
+    var loaded,
+        images = this;
+
+    this.one("unveil", function () {
+        //console.log('unveil');
+        var elem = $(this).find('[' + opts.attrib + ']');
+        var source = elem.attr(opts.attrib);
+        if (source) {
+            elem.attr("src", source);
+            if (typeof opts.afterUnveil === "function") opts.afterUnveil.call(this);
+        }
+    });
+
+    function unveil() {
+
+        var inview = images.filter(function () {
+            var elem = $(this);
+            if (elem.is(":hidden")) return;
+
+            var viewTop = opts.container.scrollTop(),
+                viewBottom = viewTop + opts.container.height(),
+                elemTop = elem.position().top,
+                elemBottom = elemTop + elem.height();
+            //console.log(elemTop,elemBottom ,viewTop - opts.threshold, viewBottom + opts.threshold);
+            return elemBottom >= viewTop - opts.threshold && elemTop <= viewBottom + opts.threshold;
+        });
+        loaded = inview.trigger("unveil");
+        images = images.not(loaded);
+    }
+
+    opts.container.on("scroll.unveil resize.unveil", unveil);
+    $(document).on(opts.customEvent, unveil);
+
+    unveil();
+
+    return this;
+
+};
+
+
+// Plugin defaults
+$.fn.unveil.defaults = {
+    container: $(window),
+    threshold: 0,
+    attrib: "data-src",
+
+};
+
+
+})(window.jQuery);

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/jquery.unveil.min.js


+ 1 - 0
fhem/core/www/tablet/lib/jquery.wakeup.min.js

@@ -0,0 +1 @@
+!function(e,n,r){var t=new Array;e.wakeUp=function(e,n,r){if(r&&"number"==typeof r||(r=1e3),"function"!=typeof e)return null;var a=(new Date).getTime(),i=setInterval(function(){var t=(new Date).getTime();if(t>a+r+1e3){var i=t-a;a=t,n?e(i,n):e(i)}else a=t},r);return t.push(i),i},e.ignoreBell=function(n){n&&(t.splice(e.inArray(n,t),1),clearInterval(n))},e.dreamOn=function(){e.each(t,function(e,n){clearInterval(n)}),t=new Array}}(jQuery,document);

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
fhem/core/www/tablet/lib/openautomation.min.css