ソースを参照

FHEM core mit in das Repo

Matthias Kleine 8 年 前
コミット
342ecf59bc
共有100 個のファイルを変更した130272 個の追加15 個の削除を含む
  1. 34 15
      fhem/Dockerfile
  2. 816 0
      fhem/core/FHEM/00_CM11.pm
  3. 1754 0
      fhem/core/FHEM/00_CUL.pm
  4. 578 0
      fhem/core/FHEM/00_FBAHA.pm
  5. 347 0
      fhem/core/FHEM/00_FBAHAHTTP.pm
  6. 1010 0
      fhem/core/FHEM/00_FHZ.pm
  7. 1455 0
      fhem/core/FHEM/00_HMLAN.pm
  8. 2645 0
      fhem/core/FHEM/00_HMUARTLGW.pm
  9. 150 0
      fhem/core/FHEM/00_HXB.pm
  10. 1319 0
      fhem/core/FHEM/00_KM271.pm
  11. 168 0
      fhem/core/FHEM/00_LIRC.pm
  12. 947 0
      fhem/core/FHEM/00_MAXLAN.pm
  13. 588 0
      fhem/core/FHEM/00_MQTT.pm
  14. 522 0
      fhem/core/FHEM/00_MYSENSORS.pm
  15. 650 0
      fhem/core/FHEM/00_NetzerI2C.pm
  16. 2584 0
      fhem/core/FHEM/00_OWX.pm
  17. 1284 0
      fhem/core/FHEM/00_OWX_ASYNC.pm
  18. 866 0
      fhem/core/FHEM/00_RPII2C.pm
  19. 3735 0
      fhem/core/FHEM/00_SIGNALduino.pm
  20. 9370 0
      fhem/core/FHEM/00_SONOS.pm
  21. 1500 0
      fhem/core/FHEM/00_TCM.pm
  22. 2028 0
      fhem/core/FHEM/00_THZ.pm
  23. 1296 0
      fhem/core/FHEM/00_TUL.pm
  24. 770 0
      fhem/core/FHEM/00_ZWCUL.pm
  25. 1279 0
      fhem/core/FHEM/00_ZWDongle.pm
  26. 4677 0
      fhem/core/FHEM/01_FHEMWEB.pm
  27. 520 0
      fhem/core/FHEM/02_FRAMEBUFFER.pm
  28. 1218 0
      fhem/core/FHEM/02_FTUISRV.pm
  29. 270 0
      fhem/core/FHEM/02_HTTPSRV.pm
  30. 1240 0
      fhem/core/FHEM/02_RSS.pm
  31. 199 0
      fhem/core/FHEM/09_BS.pm
  32. 497 0
      fhem/core/FHEM/09_CUL_FHTTK.pm
  33. 251 0
      fhem/core/FHEM/09_USF1000.pm
  34. 12118 0
      fhem/core/FHEM/10_CUL_HM.pm
  35. 484 0
      fhem/core/FHEM/10_CUL_IR.pm
  36. 654 0
      fhem/core/FHEM/10_DUOFERNSTICK.pm
  37. 1508 0
      fhem/core/FHEM/10_EIB.pm
  38. 789 0
      fhem/core/FHEM/10_EQ3BT.pm
  39. 20412 0
      fhem/core/FHEM/10_EnOcean.pm
  40. 683 0
      fhem/core/FHEM/10_FBDECT.pm
  41. 1183 0
      fhem/core/FHEM/10_FRM.pm
  42. 932 0
      fhem/core/FHEM/10_FS20.pm
  43. 306 0
      fhem/core/FHEM/10_HXBDevice.pm
  44. 1735 0
      fhem/core/FHEM/10_IT.pm
  45. 162 0
      fhem/core/FHEM/10_Itach_IR.pm
  46. 2095 0
      fhem/core/FHEM/10_KNX.pm
  47. 930 0
      fhem/core/FHEM/10_KOPP_FC.pm
  48. 1223 0
      fhem/core/FHEM/10_MAX.pm
  49. 239 0
      fhem/core/FHEM/10_MQTT_BRIDGE.pm
  50. 244 0
      fhem/core/FHEM/10_MQTT_DEVICE.pm
  51. 669 0
      fhem/core/FHEM/10_MYSENSORS_DEVICE.pm
  52. 825 0
      fhem/core/FHEM/10_OWServer.pm
  53. 2327 0
      fhem/core/FHEM/10_RESIDENTS.pm
  54. 1716 0
      fhem/core/FHEM/10_SOMFY.pm
  55. 534 0
      fhem/core/FHEM/10_UNIRoll.pm
  56. 6337 0
      fhem/core/FHEM/10_ZWave.pm
  57. 1030 0
      fhem/core/FHEM/10_pilight_ctrl.pm
  58. 1399 0
      fhem/core/FHEM/11_FHT.pm
  59. 293 0
      fhem/core/FHEM/11_FHT8V.pm
  60. 1065 0
      fhem/core/FHEM/11_OWDevice.pm
  61. 395 0
      fhem/core/FHEM/12_HMS.pm
  62. 453 0
      fhem/core/FHEM/13_KS300.pm
  63. 774 0
      fhem/core/FHEM/14_CUL_MAX.pm
  64. 346 0
      fhem/core/FHEM/14_CUL_REDIRECT.pm
  65. 1384 0
      fhem/core/FHEM/14_CUL_TCM97001.pm
  66. 205 0
      fhem/core/FHEM/14_CUL_TX.pm
  67. 467 0
      fhem/core/FHEM/14_CUL_WS.pm
  68. 484 0
      fhem/core/FHEM/14_Hideki.pm
  69. 511 0
      fhem/core/FHEM/14_SD_WS.pm
  70. 313 0
      fhem/core/FHEM/14_SD_WS07.pm
  71. 496 0
      fhem/core/FHEM/14_SD_WS09.pm
  72. 298 0
      fhem/core/FHEM/14_SD_WS_Maverick.pm
  73. 467 0
      fhem/core/FHEM/15_CUL_EM.pm
  74. 228 0
      fhem/core/FHEM/16_CUL_RFR.pm
  75. 323 0
      fhem/core/FHEM/16_STACKABLE_CC.pm
  76. 455 0
      fhem/core/FHEM/17_EGPM2LAN.pm
  77. 362 0
      fhem/core/FHEM/17_SIS_PMS.pm
  78. 127 0
      fhem/core/FHEM/18_CUL_HOERMANN.pm
  79. 162 0
      fhem/core/FHEM/19_Revolt.pm
  80. 297 0
      fhem/core/FHEM/19_VBUSIF.pm
  81. 203 0
      fhem/core/FHEM/20_FRM_AD.pm
  82. 137 0
      fhem/core/FHEM/20_FRM_I2C.pm
  83. 310 0
      fhem/core/FHEM/20_FRM_IN.pm
  84. 49 0
      fhem/core/FHEM/20_FRM_LCD.pm
  85. 165 0
      fhem/core/FHEM/20_FRM_OUT.pm
  86. 349 0
      fhem/core/FHEM/20_FRM_PWM.pm
  87. 368 0
      fhem/core/FHEM/20_FRM_RGB.pm
  88. 275 0
      fhem/core/FHEM/20_FRM_ROTENC.pm
  89. 163 0
      fhem/core/FHEM/20_FRM_SERVO.pm
  90. 333 0
      fhem/core/FHEM/20_FRM_STEPPER.pm
  91. 1865 0
      fhem/core/FHEM/20_GUEST.pm
  92. 519 0
      fhem/core/FHEM/20_N4HBUS.pm
  93. 372 0
      fhem/core/FHEM/20_OWFS.pm
  94. 1850 0
      fhem/core/FHEM/20_ROOMMATE.pm
  95. 652 0
      fhem/core/FHEM/20_X10.pm
  96. 1032 0
      fhem/core/FHEM/21_N4HMODULE.pm
  97. 1856 0
      fhem/core/FHEM/21_OWAD.pm
  98. 2229 0
      fhem/core/FHEM/21_OWCOUNT.pm
  99. 539 0
      fhem/core/FHEM/21_OWID.pm
  100. 0 0
      fhem/core/FHEM/21_OWLCD.pm

+ 34 - 15
fhem/Dockerfile

@@ -9,48 +9,68 @@ ENV TERM xterm
 # Install dependencies
 RUN apt-get update && apt-get upgrade -y --force-yes && apt-get install -y --force-yes --no-install-recommends apt-utils
 RUN apt-get -y --force-yes install \
-perl \
-wget \
-git \
 apt-transport-https \
-sudo etherwake \
-dfu-programmer \
 build-essential \
-snmpd \
+dfu-programmer \
+etherwake \
+git \
+perl \
 snmp \
-vim \
+snmpd \
+sqlite3 \
+sudo \
 telnet \
 usbutils \
-sqlite3
+vim \
+wget
 
 # Install perl packages
 RUN apt-get -y --force-yes install \
-libavahi-compat-libdnssd-dev \
 libalgorithm-merge-perl \
+libauthen-oath-perl \
+libavahi-compat-libdnssd-dev \
+libcgi-pm-perl \
 libclass-dbi-mysql-perl \
 libclass-isa-perl \
 libcommon-sense-perl \
+libconvert-base32-perl \
+libcrypt-urandom-perl \
+libdata-dump-perl \
 libdatetime-format-strptime-perl \
+libdbd-sqlite3-perl \
 libdbi-perl \
 libdevice-serialport-perl \
 libdpkg-perl \
 liberror-perl \
 libfile-copy-recursive-perl \
 libfile-fcntllock-perl \
+libgd-graph-perl \
+libgd-text-perl \
+libimage-info-perl \
+libimage-librsvg-perl \
+libio-socket-inet6-perl \
 libio-socket-ip-perl \
+libio-socket-multicast-perl \
 libio-socket-ssl-perl \
 libjson-perl \
 libjson-xs-perl \
+liblist-moreutils-perl \
+libmail-imapclient-perl \
 libmail-sendmail-perl \
+libmime-base64-perl \
+libnet-telnet-perl \
+libsoap-lite-perl \
 libsocket-perl \
+libsocket6-perl \
 libswitch-perl \
 libsys-hostname-long-perl \
 libterm-readkey-perl \
 libterm-readline-perl-perl \
+libtext-csv-perl \
+libtext-diff-perl \
+libtimedate-perl \
 libwww-perl \
-libxml-simple-perl \
-libdbd-sqlite3-perl \
-libtext-diff-perl
+libxml-simple-perl
 
 # Install fhem
 RUN echo Europe/Berlin > /etc/timezone && dpkg-reconfigure tzdata
@@ -60,9 +80,8 @@ RUN userdel fhem
 
 WORKDIR "/opt/fhem"
 
-COPY data/fhem.cfg.example ./fhem.cfg
-COPY start.sh ./
+COPY core/start.sh ./
 
 EXPOSE 8083 7072
 
-CMD bash /opt/fhem/start.sh
+CMD bash /opt/fhem/start.sh

+ 816 - 0
fhem/core/FHEM/00_CM11.pm

@@ -0,0 +1,816 @@
+################################################################
+#
+#  Copyright notice
+#
+#  (c) 2008 Dr. Boris Neubert (omega@online.de)
+#
+#  This script 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.
+#
+#  The GNU General Public License can be found at
+#  http://www.gnu.org/copyleft/gpl.html.
+#  A copy is found in the textfile GPL.txt and important notices to the license
+#  from the author is found in LICENSE.txt distributed with these scripts.
+#
+#  This script 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.
+#
+#  This copyright notice MUST APPEAR in all copies of the script!
+#
+################################################################
+
+# $Id: 00_CM11.pm 9805 2015-11-07 06:38:08Z borisneubert $
+
+package main;
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+
+sub CM11_Write($$$);
+sub CM11_Read($);
+sub CM11_Ready($$);
+
+my $msg_pollpc   = pack("H*", "5a"); # interface poll signal (CM11->PC)
+my $msg_pollpcpf = pack("H*", "a5"); # power fail poll signal (CM11->PC)
+my $msg_pollack  = pack("H*", "c3"); # response to poll signal (PC->CM11)
+my $msg_pollackpf= pack("H*", "fb"); # response to power fail poll signal (PC->CM11)
+my $msg_txok     = pack("H*", "00"); # OK for transmission (PC->CM11)
+my $msg_ifrdy    = pack("H*", "55"); # interface ready (CM11->PC)
+my $msg_statusrq = pack("H*", "8b");  # status request (PC->CM11)
+
+my %housecodes_rcv = qw(0110 A  1110 B  0010 C  1010 D
+                        0001 E  1001 F  0101 G  1101 H
+                        0111 I  1111 J  0011 K  1011 L
+                        0000 M  1000 N  0100 O  1100 P);
+
+my %unitcodes_rcv  = qw(0110 1  1110 2  0010 3  1010 4
+                        0001 5  1001 6  0101 7  1101 8
+                        0111 9  1111 10  0011 11  1011 12
+                        0000 13  1000 14 0100 15 1100 16);
+
+my %functions_rcv  = qw(0000 ALL_UNITS_OFF
+			0001 ALL_LIGHTS_ON
+			0010 ON
+			0011 OFF
+			0100 DIM
+			0101 BRIGHT
+			0110 ALL_LIGHTS_OFF
+                        0111 EXTENDED_CODE
+			1000 HAIL_REQUEST
+			1001 HAIL_ACK
+			1010 PRESET_DIM1
+			1011 PRESET_DIM2
+			1100 EXTENDED_DATA_TRANSFER
+                        1101 STATUS_ON
+			1110 STATUS_OFF
+			1111 STATUS_REQUEST);
+
+
+my %gets = (
+  "fwrev"   => "xxx",
+  "time"   => "xxx",
+);
+
+my %sets = (
+  "reopen"   => "xxx",
+);
+
+
+#####################################
+
+sub
+CM11_Initialize($)
+{
+  my ($hash) = @_;
+
+# Provider
+  $hash->{ReadFn}  = "CM11_Read";
+  $hash->{WriteFn} = "CM11_Write";
+  $hash->{Clients} = ":X10:";
+  $hash->{ReadyFn} = "CM11_Ready";
+
+# Normal Device
+  $hash->{DefFn}   = "CM11_Define";
+  $hash->{UndefFn} = "CM11_Undef";
+  $hash->{GetFn}   = "CM11_Get";
+  $hash->{SetFn}   = "CM11_Set";
+  $hash->{StateFn} = "CM11_SetState";
+  $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " .
+                     "model:CM11";
+}
+#####################################
+sub
+CM11_DoInit($$$)
+{
+  my ($name,$type,$po) = @_;
+  my @init;
+
+  $po->reset_error();
+  $po->baudrate(4800);
+  $po->databits(8);
+  $po->parity('none');
+  $po->stopbits(1);
+  $po->handshake('none');
+
+  if($type && $type eq "strangetty") {
+
+    # This part is for some Linux kernel versions whih has strange default
+    # settings.  Device::SerialPort is nice: if the flag is not defined for your
+    # OS then it will be ignored.
+    $po->stty_icanon(0);
+    #$po->stty_parmrk(0); # The debian standard install does not have it
+    $po->stty_icrnl(0);
+    $po->stty_echoe(0);
+    $po->stty_echok(0);
+    $po->stty_echoctl(0);
+
+    # Needed for some strange distros
+    $po->stty_echo(0);
+    $po->stty_icanon(0);
+    $po->stty_isig(0);
+    $po->stty_opost(0);
+    $po->stty_icrnl(0);
+  }
+
+  $po->write_settings;
+  $defs{$name}{STATE} = "Initialized";
+
+}
+
+
+#####################################
+sub
+CM11_Reopen($)
+{
+  my ($hash) = @_;
+	my $name = $hash->{NAME};
+  my $dev = $hash->{DeviceName};
+  $hash->{PortObj}->close();
+  Log3($name, 1, "Device $dev closed");
+  for(;;) {
+      sleep(5);
+      if($^O =~ m/Win/) {
+        $hash->{PortObj} = new Win32::SerialPort($dev);
+      }else{
+        $hash->{PortObj} = new Device::SerialPort($dev);
+      }
+      if($hash->{PortObj}) {
+	Log3($name, 1, "Device $dev reopened");
+        $hash->{FD} = $hash->{PortObj}->FILENO if($^O !~ m/Win/);
+        CM11_DoInit($hash->{NAME}, $hash->{ttytype}, $hash->{PortObj});
+        return;
+      }
+  }
+}
+
+#####################################
+sub
+CM11_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+  my $po;
+
+  return "wrong syntax: define <name> CM11 devicename ".
+                        "[normal|strangetty] [mobile]" if(@a < 3 || @a > 5);
+
+
+  delete $hash->{PortObj};
+  delete $hash->{FD};
+
+  my $name = $a[0];
+  my $dev = $a[2];
+  $hash->{ttytype} = $a[3] if($a[3]);
+  $hash->{MOBILE} = 1 if($a[4] && $a[4] eq "mobile");
+  $hash->{STATE} = "defined";
+
+  if($dev eq "none") {
+    Log3($name, 1, "CM11 device is none, commands will be echoed only");
+    $attr{$name}{dummy} = 1;
+    return undef;
+  }
+
+  $hash->{DeviceName} = $dev;
+  $hash->{PARTIAL} = "";
+  Log3($name, 3, "CM11 opening CM11 device $dev");
+  if ($^O=~/Win/) {
+   require Win32::SerialPort;
+   $po = new Win32::SerialPort ($dev);
+  } else  {
+   require Device::SerialPort;
+   $po = new Device::SerialPort ($dev);
+  }
+  if(!$po) {
+    my $msg = "Can't open $dev: $!";
+    Log3($name, 3, $msg) if($hash->{MOBILE});
+    return $msg if(!$hash->{MOBILE});
+    $readyfnlist{"$name.$dev"} = $hash;
+    return "";
+  }
+  Log3($name, 3, "CM11 opened CM11 device $dev");
+
+  $hash->{PortObj} = $po;
+  if( $^O !~ /Win/ ) {
+    $hash->{FD} = $po->FILENO;
+    $selectlist{"$name.$dev"} = $hash;
+  } else {
+    $readyfnlist{"$name.$dev"} = $hash;
+  }
+
+  CM11_DoInit($name, $hash->{ttytype}, $po);
+
+  #CM11_SetInterfaceTime($hash);
+  #CM11_GetInterfaceStatus($hash);
+  return undef;
+}
+
+#####################################
+sub
+CM11_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  my $name = $hash->{NAME};
+
+  foreach my $d (sort keys %defs) {
+    if(defined($defs{$d}) &&
+       defined($defs{$d}{IODev}) &&
+       $defs{$d}{IODev} == $hash)
+      {
+        my $lev = ($reread_active ? 4 : 2);
+	Log3($name, $lev, "deleting port for $d");
+        delete $defs{$d}{IODev};
+      }
+  }
+  $hash->{PortObj}->close() if($hash->{PortObj});
+  return undef;
+}
+
+
+#####################################
+sub
+CM11_SetState($$$$)
+{
+  my ($hash, $tim, $vt, $val) = @_;
+  return undef;
+}
+
+#####################################
+sub
+CM11_LogReadWrite($@)
+{
+  my ($rw,$hash, $msg, $trlr) = @_;
+  my $name= $hash->{NAME};
+  Log3($name, 5, "CM11 device " . $name . ": $rw " .
+		sprintf("%2d: ", length($msg)) . unpack("H*", $msg));
+}
+
+sub
+CM11_LogRead(@)
+{
+  CM11_LogReadWrite("read ", @_);
+}
+
+sub
+CM11_LogWrite(@)
+{
+  CM11_LogReadWrite("write", @_);
+}
+
+#####################################
+
+sub
+CM11_SimpleWrite($$)
+{
+  my ($hash, $msg) = @_;
+  return if(!$hash || !defined($hash->{PortObj}));
+  CM11_LogWrite($hash,$msg);
+  $hash->{PortObj}->write($msg);
+}
+
+#####################################
+sub
+CM11_ReadDirect($$)
+{
+  # This is a direct read for CM11_Write
+  my ($hash,$arg) = @_;
+  return undef if(!$hash || !defined($hash->{FD}));
+
+  my $name= $hash->{NAME};
+  my $prefix= "CM11 device " . $name . ":";
+  my $rin= '';
+  my $nfound;
+
+  if($^O eq 'MSWin32') {
+      $nfound= CM11_Ready($hash, undef);
+  } else {
+      vec($rin, $hash->{FD}, 1) = 1;
+      my $to = 20;  # seconds timeout (response might be damn slow)
+      $to = $hash->{RA_Timeout} if($hash->{RA_Timeout});  # ...or less
+      $nfound = select($rin, undef, undef, $to);
+      if($nfound < 0) {
+        next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
+	Log3($name, 3, "$prefix Select error $nfound / $!");
+        return undef;
+      }
+  }
+  if(!$nfound) {
+      Log3($name, 3, "$prefix Timeout reading $arg");
+      return undef;
+  }
+
+  my $buf = $hash->{PortObj}->input();
+  CM11_LogRead($hash,$buf);
+  return $buf;
+}
+
+#####################################
+sub
+CM11_Write($$$)
+{
+  # send two bytes, verify checksum, send ok
+  my ($hash,$b1,$b2) = @_;
+  my $name = $hash->{NAME};
+  my $prefix= "CM11 device $name:";
+
+  if(!$hash || !defined($hash->{PortObj})) {
+      Log3($name, 3, "$prefix device is not active, cannot send");
+    return;
+
+  }
+
+  # checksum
+  my $b1d = unpack('C', $b1);
+  my $b2d = unpack('C', $b2);
+  my $checksum_w = ($b1d + $b2d) & 0xff;
+
+  my $data;
+
+  # try 5 times to send
+  my $try= 5;
+  for(;;) {
+    $try--;
+    # send two bytes
+    $data= $b1 . $b2;
+    CM11_LogWrite($hash,$data);
+    $hash->{PortObj}->write($data);
+
+    # get checksum
+    my $checksum= CM11_ReadDirect($hash, "checksum");
+    return 0 if(!defined($checksum)); # read failure
+
+    my $checksum_r= unpack('C', $checksum);
+    if($checksum_w ne $checksum_r) {
+      Log3($name, 5, "$prefix wrong checksum (send: $checksum_w, received: $checksum_r)");
+      return 0 if(!$try);
+      my $nexttry= 6-$try;
+      Log3($name, 5, "$prefix retrying (" . $nexttry . "/5)");
+    } else {
+      Log3($name, 5, "$prefix checksum correct, OK for transmission");
+      last;
+    }
+  }
+
+  # checksum ok => send OK for transmission
+  $data= $msg_txok;
+  CM11_LogWrite($hash,$data);
+  $hash->{PortObj}->write($data);
+  my $ready= CM11_ReadDirect($hash, "ready");
+  return 0 if(!defined($ready)); # read failure
+  if($ready ne $msg_ifrdy) {
+      Log3($name, 3, "$prefix strange ready signal (" . unpack('C', $ready) . ")");
+      return 0
+  } else {
+      Log3($name, 5, "$prefix ready");
+  }
+
+  # we are fine
+  return 1;
+}
+
+#####################################
+sub
+CM11_GetInterfaceStatus($)
+{
+    my ($hash)= @_;
+
+    CM11_SimpleWrite($hash, $msg_statusrq);
+    my $statusmsg= "";
+    while(length($statusmsg)<14) {
+      my $buf= CM11_ReadDirect($hash, "status");
+      return if(!defined($buf)); # read error
+      $statusmsg.= $buf;
+    }
+    return $statusmsg;
+}
+
+#####################################
+sub CM11_Get($@)
+{
+  my ($hash, @a) = @_;
+
+  return "CM11: get needs only one parameter" if(@a != 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", sort keys %gets)
+        if(!defined($gets{$a[1]}));
+
+  my ($fn, $arg) = split(" ", $gets{$a[1]});
+
+  my $v = join(" ", @a);
+  my $name = $hash->{NAME};
+  Log3($name, 2, "CM11 get $v");
+
+  my $statusmsg= CM11_GetInterfaceStatus($hash);
+  if(!defined($statusmsg)) {
+	$v= "error";
+	Log3($name, 2, "CM11 error, device is irresponsive.")
+  } else {
+	my $msg= unpack("H*", $statusmsg);
+  	Log3($name, 5, "CM11 got ". $msg);
+
+	if($a[1] eq "fwrev") {
+    		$v = hex(substr($msg, 14, 1));
+  	} elsif($a[1] eq "time") {
+		my $sec= hex(substr($msg, 4, 2));
+		my $hour= hex(substr($msg, 8, 2))*2;
+		my $min= hex(substr($msg, 6, 2)); 
+		if($min>59) {
+			$min-= 60;
+			$hour++;	
+		}
+		my $day= hex(substr($msg, 10, 2));
+		$day+= 256 if(hex(substr($msg, 12, 1)) & 0xf);
+		$v= sprintf("%d.%02d:%02d:%02d", $day,$hour,$min,$sec);
+	}
+  }
+  $hash->{READINGS}{$a[1]}{VAL} = $v;
+  $hash->{READINGS}{$a[1]}{TIME} = TimeNow();
+
+  return "$a[0] $a[1] => $v";
+}
+
+
+#####################################
+sub
+CM11_Set($@)
+{
+  my ($hash, @a) = @_;
+
+  return "CM11: set needs one parameter" if(@a != 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
+        if(!defined($sets{$a[1]}));
+
+  my ($fn, $arg) = split(" ", $sets{$a[1]});
+
+  my $v = join(" ", @a);
+  my $name = $hash->{NAME};
+  Log3($name, 2, "CM11 set $v");
+
+  if($a[1] eq "reopen") {
+    CM11_Reopen($hash);
+  }
+
+  return undef;
+}
+
+#####################################
+sub
+CM11_SetInterfaceTime($)
+{
+    my ($hash)= @_;
+
+# 7 Bytes, Bits 0..55 are
+# 55 to 48   timer download header (0x9b)
+# 47 to 40   Current time (seconds)
+# 39 to 32   Current time (minutes ranging from 0 to 119)
+# 31 to 23   Current time (hours/2, ranging from 0 to 11)
+# 23 to 16   Current year day (bits 0 to 7)
+# 15         Current year day (bit 8)
+# 14 to 8    Day mask (SMTWTFS)
+# 7 to 4     Monitored house code
+# 3          Reserved
+# 2          Battery timer clear flag
+# 1          Monitored status clear flag
+# 0          Timer purge flag
+
+    # make the interface happy (time is set to zero)
+    my $data = pack('C7', 0x9b,0x00,0x00,0x00,0x00,0x00,0x03);
+    CM11_SimpleWrite($hash, $data);
+    # get checksum (ignored)
+    my $checksum= CM11_ReadDirect($hash, "checksum");
+    return 0 if(!defined($checksum)); # read failure
+    # tx OK
+    CM11_SimpleWrite($hash, $msg_txok);
+    # get ready (ignored)
+    my $ready= CM11_ReadDirect($hash, "ready");
+    return 0 if(!defined($ready)); # read failure
+    return 1;
+}
+
+#####################################
+sub
+CM11_Read($)
+{
+  #
+  # prolog
+  #
+
+  my ($hash) = @_;
+
+  my $buf = $hash->{PortObj}->input();
+  my $name = $hash->{NAME};
+
+  # prefix for logging
+  my $prefix= "CM11 device " . $name . ":";
+
+  # Lets' try again: Some drivers return len(0) on the first read...
+  if(defined($buf) && length($buf) == 0) {
+    $buf = $hash->{PortObj}->input();
+  }
+
+  # USB troubleshooting
+  if(!defined($buf) || length($buf) == 0) {
+    my $dev = $hash->{DeviceName};
+    Log3($name, 1, "USB device $dev disconnected, waiting to reappear");
+    $hash->{PortObj}->close();
+    DoTrigger($name, "DISCONNECTED");
+
+    delete($hash->{PortObj});
+    delete($selectlist{"$name.$dev"});
+    $readyfnlist{"$name.$dev"} = $hash; # Start polling
+    $hash->{STATE} = "disconnected";
+
+    # Without the following sleep the open of the device causes a SIGSEGV,
+    # and following opens block infinitely. Only a reboot helps.
+    sleep(5);
+  }
+
+  #
+  # begin of message digesting
+  #
+
+  # concatenate yet unparsed message and newly received data
+  my $x10data = $hash->{PARTIAL} . $buf;
+  CM11_LogRead($hash,$buf);
+  Log3($name, 5, "$prefix Data: " . unpack('H*',$x10data));
+
+  # normally the while loop will run only once
+  while(length($x10data) > 0) {
+
+        # we cut off everything before the latest poll signal
+        my $p= index(reverse($x10data), $msg_pollpc);
+        if($p<0) { $p= index(reverse($x10data), $msg_pollpcpf); }
+        if($p>=0) { $x10data= substr($x10data, -$p-1); }
+
+        # to start with, a single 0x5a is received
+	if( substr($x10data,0,1) eq $msg_pollpc ) {	# CM11 polls PC
+		Log3($name, 5, "$prefix start of message");
+		CM11_SimpleWrite($hash, $msg_pollack);	# PC ready
+		$x10data= substr($x10data,1);		# $x10data now empty
+		next;
+	}
+
+        # experimental code follows
+	#if( substr($x10data,0,2) eq pack("H*", "98e6") ) {	# CM11 polls PC
+        #	Log 5, "$prefix 98e6";
+	#	CM11_SimpleWrite($hash, $msg_pollack);	# PC ready
+        #        $x10data= "";
+	#	next;
+	#}
+	#if( substr($x10data,0,1) eq pack("H*", "98") ) {	# CM11 polls PC
+	#	Log 5, "$prefix 98";
+	#	next;
+	#}
+
+        # a single 0xa5 is a power-fail macro download poll
+        if( substr($x10data,0,1) eq $msg_pollpcpf ) {     # CM11 polls PC
+		Log3($name, 5, "$prefix power-fail poll");
+                # the documentation wrongly says that the macros should be downloaded
+                # in fact, the time must be set!
+                if(CM11_SetInterfaceTime($hash)) {
+                  Log3($name, 5, "$prefix power-fail poll satisfied");
+                } else {
+                  Log3($name, 5, "$prefix power-fail poll satisfaction failed");
+                }
+                $x10data= substr($x10data,1);             # $x10data now empty
+                next;
+        }
+
+        # a single 0x55 is a leftover from a failed transmission
+        if( substr($x10data,0,1) eq $msg_ifrdy ) {      # CM11 polls PC
+		Log3($name, 5, "$prefix skipping leftover ready signal");
+                $x10data= substr($x10data,1);
+                next;
+        }
+
+        # the message comes in small chunks of 1 or few bytes instead of the
+        # whole buffer at once
+	my $len= ord(substr($x10data,0,1))-1;		# upload buffer size
+	last if(length($x10data)< $len+2);		# wait for complete msg
+
+	# message is now complete, start interpretation
+
+	# mask: Bits 0 (LSB)..7 (MSB) correspond to data bytes 0..7
+        # bit= 0: unitcode, bit= 1: function
+	my $mask= unpack('B8', substr($x10data,1,1));
+	$x10data= substr($x10data,2); # cut off length and mask
+
+        # $x10data now contains $len data bytes
+	my $databytes= unpack('H*', substr($x10data,0));
+	Log3($name, 5, "$prefix message complete " .
+               "(length $len, mask $mask, data $databytes)");
+
+	# the following lines decode the messages into unitcodes and functions
+	# in general we have 0..n unitcodes followed by 1..m functions in the
+        # message
+	my $i= 0;
+	my $dmsg= "";
+	while($i< $len) {
+
+		my $data= substr($x10data, $i);
+           	my $bits = unpack('B8', $data);
+           	my $nibble_hi = substr($bits, 0, 4);
+           	my $nibble_lo = substr($bits, 4, 4);
+
+		my $housecode= $housecodes_rcv{$nibble_hi};
+
+		# one hash for unitcodes X_UNIT and one hash for functions
+                # X_FUNC is maintained per housecode X= A..P
+		my $housecode_unit= $housecode . "_UNIT";
+		my $housecode_func= $housecode . "_FUNC";
+
+		my $isfunc= (substr($mask, -$i-1, 1));
+		if($isfunc) {
+			# data byte is function
+			my $x10func= $functions_rcv{$nibble_lo};
+			if(($x10func eq "DIM") || ($x10func eq "BRIGHT")) {
+				my $level= ord(substr($x10data, ++$i));
+				$x10func.= " $level";
+			}
+			elsif($x10func eq "EXTENDED_DATA_TRANSFER") {
+				$data= substr($x10data, 2+(++$i));
+				my $command= substr($x10data, ++$i);
+				$x10func.= unpack("H*", $data) . ":" .
+                                            unpack("H*", $command);
+			}
+			$hash->{$housecode_func}= $x10func;
+			Log3($name, 5, "$prefix $housecode_func: " .
+                                $hash->{$housecode_func});
+			# dispatch message to clients
+
+                        my $hu = $hash->{$housecode_unit};
+                        $hu= "" unless(defined($hu));
+                        my $hf = $hash->{$housecode_func};
+                        my $dmsg= "X10:$housecode;$hu;$hf";
+			Dispatch($hash, $dmsg, undef);
+		} else {
+			# data byte is unitcode
+			# if a command was executed before, clear unitcode list
+			if(defined($hash->{$housecode_func})) {
+				undef $hash->{$housecode_unit};
+				undef $hash->{$housecode_func};
+			}
+			# get unitcode of unitcode
+			my $unitcode= $unitcodes_rcv{$nibble_lo};
+			# append to list of unitcodes
+			my $unitcodes= $hash->{$housecode_unit};
+			if(defined($hash->{$housecode_unit})) {
+				$unitcodes= $hash->{$housecode_unit} . " ";
+			} else {
+				$unitcodes= "";
+			}
+			$hash->{$housecode_unit}= "$unitcodes$unitcode";
+			Log3($name, 5, "$prefix $housecode_unit: " .
+                                $hash->{$housecode_unit});
+		}
+	$i++;
+	}
+	$x10data= '';
+  }
+
+  $hash->{PARTIAL} = $x10data;
+}
+
+#####################################
+sub
+CM11_Ready($$)
+{
+  my ($hash, $dev) = @_;
+  my $po=$hash->{PortObj};
+
+  if(!$po) {    # Looking for the device
+
+    my $dev = $hash->{DeviceName};
+    my $name = $hash->{NAME};
+
+    $hash->{PARTIAL} = "";
+    if ($^O=~/Win/) {
+     $po = new Win32::SerialPort ($dev);
+    } else  {
+     $po = new Device::SerialPort ($dev);
+    }
+    return undef if(!$po);
+
+    Log3($name, 1, "USB device $dev reappeared");
+    $hash->{PortObj} = $po;
+    if( $^O !~ /Win/ ) {
+      $hash->{FD} = $po->FILENO;
+      delete($readyfnlist{"$name.$dev"});
+      $selectlist{"$name.$dev"} = $hash;
+    } else {
+      $readyfnlist{"$name.$dev"} = $hash;
+    }
+
+    CM11_DoInit($name, $hash->{ttytype}, $po);
+    DoTrigger($name, "CONNECTED");
+    return undef;
+
+  }
+
+  # This is relevant for windows only
+  return undef if !$po;
+  my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
+  return ($InBytes>0);
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="CM11"></a>
+<h3>CM11</h3>
+<ul>
+  Note: this module requires the Device::SerialPort or Win32::SerialPort module.
+  <br><br>
+  <a name="CM11define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CM11 &lt;serial-device&gt;</code>
+    <br><br>
+    CM11 is the X10 module to interface X10 devices with the PC.<br><br>
+
+    The current implementation can evaluate incoming data on the powerline of
+    any kind. It can send on, off, dimdown and dimup commands.
+    <br><br>
+    The name of the serial-device depends on your distribution. If
+    serial-device is none, then no device will be opened, so you can experiment
+    without hardware attached.<br>
+
+    If you experience problems (for verbose 4 you get a lot of "Bad CRC message"
+    in the log), then try to define your device as <br>
+    <code>define &lt;name&gt; FHZ &lt;serial-device&gt; strangetty</code><br>
+    <br>
+
+    Example:
+    <ul>
+      <code>define x10if CM11 /dev/ttyUSB3</code><br>
+    </ul>
+    <br>
+  </ul>
+
+  <a name="CM11set"></a>
+  <b>Set</b>
+  <ul>
+    <code>set &lt;name&gt; reopen</code>
+    <br><br>
+    Reopens the serial port.
+  </ul>
+  <br>
+
+  <a name="CM11get"></a>
+  <b>Get</b>
+  <ul>
+    <code>get &lt;name&gt; fwrev</code>
+    <br><br>
+    Reads the firmware revision of the CM11 device. Returns <code>error</code>
+    if the serial connection to the device times out. Can be used for error
+    detection.
+    <br><br>
+
+    <code>get &lt;name&gt; time</code>
+    <br><br>
+    Reads the internal time of the device which is the total uptime (modulo one
+    year), since fhem sets the time to 0.00:00:00 if the device requests the time
+    to be set after being powered on. Returns <code>error</code>
+    if the serial connection to the device times out. Can be used for error
+    detection.
+  </ul>
+  <br>
+
+  <a name="CM11attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#attrdummy">dummy</a></li>
+    <li><a href="#model">model</a> (CM11)</li>
+  </ul>
+  <br>
+</ul>
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1754 - 0
fhem/core/FHEM/00_CUL.pm


+ 578 - 0
fhem/core/FHEM/00_FBAHA.pm

@@ -0,0 +1,578 @@
+##############################################
+# $Id: 00_FBAHA.pm 12235 2016-10-02 09:37:41Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+sub FBAHA_Read($@);
+sub FBAHA_Write($$$);
+sub FBAHA_ReadAnswer($$$);
+sub FBAHA_Ready($);
+
+sub FBAHA_getDevList($$);
+
+
+sub
+FBAHA_Initialize($)
+{
+  my ($hash) = @_;
+
+  require "$attr{global}{modpath}/FHEM/DevIo.pm";
+
+# Provider
+  $hash->{ReadFn}       = "FBAHA_Read";
+  $hash->{WriteFn}      = "FBAHA_Write";
+  $hash->{ReadyFn}      = "FBAHA_Ready";
+  $hash->{UndefFn}      = "FBAHA_Undef";
+  $hash->{ShutdownFn}   = "FBAHA_Undef";
+  $hash->{ReadAnswerFn} = "FBAHA_ReadAnswer";
+  $hash->{NotifyFn}     = "FBAHA_Notify";
+
+# Normal devices
+  $hash->{DefFn}   = "FBAHA_Define";
+  $hash->{GetFn}   = "FBAHA_Get";
+  $hash->{SetFn}   = "FBAHA_Set";
+  $hash->{AttrList}= "dummy:1,0";
+}
+
+
+#####################################
+sub
+FBAHA_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  if(@a != 3) {
+    return "wrong syntax: define <name> FBAHA hostname:2002";
+  }
+
+  my $name = $a[0];
+  my $dev = $a[2];
+  $hash->{Clients} = ":FBDECT:";
+  my %matchList = ( "1:FBDECT" => ".*" );
+  $hash->{MatchList} = \%matchList;
+
+  DevIo_CloseDev($hash);
+  $hash->{DeviceName} = $dev;
+
+  return undef if($dev eq "none"); # DEBUGGING
+  my $ret = DevIo_OpenDev($hash, 0, "FBAHA_DoInit");
+  return $ret;
+}
+
+#####################################
+sub
+FBAHA_Notify($$)
+{
+  my ($ntfy, $dev) = @_;
+  return if($dev->{NAME} ne "global" ||
+            !grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}));
+  delete $modules{FBAHA}{NotifyFn};
+  FBAHA_reassign($ntfy);
+  return;
+}
+
+#####################################
+sub
+FBAHA_Set($@)
+{
+  my ($hash, @a) = @_;
+  my $name = shift @a;
+  my %sets = ("createDevs"=>1, "reregister"=>1, "reopen"=>1);
+
+  return "set $name needs at least one parameter" if(@a < 1);
+  my $type = shift @a;
+
+  return "Unknown argument $type, choose one of " . join(" ", sort keys %sets)
+    if(!defined($sets{$type}));
+
+  if($type eq "createDevs") {
+
+    my %ex;
+    foreach my $sdev (devspec2array("TYPE=FBDECT")) {
+      my @dl = split(" ", $defs{$sdev}{DEF});
+      $ex{$dl[0]} = 1;
+    }
+
+    my @arg = FBAHA_getDevList($hash,0);
+    foreach my $arg (@arg) {
+      if($arg =~ m/ID:(\d+).*PROP:(.*)/) {
+        my ($i,$p) = ($1,$2,$3);
+        next if($ex{"$name:$i"});
+        my $msg = "UNDEFINED FBDECT_$i FBDECT $name:$i $p";
+        DoTrigger("global", $msg, 1);
+        Log3 $name, 3, "$msg, please define it";
+      }
+    }
+  }
+
+  if($type eq "reregister") {
+    # Release seems to be deadly on the 546e
+    FBAHA_Write($hash, "02", "") if($hash->{HANDLE});  # RELEASE
+    FBAHA_Write($hash, "00", "00022005");              # REGISTER
+    my ($err, $data) = FBAHA_ReadAnswer($hash, "REGISTER", "^01");
+    if($err) {
+      Log3 $name, 1, $err;
+      $hash->{STATE} =
+      $hash->{READINGS}{state}{VAL} = "???";
+      $hash->{READINGS}{state}{TIME} = TimeNow();
+      return $err;
+    }
+
+    if($data =~ m/^01030010(........)/) {
+      $hash->{STATE} =
+      $hash->{READINGS}{state}{VAL} = "Initialized";
+      $hash->{READINGS}{state}{TIME} = TimeNow();
+      $hash->{HANDLE} = $1;
+      Log3 $name, 1,
+        "FBAHA $hash->{NAME} registered with handle: $hash->{HANDLE}";
+
+    } else {
+      my $msg = "Got bogus answer for REGISTER request: $data";
+      Log3 $name, 1, $msg;
+      $hash->{STATE} =
+      $hash->{READINGS}{state}{VAL} = "???";
+      $hash->{READINGS}{state}{TIME} = TimeNow();
+      return $msg;
+
+    }
+    FBAHA_Write($hash, "03", "0000038200000000");  # LISTEN
+
+  }
+
+  if($type eq "reopen") {
+    DevIo_CloseDev($hash);
+    delete $hash->{HANDLE};
+    return DevIo_OpenDev($hash, 0, "FBAHA_DoInit");
+  }
+
+  return undef;
+}
+
+#####################################
+sub
+FBAHA_Get($@)
+{
+  my ($hash, @a) = @_;
+  my $name = shift @a;
+  my %gets = ("devList"=>1);
+
+  return "get $name needs at least one parameter" if(@a < 1);
+  my $type = shift @a;
+
+  return "Unknown argument $type, choose one of ". join(" ", sort keys %gets)
+    if(!defined($gets{$type}));
+
+  if($type eq "devList") {
+    return join("\n", FBAHA_getDevList($hash,0));
+  }
+
+  return undef;
+}
+
+sub
+FBAHA_getDevList($$)
+{
+  my ($hash, $onlyId) = @_;
+
+  FBAHA_Write($hash, "05", "00000000");  # CONFIG_REQ
+  my $data = "";
+  for(;;) {
+    my ($err, $buf) = FBAHA_ReadAnswer($hash, "CONFIG_RSP", "^06");
+    last if($err && $err =~ m/Timeout/);
+    return ($err) if($err);
+    $data .= substr($buf, 32);
+    last if($buf =~ m/^060[23]/);
+  }
+
+  return FBAHA_configInd($data, $onlyId);
+}
+
+sub
+FBAHA_configInd($$)
+{
+  my ($data, $onlyId) = @_;
+  #my $off = 288; #for old Client Id
+  my $off = 304;
+  my @answer;
+
+  while(length($data) >= $off) {
+    my $id  = hex(substr($data,  0, 4)); 
+    my $act = hex(substr($data,  4, 2));
+    my $typ = hex(substr($data,  8, 8));
+    my $lsn = hex(substr($data, 16, 8));
+    my $nam = pack("H*",substr($data,24,160)); $nam =~ s/\x0//g;
+
+    $act = ($act == 2 ? "active" : ($act == 1 ? "inactive" : "removed"));
+
+    my %tl = ( 2=>"AVM FRITZ!Dect Powerline 546E",
+               3=>"Comet DECT",
+               9=>"AVM FRITZ!Dect 200");
+    $typ = $tl{$typ} ? $tl{$typ} : "unknown($typ)";
+
+    my %ll = (7=>"powerMeter",9=>"switch");
+    $lsn = join ",", map { $ll{$_} if((1 << $_) & $lsn) } sort keys %ll;
+
+    my $dlen = hex(substr($data, $off-8, 8))*2; # DATA MSG
+
+    push @answer, "NAME:$nam, ID:$id, $act, TYPE:$typ PROP:$lsn"
+      if(!$onlyId || $onlyId == $id);
+
+    if($onlyId && $onlyId == $id) {
+      my $mnf = hex(substr($data,184, 8)); # empty/0
+      my $idf = substr($data,192,40); $idf =~ s/(00)*$//; $idf =pack("H*",$idf);
+      my $frm = substr($data,232,40); $frm =~ s/(00)*$//; $frm =pack("H*",$frm);
+      push @answer, "  MANUF:$mnf";
+      push @answer, "  UniqueID:$idf";
+      push @answer, "  Firmware:$frm";
+      push @answer, substr($data, $off, $dlen);
+      return @answer;
+    }
+    $data = substr($data, $off+$dlen); # rest
+  }
+  return @answer;
+}
+
+#####################################
+# Check all FBDECTs, reorg them if the id has changed and FBNAME is set.
+sub
+FBAHA_reassign($)
+{
+  my ($me) = @_;
+  my $myname = $me->{NAME};
+
+  my $devList = FBAHA_Get($me, ($myname, "devList"));
+  my %fbdata;
+  foreach my $l (split("\n", $devList)) {
+    next if($l !~ m/NAME:(.*), ID:(.*), (.*), TYPE:(.*) PROP:(.*)/);
+    if($fbdata{$1}) {
+      Log 1, "FBAHA: multiple devices are using the same name, wont reorder";
+      return;
+    }
+    $fbdata{$1} = $2;
+  }
+
+  foreach my $sdev (devspec2array("TYPE=FBDECT")) {
+    my $hash = $defs{$sdev};
+    my $name = $hash->{NAME};
+    my $fbname = ReadingsVal($name, "FBNAME", "");
+    my $fbid = $fbdata{$fbname};
+    my $oldid = $hash->{id};
+
+    next if(!$fbid || $oldid eq $fbid || $hash->{IODev}{NAME} ne $myname);
+    Log 2, "FBAHA: changing the id of $name/$fbname from $oldid to $fbid";
+
+    delete $modules{FBDECT}{defptr}{"$myname:$oldid"};
+    $modules{FBDECT}{defptr}{"$myname:$fbid"} = $hash;
+    $hash->{DEF} =~ s/^$myname:$oldid /$myname:$fbid /; # New syntax
+    $hash->{DEF} =~ s/^$oldid /$myname:$fbid /;         # Old Syntax
+    $hash->{id} = $fbid;
+  }
+
+  return;
+}
+
+#####################################
+sub
+FBAHA_DoInit($)
+{
+  my $hash = shift;
+  my $name = $hash->{NAME};
+  delete $hash->{HANDLE}; # else reregister fails / RELEASE is deadly
+  my $ret = FBAHA_Set($hash, ($name, "reregister"));
+  FBAHA_reassign($hash) if(!$ret && $init_done);
+  return $ret;
+}
+
+#####################################
+sub
+FBAHA_Undef($@)
+{
+  my ($hash, $arg) = @_;
+  FBAHA_Write($hash, "02", "");  # RELEASE
+  DevIo_CloseDev($hash);
+  return undef;
+}
+
+#####################################
+sub
+FBAHA_Write($$$)
+{
+  my ($hash,$fn,$msg) = @_;
+
+  $msg = sprintf("%s03%04x%s%s", $fn, length($msg)/2+8,
+           $hash->{HANDLE} ?  $hash->{HANDLE} : "00000000", $msg);
+  DevIo_SimpleWrite($hash, $msg, 1);
+}
+
+#####################################
+# called from the global loop, when the select for hash->{FD} reports data
+sub
+FBAHA_Read($@)
+{
+  my ($hash, $local, $regexp) = @_;
+
+  my $buf = ($local ? $local : DevIo_SimpleRead($hash));
+  return "" if(!defined($buf));
+
+  my $name = $hash->{NAME};
+
+  $buf = unpack('H*', $buf);
+  my $data = ($hash->{PARTIAL} ? $hash->{PARTIAL} : "");
+
+  # drop old data
+  if($data) {
+    $data = "" if(gettimeofday() - $hash->{READ_TS} > 5);
+    delete($hash->{READ_TS});
+  }
+
+  Log3 $name, 5, "FBAHA/RAW: $data/$buf";
+  $data .= $buf;
+
+  my $msg;
+  while(length($data) >= 16) {
+    my $len = hex(substr($data, 4,4))*2;
+    if($len < 16 || $len > 20480) { # Out of Sync
+      Log3 $name, 1, "FBAHA: resetting buffer as we are out of sync ($len)";
+      $hash->{PARTIAL} = "";
+      return "";
+    }
+    last if($len > length($data));
+    $msg = substr($data, 0, $len);
+    $data = substr($data, $len);
+    last if(defined($local) && (!defined($regexp) || ($msg =~ m/$regexp/)));
+
+    $hash->{"${name}_MSGCNT"}++;
+    $hash->{"${name}_TIME"} = TimeNow();
+    $hash->{RAWMSG} = $msg;
+    my %addvals = (RAWMSG => $msg);
+    Dispatch($hash, $msg, \%addvals) if($init_done);
+    $msg = undef;
+  }
+
+  $hash->{PARTIAL} = $data;
+  $hash->{READ_TS} = gettimeofday() if($data);
+  return $msg if(defined($local));
+  return undef;
+}
+
+#####################################
+# This is a direct read for commands like get
+sub
+FBAHA_ReadAnswer($$$)
+{
+  my ($hash, $arg, $regexp) = @_;
+  return ("No FD (dummy device?)", undef)
+        if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
+
+  for(;;) {
+    return ("Device lost when reading answer for get $arg", undef)
+      if(!$hash->{FD});
+    my $rin = '';
+    vec($rin, $hash->{FD}, 1) = 1;
+    my $nfound = select($rin, undef, undef, 3);
+    if($nfound <= 0) {
+      next if ($! == EAGAIN() || $! == EINTR());
+      my $err = ($! ? $! : "Timeout");
+      #$hash->{TIMEOUT} = 1;
+      #DevIo_Disconnected($hash);
+      return("FBAHA_ReadAnswer $arg: $err", undef);
+    }
+    my $buf = DevIo_SimpleRead($hash);
+    return ("No data", undef) if(!defined($buf));
+
+    my $ret = FBAHA_Read($hash, $buf, $regexp);
+    return (undef, $ret) if(defined($ret));
+  }
+}
+
+#####################################
+sub
+FBAHA_Ready($)
+{
+  my ($hash) = @_;
+
+  return DevIo_OpenDev($hash, 1, "FBAHA_DoInit")
+                if($hash->{STATE} eq "disconnected");
+  return 0;
+}
+
+1;
+
+=pod
+=item summary    (deprecated) connection to the Fritz!OS AHA Server
+=item summary_DE Anbindung des (veralteten) Fritz!OS AHA Servers
+=begin html
+
+<a name="FBAHA"></a>
+<h3>FBAHA</h3>
+<ul>
+  This module connects to the AHA server (AVM Home Automation) on a FRITZ!Box.
+  It serves as the "physical" counterpart to the <a href="#FBDECT">FBDECT</a>
+  devices. Note: you have to enable the access to this feature in the FRITZ!Box
+  frontend first.
+  <br><br>
+  <a name="FBAHAdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FBAHA &lt;device&gt;</code>
+  <br>
+  <br>
+  &lt;device&gt; is either a &lt;host&gt;:&lt;port&gt; combination, where
+  &lt;host&gt; is normally the address of the FRITZ!Box running the AHA server
+  (fritz.box or localhost), and &lt;port&gt; 2002, or
+  UNIX:SEQPACKET:/var/tmp/me_avm_home_external.ctl, the latter only works on
+  the fritz.box. With FRITZ!OS 5.50 the network port is available, on some
+  Labor variants only the UNIX socket is available.<br>
+
+  Example:
+  <ul>
+    <code>define fb1 FBAHA fritz.box:2002</code><br>
+    <code>define fb1 FBAHA UNIX:SEQPACKET:/var/tmp/me_avm_home_external.ctl</code><br>
+  </ul>
+  </ul>
+  <br>
+
+  <a name="FBAHAset"></a>
+  <b>Set</b>
+  <ul>
+  <li>createDevs<br>
+    create a FHEM device for each DECT device found on the AHA-Host, see also
+    get devList.
+    </li>
+  <li>reopen<br>
+    close and reopen the connection to the AHA server. Debugging only.
+    </li>
+  <li>reregister<br>
+    release existing registration handle, and get a new one. Debugging only.
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBAHAget"></a>
+  <b>Get</b>
+  <ul>
+  <li>devList<br>
+    return a list of devices with short info.
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBAHAattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#dummy">dummy</a></li>
+  </ul>
+  <br>
+
+  <a name="FBAHAevents"></a>
+  <b>Generated events:</b>
+  <ul>
+  <li>UNDEFINED FBDECT_$ahaName_${NR} FBDECT $id"
+    </li>
+  </ul>
+
+  <br>
+  As sometimes the FRITZ!Box reassigns the internal id's of the FBDECT devices,
+  the FBAHA module compares upon connect/reconnect the stored names (FBNAME)
+  with the current value. This feature will only work, if you assign each
+  FBDECT device a unique Name in the FRITZ!Box, and excecute the FHEM "get
+  FBDECTDEVICE devInfo" command, which saves the FBNAME reading.<br>
+
+</ul>
+
+
+=end html
+
+=begin html_DE
+
+<a name="FBAHA"></a>
+<h3>FBAHA</h3>
+<ul>
+  Dieses Modul verbindet sich mit dem AHA (AVM Home Automation) Server auf
+  einem FRITZ!Box. Es dient als "physikalisches" Gegenst&uuml;ck zum <a
+  href="#FBDECT">FBDECT</a> Modul. Achtung: als erstes muss der Zugang zu
+  diesen Daten in der FRITZ!Box Web-Oberfl&auml;che aktiviert werden.
+  <br><br>
+  <a name="FBAHAdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FBAHA &lt;device&gt;</code>
+  <br>
+  <br>
+  &lt;host&gt; ist normalerweise die Adresse der FRITZ!Box, wo das AHA Server
+  l&auml;uft (fritz.box oder localhost), &lt;port&gt; ist 2002.
+
+  &lt;device&gt; is entweder a eine Kombianation aus &lt;host&gt;:&lt;port&gt;,
+  wobei &lt;host&gt; die Adresse der FRITZ!Box ist (localhost AUF dem
+  FRITZ.BOX) und &lt;port&gt; 2002 ist, oder
+  UNIX:SEQPACKET:/var/tmp/me_avm_home_external.ctl, wobei das nur fuer
+  FHEM@FRITZ!BOX zur Verf&uuml;gung steht. Mit FRITZ!OS 5.50 steht auch der
+  Netzwerkport zur Verf&uuml;gung, auf manchen Laborvarianten nur das UNIX
+  socket.<br>
+  
+  Beispiel:
+  <ul>
+    <code>define fb1 FBAHA fritz.box:2002</code><br>
+    <code>define fb1 FBAHA UNIX:SEQPACKET:/var/tmp/me_avm_home_external.ctl</code><br>
+  </ul>
+  </ul>
+  <br>
+
+  <a name="FBAHAset"></a>
+  <b>Set</b>
+  <ul>
+  <li>createDevs<br>
+    legt FHEM Ger&auml;te an f&uuml;r jedes auf dem AHA-Server gefundenen DECT
+    Eintrag, siehe auch "get devList".
+    </li>
+  <li>reopen<br>
+    Schlie&szlig;t und &oulm;ffnet die Verbindung zum AHA Server. Nur f&uuml;r
+    debugging.
+    </li>
+  <li>reregister<br>
+    Gibt den AHA handle frei, und registriert sich erneut beim AHA Server. Nur
+    f&uuml;r debugging.
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBAHAget"></a>
+  <b>Get</b>
+  <ul>
+  <li>devList<br>
+    liefert die Liste aller DECT-Eintr&auml;ge der AHA Server zur&uuml;ck, mit
+    einem kurzen Info.
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBAHAattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#dummy">dummy</a></li>
+  </ul>
+  <br>
+
+  <a name="FBAHAevents"></a>
+  <b>Generierte Events:</b>
+  <ul>
+  <li>UNDEFINED FBDECT_$ahaName_${NR} FBDECT $id"
+    </li>
+  </ul>
+
+  <br>
+  Da manchmal die FRITZ!Box die interne Nummer der FBDECT Ger&auml;te
+  neu vergibt, werden beim Verbindungsaufbau zum AHA Server die gespeicherten
+  Namen (FBNAME) mit dem aktuellen Wert verglichen. Damit das funktioniert,
+  m&uuml;ssen alle FBDECT Ger&auml;te auf dem FRITZ!Box einen eindeutigen Namen
+  bekommen, und in FHEM muss f&uuml;r alle Ger&auml;te "get FBDECTDEVICE
+  devInfo" ausgef&uuml;hrt werden, um FBNAME als Reading zu speichern.<br>
+
+</ul>
+=end html_DE
+
+=cut

+ 347 - 0
fhem/core/FHEM/00_FBAHAHTTP.pm

@@ -0,0 +1,347 @@
+##############################################
+# $Id: 00_FBAHAHTTP.pm 12520 2016-11-07 09:41:47Z rudolfkoenig $
+package main;
+
+# Documentation: AHA-HTTP-Interface.pdf, AVM_Technical_Note_-_Session_ID.pdf
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+use FritzBoxUtils;
+
+sub
+FBAHAHTTP_Initialize($)
+{
+  my ($hash) = @_;
+  $hash->{WriteFn}  = "FBAHAHTTP_Write";
+  $hash->{DefFn}    = "FBAHAHTTP_Define";
+  $hash->{SetFn}    = "FBAHAHTTP_Set";
+  $hash->{AttrFn}   = "FBAHAHTTP_Attr";
+  $hash->{ReadyFn}  = "FBAHAHTTP_Ready";
+  $hash->{RenameFn} = "FBAHAHTTP_RenameFn";
+  $hash->{AttrList} = "dummy:1,0 fritzbox-user polltime async_delay ".
+                      "disable:0,1 disabledForIntervals";
+}
+
+
+#####################################
+sub
+FBAHAHTTP_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> FBAHAHTTP hostname"
+    if(@a != 3);
+
+  $hash->{Clients} = ":FBDECT:";
+  my %matchList = ( "1:FBDECT" => ".*" );
+  $hash->{MatchList} = \%matchList;
+
+  for my $d (devspec2array("TYPE=FBDECT")) {
+    if($defs{$d}{IODev} && $defs{$d}{IODev}{TYPE} eq "FBAHA") {
+      my $n = $defs{$d}{IODev}{NAME};
+      CommandAttr(undef, "$d IODev $hash->{NAME}");
+      CommandDelete(undef, $n) if($defs{$n});
+    }
+    $defs{$d}{IODev} = $hash
+  }
+  $hash->{CmdStack} = ();
+
+  return undef if($hash->{DEF} eq "none"); # DEBUGGING
+  InternalTimer(1, "FBAHAHTTP_Poll", $hash);
+  $hash->{STATE} = "defined";
+  return undef;
+}
+
+sub
+FBAHAHTTP_connect($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+  my $dev = $hash->{DEF};
+
+  my $dr = sub {
+    $hash->{STATE} = $_[0];
+    Log 2, $hash->{STATE};
+    return $hash->{STATE};
+  };
+
+  my $fb_user = AttrVal($name, "fritzbox-user", '');
+  return $dr->("MISSING: attr $name fritzbox-user") if(!$fb_user);
+
+  my ($err, $fb_pw) = getKeyValue("FBAHAHTTP_PASSWORD_$name");
+  return $dr->("ERROR: $err") if($err);
+  return $dr->("MISSING: set $name password") if(!$fb_pw);
+
+  my $sid = FB_doCheckPW($hash->{DEF}, $fb_user, $fb_pw);
+  if(!$sid) {
+    $hash->{NEXT_OPEN} = time()+60;
+    $readyfnlist{"$name.$dev"} = $hash;
+    return $dr->("$name error: cannot get SID, ".
+                      "check connection/hostname/fritzbox-user/password")
+  }
+
+  delete($hash->{RetriedCmd});
+  delete($readyfnlist{"$name.$dev"});
+  $hash->{".SID"} = $sid;
+  $hash->{STATE} = "connected";
+  Log3 $name, 4, "FBAHAHTTP_connect $name: got SID $sid";
+  return undef;
+}
+
+sub
+FBAHAHTTP_RenameFn($$)
+{
+  my ($new, $old) = @_;
+  for my $d (devspec2array("TYPE=FBDECT")) {
+    my $hash = $defs{$d};
+    next if(!$hash);
+    $hash->{DEF} =~ s/^$old:/$new:/;
+    $attr{$d}{IODev} = $new if(AttrVal($d,"IODev","") eq $old);
+  }
+  FBDECT_renameIoDev($new, $old);
+}
+
+#####################################
+sub
+FBAHAHTTP_Poll($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  return if(IsDisabled($name));
+
+  if(!$hash->{".SID"}) {
+    my $ret = FBAHAHTTP_connect($hash);
+    return $ret if($ret);
+  }
+  my $sid = $hash->{".SID"};
+
+  HttpUtils_NonblockingGet({
+    url=>"http://$hash->{DEF}/webservices/homeautoswitch.lua?sid=$sid".
+         "&switchcmd=getdevicelistinfos",
+    loglevel => AttrVal($name, "verbose", 4),
+    callback => sub {
+      if($_[1]) {
+        Log3 $name, 3, "$name: $_[1]";
+        delete $hash->{".SID"};
+        return;
+      }
+
+      Log 5, $_[2] if(AttrVal($name, "verbose", 1) >= 5);
+      if($_[2] !~ m,^<devicelist.*</devicelist>$,s) {
+        Log3 $name, 3, "$name: unexpected reply from device: $_[2]";
+        delete $hash->{".SID"};
+        return;
+      }
+
+      $_[2] =~ s+<(device|group) (.*?)</\g1>+
+                Dispatch($hash, "<$1 $2</$1>", undef);""+gse;      # Quick&Hack
+    }
+  });
+
+  my $polltime = AttrVal($name, "polltime", 300);
+  RemoveInternalTimer($hash);
+  InternalTimer(gettimeofday()+$polltime, "FBAHAHTTP_Poll", $hash);
+  return;
+}
+
+#####################################
+sub
+FBAHAHTTP_Ready($)
+{
+  my ($hash) = @_;
+
+  return if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN});
+  FBAHAHTTP_Poll($hash);
+}
+
+#####################################
+sub
+FBAHAHTTP_Attr($@)
+{
+  my ($type, $devName, $attrName, @param) = @_;
+  my $hash = $defs{$devName};
+
+  if($attrName eq "fritzbox-user") {
+    return "Cannot delete fritzbox-user" if($type eq "del");
+    if($init_done) {
+      delete($hash->{".SID"});
+      FBAHAHTTP_Poll($hash);
+    }
+  }
+  return undef;
+}
+
+#####################################
+sub
+FBAHAHTTP_Set($@)
+{
+  my ($hash, @a) = @_;
+  my $name = shift @a;
+  my %sets = (password=>2, refreshstate=>1);
+
+  return "set $name needs at least one parameter" if(@a < 1);
+  my $type = shift @a;
+
+  return "Unknown argument $type, choose one of refreshstate:noArg password"
+    if(!defined($sets{$type}));
+  return "Missing argument for $type" if(int(@a) < $sets{$type}-1);
+
+  if($type eq "password") {
+    setKeyValue("FBAHAHTTP_PASSWORD_$name", $a[0]);
+    delete($hash->{".SID"});
+    FBAHAHTTP_Poll($hash);
+    return;
+  }
+  if($type eq "refreshstate") {
+    FBAHAHTTP_Poll($hash);
+    return;
+  }
+
+  return undef;
+}
+
+sub
+FBAHAHTTP_ProcessStack($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+  my $msg = $hash->{CmdStack}->[0];
+  HttpUtils_NonblockingGet({
+    url=>"http://$hash->{DEF}/webservices/homeautoswitch.lua?$msg",
+    loglevel => AttrVal($name, "verbose", 4),
+    callback => sub {
+      if($_[1]) {
+        Log3 $name, 3, "$name: $_[1]";
+        delete $hash->{".SID"};
+        $hash->{CmdStack} = ();
+        return;
+      }
+      
+      Log3 $name, 5, "FBAHAHTTP_Write reply for $name: $_[2]";
+      if(!defined($_[2]) || $_[2] eq "") {
+        if($hash->{RetriedCmd}) {
+          Log3 $name, 1, "No sensible respone after reconnect, giving up";
+          return;
+        }
+        return if(FBAHAHTTP_connect($hash));
+        $hash->{RetriedCmd} = $msg;
+        FBAHAHTTP_ProcessStack($hash);
+        return;
+      }
+
+      delete($hash->{RetriedCmd});
+      shift @{$hash->{CmdStack}};
+      if(@{$hash->{CmdStack}} > 0) {
+        my $ad = AttrVal($name, "async_delay", 0);
+        InternalTimer(gettimeofday()+$ad, sub(){
+          FBAHAHTTP_ProcessStack($hash);
+        }, $hash);
+      }
+    }
+  });
+}
+
+#####################################
+sub
+FBAHAHTTP_Write($$$)
+{
+  my ($hash,$fn,$msg) = @_;
+  my $name = $hash->{NAME};
+
+  return if(IsDisabled($name));
+
+  my $sid = $hash->{".SID"};
+  if(!$sid) {
+    my $ret = FBAHAHTTP_connect($hash);      # try to reconnect
+    return $ret if($ret);
+    $sid = $hash->{".SID"};
+  }
+  push(@{$hash->{CmdStack}}, "sid=$sid&ain=$fn&switchcmd=$msg");
+  FBAHAHTTP_ProcessStack($hash) if(@{$hash->{CmdStack}} == 1);
+}
+
+
+1;
+
+=pod
+=item summary    connection to the Fritz!OS AHA Server via HTTP
+=item summary_DE Anbindung des Fritz!OS AHA Servers &uuml;ber HTTP
+=begin html
+
+<a name="FBAHAHTTP"></a>
+<h3>FBAHAHTTP</h3>
+<ul>
+  This module connects to the AHA server (AVM Home Automation) on a FRITZ!Box
+  via HTTP, it is a successor/drop-in replacement for the FBAHA module.  It is
+  necessary, as the FBAHA interface is deprecated by AVM. Since the AHA HTTP
+  interface do not offer any notification mechanism, the module is regularly
+  polling the FRITZ!Box.<br>
+  Important: For an existing installation with an FBAHA device, defining a
+  new FBAHAHTTP device will change the IODev of all FBDECT devices from the
+  old FBAHA to this FBAHAHTTP device, and it will delete the FBAHA device.<br>
+
+  This module serves as the "physical" counterpart to the <a
+  href="#FBDECT">FBDECT</a> devices. Note: you have to enable the access to
+  Smart Home in the FRITZ!Box frontend for the fritzbox-user, and take care
+  to configure the login in the home network with username AND password.
+  <br><br>
+  <a name="FBAHAHTTPdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FBAHAHTTP &lt;hostname&gt;</code><br>
+    <br>
+    &lt;hostnamedevice&gt; is most probably fritz.box.
+    Example:
+    <ul>
+      <code>define fb1 FBAHAHTTP fritz.box</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="FBAHAHTTPset"></a>
+  <b>Set</b>
+  <ul>
+  <li>password &lt;password&gt;<br>
+    This is the only way to set the password
+    </li>
+  <li>refreshstate<br>
+    The state of all devices is polled every &lt;polltime&gt; seconds (default
+    is 300). This command forces a state-refresh.
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBAHAHTTPget"></a>
+  <b>Get</b>
+  <ul>N/A</ul>
+  <br>
+
+  <a name="FBAHAHTTPattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#disable">disable</a></li>
+    <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
+    <li><a href="#dummy">dummy</a></li>
+    <li><a href="#fritzbox-user">fritzbox-user</a></li>
+    <li><a name="polltime">polltime</a><br>
+      measured in seconds, default is 300 i.e. 5 minutes
+      </li>
+
+    <li><a href="#async_delay">async_delay</a><br>
+      additional delay inserted, when switching more than one device, default
+      is 0.2 seconds. Note: even with async_delay 0 there will be a delay, as
+      FHEM avoids sending commands in parallel, to avoid malfunctioning of the
+      Fritz!BOX AHA server).
+      </li>
+
+  </ul>
+  <br>
+</ul>
+
+
+=end html
+
+=cut

ファイルの差分が大きいため隠しています
+ 1010 - 0
fhem/core/FHEM/00_FHZ.pm


ファイルの差分が大きいため隠しています
+ 1455 - 0
fhem/core/FHEM/00_HMLAN.pm


ファイルの差分が大きいため隠しています
+ 2645 - 0
fhem/core/FHEM/00_HMUARTLGW.pm


+ 150 - 0
fhem/core/FHEM/00_HXB.pm

@@ -0,0 +1,150 @@
+# $Id: 00_HXB.pm 7911 2015-02-07 21:11:31Z borisneubert $
+################################################################
+#
+#  Copyright notice
+#
+#  (c) 2014 Copyright: Dr. Boris Neubert
+#  e-mail: omega at online dot de
+#
+#  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/>.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+
+use IO::Socket::Multicast6;
+
+#####################################
+sub
+HXB_Initialize($)
+{
+  my ($hash) = @_;
+  
+  my %matchlist= (
+    "1:HXBDevice" => "^HX0C.+",
+  );
+
+# Provider
+  #$hash->{WriteFn} = "HXB_Write";
+  $hash->{ReadFn}  = "HXB_Read";
+  $hash->{Clients} = ":HXBDevice:";
+  $hash->{MatchList} = \%matchlist;
+  #$hash->{ReadyFn} = "HXB_Ready";
+
+# Consumer
+  $hash->{DefFn}   = "HXB_Define";
+  $hash->{UndefFn} = "HXB_Undef";
+  #$hash->{ReadyFn} = "HXB_Ready";
+  #$hash->{GetFn}   = "HXB_Get";
+  #$hash->{SetFn}   = "HXB_Set";
+  #$hash->{AttrFn}  = "HXB_Attr";
+  #$hash->{AttrList}= "";
+}
+
+#####################################
+sub
+HXB_Define($$)
+{
+  my ($hash, $def) = @_;
+  my $name= $hash->{NAME};
+
+  Log3 $hash, 3, "$name: Opening multicast socket...";
+  my $socket = IO::Socket::Multicast6->new(
+    Domain    => AF_INET6,
+    Proto     => 'udp',
+    LocalPort => '61616',
+  );
+  $socket->mcast_add('FF05::205');
+  
+  $hash->{TCPDev}= $socket;
+  $hash->{FD} = $socket->fileno();
+  delete($readyfnlist{"$name"});
+  $selectlist{"$name"} = $hash;
+
+  return undef;
+}
+
+
+#####################################
+sub
+HXB_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  
+  my $socket= $hash->{TCPDev};
+  $socket->mcast_drop('FF05::205');
+  $socket->close;
+
+  return undef;
+}
+
+#####################################
+sub
+HXB_DoInit($)
+{
+  my $hash = shift;
+ 
+  $hash->{STATE} = "Initialized" if(!$hash->{STATE});
+
+  return undef;
+}
+
+#####################################
+# called from the global loop, when the select for hash->{FD} reports data
+sub HXB_Read($) 
+{
+  my ($hash) = @_;
+  my $name= $hash->{NAME};
+  my $socket= $hash->{TCPDev};
+  my $data;
+  return unless $socket->recv($data, 128);
+  
+  Log3 $hash, 5, "$name: Received " . length($data) . " bytes.";
+  Dispatch($hash, $data, undef);  # dispatch result to HXBDevices
+}
+
+
+#############################
+1;
+#############################
+
+
+=pod
+=begin html
+
+<a name="HXB"></a>
+<h3>HXB</h3>
+<ul>
+  <br>
+
+  <a name="HXB"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; HXB</code><br>
+    <br>
+    Defines a Hexabus. You need one Hexabus to receive multicast messages from <a href="#HXBDevice">Hexabus devices</a>.
+    Have a look at the <a href="https://github.com/mysmartgrid/hexabus/wiki">Hexabus wiki</a> for more information on Hexabus.
+    <br><br>
+    You need the perl modules IO::Socket::Multicast6 and Digest::CRC. Under Debian and its derivatives they are installed with <code>apt-get install libio-socket-multicast6-perl libdigest-crc-perl</code>.
+  </ul>  
+
+</ul>
+
+
+=end html

ファイルの差分が大きいため隠しています
+ 1319 - 0
fhem/core/FHEM/00_KM271.pm


+ 168 - 0
fhem/core/FHEM/00_LIRC.pm

@@ -0,0 +1,168 @@
+##############################################
+# $Id: 00_LIRC.pm 11984 2016-08-19 12:47:50Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+use Lirc::Client;
+use IO::Select;
+
+#####################################
+# Note: we are a data provider _and_ a consumer at the same time
+sub
+LIRC_Initialize($)
+{
+  my ($hash) = @_;
+
+# Provider
+  $hash->{ReadFn}  = "LIRC_Read";
+  $hash->{ReadyFn} = "LIRC_Ready";
+  $hash->{Clients} = ":LIRC:";
+
+# Consumer
+  $hash->{DefFn}   = "LIRC_Define";
+  $hash->{UndefFn} = "LIRC_Undef";
+  $hash->{AttrList}= "";
+}
+
+#####################################
+sub
+LIRC_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  $hash->{STATE} = "Initialized";
+
+  $hash->{LircObj}->clean_up() if($hash->{LircObj});
+  delete $hash->{LircObj};
+  delete $hash->{FD};
+
+  my $name = $a[0];
+  my $config = $a[2];
+
+  Log3 $name, 3, "LIRC opening $name device $config";
+  my $lirc = Lirc::Client->new({
+        prog    => 'fhem',
+        rcfile  => "$config", 
+        debug   => 0,
+        fake    => 0,
+    });
+  return "Can't open $config: $!\n" if(!$lirc);
+  Log3 $name, 3, "LIRC opened $name device $config";
+
+  my $select = IO::Select->new();
+  $select->add( $lirc->sock );
+
+  $hash->{LircObj} = $lirc;
+  
+  $hash->{FD} = $lirc->{sock}->fileno; # is not working and sets timeout to undefined 
+  $selectlist{"$name"} = $hash;      # 
+  #$readyfnlist{"$name"} = $hash;     # thats why we start polling
+  $hash->{SelectObj} = $select;      
+  $hash->{DeviceName} = $name;    
+  $hash->{STATE} = "Opened";
+
+  return undef;
+}
+
+#####################################
+sub
+LIRC_Undef($$)
+{
+  my ($hash, $arg) = @_;
+
+  $hash->{LircObj}->clean_up() if($hash->{LircObj});
+  delete $hash->{LircObj};
+  delete $hash->{FD};
+
+  return undef;
+}
+
+#####################################
+sub
+LIRC_Read($)
+{
+  my ($hash) = @_;
+
+  my $lirc= $hash->{LircObj};
+  my $select= $hash->{SelectObj};
+
+  if( my @ready = $select->can_read(0) ){ 
+    # an ir event has been received (if you are tracking other filehandles, you need to make sure it is lirc)
+    my @codes = $lirc->next_codes;    # should not block
+    my $name = $hash->{NAME};
+    for my $code (@codes){
+      Log3 $name, 3, "LIRC $name $code";
+      DoTrigger($name, $code);
+    }
+  }
+
+}
+
+#####################################
+sub
+LIRC_Ready($)
+{
+  my ($hash) = @_;
+
+  my $select= $hash->{SelectObj};
+
+  return $select->can_read(0);
+}
+
+1;
+
+=pod
+=item summary    connection to the Linux Infrared Server (lirc)
+=item summary_DE Anbindung der Linux Infrared (lirc) Servers
+=begin html
+
+<a name="LIRC"></a>
+<h3>LIRC</h3>
+<ul>
+  Generate FHEM-events when an LIRC device receives infrared signals.
+  <br><br>
+  Note: this module needs the Lirc::Client perl module.
+  <br><br>
+
+  <a name="LIRCdefine"></a>
+  <b>Define</b>
+  <ul>
+    define &lt;name&gt; LIRC &lt;lircrc_file&gt;<br>
+    Example:<br>
+    <ul>
+     define Lirc LIRC /etc/lirc/lircrc
+    </ul>
+    Note: In the lirc configuration file you have to define each possible event.
+    If you have this configuration
+    <pre>
+    begin
+      prog = fhem
+      button = pwr
+      config = IrPower
+    end</pre>
+    and you press the pwr button the IrPower toggle event occures at fhem.
+    <pre>
+    define IrPower01 notify Lirc:IrPower set lamp toggle</pre>
+    turns the lamp on and off.
+    If you want a faster reaction to keypresses you have to change the
+    defaultvalue of readytimeout from 5 seconds to e.g. 1 second in fhem.pl
+  </ul>
+  <br>
+
+  <a name="LIRCset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="LIRCget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="LIRCattr"></a>
+  <b>Attributes</b>
+  <ul>
+  </ul><br>
+</ul>
+
+=end html
+=cut

+ 947 - 0
fhem/core/FHEM/00_MAXLAN.pm

@@ -0,0 +1,947 @@
+##############################################
+# $Id: 00_MAXLAN.pm 11307 2016-04-25 08:02:06Z rudolfkoenig $
+# Written by Matthias Gehre, M.Gehre@gmx.de, 2012-2013
+package main;
+
+use strict;
+use warnings;
+use MIME::Base64;
+use POSIX;
+use MaxCommon;
+
+sub MAXLAN_Parse($$);
+sub MAXLAN_Read($);
+sub MAXLAN_Write(@);
+sub MAXLAN_ReadSingleResponse($$);
+sub MAXLAN_SimpleWrite(@);
+sub MAXLAN_Poll($);
+sub MAXLAN_Send(@);
+sub MAXLAN_RequestConfiguration($$);
+sub MAXLAN_RemoveDevice($$);
+
+my $reconnect_interval = 60; #seconds
+
+#the time it takes after sending one command till we see its effect in the L: response
+my $roundtriptime = 3; #seconds
+
+my $read_timeout = 3; #seconds. How long to wait for an answer from the Cube over TCP/IP
+
+my $metadata_magic = 0x56;
+my $metadata_version = 2;
+
+my $defaultPollInterval = 60;
+
+sub
+MAXLAN_Initialize($)
+{
+  my ($hash) = @_;
+
+  require "$attr{global}{modpath}/FHEM/DevIo.pm";
+
+# Provider
+  $hash->{ReadFn}  = "MAXLAN_Read";
+  $hash->{SetFn}   = "MAXLAN_Set";
+  $hash->{Clients} = ":MAX:";
+  my %mc = (
+       "1:MAX" => "^MAX",
+  );
+  $hash->{MatchList} = \%mc;
+
+# Normal devices
+  $hash->{DefFn}   = "MAXLAN_Define";
+  $hash->{UndefFn} = "MAXLAN_Undef";
+  $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 set-clock-on-init:1,0 " .
+                     "loglevel:0,1,2,3,4,5,6 addvaltrigger " .
+                     "timezone:CET-CEST,GMT-BST,EET-EEST,FET-FEST,MSK-MSD,GMT,CET,EET " .
+                     $readingFnAttributes;
+}
+
+#####################################
+sub
+MAXLAN_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  if(@a < 3) {
+    my $msg = "wrong syntax: define <name> MAXLAN ip[:port] [pollintervall [ondemand]]";
+    Log3 $hash, 2, $msg;
+    return $msg;
+  }
+
+  my $name = shift @a;
+  shift @a;
+  my $dev = shift @a;
+  $dev .= ":62910" if($dev !~ m/:/ && $dev ne "none" && $dev !~ m/\@/);
+
+  if($dev eq "none") {
+    Log3 $hash, 1, "$name device is none, commands will be echoed only";
+    $attr{$name}{dummy} = 1;
+    return undef;
+  }
+  $hash->{INTERVAL} = $defaultPollInterval;
+  $hash->{persistent} = 1;
+  if(@a) {
+    $hash->{INTERVAL} = shift @a;
+    while(@a) {
+      my $arg = shift @a;
+      if($arg eq "ondemand") {
+        $hash->{persistent} = 0;
+      } else {
+        my $msg = "unknown argument $arg";
+        Log3 $hash, 1, $msg;
+        return $msg;
+      }
+    }
+  }
+
+  $hash->{cubeTimeDifference} = 99999;
+  $hash->{pairmode} = 0;
+  $hash->{PARTIAL} = "";
+  $hash->{DeviceName} = $dev;
+  #This interface is shared with 14_CUL_MAX.pm
+  $hash->{Send} = \&MAXLAN_Send;
+  $hash->{RemoveDevice} = \&MAXLAN_RemoveDevice;
+
+  #Wait until all device definitions have been loaded
+  InternalTimer(gettimeofday()+1, "MAXLAN_Poll", $hash, 0);
+  return undef;
+}
+
+sub
+MAXLAN_IsConnected($)
+{
+  return 0 if(!exists($_[0]->{FD}));
+  if(!defined($_[0]->{TCPDev})) {
+    MAXLAN_Disconnect($_[0]);
+    return 0;
+  }
+  return 1;
+}
+
+
+#Disconnects from the Cube. It is safe to call this when already disconnected.
+sub
+MAXLAN_Disconnect($)
+{
+  my $hash = shift;
+  Log3 $hash, 5, "MAXLAN_Disconnect";
+  #All operations here are no-op if already disconnected
+  DevIo_CloseDev($hash);
+  RemoveInternalTimer($hash);
+}
+
+#Connects to the Cube. If already connected, disconnects first.
+#Returns undef of success, otherwise an error message
+sub
+MAXLAN_Connect($)
+{
+  my $hash = shift;
+
+  return undef if(MAXLAN_IsConnected($hash));
+
+  delete($hash->{NEXT_OPEN}); #work around the connection rate limiter in DevIo
+  DevIo_OpenDev($hash, 0, "");
+  if(!MAXLAN_IsConnected($hash)) {
+    my $msg = "MAXLAN_Connect: Could not connect";
+    Log3 $hash, 2, $msg;
+    return $msg;
+  }
+
+  my $ret;
+  #Read initial configuration data
+  $ret = MAXLAN_ExpectAnswer($hash,"H:");
+  return "MAXLAN_Connect: $ret" if($ret);
+  $ret = MAXLAN_ExpectAnswer($hash,"M:");
+  return "MAXLAN_Connect: $ret" if($ret);
+
+  #We first reset the IODev for all MAX devices using this MAXLAN as a backend.
+  #Parsing the "C:" responses later on will set IODev correctly again.
+  #This effectively removes IODev from all devices that are not longer paired to our Cube.
+  foreach (%{$modules{MAX}{defptr}}) {
+    $modules{MAX}{defptr}{$_}{IODev} = undef if(defined($modules{MAX}{defptr}{$_}{IODev}) and $modules{MAX}{defptr}{$_}{IODev} == $hash);
+  }
+
+  my $rmsg;
+  do
+  {
+    #Receive one "C:" per device
+    $rmsg = MAXLAN_ReadSingleResponse($hash, 1);
+    return "MAXLAN_Connect: Error in ReadSingleResponse while waiting for C:" if(!defined($rmsg));
+    MAXLAN_Parse($hash, $rmsg);
+  } until($rmsg =~ m/^L:/);
+  #At the end, the cube sends a "L:"
+  
+  #Handle deferred setting of time
+  if(AttrVal($hash->{NAME},"set-clock-on-init","1") && ($hash->{cubeTimeDifference} > 1 || !$hash->{clockset})) {
+    MAXLAN_Set($hash,$hash->{NAME},"clock");
+  }
+
+  return undef; 
+}
+
+
+#####################################
+sub
+MAXLAN_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  #MAXLAN_Write($hash,"q:"); #unnecessary
+  MAXLAN_Disconnect($hash);
+  return undef;
+}
+
+#####################################
+sub
+MAXLAN_Set($@)
+{
+  my ($hash, $device, @a) = @_;
+  return "\"set MAXLAN\" needs at least one parameter" if(@a < 1);
+  my ($setting, @args) = @a;
+
+  if($setting eq "pairmode"){
+    if(@args > 0 and $args[0] eq "cancel") {
+      MAXLAN_Write($hash,"x:", "N:");
+    } else {
+      my $duration = 60;
+      $duration = $args[0] if(@args > 0);
+      $hash->{pairmode} = 1;
+      MAXLAN_Write($hash,"n:".sprintf("%04x",$duration));
+      $hash->{STATE} = "pairing";
+    }
+
+  }elsif($setting eq "raw"){
+    MAXLAN_Write($hash,$args[0]);
+
+  }elsif($setting eq "clock") {
+    #Set timezone from attribute
+    #All strings are taken from MAX! software network analysis
+    #Base64 hex decode of the CET strings gives eg.
+    #CET[00][00][0a][00][03][00][00][0e][10]CEST[00][03][00][02][00][00][1c][20] for DST
+    #CET[00][00][0a][00][03][00][00][0e][10]CEST[00][03][00][02][00][00][0e][10] for no DST
+    #bytes 10-11 and 22-23 of each string appear to represent time offset from UTC in seconds
+    #a guess is that bytes 5 & 17 represent month no.
+    #All strings below appear to follow the same pattern & identical except for name & offset.
+    #The currently set string appears at the end of the decoded C: device message for the Cube
+    my $timezoneAttr = AttrVal($hash->{NAME},"timezone","CET-CEST");
+    my %tz_list = (     #timezone & strings
+      "GMT-BST"    =>   "R01UAAAKAAMAAAAAQlNUAAADAAIAAA4Q", #DST strings
+      "CET-CEST"   =>   "Q0VUAAAKAAMAAA4QQ0VTVAADAAIAABwg",
+      "EET-EEST"   =>   "RUVUAAAKAAMAABwgRUVTVAADAAIAACow",
+      "FET-FEST"   =>   "RkVUAAAKAAMAACowRkVTVAADAAIAACow", #No DST for this region or next
+      "MSK-MSD"    =>   "TVNLAAAKAAMAADhATVNEAAADAAIAADhA",
+      "GMT"        =>   "R01UAAAKAAMAAAAAQlNUAAADAAIAAAAA", #No DST strings
+      "CET"        =>   "Q0VUAAAKAAMAAA4QQ0VTVAADAAIAAA4Q",
+      "EET"        =>   "RUVUAAAKAAMAABwgRUVTVAADAAIAABwg"
+    );
+
+    my $timezones;
+    if(exists($tz_list{$timezoneAttr})) {
+      $timezones = $tz_list{$timezoneAttr};
+      Log3 $hash, 3, "MAX Cube is set to timezone $timezoneAttr";
+    } else {
+      Log3 $hash, 2, "ERROR: Timezone $timezoneAttr of MAX Cube is invalid. Using CET-CEST";
+      $timezones = $tz_list{"CET-CEST"};
+    }
+
+    #From various sources Cube base time is year 2000, offset should perhaps be number
+    #of secs diff between 1/Jan/1970 and 1/Jan/2000 ie. 946684800, ie. 26 secs diff
+    #Occasional 1 min diffs seen in logs when close to minute rollover
+    my $time = time()-946684800;
+    my $rmsg = "v:".$timezones.",".sprintf("%08x",$time);
+    my $ret = MAXLAN_Write($hash,$rmsg, "A:");
+    $hash->{clockset} = 1;
+    return $ret;
+
+  }elsif($setting eq "factoryReset") {
+    MAXLAN_RequestReset($hash);
+
+  }elsif($setting eq "reconnect") {
+    MAXLAN_Disconnect($hash);
+    MAXLAN_Connect($hash) if($hash->{persistent});
+
+  }elsif($setting eq "inject") {
+    MAXLAN_Parse($hash,$args[0]);
+
+  }else{
+    return "Unknown argument $setting, choose one of pairmode raw clock factoryReset reconnect";
+  }
+  return undef;
+}
+
+#Returns error string if failed, undef on success
+sub
+MAXLAN_ExpectAnswer($$)
+{
+  my ($hash,$expectedanswer) = @_;
+  my $rmsg = MAXLAN_ReadSingleResponse($hash, 1);
+
+  if(!defined($rmsg)) {
+    my $msg = "MAXLAN_ExpectAnswer: Error while waiting for answer $expectedanswer";
+    Log3 $hash, 1, $msg;
+    return $msg;
+  }
+
+  my $ret = undef;
+  if($rmsg !~ m/^$expectedanswer/) {
+    Log3 $hash, 2, "MAXLAN_ExpectAnswer: Got unexpected response, expected $expectedanswer";
+    MAXLAN_Parse($hash,$rmsg);
+    return "Got unexpected response, expected $expectedanswer";
+  }
+  MAXLAN_Parse($hash,$rmsg);
+  return undef;
+}
+
+
+#Reads single line from the Cube
+#blocks if waitForResponse is true
+#
+#returns undef, if an error occured,
+#otherwise the line
+sub
+MAXLAN_ReadSingleResponse($$)
+{
+  my ($hash,$waitForResponse) = @_;
+
+  return undef if(!MAXLAN_IsConnected($hash));
+
+  my ($rin, $win, $ein, $rout, $wout, $eout);
+  $rin = $win = $ein = '';
+  vec($rin,fileno($hash->{TCPDev}),1) = 1;
+  $ein = $rin;
+
+  my $maxTime = gettimeofday()+$read_timeout;
+
+  #Read until we have a complete line
+  until($hash->{PARTIAL} =~ m/\n/) {
+
+    #Check timeout
+    if(gettimeofday() > $maxTime) {
+      if($waitForResponse) {
+        Log3 $hash, 1, "MAXLAN_ReadSingleResponse: timeout while reading from socket, disconnecting";
+        MAXLAN_Disconnect($hash);
+      }
+      return undef;;
+    }
+
+    #Wait for data
+    my $nfound = select($rout=$rin, $wout=$win, $eout=$ein, $read_timeout);
+    if($nfound == -1) {
+      Log3 $hash, 1, "MAXLAN_ReadSingleResponse: error during select, ret = $nfound";
+      return undef;
+    }
+    last if($nfound == 0 and !$waitForResponse);
+    next if($nfound == 0); #Sometimes select() returns early, just try again
+
+    #Blocking read
+    my $buf;
+    my $res = sysread($hash->{TCPDev}, $buf, 256);
+    if(!defined($res)){
+      Log3 $hash, 1, "MAXLAN_ReadSingleResponse: error during read";
+      return undef; #error occured
+    }
+
+    #Append data to partial data we got before
+    $hash->{PARTIAL} .= $buf;
+  }
+
+  my $rmsg;
+  ($rmsg,$hash->{PARTIAL}) = split("\n", $hash->{PARTIAL}, 2);
+  $rmsg =~ s/\r//; #remove \r
+  return $rmsg;
+}
+
+my %lhash;
+
+#####################################
+#Sends given msg and checks for/parses the answer
+#returns undef on success
+sub
+MAXLAN_Write(@)
+{
+  my ($hash,$msg,$expectedAnswer) = @_;
+  my $ret = undef;
+
+  $ret = MAXLAN_Connect($hash); #It's a no-op if already connected
+  return "MAXLAN_Write: $ret" if($ret);
+  $ret = MAXLAN_SimpleWrite($hash, $msg);
+  return "MAXLAN_Write: $ret" if($ret);
+  if($expectedAnswer) {
+    $ret = MAXLAN_ExpectAnswer($hash, $expectedAnswer);
+    return "MAXLAN_Write: $ret" if($ret);
+  }
+  MAXLAN_Disconnect($hash) if(!$hash->{persistent} && !$hash->{pairmode});
+  return undef;
+}
+
+#####################################
+# called from the global loop, when the select for hash->{FD} reports data
+sub
+MAXLAN_Read($)
+{
+  my ($hash) = @_;
+
+  while(1) {
+    my $rmsg = MAXLAN_ReadSingleResponse($hash, 0);
+    last if(!$rmsg);
+    # The Msg N: .... is the only one that may come spontanously from
+    # the cube while we are in pairmode
+    Log3 $hash, 2, "Unsolicated response from Cube: $rmsg" unless($hash->{pairmode} and substr($rmsg,0,2) eq "N:");
+    MAXLAN_Parse($hash, $rmsg);
+  }
+}
+
+sub
+MAXLAN_SendMetadata($)
+{
+  my $hash = shift;
+
+  if(defined($hash->{metadataVersionMismatch})){
+    Log3 $hash, 3,"MAXLAN_SendMetadata: current version of metadata unexpected, not overwriting!";
+    return;
+  }
+
+  my $maxNameLength = 32;
+  my $maxGroupCount = 20;
+  my $maxDeviceCount = 140;
+
+  my @groups = @{$hash->{groups}};
+  my @devices = @{$hash->{devices}};
+
+  if(@groups > $maxGroupCount || @devices > $maxDeviceCount) {
+    Log3 $hash, 1, "MAXLAN_SendMetadata: you got more than $maxGroupCount groups or $maxDeviceCount devices";
+    return;
+  }
+
+  my $metadata = pack("CC",$metadata_magic,$metadata_version);
+
+  $metadata .= pack("C",scalar(@groups));
+  foreach(@groups){
+    if(length($_->{name}) > $maxNameLength) {
+      Log3 $hash, 1, "Group name $_->{name} is too long, maximum of $maxNameLength characters allowed";
+      return;
+    }
+    $metadata .= pack("CC/aH6",$_->{id}, $_->{name}, $_->{masterAddr});
+  }
+  $metadata .= pack("C",scalar(@devices));
+  foreach(@devices){
+    if(length($_->{name}) > $maxNameLength) {
+      Log3 $hash, 1, "Device name $_->{name} is too long, maximum of $maxNameLength characters allowed";
+      return;
+    }
+    $metadata .= pack("CH6a[10]C/aC",$_->{type}, $_->{addr}, $_->{serial}, $_->{name}, $_->{groupid});
+  }
+
+  $metadata .= pack("C",1); #dstenables, should always be 1
+  my $blocksize = 1900;
+
+  $metadata = encode_base64($metadata,"");
+
+  my $numpackages = ceil(length($metadata)/$blocksize);
+  for(my $i=0;$i < $numpackages; $i++) {
+    my $package = substr($metadata,$i*$blocksize,$blocksize);
+
+    return MAXLAN_Write($hash,"m:".sprintf("%02d",$i).",".$package, "A:");
+  }
+}
+
+# Maps [9,61] -> [off,5.0,5.5,...,30.0,on]
+sub
+MAXLAN_ExtractTemperature($)
+{
+  return $_[0] == 61 ? "on" : ($_[0] == 9 ? "off" : sprintf("%2.1f",$_[0]/2));
+}
+
+sub
+MAXLAN_Parse($$)
+{
+  #http://www.domoticaforum.eu/viewtopic.php?f=66&t=6654
+  my ($hash, $rmsg) = @_;
+
+  my $name = $hash->{NAME};
+  Log3 $hash, 5, "Msg $rmsg";
+  my $cmd = substr($rmsg,0,1); # get leading char
+  my @args = split(',', substr($rmsg,2));
+
+  if ($cmd eq 'H'){ #Hello
+    $hash->{serial} = $args[0];
+    $hash->{addr} = $args[1];
+    $modules{MAX}{defptr}{$hash->{addr}} = $hash;
+    $hash->{fwversion} = $args[2];
+    my $dutycycle = 0;
+    if(@args > 5){
+      $dutycycle = hex($args[5]);
+      $hash->{dutycycle} = sprintf("%3.0f %%", $dutycycle);
+      readingsSingleUpdate( $hash, 'dutycycle', $dutycycle, 1 );
+    }
+    my $freememory = 0;
+    if(@args > 6){
+      $freememory = $args[6];
+    }
+    my $cubedatetime = {
+            year => 2000+hex(substr($args[7],0,2)),
+            month => hex(substr($args[7],2,2)),
+            day => hex(substr($args[7],4,2)),
+            hour => hex(substr($args[8],0,2)),
+            minute => hex(substr($args[8],2,2)),
+          };
+    $hash->{clockset} = hex($args[9]);
+    #$cubedatetime field is only valid if $clockset is 1
+    if($hash->{clockset}) {
+      my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
+      my $difference = ((((($cubedatetime->{year} - $year-1900)*12
+                          + $cubedatetime->{month} - $mon-1)*30
+                          + $cubedatetime->{day} - $mday)*24
+                          + $cubedatetime->{hour} - $hour)*60
+                          + $cubedatetime->{minute} - $min);
+      $hash->{cubeTimeDifference} = $difference;
+      if($difference > 1) {
+        Log3 $hash, 2, "MAXLAN_Parse: Cube thinks it is $cubedatetime->{day}.$cubedatetime->{month}.$cubedatetime->{year} $cubedatetime->{hour}:$cubedatetime->{minute}";
+        Log3 $hash, 2, "MAXLAN_Parse: Time difference is $difference minutes";
+      }
+    } else {
+      Log3 $hash, 2, "MAXLAN_Parse: Cube has no time set";
+    }
+
+    Log3 $hash, 5, "MAXLAN_Parse: Got hello, connection ip $args[4], duty cycle $dutycycle, freememory $freememory, clockset $hash->{clockset}";
+
+  } elsif($cmd eq 'M') {
+    #Metadata, this is basically a readwrite part of the cube's memory.
+    #I don't think that the cube interprets any of that data.
+    #One can write to that memory with the "m:" command
+    #The actual configuration comes with the "C:" response and can be set
+    #with the "s:" command.
+    return $name if(@args < 3); #On virgin devices, we get nothing, not even $magic$version$numgroups$numdevices
+
+    my $bindata = decode_base64($args[2]);
+    #$version is the version the serialized data format I guess
+    my ($magic,$version,$numgroups,@groupsdevices);
+    eval {
+      ($magic,$version,$numgroups,@groupsdevices) = unpack("CCCXC/(CC/aH6)C/(CH6a[10]C/aC)C",$bindata);
+      1;
+    } or do {
+      Log3 $hash, 1, "MAXLAN_Parse: Metadata response is malformed!";
+      return $name;
+    };
+    
+    if($magic != $metadata_magic || $version != $metadata_version) {
+      Log3 $hash, 3, "MAXLAN_Parse: magic $magic/version $version are not $metadata_magic/$metadata_version as expected";
+      $hash->{metadataVersionMismatch} = 1;
+    }
+
+    my $daylightsaving = pop(@groupsdevices); #should be always true (=0x01)
+
+    my $i;
+    $hash->{groups} = ();
+    for($i=0;$i<3*$numgroups;$i+=3){
+      $hash->{groups}[@{$hash->{groups}}]->{id} = $groupsdevices[$i];
+      $hash->{groups}[-1]->{name} = $groupsdevices[$i+1];
+      $hash->{groups}[-1]->{masterAddr} = $groupsdevices[$i+2];
+    }
+    #After a device is freshly paired, it does not appear in this metadata response,
+    #we first have to set some metadata for it
+    $hash->{devices} = ();
+    for(;$i<@groupsdevices;$i+=5){
+      $hash->{devices}[@{$hash->{devices}}]->{type} = $groupsdevices[$i];
+      $hash->{devices}[-1]->{addr} = $groupsdevices[$i+1];
+      $hash->{devices}[-1]->{serial} = $groupsdevices[$i+2];
+      $hash->{devices}[-1]->{name} = $groupsdevices[$i+3];
+      $hash->{devices}[-1]->{groupid} = $groupsdevices[$i+4];
+    }
+
+
+  }elsif($cmd eq "C"){#Configuration
+    return $name if(@args < 2);
+    my $bindata = decode_base64($args[1]);
+
+    if(length($bindata) < 18) {
+      Log3 $hash, 1, "Invalid C: response, not enough data";
+      return $name;
+    }
+
+    #Parse the first 18 bytes, those are send for every device
+    my ($len,$addr,$devicetype,$groupid,$firmware,$testresult,$serial) = unpack("CH6CCCCa[10]", $bindata);
+    Log3 $hash, 5, "MAXLAN_Parse: len $len, addr $addr, devicetype $devicetype, firmware $firmware, testresult $testresult, groupid $groupid, serial $serial";
+
+    $len = $len+1; #The len field itself was not counted
+
+    Dispatch($hash, "MAX,1,define,$addr,$device_types{$devicetype},$serial,$groupid", {}) if($device_types{$devicetype} ne "Cube");
+
+    #Set firmware and testresult on device
+    my $dhash = $modules{MAX}{defptr}{$addr};
+    if(defined($dhash)) {
+      readingsBeginUpdate($dhash);
+      readingsBulkUpdate($dhash, "firmware", sprintf("%u.%u",int($firmware/16),$firmware%16));
+      readingsBulkUpdate($dhash, "testresult", $testresult);
+      readingsEndUpdate($dhash, 1);
+    }
+
+    if($len != length($bindata)) {
+      Dispatch($hash, "MAX,1,Error,$addr,Parts of configuration are missing", {});
+      return $name;
+    }
+
+    #devicetype: Cube = 0, HeatingThermostat = 1, HeatingThermostatPlus = 2, WallMountedThermostat = 3, ShutterContact = 4, PushButton = 5
+    #Seems that ShutterContact does not have any configdata
+    if($device_types{$devicetype} eq "Cube"){
+      #TODO: there is a lot of data left to interpret
+
+    }elsif($device_types{$devicetype} =~ /HeatingThermostat.*/){
+      my ($comforttemp,$ecotemp,$maxsetpointtemp,$minsetpointtemp,$tempoffset,$windowopentemp,$windowopendur,$boost,$decalcifiction,$maxvalvesetting,$valveoffset,$weekprofile) = unpack("CCCCCCCCCCCH364",substr($bindata,18));
+      my $boostValve = ($boost & 0x1F) * 5;
+      my $boostDuration = $boost >> 5;
+      $comforttemp     = MAXLAN_ExtractTemperature($comforttemp); #convert to degree celcius
+      $ecotemp         = MAXLAN_ExtractTemperature($ecotemp); #convert to degree celcius
+      $tempoffset      = $tempoffset/2.0-3.5; #convert to degree
+      $maxsetpointtemp = MAXLAN_ExtractTemperature($maxsetpointtemp);
+      $minsetpointtemp = MAXLAN_ExtractTemperature($minsetpointtemp);
+      $windowopentemp  = MAXLAN_ExtractTemperature($windowopentemp);
+      $windowopendur   *= 5;
+      $maxvalvesetting = int($maxvalvesetting*100/255 + 0.5); # + 0.5 for correct rounding
+      $valveoffset     = int($valveoffset*100/255 + 0.5); # + 0.5 for correct rounding
+      my $decalcDay    = ($decalcifiction >> 5) & 0x07;
+      my $decalcTime   = $decalcifiction & 0x1F;
+      Log3 $hash, 5, "comfortemp $comforttemp, ecotemp $ecotemp, boostValve $boostValve, boostDuration $boostDuration, tempoffset $tempoffset, minsetpointtemp $minsetpointtemp, maxsetpointtemp $maxsetpointtemp, windowopentemp $windowopentemp, windowopendur $windowopendur";
+      Dispatch($hash, "MAX,1,HeatingThermostatConfig,$addr,$ecotemp,$comforttemp,$maxsetpointtemp,$minsetpointtemp,$weekprofile,$boostValve,$boostDuration,$tempoffset,$windowopentemp,$windowopendur,$maxvalvesetting,$valveoffset,$decalcDay,$decalcTime", {});
+
+    }elsif($device_types{$devicetype} eq "WallMountedThermostat"){
+      my ($comforttemp,$ecotemp,$maxsetpointtemp,$minsetpointtemp,$weekprofile,$tempoffset,$windowopentemp,$boost) = unpack("CCCCH364CCC",substr($bindata,18));
+      $comforttemp     = MAXLAN_ExtractTemperature($comforttemp);
+      $ecotemp         = MAXLAN_ExtractTemperature($ecotemp);
+      $maxsetpointtemp = MAXLAN_ExtractTemperature($maxsetpointtemp);
+      $minsetpointtemp = MAXLAN_ExtractTemperature($minsetpointtemp);
+      Log3 $hash, 5, "comfortemp $comforttemp, ecotemp $ecotemp, minsetpointtemp $minsetpointtemp, maxsetpointtemp $maxsetpointtemp";
+      if(defined($tempoffset)) { #With firmware 18 (opposed to firmware 16)
+        $tempoffset = $tempoffset/2.0-3.5; #convert to degree
+        my $boostValve = ($boost & 0x1F) * 5;
+        my $boostDuration = $boost >> 5;
+        $windowopentemp  = MAXLAN_ExtractTemperature($windowopentemp);
+        Log3 $hash, 5, "tempoffset $tempoffset, boostValve $boostValve, boostDuration $boostDuration, windowOpenTemp $windowopentemp";
+        Dispatch($hash, "MAX,1,WallThermostatConfig,$addr,$ecotemp,$comforttemp,$maxsetpointtemp,$minsetpointtemp,$weekprofile,$boostValve,$boostDuration,$tempoffset,$windowopentemp", {});
+      } else {
+        Dispatch($hash, "MAX,1,WallThermostatConfig,$addr,$ecotemp,$comforttemp,$maxsetpointtemp,$minsetpointtemp,$weekprofile", {});
+      }
+
+    }elsif($device_types{$devicetype} eq "ShutterContact"){
+      Log3 $hash, 2, "MAXLAN_Parse: ShutterContact send some configuration, but none was expected" if($len > 18);
+    }elsif($device_types{$devicetype} eq "PushButton"){
+      Log3 $hash, 2, "MAXLAN_Parse: PushButton send some configuration, but none was expected" if($len > 18);
+    }else{ #TODO
+      Log3 $hash, 2, "MAXLAN_Parse: Got configdata for unimplemented devicetype $devicetype";
+    }
+
+    #Clear Error
+    Dispatch($hash, "MAX,1,Error,$addr", {}) if($addr ne $hash->{addr}); #don't clear own error
+
+    #Check if it is already recorded in devices
+    my $found = 0;
+    foreach (@{$hash->{devices}}) {
+      $found = 1 if($_->{addr} eq $addr);
+    }
+    #Add device if it is not already known and not the cube itself
+    if(!$found && $devicetype != 0){
+      $hash->{devices}[@{$hash->{devices}}]->{type} = $devicetype;
+      $hash->{devices}[-1]->{addr} = $addr;
+      $hash->{devices}[-1]->{serial} = $serial;
+      $hash->{devices}[-1]->{name} = "no name";
+      $hash->{devices}[-1]->{groupid} = $groupid;
+    }
+
+  }elsif($cmd eq 'L'){#List
+
+    my $bindata = "";
+    $bindata  = decode_base64($args[0]) if(@args > 0);
+    #The L command consists of blocks of states (one for each device)
+    while(length($bindata)){
+      my ($len,$addr,$errCmd,$bits1) = unpack("CH6H2a",$bindata);
+      $errCmd = uc($errCmd);
+      my $unkbit1 = vec($bits1,0,1);
+      my $initialized = vec($bits1,1,1); #I never saw this beeing 0
+      my $answer = vec($bits1,2,1); #answer to what?
+      my $error = vec($bits1,3,1); # if 1 then see errframetype
+      my $valid = vec($bits1,4,1); #is the status following the common header valid
+      my $unkbit2 = vec($bits1,5,2);
+      my $unkbit3 = vec($bits1,7,1);
+  
+      Log3 $hash, 5, "len $len, addr $addr, initialized $initialized, valid $valid, error $error, errCmd $errCmd, answer $answer, unkbit ($unkbit1,$unkbit2,$unkbit3)";
+
+      my $payload = unpack("H*",substr($bindata,6,$len-6+1)); #+1 because the len field is not counted
+      if($valid) {
+        my $shash = $modules{MAX}{defptr}{$addr};
+
+        if(!$shash) {
+          Log3 $hash, 2, "Got List response for undefined device with addr $addr";
+        }elsif($shash->{type} =~ /HeatingThermostat.*/){
+          Dispatch($hash, "MAX,1,ThermostatState,$addr,$payload", {});
+        }elsif($shash->{type} eq "WallMountedThermostat"){
+          Dispatch($hash, "MAX,1,WallThermostatState,$addr,$payload", {});
+        }elsif($shash->{type} eq "ShutterContact"){
+          Dispatch($hash, "MAX,1,ShutterContactState,$addr,$payload", {});
+        }elsif($shash->{type} eq "PushButton"){
+          Dispatch($hash, "MAX,1,PushButtonState,$addr,$payload", {});
+        }else{
+          Log3 $hash, 2, "MAXLAN_Parse: Got status for unimplemented device type $shash->{type}";
+        }
+      }
+
+      my $dhash = $modules{MAX}{defptr}{$addr};
+      if(defined($dhash)) {
+        readingsBeginUpdate($dhash);
+        readingsBulkUpdate($dhash, "MAXLAN_initialized", $initialized);
+        readingsBulkUpdate($dhash, "MAXLAN_error", $error);
+        readingsBulkUpdate($dhash, "MAXLAN_errorInCommand", $error ? (exists($msgId2Cmd{$errCmd}) ? $msgId2Cmd{$errCmd} : $errCmd) : "");
+        readingsBulkUpdate($dhash, "MAXLAN_valid", $valid);
+        readingsBulkUpdate($dhash, "MAXLAN_isAnswer", $answer);
+        readingsEndUpdate($dhash, 1);
+        if($error) {
+          MAXLAN_Write($hash,"r:01,".encode_base64(pack("H*",$addr),""), "S:");
+        }
+      }
+
+      $bindata=substr($bindata,$len+1); #+1 because the len field is not counted
+    } # while(length($bindata))
+
+  }elsif($cmd eq "N"){#New device paired
+    if(@args==0){
+      $hash->{STATE} = "initalized"; #pairing ended
+      $hash->{pairmode} = 0;
+      return $name;
+    }
+    my ($type, $addr, $serial) = unpack("CH6a[10]", decode_base64($args[0]));
+    Log3 $hash, 2, "MAXLAN_Parse: Paired new device, type $device_types{$type}, addr $addr, serial $serial";
+    Dispatch($hash, "MAX,1,define,$addr,$device_types{$type},$serial,0", {});
+
+    #After a device has been paired, it automatically appears in the "L" and "C" commands,
+    MAXLAN_RequestConfiguration($hash,$addr);
+  } elsif($cmd eq "A"){#Acknowledged
+
+  } elsif($cmd eq "S"){#Response to s:
+    $hash->{dutycycle} = hex($args[0]); #number of command send over the air
+    readingsSingleUpdate( $hash, 'dutycycle', $hash->{dutycycle}, 1 );
+
+    my $discarded = $args[1];
+    $hash->{freememoryslot} = hex($args[2]);
+    Log3 $hash, 5, "MAXLAN_Parse: dutycyle $hash->{dutycycle}, freememoryslot $hash->{freememoryslot}";
+
+    Log3 $hash, 3, "MAXLAN_Parse: 1% rule: we sent too much, cmd is now in queue" if($hash->{dutycycle} == 100 && $hash->{freememoryslot} > 0);
+    Log3 $hash, 2, "MAXLAN_Parse: 1% rule: we sent too much, queue is full" if($hash->{dutycycle} == 100 && $hash->{freememoryslot} == 0);
+    Log3 $hash, 2, "MAXLAN_Parse: Command was discarded" if($discarded);
+  } else {
+    Log3 $hash, 2, "MAXLAN_Parse: Unknown command $cmd";
+  }
+  return $name;
+}
+
+
+########################
+#Returns undef on sucess
+sub
+MAXLAN_SimpleWrite(@)
+{
+  my ($hash, $msg) = @_;
+  my $name = $hash->{NAME};
+
+  Log3 $hash, 5, 'MAXLAN_SimpleWrite:  '.$msg;
+  
+  return "MAXLAN_SimpleWrite: Not connected" if(!MAXLAN_IsConnected($hash));
+
+  $msg .= "\r\n";
+  
+  my $ret = syswrite($hash->{TCPDev}, $msg);
+  #TODO: none of those conditions detect if the connection is actually lost!
+  if(!$hash->{TCPDev} || !defined($ret) || !$hash->{TCPDev}->connected) {
+    Log3 $hash, 1, 'MAXLAN_SimpleWrite failed';
+    MAXLAN_Disconnect($hash);
+    return "MAXLAN_SimpleWrite: syswrite failed";
+  }
+  return undef;
+}
+
+########################
+sub
+MAXLAN_DoInit($)
+{
+  my ($hash) = @_;
+  return undef;
+}
+
+#Returns undef on success
+sub
+MAXLAN_RequestList($)
+{
+  my $hash = shift;
+  return MAXLAN_Write($hash, "l:", "L:");
+}
+
+#####################################
+sub
+MAXLAN_Poll($)
+{
+  my $hash = shift;
+
+  my $ret = undef;
+  if(MAXLAN_IsConnected($hash)) {
+    $ret = MAXLAN_RequestList($hash);
+  } else {
+    #Connecting gives us a RequestList for free
+    $ret = MAXLAN_Connect($hash);
+  }
+
+  if($ret) {
+    #Connecting failed/Got invalid answer
+    MAXLAN_Disconnect($hash);
+    InternalTimer(gettimeofday()+$reconnect_interval, "MAXLAN_Poll", $hash, 0);
+    return;
+  }
+
+  MAXLAN_Disconnect($hash) if(!$hash->{persistent} && !$hash->{pairmode});
+
+  InternalTimer(gettimeofday()+$hash->{INTERVAL}, "MAXLAN_Poll", $hash, 0);
+}
+
+#This only works for a device that got just paired
+sub
+MAXLAN_RequestConfiguration($$)
+{
+  my ($hash,$addr) = @_;
+  return MAXLAN_Write($hash,"c:$addr", "C:");
+}
+
+sub
+MAXLAN_Send(@)
+{
+  my ($hash, $cmd, $dst, $payload, %opts) = @_;
+
+  my $flags = "00";
+  my $groupId = "00";
+  my $callbackParam = undef;
+
+  $flags = $opts{flags} if(exists($opts{flags}));
+  $groupId = $opts{groupId} if(exists($opts{groupId}));
+  Log3 $hash, 2, "MAXLAN_Send: MAXLAN does not support src" if(exists($opts{src}));
+  $callbackParam = $opts{callbackParam} if(exists($opts{callbackParam}));
+
+  $payload = pack("H*","00".$flags.$msgCmd2Id{$cmd}."000000".$dst.$groupId.$payload);
+
+  my $ret = MAXLAN_Write($hash,"s:".encode_base64($payload,""), "S:");
+  #TODO: actually check return value
+  if(defined($opts{callbackParam})) {
+    Dispatch($hash, "MAX,1,Ack$cmd,$dst,$opts{callbackParam}", {});
+  }
+  #Reschedule a poll in the near future after the cube will
+  #have gotten an answer
+  RemoveInternalTimer($hash);
+  InternalTimer(gettimeofday()+$roundtriptime, "MAXLAN_Poll", $hash, 0);
+  return $ret;
+}
+
+#Resets the cube, i.e. do a factory reset. All pairings will be lost from the cube
+#(but you will have to manually reset each individual device.
+sub
+MAXLAN_RequestReset($)
+{
+  my $hash = shift;
+  return MAXLAN_Write($hash,"a:", "A:");
+}
+
+#Remove the device from the cube, i.e. deletes the pairing
+sub
+MAXLAN_RemoveDevice($$)
+{
+  my ($hash,$addr) = @_;
+  #This does a factoryReset on the Device
+  my $ret = MAXLAN_Write($hash,"t:1,1,".encode_base64(pack("H6",$addr),""), "A:");
+  if(!defined($ret)) { #success
+    #The device is not longer accessable by the Cube
+    $modules{MAX}{defptr}{$addr}{IODev} = undef;
+  }
+  return $ret;
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="MAXLAN"></a>
+<h3>MAXLAN</h3>
+<ul>
+  The MAXLAN is the fhem module for the eQ-3 MAX! Cube LAN Gateway.
+  <br><br>
+  The fhem module makes the MAX! "bus" accessible to fhem, automatically detecting paired MAX! devices. It also represents properties of the MAX! Cube. The other devices are handled by the <a href="#MAX">MAX</a> module, which uses this module as its backend.<br>
+  <br>
+
+  <a name="MAXLANdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; MAXLAN &lt;ip-address&gt;[:port] [&lt;pollintervall&gt; [ondemand]]</code><br>
+    <br>
+    port is 62910 by default. (If your Cube listens on port 80, you have to update the firmware with
+    the official MAX! software).
+    If the ip-address is called none, then no device will be opened, so you
+    can experiment without hardware attached.<br>
+    The optional parameter &lt;pollintervall&gt; defines the time in seconds between each polling of data from the cube.<br>
+    You may provide the option <code>ondemand</code> forcing the MAXLAN module to tear-down the connection as often as possible
+    thus making the cube usable by other applications or the web portal.
+  </ul>
+  <br>
+
+  <a name="MAXLANset"></a>
+  <b>Set</b>
+  <ul>
+    <li>pairmode [&lt;n&gt;,cancel]<br>
+    Sets the cube into pairing mode for &lt;n&gt; seconds (default is 60s ) where it can be paired with other devices (Thermostats, Buttons, etc.). You also have to set the other device into pairing mode manually. (For Thermostats, this is pressing the "Boost" button for 3 seconds, for example).
+Setting pairmode to "cancel" puts the cube out of pairing mode.</li>
+    <li>raw &lt;data&gt;<br>
+    Sends the raw &lt;data&gt; to the cube.</li>
+    <li>clock<br>
+    Sets the internal clock in the cube to the current system time of fhem's machine (uses timezone attribute if set). You can add<br>
+    <code>attr ml set-clock-on-init</code><br>
+    to your fhem.cfg to do this automatically on startup.</li>
+    <li>factorReset<br>
+      Reset the cube to factory defaults.</li>
+    <li>reconnect<br>
+      FHEM will terminate the current connection to the cube and then reconnect. This allows
+      re-reading the configuration data from the cube, as it is only send after establishing a new connection.</li>
+  </ul>
+  <br>
+
+  <a name="MAXLANget"></a>
+  <b>Get</b>
+  <ul>
+  N/A
+  </ul>
+  <br>
+  <br>
+
+  <a name="MAXLANattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li>set-clock-on-init<br>
+      (Default: 1). Automatically call "set clock" after connecting to the cube.</li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#attrdummy">dummy</a></li>
+    <li><a href="#loglevel">loglevel</a></li>
+    <li><a href="#addvaltrigger">addvaltrigger</a></li>
+    <li>timezone<br>
+      (Default: CET-CEST). Set MAX Cube timezone (requires "set clock" to take effect).<br>
+      <b>NB.</b>Cube time and cubeTimeDifference will not change until Cube next connects.<br>
+      <ul>
+      <li>GMT-BST - (UTC +0, UTC+1)</li>
+      <li>CET-CEST - (UTC +1, UTC+2)</li>
+      <li>EET-EEST - (UTC +2, UTC+3)</li>
+      <li>FET-FEST - (UTC +3)</li>
+      <li>MSK-MSD - (UTC +4)</li>
+      </ul>
+      The following are settings with no DST (daylight saving time)
+      <ul>
+      <li>GMT - (UTC +0)</li>
+      <li>CET - (UTC +1)</li>
+      <li>EET - (UTC +2)</li>
+      </ul>
+    </li>
+  </ul>
+</ul>
+
+=end html
+=cut

+ 588 - 0
fhem/core/FHEM/00_MQTT.pm

@@ -0,0 +1,588 @@
+##############################################
+#
+# fhem bridge to mqtt (see http://mqtt.org)
+#
+# Copyright (C) 2017 Stephan Eisler
+# Copyright (C) 2014 - 2016 Norbert Truchsess
+#
+#     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: 00_MQTT.pm 13318 2017-02-03 09:42:52Z eisler $
+#
+##############################################
+
+my %sets = (
+  "connect" => "",
+  "disconnect" => "",
+);
+
+my %gets = (
+  "version"   => ""
+);
+
+my @clients = qw(
+  MQTT_DEVICE
+  MQTT_BRIDGE
+);
+
+sub MQTT_Initialize($) {
+
+  my $hash = shift @_;
+
+  require "$main::attr{global}{modpath}/FHEM/DevIo.pm";
+
+  # Provider
+  $hash->{Clients} = join (':',@clients);
+  $hash->{ReadyFn} = "MQTT::Ready";
+  $hash->{ReadFn}  = "MQTT::Read";
+
+  # Consumer
+  $hash->{DefFn}    = "MQTT::Define";
+  $hash->{UndefFn}  = "MQTT::Undef";
+  $hash->{SetFn}    = "MQTT::Set";
+  $hash->{NotifyFn} = "MQTT::Notify";
+
+  $hash->{AttrList} = "keep-alive ".$main::readingFnAttributes;
+}
+
+package MQTT;
+
+use Exporter ('import');
+@EXPORT = ();
+@EXPORT_OK = qw(send_publish send_subscribe send_unsubscribe client_attr client_subscribe_topic client_unsubscribe_topic topic_to_regexp);
+%EXPORT_TAGS = (all => [@EXPORT_OK]);
+
+use strict;
+use warnings;
+
+use GPUtils qw(:all);
+
+use Net::MQTT::Constants;
+use Net::MQTT::Message;
+
+our %qos = map {qos_string($_) => $_} (MQTT_QOS_AT_MOST_ONCE,MQTT_QOS_AT_LEAST_ONCE,MQTT_QOS_EXACTLY_ONCE);
+
+BEGIN {GP_Import(qw(
+  gettimeofday
+  readingsSingleUpdate
+  DevIo_OpenDev
+  DevIo_SimpleWrite
+  DevIo_SimpleRead
+  DevIo_CloseDev
+  RemoveInternalTimer
+  InternalTimer
+  AttrVal
+  Log3
+  AssignIoPort
+  getKeyValue
+  setKeyValue
+  ))};
+
+sub Define($$) {
+  my ( $hash, $def ) = @_;
+
+  $hash->{NOTIFYDEV} = "global";
+  $hash->{msgid} = 1;
+  $hash->{timeout} = 60;
+  $hash->{messages} = {};
+
+  my ($host,$username,$password) = split("[ \t]+", $hash->{DEF});
+  $hash->{DeviceName} = $host;
+  
+  my $name = $hash->{NAME};
+  my $user = getKeyValue($name."_user");
+  my $pass = getKeyValue($name."_pass");
+
+  setKeyValue($name."_user",$username) unless(defined($user));
+  setKeyValue($name."_pass",$password) unless(defined($pass));
+
+  $hash->{DEF} = $host;
+
+  if ($main::init_done) {
+    return Start($hash);
+  } else {
+    return undef;
+  }
+}
+
+sub Undef($) {
+  my $hash = shift;
+  Stop($hash);
+  my $name = $hash->{NAME};
+  setKeyValue($name."_user",undef);
+  setKeyValue($name."_pass",undef);
+  return undef;
+}
+
+sub Set($@) {
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
+    if(!defined($sets{$a[1]}));
+  my $command = $a[1];
+  my $value = $a[2];
+
+  COMMAND_HANDLER: {
+    $command eq "connect" and do {
+      Start($hash);
+      last;
+    };
+    $command eq "disconnect" and do {
+      Stop($hash);
+      last;
+    };
+  };
+}
+
+sub Notify($$) {
+  my ($hash,$dev) = @_;
+  if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
+    Start($hash);
+  } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
+  }
+}
+
+sub Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+
+  my $hash = $main::defs{$name};
+  ATTRIBUTE_HANDLER: {
+    $attribute eq "keep-alive" and do {
+      if ($command eq "set") {
+        $hash->{timeout} = $value;
+      } else {
+        $hash->{timeout} = 60;
+      }
+      if ($main::init_done) {
+        $hash->{ping_received}=1;
+        Timer($hash);
+      };
+      last;
+    };
+  };
+}
+
+sub Start($) {
+  my $hash = shift;
+  DevIo_CloseDev($hash);
+  return DevIo_OpenDev($hash, 0, "MQTT::Init");
+}
+
+sub Stop($) {
+  my $hash = shift;
+  send_disconnect($hash);
+  DevIo_CloseDev($hash);
+  RemoveInternalTimer($hash);
+  readingsSingleUpdate($hash,"connection","disconnected",1);
+}
+
+sub Ready($) {
+  my $hash = shift;
+  return DevIo_OpenDev($hash, 1, "MQTT::Init") if($hash->{STATE} eq "disconnected");
+}
+
+sub Rename() {
+  my ($new,$old) = @_;
+  setKeyValue($new."_user",getKeyValue($old."_user"));
+  setKeyValue($new."_pass",getKeyValue($old."_pass"));
+	
+  setKeyValue($old."_user",undef);
+  setKeyValue($old."_pass",undef);
+  return undef;
+}
+
+sub Init($) {
+  my $hash = shift;
+  send_connect($hash);
+  readingsSingleUpdate($hash,"connection","connecting",1);
+  $hash->{ping_received}=1;
+  Timer($hash);
+  return undef;
+}
+
+sub Timer($) {
+  my $hash = shift;
+  RemoveInternalTimer($hash);
+  readingsSingleUpdate($hash,"connection","timed-out",1) unless $hash->{ping_received};
+  $hash->{ping_received} = 0;
+  InternalTimer(gettimeofday()+$hash->{timeout}, "MQTT::Timer", $hash, 0);
+  send_ping($hash);
+}
+
+sub Read {
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+  my $buf = DevIo_SimpleRead($hash);
+  return undef unless $buf;
+  $hash->{buf} .= $buf;
+  while (my $mqtt = Net::MQTT::Message->new_from_bytes($hash->{buf},1)) {
+
+    my $message_type = $mqtt->message_type();
+
+    Log3($name,5,"MQTT $name message received: ".$mqtt->string());
+
+    MESSAGE_TYPE: {
+      $message_type == MQTT_CONNACK and do {
+        readingsSingleUpdate($hash,"connection","connected",1);
+        GP_ForallClients($hash,\&client_start);
+        foreach my $message_id (keys %{$hash->{messages}}) {
+          my $msg = $hash->{messages}->{$message_id}->{message};
+          $msg->{dup} = 1;
+          DevIo_SimpleWrite($hash,$msg->bytes,undef);
+        }
+        last;
+      };
+
+      $message_type == MQTT_PUBLISH and do {
+        my $topic = $mqtt->topic();
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          Log3($client->{NAME},5,"publish received for $topic, ".$mqtt->message());
+          if (grep { $topic =~ $_ } @{$client->{subscribeExpr}}) {
+            readingsSingleUpdate($client,"transmission-state","incoming publish received",1);
+            if ($client->{TYPE} eq "MQTT_DEVICE") {
+              MQTT::DEVICE::onmessage($client,$topic,$mqtt->message());
+            } else {
+              MQTT::BRIDGE::onmessage($client,$topic,$mqtt->message());
+            }
+          };
+        },undef);
+        if (my $qos = $mqtt->qos() > MQTT_QOS_AT_MOST_ONCE) {
+          my $message_id = $mqtt->message_id();
+          if ($qos == MQTT_QOS_AT_LEAST_ONCE) {
+            send_message($hash, message_type => MQTT_PUBACK, message_id => $message_id);
+          } else {
+            send_message($hash, message_type => MQTT_PUBREC, message_id => $message_id);
+          }
+        }
+        last;
+      };
+
+      $message_type == MQTT_PUBACK and do {
+        my $message_id = $mqtt->message_id();
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          if ($client->{message_ids}->{$message_id}) {
+            readingsSingleUpdate($client,"transmission-state","outgoing publish acknowledged",1);
+            delete $client->{message_ids}->{$message_id};
+          };
+        },undef);
+        delete $hash->{messages}->{$message_id}; #QoS Level 1: at_least_once handling
+        last;
+      };
+
+      $message_type == MQTT_PUBREC and do {
+        my $message_id = $mqtt->message_id();
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          if ($client->{message_ids}->{$message_id}) {
+            readingsSingleUpdate($client,"transmission-state","outgoing publish received",1);
+          };
+        },undef);
+        send_message($hash, message_type => MQTT_PUBREL, message_id => $message_id); #QoS Level 2: exactly_once handling
+        last;
+      };
+
+      $message_type == MQTT_PUBREL and do {
+        my $message_id = $mqtt->message_id();
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          if ($client->{message_ids}->{$message_id}) {
+            readingsSingleUpdate($client,"transmission-state","incoming publish released",1);
+            delete $client->{message_ids}->{$message_id};
+          };
+        },undef);
+        send_message($hash, message_type => MQTT_PUBCOMP, message_id => $message_id); #QoS Level 2: exactly_once handling
+        delete $hash->{messages}->{$message_id};
+        last;
+      };
+
+      $message_type == MQTT_PUBCOMP and do {
+        my $message_id = $mqtt->message_id();
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          if ($client->{message_ids}->{$message_id}) {
+            readingsSingleUpdate($client,"transmission-state","outgoing publish completed",1);
+            delete $client->{message_ids}->{$message_id};
+          };
+        },undef);
+        delete $hash->{messages}->{$message_id}; #QoS Level 2: exactly_once handling
+        last;
+      };
+
+      $message_type == MQTT_SUBACK and do {
+        my $message_id = $mqtt->message_id();
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          if ($client->{message_ids}->{$message_id}) {
+            readingsSingleUpdate($client,"transmission-state","subscription acknowledged",1);
+            delete $client->{message_ids}->{$message_id};
+          };
+        },undef);
+        delete $hash->{messages}->{$message_id}; #QoS Level 1: at_least_once handling
+        last;
+      };
+
+      $message_type == MQTT_UNSUBACK and do {
+        my $message_id = $mqtt->message_id();
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          if ($client->{message_ids}->{$message_id}) {
+            readingsSingleUpdate($client,"transmission-state","unsubscription acknowledged",1);
+            delete $client->{message_ids}->{$message_id};
+          };
+        },undef);
+        delete $hash->{messages}->{$message_id}; #QoS Level 1: at_least_once handling
+        last;
+      };
+
+      $message_type == MQTT_PINGRESP and do {
+        $hash->{ping_received} = 1;
+        readingsSingleUpdate($hash,"connection","active",1);
+        last;
+      };
+
+      Log3($hash->{NAME},4,"MQTT::Read '$hash->{NAME}' unexpected message type '".message_type_string($message_type)."'");
+    }
+  }
+  return undef;
+};
+
+sub send_connect($) {
+  my $hash = shift;
+  my $name = $hash->{NAME};
+  my $user = getKeyValue($name."_user");
+  my $pass = getKeyValue($name."_pass");
+  return send_message($hash, message_type => MQTT_CONNECT, keep_alive_timer => $hash->{timeout}, user_name => $user, password => $pass);
+};
+
+sub send_publish($@) {
+  my ($hash,%msg) = @_;
+  if ($msg{qos} == MQTT_QOS_AT_MOST_ONCE) {
+    send_message(shift, message_type => MQTT_PUBLISH, %msg);
+    return undef;
+  } else {
+    my $msgid = $hash->{msgid}++;
+    send_message(shift, message_type => MQTT_PUBLISH, message_id => $msgid, %msg);
+    return $msgid;
+  }
+};
+
+sub send_subscribe($@) {
+  my $hash = shift;
+  my $msgid = $hash->{msgid}++;
+  send_message($hash, message_type => MQTT_SUBSCRIBE, message_id => $msgid, qos => MQTT_QOS_AT_LEAST_ONCE, @_);
+  return $msgid;
+};
+
+sub send_unsubscribe($@) {
+  my $hash = shift;
+  my $msgid = $hash->{msgid}++;
+  send_message($hash, message_type => MQTT_UNSUBSCRIBE, message_id => $msgid, qos => MQTT_QOS_AT_LEAST_ONCE, @_);
+  return $msgid;
+};
+
+sub send_ping($) {
+  return send_message(shift, message_type => MQTT_PINGREQ);
+};
+
+sub send_disconnect($) {
+  return send_message(shift, message_type => MQTT_DISCONNECT);
+};
+
+sub send_message($$$@) {
+  my ($hash,%msg) = @_;
+  my $name = $hash->{NAME};
+  my $message = Net::MQTT::Message->new(%msg);
+  Log3($name,5,"MQTT $name message sent: ".$message->string());
+  if (defined $msg{message_id}) {
+    $hash->{messages}->{$msg{message_id}} = {
+      message => $message,
+      timeout => gettimeofday()+$hash->{timeout},
+    };
+  }
+  DevIo_SimpleWrite($hash,$message->bytes,undef);
+};
+
+sub topic_to_regexp($) {
+  my $t = shift;
+  $t =~ s|#$|.\*|;
+  $t =~ s|\/\.\*$|.\*|;
+  $t =~ s|\/|\\\/|g;
+  $t =~ s|(\+)([^+]*$)|(+)$2|;
+  $t =~ s|\+|[^\/]+|g;
+  return "^$t\$";
+}
+
+sub client_subscribe_topic($$) {
+  my ($client,$topic) = @_;
+  push @{$client->{subscribe}},$topic unless grep {$_ eq $topic} @{$client->{subscribe}};
+  my $expr = topic_to_regexp($topic);
+  push @{$client->{subscribeExpr}},$expr unless grep {$_ eq $expr} @{$client->{subscribeExpr}};
+  if ($main::init_done) {
+    if (my $mqtt = $client->{IODev}) {;
+      my $msgid = send_subscribe($mqtt,
+        topics => [[$topic => $client->{qos} || MQTT_QOS_AT_MOST_ONCE]],
+      );
+      $client->{message_ids}->{$msgid}++;
+      readingsSingleUpdate($client,"transmission-state","subscribe sent",1)
+    }
+  }
+};
+
+sub client_unsubscribe_topic($$) {
+  my ($client,$topic) = @_;
+  $client->{subscribe} = [grep { $_ ne $topic } @{$client->{subscribe}}];
+  my $expr = topic_to_regexp($topic);
+  $client->{subscribeExpr} = [grep { $_ ne $expr} @{$client->{subscribeExpr}}];
+  if ($main::init_done) {
+    if (my $mqtt = $client->{IODev}) {;
+      my $msgid = send_unsubscribe($mqtt,
+        topics => [$topic],
+      );
+      $client->{message_ids}->{$msgid}++;
+      readingsSingleUpdate($client,"transmission-state","unsubscribe sent",1)
+    }
+  }
+};
+
+sub Client_Define($$) {
+  my ( $client, $def ) = @_;
+
+  $client->{NOTIFYDEV} = $client->{DEF} if $client->{DEF};
+  $client->{qos} = MQTT_QOS_AT_MOST_ONCE;
+  $client->{retain} = 0;
+  $client->{subscribe} = [];
+  $client->{subscribeExpr} = [];
+  AssignIoPort($client);
+
+  if ($main::init_done) {
+    return client_start($client);
+  } else {
+    return undef;
+  }
+};
+
+sub Client_Undefine($) {
+  client_stop(shift);
+  return undef;
+};
+
+sub client_attr($$$$$) {
+  my ($client,$command,$name,$attribute,$value) = @_;
+
+  ATTRIBUTE_HANDLER: {
+    $attribute eq "qos" and do {
+      if ($command eq "set") {
+        $client->{qos} = $MQTT::qos{$value};
+      } else {
+        $client->{qos} = MQTT_QOS_AT_MOST_ONCE;
+      }
+      last;
+    };
+    $attribute eq "retain" and do {
+      if ($command eq "set") {
+        $client->{retain} = $value;
+      } else {
+        $client->{retain} = 0;
+      }
+      last;
+    };
+    $attribute eq "IODev" and do {
+      if ($main::init_done) {
+        if ($command eq "set") {
+          client_stop($client);
+          $main::attr{$name}{IODev} = $value;
+          client_start($client);
+        } else {
+          client_stop($client);
+        }
+      }
+      last;
+    };
+  }
+};
+
+sub client_start($) {
+  my $client = shift;
+  my $name = $client->{NAME};
+  if (! (defined AttrVal($name,"stateFormat",undef))) {
+    $main::attr{$name}{stateFormat} = "transmission-state";
+  }
+  if (@{$client->{subscribe}}) {
+    my $msgid = send_subscribe($client->{IODev},
+      topics => [map { [$_ => $client->{qos} || MQTT_QOS_AT_MOST_ONCE] } @{$client->{subscribe}}],
+    );
+    $client->{message_ids}->{$msgid}++;
+    readingsSingleUpdate($client,"transmission-state","subscribe sent",1);
+  }
+};
+
+sub client_stop($) {
+  my $client = shift;
+  if (@{$client->{subscribe}}) {
+    my $msgid = send_unsubscribe($client->{IODev},
+      topics => [@{$client->{subscribe}}],
+    );
+    $client->{message_ids}->{$msgid}++;
+    readingsSingleUpdate($client,"transmission-state","unsubscribe sent",1);
+  }
+};
+
+1;
+
+=pod
+=item [device]
+=item summary connects fhem to MQTT
+=begin html
+
+<a name="MQTT"></a>
+<h3>MQTT</h3>
+<ul>
+  <p>connects fhem to <a href="http://mqtt.org">mqtt</a>.</p>
+  <p>A single MQTT device can serve multiple <a href="#MQTT_DEVICE">MQTT_DEVICE</a> and <a href="#MQTT_BRIDGE">MQTT_BRIDGE</a> clients.<br/>
+     Each <a href="#MQTT_DEVICE">MQTT_DEVICE</a> acts as a bridge in between an fhem-device and mqtt.<br/>
+     Note: this module is based on <a href="https://metacpan.org/pod/distribution/Net-MQTT/lib/Net/MQTT.pod">Net::MQTT</a> which needs to be installed from CPAN first.</p>
+  <a name="MQTTdefine"></a>
+  <p><b>Define</b></p>
+  <ul>
+    <p><code>define &lt;name&gt; MQTT &lt;ip:port&gt; [&lt;username&gt;] [&lt;password&gt;]</code></p>
+    <p>Specifies the MQTT device.</p>
+  </ul>
+  <a name="MQTTset"></a>
+  <p><b>Set</b></p>
+  <ul>
+    <li>
+      <p><code>set &lt;name&gt; connect</code><br/>
+         (re-)connects the MQTT-device to the mqtt-broker</p>
+    </li>
+    <li>
+      <p><code>set &lt;name&gt; disconnect</code><br/>
+         disconnects the MQTT-device from the mqtt-broker</p>
+    </li>
+  </ul>
+  <a name="MQTTattr"></a>
+  <p><b>Attributes</b></p>
+  <ul>
+    <li>
+      <p>keep-alive<br/>
+         sets the keep-alive time (in seconds).</p>
+    </li>
+  </ul>
+</ul>
+
+=end html
+=cut

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

@@ -0,0 +1,522 @@
+##############################################
+#
+# fhem driver for MySensors serial or network gateway (see http://mysensors.org)
+#
+# Copyright (C) 2014 Norbert Truchsess
+#
+#     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: 00_MYSENSORS.pm 9341 2015-10-02 14:55:54Z ntruchsess $
+#
+##############################################
+
+my %sets = (
+  "connect" => [],
+  "disconnect" => [],
+  "inclusion-mode" => [qw(on off)],
+);
+
+my %gets = (
+  "version"   => ""
+);
+
+my @clients = qw(
+  MYSENSORS_DEVICE
+);
+
+sub MYSENSORS_Initialize($) {
+
+  my $hash = shift @_;
+
+  require "$main::attr{global}{modpath}/FHEM/DevIo.pm";
+
+  # Provider
+  $hash->{Clients} = join (':',@clients);
+  $hash->{ReadyFn} = "MYSENSORS::Ready";
+  $hash->{ReadFn}  = "MYSENSORS::Read";
+
+  # Consumer
+  $hash->{DefFn}    = "MYSENSORS::Define";
+  $hash->{UndefFn}  = "MYSENSORS::Undef";
+  $hash->{SetFn}    = "MYSENSORS::Set";
+  $hash->{AttrFn}   = "MYSENSORS::Attr";
+  $hash->{NotifyFn} = "MYSENSORS::Notify";
+
+  $hash->{AttrList} = 
+    "autocreate:1 ".
+    "requestAck:1 ".
+    "first-sensorid ".
+    "last-sensorid ".
+    "stateFormat";
+}
+
+package MYSENSORS;
+
+use Exporter ('import');
+@EXPORT = ();
+@EXPORT_OK = qw(sendMessage);
+%EXPORT_TAGS = (all => [@EXPORT_OK]);
+
+use strict;
+use warnings;
+
+use GPUtils qw(:all);
+
+use Device::MySensors::Constants qw(:all);
+use Device::MySensors::Message qw(:all);
+
+BEGIN {GP_Import(qw(
+  CommandDefine
+  CommandModify
+  CommandAttr
+  gettimeofday
+  readingsSingleUpdate
+  DevIo_OpenDev
+  DevIo_SimpleWrite
+  DevIo_SimpleRead
+  DevIo_CloseDev
+  RemoveInternalTimer
+  InternalTimer
+  AttrVal
+  Log3
+  ))};
+
+my %sensorAttr = (
+  LIGHT => ['setCommands on:V_LIGHT:1 off:V_LIGHT:0' ],
+  ARDUINO_NODE => [ 'config M' ],
+  ARDUINO_REPEATER_NODE => [ 'config M' ],
+);
+
+sub Define($$) {
+  my ( $hash, $def ) = @_;
+
+  $hash->{NOTIFYDEV} = "global";
+
+  if ($main::init_done) {
+    return Start($hash);
+  } else {
+    return undef;
+  }
+}
+
+sub Undef($) {
+  Stop(shift);
+}
+
+sub Set($@) {
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", map {@{$sets{$_}} ? $_.':'.join ',', @{$sets{$_}} : $_} sort keys %sets)
+    if(!defined($sets{$a[1]}));
+  my $command = $a[1];
+  my $value = $a[2];
+
+  COMMAND_HANDLER: {
+    $command eq "connect" and do {
+      Start($hash);
+      last;
+    };
+    $command eq "disconnect" and do {
+      Stop($hash);
+      last;
+    };
+    $command eq "inclusion-mode" and do {
+      sendMessage($hash,radioId => 0, childId => 0, cmd => C_INTERNAL, ack => 0, subType => I_INCLUSION_MODE, payload => $value eq 'on' ? 1 : 0);
+      $hash->{'inclusion-mode'} = $value eq 'on' ? 1 : 0;
+      last;
+    };
+  };
+}
+
+sub Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+
+  my $hash = $main::defs{$name};
+  ATTRIBUTE_HANDLER: {
+    $attribute eq "autocreate" and do {
+      if ($main::init_done) {
+        my $mode = $command eq "set" ? 1 : 0;
+        sendMessage($hash,radioId => $hash->{radioId}, childId => $hash->{childId}, ack => 0, subType => I_INCLUSION_MODE, payload => $mode);
+        $hash->{'inclusion-mode'} = $mode;
+      }
+      last;
+    };
+    $attribute eq "requestAck" and do {
+      if ($command eq "set") {
+        $hash->{ack} = 1;
+      } else {
+        $hash->{ack} = 0;
+        $hash->{messages} = {};
+        $hash->{outstandingAck} = 0;
+      }
+      last;
+    };
+  }
+}
+
+sub Notify($$) {
+  my ($hash,$dev) = @_;
+  if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
+    Start($hash);
+  } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
+  }
+}
+
+sub Start($) {
+  my $hash = shift;
+  my ($dev) = split("[ \t]+", $hash->{DEF});
+  $hash->{DeviceName} = $dev;
+  CommandAttr(undef, "$hash->{NAME} stateFormat connection") unless AttrVal($hash->{NAME},"stateFormat",undef);
+  DevIo_CloseDev($hash);
+  return DevIo_OpenDev($hash, 0, "MYSENSORS::Init");
+}
+
+sub Stop($) {
+  my $hash = shift;
+  DevIo_CloseDev($hash);
+  RemoveInternalTimer($hash);
+  readingsSingleUpdate($hash,"connection","disconnected",1);
+}
+
+sub Ready($) {
+  my $hash = shift;
+  return DevIo_OpenDev($hash, 1, "MYSENSORS::Init") if($hash->{STATE} eq "disconnected");
+}
+
+sub Init($) {
+  my $hash = shift;
+  my $name = $hash->{NAME};
+  $hash->{'inclusion-mode'} = AttrVal($name,"autocreate",0);
+  $hash->{ack} = AttrVal($name,"requestAck",0);
+  $hash->{outstandingAck} = 0;
+  if ($hash->{ack}) {
+    GP_ForallClients($hash,sub {
+      my $client = shift;
+      $hash->{messagesForRadioId}->{$client->{radioId}} = {
+        lastseen => -1,
+        nexttry  => -1,
+        numtries => 1,
+        messages => [],
+      };
+    });
+  }
+  readingsSingleUpdate($hash,"connection","connected",1);
+  sendMessage($hash, radioId => 0, childId => 0, cmd => C_INTERNAL, ack => 0, subType => I_VERSION, payload => '');
+  return undef;
+}
+
+sub Timer($) {
+  my $hash = shift;
+  my $now = time;
+  foreach my $radioid (keys %{$hash->{messagesForRadioId}}) {
+    my $msgsForId = $hash->{messagesForRadioId}->{$radioid};
+    if ($now > $msgsForId->{nexttry}) {
+      foreach my $msg (@{$msgsForId->{messages}}) {
+        my $txt = createMsg(%$msg);
+        Log3 ($hash->{NAME},5,"MYSENSORS outstanding ack, re-send: ".dumpMsg($msg));
+        DevIo_SimpleWrite($hash,"$txt\n",undef);
+      }
+      $msgsForId->{numtries}++;
+      $msgsForId->{nexttry} = gettimeofday()+$msgsForId->{numtries};
+    }
+  }
+  _scheduleTimer($hash);
+}
+
+sub Read {
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  my $buf = DevIo_SimpleRead($hash);
+  return "" if(!defined($buf));
+
+  my $data = $hash->{PARTIAL};
+  Log3 ($name, 5, "MYSENSORS/RAW: $data/$buf");
+  $data .= $buf;
+
+  while ($data =~ m/\n/) {
+    my $txt;
+    ($txt,$data) = split("\n", $data, 2);
+    $txt =~ s/\r//;
+    if (my $msg = parseMsg($txt)) {
+      Log3 ($name,5,"MYSENSORS Read: ".dumpMsg($msg));
+
+      if ($msg->{ack}) {
+        onAcknowledge($hash,$msg);
+      }
+
+      my $type = $msg->{cmd};
+      MESSAGE_TYPE: {
+        $type == C_PRESENTATION and do {
+          onPresentationMsg($hash,$msg);
+          last;
+        };
+        $type == C_SET and do {
+          onSetMsg($hash,$msg);
+          last;
+        };
+        $type == C_REQ and do {
+          onRequestMsg($hash,$msg);
+          last;
+        };
+        $type == C_INTERNAL and do {
+          onInternalMsg($hash,$msg);
+          last;
+        };
+        $type == C_STREAM and do {
+          onStreamMsg($hash,$msg);
+          last;
+        };
+      }
+    } else {
+      Log3 ($name,5,"MYSENSORS Read: ".$txt."is no parsable mysensors message");
+    }
+  }
+  $hash->{PARTIAL} = $data;
+  return undef;
+};
+
+sub onPresentationMsg($$) {
+  my ($hash,$msg) = @_;
+  my $client = matchClient($hash,$msg);
+  my $clientname;
+  my $sensorType = $msg->{subType};
+  unless ($client) {
+    if ($hash->{'inclusion-mode'}) {
+      $clientname = "MYSENSOR_$msg->{radioId}";
+      CommandDefine(undef,"$clientname MYSENSORS_DEVICE $msg->{radioId}");
+      $client = $main::defs{$clientname};
+      return unless ($client);
+    } else {
+      Log3($hash->{NAME},3,"MYSENSORS: ignoring presentation-msg from unknown radioId $msg->{radioId}, childId $msg->{childId}, sensorType $sensorType");
+      return;
+    }
+  }
+  MYSENSORS::DEVICE::onPresentationMessage($client,$msg);
+};
+
+sub onSetMsg($$) {
+  my ($hash,$msg) = @_;
+  if (my $client = matchClient($hash,$msg)) {
+    MYSENSORS::DEVICE::onSetMessage($client,$msg);
+  } else {
+    Log3($hash->{NAME},3,"MYSENSORS: ignoring set-msg from unknown radioId $msg->{radioId}, childId $msg->{childId} for ".variableTypeToStr($msg->{subType}));
+  }
+};
+
+sub onRequestMsg($$) {
+  my ($hash,$msg) = @_;
+  if (my $client = matchClient($hash,$msg)) {
+    MYSENSORS::DEVICE::onRequestMessage($client,$msg);
+  } else {
+    Log3($hash->{NAME},3,"MYSENSORS: ignoring req-msg from unknown radioId $msg->{radioId}, childId $msg->{childId} for ".variableTypeToStr($msg->{subType}));
+  }
+};
+
+sub onInternalMsg($$) {
+  my ($hash,$msg) = @_;
+  my $address = $msg->{radioId};
+  my $type = $msg->{subType};
+  if ($address == 0 or $address == 255) { #msg to or from gateway
+    TYPE: {
+      $type == I_INCLUSION_MODE and do {
+        if (AttrVal($hash->{NAME},"autocreate",0)) { #if autocreate is switched on, keep gateways inclusion-mode active
+          if ($msg->{payload} == 0) {
+            sendMessage($hash,radioId => $msg->{radioId}, childId => $msg->{childId}, ack => 0, subType => I_INCLUSION_MODE, payload => 1);
+          }
+        } else {
+          $hash->{'inclusion-mode'} = $msg->{payload};
+        }
+        last;
+      };
+      $type == I_GATEWAY_READY and do {
+        readingsSingleUpdate($hash,'connection','startup complete',1);
+        GP_ForallClients($hash,sub {
+          my $client = shift;
+          MYSENSORS::DEVICE::onGatewayStarted($client);
+        });
+        last;
+      };
+      $type == I_VERSION and do {
+        $hash->{version} = $msg->{payload};
+        last;
+      };
+      $type == I_LOG_MESSAGE and do {
+        Log3($hash->{NAME},5,"MYSENSORS gateway $hash->{NAME}: $msg->{payload}");
+        last;
+      };
+      $type == I_ID_REQUEST and do {
+        if ($hash->{'inclusion-mode'}) {
+          my %nodes = map {$_ => 1} (AttrVal($hash->{NAME},"first-sensorid",20) ... AttrVal($hash->{NAME},"last-sensorid",254));
+          GP_ForallClients($hash,sub {
+            my $client = shift;
+            delete $nodes{$client->{radioId}};
+          });
+          if (keys %nodes) {
+            my $newid = (sort keys %nodes)[0];
+            sendMessage($hash,radioId => 255, childId => 255, cmd => C_INTERNAL, ack => 0, subType => I_ID_RESPONSE, payload => $newid);
+            Log3($hash->{NAME},4,"MYSENSORS $hash->{NAME} assigned new nodeid $newid");
+          } else {
+            Log3($hash->{NAME},4,"MYSENSORS $hash->{NAME} cannot assign new nodeid");
+          }
+        } else {
+          Log3($hash->{NAME},4,"MYSENSORS: ignoring id-request-msg from unknown radioId $msg->{radioId}");
+        }
+        last;
+      };
+    }
+  } elsif (my $client = matchClient($hash,$msg)) {
+    MYSENSORS::DEVICE::onInternalMessage($client,$msg);
+  } else {
+    Log3($hash->{NAME},3,"MYSENSORS: ignoring internal-msg from unknown radioId $msg->{radioId}, childId $msg->{childId} for ".internalMessageTypeToStr($msg->{subType}));
+  }
+};
+
+sub onStreamMsg($$) {
+  my ($hash,$msg) = @_;
+};
+
+sub onAcknowledge($$) {
+  my ($hash,$msg) = @_;
+  my $ack;
+  if (defined (my $outstanding = $hash->{messagesForRadioId}->{$msg->{radioId}}->{messages})) {
+    my @remainMsg = grep {
+         $_->{childId} != $msg->{childId}
+      or $_->{cmd}     != $msg->{cmd}
+      or $_->{subType} != $msg->{subType}
+      or $_->{payload} ne $msg->{payload}
+    } @$outstanding;
+    if ($ack = @remainMsg < @$outstanding) {
+      $hash->{outstandingAck} -= 1;
+      @$outstanding = @remainMsg;
+    }
+    $hash->{messagesForRadioId}->{$msg->{radioId}}->{numtries} = 1;
+  }
+  Log3 ($hash->{NAME},4,"MYSENSORS Read: unexpected ack ".dumpMsg($msg)) unless $ack;
+}
+
+sub sendMessage($%) {
+  my ($hash,%msg) = @_;
+  $msg{ack} = $hash->{ack} unless defined $msg{ack};
+  my $txt = createMsg(%msg);
+  Log3 ($hash->{NAME},5,"MYSENSORS send: ".dumpMsg(\%msg));
+  DevIo_SimpleWrite($hash,"$txt\n",undef);
+  if ($msg{ack}) {
+    my $messagesForRadioId = $hash->{messagesForRadioId}->{$msg{radioId}};
+    unless (defined $messagesForRadioId) {
+      $messagesForRadioId = {
+        lastseen => -1,
+        numtries => 1,
+        messages => [],
+      };
+      $hash->{messagesForRadioId}->{$msg{radioId}} = $messagesForRadioId;
+    }
+    my $messages = $messagesForRadioId->{messages};
+    @$messages = grep {
+         $_->{childId} != $msg{childId}
+      or $_->{cmd}     != $msg{cmd}
+      or $_->{subType} != $msg{subType}
+    } @$messages;
+    push @$messages,\%msg;
+
+    $messagesForRadioId->{nexttry} = gettimeofday()+$messagesForRadioId->{numtries};
+    _scheduleTimer($hash);
+  }
+};
+
+sub _scheduleTimer($) {
+  my ($hash) = @_;
+  $hash->{outstandingAck} = 0;
+  RemoveInternalTimer($hash);
+  my $next;
+  foreach my $radioid (keys %{$hash->{messagesForRadioId}}) {
+    my $msgsForId = $hash->{messagesForRadioId}->{$radioid};
+    $hash->{outstandingAck} += @{$msgsForId->{messages}};
+    $next = $msgsForId->{nexttry} unless (defined $next and $next < $msgsForId->{nexttry});
+  };
+  InternalTimer($next, "MYSENSORS::Timer", $hash, 0) if (defined $next);
+}
+
+sub matchClient($$) {
+  my ($hash,$msg) = @_;
+  my $radioId = $msg->{radioId};
+  my $found;
+  GP_ForallClients($hash,sub {
+    return if $found;
+    my $client = shift;
+    if ($client->{radioId} == $radioId) {
+      $found = $client;
+    }
+  });
+  return $found;
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="MYSENSORS"></a>
+<h3>MYSENSORS</h3>
+<ul>
+  <p>connects fhem to <a href="http://MYSENSORS.org">MYSENSORS</a>.</p>
+  <p>A single MYSENSORS device can serve multiple <a href="#MYSENSORS_DEVICE">MYSENSORS_DEVICE</a> clients.<br/>
+     Each <a href="#MYSENSORS_DEVICE">MYSENSORS_DEVICE</a> represents a mysensors node.<br/>
+  <a name="MYSENSORSdefine"></a>
+  <p><b>Define</b></p>
+  <ul>
+    <p><code>define &lt;name&gt; MYSENSORS &lt;serial device&gt|&lt;ip:port&gt;</code></p>
+    <p>Specifies the MYSENSORS device.</p>
+  </ul>
+  <a name="MYSENSORSset"></a>
+  <p><b>Set</b></p>
+  <ul>
+    <li>
+      <p><code>set &lt;name&gt; connect</code><br/>
+         (re-)connects the MYSENSORS-device to the MYSENSORS-gateway</p>
+    </li>
+    <li>
+      <p><code>set &lt;name&gt; disconnect</code><br/>
+         disconnects the MYSENSORS-device from the MYSENSORS-gateway</p>
+    </li>
+    <li>
+      <p><code>set &lt;name&gt; inclusion-mode on|off</code><br/>
+         turns the gateways inclusion-mode on or off</p>
+    </li>
+  </ul>
+  <a name="MYSENSORSattr"></a>
+  <p><b>Attributes</b></p>
+  <ul>
+    <li>
+      <p><code>att &lt;name&gt; autocreate</code><br/>
+         enables auto-creation of MYSENSOR_DEVICE-devices on receival of presentation-messages</p>
+    </li>
+    <li>
+      <p><code>att &lt;name&gt; requestAck</code><br/>
+         request acknowledge from nodes.<br/>
+         if set the Readings of nodes are updated not before requested acknowledge is received<br/>
+         if not set the Readings of nodes are updated immediatly (not awaiting the acknowledge).
+         May also be configured for individual nodes if not set for gateway.</p>
+    </li>
+    <li>
+      <p><code>att &lt;name&gt; first-sensorid <&lt;number &lth; 255&gt;></code><br/>
+         configures the lowest node-id assigned to a mysensor-node on request (defaults to 20)</p>
+    </li>
+  </ul>
+</ul>
+
+=end html
+=cut

+ 650 - 0
fhem/core/FHEM/00_NetzerI2C.pm

@@ -0,0 +1,650 @@
+##############################################
+# $Id: 00_NetzerI2C.pm 12059 2016-08-22 21:14:59Z klauswitt $
+package main;
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+sub NetzerI2C_Attr(@);
+sub NetzerI2C_HandleQueue($);
+sub NetzerI2C_Read($);
+#sub NetzerI2C_Ready($);
+sub NetzerI2C_Write($$);
+
+#my $clientsI2C = ":I2C_PCF8574:I2C_PCA9532:I2C_BMP180:FHT.*:";
+
+#my %matchListI2C = (
+#    "1:I2C_PCF8574"=> ".*",
+#    "2:FHT"       => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..",
+#);
+
+my @clients = qw(
+I2C_LCD
+I2C_DS1307
+I2C_PC.*
+I2C_MCP.*
+I2C_BME280
+I2C_BMP180
+I2C_SHT21
+I2C_TSL2561
+);
+
+sub NetzerI2C_Initialize($) {
+  my ($hash) = @_;
+  
+  require "$attr{global}{modpath}/FHEM/DevIo.pm";
+
+# Provider
+	$hash->{Clients} = join (':',@clients);
+  $hash->{ReadFn}   = "NetzerI2C_Read";		#wird von der globalen loop aufgerufen (ueber $hash->{FD} gefunden), wenn Daten verfuegbar sind 
+  $hash->{WriteFn}  = "NetzerI2C_Write";    #wird vom client per IOWrite($@) aufgerufen
+  $hash->{ReadyFn}  = "NetzerI2C_Ready";
+  $hash->{I2CWrtFn} = "NetzerI2C_Write";    #zum testen als alternative fuer IOWrite
+
+# Normal devices
+  $hash->{DefFn}   = "NetzerI2C_Define";
+  $hash->{UndefFn} = "NetzerI2C_Undef";
+  $hash->{GetFn}   = "NetzerI2C_Get";
+  $hash->{SetFn}   = "NetzerI2C_Set";
+	$hash->{NotifyFn}= "NetzerI2C_Notify";
+  $hash->{AttrFn}  = "NetzerI2C_Attr";
+  $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 " .
+                     "timeout socat:1,0";
+}
+#####################################
+sub NetzerI2C_Define($$) {					#
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  unless(@a == 3) {
+    my $msg = "wrong syntax: define <name> NetzerI2C {none | hostname:port}";
+    Log3 undef, 2, $msg;
+    return $msg;
+  }
+
+  DevIo_CloseDev($hash);
+
+  my $name = $a[0];
+  my $dev = $a[2];
+
+  #$hash->{Clients} = $clientsI2C;
+  #$hash->{MatchList} = \%matchListI2C;
+
+  if($dev eq "none") {
+    Log3 $name, 1, "$name device is none, commands will be echoed only";
+    $attr{$name}{dummy} = 1;
+    return undef;
+  }
+  $hash->{DeviceName} = $dev;
+  #my $ret = DevIo_OpenDev($hash, 0, "CUL_DoInit");
+  my $ret = DevIo_OpenDev($hash, 0, "");
+  return $ret;
+}
+#####################################
+sub NetzerI2C_Notify {							#
+  my ($hash,$dev) = @_;
+  my $name = $hash->{NAME};
+  my $type = $hash->{TYPE};
+  if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
+   	NetzerI2C_forall_clients($hash,\&NetzerI2C_Init_Client,undef);;
+  } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
+  }
+}
+#####################################
+sub NetzerI2C_forall_clients($$$) {	#
+  my ($hash,$fn,$args) = @_;
+  foreach my $d ( sort keys %main::defs ) {
+    if ( defined( $main::defs{$d} )
+      && defined( $main::defs{$d}{IODev} )
+      && $main::defs{$d}{IODev} == $hash ) {
+       &$fn($main::defs{$d},$args);
+    }
+  }
+  return undef;
+}
+#####################################
+sub NetzerI2C_Init_Client($@) {			#
+	my ($hash,$args) = @_;
+	if (!defined $args and defined $hash->{DEF}) {
+		my @a = split("[ \t][ \t]*", $hash->{DEF});
+		$args = \@a;
+	}
+	my $name = $hash->{NAME};
+	Log3 $name,1,"im init client fuer $name "; 
+	my $ret = CallFn($name,"InitFn",$hash,$args);
+	if ($ret) {
+		Log3 $name,2,"error initializing '".$hash->{NAME}."': ".$ret;
+	}
+}
+#####################################
+sub NetzerI2C_Undef($$) {						#
+  my ($hash, $arg) = @_;
+  my $name = $hash->{NAME};
+
+  foreach my $d (sort keys %defs) {
+    if(defined($defs{$d}) &&
+       defined($defs{$d}{IODev}) &&
+       $defs{$d}{IODev} == $hash)
+      {
+        Log3 $name, 3, "deleting port for $d";
+        delete $defs{$d}{IODev};
+      }
+  }
+  DevIo_CloseDev($hash); 
+  return undef;
+}
+#####################################
+sub NetzerI2C_Set($@) {							#
+  my ($hash, @a) = @_;
+  my $name = shift @a;
+  my $type = shift @a;
+  my $arg = join(" ", @a);
+  my @sets = ('writeByte', 'writeByteReg', 'writeBlock');
+	
+  if($type eq "writeByte") {
+    return "Usage: set $name i2csend <i2caddress> [<register address>] <data> [...<data>]"
+        if(!$arg || $arg !~ /^[0-7][0-9A-F](\W[0-9A-F][0-9A-F]){0,64}$/xi);
+		foreach (@a) {
+			$_ = hex;
+		}
+		my $i2caddr = shift @a;
+    my %sendpackage = ( i2caddress => $i2caddr, direction => "i2cwrite", data => join(" ", @a), direct=>1 );
+		Log3 $hash, 1, "$sendpackage{data}";
+		NetzerI2C_Write($hash, \%sendpackage);
+  } elsif($type eq "writeByteReg") {
+		return "Usage: set $name writeByteReg <i2caddress> <register address> <data> [...<data>]"
+        if(!$arg || $arg !~ /^[0-7][0-9A-F](\W[0-9A-F][0-9A-F]){0,64}$/xi);
+		foreach (@a) {
+			$_ = hex;
+		}
+		my $i2caddr = shift @a;
+		my $reg = shift @a;
+		my %sendpackage = ( i2caddress => $i2caddr, direction => "i2cwrite", reg => $reg, data => join(" ", @a), direct=>1 );
+		NetzerI2C_Write($hash, \%sendpackage);
+	} elsif($type eq "writeBlock") {
+		return "Usage: set $name writeBlock <i2caddress> <register address> <data> [...<data>]"
+        if(!$arg || $arg !~ /^[0-7][0-9A-F](\W[0-9A-F][0-9A-F]){0,64}$/xi);
+		foreach (@a) {
+			$_ = hex;
+		}
+		my $i2caddr = shift @a;
+		my $reg = shift @a;
+		my %sendpackage = ( i2caddress => $i2caddr, direction => "i2cblockwrite", reg => $reg, data => join(" ", @a), direct=>1 );
+		NetzerI2C_Write($hash, \%sendpackage);
+	} else {
+    return "Unknown argument $type, choose one of " . join(" ", @sets);
+  }
+  return undef;
+}
+#####################################
+sub NetzerI2C_Get($@) {							#
+  my ($hash, @a) = @_;
+  my $nargs = int(@a);
+  my $name = $hash->{NAME};
+  my @gets = ('read');
+  unless ( exists($a[1]) && $a[1] ne "?" && grep {/^$a[1]$/} @gets ) { 
+		return "Unknown argument $a[1], choose one of " . join(" ", @gets);
+  }
+  
+  my ($msg, $err);
+  return "No $a[1] for dummies" if(IsDummy($name));
+  
+  if ($a[1] eq "read") {
+    return "use: \"get $name $a[1] <i2cAddress> [<RegisterAddress> [<Number od bytes to get>]]\"" if(@a < 3);  
+    return "$name I2C address must be 2-digit hexvalues"    unless ($a[2] =~ /^(0x|)(|[0-7])[0-9A-F]$/xi);  # && hex($a[2]) % 2 == 0);
+		return "$name register address must be a hexvalues" if (defined($a[3]) && $a[3] !~ /^(0x|)[0-9A-F]{1,2}$/xi);
+		return "$name number of bytes must be decimal value"      if (defined($a[4]) && $a[4] !~ /^[0-9]{1,2}$/ && $a[4] < 65);
+	
+		my $hmsg = chr( (hex( $a[2] ) << 1) + 1 );								#I2C Adresse (read) in Zeichen wandeln
+    if ( $a[3] ) {  																					#Registeradresse in Hexwerte wandeln
+	    $hmsg .= chr( hex("5C")  ) if ( (hex($a[3])) == "00"); 	#wenn 0x00 gesendet mit 0x5C escapen
+	    $hmsg .= chr( hex($a[3]) );
+    }	
+		if ( $a[4] ) {  
+			for(my $n=1; $n<$a[4]; $n++) {						#Fuer jedes zu lesende Byte ein Byte rausschicken
+				$hmsg .= chr( hex("01") );
+			}
+    }
+    $hmsg .= chr( hex("00") );  							#Endezeichen anhaengen
+    #nur zum testen mit socat#######################
+    $hmsg =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg if ( AttrVal($hash->{NAME}, 'socat', 0) == 1 );
+		################################################
+		DevIo_SimpleWrite($hash, $hmsg, undef);
+	
+		my $buf = undef;
+		my $timeout = 10;
+		return $hash->{NAME} . " disconnected" unless $hash->{FD};
+		for(;;) {												#Werte direkt lesen (mit Timeout)
+      my $rin = "";
+      vec($rin, $hash->{FD}, 1) = 1;
+      my $nfound = select($rin, undef, undef, $timeout);
+      last if($nfound <= 0);
+      my $r = DevIo_DoSimpleRead($hash);
+      if(!defined($r) || $r ne "") {
+				$buf = $r;
+				last;
+			}
+    }
+		if ($buf) {
+			if ( AttrVal($hash->{NAME}, 'socat', 0) == 0 ) {
+				$buf =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg;					#empfangene Zeichen in Hexwerte wandeln (fuer Socat auskommentieren)
+      } else {
+				chomp($buf);		#weg nach testen mit Socat
+				$buf = uc($buf);	#weg nach testen mit Socat
+			}
+			my @abuf = split (/ /,$buf);
+      for (my $i = 1; $i < (defined($a[3])? 3 : 2 ) ; $i++) {	#pruefen, ob jedes gesendete Byte ein positives Ack bekommen hat
+				return "error, no Ack received for $a[$1]; received: $buf" if $abuf[0] ne "FF";
+				shift(@abuf);
+			}
+			my $rmsg = undef;
+			my $nrec = int(@abuf);
+			for (my $j = 0; $j < $nrec ; $j++) {							#escape Zeichen fuer 0x00 entfernen
+				$rmsg .= " " if (defined($rmsg));
+				$rmsg .= $abuf[$j] unless( $abuf[$j] eq "5C" && defined($abuf[$j + 1]) && $abuf[$j + 1] eq "00" );
+			}
+			$buf = $rmsg;
+		} else {
+			$buf = "no Message received";
+		}
+    return $buf;
+  } 
+  #$hash->{READINGS}{$a[1]}{VAL} = $msg;
+  #$hash->{READINGS}{$a[1]}{TIME} = TimeNow();
+  #return "$a[0] $a[1] => $msg";
+  return undef;
+}
+#####################################
+sub NetzerI2C_DoInit($) { 					#ausfuehren beim start von devio evtl. loeschen oder reinit von clienten reinbauen
+  my $hash = shift;
+  my $name = $hash->{NAME};
+  # Reset the counter
+  delete($hash->{XMIT_TIME});
+  delete($hash->{NR_CMD_LAST_H});
+  return undef;
+}
+#####################################
+sub NetzerI2C_Write($$) { 					#wird vom Client aufgerufen
+  my ($hash, $clientmsg) = @_;
+  foreach my $av (keys %{$clientmsg}) { Log3 $hash, 5, "$hash->{NAME} vom Clienten: $av= " . $clientmsg->{$av}; }
+  if ($clientmsg->{direction} && $clientmsg->{i2caddress}) {
+    if(!$hash->{QQUEUE} || 0 == scalar(@{$hash->{QQUEUE}})) {
+      $hash->{QQUEUE} = [ $clientmsg ];
+      NetzerI2C_SendFromQueue($hash, $clientmsg);
+    } else {
+      push(@{$hash->{QQUEUE}}, $clientmsg);
+    }
+  }  
+  return undef;
+}
+#####################################
+sub NetzerI2C_SendFromQueue($$) {		#
+  my ($hash, $clientmsg) = @_;
+  my $name = $hash->{NAME};
+  	
+  my (@msg,@adata) = ();
+  
+  @adata = split(/ /,$clientmsg->{data}) if defined($clientmsg->{data});
+	
+	if (defined($clientmsg->{reg}) && ($clientmsg->{direction} eq "i2cwrite" && int(@adata) > 1) 
+	        || ($clientmsg->{nbyte} && $clientmsg->{nbyte} > 1)) {		#klaeren, ob Register sequentiell geschrieben werden
+		$clientmsg->{smsg} = ( $clientmsg->{direction} eq "i2cwrite" ? int(@adata) : $clientmsg->{nbyte} ) if !$clientmsg->{smsg};
+		$clientmsg->{smsgcnt}++;
+		push(@msg, $clientmsg->{reg} + $clientmsg->{smsgcnt} - 1 ) if ($clientmsg->{reg});		#Registeradresse hochzaehlen wenn vorhanden
+		push(@msg, $adata[$clientmsg->{smsgcnt} - 1]) if ($clientmsg->{direction} eq "i2cwrite"); 
+		Log3 $hash, 5, $clientmsg->{direction} . " Nachricht zerteilen: ". ( defined($clientmsg->{data}) ? $clientmsg->{data} : "leer" ) ." Teil Nr: " .$clientmsg->{smsgcnt} ." = ". $clientmsg->{smsg};
+	} else {																																												#oder alle auf einmal
+		Log3 $hash, 5, $clientmsg->{direction} . " Nachricht nicht zerteilen: ". ( defined($clientmsg->{data}) ? $clientmsg->{data} : "leer" ) ." Nbytes: " . int(@adata);
+		push(@msg, $clientmsg->{reg} ) if defined($clientmsg->{reg});
+		push(@msg, @adata);
+	}
+	
+  my $hmsg = chr(  ( $clientmsg->{i2caddress} << 1 ) + (($clientmsg->{direction} eq "i2cread")? 1 : 0) );
+  if ( int(@msg) > 0 ) {  
+		foreach (@msg) {																			#Daten in Zeichen wandeln
+			$hmsg .= chr( hex("5C") ) if ( $_ == hex("00") ); 	#wenn 0x00 gesendet mit 0x5C escapen
+			$hmsg .= chr( $_ );
+		}
+  }
+  $hmsg .= chr( hex("00") );  														#Endezeichen anhaengen
+	
+#nur zum Testen########
+  $clientmsg->{bytecount} = int(@msg) + 1;								#Anzahl Nutzdaten + Adressbyte
+  (my $smsg = $hmsg) =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg;
+  Log3 $hash, 5, "$name SendFromQueue: $clientmsg->{direction}, String: $smsg, Hex: $hmsg, NBytes: $clientmsg->{bytecount}";
+#######################
+  #DevIo_SimpleWrite($hash, $hmsg, undef);
+  DevIo_SimpleWrite($hash, AttrVal($hash->{NAME}, 'socat', 0) == 1 ? $smsg : $hmsg, undef); #fuer Socat zum testen
+  NetzerI2C_InternalTimer("RecvTimeout", gettimeofday() + AttrVal($hash->{NAME}, 'timeout', 10), "NetzerI2C_TransceiveTimeout", $hash, 0);
+}
+#####################################
+sub NetzerI2C_HandleQueue($) {			#
+  my $hash = shift;
+  my $arr = $hash->{QQUEUE};
+  if(defined($arr) && @{$arr} > 0) {
+		shift(@{$arr}) unless $arr->[0]->{smsg} && $arr->[0]->{smsg} > $arr->[0]->{smsgcnt};  #nur auf naechste Botschaft wechseln wenn alle Byte gesendet wurden
+		if(@{$arr} == 0) {
+			delete($hash->{QQUEUE});
+			return;
+		}
+		my $clientmsg = $arr->[0];
+		if(defined($clientmsg) && $clientmsg eq "") {
+			NetzerI2C_HandleQueue($hash) if defined($hash);
+		} else {
+			NetzerI2C_SendFromQueue($hash, $clientmsg);
+		}
+  }
+}
+#####################################
+sub NetzerI2C_TransceiveTimeout($) {#
+  #my $hash = shift;
+  #Hash finden wenn myinternaltimer genutzt wird#
+  my ($myHash) = @_;														#
+  my $hash = $myHash->{HASH};										#
+  ###############################################
+  my $name = $hash->{NAME};
+  Log3 $hash, 1, "$name: Timeout I2C response";
+	my $arr = $hash->{QQUEUE};
+	delete $arr->[0]->{smsg} if $arr->[0]->{smsg}; 
+  NetzerI2C_HandleQueue($hash);
+}
+#####################################
+sub NetzerI2C_Read($) {							# called from the global loop, when the select for hash->{FD} reports data
+  my ($hash) = @_;
+  my $buf = DevIo_SimpleRead($hash);
+	return undef if(!defined($buf));					#Aendern????
+	#Log3 $hash, 1, "$hash->{NAME} vom I2C empfangen 1: $buf";
+	#hier noch abfangen, wenn $buf leer ist
+  if ( AttrVal($hash->{NAME}, 'socat', 0) == 1 ) { 			#weg nach testen mit Socat
+		chomp($buf);
+		#$buf = hex($buf);
+	} else {
+		$buf =~ s/(.|\n)/sprintf("%.2X ",ord($1))/eg				#empfangene Zeichen in Hexwerte wandeln -> in wandlung nach Zahl aendern
+	}
+	Log3 $hash, 5, "$hash->{NAME} vom I2C empfangen: $buf";
+  my @abuf = split (/ /,$buf);
+	foreach (@abuf) {																			#weg wenn Zeichen direkt gewandelt werden
+		$_ = hex;
+		#Log3 $hash, 1, "$hash->{NAME} vom I2C: $_";
+	}
+  my $name = $hash->{NAME};
+  #Log3 $hash, 1, "$hash->{NAME} vom I2C empfangen: $buf";
+
+  my $arr = $hash->{QQUEUE};
+  if(defined($arr) && @{$arr} > 0) {
+    my $clientmsg = $arr->[0];
+		NetzerI2C_RemoveInternalTimer("RecvTimeout", $hash);
+		my $status = "Ok";
+    for (my $i = 0; $i < $clientmsg->{bytecount} ; $i++) {	#pruefen, ob jedes gesendete Byte ein positives Ack (FF) bekommen hat
+			$status = "error" . ($arr->[0]->{smsg} ? "@ reg: ". sprintf("%.2X ",($clientmsg->{reg} + $clientmsg->{smsgcnt} - 1)) :"") if !defined($abuf[0]) || $abuf[0] != 255;
+			shift(@abuf);
+		}
+		my $rmsg = undef;
+		my $nrec = int(@abuf);
+		for (my $i = 0; $i < $nrec ; $i++) {					#escape Zeichen (0x5C) fuer 0x00 entfernen
+			$rmsg .= " " if (defined($rmsg));
+			#$rmsg .= $abuf[$i] unless( $abuf[$i] eq "5C" && defined($abuf[$i + 1]) && $abuf[$i + 1] eq "00" );
+			$rmsg .= $abuf[$i] unless( $abuf[$i] == 92 && defined($abuf[$i + 1]) && $abuf[$i + 1] == 0 );
+		}
+		
+		if ( $arr->[0]->{smsg} && defined($rmsg) ) {									#wenn Nachricht Teil einer Nachrichtenfolge, dann Daten anhaengen
+			$clientmsg->{received} .= ( defined($arr->[0]->{smsg}) && $arr->[0]->{smsg} == 1 ? "" : " ") . $rmsg;
+		} else {
+			$clientmsg->{received} = $rmsg;
+		}
+		unless ( $arr->[0]->{smsg} && $arr->[0]->{smsg} > $arr->[0]->{smsgcnt} && $status eq "Ok" ) {	#erst senden, wenn Transfer abgeschlossen oder bei Fehler
+		delete $arr->[0]->{smsg} if $arr->[0]->{smsg} && $status ne "Ok";				#aktuellen Einzeltransfer durch loeschen der Botschaftszahl abbrechen
+			#$clientmsg->{received} = $rmsg if defined($rmsg);
+			$clientmsg->{$name . "_" . "RAWMSG"} = $buf;
+			$clientmsg->{$name . "_" . "SENDSTAT"} = $status;
+			if ($clientmsg->{direct}) {																							#Vorgang wurde von diesem Modul ausgeloest
+				$hash->{direct_send}    = $clientmsg->{data};
+				$hash->{direct_answer}  = $clientmsg->{$name . "_" . "RAWMSG"};
+				$hash->{direct_I2Caddr} = $clientmsg->{i2caddress};
+				$hash->{direct_SENDSTAT} = $status; 
+			}
+			########################################### neue Variante zum senden an client
+			foreach my $d ( sort keys %main::defs ) {				#zur Botschaft passenden Clienten ermitteln geht auf Client: I2CRecFn
+				#Log3 $hash, 1, "Clients suchen d: $d". ($main::defs{$d}{IODev}? ", IODev: $main::defs{$d}{IODev}":"") . ($main::defs{$d}{I2C_Address} ? ", I2C: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{i2caddress} ? " CI2C: $clientmsg->{i2caddress}" : "");
+				if ( defined( $main::defs{$d} )
+						&& defined( $main::defs{$d}{IODev} )    && $main::defs{$d}{IODev} == $hash
+						&& defined( $main::defs{$d}{I2C_Address} )  && defined( $clientmsg->{i2caddress} )
+						&& $main::defs{$d}{I2C_Address} eq $clientmsg->{i2caddress} ) {
+					my $chash = $main::defs{$d};
+					Log3 $hash, 5, "Client gefunden d: $d". ($main::defs{$d}{IODev}? ", IODev: $main::defs{$d}{IODev}":"") . ($main::defs{$d}{I2C_Address} ? ", I2C: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{i2caddress} ? " CI2C: $clientmsg->{i2caddress}" : "");
+					CallFn($d, "I2CRecFn", $chash, $clientmsg);
+				}
+			}
+			######################################## alte Variante ueber Dispatch ######################
+			#	  my $dir = $clientmsg->{direction};																										#
+			#	  my $sid = $clientmsg->{id};																														#
+			#      if($dir eq "i2cread" || $dir eq "i2cwrite") {																			#
+			#		my $dev = $clientmsg->{i2caddress};																										#
+			#		my %addvals = (RAWMSG => $buf, SENDSTAT => $status);																	#
+			#		$rmsg = ( defined($rmsg) ? ($sid . " " . $dev . " " . $rmsg) : ($sid . " " . $dev) );	#
+			#       Log 1, "wird an Client geschickt: $rmsg";																					#
+			#       Dispatch($hash, $rmsg, \%addvals);																								#
+			#	   }																																										#
+			###########################################################################################
+			undef $clientmsg; #Hash loeschen nachdem Daten verteilt wurden
+		}
+		NetzerI2C_HandleQueue($hash);	
+  } else {
+		Log3 $hash, 1, "$name: unknown data received: $buf";
+  }
+}
+#####################################
+sub NetzerI2C_Ready($) {############# kann geloescht werden?
+  my ($hash) = @_;
+
+  return DevIo_OpenDev($hash, 1, "")
+                if($hash->{STATE} eq "disconnected");
+
+  # This is relevant for windows/USB only
+  my $po = $hash->{USBDev};
+  my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
+  if($po) {
+    ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
+  }
+  return ($InBytes && $InBytes>0);
+}
+#####################################
+sub NetzerI2C_Attr(@) {							#
+  my ($cmd,$name,$aName,$aVal) = @_;
+  my $msg = undef;
+  if($aName eq "timeout") {
+    if ( defined($aVal) ) {
+    unless ( looks_like_number($aVal) && $aVal >= 0.1 && $aVal <= 20 ) {
+	  $msg = "$name: Wrong $aName defined. Value must be a number between 0.1 and 20";
+    }    
+   } 
+  } 
+  return $msg;
+}
+##################################### 
+sub NetzerI2C_InternalTimer($$$$$) {#(von Dietmar63)
+   my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_;
+
+   my $mHash;
+   if ($modifier eq "") {
+      $mHash = $hash;
+   } else {
+      my $timerName = "$hash->{NAME}_$modifier";
+      if (exists  ($hash->{TIMER}{$timerName})) {
+          $mHash = $hash->{TIMER}{$timerName};
+      } else {
+          $mHash = { HASH=>$hash, NAME=>"$hash->{NAME}_$modifier", MODIFIER=>$modifier};
+          $hash->{TIMER}{$timerName} = $mHash;
+      }
+   }
+   InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone);
+}
+#####################################
+sub NetzerI2C_RemoveInternalTimer($$) {
+   my ($modifier, $hash) = @_;
+
+   my $timerName = "$hash->{NAME}_$modifier";
+   if ($modifier eq "") {
+      RemoveInternalTimer($hash);
+   } else {
+      my $myHash = $hash->{TIMER}{$timerName};
+      if (defined($myHash)) {
+         delete $hash->{TIMER}{$timerName};
+         RemoveInternalTimer($myHash);
+      }
+   }
+}
+
+1;
+
+=pod
+=item device
+=item summary accesses I2C interface on an Netzer
+=item summary_DE Zugriff auf das I2C-Interface einer Netzer
+=begin html
+
+<a name="NetzerI2C"></a>
+<h3>NetzerI2C</h3>
+<ul>
+	<a name="NetzerI2C"></a>
+		Provides access to <a href="http://www.mobacon.de/wiki/doku.php/en/netzer/index">Netzer's</a> I2C interfaces for some logical modules and also directly.<br><br>
+		<b>preliminary:</b><br>
+		Serial Server of Netzer must be <a href="http://www.mobacon.de/wiki/doku.php/en/netzer/serialserveraktiviert"> activated and configured for I2C	</a>.<br>
+	<a name="NetzerI2CDefine"></a><br>
+	<b>Define</b>
+	<ul>
+		<code>define &lt;name&gt; NetzerI2C &lt;Device-Address:Port&gt;</code><br>
+		where <code>&lt;Device-Address:Port&gt;</code> Device Address/ IP-Address and Serial Server TCP Port of the Netzer<br><br>
+	</ul>
+
+	<a name="NetzerI2CSet"></a>
+	<b>Set</b>
+	<ul>
+		<li>
+			Write one byte (or more bytes sequentially) directly to an I2C device (for devices that have only one register to write):<br>
+			<code>set &lt;name&gt; writeByte    &lt;I2C Address&gt; &lt;value&gt;</code><br><br>
+		</li>
+		<li>
+			Write one byte (or more bytes sequentially) to the specified register of an I2C device:<br>
+			<code>set &lt;name&gt; writeByteReg &lt;I2C Address&gt; &lt;Register Address&gt;  &lt;value&gt;</code><br><br>
+		</li>
+		<li>
+			Write n-bytes to an register range, beginning at the specified register:<br>	
+			<code>set &lt;name&gt; writeBlock   &lt;I2C Address&gt; &lt;Register Address&gt; &lt;value&gt;</code><br><br>
+		</li>
+		Examples:
+		<ul>
+			Write 0xAA to device with I2C address 0x60<br>
+			<code>set test1 writeByte 60 AA</code><br>
+			Write 0xAA to register 0x01 of device with I2C address 0x6E<br>
+			<code>set test1 writeByteReg 6E 01 AA</code><br>
+			Write 0xAA to register 0x01 of device with I2C address 0x6E, after it write 0x55 to register 0x02<br>
+			<code>set test1 writeByteReg 6E 01 AA 55</code><br>
+			Write 0xA4 to register 0x03, 0x00 to register 0x04 and 0xDA to register 0x05 of device with I2C address 0x60 as block operation<br>
+			<code>set test1 writeBlock 60 03 A4 00 DA</code><br>
+		</ul><br>
+	</ul>
+
+	<a name="NetzerI2CGet"></a>
+	<b>Get</b>
+	<ul>
+		<code>get &lt;name&gt; read &lt;I2C Address&gt; [&lt;Register Address&gt; [&lt;number of registers&gt;]] </code>
+		<br>
+		gets value of I2C device's registers<br><br>
+		Examples:
+		<ul>
+			Reads byte from device with I2C address 0x60<br>
+			<code>get test1 writeByte 60</code><br>
+			Reads register 0x01 of device with I2C address 0x6E.<br>
+			<code>get test1 read 6E 01 AA 55</code><br>
+			Reads register 0x03 to 0x06 of device with I2C address 0x60.<br>
+			<code>get test1 read 60 03 4</code><br>
+		</ul><br>
+	</ul><br>
+
+	<a name="NetzerI2CAttr"></a>
+	<b>Attributes</b>
+	<ul>
+		<li><a href="#ignore">ignore</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="NetzerI2C"></a>
+<h3>NetzerI2C</h3>
+<ul>
+	<a name="NetzerI2C"></a>
+		Erm&ouml;glicht den Zugriff auf die I2C Schnittstelle des <a href="http://www.mobacon.de/wiki/doku.php/de/netzer/index">Netzer</a>.<br> &uuml;ber logische Module. Register von I2C IC's k&ouml;nnen auch direkt gelesen und geschrieben werden.<br><br>
+		<b>Vorbereitung:</b><br>
+		Bevor dieses Modul verwendet werden kann muss der Serielle Server des Netzers <a href="http://www.mobacon.de/wiki/doku.php/de/netzer/serialserveraktiviert"> und auf I2C gestellt</a> werden.
+	<a name="NetzerI2CDefine"></a><br><br>
+	<b>Define</b>
+	<ul>
+		<code>define &lt;name&gt; NetzerI2C &lt;Device-Address:Port&gt;</code><br>
+		<code>&lt;Device-Address:Port&gt;</code> ist  die Adresse/IP-Adresse und Serial Server TCP-Port des Netzer<br><br>
+	</ul>
+
+	<a name="NetzerI2CSet"></a>
+	<b>Set</b>
+	<ul>
+		<li>
+			Schreibe ein Byte (oder auch mehrere nacheinander) direkt auf ein I2C device (manche I2C Module sind so einfach, das es nicht einmal mehrere Register gibt):<br>
+			<code>set &lt;name&gt; writeByte    &lt;I2C Address&gt; &lt;value&gt;</code><br><br>
+		</li>
+		<li>
+			Schreibe ein Byte (oder auch mehrere nacheinander) direkt auf ein Register des adressierten I2C device:<br>
+			<code>set &lt;name&gt; writeByteReg &lt;I2C Address&gt; &lt;Register Address&gt;  &lt;value&gt;</code><br><br>
+		</li>
+		<li>
+			Schreibe n-bytes auf einen Registerbereich, beginnend mit dem angegebenen Register:<br>	
+			<code>set &lt;name&gt; writeBlock   &lt;I2C Address&gt; &lt;Register Address&gt; &lt;value&gt;</code><br><br>
+		</li>
+		Beispiele:
+		<ul>
+			Schreibe 0xAA zu Modul mit I2C Addresse 0x60<br>
+			<code>set test1 writeByte 60 AA</code><br>
+			Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E<br>
+			<code>set test1 writeByteReg 6E 01 AA</code><br>
+			Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E, schreibe danach 0x55 zu Register 0x01<br>
+			<code>set test1 writeByteReg 6E 01 AA 55</code><br>
+			Schreibe 0xA4 zu Register 0x03, 0x00 zu Register 0x04 und 0xDA zu Register 0x05 des Moduls mit der I2C Adresse 0x60 als Block<br>
+			<code>set test1 writeBlock 60 03 A4 00 DA</code><br>
+
+		</ul><br>
+	</ul>
+
+	<a name="NetzerI2CGet"></a>
+	<b>Get</b>
+	<ul>
+		<code>get &lt;name&gt; read &lt;I2C Address&gt; [&lt;Register Address&gt; [&lt;number of registers&gt;]] </code>
+		<br>
+		Auslesen der Registerinhalte des I2C Moduls<br><br>
+		Examples:
+		<ul>
+			Lese Byte vom Modul mit der I2C Adresse 0x60<br>
+			<code>get test1 writeByte 60</code><br>
+			Lese den Inhalt des Registers 0x01 vom Modul mit der I2C Adresse 0x6E.<br>
+			<code>get test1 read 6E 01 AA 55</code><br>
+			Lese den Inhalt des Registerbereichs 0x03 bis 0x06 vom Modul mit der I2C Adresse 0x60.<br>
+			<code>get test1 read 60 03 4</code><br>
+		</ul><br>
+	</ul><br>
+
+	<a name="NetzerI2CAttr"></a>
+	<b>Attribute</b>
+	<ul>
+		<li><a href="#ignore">ignore</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

ファイルの差分が大きいため隠しています
+ 2584 - 0
fhem/core/FHEM/00_OWX.pm


ファイルの差分が大きいため隠しています
+ 1284 - 0
fhem/core/FHEM/00_OWX_ASYNC.pm


+ 866 - 0
fhem/core/FHEM/00_RPII2C.pm

@@ -0,0 +1,866 @@
+##############################################
+# $Id: 00_RPII2C.pm 12566 2016-11-13 17:06:19Z klauswitt $
+package main;
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday usleep);
+#use Device::SMBus;
+
+#my $clientsI2C = ":I2C_PC.*:I2C_SHT21:I2C_MCP23017:I2C_BMP180:";
+
+my @clients = qw(
+I2C_LCD
+I2C_DS1307
+I2C_PC.*
+I2C_MCP.*
+I2C_BME280
+I2C_BMP180
+I2C_SHT21
+I2C_TSL2561
+I2C_SUSV
+);
+
+my $gpioprg = "/usr/local/bin/gpio";		#WiringPi GPIO utility
+my $I2C_SLAVE = 0x0703;									#Variable for IOCTL (set I2C slave address)
+
+#my %matchListI2C = (			#kann noch weg?
+#    "1:I2C_PCF8574"=> ".*",
+#    "2:FHT"       => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..",
+#);
+my $libcheck_SMBus = 1;
+my $check_ioctl_ph = 1;
+
+sub RPII2C_Initialize($) {
+	my ($hash) = @_;
+	eval "use Device::SMBus;";
+	$libcheck_SMBus = 0 if($@);
+	eval {require "sys/ioctl.ph"};
+	$check_ioctl_ph = 0 if($@);
+	
+# Provider
+	$hash->{Clients} = join (':',@clients);
+	#$hash->{WriteFn}  = "RPII2C_Write";    #wird vom client per IOWrite($@) aufgerufen
+	$hash->{I2CWrtFn} = "RPII2C_Write";    #zum testen als alternative fuer IOWrite
+
+# Normal devices
+	$hash->{DefFn}   = "RPII2C_Define";
+	$hash->{UndefFn} = "RPII2C_Undef";
+	$hash->{GetFn}   = "RPII2C_Get";
+	$hash->{SetFn}   = "RPII2C_Set";
+	$hash->{AttrFn}  = "RPII2C_Attr";
+	$hash->{NotifyFn} = "RPII2C_Notify";
+	$hash->{AttrList}= "do_not_notify:1,0 ignore:1,0 showtime:1,0 " .
+										 "$readingFnAttributes";
+	$hash->{AttrList} .= " useHWLib:IOCTL,SMBus " if( $libcheck_SMBus && $check_ioctl_ph);
+	$hash->{AttrList} .= " swap_i2c0:off,on";
+}
+#####################################
+sub RPII2C_Define($$) {							#
+	my ($hash, $def) = @_;
+	my @a = split("[ \t][ \t]*", $def);
+	unless(@a == 3) {
+		my $msg = "wrong syntax: define <name> RPII2C <0|1>";
+		Log3 undef, 2, $msg;
+		return $msg;
+	}
+	
+	$hash->{SMBus_exists}    = $libcheck_SMBus if($libcheck_SMBus);
+	$hash->{ioctl_ph_exists} = $check_ioctl_ph if($check_ioctl_ph);
+
+	my $name = $a[0];
+	my $dev = $a[2];
+	
+	if ($check_ioctl_ph) {
+		$hash->{hwfn} = \&RPII2C_HWACCESS_ioctl;
+	} elsif ($libcheck_SMBus) {
+		$hash->{hwfn} = \&RPII2C_HWACCESS;
+	} else {
+		return $name . ": Error! no library for Hardware access installed";
+	}
+	my $device = "/dev/i2c-".$dev;
+	if ( RPII2C_CHECK_I2C_DEVICE($device) ) {
+		Log3 $hash, 3, "$hash->{NAME}: file $device not accessible try to use gpio utility to fix it";
+		if ( defined(my $ret = RPII2C_CHECK_GPIO_UTIL($gpioprg)) ) {
+			Log3 $hash, 1, "$hash->{NAME}: " . $ret if $ret;
+		} else {													#I2C Devices mit gpio utility fuer FHEM User lesbar machen
+			my $exp = $gpioprg.' load i2c';
+			$exp = `$exp`;
+		}
+	}
+	$hash->{NOTIFYDEV} = "global";
+	
+	#$hash->{Clients} = $clientsI2C;
+	#$hash->{MatchList} = \%matchListI2C;
+
+	if($dev eq "none") {
+		Log3 $name, 1, "$name device is none, commands will be echoed only";
+		$attr{$name}{dummy} = 1;
+		return undef;
+	}
+	my $check = RPII2C_CHECK_I2C_DEVICE($device);
+	return $name . $check if $check;
+	
+	$hash->{DeviceName} = $device;
+	$hash->{STATE} = "initialized";
+	return undef;
+}
+#####################################
+sub RPII2C_Notify {									#
+	my ($hash,$dev) = @_;
+	my $name = $hash->{NAME};
+	my $type = $hash->{TYPE};
+	if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
+		RPII2C_forall_clients($hash,\&RPII2C_Init_Client,undef);;
+	} elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
+	}
+}
+#####################################
+sub RPII2C_forall_clients($$$) {		#
+	my ($hash,$fn,$args) = @_;
+	foreach my $d ( sort keys %main::defs ) {
+		if ( defined( $main::defs{$d} )
+			&& defined( $main::defs{$d}{IODev} )
+			&& $main::defs{$d}{IODev} == $hash ) {
+			 &$fn($main::defs{$d},$args);
+		}
+	}
+	return undef;
+}
+#####################################
+sub RPII2C_Init_Client($@) {				#
+	my ($hash,$args) = @_;
+	if (!defined $args and defined $hash->{DEF}) {
+		my @a = split("[ \t][ \t]*", $hash->{DEF});
+		$args = \@a;
+	}
+	my $name = $hash->{NAME};
+	Log3 $name,5,"im init client fuer $name "; 
+	my $ret = CallFn($name,"InitFn",$hash,$args);
+	if ($ret) {
+		Log3 $name,2,"error initializing '".$hash->{NAME}."': ".$ret;
+	}
+}
+#####################################
+sub RPII2C_Undef($$) {			 		   	#
+	my ($hash, $arg) = @_;
+	my $name = $hash->{NAME};
+
+	foreach my $d (sort keys %defs) {
+		if(defined($defs{$d}) &&
+			 defined($defs{$d}{IODev}) &&
+			 $defs{$d}{IODev} == $hash)
+			{
+				Log3 $name, 3, "deleting port for $d";
+				delete $defs{$d}{IODev};
+			}
+	}
+	return undef;
+}
+#####################################
+sub RPII2C_Attr(@){
+	my (undef, $name, $attr, $val) = @_;
+	my $hash = $defs{$name};
+	if ($attr && $attr eq 'useHWLib') {
+		$hash->{hwfn} = \&RPII2C_HWACCESS_ioctl if $val eq "IOCTL";
+		$hash->{hwfn} = \&RPII2C_HWACCESS if $val eq "SMBus";
+	} elsif ($attr && $attr eq 'swap_i2c0' && defined($val)) {
+		RPII2C_SWAPI2C0($hash,$val);
+	}
+	return undef;
+}
+#####################################
+sub RPII2C_Set($@) {								#writeBlock noch nicht fertig
+	my ($hash, @a) = @_;
+	my $name = shift @a;
+	my $type = shift @a;
+	my @sets = ('writeByte', 'writeByteReg', 'writeBlock', 'writeBlockReg'); #, 'writeNBlock');
+	return "Unknown argument $type, choose one of " . join(" ", @sets) if @a < 2;
+
+	foreach (@a) {																																					#Hexwerte pruefen und in Dezimalwerte wandeln
+		return "$name: $_ is no 1byte hexadecimal value" if $_ !~ /^(0x|)[0-9A-F]{1,2}$/xi ;
+		$_ = hex;
+	}
+	my $i2ca = shift @a;
+	return "$name: I2C Address not valid" unless ($i2ca > 3 && $i2ca < 128);								#pruefe auf Hexzahl zwischen 4 und 7F
+
+	my $i2chash = { i2caddress => $i2ca, direction => "i2cbytewrite", test => "local" };
+	my ($reg, $nbyte, $data) = undef;
+	if ($type eq "writeByte") {
+		$data = join(" ", @a);
+	} elsif ($type eq "writeByteReg") {
+		$reg = shift @a;
+		$data = join(" ", @a);
+	} elsif ($type eq "writeBlock") {
+		$nbyte = int(@a);
+		return "$name maximal blocksize (32byte) exeeded" if $nbyte > 32;
+		$data = join(" ", @a);
+		$i2chash->{direction} = "i2cwrite";
+	} elsif ($type eq "writeBlockReg") {
+		$reg = shift @a;
+		$nbyte = int(@a);
+		return "$name maximal blocksize (32byte) exeeded" if $nbyte > 32;
+		$data = join(" ", @a);
+		$i2chash->{direction} = "i2cwrite";
+	} else {
+		return "Unknown argument $type, choose one of " . join(" ", @sets);
+	}
+	
+	$i2chash->{reg}   = $reg if defined($reg);																			#startadresse zum lesen
+	$i2chash->{nbyte} = $nbyte if defined($nbyte);
+	$i2chash->{data} = $data if defined($data);
+	&{$hash->{hwfn}}($hash, $i2chash);
+	undef $i2chash;																																	#Hash loeschen
+	return undef;
+}
+##################################### nicht fertig!
+sub RPII2C_Get($@) {								#
+	my ($hash, @a) = @_;
+	my $nargs = int(@a);
+	my $name = $hash->{NAME};
+	my @gets = ('read','readblock','readblockreg');
+	unless ( exists($a[1]) && $a[1] ne "?" && grep {/^$a[1]$/} @gets ) { 
+	return "Unknown argument $a[1], choose one of " . join(" ", @gets);
+	}
+	if ($a[1] eq "read") {
+		return "use: \"get $name $a[1] <i2cAddress> [<RegisterAddress> [<Number od bytes to get>]]\"" if(@a < 3);
+		return "$name: I2C Address not valid"             unless (                  $a[2] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi);
+		return "$name register address must be a hexvalue" 		if (defined($a[3]) && $a[3] !~ /^(0x|)[0-9A-F]{1,4}$/xi);
+		return "$name number of bytes must be decimal value"  if (defined($a[4]) && $a[4] !~ /^[0-9]{1,2}$/);
+		my $i2chash = { i2caddress => hex($a[2]), direction => "i2cbyteread" };
+		$i2chash->{reg}   = hex($a[3]) if defined($a[3]);																			#startadresse zum lesen
+		$i2chash->{nbyte} = $a[4] if defined($a[4]);
+		#Log3 $hash, 1, "Reg: ". $i2chash->{reg};
+		#my $status = RPII2C_HWACCESS_ioctl($hash, $i2chash);
+		my $status  = &{$hash->{hwfn}}($hash, $i2chash);
+		#my $received = join(" ", @{$i2chash->{received}});															#als Array
+		my $received = $i2chash->{received};																						#als Scalar
+		undef $i2chash;																																	#Hash loeschen
+		return (defined($received) ? "received : " . $received ." | " : "" ) . " transmission: $status";	
+	} elsif ($a[1] eq "readblock") {
+		return "use: \"get $name $a[1] <i2cAddress> [<Number od bytes to get>]\"" if(@a < 3);
+		return "$name: I2C Address not valid"             unless (                  $a[2] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi);
+		return "$name number of bytes must be decimal value"  if (defined($a[3]) && $a[3] !~ /^[0-9]{1,2}$/);
+        my $i2chash = { i2caddress => hex($a[2]), direction => "i2cread" };
+        $i2chash->{nbyte} = $a[3] if defined($a[3]);
+        my $status  = &{$hash->{hwfn}}($hash, $i2chash);
+		my $received = $i2chash->{received};																						#als Scalar
+		undef $i2chash;																																	#Hash loeschen
+		return (defined($received) ? "received : " . $received ." | " : "" ) . " transmission: $status";        
+	} elsif ($a[1] eq "readblockreg") {
+		return "use: \"get $name $a[1] <i2cAddress> [<Number od bytes to get>]\"" if(@a < 2);
+		return "$name: I2C Address not valid"             unless (                  $a[2] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi);
+		return "$name register address must be a hexvalue" 		if (defined($a[3]) && $a[3] !~ /^(0x|)[0-9A-F]{1,4}$/xi);
+		return "$name number of bytes must be decimal value"  if (defined($a[4]) && $a[4] !~ /^[0-9]{1,2}$/);
+        my $i2chash = { i2caddress => hex($a[2]), direction => "i2cread" };
+				$i2chash->{reg}   = hex($a[3]) if defined($a[3]);
+        $i2chash->{nbyte} = $a[4] if defined($a[4]);
+        my $status  = &{$hash->{hwfn}}($hash, $i2chash);
+		my $received = $i2chash->{received};																						#als Scalar
+		undef $i2chash;																																	#Hash loeschen
+		return (defined($received) ? "received : " . $received ." | " : "" ) . " transmission: $status";        
+	}
+	return undef;
+}
+#####################################
+sub RPII2C_Write($$) { 							#wird vom Client aufgerufen
+	my ($hash, $clientmsg) = @_;
+	my $name = $hash->{NAME};
+	my $ankommen = "$name: vom client empfangen";
+	foreach my $av (keys %{$clientmsg}) { $ankommen .= "|" . $av . ": " . $clientmsg->{$av}; }
+	Log3 $hash, 5, $ankommen;
+	
+	if ( $clientmsg->{direction} && $clientmsg->{i2caddress} ) {
+		$clientmsg->{$name . "_" . "SENDSTAT"} = &{$hash->{hwfn}}($hash, $clientmsg);
+		#$clientmsg->{$name . "_" . "SENDSTAT"} = RPII2C_HWACCESS($hash, $clientmsg);
+	}
+	
+	foreach my $d ( sort keys %main::defs ) {				#zur Botschaft passenden Clienten ermitteln geht auf Client: I2CRecFn
+		#Log3 $hash, 1, "d: $d". ($main::defs{$d}{IODev}? ", IODev: $main::defs{$d}{IODev}":"") . ($main::defs{$d}{I2C_Address} ? ", I2C: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{i2caddress} ? " CI2C: $clientmsg->{i2caddress}" : "");
+		if ( defined( $main::defs{$d} )
+				&& defined( $main::defs{$d}{IODev} )    && $main::defs{$d}{IODev} == $hash
+				&& defined( $main::defs{$d}{I2C_Address} ) && defined($clientmsg->{i2caddress})
+								&& $main::defs{$d}{I2C_Address} eq $clientmsg->{i2caddress} ) {
+			my $chash = $main::defs{$d};
+			Log3 $hash, 5, "$name ->Client gefunden: $d". ($main::defs{$d}{I2C_Address} ? ", I2Caddress: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{data} ? " Data: $clientmsg->{data}" : "") . ($clientmsg->{received} ? " Gelesen: $clientmsg->{received}" : "");
+			CallFn($d, "I2CRecFn", $chash, $clientmsg);
+			undef $clientmsg														#Hash loeschen nachdem Daten verteilt wurden
+		}
+	}
+	return undef;
+}
+#####################################
+#FRM_forall_clients($$$)
+#{
+#  my ($hash,$fn,$args) = @_;
+#  foreach my $d ( sort keys %main::defs ) {
+#    if ( defined( $main::defs{$d} )
+#      && defined( $main::defs{$d}{IODev} )
+#      && $main::defs{$d}{IODev} == $hash ) {
+#       &$fn($main::defs{$d},$args);					#funktion mit Varianblennamen von $fn ausfuehren
+#    }
+#  }
+#  return undef;
+#}
+#####################################
+
+sub RPII2C_CHECK_I2C_DEVICE {	
+	my ($dev) = @_;
+		my $ret = undef;
+	if(-e $dev) {
+		if(-r $dev) {
+			unless(-w $dev) {
+				$ret =  ': Error! I2C device not writable: '.$dev . '. Please install wiringpi or change access rights for fhem user'; 
+			}
+		} else {
+			$ret =    ': Error! I2C device not readable: '.$dev . '. Please install wiringpi or change access rights for fhem user'; 
+		}
+	} else {
+		$ret =      ': Error! I2C device not found: '   .$dev . '. Please check kernelmodules must loaded: i2c_bcm2708, i2c_dev'; 
+	}
+	return $ret;
+}
+
+sub RPII2C_CHECK_GPIO_UTIL {
+	my ($gpioprg) = @_;
+	my $ret = undef;
+	if(-e $gpioprg) {
+		if(-x $gpioprg) {
+			unless(-u $gpioprg) {
+				$ret =  "file $gpioprg is not setuid"; 
+			}
+		} else {
+			$ret =  "file $gpioprg is not executable"; 
+		}
+	} else {
+		$ret = "file $gpioprg doesnt exist"; 
+	}
+	return $ret;
+}
+
+sub RPII2C_SWAPI2C0 {
+	my ($hash,$set) = @_;
+		unless (defined(my $ret = RPII2C_CHECK_GPIO_UTIL($gpioprg))) {
+			if (defined($set) && $set eq "on") {
+				system "$gpioprg -g mode 0 in";
+				system "$gpioprg -g mode 1 in";
+				system "$gpioprg -g mode 28 ALT0";
+				system "$gpioprg -g mode 29 ALT0";
+			} else {
+				system "$gpioprg -g mode 28 in";
+				system "$gpioprg -g mode 29 in";
+				system "$gpioprg -g mode 0 ALT0";
+				system "$gpioprg -g mode 1 ALT0";
+			}
+		} else {
+					Log3 $hash, 1, $hash->{NAME} . ": " . $ret if $ret;
+		}
+	return
+}
+
+sub RPII2C_HWACCESS($$) {
+		my ($hash, $clientmsg) = @_;
+		my $status = "error";
+		my $inh = undef;
+		Log3 $hash, 5, "$hash->{NAME}: HWaccess I2CAddr: " . sprintf("0x%.2X", $clientmsg->{i2caddress});
+		my $dev = Device::SMBus->new(
+			I2CBusDevicePath => $hash->{DeviceName},
+			I2CDeviceAddress => hex( sprintf("%.2X", $clientmsg->{i2caddress}) ),
+		);
+#		if (defined($clientmsg->{nbyte}) && defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cblockwrite") {	#blockweise beschreiben (Register)
+		if (                                defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") {				#blockweise beschreiben (Register)
+		my @data = split(" ", $clientmsg->{data});
+			my $dataref = \@data;
+			$inh = $dev->writeBlockData( $clientmsg->{reg} , $dataref );
+			my $wr = join(" ", @{$dataref});
+			Log3 $hash, 5, "$hash->{NAME}: Block schreiben Register: " . sprintf("0x%.2X", $clientmsg->{reg}) . " Inhalt: " . $wr . " N: ". int(@data) ." Returnvar.: $inh";
+			$status = "Ok" if $inh == 0;
+#		} elsif (defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") {																	#byteweise beschreiben (Register)
+		} elsif (defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cbytewrite") {															#byteweise beschreiben (Register)
+			my @data = split(" ", $clientmsg->{data});
+			foreach (@data) {
+				$inh = $dev->writeByteData($clientmsg->{reg},$_);
+				Log3 $hash, 5, "$hash->{NAME}; Register ".sprintf("0x%.2X", $clientmsg->{reg})." schreiben - Inhalt: " .sprintf("0x%.2X",$_) . " Returnvar.: $inh";
+				last if $inh != 0;
+				$status = "Ok" if $inh == 0;
+			} 
+		} elsif (defined($clientmsg->{data}) && ( $clientmsg->{direction} eq "i2cwrite" || $clientmsg->{direction} eq "i2cbytewrite" ) ) {							#Byte(s) schreiben
+			my @data = split(" ", $clientmsg->{data});
+			foreach (@data) {
+				$inh = $dev->writeByte($_);
+				Log3 $hash, 5, "$hash->{NAME} Byte schreiben; Inh: " . $_ . " Returnvar.: $inh";
+				last if $inh != 0;
+				$status = "Ok" if $inh == 0;
+			}	
+		} elsif (defined($clientmsg->{reg}) && ( $clientmsg->{direction} eq "i2cread" || $clientmsg->{direction} eq "i2cbyteread" ) ) {									#byteweise lesen (Register)
+			my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1;
+			my $rmsg = "";
+			for (my $n = 0; $n < $nbyte; $n++) {
+				$inh = $dev->readByteData($clientmsg->{reg} + $n );
+				Log3 $hash, 5, "$hash->{NAME}; Register ".sprintf("0x%.2X", $clientmsg->{reg} + $n )." lesen - Inhalt: ".sprintf("0x%.2X",$inh);
+				last if ($inh < 0);
+				#$rmsg .= sprintf("%.2X",$inh);
+				$rmsg .= $inh;
+				$rmsg .= " " if $n <= $nbyte;
+				$status = "Ok" if ($n + 1) == $nbyte;
+			}
+			#@{$clientmsg->{received}} = split(" ", $rmsg) if($rmsg);										#Daten als Array uebertragen
+			$clientmsg->{received} = $rmsg if($rmsg);																	#Daten als Scalar uebertragen
+		} elsif ($clientmsg->{direction} eq "i2cread"|| $clientmsg->{direction} eq "i2cbyteread") {																											#Byte lesen
+			my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1;
+			my $rmsg = "";
+			for (my $n = 0; $n < $nbyte; $n++) {
+				$inh = $dev->readByte();
+				Log3 $hash, 5, "$hash->{NAME} Byte lesen; Returnvar.: $inh";
+				last if ($inh < 0);
+				$rmsg .= $inh;
+				$rmsg .= " " if $n <= $nbyte;
+				$status = "Ok" if ($n + 1) == $nbyte;
+			}
+			#@{$clientmsg->{received}} = split(" ", $rmsg) if($rmsg);										#Daten als Array uebertragen
+			$clientmsg->{received} = $rmsg if($rmsg);																	#Daten als Scalar uebertragen
+		}
+		$hash->{STATE} = $status;
+		$hash->{ERRORCNT} = defined($hash->{ERRORCNT}) ? $hash->{ERRORCNT} += 1 : 1 if $status ne "Ok";
+		$clientmsg->{$hash->{NAME} . "_" . "RAWMSG"} = $inh;
+	return $status;
+}
+#####################
+
+sub RPII2C_HWACCESS_ioctl($$) {
+	my ($hash, $clientmsg) = @_;
+	my $status = "error";
+	Log3 $hash, 5, "$hash->{NAME}: HWaccess I2CAddr: " . sprintf("0x%.2X", $clientmsg->{i2caddress});
+	my ($fh, $msg) = undef;
+	
+	my $ankommen = "$hash->{NAME}: vom client empfangen";
+		foreach my $av (keys %{$clientmsg}) { $ankommen .= "|" . $av . ": " . $clientmsg->{$av}; }
+	Log3 $hash, 5, $ankommen;
+	#Log3 $hash, 1, $ankommen if $clientmsg->{test} eq "local";
+	
+	my $i2caddr = hex(sprintf "%x", $clientmsg->{i2caddress});
+	if ( sysopen(my $fh, $hash->{DeviceName}, O_RDWR) != 1) {																																														#Datei oeffnen
+		Log3 $hash, 3, "$hash->{NAME}: HWaccess sysopen failure: $!"
+	} elsif( not defined( ioctl($fh,$I2C_SLAVE,$i2caddr) ) ) {																																													#I2C Adresse per ioctl setzen
+		Log3 $hash, 3, "$hash->{NAME}: HWaccess (0x".unpack( "H2",pack "C", $clientmsg->{i2caddress}).") ioctl failure: $!"
+#	} elsif (defined($clientmsg->{nbyte}) && defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cblockwrite") {	#blockweise schreiben
+	} elsif (                                                              defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") {			#blockweise schreiben
+		my $data = defined($clientmsg->{reg}) ? chr($clientmsg->{reg}) : undef;
+		foreach (split(" ", $clientmsg->{data})) {
+			$data .= chr($_);
+		}
+		my $retval = syswrite($fh, $data, length($data));
+		unless (defined($retval) && $retval == length($data)) {
+			Log3 $hash, 3, "$hash->{NAME}: HWaccess blockweise nach 0x".unpack( "H2",pack "C", $clientmsg->{i2caddress})." schreiben, " . (defined($clientmsg->{reg}) ? "Reg: 0x". unpack( "H2",pack "C", $clientmsg->{reg}) : "") . " Inh: $clientmsg->{data}, laenge: ".length($data)."| -> syswrite failure: $!";
+		} else {
+			$status = "Ok";
+			Log3 $hash, 5, "$hash->{NAME}: HWaccess block schreiben, " . (defined($clientmsg->{reg}) ? "Reg: 0x". unpack( "H2",pack "C", $clientmsg->{reg}) : "") . " Inh(dec):|$clientmsg->{data}|, laenge: |".length($data)."|";
+		}
+
+#	} elsif (defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") {																																		#byteweise schreiben
+	} elsif (defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cbytewrite") {																																#byteweise schreiben
+		my $reg = undef;
+		$reg = $clientmsg->{reg} if (defined($clientmsg->{reg}));
+		$status = "Ok";
+		foreach (split(" ", $clientmsg->{data})) {
+			my $data = (defined($reg) ? chr($reg++) : "") . chr($_);
+			my $retval = syswrite($fh, $data, length($data));
+			#Log3 $hash, 1, "retval= $retval" if $clientmsg->{test} eq "local";
+			unless (defined($retval) && $retval == length($data)) {
+				Log3 $hash, 3, "$hash->{NAME}: HWaccess byteweise nach 0x".unpack( "H2",pack "C", $clientmsg->{i2caddress})." schreiben, ". (defined($reg) ?	"Reg: 0x". unpack( "H2",pack "C", ($reg - 1)) . " " : "")."Inh: 0x" .  unpack( "H2",pack "C", $_) .", laenge: ".length($data)."| -> syswrite failure: $!";
+				$status = "error";
+				last;
+			}
+		Log3 $hash, 5,   "$hash->{NAME}: HWaccess byteweise schreiben, ". (defined($reg) ?  "Reg: 0x". unpack( "H2",pack "C", ($reg - 1)) . " " : "")."Inh: 0x" .  unpack( "H2",pack "C", $_) .", laenge: ".length($data);
+		#Log3 $hash, 1, "$hash->{NAME}: HWaccess byteweise zu 0x".unpack( "H2",pack "C", $clientmsg->{i2caddress})."  schreiben, ". (defined($reg) ?  "Reg: 0x". unpack( "H2",pack "C", ($reg - 1)) . " " : "")."Inh: 0x" .  unpack( "H2",pack "C", $_) .", laenge: ".length($data) if $clientmsg->{test} eq "local";	
+		}
+#	} elsif ($clientmsg->{direction} eq "i2cread") {																																																		#byteweise lesen
+	} elsif ($clientmsg->{direction} eq "i2cbyteread") {																																																#byteweise lesen
+		my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1;
+		my $rmsg = "";
+		foreach (my $n = 0; $n < $nbyte; $n++) {
+			if ( defined($clientmsg->{reg}) ) {
+				Log3 $hash, 5, "$hash->{NAME}: HWaccess byteweise lesen setze Registerpointer auf " . ($clientmsg->{reg} + $n);
+				my $retval = syswrite($fh, chr($clientmsg->{reg} + $n), 1);
+				unless (defined($retval) && $retval == 1) {
+					Log3 $hash, 3, "$hash->{NAME}: HWaccess byteweise von 0x".unpack( "H2",pack "C", $clientmsg->{i2caddress})." lesen,". (defined($clientmsg->{reg}) ? " Reg: 0x". unpack( "H2",pack "C", ($clientmsg->{reg} + $n)) : "") . " -> syswrite failure: $!" if $!;
+					last;
+				}
+			}
+            if (defined($clientmsg->{usleep})) {
+                usleep($clientmsg->{usleep});
+            }
+            my $buf = undef;
+			my $retval = sysread($fh, $buf, 1);
+			unless (defined($retval) && $retval == 1) {
+				Log3 $hash, 3, "$hash->{NAME}: HWaccess byteweise von 0x".unpack( "H2",pack "C", $clientmsg->{i2caddress})." lesen,". (defined($clientmsg->{reg}) ? " Reg: 0x". unpack( "H2",pack "C", ($clientmsg->{reg} + $n)) : "") . " -> sysread failure: $!" if $!;
+				last;
+			}
+			$rmsg .= ord($buf);
+			$rmsg .= " " if $n <= $nbyte;
+			$status = "Ok" if ($n + 1) == $nbyte;
+		}
+		$clientmsg->{received} = $rmsg if($rmsg);											  #Daten als Scalar uebertragen
+#	} elsif ($clientmsg->{direction} eq "i2cblockread") {																																																#blockweise lesen
+	} elsif ($clientmsg->{direction} eq "i2cread") {																																																		#blockweise lesen
+		my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1;
+		#Log3 $hash, 1, "test Blockweise lese menge: |$nbyte|, reg: |". $clientmsg->{reg} ."|";
+		my $rmsg = "";
+		if ( defined($clientmsg->{reg}) ) {
+			Log3 $hash, 4, "$hash->{NAME}: HWaccess blockweise lesen setze Registerpointer auf " . ($clientmsg->{reg});
+			my $retval = syswrite($fh, chr($clientmsg->{reg}), 1);
+			unless (defined($retval) && $retval == 1) {
+				Log3 $hash, 3, "$hash->{NAME}: HWaccess blockweise von 0x".unpack( "H2",pack "C", $clientmsg->{i2caddress})." lesen,". (defined($clientmsg->{reg}) ? " Reg: 0x". unpack( "H2",pack "C", ($clientmsg->{reg})) : "") . " -> syswrite failure: $!" if $!;
+				last;
+			}
+		}
+        if (defined($clientmsg->{usleep})) {
+            usleep($clientmsg->{usleep});
+        }
+		my $buf = undef;
+		my $retval = sysread($fh, $buf, $nbyte);
+		#Log3 $hash, 1, "test Blockweise lesen menge: |$nbyte|, return: |$retval|, inh: |$buf|";
+		unless (defined($retval) && $retval == $nbyte) {
+			Log3 $hash, 3, "$hash->{NAME}: HWaccess blockweise von 0x".unpack( "H2",pack "C", $clientmsg->{i2caddress})." lesen,". (defined($clientmsg->{reg}) ? " Reg: 0x". unpack( "H2",pack "C", ($clientmsg->{reg})) : "") . " -> sysread failure: $!" if $!;
+			last;
+		} else {
+			$status = "Ok"
+		}
+		#Log3 $hash, 1, "test Blockweise lesen menge: |$nbyte|, inh: $buf";
+		$rmsg = $buf;
+		$rmsg =~ s/(.|\n)/sprintf("%u ",ord($1))/eg;
+		#Log3 $hash, 1, "test Blockweise lesen ergebnis: |$rmsg|";
+		$clientmsg->{received} = $rmsg if($rmsg);												#Daten als Scalar uebertragen
+	}
+	$hash->{STATE} = $status;
+	$hash->{ERRORCNT} = defined($hash->{ERRORCNT}) ? $hash->{ERRORCNT} += 1 : 1 if $status ne "Ok";
+	#$clientmsg->{$hash->{NAME} . "_" . "RAWMSG"} = $inh;
+	return $status;
+}
+
+
+=pod
+=item device
+=item summary accesses I2C interface via sysfs on linux
+=item summary_DE Zugriff auf das I2C-Interface &uuml;ber sysfs auf Linux Systemen
+=begin html
+
+<a name="RPII2C"></a>
+<h3>RPII2C</h3>
+(en | <a href="commandref_DE.html#RPII2C">de</a>)
+<ul>
+	<a name="RPII2C"></a>
+		Provides access to Raspberry Pi's I2C interfaces for some logical modules and also directly.<br>
+		This modul will basically work on every linux system that provides <code>/dev/i2c-x</code>.<br><br>	
+
+		<b>preliminary:</b><br>
+		<ul>
+			<li>
+				load I2C kernel modules (choose <b>one</b> of the following options):<br>
+				<ul>
+				<li>
+          open /etc/modules<br>
+          <ul><code>sudo nano /etc/modules</code></ul><br>
+          add these lines<br>
+          <ul><code>
+            i2c-dev<br>
+            i2c-bcm2708<br>
+          </code></ul>
+				</li>
+				<li>
+          Since Kernel 3.18.x on raspberry pi and maybe on other boards too, device tree support was implemented and enabled by default.
+          To enable I2C support just add
+          <ul><code>device_tree_param=i2c0=on,i2c1=on</code></ul> to /boot/config.txt
+          You can also enable just one of the I2C. In this case remove the unwantet one from the line.
+				</li>
+				<li>
+          On Raspbian images since 2015 just start <code>sudo raspi-config</code> and enable I2C there. Parameters will be added automaticly to /boot/config.txt
+				</li>
+				reboot
+				</ul>
+			</li><br>
+			<li>Choose <b>only one</b> of the three follwing methodes do grant access to <code>/dev/i2c-*</code> for FHEM user:
+				<ul>
+				<li>
+					<code>sudo apt-get install i2c-tools<br>
+					sudo adduser fhem i2c<br>
+					sudo reboot</code><br>
+				</li><br>
+				<li>
+					Add following lines into <code>/etc/init.d/fhem</code> before <code>perl fhem.pl</code> line in start or into <code>/etc/rc.local</code>:<br>
+					<code>
+						sudo chown fhem /dev/i2c-*<br>
+						sudo chgrp dialout /dev/i2c-*<br>
+						sudo chmod +t /dev/i2c-*<br>
+						sudo chmod 660 /dev/i2c-*<br>
+					</code>
+				</li><br>
+				<li>
+					Alternatively for Raspberry Pi you can install the gpio utility from <a href="http://wiringpi.com/download-and-install/">WiringPi</a> library change access rights of I2C-Interface<br>
+					WiringPi installation is described here: <a href="#RPI_GPIO">RPI_GPIO.</a><br>
+					gpio utility will be automaticly used, if installed.<br>
+					Important: to use I2C-0 at P5 connector you must use attribute <code>swap_i2c0</code>.<br>
+				</li>
+				</ul>
+			</li><br>
+			<li>
+				<b>Optional</b>: access via IOCTL will be used (RECOMMENDED) if Device::SMBus is not present.<br>
+				To access the I2C-Bus via the Device::SMBus module, following steps are necessary:<br>
+				<ul><code>sudo apt-get install libmoose-perl<br>
+				sudo cpan Device::SMBus</code></ul><br>
+			</li>
+			<li>
+				<b>For Raspbian users only</b><br>
+				If you are using I2C-0 at P5 connector on Raspberry Pi model B with newer raspbian versions, including support for Raspberry Pi model B+, you must add following line to <code>/boot/cmdline.txt</code>:<br>
+				<ul><code>bcm2708.vc_i2c_override=1</code></ul><br>
+			</li>
+		</ul>
+	<a name="RPII2CDefine"></a><br>
+	<b>Define</b>
+	<ul>
+		<code>define &lt;name&gt; RPII2C &lt;I2C Bus Number&gt;</code><br>
+		where <code>&lt;I2C Bus Number&gt;</code> is the number of the I2C bus that should be used (0 or 1)<br><br>
+	</ul>
+
+	<a name="RPII2CSet"></a>
+	<b>Set</b>
+	<ul>
+		<li>
+			Write one byte (or more bytes sequentially) directly to an I2C device (for devices that have only one register to write):<br>
+			<code>set &lt;name&gt; writeByte &lt;I2C Address&gt; &lt;value&gt;</code><br><br>
+		</li>
+		<li>
+			Write n-bytes to an register range (as an series of single register write operations), beginning at the specified register:<br>
+			<code>set &lt;name&gt; writeByteReg &lt;I2C Address&gt; &lt;Register Address&gt; &lt;value&gt; [&lt;value&gt; [..]]</code><br><br>
+		</li>
+		<li>
+			Write n-bytes directly to an I2C device (as an block write operation):<br>	
+			<code>set &lt;name&gt; writeBlock &lt;I2C Address&gt; &lt;Register Address&gt; &lt;value&gt; [&lt;value&gt; [..]]</code><br><br>
+		</li>
+		<li>
+			Write n-bytes to an register range (as an block write operation), beginning at the specified register:<br>	
+			<code>set &lt;name&gt; writeBlockReg &lt;I2C Address&gt; &lt;Register Address&gt; &lt;value&gt; [&lt;value&gt; [..]]</code><br><br>
+		</li><br>
+		Examples:
+		<ul>
+			Write 0xAA to device with I2C address 0x60<br>
+			<code>set test1 writeByte 60 AA</code><br>
+			Write 0xAA to register 0x01 of device with I2C address 0x6E<br>
+			<code>set test1 writeByteReg 6E 01 AA</code><br>
+			Write 0xAA to register 0x01 of device with I2C address 0x6E, after it write 0x55 to 0x02 as two separate commands<br>
+			<code>set test1 writeByteReg 6E 01 AA 55</code><br>
+			Write 0xA4 to register 0x03, 0x00 to register 0x04 and 0xDA to register 0x05 of device with I2C address 0x60 as an block command<br>
+			<code>set test1 writeBlock 60 03 A4 00 DA</code><br>
+
+		</ul><br>
+	</ul>
+
+	<a name="RPII2CGet"></a>
+	<b>Get</b>
+	<ul>
+		<li>
+			Gets value of I2C device's registers:<br>
+			<code>get &lt;name&gt; read &lt;I2C Address&gt; [&lt;Register Address&gt; [&lt;number of registers&gt;]]</code><br><br>
+		</li>
+		<li>
+			Gets value of I2C device in blockwise mode:<br>
+			<code>get &lt;name&gt; readblock &lt;I2C Address&gt; [&lt;number of registers&gt;]</code><br><br>
+		</li>
+		<li>
+			Gets value of I2C device's registers in blockwise mode:<br>
+			<code>get &lt;name&gt; readblockreg &lt;I2C Address&gt; &lt;Register Address&gt; [&lt;number of registers&gt;]</code><br><br>
+		</li><br>
+		Examples:
+		<ul>
+			Reads byte from device with I2C address 0x60<br>
+			<code>get test1 read 60</code><br>
+			Reads register 0x01 of device with I2C address 0x6E.<br>
+			<code>get test1 read 6E 01 AA 55</code><br>
+			Reads register 0x03 to 0x06 of device with I2C address 0x60.<br>
+			<code>get test1 read 60 03 4</code><br>
+		</ul><br>
+	</ul><br>
+
+	<a name="RPII2CAttr"></a>
+	<b>Attributes</b>
+	<ul>
+		<li>swap_i2c0<br>
+			Swap Raspberry Pi's I2C-0 from J5 to P5 rev. B<br>
+			This attribute is for Raspberry Pi only and needs gpio utility from <a href="http://wiringpi.com/download-and-install/">WiringPi</a> library.<br>
+			Default: none, valid values: on, off<br><br>
+		</li>
+		<li>useHWLib<br>
+		Change hardware access method.<br>
+		Attribute exists only if both access methods are usable<br>
+		Default: IOCTL, valid values: IOCTL, SMBus<br><br>
+		</li>
+		<li><a href="#ignore">ignore</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="RPII2C"></a>
+<h3>RPII2C</h3>
+(<a href="commandref.html#RPII2C">en</a> | de)
+<ul>
+	<a name="RPII2C"></a>
+		Erm&ouml;glicht den Zugriff auf die I2C Schnittstellen des Raspberry Pi, BBB, Cubie &uuml;ber logische Module. Register von I2C IC's k&ouml;nnen auch direkt gelesen und geschrieben werden.<br><br>
+		Dieses Modul funktioniert gruns&auml;tzlich auf allen Linux Systemen, die <code>/dev/i2c-x</code> bereitstellen.<br><br>
+		
+		<b>Vorbereitung:</b><br>
+      <ul>
+			<li>
+				I2C Kernelmodule laden (chose <b>one</b> of the following options):<br>
+				<ul>
+				<li>
+          I2C Kernelmodule laden:<br>
+          modules Datei &ouml;ffnen<br>
+          <ul><code>sudo nano /etc/modules</code></ul><br>
+          folgendes einf&uuml;gen<br>
+          <ul><code>
+            i2c-dev<br>
+            i2c-bcm2708<br>
+				</code></ul>
+				</li>
+				<li>
+          Seit Kernel 3.18.x auf dem Raspberry Pi und evtl. auch auf anderen Systemen ist der "Device tree support" implementiert und standardm&auml;&szlig;ig aktiviert.
+          Um I2C Unterst&uuml;tzung zu aktivieren mu&szlig;
+          <ul><code>device_tree_param=i2c0=on,i2c1=on</code></ul> zur /boot/config.txt hinzu gef&uuml;gt werden.
+          Wenn nur einer der Busse genutzt wird, kann der andere einfach aus der Zeile entfernt werden.
+				</li>
+				<li>
+          Bei Raspbian Images seit 2015 kann der I2C Bus einfach &uuml;ber <code>sudo raspi-config</code> aktiviert werden. Die Parameter werden automatisch in die /boot/config.txt eingetragen.
+				</li>
+				Neustart
+				</ul>
+			</li><br>
+			<li><b>Eine</b> der folgenden drei M&ouml;glichkeiten w&auml;hlen um dem FHEM User Zugriff auf <code>/dev/i2c-*</code> zu geben:
+				<ul>
+				<li>
+					<code>
+						sudo apt-get install i2c-tools<br>
+						sudo adduser fhem i2c</code><br>
+				</li><br>
+				<li>
+					Folgende Zeilen entweder in die Datei <code>/etc/init.d/fhem</code> vor <code>perl fhem.pl</code> in start, oder in die Datei <code>/etc/rc.local</code> eingef&uuml;gen:<br>
+					<code>
+						sudo chown fhem /dev/i2c-*<br>
+						sudo chgrp dialout /dev/i2c-*<br>
+						sudo chmod +t /dev/i2c-*<br>
+						sudo chmod 660 /dev/i2c-*<br>
+					</code>
+				</li><br>
+				<li>
+					F&uumlr das Raspberry Pi kann alternativ das gpio Utility der <a href="http://wiringpi.com/download-and-install/">WiringPi</a> Bibliothek benutzt werden um FHEM Schreibrechte auf die I2C Schnittstelle zu bekommen.<br>
+					WiringPi Installation ist hier beschrieben: <a href="#RPI_GPIO">RPI_GPIO</a><br>
+					Das gpio Utility wird, wenn vorhanden, automatisch verwendet<br>
+					Wichtig: um den I2C-0 am P5 Stecker des Raspberry nutzen zu k&ouml;nnen muss das Attribut <code>swap_i2c0</code> verwendet werden.<br>
+				</li>
+				</ul>
+			</li><br>
+			<li>
+				<b>Optional</b>: Hardwarezugriff via IOCTL wird standardm&auml;&szlig;ig genutzt (EMPFOHLEN), wenn Device::SMBus nicht installiert ist<br>
+				Soll der Hardwarezugriff &uuml;ber das Perl Modul Device::SMBus erfolgen sind diese Schritte notwendig:<br>
+				<ul><code>sudo apt-get install libmoose-perl<br>
+				sudo cpan Device::SMBus</code></ul><br>
+			</li>
+			<li>
+				<b>Nur f&uuml;r Raspbian Nutzer</b><br>
+				Um I2C-0 am P5 Stecker auf Raspberry Pi modell B mit neueren Raspbian Versionen zu nutzen, welche auch das Raspberry Pi model B+ unterst&uuml;tzen, muss folgende Zeile in die <code>/boot/cmdline.txt</code> eingef&uuml;gt werden:<br>
+				<ul><code>bcm2708.vc_i2c_override=1</code></ul><br>
+			</li>
+		</ul>
+	<a name="RPII2CDefine"></a><br>
+	<b>Define</b>
+	<ul>
+		<code>define &lt;name&gt; RPII2C &lt;I2C Bus Number&gt;</code><br>
+		Die <code>&lt;I2C Bus Number&gt;</code> ist die Nummer des I2C Bus an den die I2C IC's angeschlossen werden<br><br>
+	</ul>
+
+	<a name="RPII2CSet"></a>
+	<b>Set</b>
+	<ul>
+		<li>
+			Schreibe ein Byte (oder auch mehrere nacheinander) direkt auf ein I2C device (manche I2C Module sind so einfach, das es nicht einmal mehrere Register gibt):<br>
+			<code>set &lt;name&gt; writeByte &lt;I2C Address&gt; &lt;value&gt;</code><br><br>
+		</li>
+		<li>
+			Schreibe n-bytes auf einen Registerbereich (als Folge von Einzelbefehlen), beginnend mit dem angegebenen Register:<br>
+			<code>set &lt;name&gt; writeByteReg &lt;I2C Address&gt; &lt;Register Address&gt; &lt;value&gt; [&lt;value&gt; [..]]</code><br><br>
+		</li>
+		<li>
+			Schreibe n-bytes auf ein I2C device (als Blockoperation):<br>	
+			<code>set &lt;name&gt; writeBlock &lt;I2C Address&gt; &lt;value&gt; [&lt;value&gt; [..]]</code><br><br>
+		</li>		
+		<li>
+			Schreibe n-bytes auf einen Registerbereich (als Blockoperation), beginnend mit dem angegebenen Register:<br>	
+			<code>set &lt;name&gt; writeBlockReg &lt;I2C Address&gt; &lt;Register Address&gt; &lt;value&gt; [&lt;value&gt; [..]]</code><br><br>
+		</li><br>
+		Beispiele:
+		<ul>
+			Schreibe 0xAA zu Modul mit I2C Addresse 0x60<br>
+			<code>set test1 writeByte 60 AA</code><br>
+			Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E<br>
+			<code>set test1 writeByteReg 6E 01 AA</code><br>
+			Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E, schreibe danach 0x55 in das Register 0x02 als einzelne Befehle<br>
+			<code>set test1 writeByteReg 6E 01 AA 55</code><br>
+			Schreibe 0xA4 zu Register 0x03, 0x00 zu Register 0x04 und 0xDA zu Register 0x05 des Moduls mit der I2C Adresse 0x60 zusammen als ein Blockbefehl<br>
+			<code>set test1 writeBlockReg 60 03 A4 00 DA</code><br>
+		</ul><br>
+	</ul>
+
+	<a name="RPII2CGet"></a>
+	<b>Get</b>
+	<ul>
+		<li>
+			Auslesen der Registerinhalte des I2C Moduls:<br>
+			<code>get &lt;name&gt; read &lt;I2C Address&gt; [&lt;Register Address&gt; [&lt;number of registers&gt;]]</code><br><br>
+		</li>
+		<li>
+			Blockweises Auslesen des I2C Moduls (ohne separate Register):<br>
+			<code>get &lt;name&gt; readblock &lt;I2C Address&gt; [&lt;number of registers&gt;]</code><br><br>
+		</li>
+		<li>
+			Blockweises Auslesen der Registerinhalte des I2C Moduls:<br>
+			<code>get &lt;name&gt; readblockreg &lt;I2C Address&gt; &lt;Register Address&gt; [&lt;number of registers&gt;]</code><br><br>
+		</li><br>
+		Beispiele:
+		<ul>
+			Lese Byte vom Modul mit der I2C Adresse 0x60<br>
+			<code>get test1 read 60</code><br>
+			Lese den Inhalt des Registers 0x01 vom Modul mit der I2C Adresse 0x6E.<br>
+			<code>get test1 read 6E 01 AA 55</code><br>
+			Lese den Inhalt des Registerbereichs 0x03 bis 0x06 vom Modul mit der I2C Adresse 0x60.<br>
+			<code>get test1 read 60 03 4</code><br>
+		</ul><br>
+	</ul><br>
+
+	<a name="RPII2CAttr"></a>
+	<b>Attribute</b>
+	<ul>
+		<li>swap_i2c0<br>
+			Umschalten von I2C-0 des Raspberry Pi Rev. B von J5 auf P5<br>
+			Dieses Attribut ist nur f&uuml;r das Raspberry Pi vorgesehen und ben&ouml;tigt das gpio utility wie unter dem Punkt Vorbereitung beschrieben.<br>
+			Standard: keiner, g&uuml;ltige Werte: on, off<br><br>
+		</li>
+		<li>useHWLib<br>
+			&Auml;ndern der Methode des Hardwarezugriffs.<br>
+			Dieses Attribut existiert nur, wenn beide Zugriffsmethoden verf&uuml;gbar sind<br>
+			Standard: IOCTL, g&uuml;ltige Werte: IOCTL, SMBus<br><br>
+		</li>
+		<li><a href="#ignore">ignore</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
+
+1;

ファイルの差分が大きいため隠しています
+ 3735 - 0
fhem/core/FHEM/00_SIGNALduino.pm


ファイルの差分が大きいため隠しています
+ 9370 - 0
fhem/core/FHEM/00_SONOS.pm


ファイルの差分が大きいため隠しています
+ 1500 - 0
fhem/core/FHEM/00_TCM.pm


ファイルの差分が大きいため隠しています
+ 2028 - 0
fhem/core/FHEM/00_THZ.pm


ファイルの差分が大きいため隠しています
+ 1296 - 0
fhem/core/FHEM/00_TUL.pm


+ 770 - 0
fhem/core/FHEM/00_ZWCUL.pm

@@ -0,0 +1,770 @@
+##############################################
+# $Id: 00_ZWCUL.pm 11984 2016-08-19 12:47:50Z rudolfkoenig $
+package main;
+
+# TODO
+#   static routing: to and from the device
+#   automatic routing via neighborUpdate
+#   explorer frames
+#   check security
+#   multicast
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+use ZWLib;
+use DevIo;
+
+sub ZWCUL_Parse($$$$$);
+sub ZWCUL_Read($@);
+sub ZWCUL_ReadAnswer($$$);
+sub ZWCUL_Ready($);
+sub ZWCUL_SimpleWrite($$);
+sub ZWCUL_Write($$$);
+sub ZWCUL_ProcessSendStack($);
+
+our %zwave_id2class;
+my %ZWCUL_sentIdx;
+my %ZWCUL_sentIdx2cbid;
+
+my %sets = (
+  "reopen"     => { cmd=>"" },
+  "led"        => { cmd=>"l%02x", param=>{ on=>1, off=>0, blink=>2 } },
+  "addNode"    => { cmd=>"x%x", param => { on=>1, off=>0, onSec=>2 } },
+  "addNodeId"  => { cmd=>"x%x" },
+  "removeNode" => { cmd=>"x%x", param => { on=>1, off=>0 } },
+  "raw"        => { cmd=> "%s" },
+);
+
+my %gets = (
+  "homeId"     => { cmd=> "zi", regex => "^. [A-F0-9]{8} [A-F0-9]{2}\$" },
+  "version"    => { cmd=> "V",  regex => "^V " },
+  "nodeInfo"   => { cmd=> "x%x" },
+  "raw"        => { cmd=> "%s", regex => ".*" }
+);
+
+sub
+ZWCUL_Initialize($)
+{
+  my ($hash) = @_;
+
+# Provider
+  $hash->{ReadFn}  = "ZWCUL_Read";
+  $hash->{WriteFn} = "ZWCUL_Write";
+  $hash->{ReadyFn} = "ZWCUL_Ready";
+  $hash->{ReadAnswerFn} = "ZWCUL_ReadAnswer";
+
+# Normal devices
+  $hash->{DefFn}   = "ZWCUL_Define";
+  $hash->{SetFn}   = "ZWCUL_Set";
+  $hash->{GetFn}   = "ZWCUL_Get";
+  $hash->{AttrFn}  = "ZWCUL_Attr";
+  $hash->{UndefFn} = "ZWCUL_Undef";
+  $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 model disable:0,1 ".
+                     "networkKey intruderMode dataRate:40k,100k,9600";
+}
+
+#####################################
+sub
+ZWCUL_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> ZWCUL device homeId ctrlId" if(@a != 5);
+  return "wrong syntax: homeId is 8 digit hex" if($a[3] !~ m/^[0-9A-F]{8}$/i);
+  return "wrong syntax: ctrlId is 2 digit hex" if($a[4] !~ m/^[0-9A-F]{2}$/i);
+
+  DevIo_CloseDev($hash);
+
+  my $name = $a[0];
+  my $dev = $a[2];
+  $hash->{homeId} = lc($a[3]);
+  $hash->{homeIdSet} = lc($a[3]);
+  $hash->{nodeIdHex} = lc($a[4]);
+  $hash->{initString} = ($hash->{homeIdSet} =~ m/^0*$/ ? "zm4":"zr4");
+  $hash->{baudRate} = "40k";
+  $hash->{monitor} = 1 if($hash->{homeIdSet} eq "00000000");
+  setReadingsVal($hash, "homeId",       # ZWDongle compatibility
+          "HomeId:$hash->{homeId} CtrlNodeIdHex:$hash->{nodeIdHex}", TimeNow());
+
+  $hash->{Clients} = ":ZWave:STACKABLE_CC:";
+  my %matchList = ( "1:ZWave" => ".*",
+                    "2:STACKABLE_CC"=>"^\\*");
+  $hash->{MatchList} = \%matchList;
+
+  if($dev eq "none") {
+    Log3 $name, 1, "$name device is none, commands will be echoed only";
+    $attr{$name}{dummy} = 1;
+    readingsSingleUpdate($hash, "state", "dummy", 1);
+    return undef;
+
+  } elsif($dev !~ m/@/) {
+    $def .= "\@9600";  # default baudrate
+
+  }
+
+  $hash->{DeviceName} = $dev;
+  return DevIo_OpenDev($hash, 0, "ZWCUL_DoInit");
+}
+
+#####################################
+sub
+ZWCUL_DoInit($)
+{
+  my $hash = shift;
+  my $name = $hash->{NAME};
+
+  $hash->{PARTIAL} = "";
+  
+  $hash->{RA_Timeout} = 0.5;     # Clear the pipe
+  for(;;) {
+    my ($err, undef) = ZWCUL_ReadAnswer($hash, "Clear", "wontmatch");
+    last if($err && ($err =~ m/^Timeout/ || $err =~ m/No FD/));
+  }
+  delete($hash->{RA_Timeout});
+
+  my ($err, $ver, $try) = ("", "", 0);
+  while($try++ < 3 && $ver !~ m/^V/) {
+    ZWCUL_SimpleWrite($hash, "V");
+    ($err, $ver) = ZWCUL_ReadAnswer($hash, "Version", "^V");
+    return "$name: $err" if($err && ($err !~ m/Timeout/ || $try == 3));
+    $ver = "" if(!$ver);
+  }
+  if($ver !~ m/^V/) {
+    $attr{$name}{dummy} = 1;
+    my $msg = "Not an CUL device, got for V:  $ver";
+    Log3 $name, 1, $msg;
+    return $msg;
+  }
+  $ver =~ s/[\r\n]//g;
+  $hash->{VERSION} = $ver;
+
+  ZWCUL_SimpleWrite($hash, "zi".$hash->{homeIdSet}.$hash->{nodeIdHex});
+  ZWCUL_SimpleWrite($hash, $hash->{initString});
+
+  readingsSingleUpdate($hash, "state", "Initialized", 1);
+  return undef;
+}
+
+
+#####################################
+sub
+ZWCUL_Undef($$) 
+{
+  my ($hash,$arg) = @_;
+  ZWCUL_SimpleWrite($hash, "zx");
+  DevIo_CloseDev($hash); 
+  return undef;
+}
+
+sub
+ZWCUL_tmp9600($$)
+{
+  my ($hash, $on) = @_;
+  $hash->{baudRate} = ($on ? "9600" : AttrVal($hash->{NAME},"dataRate","40k"));
+  ZWCUL_SimpleWrite($hash, $on ? $on : $hash->{initString});
+}
+
+#####################################
+sub
+ZWCUL_cmd($$@)
+{
+  my ($type, $cmdList, $hash, @a) = @_;
+  my $name = shift @a;
+
+  return "\"$type $name\" needs at least one parameter" if(@a < 1);
+  my $cmdName = shift @a;
+
+  if(!defined($cmdList->{$cmdName})) {
+    my @r;
+    map { my $p = $cmdList->{$_}{param};
+          push @r,($p ? "$_:".join(",",sort keys %{$p}) : $_) }
+          sort keys %{$cmdList};
+    return "Unknown argument $cmdName, choose one of ".join(" ",@r);
+  }
+
+  Log3 $hash, 4, "ZWCUL $type $name $cmdName ".join(" ",@a);
+  if($cmdName eq "reopen") {
+    return if(AttrVal($name, "dummy",undef) || AttrVal($name, "disable",undef));
+    delete $hash->{NEXT_OPEN};
+    DevIo_CloseDev($hash);
+    sleep(1);
+    DevIo_OpenDev($hash, 0, "ZWCUL_DoInit");
+    return;
+  }
+
+  my $cmd = $cmdList->{$cmdName}{cmd};
+  my @ca = split("%", $cmd, -1);
+  my $nargs = int(@ca)-1;
+  return "$type $name $cmdName needs $nargs arguments" if($nargs != int(@a));
+
+  my $param = $cmdList->{$cmdName}{param};
+  if($param) {
+    return "invalid parameter $a[0] for $cmdName" if(!defined($param->{$a[0]}));
+    $a[0] = $param->{$a[0]};
+  }
+
+  if($cmdName =~ m/^addNode/) {
+    delete $hash->{removeNode};
+    delete $hash->{addNode};
+    if($cmdName eq "addNodeId") {
+      $hash->{addNode} = sprintf("%02x", $a[0]);
+
+    } else {
+      $hash->{addNode} = ZWCUL_getNextNodeId($hash) if($a[0]);
+      $hash->{addSecure} = 1 if($a[0] == 2);
+    }
+    Log3 $hash, 3, "ZWCUL going to assigning new node id $hash->{addNode}"
+        if($a[0]);
+    ZWCUL_tmp9600($hash, $a[0] ? "zm9" : 0); # expect random homeId
+    return;
+  }
+
+  if($cmdName eq "removeNode") {
+    delete $hash->{addNode};
+    delete $hash->{removeNode};
+    $hash->{removeNode} = $a[0] if($a[0]);
+    ZWCUL_tmp9600($hash, $a[0] ? "zr9" : 0);
+    return;
+  }
+
+  if($cmdName eq "nodeInfo") {
+    my $node = ZWCUL_getNode($hash, sprintf("%02x", $a[0]));
+    return "Node with decimal id $a[0] not found" if(!$node);
+    my $ni = ReadingsVal($node->{NAME}, "nodeInfo", undef);
+    return "No nodeInfo present" if(!$ni);
+    $ni = "0141${ni}041001";    # TODO: Remove fixed values
+    my @r = map { ord($_) } split("", pack('H*', $ni));
+    my $msg = zwlib_parseNodeInfo(@r);
+    setReadingsVal($hash, "nodeInfo_".$a[0], $msg, TimeNow());
+    return $msg;
+  }
+
+  $cmd = sprintf($cmd, @a);
+  ZWCUL_SimpleWrite($hash,  $cmd);
+  
+  return undef if($type eq "set");
+
+  my $re = $cmdList->{$cmdName}{regexp};
+  my ($e, $d) = ZWCUL_ReadAnswer($hash, $cmdName, $cmdList->{$cmdName}{regexp});
+  return $e if($e);
+  return $d;
+}
+
+sub ZWCUL_Set() { return ZWCUL_cmd("set", \%sets, @_); };
+sub ZWCUL_Get() { return ZWCUL_cmd("get", \%gets, @_); };
+
+#####################################
+sub
+ZWCUL_SimpleWrite($$)
+{
+  my ($hash, $msg) = @_;
+  return if(!$hash);
+
+  my $name = $hash->{NAME};
+  Log3 $name, 5, "SW: $msg";
+  $msg .= "\n";
+
+  $hash->{USBDev}->write($msg)    if($hash->{USBDev});
+  syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev});
+  syswrite($hash->{DIODev}, $msg) if($hash->{DIODev});
+  select(undef, undef, undef, 0.001);
+}
+
+#####################################
+sub
+ZWCUL_Write($$$)
+{
+  my ($hash,$fn,$msg) = @_;
+
+  Log3 $hash, 5, "ZWCUL_Write $fn $msg";
+  if($msg =~ m/0013(..)(..)(.*)(..)(..)/) {
+    my ($targetId,$l,$p,$flags,$cbid) = ($1,$2,$3,$4,$5);
+    my ($homeId,$route) = split(",",$fn);
+
+    my $sn = $ZWCUL_sentIdx{$targetId};
+    $sn = (!$sn || $sn==15) ? 1 : ($sn+1);
+    $ZWCUL_sentIdx2cbid{"$targetId$sn"} = $cbid;
+    $ZWCUL_sentIdx{$targetId} = $sn;
+
+    my $s100 = ($hash->{baudRate} eq "100k");
+
+    my $rf = 0x41;
+    if($route) {
+      $rf = 0x81;
+      $p = sprintf("00%d0%s%s",length($route)/2, $route, $p);
+    }
+
+    $msg = sprintf("%s%s%02x%02x%02x%s%s", 
+                    $homeId, $hash->{nodeIdHex}, $rf, $sn,
+                    length($p)/2+($s100 ? 11 : 10), $targetId, $p);
+    $msg .= ($s100 ? zwlib_checkSum_16($msg) : zwlib_checkSum_8($msg));
+
+    ZWCUL_SimpleWrite($hash, "zs".$msg);
+  }
+}
+
+#####################################
+# called from the global loop, when the select for hash->{FD} reports data
+sub
+ZWCUL_Read($@)
+{
+  my ($hash, $local, $regexp) = @_;
+
+  my $buf = (defined($local) ? $local : DevIo_SimpleRead($hash));
+  return "" if(!defined($buf));
+  my $name = $hash->{NAME};
+
+  my $culdata = $hash->{PARTIAL};
+  #Log3 $name, 5, "ZWCUL/RAW: $culdata/$buf";
+  $culdata .= $buf;
+
+  while($culdata =~ m/\n/) {
+    my $rmsg;
+    ($rmsg,$culdata) = split("\n", $culdata, 2);
+    $rmsg =~ s/\r//;
+    $hash->{PARTIAL} = $culdata; # for recursive calls
+    return $rmsg 
+        if(defined($local) && (!defined($regexp) || ($rmsg =~ m/$regexp/)));
+    ZWCUL_Parse($hash, $hash, $name, $rmsg, 0) if($rmsg);
+    $culdata = $hash->{PARTIAL};
+  }
+  $hash->{PARTIAL} = $culdata;
+  return undef;
+}
+
+sub
+ZWCUL_getNode($$)
+{
+  my ($hash, $id) = @_;
+  my @l = devspec2array(sprintf("TYPE=ZWave:".
+                "FILTER=homeId=%s:FILTER=nodeIdHex=%s", $hash->{homeId}, $id));
+  return undef if(!int(@l));
+  return $defs{$l[0]};
+}
+
+sub
+ZWCUL_getNextNodeId($)
+{
+  my ($hash) = @_;
+  my @l = devspec2array(sprintf(".*:FILTER=homeId=%s",$hash->{homeId}));
+  my %h = map { $defs{$_}{nodeIdHex} => 1 } @l;
+  for(my $i = 1; $i <= 232; $i++) {
+    my $s = sprintf("%02x", $i);
+    return $s if(!$h{$s});
+  }
+  Log 1, "NOTE: NO MORE nodeIDs available";
+  return "ff";
+}
+
+sub
+ZWCUL_assignId($$$$)
+{
+  my ($hash, $oldNodeId, $newHomeId, $newNodeId) = @_;
+
+  my $myHash;
+  my $key = "$hash->{homeIdSet} $oldNodeId";
+  if(!defined($modules{ZWave}{defptr}{$key})) {
+    $modules{ZWave}{defptr}{$key} = { nodeIdHex => $oldNodeId };
+    $myHash = 1;
+  }
+  ZWCUL_Write($hash, $hash->{homeIdSet}, 
+          sprintf("0013%s080103%s%s####", $oldNodeId, $newNodeId, $newHomeId));
+  delete $modules{ZWave}{defptr}{$key} if($myHash);
+}
+
+sub
+ZWCUL_Parse($$$$$)
+{
+  my ($hash, $iohash, $name, $rmsg, $nodispatch) = @_;
+
+  if($rmsg =~ m/^\*/) {                           # STACKABLE_CC
+    Dispatch($hash, $rmsg, undef);
+    return;
+  }
+
+  $hash->{"${name}_MSGCNT"}++;
+  $hash->{"${name}_TIME"} = TimeNow();
+  # showtime attribute
+  readingsSingleUpdate($hash, "state", $hash->{READINGS}{state}{VAL}, 0);
+  $hash->{RAWMSG} = $rmsg;
+  my %addvals = (RAWMSG => $rmsg);
+
+  Dispatch($hash, $rmsg, \%addvals) if($rmsg !~ m/^z/);
+
+  $rmsg = lc($rmsg);
+  my $me = $hash->{NAME};
+  my $s100 = ($hash->{baudRate} eq "100k");
+
+  if($rmsg =~ m/^za(..)$/) {
+    Log3 $hash, 5, "$me sent ACK to $1";
+    return;
+  }
+
+  if($rmsg =~ m/^zr(..)$/) {
+    Log3 $hash, 5, "$me fw-resend nr ".hex($1);
+    return;
+  }
+
+
+  my ($H, $S, $F, $f, $sn, $L, $T, $P, $C);
+  if($s100 && $rmsg =~ '^z(........)(..)(..)(.)(.)(..)(..)(.*)(....)$') {
+    ($H,$S,$F,$f,$sn,$L,$T,$P,$C) = ($1,$2,$3,$4,$5,$6,$7,$8,$9);
+
+  } elsif(!$s100 && $rmsg =~ '^z(........)(..)(..)(.)(.)(..)(..)(.*)(..)$') {
+    ($H,$S,$F,$f,$sn,$L,$T,$P,$C) = ($1,$2,$3,$4,$5,$6,$7,$8,$9);
+
+  } else {
+    Log3 $hash, 1, "ERROR: Unknown packet $rmsg";
+    return;
+
+  }
+
+  my ($hF,$hf, $rf,$hc,$hp,$hops,$ri,$u1) = (hex($F),hex($f),"",0,0,"","","");
+  # ITU G.9959, 8-4, 8-11
+  if($hF&0x80) { # routing
+    $hc = hex(substr($P,2,1));
+    $hp = hex(substr($P,3,1));
+    $ri = "R:".substr($P, 0, ($hc+2)*2)." ";
+    $rf = substr($P, 0, 2);
+    $hops = substr($P, 4, $hc*2);
+    $hops =~ s/(..)/$1 /g;
+    $P = substr($P,($hc+2)*2);
+  }
+  if($hF&4) { # Explorer?
+    $u1 = " E:".substr($P,0,16)." ";
+    $P = substr($P,16);
+  }
+
+  if(AttrVal($me, "verbose", 1) > 4) {
+    Log3 $hash, 5, "$H S:$S F:$F f:$f SN:$sn L:$L T:$T ${ri}${u1}P:$P C:$C";
+    Log3 $hash, 5, "   F:".
+      (($hF & 3)==1 ? " singleCast" :
+       ($hF & 3)==2 ? " multiCast" :
+       ($hF & 3)==3 ? " ack" : " unknownHeaderType:".($hF&0x3)).
+      (($hF & 4)    ? " explorer" : "").
+      (($hF & 0x10)==0x10 ? " speedModified":"").
+      (($hF & 0x20)==0x20 ? " lowPower":"").
+      (($hF & 0x40)==0x40 ? " ackReq":"").
+      (($hF & 0x80)==0x80 ? 
+                        " routed, rf:$rf hopCnt:$hc hopPos:$hp hops:$hops":"").
+      ((($hf>>1)&3)==0 ? " "          : 
+      (($hf>>1)&3)==1 ? " shortBeam" :
+      (($hf>>1)&3)==2 ? " longBeam"  :" unknownBeam");
+  }
+
+  return if($hc && !$hash->{monitor} && $hc == $hp);
+  return if($hash->{monitor} && !AttrVal($me, "intruderMode", 0));
+
+
+  $hash->{homeId} = $H; # Fake homeId for monitor mode
+
+  if(length($P)) {
+
+    if($hash->{removeNode} && $T eq "ff") {
+      ZWCUL_assignId($hash, $S, "00000000", "00");
+      $hash->{removeNode} = $S;
+      return;
+    }
+
+    if($hash->{addNode} && $T eq "ff" && $S eq "00" && $P =~ m/^0101/) {
+      ZWCUL_assignId($hash, "00", $hash->{homeIdSet}, $hash->{addNode});
+      $hash->{addNodeParam} = $P;
+      return;
+    }
+
+    if($P =~ m/^0101(......)(..)..(.*)/) {
+      my ($nodeInfo, $type6, $classes) = ($1, $2, $3);
+      $rmsg = sprintf("004a0003%s####%s##%s", $S, $2, $3);
+
+    } else {
+      $rmsg = sprintf("0004%s%s%02x%s", $S, $S, length($P)/2, $P);
+
+    }
+
+  } else {      # ACK
+    if($hash->{removeNode} && $hash->{removeNode} eq $S) { #############
+      Log3 $hash, 3, "Node $S excluded from network";
+      delete $hash->{removeNode};
+      ZWCUL_tmp9600($hash, 0);
+      return;
+    }
+
+    if($hash->{addNode} && $S eq "00") {                   #############
+      $hash->{addNodeParam} =~ m/^0101(......)(..)..(.*)/;
+      my ($nodeInfo, $type6, $classes) = ($1, $2, $3);
+
+      ZWCUL_tmp9600($hash, 0);
+      $hash->{homeId} = $hash->{homeIdSet};
+      Dispatch($hash, sprintf("004a0003%s####%s##%s",
+                        $hash->{addNode}, $type6, $classes), \%addvals);
+
+      my $node = ZWCUL_getNode($hash, $hash->{addNode});
+      if($node) { # autocreated a node
+        readingsSingleUpdate($node, "nodeInfo", $nodeInfo, 0);
+        Dispatch($hash, sprintf("004a0005%s##", $hash->{addNode}), \%addvals);
+      }
+
+      delete $hash->{addNode};
+      delete $hash->{addNodeParam};
+      return;
+    }
+
+    if($hash->{addNode} && $hash->{addNode} eq $S) { # Another hack
+      Log3 $hash, 3,"ZWCUL node ".hex($S)." (hex $S) included into the network";
+      delete $hash->{addNode};
+      ZWCUL_tmp9600($hash, 0);
+      return;
+    }
+
+    $rmsg = sprintf("0013%s00", 
+        $ZWCUL_sentIdx2cbid{"$S$sn"} ? $ZWCUL_sentIdx2cbid{"$S$sn"} : 00);
+
+  }
+  return $rmsg if($nodispatch);
+  Dispatch($hash, $rmsg, \%addvals);
+}
+
+#####################################
+# This is a direct read for commands like get
+sub
+ZWCUL_ReadAnswer($$$)
+{
+  my ($hash, $arg, $regexp) = @_;
+  Log3 $hash, 4, "ZWCUL_ReadAnswer arg:$arg regexp:".($regexp ? $regexp:"");
+  my $transform;
+  if($regexp && $regexp =~ m/^\^000400(..)..(..)/) {
+    $regexp = "^z........$1........$2";
+    $transform = 1;
+  }
+  return ("No FD (dummy device?)", undef)
+        if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
+  my $to = ($hash->{RA_Timeout} ? $hash->{RA_Timeout} : 1);
+
+  for(;;) {
+
+    my $buf;
+    if($^O =~ m/Win/ && $hash->{USBDev}) {
+      $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
+      # Read anstatt input sonst funzt read_const_time nicht.
+      $buf = $hash->{USBDev}->read(999);
+      return ("Timeout reading answer for get $arg", undef)
+        if(length($buf) == 0);
+
+    } else {
+      if(!$hash->{FD}) {
+        Log3 $hash, 1, "ZWCUL_ReadAnswer: device lost";
+        return ("Device lost when reading answer for get $arg", undef);
+      }
+
+      my $rin = '';
+      vec($rin, $hash->{FD}, 1) = 1;
+      my $nfound = select($rin, undef, undef, $to);
+      if($nfound < 0) {
+        my $err = $!;
+        Log3 $hash, 5, "ZWCUL_ReadAnswer: nfound < 0 / err:$err";
+        next if ($err == EAGAIN() || $err == EINTR() || $err == 0);
+        DevIo_Disconnected($hash);
+        return("ZWCUL_ReadAnswer $arg: $err", undef);
+      }
+
+      if($nfound == 0){
+        Log3 $hash, 5, "ZWCUL_ReadAnswer: select timeout";
+        return ("Timeout reading answer for get $arg", undef);
+      }
+
+      $buf = DevIo_SimpleRead($hash);
+      if(!defined($buf)){
+        Log3 $hash, 1,"ZWCUL_ReadAnswer: no data read";
+        return ("No data", undef);
+      }
+    }
+
+    my $ret = ZWCUL_Read($hash, $buf, $regexp);
+    if(defined($ret)){
+      if($transform) {
+        my $name = $hash->{NAME};
+        $ret = ZWCUL_Parse($hash, $hash, $name, $ret, 1);
+      }
+      Log3 $hash, 4, "ZWCUL_ReadAnswer for $arg: $ret";
+      return (undef, $ret);
+    }
+  }
+}
+
+#####################################
+sub
+ZWCUL_Attr($$$$)
+{
+  my ($cmd, $name, $attr, $value) = @_;
+  my $hash = $defs{$name};
+  
+  if($attr eq "disable") {
+    if($cmd eq "set" && ($value || !defined($value))) {
+      DevIo_CloseDev($hash) if(!AttrVal($name,"dummy",undef));
+      readingsSingleUpdate($hash, "state", "disabled", 1);
+
+    } else {
+      if(AttrVal($name,"dummy",undef)) {
+        readingsSingleUpdate($hash, "state", "dummy", 1);
+        return;
+      }
+      DevIo_OpenDev($hash, 0, "ZWCUL_DoInit");
+
+    }
+
+  } elsif($attr eq "networkKey" && $cmd eq "set") {
+    if(!$value || $value !~ m/^[0-9A-F]{32}$/i) {
+      return "attr $name networkKey: not a hex string with a length of 32";
+    }
+    return;
+
+  } elsif($attr eq "dataRate" && $cmd eq "set") {
+    my $sfx = ($value eq "100k" ? "1" :
+              ($value eq "9600" ? "9" : "4"));
+    $hash->{initString} = ($hash->{homeIdSet} =~ m/^0*$/ ? "zm$sfx":"zr$sfx");
+    $hash->{baudRate} = $value;
+    ZWCUL_SimpleWrite($hash, $hash->{initString});
+
+  }
+
+  return undef;  
+  
+}
+
+#####################################
+sub
+ZWCUL_Ready($)
+{
+  my ($hash) = @_;
+
+  return undef if (IsDisabled($hash->{NAME}));
+
+  return DevIo_OpenDev($hash, 1, "ZWCUL_DoInit")
+            if(ReadingsVal($hash->{NAME}, "state","") eq "disconnected");
+
+  # This is relevant for windows/USB only
+  my $po = $hash->{USBDev};
+  if($po) {
+    my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
+    return ($InBytes>0);
+  }
+  return 0;
+}
+
+
+1;
+
+=pod
+=item summary    connection to a culfw Device in ZWave mode (e.g. CUL)
+=item summary_DE Anbindung eines culfw Ger&auml;tes in ZWave Modus (z.Bsp. CUL)
+=begin html
+
+<a name="ZWCUL"></a>
+<h3>ZWCUL</h3>
+<ul>
+  This module serves a CUL in ZWave mode (starting from culfw version 1.66),
+  which is attached via USB or TCP/IP, and enables the use of ZWave devices
+  (see also the <a href="#ZWave">ZWave</a> module). 
+  <br><br>
+  <a name="ZWCULdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; ZWCUL &lt;device&gt; &lt;homeId&gt;
+          &lt;ctrlId&gt;</code>
+  <br>
+  <br>
+  Since the DevIo module is used to open the device, you can also use devices
+  connected via  TCP/IP. See <a href="#CULdefine">this</a> paragraph on device
+  naming details.
+  <br>
+  Example:
+  <ul>
+    <code>define ZWCUL_1 ZWCUL /dev/cu.usbmodemfa141@9600 12345678 01</code><br>
+  </ul>
+  If the homeId is set to 00000000, then culfw will enter monitor mode, i.e. no
+  acks for received messages will be sent, and no homeId filtering will be done.
+  </ul>
+  <br>
+
+  <a name="ZWCULset"></a>
+  <b>Set</b>
+  <ul>
+
+  <li>reopen<br>
+    First close and then open the device. Used for debugging purposes.
+    </li>
+
+  <li>led [on|off|blink]<br>
+    Set the LED on the CUL.
+    </li>
+
+  <li>raw<br>
+    send a raw string to culfw
+    </li>
+
+  <li>addNode [on|onSec|off]<br>
+    Activate (or deactivate) inclusion mode. The CUL will switch to dataRate
+    9600 until terminating this mode with off, or a node is included. If onSec
+    is specified, the ZWCUL networkKey ist set, and the device supports the
+    SECURITY class, then a secure inclusion is attempted. </li>
+
+  <li>addNodeId &lt;decimalNodeId&gt;<br>
+    Activate inclusion mode, and assign decimalNodeId to the next node.
+    To deactivate this mode, use addNode off. Note: addNode won't work for a
+    FHEM2FHEM:RAW attached ZWCUL, use addNodeId instead</li>
+
+  <li>removeNode [onNw|on|off]<br>
+    Activate (or deactivate) exclusion mode. Like with addNode, the CUL will
+    switch temporarily to dataRate 9600, potentially missing some packets sent
+    on higher dataRates.  Note: the corresponding fhem device have to be
+    deleted manually.</li>
+
+  </ul>
+  <br>
+
+  <a name="ZWCULget"></a>
+  <b>Get</b>
+  <ul>
+  <li>homeId<br>
+    return the homeId and the ctrlId of the controller.</li>
+  <li>nodeInfo<br>
+    return node specific information. Needed by developers only.</li>
+  <li>raw<br>
+    Send raw data to the controller.</li>
+  </ul>
+  <br>
+
+  <a name="ZWCULattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a name="dataRate">dataRate</a> [40k|100k|9600]<br>
+      specify the data rate.
+      </li>
+    <li><a href="#dummy">dummy</a></li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#model">model</a></li>
+    <li><a href="#disable">disable</a></li>
+    <li><a href="#networkKey">networkKey</a></li>
+    <li><a name="intruderMode">intruderMode</a><br>
+      In monitor mode (see above) events are not dispatched to the ZWave module
+      per default. Setting this attribute will allow to get decoded messages,
+      and to send commands to devices not included by this controller.
+      </li>
+    <li>verbose<br>
+      If the verbose attribute of this device (not global!) is set to 5 or
+      higher, then detailed logging of the RF message will be done.
+      </li>
+  </ul>
+  <br>
+
+  <a name="ZWCULevents"></a>
+  <b>Generated events: TODO</b>
+
+</ul>
+
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1279 - 0
fhem/core/FHEM/00_ZWDongle.pm


ファイルの差分が大きいため隠しています
+ 4677 - 0
fhem/core/FHEM/01_FHEMWEB.pm


+ 520 - 0
fhem/core/FHEM/02_FRAMEBUFFER.pm

@@ -0,0 +1,520 @@
+#
+#
+# 02_FRAMEBUFFER.pm
+# written by Kai Stuke
+# based on 02_RSS.pm 
+#
+##############################################
+# $Id: 02_FRAMEBUFFER.pm 12126 2016-09-06 18:35:26Z kaihs $
+
+package main;
+use strict;
+use warnings;
+use GD;
+use feature qw/switch/;
+use vars qw(%data);
+use Scalar::Util qw(looks_like_number);
+
+require "02_RSS.pm"; # enable use of layout files and image creation
+
+my %sets = (
+	'updateDisplay' => "",
+	'relLayoutNo' => "",
+	'absLayoutNo' => "",
+	'layoutFilename' => "",
+);
+
+
+##################################################
+# Forward declarations
+#
+##################
+sub FRAMEBUFFER_Initialize($);
+sub FRAMEBUFFER_rewindCounter($);
+sub FRAMEBUFFER_readLayout($);
+sub FRAMEBUFFER_Define($$);
+sub FRAMEBUFFER_updateDisplay($$);
+sub FRAMEBUFFER_Set($@);
+sub FRAMEBUFFER_Attr(@);
+sub FRAMEBUFFER_returnPNG($);
+
+
+sub
+FRAMEBUFFER_Initialize($) {
+    my ($hash) = @_;
+    $hash->{DefFn}   = "FRAMEBUFFER_Define";
+    $hash->{AttrFn}  = "FRAMEBUFFER_Attr";
+    $hash->{AttrList} = 'loglevel:0,1,2,3,4,5,6 update_interval:1,2,5,10,20,30 ' .
+	                'size layoutBasedir layoutList startLayoutNo debugFile bgcolor enableCmd disableCmd ' . $readingFnAttributes;
+    $hash->{SetFn}   = "FRAMEBUFFER_Set";
+    $hash->{UndefFn}  = 'FRAMEBUFFER_Undef';
+}
+
+sub FRAMEBUFFER_Undef($$) {
+	my ($hash, $arg) = @_;
+	
+	RemoveInternalTimer($hash);
+	return undef;
+}
+ 
+##################
+sub FRAMEBUFFER_rewindCounter($) {
+	my ($hash) = @_;
+	my $name= $hash->{NAME};
+	my $updateInterval = AttrVal($hash->{NAME}, 'update_interval', 0);
+	
+	Log3 $name, 5, "rewindCounter $updateInterval"; 
+	if ($updateInterval > 0) {
+		# round to the begin of the next minute to get a more accurate time display
+		my $currentTime = time();
+		my $triggerTime = int(($currentTime + ($updateInterval * 60))/60)*60;
+		Log3 $name, 5, "current $currentTime next trigger at $triggerTime";
+		InternalTimer($triggerTime, 'FRAMEBUFFER_rewindCounter', $hash, 0);
+	}
+	FRAMEBUFFER_updateDisplay($hash, 0);
+}
+ 
+##################
+sub FRAMEBUFFER_readLayout($) {
+  my ($hash) = @_;
+  my $name= $hash->{NAME};
+
+  my $filename= $hash->{fhem}{filename};
+  if (!defined $filename) {
+	return 0;
+  }
+  
+  if (defined $hash->{layoutBasedir} && substr($filename,0,1) ne '/') {
+	$filename = $hash->{layoutBasedir} . '/' . $filename;
+  }
+
+  if(open(LAYOUT, $filename)) {
+    my @layout= <LAYOUT>;
+    $hash->{fhem}{layout}= join("", @layout);
+    close(LAYOUT);
+    return 1;
+  } else {
+    $hash->{fhem}{layout}= ();
+    Log3 $name, 1, "Cannot open $filename";
+    return 0;
+  }
+}  
+ 
+##################
+sub FRAMEBUFFER_Define($$) {
+
+  my ($hash, $def) = @_;
+
+  my @a = split("[ \t]+", $def);
+
+  return "Usage: define <name> FRAMEBUFFER framebuffer_device"  if(int(@a) != 3);
+  my $name= $a[0];
+  my $fb_device= $a[2];
+
+  if (! (-r $fb_device && -w $fb_device)) {
+	return "$fb_device isn't readable and writable";
+  }
+  $hash->{fhem}{fb_device}= $fb_device;
+
+  eval "use GD::Text::Align";
+  $hash->{fhem}{useTextAlign} = ($@ ? 0 : 1 );
+  if(!($hash->{fhem}{useTextAlign})) { 
+    Log3 $hash, 2, "$name: Cannot use text alignment: $@";
+  }
+    
+  eval "use GD::Text::Wrap";
+  $hash->{fhem}{useTextWrap} = ($@ ? 0 : 1 );
+  if(!($hash->{fhem}{useTextWrap})) { 
+    Log3 $hash, 2, "$name: Cannot use text wrapping: $@";
+  }
+
+  readingsSingleUpdate($hash, 'state', 'Initialized',1);
+  return undef;
+}
+
+##################
+sub FRAMEBUFFER_updateDisplay($$) {
+  my ($hash, $timeout) = @_;
+  my $name = $hash->{NAME};
+  my $fbv = '/usr/local/bin/fbvs';
+  my $fd = $hash->{fd};
+  
+  if (defined $fd) {
+	close $fd;
+  }
+  
+  if (-x $fbv) {
+	if (defined $hash->{debugFile}) {
+		use File::Spec;
+		my $dfile = $hash->{debugFile};
+                my($vol,$dir,$file) = File::Spec->splitpath($dfile);
+		if ((-e $dfile && -w $dfile) || -w $dir) { 
+			$fbv = "tee $dfile | $fbv";
+		}
+	}  
+	
+	# check if this is a display with a timeout
+	if ($timeout) {
+    # yes, execute enable command (e.g. enable backlight)
+    fhem($hash->{enableCmd}) if $hash->{enableCmd};
+  } else {
+    # yes, execute enable command (e.g. enable backlight)
+    fhem($hash->{disableCmd}) if $hash->{disableCmd};
+  }
+
+	if (FRAMEBUFFER_readLayout($hash)) {
+		open($fd, "|".$fbv . ' -d '. $hash->{fhem}{fb_device});
+		binmode $fd;
+		print $fd FRAMEBUFFER_returnPNG($name);
+		# don't close the file immediately, as this will wait
+		# for the fbv process to terminate which may take some time
+		#close FBV;
+	}
+  } else {
+	Log3 $name, 1, "$fbv doesn't exist or isn't executable, please install it";
+	
+  }
+}
+
+##################
+sub
+FRAMEBUFFER_Set($@) {
+
+  my ($hash, @a) = @_;
+
+  my $name =$a[0];
+  my $cmd = $a[1];
+  my $val = $a[2];
+  my $val2 = $a[3];
+
+  # usage check
+  my $usage= "Unknown argument, choose one of " . join(' ', keys %sets);
+  if (@a == 2) {
+    if ($cmd eq "updateDisplay") {
+	# just display the current layout again
+      FRAMEBUFFER_updateDisplay($hash, 0);
+      $usage = undef;
+    }
+  } elsif (@a == 3 || @a == 4) { 
+     if ($cmd eq "absLayoutNo") {
+	my $layoutNo = (defined($val) && looks_like_number($val) && $val >= 0) ? $val : 0;
+	my @layoutList = split(/ /,$hash->{layoutList});
+	my $noOfLayouts = @layoutList;
+
+	if ($val < $noOfLayouts) {	
+		$hash->{fhem}{filename} = $layoutList[$layoutNo];
+		$hash->{fhem}{absLayoutNo} = $layoutNo;
+		FRAMEBUFFER_updateDisplay($hash, 0);
+		$usage = undef;
+	} else {
+		$usage = "absLayoutNo out of bounds, must be between 0 and $noOfLayouts";
+	}
+    } elsif ($cmd eq "relLayoutNo") {
+	my $relLayoutNo = (defined($val) && looks_like_number($val)) ? $val : 0;
+	my @layoutList = split(/ /,$hash->{layoutList});
+	my $noOfLayouts = @layoutList;
+	
+	if ($noOfLayouts > 0) {
+		$hash->{fhem}{absLayoutNo} += $relLayoutNo;
+		if ($hash->{fhem}{absLayoutNo} > $noOfLayouts-1) {
+			$hash->{fhem}{absLayoutNo} = $noOfLayouts-1;
+		} elsif ($hash->{fhem}{absLayoutNo} < 0) {
+			$hash->{fhem}{absLayoutNo} = 0;
+		}
+		$hash->{fhem}{filename} = $layoutList[$hash->{fhem}{absLayoutNo}];
+		FRAMEBUFFER_updateDisplay($hash, 0);
+		$usage = undef;
+	} else {
+		$usage = "layoutList is empty, please set that attribute first";
+	}
+    } elsif ($cmd eq "layoutFilename") {
+	my $timeout = (defined($val2) && looks_like_number($val2) && $val2 >= 0) ? $val2 : 0;
+	my $prevFilename = $hash->{fhem}{filename};
+	$hash->{fhem}{filename} = $val;
+	FRAMEBUFFER_updateDisplay($hash, $timeout);
+	if ($timeout > 0) {
+		# nach timeout Sekunden wieder das aktuelle Layout anzeigen
+		RemoveInternalTimer($hash);
+		$hash->{fhem}{filename} = $prevFilename;
+		InternalTimer(time() + $timeout, 'FRAMEBUFFER_rewindCounter', $hash, 0);
+	}
+	$usage = undef;
+    }
+    readingsBeginUpdate($hash);
+    readingsBulkUpdate($hash,"absLayoutNo", $hash->{fhem}{absLayoutNo});
+    readingsBulkUpdate($hash,"layoutFilename", $hash->{fhem}{filename});
+    readingsEndUpdate($hash,1);
+  }
+  return $usage;
+}
+
+
+###################
+sub
+FRAMEBUFFER_Attr(@)
+{
+	my (undef, $name, $attr, $val) =  @_;
+	my $hash = $defs{$name};
+	my $msg = '';
+
+	Log3 $name, 5, "attr " . $attr . " val " . $val; 
+	if ($attr eq 'debugFile') {
+		$hash->{debugFile} = $val;
+	} elsif ($attr eq 'update_interval') {
+		my $updateInterval = (defined($val) && looks_like_number($val) && $val >= 0) ? $val : -1;
+
+		
+		if ($updateInterval >= 0) {
+			if ($updateInterval != AttrVal($hash->{NAME}, 'update_interval', 0)) {
+				RemoveInternalTimer($hash);
+				$hash->{updateInterval} = $updateInterval;
+				if ($val > 0) {
+					InternalTimer(1, 'FRAMEBUFFER_rewindCounter', $hash, 0);
+				}
+			}
+		} else {
+			$msg = 'Wrong update_interval defined. update_interval must be a number >= 0';
+		}
+        } elsif ($attr eq 'layoutBasedir') {
+		my $layoutBasedir = $val;
+		
+		if (-d $val && -r $val) {
+			$hash->{layoutBasedir} = $val;
+		} else {
+			$msg = "$val is not a readable directory";
+		}
+		
+	} elsif ($attr eq 'layoutList') {
+		$hash->{layoutList} = $val;
+	} elsif ($attr eq 'startLayoutNo') {
+		# Beim start des Moduls das anzuzeigenden Layout aus diesem Attribut nehmen
+		if (!defined $hash->{fhem}{absLayoutNo}) {
+			fhem "set $name absLayoutNo $val" ;
+		}
+	} elsif ($attr eq 'bgcolor') {
+	} elsif ($attr eq 'enableCmd') {
+    $hash->{enableCmd} = $val;
+  } elsif ($attr eq 'disableCmd') {
+    $hash->{disableCmd} = $val;
+  }  
+	
+
+	return ($msg) ? $msg : undef;  
+}
+
+
+
+##################
+sub
+FRAMEBUFFER_returnPNG($) {
+  my ($name)= @_;
+
+  my ($width,$height)= split(/x/, AttrVal($name,"size","128x160"));
+
+  #
+  # increase counter
+  #
+  if(defined($defs{$name}{fhem}) && defined($defs{$name}{fhem}{counter})) {
+    $defs{$name}{fhem}{counter}++;
+  } else {
+    $defs{$name}{fhem}{counter}= 1;
+  }
+
+  # true color
+  GD::Image->trueColor(1);
+  
+  #
+  # create the image
+  #
+  my $S;
+  # let's create a blank image, we will need it in most cases. 
+  $S= GD::Image->newTrueColor($width,$height);
+  my $bgcolor = AttrVal($name,'bgcolor','000000'); #default bg color = black
+  $bgcolor = RSS_color($S, $bgcolor);
+  # $S->colorAllocate(0,0,0); # other colors seem not to work (issue with GD)
+  $S->fill(0,0,$bgcolor);
+
+  # wrap to make problems with GD non-lethal
+
+    #
+    # evaluate layout
+    #
+    eval { RSS_evalLayout($S, $name, $defs{$name}{fhem}{layout}) };
+    Log3 $name, 1, "Problem with layout " . $defs{$name}{fhem}{layout} . ", maybe wrong syntax or included images don't exist: $@" if $@ ne "";
+
+  #
+  # return png image
+  #
+  return $S->png(0);
+}
+  
+
+
+
+1;
+
+
+
+
+=pod
+=item device
+=item summary Graphical display on a Linux framebuffer
+=begin html
+
+<a name="FRAMEBUFFER"></a>
+<h3>FRAMEBUFFER</h3>
+<ul>
+  Provides a device to display arbitrary content on a linux framebuffer device<p>
+
+  You need to have the perl module <code>GD</code> installed. This module is most likely not
+  available for small systems like Fritz!Box.<p>
+  FRAMEBUFFER uses <a href="#RSS">RSS</a> to create an image that is displayed on the framebuffer.<br>
+  The binary program fbvs is required to display the image. You can download it from <a href="https://github.com/kaihs/fbvs">github</a>.
+  </p>
+
+  <a name="FRAMEBUFFERdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FRAMEBUFFER &lt;framebuffer_device_name&gt;</code><br><br>
+
+    Defines a framebuffer device. <code>&lt;framebuffer_device_name&gt;</code> is the name of the linux
+    device file for the kernel framebuffer device, e.g. /dev/fb1 or /dev/fb0.
+    
+    Examples:
+    <ul>
+      <code>define display FRAMEBUFFER /dev/fb1</code><br>
+      <code>define TV FRAMEBUFFER /dev/fb0</code><br>
+    </ul>
+    <br>
+  </ul>
+
+  <a name="FRAMBUFFERset"></a>
+  <b>Set</b>
+  <ul>
+    <code>set &lt;name&gt; absLayoutNo &lt;number&gt;</code>
+    <br><br>
+    A list of layout files can be defined with <code>attr layoutList</code>, see below.
+    This command selects the layout with the given number from this list and displays it.
+    This can e.g. be useful if bound to a key of a remote control to display a specific layout.
+    <br><br>
+  </ul>
+  <ul>
+    <code>set &lt;name&gt; layoutFilename &lt;name&gt; [&lt;timeout in seconds&gt;]</code>
+    <br><br>
+    Displays the image described by the layout in file &lt;name&gt;. If &lt;name&gt; is an absolute path
+    name it is used as is to access the file. Otherwise the attribute  &lt;layoutBasedir&gt; is prepended to 
+    the &lt;name&gt;.
+    If a timeout is given, the image is only displayed for timeout seconds before the previously displayed image
+    is displayed again.
+    Useful for displaying an image only for a certain time after an event has occured.
+    <br><br>
+  </ul>
+  <ul>
+    <code>set &lt;name&gt; relLayoutNo &lt;number&gt;</code>
+    <br><br>
+    Like absLayoutNo this displays a certain image from the layoutList. Here &lt;number&gt; is added to the current
+    layout number. 
+    So<br>
+    <code>set &lt;name&gt; relLayoutNo 1</code>
+    displays the next image from the list while<br>
+    <code>set &lt;name&gt; relLayoutNo -1</code><br>
+    displays the previous one.
+    Useful if bound to a next/previous key on a remote control to scroll through all defined layouts.
+    <br><br>
+  </ul>
+  <ul>
+    <code>set &lt;name&gt; updateDisplay</code>
+    <br><br>
+    Refreshes the display defined by the currently active layout.
+    <br><br>
+  </ul>
+
+  <a name="FRAMEUFFERattr"></a>
+  <b>Attributes</b>
+  <br>
+  <ul>
+    <code>size &lt;width&gt;x&lt;height&gt;</code><br>
+    The dimensions of the display in pixels.
+    Images will generated using this size. If the size is greater than the actual display
+    size they will be scaled to fit. As this requires computing performance it should be avoided by
+    defining the size to match the display size.
+    <br>Example<br>
+    <code>attr &lt;name&gt; size 128x160</code>
+    <br><br>
+  </ul>
+  <ul>
+    <code>layoutBasedir &lt;directory name&gt;</code><br>
+    Directory that contains the layout files. If a layout filename is specified using a relative path 
+    <code>layoutBasedir</code> will be prepended before accessing the file.
+    <br>Example<br>
+    <code>attr &lt;name&gt; layoutBasedir /opt/fhem/layouts</code>
+    <br><br>
+  </ul>
+  <ul>
+    <code>layoutList &lt;file1&gt; [&lt;file2&gt;] ...</code>
+    <br>Space separated list of layout files.
+    These will be used by <code>absLayoutNo</code> and <code>relLayoutNo</code>.
+    <code>layoutBasedir</code> will be prepended to each file if it is a relative path.
+    <br>Example<br>
+    <code>attr &lt;name&gt; layoutList standard.txt wetter.txt schalter.txt</code>
+    <br><br>
+  </ul>
+  <ul>
+    <code>update_interval &lt;interval&gt;</code>
+    <br>Update interval in minutes.
+    The currently displayed layout will be refreshed every &lt;interval&gt; minutes. The first
+    interval will be scheduled to the beginning of the next minute to help create an accurate
+    time display.<br>
+    <br>Example<br>
+    <code>attr &lt;name&gt; update_interval 1</code>
+    <br><br>
+  </ul>
+  <ul>
+    <code>debugFile &lt;file&gt;</code><br>
+    Normally the generated image isn't written to a file. To ease debugging of layouts the generated image is written to the
+    filename specified by this attribute.
+    This attribute shouldn't be set during normal operation.
+    <br><br>
+  </ul>
+  <ul>
+    <code>startLayoutNo &lt;number&gt;</code><br>
+    The number of the layout to be displayed on startup of the FRAMEBUFFER device. 
+    <br><br>
+  </ul>
+  <ul>bgcolor &lt;color&gt;<br>Sets the background color. &lt;color&gt; is 
+    a 6-digit hex number, every 2 digits  determining the red, green and blue 
+    color components as in HTML color codes (e.g.<code>FF0000</code> for red, <code>C0C0C0</code> for light gray).
+    <br><br>
+  </ul>
+  <ul>enableCmd &lt;fhem cmd&gt;<br>
+    if set this command is executed before a layout with a timeout is displayed. This can e.g. be used to enable a backlight.
+    <br><br>
+  </ul>
+  <ul>disableCmd &lt;fhem cmd&gt;<br>
+    if set this command is executed after a layout with a timeout has expired. This can e.g. be used to disable a backlight.
+  </ul>
+  <br><br>
+
+  <b>Usage information</b>
+  <br>
+  <ul>
+  This module requires the binary program fbvs to be installed in /usr/local/bin and it must be executable
+  by user fhem.
+  fbvs (framebuffer viewer simple) is a stripped down version of fbv that can only display png images. It reads
+  the image from stdin, displays it on the framebuffer and terminates afterwards.
+  This module generates a png image based on a layout description internally and then pipes it to fbvs for display.
+
+  </ul>
+  <br>
+
+  <a name="FRAMEBUFFERlayout"></a>
+  <b>Layout definition</b>
+  <br>
+  <ul>
+    FRAMEBUFFER uses the same <a href="#RSSlayout">layout definition</a> as <a href="#RSS">RSS</a>. In fact FRAMEBUFFER calls RSS to generate an image.
+  </ul>
+
+</ul>
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1218 - 0
fhem/core/FHEM/02_FTUISRV.pm


+ 270 - 0
fhem/core/FHEM/02_HTTPSRV.pm

@@ -0,0 +1,270 @@
+#
+#
+# 02_HTTPSRV.pm
+# written by Dr. Boris Neubert 2012-08-27
+# e-mail: omega at online dot de
+#
+##############################################
+# $Id: 02_HTTPSRV.pm 10733 2016-02-06 09:02:02Z borisneubert $
+
+package main;
+use strict;
+use warnings;
+use vars qw(%data);
+use HttpUtils;
+
+my $HTTPSRV_matchlink = "^\/?(([^\/]*(\/[^\/]+)*)\/?)\$";
+
+#########################
+sub
+HTTPSRV_addExtension($$$$) {
+    my ($name,$func,$link,$friendlyname)= @_;
+
+    # do some cleanup on link/url
+    #   link should really show the link as expected to be called (might include trailing / but no leading /)
+    #   url should only contain the directory piece with a leading / but no trailing /
+    #   $1 is complete link without potentially leading /
+    #   $2 is complete link without potentially leading / and trailing /
+    $link =~ /$HTTPSRV_matchlink/;
+
+    my $url = "/".$2;
+    my $modlink = $1;
+
+    Log3 $name, 3, "Registering HTTPSRV $name for URL $url   and assigned link $modlink ...";
+    $data{FWEXT}{$url}{deviceName}= $name;
+    $data{FWEXT}{$url}{FUNC} = $func;
+    $data{FWEXT}{$url}{LINK} = $modlink;
+    $data{FWEXT}{$url}{NAME} = $friendlyname;
+}
+
+sub 
+HTTPSRV_removeExtension($) {
+    my ($link)= @_;
+
+    # do some cleanup on link/url
+    #   link should really show the link as expected to be called (might include trailing / but no leading /)
+    #   url should only contain the directory piece with a leading / but no trailing /
+    #   $1 is complete link without potentially leading /
+    #   $2 is complete link without potentially leading / and trailing /
+    $link =~ /$HTTPSRV_matchlink/;
+
+    my $url = "/".$2;
+
+    my $name= $data{FWEXT}{$url}{deviceName};
+    Log3 $name, 3, "Unregistering HTTPSRV $name for URL $url...";
+    delete $data{FWEXT}{$url};
+}
+
+##################
+sub
+HTTPSRV_Initialize($) {
+    my ($hash) = @_;
+    $hash->{DefFn}     = "HTTPSRV_Define";
+    $hash->{DefFn}     = "HTTPSRV_Define";
+    $hash->{UndefFn}   = "HTTPSRV_Undef";
+    #$hash->{AttrFn}    = "HTTPSRV_Attr";
+    $hash->{AttrList}  = "directoryindex " .
+                        "readings";
+    $hash->{AttrFn}    = "HTTPSRV_Attr";                    
+    #$hash->{SetFn}     = "HTTPSRV_Set";
+
+    return undef;
+ }
+
+##################
+sub
+HTTPSRV_Define($$) {
+
+  my ($hash, $def) = @_;
+
+  my @a = split("[ \t]+", $def, 5);
+
+  return "Usage: define <name> HTTPSRV <infix> <directory> <friendlyname>"  if(int(@a) != 5);
+  my $name= $a[0];
+  my $infix= $a[2];
+  my $directory= $a[3];
+  my $friendlyname= $a[4];
+
+  $hash->{fhem}{infix}= $infix;
+  $hash->{fhem}{directory}= $directory;
+  $hash->{fhem}{friendlyname}= $friendlyname;
+
+  Log3 $name, 3, "$name: new ext defined infix:$infix: dir:$directory:";
+
+  HTTPSRV_addExtension($name, "HTTPSRV_CGI", $infix, $friendlyname);
+  
+  $hash->{STATE} = $name;
+  return undef;
+}
+
+##################
+sub
+HTTPSRV_Undef($$) {
+
+  my ($hash, $name) = @_;
+
+  HTTPSRV_removeExtension($hash->{fhem}{infix});
+
+  return undef;
+}
+
+##################
+sub
+HTTPSRV_Attr(@)
+{
+    my ($cmd,$name,$aName,$aVal) = @_;
+    if ($cmd eq "set") {        
+        if ($aName =~ "readings") {
+            if ($aVal !~ /^[A-Z_a-z0-9\,]+$/) {
+                Log3 $name, 3, "$name: Invalid reading list in attr $name $aName $aVal (only A-Z, a-z, 0-9, _ and , allowed)";
+                return "Invalid reading name $aVal (only A-Z, a-z, 0-9, _ and , allowed)";
+            }
+        addToDevAttrList($name, $aName);
+        }
+    }
+    return undef;
+}
+
+
+
+##################
+#
+# here we answer any request to http://host:port/fhem/$infix and below
+
+sub HTTPSRV_CGI() {
+
+  my ($request) = @_;   # /$infix/filename
+
+#  Debug "request= $request";
+  
+  # Match request first without trailing / in the link part 
+  if($request =~ m,^(/[^/]+)(/(.*)?)?$,) {
+    my $link= $1;
+    my $filename= $3;
+    my $name;
+
+    # If FWEXT not found for this make a second try with a trailing slash in the link part
+    if(! $data{FWEXT}{$link}) {
+      $link = $link."/";
+      return("text/plain; charset=utf-8", "Illegal request: $request") if(! $data{FWEXT}{$link});
+    }
+    
+    # get device name
+    $name= $data{FWEXT}{$link}{deviceName}; 
+
+#    Debug "link= $link";
+#    Debug "filename= $filename";
+#    Debug "name= $name";
+
+    # return error if no such device
+    return("text/plain; charset=utf-8", "No HTTPSRV device for $link") unless($name);
+
+    my $fullName = $filename;
+    foreach my $reading (split (/,/, AttrVal($name, "readings", "")))  {
+        my $value   = "";
+        if ($fullName =~ /^([^\?]+)\?(.*)($reading)=([^;&]*)([&;].*)?$/) {
+            $filename = $1;
+            $value    = $4;
+            Log3 $name, 5, "$name: set Reading $reading = $value";
+            readingsSingleUpdate($defs{$name}, $reading, $value, 1);
+        }
+    };
+    
+    # set directory index
+    $filename= AttrVal($name,"directoryindex","index.html") unless($filename);
+    my $MIMEtype= filename2MIMEType($filename);
+
+    my $directory= $defs{$name}{fhem}{directory};
+    $filename= "$directory/$filename";
+    #Debug "read filename= $filename";
+    my @contents;
+    if(open(INPUTFILE, $filename)) {
+      binmode(INPUTFILE);
+      @contents= <INPUTFILE>;
+      close(INPUTFILE);
+      return("$MIMEtype; charset=utf-8", join("", @contents));
+    } else {
+      return("text/plain; charset=utf-8", "File not found: $filename");
+    }
+
+  } else {
+    return("text/plain; charset=utf-8", "Illegal request: $request");
+  }
+
+    
+}   
+    
+   
+
+
+####
+
+1;
+
+
+
+
+=pod
+=item helper
+=begin html
+
+<a name="HTTPSRV"></a>
+<h3>HTTPSRV</h3>
+<ul>
+  Provides a mini HTTP server plugin for FHEMWEB. It serves files from a given directory. 
+  It optionally accepts a query string to set readings of this device if an attribute allows the given reading<p>
+
+  HTTPSRV is an extension to <a href="HTTPSRV">FHEMWEB</a>. You must install FHEMWEB to use HTTPSRV.</p>
+
+  <a name="HTTPSRVdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; &lt;infix&gt; &lt;directory&gt; &lt;friendlyname&gt;</code><br><br>
+
+    Defines the HTTP server. <code>&lt;infix&gt;</code> is the portion behind the FHEMWEB base URL (usually
+    <code>http://hostname:8083/fhem</code>), <code>&lt;directory&gt;</code> is the absolute path the
+    files are served from, and <code>&lt;friendlyname&gt;</code> is the name displayed in the side menu of FHEMWEB.<p><p>
+
+    Example:
+    <ul>
+      <code>define myJSFrontend HTTPSRV jsf /usr/share/jsfrontend My little frontend</code><br>
+      or <br>
+      <code>
+        define kindleweb HTTPSRV kindle /opt/fhem/kindle Kindle Web<br>
+        attr kindleweb readings KindleBatt
+      </code><br>
+    </ul>
+    <br>
+  </ul>
+
+  <a name="HTTPSRVset"></a>
+  <b>Set</b>
+  <ul>
+    n/a
+  </ul>
+  <br><br>
+
+  <a name="HTTPSRVattr"></a>
+  <b>Attributes</b>
+  <br><br>
+  <ul>
+    <li>directoryindex: if the request is sent with no filename, i.e. the infix (with or without trailing slash) only, the file given in this attribute is loaded. Defaults to <code>index.html</code>.</li>
+    <li>readings: a comma separated list of reading names. If the request ends with a querystring like <code>?Batt=43</code> and an attribute is set like <code>attr kindleweb readings Batt</code>, then a reading with the Name of this Attribute (here Batt) is created with the value from the request.</li>
+  </ul>
+  <br><br>
+
+  <b>Usage information</b>
+  <br><br>
+  <ul>
+
+  The above example on <code>http://hostname:8083/fhem</code> will return the file
+  <code>/usr/share/jsfrontend/foo.html</code> for <code>http://hostname:8083/fhem/jsf/foo.html</code>.
+  If no filename is given, the filename prescribed by the <code>directoryindex</code> attribute is returned.<p>
+
+  Notice: All links are relative to <code>http://hostname:8083/fhem</code>.
+  </ul>
+  <br><br>
+</ul>
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1240 - 0
fhem/core/FHEM/02_RSS.pm


+ 199 - 0
fhem/core/FHEM/09_BS.pm

@@ -0,0 +1,199 @@
+#
+#
+# 09_BS.pm
+# written by Dr. Boris Neubert 2009-06-20
+# e-mail: omega at online dot de
+#
+##############################################
+# $Id: 09_BS.pm 3830 2013-08-31 17:09:10Z borisneubert $
+package main;
+
+use strict;
+use warnings;
+
+#############################
+sub
+BS_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^81..(04|0c)..0101a001a5cf";
+  $hash->{DefFn}     = "BS_Define";
+  $hash->{UndefFn}   = "BS_Undef";
+  $hash->{ParseFn}   = "BS_Parse";
+  $hash->{AttrList}  = "do_not_notify:1,0 showtime:0,1 ".
+                       "ignore:1,0 model:BS " . $readingFnAttributes;
+
+}
+
+#############################
+sub
+BS_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  my $u= "wrong syntax: define <name> BS <sensor> [[RExt] luxOffset]";
+  return $u if((int(@a)< 3) || (int(@a)>5));
+
+  my $name	= $a[0];
+  my $sensor	= $a[2];
+  if($sensor !~ /[123456789]/) {
+  	return "erroneous sensor specification $sensor, use one of 1..9";
+  }
+  $sensor= "0$sensor";
+
+  my $RExt	= 50000; # default is 50kOhm
+  $RExt= $a[3] if(int(@a)>=4);
+  my $luxOffset= 0;  # default is no offset
+  $luxOffset= $a[4] if(int(@a)>=5);
+  $hash->{SENSOR}= "$sensor";
+  $hash->{RExt}= $RExt;
+  $hash->{luxOffset}= $luxOffset;
+
+  my $dev= "a5cf $sensor";
+  $hash->{DEF}= $dev;
+
+  $modules{BS}{defptr}{$dev} = $hash;
+  AssignIoPort($hash);
+}
+
+#############################
+sub
+BS_Undef($$)
+{
+  my ($hash, $name) = @_;
+  
+  delete($modules{BS}{defptr}{$hash->{DEF}});
+  return undef;
+}
+
+#############################
+sub
+BS_Parse($$)
+{
+  my ($hash, $msg) = @_;	# hash points to the FHZ, not to the BS
+
+
+  # Msg format:
+  # 01 23 45 67 8901 2345 6789 01 23 45 67
+  # 81 0c 04 .. 0101 a001 a5cf xx 00 zz zz
+
+  my $sensor= substr($msg, 20, 2);
+  my $dev= "a5cf $sensor";
+
+  my $def= $modules{BS}{defptr}{$dev};
+  if(!defined($def)) {
+    $sensor =~ s/^0//; 
+    Log3 $hash, 3, "BS Unknown device $sensor, please define it";
+    return "UNDEFINED BS_$sensor BS $sensor";
+  }
+
+  my $name= $def->{NAME};
+  return "" if(IsIgnored($name));
+
+  my $t= TimeNow();
+
+  my $flags= hex(substr($msg, 24, 1)) & 0xdc;
+  my $value= hex(substr($msg, 25, 3)) & 0x3ff;
+
+  my $RExt= $def->{RExt};
+  my $luxOffset= $def->{luxOffset};
+  my $brightness= $value/10.24; # Vout in percent of reference voltage 1.1V
+
+  # brightness in lux= 100lux*(VOut/RExt/1.8muA)^2;
+  my $VOut= $value*1.1/1024.0;
+  my $temp= $VOut/$RExt/1.8E-6;
+  my $lux= 100.0*$temp*$temp;
+  $lux+= $luxOffset; # add lux offset
+
+  my $state= sprintf("brightness: %.2f  lux: %.0f  flags: %d",
+  	$brightness, $lux, $flags);
+
+  readingsBeginUpdate($def);
+  readingsBulkUpdate($def, "state", $state);
+  #Debug "BS $name: $state";
+  readingsBulkUpdate($def, "brightness", $brightness);
+  readingsBulkUpdate($def, "lux", $lux);
+  readingsBulkUpdate($def, "flags", $flags);
+  readingsEndUpdate($def, 1);
+
+  return $name;
+
+}
+
+#############################
+
+1;
+
+=pod
+=begin html
+
+<a name="BS"></a>
+<h3>BS</h3>
+<ul>
+  The module BS allows to collect data from a brightness sensor through a
+  <a href="#FHZ">FHZ</a> device. For details on the brightness sensor see
+  <a href="http://www.busware.de/tiki-index.php?page=CPM-BS">busware wiki</a>.
+  You can have at most nine different brightness sensors in range of your
+  FHZ.<br>
+  <br>
+
+  The state contains the brightness in % (reading <code>brightness</code>) and
+  the brightness in lux (reading <code>lux</code>). The <code>flags</code>
+  reading is always zero. The meaning of these readings is explained in more
+  detail on the above mentioned wiki page.<br>
+  <br>
+
+  <a name="BSDefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; BS &lt;sensor#&gt; [&lt;RExt&gt;]</code>
+    <br><br>
+
+    <code>&lt;sensor#&gt;</code> is the number of sensor in the brightness
+    sensor address system that runs from 1 to 9.<br>
+    <br>
+    <code>&lt;RExt&gt;</code> is the value of the resistor on your brightness
+    sensor in &Omega; (Ohm). The brightness reading in % is proportional to the resistance, the
+    lux reading is proportional to the resistance squared. The value is
+    optional. The default resistance is RExt= 50.000&Omega;.<br>
+    <br>
+
+    Example:<br>
+    <ul>
+      <code>define bs1 BS 1 40000</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="BSset"></a>
+  <b>Set </b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="BSget"></a>
+  <b>Get</b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="BSattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#model">model</a> (bs)</li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+</ul>
+
+
+=end html
+=cut

+ 497 - 0
fhem/core/FHEM/09_CUL_FHTTK.pm

@@ -0,0 +1,497 @@
+#
+# 09_CUL_FHTTK.pm
+#
+# A module for FHEM to handle ELV's FHT80 TF-type sensors
+# written by Kai 'wusel' Siering, 2009-11-06 with help
+# from previously written FHEM code as well as members
+# of fhem-users at googlegroups.com! Thanks, guys!
+#
+# e-mail: wusel+source at uu punkt org
+#
+# This module reads, despite setting an IODev explicitely,
+# from any (CUL-) source and drops any identical message
+# arriving within 5 seconds. It does handle the automatic
+# retransmission of FHT80 TF as well as concurrent recep-
+# tion from multiple sources; in my system, it could happen
+# that the CUL in the same room "overhears" a telegram from
+# FHT80 TF (most likely due to other messages sent/received
+# at the same time) but the one downstairs still picks it up.
+# My implementation should be safe for the device in question,
+# if you see problems, the "only on this IODev"-code is still
+# in place but commented out.
+#
+#
+# Note: The sensor in question is named "FHT80 TF",
+# in it's (formerly current, now old) design it looks
+# similar to "FS20 TFK" but operates differently.
+#
+# FHT80 TF is designed to serve as a sensor to FHT80 B,
+# only the B receives TF's transmissions (after made
+# known to each FHT80 B) normally. The B then, if in-
+# structed that way, turns down the heating while any
+# of the TFs known to it signal "Window open". The TF
+# transmits about every 255 seconds a telegram stating
+# whether or nor the (reed-) contact is open (which
+# means Window or Door, relevant for heating, open)
+# and whether the battery is still full enough.
+#
+# The FS20 TFK on the other hand just directly addresses
+# another FS20 device on opening/closing of it's (reed-)
+# contact.
+#
+# Finally, the HMS100 TFK is designed to notify a HMS-
+# central about opened/closed contacts immediately,
+# but you can't directly address FS20 devices ...
+#
+# So, to notify e. g. FHEM instantly about opening
+# or closure of doors/windows, your best buy might be
+# an HMS100 TFK (as of this writing EUR 29,95 @ ELV).
+# You could use an FS20 TFK as well (EUR 34,95 @ ELV),
+# that way you could directly have FS20 switches act
+# on opened/closed doors or windows in parallel or
+# even without FHEM. The FHT80 TF (as eQ-3 FHT 80 TF
+# currently for EUR 14,95 available @ ELV) only sends
+# out a status telegram every ca. 2,5 minutes, so it's
+# ok for seeing where one might have left a window
+# open before leaving the house but by no means suit-
+# able for any alerting uses (unless a delay of said
+# amount of time doesn't matter, of course ;)).
+#
+# in charge of code: Matscher 
+#
+# $Id: 09_CUL_FHTTK.pm 12306 2016-10-09 18:29:18Z matscher $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+my %fhttfk_codes = (
+    "02" => "Window:Closed",
+    "82" => "Window:Closed",
+    "01" => "Window:Open",
+    "81" => "Window:Open",
+    "0c" => "Sync:Syncing",
+    "91" => "Window:Open, Low Batt",
+    "11" => "Window:Open, Low Batt",
+    "92" => "Window:Closed, Low Batt",
+    "12" => "Window:Closed, Low Batt",
+    "0f" => "Test:Success");
+
+# -wusel, 2009-11-09: Map retransmission codes to major (8x) ones (0x)
+#                     As I'm somewhat lazy, I just list all codes from
+#                     %fhttfk_codes and map them to their major one.
+#                     (FIXME: it would be sufficient to have %fhttfk_codes
+#                     only list these major, "translated" ones.)
+my %fhttfk_translatedcodes = (
+    "01" => "01",
+    "11" => "11",
+    "12" => "12",
+    "02" => "02",
+    "0c" => "0c",
+    "0f" => "0f",
+    "81" => "01",
+    "82" => "02",
+    "91" => "11",
+    "92" => "12");
+
+# set
+my %fhttfk_c2b;	# command->button hash
+my %canset = (
+  "01" => "Open",
+  "02" => "Closed",
+  "0c" => "Pair",
+  "ff" => "ReSync");
+
+# -wusel, 2009-11-06
+#
+# Parse messages from FHT80TK, normally interpreted only by FHT80
+#
+# Format as follows: "TCCCCCCXX" with CCCCCC being the id of the
+# sensor in hex, XX being the current status: 02/82 is Window
+# closes, 01/81 is Window open, 0C is synchronization, ?? is the
+# battery low warning. FIXME!
+
+
+#############################
+sub
+CUL_FHTTK_Initialize($)
+{
+  my ($hash) = @_;
+  
+  foreach my $k (keys %canset) {
+  my $v = $canset{$k};
+  $fhttfk_c2b{$v} = $k;
+  }
+
+  $hash->{Match}     = "^T[A-F0-9]{8}";
+  $hash->{SetFn}     = "CUL_FHTTK_Set";
+  $hash->{DefFn}     = "CUL_FHTTK_Define";
+  $hash->{UndefFn}   = "CUL_FHTTK_Undef";
+  $hash->{ParseFn}   = "CUL_FHTTK_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 showtime:0,1 " .
+                        "model:FHT80TF,FHT80TF-2,dummy ".
+                        $readingFnAttributes;
+  $hash->{AutoCreate}=
+     { "CUL_FHTTK.*" => { GPLOT => "fht80tf:Window,", FILTER => "%NAME" } };
+
+}
+
+#############################
+
+sub
+CUL_FHTTK_Set($@)
+{
+  my ($hash, @a) = @_;
+  my $ret = "";
+  
+  return "\"set $a[0]\" needs at least two parameters" if(@a < 2);
+  
+  my $name = shift(@a);
+  my $opt = shift @a;
+  
+  # suppress SET option
+  if(defined($attr{$name}) && defined($attr{$name}{"model"})) {
+    if($attr{$name}{"model"} ne "dummy") {
+      return $ret;
+    }
+  }
+  else {
+    return $ret;
+  }
+    
+  my $value = join("", @a);
+  
+  Log3 $name, 5, "CUL_FHTTK ($name) option: $opt and value: $value";
+  
+  if(!defined($fhttfk_c2b{$opt})) {
+    my @cList = keys %fhttfk_c2b;
+    return "Unknown argument $opt ($value), choose one of " . join(" ", @cList);
+  }
+  
+  if ($opt eq "Open" ) {
+    Log3 $name, 3, "CUL_FHTTK ($name) changed window state to open.";
+    IOWrite($hash, "", sprintf("T%s01", $hash->{CODE})); # 0x01 - open or 0x81
+
+  } elsif ($opt eq "Closed" ) {
+    Log3 $name, 3, "CUL_FHTTK ($name) changed window state to closed.";
+    
+    IOWrite($hash, "", sprintf("T%s02", $hash->{CODE})); # 0x02 - closed or 0x82
+
+  } elsif($opt eq "Pair" ) {
+    Log3 $name, 3, "CUL_FHTTK ($name) pairing with FHT80b.";
+
+    IOWrite($hash, "", sprintf("T%s0c", $hash->{CODE})); # 0x0c - sync
+    # window state switch to closed through cul FW implementation
+    $opt = "Closed";
+
+  } elsif($opt eq "ReSync" ) {
+    Log3 $name, 3, "CUL_FHTTK ($name) resyncing with FHT80b.";
+
+    IOWrite($hash, "", sprintf("T%s%s", $hash->{CODE}, $fhttfk_c2b{$opt})); # 0xff - ReSync
+    # window state switch to closed through cul FW implementation
+    $opt = "Closed";
+    
+  } else {
+    return "Unknown argument $a[1], choose one of Pair ReSync Open Closed"
+  }
+  
+  # update new state 
+  readingsSingleUpdate($hash, "state", $opt, 1);
+  readingsSingleUpdate($hash, "Window", $opt, 1);
+   
+  return $ret;
+}
+
+#############################
+sub
+CUL_FHTTK_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  my $u= "wrong syntax: define <name> CUL_FHTTK <sensor>";
+  return $u if((int(@a)< 3) || (int(@a)>3));
+
+  my $name     = $a[0];
+  my $sensor   = lc($a[2]);
+  if($sensor !~ /^[0-9a-f]{6}$/) {
+    return "wrong sensor specification $sensor, need a 6 digit hex number!";
+  }
+  
+  $hash->{CODE} = $sensor;
+  $modules{CUL_FHTTK}{defptr}{$sensor} = $hash;
+
+  AssignIoPort($hash);
+  return undef;
+}
+
+
+#############################
+sub
+CUL_FHTTK_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{CUL_FHTTK}{defptr}{$hash->{CODE}}) if($hash && $hash->{CODE});
+  return undef;
+}
+
+
+#############################
+sub
+CUL_FHTTK_Parse($$)
+{
+  my ($hash, $msg) = @_;
+
+  my $sensor= lc(substr($msg, 1, 6));
+  my $def   = $modules{CUL_FHTTK}{defptr}{$sensor};
+  if(!$def) {
+    Log3 $hash, 1, "FHTTK Unknown device $sensor, please define it";
+    return "UNDEFINED CUL_FHTTK_$sensor CUL_FHTTK $sensor";
+  }
+
+  my $name  = $def->{NAME};
+  my $state = lc(substr($msg, 7, 2));
+
+  return "" if(IsIgnored($name));
+
+  if(!defined($fhttfk_translatedcodes{$state})) {
+      Log3 $name, 1, sprintf("FHTTK $def Unknown state $state");
+      $defs{$name}{READINGS}{"Unknown"}{VAL} = $state;
+      $defs{$name}{READINGS}{"Unknown"}{TIME} = TimeNow();
+      return "";
+  }
+
+  $state=$fhttfk_translatedcodes{$state};
+  # PREVIOUS
+  # FIXME: Message regarded as similar if last char is identical;
+  # sure that's always the differentiator? -wusel, 2009-11-09
+  if(defined($defs{$name}{PREV}{TIMESTAMP})) {
+      if($defs{$name}{PREV}{TIMESTAMP} > time()-5) {
+         if(defined($defs{$name}{PREV}{STATE})) {
+             if($defs{$name}{PREV}{STATE} eq $state) {
+                 Log3 $name, 4, sprintf("FHTTK skipping state $state as last similar telegram was received less than 5 (%s) secs ago", $defs{$name}{PREV}{STATE}, time()-$defs{$name}{PREV}{TIMESTAMP});
+                 return "";
+             }
+         }
+      }
+  }
+  
+  if (! defined($defs{$name}{READINGS}{"Previous"})) {
+    $defs{$name}{READINGS}{"Previous"}{VAL} = "";
+    $defs{$name}{READINGS}{"Previous"}{TIME} = "";
+  }
+  
+  if (defined($defs{$name}{PREV}{STATE}) && $defs{$name}{PREV}{STATE} ne $state) {
+    my $prevState = $defs{$name}{PREV}{STATE};
+    my ($windowReading,$windowState) = split(/:/, $fhttfk_codes{$prevState});
+    $defs{$name}{READINGS}{"Previous"}{VAL} = $windowState if defined($windowState) && $windowState ne "";
+    $defs{$name}{READINGS}{"Previous"}{TIME} = TimeNow();
+  }
+ 
+  $def->{PREVTIMESTAMP} = defined($defs{$name}{PREV}{TIMESTAMP})?$defs{$name}{PREV}{TIMESTAMP}:time();
+  $def->{PREVSTATE} = defined($def->{STATE})?$def->{STATE}:"Unknown";
+  $defs{$name}{PREV}{STATE}=$state;
+  
+  #
+  # from here readings are effectively updated
+  #
+  readingsBeginUpdate($def);
+  
+  #READINGS
+  my ($reading,$val) = split(/:/, $fhttfk_codes{$state});
+  readingsBulkUpdate($def, $reading, $val);
+
+  $defs{$name}{PREV}{TIMESTAMP} = time();
+  # -wusel, 2009-11-09: According to http://fhz4linux.info/tiki-index.php?page=FHT+protocol,
+  #                     FHT80TF usually transmitts between 60 and 240 seconds. (255-256 sec in
+  #                     my experience ...) If we got no fresh data for over 5 minutes (300 sec),
+  #                     flag this.
+  if($defs{$name}{PREV}{TIMESTAMP}+720 < time()) {
+      readingsBulkUpdate($def, "Reliability", "dead");
+  } elsif($defs{$name}{PREV}{TIMESTAMP}+600 < time()) {
+      readingsBulkUpdate($def, "Reliability", "low");
+  } elsif($defs{$name}{PREV}{TIMESTAMP}+300 < time()) {
+      readingsBulkUpdate($def, "Reliability", "medium");
+  } else {
+      readingsBulkUpdate($def, "Reliability", "ok");
+  }
+  # Flag the battery warning separately
+  if($state eq "11" || $state eq "12") {
+    readingsBulkUpdate($def, "Battery", "Low");
+  } else {
+    readingsBulkUpdate($def, "Battery", "ok");
+  }
+  #CHANGED
+  readingsBulkUpdate($def, "state", $val);
+
+  $def->{OPEN} = lc($val) eq "open" ? 1 : 0;
+  Log3 $name, 4, "FHTTK Device $name ($reading: $val)";
+
+  #
+  # now we are done with updating readings
+  #
+  readingsEndUpdate($def, 1);
+  
+  return $def->{NAME};
+}
+
+#############################
+
+1;
+
+=pod
+=item summary    support for the window sensor fht80tf and fht80tf-2
+=item summary_DE Einbindung des fht80tf und fht80tf-2 Fensterkontaktes
+=begin html
+
+<a name="CUL_FHTTK"></a>
+<h3>CUL_FHTTK</h3>
+<ul>
+  This module handles messages from the FHT80 TF "Fenster-T&uuml;r-Kontakt" (Window-Door-Contact)
+  which are normally only acted upon by the <a href="#FHT">FHT80B</a>. With this module,
+  FHT80 TFs are in a limited way (see <a href="http://fhz4linux.info/tiki-index.php?page=FHT+protocol">Wiki</a>
+  for detailed explanation of TF's mode of operation) usable similar to HMS100 TFK. The name
+  of the module was chosen as a) only CUL will spill out the datagrams and b) "TF" designates
+  usually temperature+humidity sensors (no clue, why ELV didn't label this one "TFK" like with
+  FS20 and HMS).<br><br>
+  As said before, FHEM can receive FHT80 TF radio (868.35 MHz) messages only through an
+  <a href="#CUL">CUL</a> device, so this must be defined first.
+  <br><br>
+  With the latest build on <a href="http://sourceforge.net/p/culfw/code/HEAD/tree/trunk/culfw/Devices/">SVN</a> 
+  or next official version 1.62 or higher, it is possible to send out FHT80 TF data with a CUL or simular 
+  devices. So it can be simulate up to four window sensor with one device 
+  (see <a href="http://www.fhemwiki.de/wiki/CUL_FHTTK">FHEM Wiki</a>). To setup a window sensor, you have to
+  add and/or change the attribute "model" to dummy. The 6 digit hex number must not equal to FHTID.<br><br>
+
+  <a name="CUL_FHTTKdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_FHTTK &lt;devicecode&gt;</code>
+    <br><br>
+
+    <code>&lt;devicecode&gt;</code> is a six digit hex number, given to the FHT80 TF during
+    production, i. e. it is not changeable. (Yes, it keeps this code after changing batteries
+    as well.)<br>
+
+    Examples:
+    <ul>
+      <code>define TK_TEST CUL_FHTTK 965AB0</code>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="CUL_FHTTKset"></a>
+  <b>Set</b>
+    <ul> Only available, if model is set to dummy.<br><br>
+    <code>set &lt;name&gt; &lt;value&gt;</code>
+    <br><br>
+    where <code>value</code> is one of:<br>
+    <ul><code>
+      Pair     # start pairing with FHT80B (activate FHT80B sync mode before) - state after pairing is Closed<br>
+      Closed   # set window state to Closed<br>
+      Open     # set window state to Open<br>
+      ReSync   # resync virtual sensor with FHT80b after a reset of CUL device. In other words, perform a virtual
+                 battery exchange to synchronize the sensor with FHT80b device again. (at the moment, only 
+                 available with prototype cul_fw - see forum 55774)<br>
+    </code></ul>
+    </ul>
+    <br>
+
+  <b>Get</b>
+   <ul> No get implemented yet ...
+   </ul><br>
+
+  <a name="CUL_FHTTKattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#verbose">verbose</a></li><br>
+    <li><a href="#model">model</a><br>Possible values are: FHT80TF, FHT80TF-2, dummy (value, which allow to simulate a window sensor)</li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+</ul>
+
+=end html
+=begin html_DE
+
+<a name="CUL_FHTTK"></a>
+<h3>CUL_FHTTK</h3>
+<ul>
+  Dieses Modul hantiert die empfangen Daten von FHT80 TF "Fenster-T&uuml;r-Kontakt" Sensoren, welche 
+  normalerweise nur mit den <a href="#FHT">FHT80B</a> Geräten kommunizieren. Mit diesen Modul k&ouml;nnen 
+  FHT80 TFs in eingeschr&auml;nkter Weise &auml;hnlich wie HMS TFK Sensoren benutzt werden (weitere 
+  Informationen sind unter <a href="http://fhz4linux.info/tiki-index.php?page=FHT+protocol">Wiki</a> zu lesen).
+  Der name des FHEM Moduls wurde so gewählt, weil a) nur der CUL die Daten empfangen kann und b) "TF" normalerweise
+  Temperatur- und Feuchtigkeitssensoren suggeriert. (Keine Ahnung, warum ELV diesen Sensor nicht TFK genannt hat, 
+  wie die Sensoren von FS20 und HMS).
+  <br><br>
+  <a href="#CUL">CUL</a> device muss vorhr definiert sein.
+  <br><br>
+  Mit dem letzten Build auf <a href="http://sourceforge.net/p/culfw/code/HEAD/tree/trunk/culfw/Devices/">SVN</a> 
+  oder mit der n&auml;chsten offiziellen Version 1.62 oder h&ouml;her, ist es m&ouml;glich, FHT80 TF Daten zu senden. 
+  M&ouml;glich mit einem CUL oder &auml;hnlichen Ger&auml;ten. So k&ouml;nnen bis zu vier Fenstersensoren mit einem Ger&auml;t
+  simuliert werden (siehe <a href="http://www.fhemwiki.de/wiki/CUL_FHTTK">FHEM Wiki</a>). Es muss lediglich das Attribut model mit dem 
+  Wert "dummy" hinzugef&uuml;gt oder ge&auml;ndert werden. Wichtig: Der Devicecode sollte nicht der FHTID entsprechen.<br><br>
+
+  <a name="CUL_FHTTKdefine"></a>
+  <b>D</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_FHTTK &lt;devicecode&gt;</code>
+    <br><br>
+
+    <code>&lt;devicecode&gt;</code> Ist eine sechstellige Hexadezimalzahl, welche zum Zeitpunkt der Produktion 
+  des FHT80 TF gegeben wurde. Somit ist diese auch nicht mehr &auml;nderbar und bleibt auch nach einem Batteriewechsel 
+  erhalten.<br>
+
+    Examples:
+    <ul>
+      <code>define TK_TEST CUL_FHTTK 965AB0</code>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="CUL_FHTTKset"></a>
+  <b>Set</b>
+    <ul> Nur vorhanden, wenn das Attribut model mit dummy definiert wurde.<br><br>
+    <code>set &lt;name&gt; &lt;value&gt;</code>
+    <br><br>
+    wobei <code>value</code> folgendes sein kann:<br>
+    <ul><code>
+      Pair     # startet das Anlernen an das FHT80B (FHT80B muss sich im Sync mode befinden) - danach wird der state auf "Closed" gesetzt<br>
+      Closed   # setzt den Fensterstatus zu Closed<br>
+      Open     # setzt den Fensterstatus zu Open<br>
+      ReSync   # neu synchronisieren des virtuellen Sensor mit dem FHT80b Module. Damit wird ein virtueller Batteriewechsel symuliert und der angelernte
+                 Sensor wieder aufsynchronisiert. (aktuell nur mit Prototyp CUL FW verfügbar Forum 55774)<br>
+    </code></ul>
+    </ul>
+    <br>
+
+  <b>Get</b>
+  <ul> N/A </ul>
+  <br>
+
+  <a name="CUL_FHTTKattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#verbose">verbose</a></li><br>
+    <li><a href="#model">model</a><br>M&ouml;gliche Werte sind: FHT80TF, FHT80TF-2, dummy (zum simulieren eines Fensterkontaktes)</li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+</ul>
+
+=end html_DE
+=cut

+ 251 - 0
fhem/core/FHEM/09_USF1000.pm

@@ -0,0 +1,251 @@
+#
+#
+# 09_USF1000.pm
+# written by Dr. Boris Neubert 2009-06-20
+# e-mail: omega at online dot de
+#
+##############################################
+# $Id: 09_USF1000.pm 3830 2013-08-31 17:09:10Z borisneubert $
+package main;
+
+use strict;
+use warnings;
+
+my $PI= 3.141592653589793238;
+
+my $dev= "a5ce aa";
+
+#############################
+sub
+USF1000_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^81..(04|0c)..0101a001a5ceaa00....";
+  $hash->{DefFn}     = "USF1000_Define";
+  $hash->{UndefFn}   = "USF1000_Undef";
+  $hash->{ParseFn}   = "USF1000_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 showtime:0,1 " .
+                        "model:usf1000s " . $readingFnAttributes;
+
+}
+
+
+#############################
+sub
+USF1000_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  my $u= "wrong syntax: define <name> USF1000 geometry";
+  my $g= "wrong geometry for USF1000";
+
+  # geometry (units: meter)
+  #	 cub	length width height offset	cuboid			3+4
+  #	 cylv	diameter height offset		vertical cylinder	3+3
+  # the offset is measured from the TOP of the box!
+
+  return $u if(int(@a)< 6);
+
+  my $name	= $a[0];
+  my $geometry	= $a[2];
+
+  if($geometry eq "cub") {
+  	# cuboid
+  	return $g if(int(@a)< 7);
+  	$hash->{GEOMETRY}= $geometry;
+  	$hash->{LENGTH}=   $a[3];
+  	$hash->{WIDTH}=    $a[4];
+  	$hash->{HEIGHT}=   $a[5];
+  	$hash->{OFFSET}=   $a[6];
+  	$hash->{CAPACITY}= int($hash->{LENGTH}*$hash->{WIDTH}*$hash->{HEIGHT}*100.0+0.5)*10.0;
+  } elsif($geometry eq "cylv") {
+  	# vertical cylinder
+  	return $g if(int(@a)< 6);
+  	$hash->{GEOMETRY}= $geometry;
+  	$hash->{DIAMETER}= $a[3];
+  	$hash->{HEIGHT}=   $a[4];
+  	$hash->{OFFSET}=   $a[5];
+  	$hash->{CAPACITY}= int($PI*$hash->{DIAMETER}*$hash->{DIAMETER}/4.0*$hash->{HEIGHT}*100.0+0.5)*10.0;
+  } else {
+  	 return $g;
+  }
+
+  $modules{USF1000}{defptr}{$dev} = $hash;
+  AssignIoPort($hash);
+}
+
+#############################
+sub
+USF1000_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{USF1000}{defptr}{$dev});
+  return undef;
+}
+
+#############################
+sub
+USF1000_Parse($$)
+{
+  my ($hash, $msg) = @_;	# hash points to the FHZ, not to the USF1000
+
+  if(!defined($modules{USF1000}{defptr}{$dev})) {
+    Log3 $hash, 3, "USF1000 Unknown device, please define it";
+    return "UNDEFINED USF1000 USF1000 cylv 1 1 0.5";
+  }
+
+  my $def= $modules{USF1000}{defptr}{$dev};
+  my $name= $def->{NAME};
+
+  return "" if(IsIgnored($name));
+
+  my $t= TimeNow();
+
+  # Msg format:
+  # 01 23 45 67 8901 2345 6789 01 23 45 67
+  # 81 0c 04 .. 0101 a001 a5ce aa 00 cc xx
+
+  my $cc= substr($msg, 24, 2);
+  my $xx= substr($msg, 26, 2);
+
+
+  my $lowbattery= (hex($cc) & 0x40 ? 1 : 0);
+  my $testmode=   (hex($cc) & 0x80 ? 1 : 0);
+  my $distance=   hex($xx)/100.0; # in meters
+  my $valid= (($distance>0.00) && ($distance<2.55));
+
+  readingsBeginUpdate($def);
+
+  if($valid) {
+  	my $wlevel  =   $def->{HEIGHT}-($distance-$def->{OFFSET}); # water level
+
+	my $geometry= $def->{GEOMETRY};
+  	my $capacity= $def->{CAPACITY}; # capacity of tank (for distance= offset) in liters
+  	my $volume;   # current volume in tank in liters
+  	my $flevel;	# fill level in percent
+
+  	if($geometry eq "cub") {
+  		# cuboid
+  		$volume  = $def->{LENGTH}*$def->{WIDTH}*$wlevel*1000.0;
+  	} elsif($geometry eq "cylv") {
+  		# vertical cylinder
+  		$volume  = $PI*$def->{DIAMETER}*$def->{DIAMETER}/4.0*$wlevel*1000.0;
+  	} else {
+  		return 0;
+  	}
+
+  	$flevel  = int($volume/$capacity*100.0+0.5);
+  	$volume= int($volume/10.0+0.5)*10.0;
+
+  	if($flevel>-5) {
+		# reflections may lead to false reading (distance too large)
+		# the meaningless results are suppressed
+
+		my $state= sprintf("v: %d  V: %d", $flevel, $volume);
+
+                readingsBulkUpdate($def, "state", $state);
+                readingsBulkUpdate($def, "distance", $distance);
+                readingsBulkUpdate($def, "level", $flevel);
+                readingsBulkUpdate($def, "volume", $volume);
+		
+		#Debug "USF1000 $name: $state";
+
+	}
+  }
+
+  my $warnings= ($lowbattery ? "Battery low" : "");
+  if($testmode) {
+  	$warnings.= "; " if($warnings);
+  	$warnings.= "Test mode";
+  }
+  $warnings= $warnings ? $warnings : "none";
+
+  readingsBulkUpdate($def, "warnings", $warnings);
+
+  readingsEndUpdate($def, 1);
+
+  return $name;
+
+}
+
+#############################
+
+1;
+
+=pod
+=begin html
+
+<a name="USF1000"></a>
+<h3>USF1000</h3>
+<ul>
+  Fhem can receive your tank's fill level from the USF1000S device
+  through a <a href="#FHZ">FHZ</a> device, so one must be defined first.
+  The state contains the fill level in % (lower case v in the device state)
+  and the current volume in liters (upper case V in the device state).
+  Measured distance to the liquid's surface, fill level, volume and warnings
+  (Test mode, Battery low) are available. Due to the design of the USF1000S
+  protocol, you can have only one USF1000S in range of your FHZ as these
+  devices cannot be distinguished.<br>
+  <br>
+
+  <a name="USF1000Define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; USF1000 &lt;geometry&gt;</code>
+    <br><br>
+
+    <code>&lt;geometry&gt;</code> determines the form of the tank and the
+    position of the sensor. The following geometries are currently
+    supported:<br><br>
+    <ul>
+        <li><code>cub &lt;length&gt; &lt;width&gt; &lt;height&gt; &lt;offset&gt;</code></li>
+        <li><code>cylv &lt;diameter&gt; &lt;height&gt; &lt;offset&gt;</code></li>
+    </ul>
+    <br>
+    <code>cub</code> stands for a cuboid whose base is &lt;length&gt; &times; &lt;width&gt;.
+    <code>cylv</code> stands for a vertical cylinder whose diameter is &lt;diameter&gt;.
+    &lt;height&gt; is the distance of the surface of the liquid from the ground
+    if the tank is full. &lt;offset&gt; is the distance of the sensor relative to
+    the surface of the liquid. All quantities are expressed in meters.<br>
+    <br>
+
+    Example:<br>
+    <ul>
+      <code>define MyTank USF1000 cylv 2 1 0.3</code>: a cylindrical water tank with
+      2 meters diameter. The water stands 1 meter high if the tank is full. The
+      sensor is fixed 1,3 meters above ground.<br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="USF1000set"></a>
+  <b>Set </b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="USF1000get"></a>
+  <b>Get</b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="USF1000attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#model">model</a> (usf1000s)</li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+</ul>
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 12118 - 0
fhem/core/FHEM/10_CUL_HM.pm


+ 484 - 0
fhem/core/FHEM/10_CUL_IR.pm

@@ -0,0 +1,484 @@
+######################################################
+# CUL InfraRed handler as FHM-Module
+#
+# (c) Olaf Droegehorn / DHS-Computertechnik GmbH
+# 
+# Published under GNU GPL License
+######################################################
+# $Id: 10_CUL_IR.pm 3580 2013-08-02 16:17:38Z betateilchen $
+package main;
+
+use strict;
+use warnings;
+
+sub CUL_IR_Define($$);
+sub CUL_IR_Undef($$);
+sub CUL_IR_Initialize($);
+sub CUL_IR_Parse($$);
+sub CUL_IR_SendCmd($$);
+sub CUL_IR_Set($@);
+sub CUL_IR_Attr(@);
+
+
+my %sets = (
+  "irLearnForSec" => "",
+  "irSend" => ""
+);
+
+
+sub
+CUL_IR_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^I............";
+  $hash->{DefFn}     = "CUL_IR_Define";
+  $hash->{UndefFn}   = "CUL_IR_Undef";  
+  $hash->{ParseFn}   = "CUL_IR_Parse";
+  $hash->{SetFn}     = "CUL_IR_Set";
+  $hash->{AttrFn}       = "CUL_IR_Attr";  
+  $hash->{AttrList}  = "do_not_notify:1,0 ignore:0,1 " .
+                       "loglevel:0,1,2,3,4,5,6 learnprefix learncount " .
+                       "Button.* Group.* irReceive:OFF,ON,ON_NR";
+}
+
+
+#############################
+sub
+CUL_IR_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+  my $testhash = "";
+
+  my $u = "wrong syntax: define <name> CUL_IR <IODEV:CUL/CUN_Device>";
+
+  return $u if(int(@a) < 3);
+
+  #Assign CUL/CUN within definition as IODev
+  for my $p (sort { $defs{$b}{NR} <=> $defs{$a}{NR} } keys %defs) {
+  
+      my $cl = $defs{$p}{Clients};
+      $cl = $modules{$defs{$p}{TYPE}}{Clients} if(!$cl);
+
+    if((defined($cl) && $cl =~ m/:CUL_IR:/) &&
+       $defs{$p}{NAME} eq $a[2]) {    
+          $hash->{IODev} = $defs{$p}; 
+          last;  
+        }
+    }
+    if(!$hash->{IODev}) {
+      Log 3, "No I/O device found for $hash->{NAME}";
+      return "Wrong IODev specified or IODev doesn't support CUL_IR";
+  }
+  
+  #Check if we have already an CUL_IR Device for IODEV
+  foreach my $name (keys %{ $modules{CUL_IR}{defptr} }) {
+     $testhash = ($modules{CUL_IR}{defptr}{$name})
+        if($modules{CUL_IR}{defptr}{$name}->{IODev} == $hash->{IODev});
+  }
+    if ($testhash) {    #found another CUL_IR for this IODEV!!
+      Log 2, "CUL_IR already defined for this I/O device";
+      return "CUL_IR already defined for this I/O device, it's: $testhash->{NAME}";
+    }  
+
+    #Everything is fine, let's finish definition
+  $modules{CUL_IR}{defptr}{("IR_" . $a[2])} = $hash;
+  $hash->{STATE} = "Initialized";
+  if (!$attr{$hash->{NAME}}{'learnprefix'}) {
+      $attr{$hash->{NAME}}{'learnprefix'} = "A";
+  }
+  if (!$attr{$hash->{NAME}}{'learncount'}) {
+      $attr{$hash->{NAME}}{'learncount'} = 0;
+  }
+  
+     Log 4, "Sending $hash->{IODev}->{NAME}: Ir";
+  my $msg = CallFn($hash->{IODev}->{NAME}, "GetFn", $hash->{IODev}, (" ", "raw", "Ir"));
+     Log 4, "Answer from $hash->{IODev}->{NAME}: $msg";
+
+  if ($msg =~ m/raw => 00/) {
+        $hash->{irReceive} = "OFF";
+    } elsif ($msg =~ m/raw => 01/) {
+         $hash->{irReceive} = "ON";
+     } elsif ($msg =~ m/raw => 02/) {
+         $hash->{irReceive} = "ON_NR";
+    } elsif ($msg =~ m/No answer/) {
+          $hash->{irReceive} = "NoAnswer";
+  } else {
+      Log 2, "CUL_IR IODev device didn't answer Ir command correctly: $msg";
+      $hash->{irReceive} = "UNKNOWN";
+  }
+   
+  return undef;
+}
+
+#############################
+sub
+CUL_IR_Undef($$)
+{
+  my ($hash, $name) = @_;
+
+  # As after a rename the $name my be different from the $defptr{$n}
+  # we look for the hash.
+  foreach my $name (keys %{ $modules{CUL_IR}{defptr} }) {
+    delete($modules{CUL_IR}{defptr}{$name})
+      if($modules{CUL_IR}{defptr}{$name} == $hash);
+  }
+
+  return undef;
+}
+
+
+#############################
+sub
+CUL_IR_Parse($$)
+{
+  my ($iohash, $msg) = @_;
+  my $myhash = "";
+  my $found  = 0;
+      
+  foreach my $name (keys %{ $modules{CUL_IR}{defptr} }) {
+     $myhash = ($modules{CUL_IR}{defptr}{$name})
+        if($modules{CUL_IR}{defptr}{$name}->{IODev} == $iohash);
+  }
+
+    if(!$myhash) {
+        Log 4, "IR-Message from $iohash->{NAME}: $msg";
+      Log 2, "No CUL_IR device found for $iohash->{NAME}, please define a new one";
+      return "UNDEFINED CUL_IR_$iohash->{NAME} CUL_IR $iohash->{NAME}";
+  }
+
+     Log 4, "IR-Reception: $msg";
+
+    # Search for Button-Group
+  foreach my $name (keys %{ $attr{$myhash->{NAME}} }) {
+       if ($name =~ m/^Group.*/) {
+           my ($ir, $cmd) = split("[ \t]", $attr{$myhash->{NAME}}{$name}, 2);
+           if ($msg =~ m/$ir.*/) {
+              if (!$cmd) {
+                 Log 5, "Group found; IR:$ir Def:-";
+               } else {
+                  Log 5, "Group found; IR:$ir Def:$cmd";
+                  AnalyzeCommandChain(undef, $cmd);
+               }
+           }
+      }
+  }
+
+    # Search for single Button(s)
+  foreach my $name (keys %{ $attr{$myhash->{NAME}} }) {
+       if ($name =~ m/^Button.*/) {
+           my ($ir, $cmd) = split("[ \t]", $attr{$myhash->{NAME}}{$name}, 2);
+           if ($msg eq $ir) {
+               $found++;
+               if (!$cmd) {
+                 Log 5, "Button found; IR:$ir Def:-";
+               } else {
+                 Log 5, "Button found; IR:$ir Def:$cmd";
+                 AnalyzeCommandChain(undef, $cmd);
+               }
+           }
+      }
+  }
+  
+  if (!$found) {    # No Button identified yet !!
+      if (!$myhash->{irLearn}) {  # OK, No Learning, then leave
+          return "";
+      } else {    # Let's learn Buttons
+          my $buttonname = "";
+          if (!$attr{$myhash->{NAME}}{'learncount'}) {
+              $attr{$myhash->{NAME}}{'learncount'} = 0;
+          }
+          if (!$attr{$myhash->{NAME}}{'learnprefix'}) {    # is the learnprefix there?
+              $buttonname = sprintf("Button%03d", $attr{$myhash->{NAME}}{'learncount'});      
+          } else {
+                $buttonname = sprintf("Button%s%03d", $attr{$myhash->{NAME}}{'learnprefix'}, $attr{$myhash->{NAME}}{'learncount'});      
+            }
+            $attr{$myhash->{NAME}}{'learncount'}++;    
+            $attr{$myhash->{NAME}}{$buttonname} = $msg;
+      }
+  }
+
+  return "";
+}
+
+sub
+CUL_IR_RemoveIRLearn($)
+{
+  my $hash = shift;
+  delete($hash->{irLearn});
+}
+
+###################################
+sub
+CUL_IR_Set($@)
+{
+  my ($hash, @a) = @_;
+
+  return "\"set CUL_IR\" needs at least one parameter" if(@a < 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
+      if(!defined($sets{$a[1]}));
+
+  my $name = shift @a;
+  my $type = shift @a;
+  my $arg = join("", @a);
+  my $ll = GetLogLevel($name,3);
+  my $ret = "";
+  my $reccommand = "";
+
+  if($type eq "irLearnForSec") { ####################################
+    return "Usage: set $name irLearnForSec <seconds_active>"
+        if(!$arg || $arg !~ m/^\d+$/);
+    $hash->{irLearn} = 1;
+    InternalTimer(gettimeofday()+$arg, "CUL_IR_RemoveIRLearn", $hash, 1);
+  } 
+  
+  if($type eq "irSend") { ####################################
+    return "Usage: set $name irSend <IR-Code | ButtonName>"
+        if(!$arg || $arg !~ m/^\w+$/); 
+        CUL_IR_SendCmd ($hash, $arg);
+  } 
+
+  return $ret;
+}
+
+    
+###################################
+sub
+CUL_IR_SendCmd($$)
+{
+  my ($hash, $arg) = @_;
+  my $l4 = GetLogLevel($hash->{NAME},4);
+  my $found  = 0;
+  my $ir;
+  my $cmd;
+  my $bcmd;
+
+  if ($arg =~ m/^Button.*/) {        #Argument is a ButtonName
+      # Search for single Button(s)
+      foreach my $name (keys %{ $attr{$hash->{NAME}} }) {
+           if ($name eq $arg) {
+               ($ir, $bcmd) = split("[ \t]", $attr{$hash->{NAME}}{$name}, 2);
+               $found++;
+           }
+      }
+      if (!$found) { 
+          Log 2, "$hash->{NAME}->irSend: No Button found with name $arg, please define/learn a new one";
+          return "$hash->{NAME}: Unknown button name $arg";
+      }
+      $cmd = sprintf("Is%s", substr($ir, 1));
+  } else {
+      if (length ($arg) == 12) {
+          if ($arg  =~ m/^[0-9A-F]+$/) {
+              $cmd = sprintf("Is%s", $arg);
+          } else {
+                  Log 2, "$hash->{NAME}->irSend: Wrong IR-Code";
+                  return "$hash->{NAME}: Wrong IR-Code";
+          }
+      } elsif (length ($arg) == 13) {
+              if (substr($arg, 0, 1) eq "I") {
+                  if (substr($arg, 1) =~ m/^[0-9A-F]+$/) {
+                      $cmd = sprintf("Is%s", substr($arg, 1));
+                  } else {
+                          Log 2, "$hash->{NAME}->irSend: Wrong IR-Code";
+                          return "$hash->{NAME}: Wrong IR-Code";
+                  }
+              } else {
+                  Log 2, "$hash->{NAME}->irSend: Wrong IR-Code";
+                  return "$hash->{NAME}: Wrong IR-Code";
+              }
+      } else {
+              Log 2, "$hash->{NAME}->irSend: Wrong IR-Code";
+              return "$hash->{NAME}: Wrong IR-Code";
+      }
+          
+  }
+  Log $l4, "$hash->{NAME} SEND $cmd";
+  IOWrite($hash, "", $cmd);
+}
+
+sub
+CUL_IR_Attr(@)
+{
+  my @a = @_;
+
+  if($a[2] eq "irReceive") {
+
+    my $name = $a[1];
+    my $hash = $defs{$name};
+    my $reccommand = "";
+
+    if($a[3] eq "OFF") {
+        $reccommand = "00";
+    } elsif ($a[3] eq "ON") {
+        $reccommand = "01";
+    } else {
+        $reccommand = "02";        
+    }
+
+      my $io = $hash->{IODev};
+            
+      Log 4, "Sending $io->{NAME}: Ir$reccommand";
+      my $msg = CallFn($io->{NAME}, "GetFn", $io, (" ", "raw", "Ir".$reccommand));
+      Log 4, "Answer from $io->{NAME}: $msg";
+
+    if ($msg =~ m/raw => 00/) {
+          $hash->{irReceive} = "OFF";
+      } elsif ($msg =~ m/raw => 01/) {
+          $hash->{irReceive} = "ON";
+      } elsif ($msg =~ m/raw => 02/) {
+          $hash->{irReceive} = "ON_NR";
+      } elsif ($msg =~ m/No answer/) {
+          $hash->{irReceive} = "NoAnswer";
+      } else {
+          Log 2, "CUL_IR IODev device didn't answer Ir command correctly: $msg";
+          $hash->{irReceive} = "UNKNOWN";
+      }
+
+    Log 2, "Switched $name irReceive to $hash->{irReceive}";
+
+  }
+ 
+  return undef;
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="CUL_IR"></a>
+<h3>CUL_IR</h3>
+<ul>
+
+  The CUL_IR module interprets Infrared messages received by the CUN/CUNO/CUNOv2/TuxRadio.
+  Those devices can receive Infrared Signals from basically any Remote controller and will transform
+  that information in a so called Button-Code  <br><br>
+
+
+  <a name="CUL_IRdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_IR &lt;<a href="#IODev">IODev</a>&gt;</code> <br>
+    <br>
+    &lt;<a href="#IODev">IODev</a>&gt; is the devicename of the IR-receivung device, e.g. CUNO1.<br><br>
+
+    Your definition should look like E.g.:
+    <pre>
+    define IR-Dev CUL_IR CUNO1</pre>
+  </ul>
+
+  <a name="CUL_IRset"></a>
+  <b>Set</b>
+  <ul>
+    <a name="irLearnForSec"></a>
+    <li>irLearnForSec<br>
+       Sets the CUL_IR device in an IR-Code Learning mode for the given seconds. Any received IR-Code will
+       be stored as a Button attribute for this devices. The name of these attributes is dependent on the two
+       attributes <a href="#CUL_IRattr">learncount</a> and <a href="#CUL_IRattr">learnprefix</a>.<br>
+       Attention: Before learning IR-Codes the CUL_IR device needs to be set in IR-Receiving mode
+       by modifying the <a href="#irReceive">irReceive</a> attribute.
+     </li>
+    <a name="irSend"></a>
+    <li>irSend<br>
+       Sends out IR-commands via the connected IODev. The IR-command can be specified as buttonname according
+	   to <a href="#Button.*">Button.*</a> or as IR-Code directly. If a buttonname is specified, the
+	   corresponding IR-Code will be sent out.<br>
+	   Example: <br>
+   	   <pre>set IR-Dev irSend ButtonA001 </pre>
+	   If defining an IR-Code directly the following Code-Syntax needs to be followed:<br>
+	   <pre>IRCode: &lt;PP&gt;&lt;AAAA&gt;&lt;CCCC&gt;&lt;FF&gt; </pre>
+	   with P = Protocol; A = Address; C = Command; F = Flags<br>
+	   With the Flags you can modify IR-Repetition. Flags between 00-0E will produce
+	   0-15 IR-Repetitions.
+	   You can type the IR-Code as plain as above, or with a heading "I" as learnt for the buttons.<br>
+	   Example: <br>
+   	   <code>set IR-Dev irSend 0A07070F0F02<br>
+	   set IR-Dev irSend I0A07070F0F00 </code>
+
+     </li>
+  </ul>
+
+  <a name="CUL_IRget"></a>
+  <b>Get</b>
+  <ul>N/A</ul>
+
+  <a name="CUL_IRattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#loglevel">loglevel</a></li><br>
+    <li><a href="#irReceive">irReceive</a><br>
+        Configure the IR Transceiver of the &lt;<a href="#IODev">IODev</a>&gt; (the CUNO1). Available
+        arguments are:
+        <ul>
+        <li>OFF<br>
+            Switching off the reception of IR signals. This is the default.</li>
+
+        <li>ON<br>
+            Switching on the reception of IR signals. This is WITHOUT filtering repetitions. This is
+            not recommended as many remote controls do repeat their signals.</li>
+
+        <li>ON_NR<br>
+            Switching on the reception of IR signals with filtering of repetitions. This is
+            the recommended modus operandi.</li>
+        </ul>
+    </li><br>
+
+	<li><a name="Button.*"></a>Button.*<br>
+        Button.* is the wildcard for all learnt IR-Codes. IR-Codes are learnt as Button-Attributes.
+        The name for a learnt Button - IR-Code is compiled out of three elements:<br>
+        <pre>
+        Button&lt;learnprefix&gt;&lt;learncount&gt;
+        </pre>
+        When the CUL_IR device is set into <a href="#irLearnForSec">learning mode</a> it will generate a
+        new button-attribute for each new IR-Code received.This is done according to the following syntax:<br>
+        <pre>
+        &lt;Button-Attribute-Name&gt; &lt;IR-Code&gt;</pre>
+        Examples of learnt button-attributes with EMPTY &lt;learnprefix&gt; and &lt;learncount&gt; starting from 1:<br>
+        <pre>
+        Button001   I02029A000000
+        Button002   I02029A000001</pre>
+        To make sure that something happens when this IR-code is received later on one has to modify the attribute
+        and to add commands as attribute values.
+        Examples:
+        <pre>
+        Button001   I02029A000000   set WZ_Lamp on
+        Button002   I02029A000001   set Switch on</pre>
+        The syntax for this is:
+        <pre>
+        attr &lt;device-name&gt; &lt;attribute-name&gt; &lt;IR-Code&gt; &lt;command&gt;
+        </pre>
+    </li>
+    <li><a name="Group.*"></a>Group.*<br>
+        Group.* is the wildcard for IR-Code groups. With these attributes one can define
+        IR-Code parts, which may match to several Button-IR-Codes.<br>
+        This is done by defining group-attributes that contain only parts of the IR-Code.
+        The syntax is:
+        <pre>
+        &lt;Group-Attribute-Name&gt; &lt;IR-Code&gt;</pre>
+        Examples of a group-attribute is:<br>
+        <pre>
+        Group001   I02029A</pre>
+        With this all IR-Codes starting with I02029A will match the Group001.
+    </li><br>
+    <li><a name="learncount"></a>learncount<br>
+        learncount is used to store the next button-code-number that needs to be learned.
+        By manually modifying this attribute new button sequences can be arranged.
+    </li><br>
+    <li><a name="learnprefix"></a>learnprefix<br>
+        learnprefix is a string which will be added to the button-attribute-name. <br>
+        A button-attribute-name is constructed by:
+        <pre>
+        Button&lt;learnprefix&gt;&lt;learncount&gt;    </pre>
+        If learnprefix is empty the button-attribute-name only contains the term
+        "Button" and the actual number of <a href="#learncount">learncount</a>.
+    </li><br>
+  </ul>
+  <br>
+</ul>
+
+
+=end html
+=cut

+ 654 - 0
fhem/core/FHEM/10_DUOFERNSTICK.pm

@@ -0,0 +1,654 @@
+##############################################
+# $Id: 10_DUOFERNSTICK.pm 12786 2016-12-15 19:15:18Z telekatz $
+
+package main;
+
+use strict;
+use warnings;
+
+sub DUOFERNSTICK_Read($);
+sub DUOFERNSTICK_Ready($);
+
+my %matchList = ( "1:DUOFERN" => "^(06|0F|81).{42}") ;
+                  
+my %sets = ( 
+  "reopen:noArg" => "",
+  "statusBroadcast:noArg" => "",
+  "pair:noArg" => "",
+  "unpair:noArg" => "",
+  "remotePair" => "",
+  "raw" => "",
+);
+
+my $duoInit1            = "01000000000000000000000000000000000000000000";
+my $duoInit2            = "0E000000000000000000000000000000000000000000";
+my $duoSetDongle        = "0Azzzzzz000100000000000000000000000000000000";
+my $duoInit3            = "14140000000000000000000000000000000000000000";
+my $duoSetPairs         = "03nnyyyyyy0000000000000000000000000000000000";
+my $duoInitEnd          = "10010000000000000000000000000000000000000000";
+my $duoACK              = "81000000000000000000000000000000000000000000";
+my $duoStatusRequest    = "0DFF0F400000000000000000000000000000FFFFFF01";
+my $duoStartPair        = "04000000000000000000000000000000000000000000";
+my $duoStopPair         = "05000000000000000000000000000000000000000000";
+my $duoStartUnpair      = "07000000000000000000000000000000000000000000";
+my $duoStopUnpair       = "08000000000000000000000000000000000000000000";
+my $duoRemotePair       = "0D0006010000000000000000000000000000yyyyyy01";
+
+sub DUOFERNSTICK_Initialize($)
+{
+  my ($hash) = @_;
+  
+  require "$attr{global}{modpath}/FHEM/DevIo.pm";
+  
+  $hash->{ReadFn}  = "DUOFERNSTICK_Read";
+  $hash->{ReadyFn} = "DUOFERNSTICK_Ready";
+  $hash->{WriteFn} = "DUOFERNSTICK_Write";
+  $hash->{DefFn}   = "DUOFERNSTICK_Define";
+  $hash->{UndefFn} = "DUOFERNSTICK_Undef";
+  $hash->{SetFn}   = "DUOFERNSTICK_Set";
+  $hash->{NotifyFn}= "DUOFERNSTICK_Notify";
+  $hash->{Clients} = ":DUOFERN:";
+  $hash->{MatchList} = \%matchList; 
+  $hash->{AttrList}= $readingFnAttributes;
+  
+}
+
+#####################################
+sub
+DUOFERNSTICK_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  if(@a < 4 || @a > 5) {
+    my $msg = "wrong syntax: define <name> DUOFERNSTICK devicename DongleSerial";
+    Log3 undef, 2, $msg;
+    return $msg;
+  }
+
+  DevIo_CloseDev($hash);
+
+  my $name = $a[0];
+  my $dev = $a[2];
+  return "wrong DongleSerial format: specify a 6 digit hex value starting with 6F"
+                if(uc($a[3]) !~ m/^6F[a-f0-9]{4}$/i);
+                
+  $hash->{DongleSerial} = uc($a[3]);
+
+  $hash->{DeviceName} = $dev;
+  my $ret = DevIo_OpenDev($hash, 0, "DUOFERNSTICK_DoInit");
+  return $ret;
+}
+
+#####################################
+sub 
+DUOFERNSTICK_setStates($$)
+{
+  my ($hash, $val) = @_;
+  $hash->{STATE} = $val;
+  $val = "disconnected" if ($val eq "closed");
+  setReadingsVal($hash, "state", $val, TimeNow());
+}
+
+#####################################
+sub
+DUOFERNSTICK_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  my $name = $hash->{NAME};
+
+  foreach my $d (sort keys %defs) {
+    if(defined($defs{$d}) &&
+       defined($defs{$d}{IODev}) &&
+       $defs{$d}{IODev} == $hash)
+      {
+        my $lev = ($reread_active ? 4 : 2);
+        Log3 $name, $lev, "deleting port for $d";
+        delete $defs{$d}{IODev};
+      }
+  }
+
+  DevIo_CloseDev($hash);
+  return undef;
+}
+
+
+#####################################
+sub 
+DUOFERNSTICK_Set($@)
+{
+  my ($hash, @a) = @_;
+
+  return "set $hash->{NAME} needs at least one parameter" if(@a < 2);
+
+  my $me   = shift @a;
+  my $cmd  = shift @a;
+  my $arg  = shift @a;
+  my $err;
+  my $buf;
+  
+  return join(" ", sort keys %sets) if ($cmd eq "?");
+
+  if ($cmd eq "reopen") { 
+    return DUOFERNSTICK_Reopen($hash);
+    
+  } elsif ($cmd eq "statusBroadcast") {
+    DUOFERNSTICK_AddSendQueue($hash, $duoStatusRequest);
+    return undef;
+    
+  } elsif ($cmd eq "raw") {
+    return "wrong raw format: specify a 44 digit hex value"
+                if(!$arg || (uc($arg) !~ m/^[a-f0-9]{44}$/i));
+    DUOFERNSTICK_AddSendQueue($hash, $arg);
+    return undef;
+    
+  } elsif ($cmd eq "pair") {
+    DUOFERNSTICK_AddSendQueue($hash, $duoStartPair);
+    $hash->{pair} = 1;
+    delete($hash->{unpair});
+    InternalTimer(gettimeofday()+60, "DUOFERNSTICK_RemovePair", "$hash->{NAME}:RP", 1);
+    return undef;
+    
+  } elsif ($cmd eq "unpair") {
+    DUOFERNSTICK_AddSendQueue($hash, $duoStartUnpair);
+    $hash->{unpair} = 1;
+    delete($hash->{pair});
+    InternalTimer(gettimeofday()+60, "DUOFERNSTICK_RemoveUnpair", "$hash->{NAME}:RU", 1);
+    return undef;
+  
+   } elsif ($cmd eq "remotePair") {
+    return "wrong serial format: specify a 6 digit hex value"
+                if(!$arg || (uc($arg) !~ m/^[a-f0-9]{6}$/i)); 
+    my $buf =  $duoRemotePair;
+    $buf =~ s/yyyyyy/$arg/;      
+    DUOFERNSTICK_AddSendQueue($hash, $buf);
+    return undef;
+      
+  }
+  
+  return "Unknown argument $cmd, choose one of ". join(" ", sort keys %sets); 
+}
+
+
+#####################################
+# called from the global loop, when the select for hash->{FD} reports data
+sub
+DUOFERNSTICK_Read($)
+{
+  my ($hash) = @_;
+  my $buf = "";
+  my $rbuf = DevIo_SimpleRead($hash);
+  
+  return "" if(!defined($rbuf));
+  
+  if ($hash->{PARTIAL} ne "") {
+    RemoveInternalTimer("$hash->{NAME}:FB");
+  }
+  
+  my @array=split('',$rbuf);
+  
+  foreach (@array){
+    $buf .= sprintf "%02x", ord($_) ;
+  }
+   
+  my $name = $hash->{NAME};
+
+  my $duodata = $hash->{PARTIAL};
+  
+  $duodata .= $buf;
+
+  while(length($duodata) >= 44) {
+    my $rmsg;
+    my $me = $hash->{NAME};
+    ($rmsg,$duodata) = unpack("a44 a*", $duodata);
+    Log3 $name, 4, "$me: rx  -> $rmsg";
+    $hash->{PARTIAL} = $duodata; # for recursive calls
+    DUOFERNSTICK_Parse($hash, uc($rmsg)) if($rmsg);
+    $duodata = $hash->{PARTIAL};
+  }
+  $hash->{PARTIAL} = $duodata;
+  
+  my $now = gettimeofday();
+  if ($hash->{PARTIAL} ne "") {
+  InternalTimer($now+0.5, "DUOFERNSTICK_Flush_Buffer", "$hash->{NAME}:FB", 0);
+  }
+}
+
+#####################################
+sub
+DUOFERNSTICK_Write($$)
+{
+  my ($hash,$msg) = @_;
+  my $err;
+  my $buf;
+  
+  my $name = $hash->{NAME};
+  Log3 $name, 5, "$hash->{NAME} sending $msg";
+
+  $msg =~ s/zzzzzz/$hash->{DongleSerial}/;
+  
+  DUOFERNSTICK_AddSendQueue($hash,$msg);
+  
+}
+
+#####################################
+sub
+DUOFERNSTICK_Parse($$)
+{
+  my ($hash, $rmsg) = @_;
+
+  DUOFERNSTICK_SimpleWrite($hash, $duoACK) if($rmsg ne $duoACK);;
+  
+  if($rmsg =~ m/81.{42}/) {
+    DUOFERNSTICK_HandleWriteQueue($hash);
+  }
+  
+  return if($rmsg eq $duoACK);
+  
+  $hash->{RAWMSG} = $rmsg;
+  
+  if($rmsg =~ m/0602.{40}/) {
+    my %addvals = (RAWMSG => $rmsg);
+    Dispatch($hash, $rmsg, \%addvals) if ($hash->{pair});
+    delete($hash->{pair});
+    RemoveInternalTimer($hash);
+    return undef;
+    
+  } elsif ($rmsg =~ m/0603.{40}/) {
+    my %addvals = (RAWMSG => $rmsg);
+    Dispatch($hash, $rmsg, \%addvals) if ($hash->{unpair});
+    delete($hash->{unpair});
+    RemoveInternalTimer($hash);
+    return undef;
+    
+  } elsif ($rmsg =~ m/0FFF11.{38}/) {
+    return undef;
+  
+  } elsif ($rmsg =~ m/81000000.{36}/) {
+    return undef;
+  
+  }
+    
+  my %addvals = (RAWMSG => $rmsg);
+  Dispatch($hash, $rmsg, \%addvals);
+  
+}
+ 
+#####################################
+sub
+DUOFERNSTICK_Ready($)
+{
+  my ($hash) = @_;
+
+  return DevIo_OpenDev($hash, 1, "DUOFERNSTICK_DoInit")
+                if($hash->{STATE} eq "disconnected");
+
+  # This is relevant for windows/USB only
+  my $po = $hash->{USBDev};
+  my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
+  if($po) {
+    ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
+  }
+  return ($InBytes && $InBytes>0);
+}
+
+#####################################
+sub
+DUOFERNSTICK_RemovePair($)
+{
+    my ($name,$id) = split(":",$_[0]);
+    my ($hash) = $defs{$name};
+    
+    DUOFERNSTICK_AddSendQueue($hash, $duoStopPair);
+    
+    delete($hash->{pair});
+    
+    return undef;
+}
+
+#####################################
+sub
+DUOFERNSTICK_RemoveUnpair($)
+{
+    my ($name,$id) = split(":",$_[0]);
+    my ($hash) = $defs{$name};
+    
+    DUOFERNSTICK_AddSendQueue($hash, $duoStopUnpair);
+    
+    delete($hash->{unpair});
+    
+    return undef;
+}
+
+#####################################
+sub
+DUOFERNSTICK_Flush_Buffer($)
+{
+    my ($name,$id) = split(":",$_[0]);
+    
+    if ($defs{$name}{PARTIAL} ne "") {
+      Log3 $name, 4, "$name discard $defs{$name}{PARTIAL}";
+    }
+   
+    $defs{$name}{PARTIAL} ="";
+    
+    return undef;
+}
+
+#####################################
+sub
+DUOFERNSTICK_Reopen($)
+{
+  my ($hash) = @_;
+  DevIo_CloseDev($hash);
+  DevIo_OpenDev($hash, 1, "DUOFERNSTICK_DoInit");
+}
+
+#####################################
+sub
+DUOFERNSTICK_DoInit($)
+{
+  my $hash = shift;
+  my $name = $hash->{NAME};
+  my $err;
+  my $msg = undef;
+  my $buf = "";
+
+  my @pairs;
+  
+  foreach my $d (keys %defs)   
+  { 
+    my $module   = $defs{$d}{TYPE};
+    next if ($module ne "DUOFERN");
+    
+    my $code = $defs{$d}{CODE};
+    if(AttrVal($defs{$d}{NAME}, "ignore", "0") == "0") {
+      push(@pairs, $code) if(length($code) == 6);
+    }
+  }
+
+  $hash->{helper}{cmdEx} = 0;
+  @{$hash->{cmdStack}} = ();
+  
+  return undef if (!$init_done);
+  
+  for(my $i = 0; $i < 4; $i++) {
+    DUOFERNSTICK_SimpleWrite($hash, $duoInit1);
+    ($err, $buf) = DUOFERNSTICK_ReadAnswer($hash, "INIT1");
+    next if($err);
+    
+    DUOFERNSTICK_SimpleWrite($hash, $duoInit2);
+    ($err, $buf) = DUOFERNSTICK_ReadAnswer($hash, "INIT2");
+    next if($err);
+    
+    $buf = $duoSetDongle;
+    $buf =~ s/zzzzzz/$hash->{DongleSerial}/;
+    DUOFERNSTICK_SimpleWrite($hash, $buf);
+    ($err, $buf) = DUOFERNSTICK_ReadAnswer($hash, "SetDongle");
+    next if($err);
+    DUOFERNSTICK_SimpleWrite($hash, $duoACK);
+    
+    DUOFERNSTICK_SimpleWrite($hash, $duoInit3);
+    ($err, $buf) = DUOFERNSTICK_ReadAnswer($hash, "INIT3");
+    next if($err);
+    DUOFERNSTICK_SimpleWrite($hash, $duoACK);
+    
+    my $counter = 0;
+    foreach (@pairs){
+      $buf = $duoSetPairs;
+      my $chex .= sprintf "%02x", $counter;
+      $buf =~ s/nn/$chex/;
+      $buf =~ s/yyyyyy/$_/;
+      DUOFERNSTICK_SimpleWrite($hash, $buf);
+      ($err, $buf) = DUOFERNSTICK_ReadAnswer($hash, "SetPairs");
+      next if($err);
+      DUOFERNSTICK_SimpleWrite($hash, $duoACK);
+      $counter++;
+    }  
+    
+    DUOFERNSTICK_SimpleWrite($hash, $duoInitEnd);
+    ($err, $buf) = DUOFERNSTICK_ReadAnswer($hash, "INIT3");
+    return "$name: $err" if($err);
+    DUOFERNSTICK_SimpleWrite($hash, $duoACK);
+    next if($err);
+    
+    DUOFERNSTICK_SimpleWrite($hash, $duoStatusRequest);
+    ($err, $buf) = DUOFERNSTICK_ReadAnswer($hash, "statusRequest");
+    next if($err);
+    DUOFERNSTICK_SimpleWrite($hash, $duoACK);
+  
+    readingsSingleUpdate($hash, "state", "Initialized", 1);
+    return undef;
+  }
+  return "$name: Init fail";
+  
+}
+
+#####################################
+sub 
+DUOFERNSTICK_SimpleWrite(@)
+{
+  my ($hash, $msg) = @_;
+  my $buf = "";
+  return if(!$hash);
+  my $name = $hash->{NAME};
+   
+  $msg =~ s/ //g;
+  my $me = $hash->{NAME};
+  Log3 $me, 4, "$me: snd -> $msg";
+
+  my @hex    = ($msg =~ /(..)/g);
+  foreach (@hex){
+    $buf .= chr(hex($_)) ;
+  }
+
+  DevIo_SimpleWrite($hash,$buf,0);
+    
+  return undef;
+}
+
+#####################################
+sub
+DUOFERNSTICK_ReadAnswer($$$$)
+{
+  my ($hash, $arg) = @_;
+  my $ohash = $hash;
+
+  while($hash->{TYPE} ne "DUOFERNSTICK") {   
+    $hash = $hash->{IODev};
+  }
+  return ("No FD", undef)
+        if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
+
+  my ($mduodata, $rin) = ("", '');
+  my $buf;
+  my $to = 1;                                         # 3 seconds timeout
+  $mduodata = $hash->{PARTIAL} if(defined($hash->{PARTIAL}));
+
+  $to = $ohash->{RA_Timeout} if($ohash->{RA_Timeout});  # ...or less
+  for(;;) {
+
+    if($^O =~ m/Win/ && $hash->{USBDev}) {
+      $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
+      # Read anstatt input sonst funzt read_const_time nicht.
+      $buf = $hash->{USBDev}->read(22);
+      return ("Timeout reading answer for get $arg", undef)
+        if(length($buf) == 0);
+
+    } else {
+      return ("Device lost when reading answer for get $arg", undef)
+        if(!$hash->{FD});
+
+      vec($rin, $hash->{FD}, 1) = 1;
+      my $nfound = select($rin, undef, undef, $to);
+      if($nfound < 0) {
+        next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
+        my $err = $!;
+        DevIo_Disconnected($hash);
+        return("DUOFERNSTICK_ReadAnswer $arg: $err", undef);
+      }
+      return ("Timeout reading answer for get $arg", undef)
+        if($nfound == 0);
+      $buf = DevIo_SimpleRead($hash);
+      return ("No data", undef) if(!defined($buf));
+
+    }
+
+    if(defined($buf)) {
+      
+      my $rbuf  = "";
+      my @array=split('',$buf);
+      foreach (@array){
+        $rbuf .= sprintf "%02x", ord($_) ;
+    }
+      Log3 $ohash->{NAME}, 5, "DUOFERNSTICK/RAW (ReadAnswer): $rbuf";
+      $mduodata .= $rbuf;
+    }
+
+    # Dispatch data in the buffer before the proper answer.
+    if(length($mduodata) >= 44) {  
+      my $rmsg;
+      ($rmsg,$mduodata) = unpack("a44 a*", $mduodata);
+      $hash->{PARTIAL} = $mduodata; # for recursive calls
+      return (undef, $rmsg);
+    }
+  }
+}
+
+#####################################
+sub 
+DUOFERNSTICK_Notify($$)
+{
+  my ($own, $dev) = @_;
+  my $me = $own->{NAME}; # own name / hash
+  my $devName = $dev->{NAME}; # Device that created the events
+
+  return undef if ($devName ne "global");
+  
+  my $max = int(@{$dev->{CHANGED}}); # number of events / changes
+  for (my $i = 0; $i < $max; $i++) {
+    my $s = $dev->{CHANGED}[$i];
+    
+    next if(!defined($s));
+    my ($what,$who) = split(' ',$s);
+    
+    if ($what && ($what =~ m/INITIALIZED/)) {
+      DUOFERNSTICK_DoInit($own);
+    }
+  }
+  return undef;
+}
+
+#####################################
+sub
+DUOFERNSTICK_HandleWriteQueue($)
+{
+  my ($hash) = @_;
+  
+  RemoveInternalTimer($hash);
+  
+  $hash->{helper}{cmdEx} -= 1 if ($hash->{helper}{cmdEx});
+  
+  my $entries = scalar @{$hash->{cmdStack}};
+  if ($entries > 0) {
+    readingsSingleUpdate($hash, "state", ($entries + $hash->{helper}{cmdEx})." CMDs_pending", 1);
+    my $msg = shift @{$hash->{cmdStack}};
+    $hash->{helper}{cmdEx} += 1;
+    DUOFERNSTICK_SimpleWrite($hash, $msg);
+    InternalTimer(gettimeofday()+5, "DUOFERNSTICK_HandleWriteQueue", $hash, 1);
+  } else {
+    readingsSingleUpdate($hash, "state","CMDs_done", 1);
+  }   
+
+}
+
+#####################################
+sub
+DUOFERNSTICK_AddSendQueue($$)
+{
+  my ($hash, $msg) = @_;
+  
+  push(@{$hash->{cmdStack}}, $msg);
+  my $entries = scalar @{$hash->{cmdStack}};
+  
+  if ($hash->{helper}{cmdEx} == 0 ) {
+    DUOFERNSTICK_HandleWriteQueue($hash);
+  } else {
+    readingsSingleUpdate($hash, "state", ($entries + $hash->{helper}{cmdEx})." CMDs_pending", 1);
+    InternalTimer(gettimeofday()+5, "DUOFERNSTICK_HandleWriteQueue", $hash, 1);
+  };
+
+}
+
+1;
+
+=pod
+=item summary    IO device for Rademacher DuoFern devices
+=item summary_DE IO device für Rademacher DuoFern Ger&auml;te
+=begin html
+
+<a name="DUOFERNSTICK"></a>
+<h3>DUOFERNSTICK</h3>
+<ul>
+
+  The DUOFERNSTICK is the fhem module for the Rademacher DuoFern USB stick. <br>
+    
+  <br><br>
+
+  <a name="DUOFERNSTICK_define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; DUOFERNSTICK &lt;device&gt; &lt;code&gt;</code><br><br>
+    &lt;device&gt; specifies the serial port to communicate with the DuoFern stick.<br>
+    &lt;code&gt; specifies the radio code of the DuoFern stick.<br>
+    <br>
+    The baud rate must be 115200 baud.<br>
+    The code of the DuoFern stick must start with 6F.
+    <br><br>
+    Example:<br>
+    <ul>
+      <code>define myDuoFernStick DUOFERNSTICK COM5@115200 6FEDCB</code><br>
+      <code>define myDuoFernStick DUOFERNSTICK /dev/serial/by-id/usb-Rademacher_DuoFern_USB-Stick_WR0455TN-if00-port0@115200 6FEDCB</code><br>
+    </ul>
+  </ul>
+  <br>
+  <a name="DUOFERNSTICK_set"></a>
+  <p><b>Set</b></p>
+  <ul>
+    <li><b>pair</b><br>
+        Set the DuoFern stick in pairing mode for 60 seconds. Any DouFern device set into
+        pairing mode in this time will be paired with the DuoFern stick.
+        </li><br>
+    <li><b>unpair</b><br>
+        Set the DuoFern stick in unpairing mode for 60 seconds. Any DouFern device set into
+        unpairing mode in this time will be unpaired from the DuoFern stick.
+        </li><br>
+    <li><b>reopen</b><br>
+        Reopens the connection to the device and reinitializes it.
+        </li><br>
+    <li><b>statusBroadcast</b><br>
+        Sends a status request message to all DuoFern devices.
+        </li><br>
+    <li><b>remotePair &lt;code&gt</b><br>
+        Activates the pairing mode on the device specified by the code.<br>
+        Some actors accept this command in unpaired mode up to two hours afte power up. 
+        </li><br>
+    <li><b>raw &lt;rawmsg&gt;</b><br>
+        Sends a raw message.
+        </li><br>
+  </ul>
+  <br>
+  <b>Get</b> <ul>N/A</ul><br>
+  <a name="DUOFERNSTICK_attr"></a>
+  <b>Attributes</b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+</ul>
+
+=end html
+
+=cut
+

ファイルの差分が大きいため隠しています
+ 1508 - 0
fhem/core/FHEM/10_EIB.pm


+ 789 - 0
fhem/core/FHEM/10_EQ3BT.pm

@@ -0,0 +1,789 @@
+#############################################################
+#
+# EQ3BT.pm (c) by Dominik Karall, 2016-2017
+# dominik karall at gmail dot com
+# $Id: 10_EQ3BT.pm 13274 2017-01-29 17:45:23Z dominik $
+#
+# FHEM module to communicate with EQ-3 Bluetooth thermostats
+#
+# Version: 2.0.0
+#
+#############################################################
+#
+# v2.0.0 - 20170129
+# - FEATURE: use all available bluetooth interfaces to communicate
+#            with the bluetooth thermostat
+# - FEATURE: new reading bluetoothDevice (shows used hci device)
+# - CHANGE:  change maximum retries to 20
+# - FEATURE: new set function resetErrorCounters
+# - FEATURE: new set function resetConsumption (not today/yesterday)
+# - FEATURE: new reading lastChangeBy FHEM or thermostat
+#            indicates who was responsible for the last change
+# - FEATURE: support $readingFnAttributes
+# - FEATURE: add VERSION internal and log output
+# - CHANGE:  updateStatus is now 3min intervall starting from
+#            last working updateStatus
+# - BUGFIX:  do not run parallel gatttool commands for the same device
+#
+# v1.1.3 - 20161211
+# - BUGFIX:  better error handling if no notification was received
+# - BUGFIX:  update system information fixed
+# - CHANGE:  allow multiple gatttools to be executed in parallel
+# - CHANGE:  remove error reading
+# - CHANGE:  add errorCounters based on function (update/...)
+#            which will be increased if reading from the thermostat
+#            fails 30 times for one command
+# - BUGFIX:  retry mechanism for commands with notifications (updateStatus)
+# - BUGFIX:  remain consumption values after restart
+#
+# v1.1.2 - 20161108
+# - FEATURE: support set <name> eco (eco temperature)
+# - FEATURE: support set <name> comfort (comfort temperature)
+# - CHANGE:  updated commandref
+#
+# v1.1.1 - 20161106
+# - FEATURE: new reading consumption today/yesterday
+# - FEATURE: new reading firmware which shows the current version
+# - FEATURE: support set <name> mode automatic/manual
+#
+# v1.1.0 - 20161105
+# - CHANGE:  code cleanup to make support of new functions easier
+# - FEATURE: support boost on/off command
+# - BUGFIX:  redirect stderr to stdout to avoid "Device or ressource busy"
+#            and other error messages in the log output, only
+#            if an action fails 20 times an error will be shown in the log
+#
+# v1.0.7 - 20161101
+# - FEATURE: new reading consumption
+#            calculation based on valvePosition and time (unit = %h)
+# - FEATURE: new reading battery
+# - FEATURE: new reading boost
+# - FEATURE: new reading windowOpen
+# - CHANGE:  change mode reading to Automatic/Manual only
+# - FEATURE: new reading ecoMode (=holiday)
+#
+# v1.0.6 - 20161028
+# - BUGFIX:  support temperature down to 4.5 (=OFF) degrees
+#
+# v1.0.5 - 20161027
+# - BUGFIX:  fix wrong date/time after updateStatus again
+#
+# v1.0.4 - 20161025
+# - BUGFIX:  remove unnecessary scan command on define
+#
+# v1.0.3 - 20161024
+# - BUGFIX:  another fix for retry mechanism
+# - BUGFIX:  wait before gatttool execution when
+#            another gatttool/hcitool process is running
+# - BUGFIX:  fix wrong date/time after updateStatus
+#
+# v1.0.2 - 20161020
+# - FEATURE: automatically pair/trust device on define
+# - FEATURE: add updateStatus method to update all values
+# - BUGFIX:  fix retry mechanism for setDesiredTemperature
+# - BUGFIX:  fix valvePosition value
+# - BUGFIX:  fix uninitialized value error
+# - BUGFIX:  RemoveTimer if set desired temp works again
+# - BUGFIX:  set error reading to "" after it works again
+# - BUGFIX:  disconnect device on define (startup)
+#
+# v1.0.1 - 20161016
+# - FEATURE: read mode/desiredTemp/valvePos every 2 hours
+#            might have impact on battery life!
+# - CHANGED: temperature renamed to desiredTemperature
+# - FEATURE: retry setTemperature 20 times if it fails
+#
+# v1.0.0 - 20161015
+# - FEATURE: first public release
+#
+# NOTES
+# command            dec
+# DONE: boost mode command 69 00/01
+# temperature offset 19 (x*2)+7
+# request profile    32 01-07
+# vacation mode      64 ...
+# system info        00 => frameType=1,version=value[1],typeCode=value[2]
+# window             20 t*2 time*5
+# factory reset      -16
+# DONE: comfort temp       67
+# lock               -128 00/01
+# DONE: mode               64 mode<<6
+# DONE: temp               65 temp*2
+# timer              3...
+# start FW update    -96
+# DONE: eco mode           68
+# FW data            -95 ...
+# profile set        16 ...
+# set tempconf       17 comfort*2 eco*2
+#
+# TODOs
+# - create virtual device (wohnzimmer)
+# - read/set eco/comfort temperature
+# - read/set tempOffset
+# - read/set windowOpen time settings
+# - read/set profiles per day
+#
+#############################################################
+
+package main;
+
+use strict;
+use warnings;
+
+use Blocking;
+use Encode;
+use SetExtensions;
+
+sub EQ3BT_Initialize($) {
+    my ($hash) = @_;
+    
+    $hash->{DefFn}    = 'EQ3BT_Define';
+    $hash->{UndefFn}  = 'EQ3BT_Undef';
+    $hash->{GetFn}    = 'EQ3BT_Get';
+    $hash->{SetFn}    = 'EQ3BT_Set';
+    $hash->{AttrFn}   = 'EQ3BT_Attribute';
+    $hash->{AttrList}  = $readingFnAttributes;
+    
+    return undef;
+}
+
+sub EQ3BT_Define($$) {
+    #save BTMAC address
+    my ($hash, $def) = @_;
+    my @a = split("[ \t]+", $def);
+    my $name = $a[0];
+    my $mac;
+    
+    $hash->{STATE} = "initialized";
+    $hash->{VERSION} = "2.0.0";
+    Log3 $hash, 3, "EQ3BT: EQ-3 Bluetooth Thermostat ".$hash->{VERSION};
+    
+    if (int(@a) > 3) {
+        return 'EQ3BT: Wrong syntax, must be define <name> EQ3BT <mac address>';
+    } elsif(int(@a) == 3) {
+        $mac = $a[2];
+        $hash->{MAC} = $a[2];
+    }
+    
+    EQ3BT_updateHciDevicelist($hash);
+    
+    BlockingCall("EQ3BT_pairDevice", $name."|".$hash->{MAC});
+    
+    RemoveInternalTimer($hash);
+    InternalTimer(gettimeofday()+60, "EQ3BT_updateStatus", $hash, 0);
+    InternalTimer(gettimeofday()+20, "EQ3BT_updateSystemInformation", $hash, 0);
+    
+    return undef;
+}
+
+sub EQ3BT_updateHciDevicelist {
+    my ($hash) = @_;
+    #check for hciX devices
+    $hash->{helper}{hcidevices} = ();
+    my @btDevices = split("\n", qx(hcitool dev));
+    foreach my $btDevLine (@btDevices) {
+        if($btDevLine =~ /hci(.)/) {
+            push(@{$hash->{helper}{hcidevices}}, $1);
+        }
+    }
+    $hash->{helper}{currenthcidevice} = 0;
+    readingsSingleUpdate($hash, "bluetoothDevice", "hci".$hash->{helper}{hcidevices}[$hash->{helper}{currenthcidevice}], 1);
+    return undef;
+}
+
+sub EQ3BT_pairDevice {
+    my ($string) = @_;
+    my ($name, $mac) = split("\\|", $string);
+
+    qx(echo "pair $mac\\n";sleep 7;echo "trust $mac\\ndisconnect $mac\\n";sleep 2; echo "quit\\n" | bluetoothctl);
+
+    return $name;
+}
+
+sub EQ3BT_Attribute($$$$) {
+    my ($mode, $devName, $attrName, $attrValue) = @_;
+    
+    if($mode eq "set") {
+        
+    } elsif($mode eq "del") {
+        
+    }
+    
+    return undef;
+}
+
+sub EQ3BT_Set($@) {
+    #set temperature/mode/...
+    #BlockingCall for gatttool
+    #handle result from BlockingCall in separate function and
+    # write result into readings
+    #
+    my ($hash, $name, @params) = @_;
+    my $workType = shift(@params);
+    my $list = "desiredTemperature:slider,4.5,0.5,29.5,1 updateStatus:noArg boost:on,off mode:manual,automatic eco:noArg comfort:noArg ".
+               "resetErrorCounters:noArg resetConsumption:noArg";
+
+    # check parameters for set function
+    if($workType eq "?") {
+        return SetExtensions($hash, $list, $name, $workType, @params);
+    }
+
+    if($workType eq "desiredTemperature") {
+        return "EQ3BT: desiredTemperature requires <temperature> in celsius degrees as additional parameter" if(int(@params) < 1);
+        return "EQ3BT: desiredTemperature supports temperatures from 4.5 - 29.5 degrees" if($params[0]<4.5 || $params[0]>29.5);
+        EQ3BT_setDesiredTemperature($hash, $params[0]);
+    } elsif($workType eq "updateStatus") {
+        $hash->{helper}{retryUpdateStatusCounter} = 0;
+        EQ3BT_updateStatus($hash, 1);
+    } elsif($workType eq "boost") {
+        return "EQ3BT: boost requires on/off as additional parameter" if(int(@params) < 1);
+        EQ3BT_setBoost($hash, $params[0]);
+    } elsif($workType eq "mode") {
+        return "EQ3BT: mode requires automatic/manual as additional parameter" if(int(@params) < 1);
+        EQ3BT_setMode($hash, $params[0]);
+    } elsif($workType eq "eco") {
+        EQ3BT_setEco($hash);
+    } elsif($workType eq "comfort") {
+        EQ3BT_setComfort($hash);
+    } elsif($workType eq "resetErrorCounters") {
+        EQ3BT_setResetErrorCounters($hash);
+    } elsif($workType eq "resetConsumption") {
+        EQ3BT_setResetConsumption($hash);
+    } elsif($workType eq "childlock") {
+        return "EQ3BT: childlock requires on/off as additional parameter" if(int(@params) < 1);
+        EQ3BT_setChildlock($hash, $params[0]);
+    } elsif($workType eq "holidaymode") {
+        return "EQ3BT: holidaymode requires YYMMDDHHMM as additional parameter" if(int(@params) < 1);
+        EQ3BT_setHolidaymode($hash, $params[0]);
+    } elsif($workType eq "datetime") {
+        return "EQ3BT: datetime requires YYMMDDHHMM as additional parameter" if(int(@params) < 1);
+        EQ3BT_setDatetime($hash, $params[0]);
+    } elsif($workType eq "window") {
+        return "EQ3BT: windows requires open/closed as additional parameter" if(int(@params) < 1);
+        EQ3BT_setWindow($hash, $params[0]);
+    } elsif($workType eq "program") {
+        return "EQ3BT: programming the device is not supported yet";
+    } else {
+        return SetExtensions($hash, $list, $name, $workType, @params);
+    }
+    
+    return undef;
+}
+
+### resetErrorCounters ###
+sub EQ3BT_setResetErrorCounters {
+    my ($hash) = @_;
+    
+    foreach my $reading (keys %{ $hash->{READINGS} }) {
+        if($reading =~ /errorCount-.*/) {
+            readingsSingleUpdate($hash, $reading, 0, 1);
+        }
+    }
+
+    return undef;
+}
+
+### resetConsumption ###
+sub EQ3BT_setResetConsumption {
+    my ($hash) = @_;
+    readingsSingleUpdate($hash, "consumption", 0, 1);
+    return undef;
+}
+
+### updateSystemInformation ###
+sub EQ3BT_updateSystemInformation {
+    my ($hash) = @_;
+    my $name = $hash->{NAME};
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $name."|".$hash->{MAC}."|updateSystemInformation|0x0411|00|listen", "EQ3BT_processGatttoolResult", 300, "EQ3BT_killGatttool", $hash);
+}
+
+sub EQ3BT_updateSystemInformationSuccessful {
+    my ($hash, $handle, $value) = @_;
+    InternalTimer(gettimeofday()+7200+int(rand(180)), "EQ3BT_updateSystemInformation", $hash, 0);
+    return undef;
+}
+
+sub EQ3BT_updateSystemInformationRetry {
+    my ($hash) = @_;
+    EQ3BT_updateSystemInformation($hash);
+    return undef;
+}
+
+sub EQ3BT_updateSystemInformationFailed {
+    my ($hash) = @_;
+    InternalTimer(gettimeofday()+7000+int(rand(180)), "EQ3BT_updateSystemInformation", $hash, 0);
+    return undef;
+}
+
+### updateStatus ###
+sub EQ3BT_updateStatus {
+    my ($hash) = @_;
+    my $name = $hash->{NAME};
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $name."|".$hash->{MAC}."|updateStatus|0x0411|03|listen", "EQ3BT_processGatttoolResult", 300, "EQ3BT_killGatttool", $hash);
+}
+
+sub EQ3BT_updateStatusSuccessful {
+    my ($hash, $handle, $value) = @_;
+    InternalTimer(gettimeofday()+140+int(rand(60)), "EQ3BT_updateStatus", $hash, 0);
+    return undef;
+}
+
+sub EQ3BT_updateStatusRetry {
+    my ($hash) = @_;
+    EQ3BT_updateStatus($hash);
+    return undef;
+}
+
+sub EQ3BT_updateStatusFailed {
+    my ($hash, $handle, $value) = @_;
+    InternalTimer(gettimeofday()+170+int(rand(60)), "EQ3BT_updateStatus", $hash, 0);
+    return undef;
+}
+
+### setDesiredTemperature ###
+sub EQ3BT_setDesiredTemperature($$) {
+    my ($hash, $desiredTemp) = @_;
+    my $name = $hash->{NAME};
+    
+    my $eq3Temp = sprintf("%02X", $desiredTemp * 2);
+    
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $name."|".$hash->{MAC}."|setDesiredTemperature|0x0411|41".$eq3Temp, "EQ3BT_processGatttoolResult", 60, "EQ3BT_killGatttool", $hash);
+    return undef;
+}
+
+sub EQ3BT_setDesiredTemperatureSuccessful {
+    my ($hash, $handle, $tempVal) = @_;
+    my $temp = (hex($tempVal) - 0x4100) / 2;
+    readingsSingleUpdate($hash, "desiredTemperature", sprintf("%.1f", $temp), 1);
+    return undef;
+}
+
+sub EQ3BT_setDesiredTemperatureRetry {
+    my ($hash) = @_;
+    EQ3BT_retryGatttool($hash, "setDesiredTemperature");
+    return undef;
+}
+
+### setBoost ###
+sub EQ3BT_setBoost {
+    my ($hash, $onoff) = @_;
+    my $name = $hash->{NAME};
+    my $data = "01";
+    $data = "00" if($onoff eq "off");
+    
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $name."|".$hash->{MAC}."|setBoost|0x0411|45".$data, "EQ3BT_processGatttoolResult", 60, "EQ3BT_killGatttool", $hash);
+    return undef;
+}
+
+sub EQ3BT_setBoostSuccessful {
+    my ($hash, $handle, $value) = @_;
+    my $val = (hex($value) - 0x4500);
+    readingsSingleUpdate($hash, "boost", $val, 1);
+    return undef;
+}
+
+sub EQ3BT_setBoostRetry {
+    my ($hash) = @_;
+    EQ3BT_retryGatttool($hash, "setBoost");
+    return undef;
+}
+
+### setMode ###
+sub EQ3BT_setMode {
+    my ($hash, $mode) = @_;
+    my $name = $hash->{NAME};
+    my $data = "40";
+    $data = "00" if($mode eq "automatic");
+    
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $name."|".$hash->{MAC}."|setMode|0x0411|40".$data."|listen", "EQ3BT_processGatttoolResult", 60, "EQ3BT_killGatttool", $hash);
+    return undef;
+}
+
+sub EQ3BT_setModeSuccessful {
+    my ($hash, $handle, $value) = @_;
+    
+    return undef;
+}
+
+sub EQ3BT_setModeRetry {
+    my ($hash) = @_;
+    EQ3BT_retryGatttool($hash, "setMode");
+    return undef;
+}
+
+### setEco ###
+sub EQ3BT_setEco {
+    my ($hash) = @_;
+    my $name = $hash->{NAME};
+    
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $name."|".$hash->{MAC}."|setEco|0x0411|44|listen", "EQ3BT_processGatttoolResult", 60, "EQ3BT_killGatttool", $hash);
+    return undef;
+}
+
+sub EQ3BT_setEcoSuccessful {
+    my ($hash, $handle, $value) = @_;
+    
+    return undef;
+}
+
+sub EQ3BT_setEcoRetry {
+    my ($hash) = @_;
+    EQ3BT_retryGatttool($hash, "setEco");
+    return undef;
+}
+
+### setComfort ###
+sub EQ3BT_setComfort {
+    my ($hash) = @_;
+    my $name = $hash->{NAME};
+    
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $name."|".$hash->{MAC}."|setComfort|0x0411|43|listen", "EQ3BT_processGatttoolResult", 60, "EQ3BT_killGatttool", $hash);
+    return undef;
+}
+
+sub EQ3BT_setComfortSuccessful {
+    my ($hash, $handle, $value) = @_;
+    
+    return undef;
+}
+
+sub EQ3BT_setComfortRetry {
+    my ($hash) = @_;
+    EQ3BT_retryGatttool($hash, "setEco");
+    return undef;
+}
+
+### Gatttool functions ###
+sub EQ3BT_retryGatttool {
+    my ($hash, $workType) = @_;
+    $hash->{helper}{RUNNING_PID} = BlockingCall("EQ3BT_execGatttool", $hash->{NAME}."|".$hash->{MAC}."|$workType|".$hash->{helper}{"handle$workType"}."|".$hash->{helper}{"value$workType"}."|".$hash->{helper}{"listen$workType"}, "EQ3BT_processGatttoolResult", 60, "EQ3BT_killGatttool", $hash);
+    return undef;
+}
+
+sub EQ3BT_execGatttool($) {
+    my ($string) = @_;
+    my ($name, $mac, $workType, $handle, $value, $listen) = split("\\|", $string);
+    my $wait = 1;
+    my $hash = $main::defs{$name};
+    
+    my $gatttool = qx(which gatttool);
+    chomp $gatttool;
+    
+    if(-x $gatttool) {
+        my $gtResult;
+
+        while($wait) {
+            my $grepGatttool = qx(ps ax| grep -E \'gatttool -b $mac\' | grep -v grep);
+            if(not $grepGatttool =~ /^\s*$/) {
+                #another gattool is running
+                Log3 $name, 5, "EQ3BT ($name): another gatttool process is running. waiting...";
+                sleep(1);
+            } else {
+                $wait = 0;
+            }
+        }
+
+        if($value eq "03") {
+            my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
+            my $currentDate = sprintf("%02X%02X%02X%02X%02X", $year+1900-2000, $mon+1, $mday, $hour, $min);
+            $value .= $currentDate;
+        }
+
+        my $hciDevice = "hci".$hash->{helper}{hcidevices}[$hash->{helper}{currenthcidevice}];
+        my $cmd = "gatttool -b $mac -i $hciDevice --char-write-req --handle=$handle --value=$value";
+        if(defined($listen) && $listen eq "listen") {
+            $cmd = "timeout 15 ".$cmd." --listen";
+        }
+        
+        #redirect stderr to stdout
+        $cmd .= " 2>&1";
+
+        Log3 $name, 5, "EQ3BT ($name): $cmd";
+        $gtResult = qx($cmd);
+        chomp $gtResult;
+        my @gtResultArr = split("\n", $gtResult);
+        Log3 $name, 4, "EQ3BT ($name): gatttool result: ".join(",", @gtResultArr);
+        if(defined($gtResultArr[0]) && $gtResultArr[0] eq "Characteristic value was written successfully") {
+            #read notification
+            if(defined($gtResultArr[1]) && $gtResultArr[1] =~ /Notification handle = 0x0421 value: (.*)/) {
+                return "$name|$mac|ok|$workType|$handle|$value|$1";
+            } else {
+                if(defined($listen) && $listen eq "listen") {
+                    return "$name|$mac|error|$workType|$handle|$value|notification missing";
+                } else {
+                    return "$name|$mac|ok|$workType|$handle|$value";
+                }
+            }
+        } else {
+            return "$name|$mac|error|$workType|$handle|$value|$workType failed";
+        }
+    } else {
+        return "$name|$mac|error|$workType|$handle|$value|no gatttool binary found. Please check if bluez-package is properly installed";
+    }
+}
+
+sub EQ3BT_processGatttoolResult($) {
+    my ($string) = @_;
+    
+    return unless(defined($string));
+    
+    my @a = split("\\|", $string);
+    my $name = $a[0];
+    my $hash = $defs{$name};
+    my $mac = $a[1];
+    my $ret = $a[2];
+    my $workType = $a[3];
+    my $handle = $a[4];
+    my $value = $a[5];
+    my $notification = $a[6];
+    
+    delete($hash->{helper}{RUNNING_PID});
+    
+    Log3 $hash, 5, "EQ3BT ($name): gatttool return string: $string";
+    
+    $hash->{helper}{"handle$workType"} = $handle;
+    $hash->{helper}{"value$workType"} = $value;
+    $hash->{helper}{"listen$workType"} = $notification;
+    
+    if($ret eq "ok") {
+        #process notification
+        if(defined($notification)) {
+            EQ3BT_processNotification($hash, $notification);
+        }
+        if($workType =~ /set.*/) {
+            readingsSingleUpdate($hash, "lastChangeBy", "FHEM", 1);
+        }
+        #call WorkTypeSuccessful function
+        my $call = "EQ3BT_".$workType."Successful";
+        no strict "refs";
+        eval {
+            &{$call}($hash, $handle, $value);
+        };
+        use strict "refs";
+        RemoveInternalTimer($hash, "EQ3BT_".$workType."Retry");
+        $hash->{helper}{"retryCounter$workType"} = 0;
+    } else {
+        $hash->{helper}{"retryCounter$workType"} = 0 if(!defined($hash->{helper}{"retryCounter$workType"}));
+        $hash->{helper}{"retryCounter$workType"}++;
+        Log3 $hash, 4, "EQ3BT ($name): $workType failed ($handle, $value, $notification)";
+        if ($hash->{helper}{"retryCounter$workType"} > 20) {
+            my $errorCount = ReadingsVal($hash->{NAME}, "errorCount-$workType", 0);
+            readingsSingleUpdate($hash, "errorCount-$workType", $errorCount+1, 1);
+            Log3 $hash, 3, "EQ3BT ($name): $workType, $handle, $value failed 20 times.";
+            $hash->{helper}{"retryCounter$workType"} = 0;
+            $hash->{helper}{"retryCounterHci".$hash->{helper}{currenthcidevice}} = 0;
+            #call WorkTypeFailed function
+            my $call = "EQ3BT_".$workType."Failed";
+            no strict "refs";
+            eval {
+                &{$call}($hash, $handle, $value);
+            };
+            use strict "refs";
+            
+            #update hci devicelist
+            EQ3BT_updateHciDevicelist($hash);
+        } else {
+            $hash->{helper}{"retryCounterHci".$hash->{helper}{currenthcidevice}} = 0 if(!defined($hash->{helper}{"retryCounterHci".$hash->{helper}{currenthcidevice}}));
+            $hash->{helper}{"retryCounterHci".$hash->{helper}{currenthcidevice}}++;
+            if ($hash->{helper}{"retryCounterHci".$hash->{helper}{currenthcidevice}} > 7) {
+                #reset error counter
+                $hash->{helper}{"retryCounterHci".$hash->{helper}{currenthcidevice}} = 0;
+                #use next hci device next time
+                $hash->{helper}{currenthcidevice} += 1;
+                my $maxHciDevices = @{ $hash->{helper}{hcidevices} } - 1;
+                if($hash->{helper}{currenthcidevice} > $maxHciDevices) {
+                    $hash->{helper}{currenthcidevice} = 0;
+                }
+                #update reading
+                readingsSingleUpdate($hash, "bluetoothDevice", "hci".$hash->{helper}{hcidevices}[$hash->{helper}{currenthcidevice}], 1);
+            }
+            InternalTimer(gettimeofday()+3+int(rand(5)), "EQ3BT_".$workType."Retry", $hash, 0);
+        }
+    }
+    
+    return undef;
+}
+
+sub EQ3BT_processNotification {
+    my ($hash, $notification) = @_;
+    my @vals = split(" ", $notification);
+    
+    my $frameType = $vals[0];
+    
+    if($frameType eq "01") {
+        my $version = hex($vals[1]);
+        my $typeCode = hex($vals[2]);
+        readingsSingleUpdate($hash, "firmware", $version, 1);
+        #readingsSingleUpdate($hash, "typeCode", $typeCode, 1);
+    } elsif($frameType eq "02") {
+        return undef if(!defined($vals[2]));
+      
+        #vals[2]
+        my $mode = hex($vals[2]) & 1;
+        my $modeStr = "Manual";
+        if($mode == 0) {
+            $modeStr = "Automatic";
+        }
+        my $eco  = (hex($vals[2]) & 2) >> 1;
+        my $isBoost = (hex($vals[2]) & 4) >> 2;
+        my $dst  = (hex($vals[2]) & 8) >> 3;
+        my $wndOpen = (hex($vals[2]) & 16) >> 4;
+        my $unknown = (hex($vals[2]) & 32) >> 5;
+        $unknown = (hex($vals[2]) & 64) >> 6;
+        my $isLowBattery = (hex($vals[2]) & 128) >> 7;
+        my $batteryStr = "ok";
+        if($isLowBattery > 0) {
+            $batteryStr = "low";
+        }
+
+        #vals[3]
+        my $pct  = hex($vals[3]);
+
+        #vals[5]
+        my $temp = hex($vals[5]) / 2;
+
+        my $timeSinceLastChange = ReadingsAge($hash->{NAME}, "valvePosition", 0);
+        my $consumption = ReadingsVal($hash->{NAME}, "consumption", 0);
+        my $consumptionToday = ReadingsVal($hash->{NAME}, "consumptionToday", 0);
+        my $consumptionTodaySecSinceLastChange = ReadingsAge($hash->{NAME}, "consumptionToday", 0);
+        my $oldVal = ReadingsVal($hash->{NAME}, "valvePosition", 0);
+        my $consumptionDiff = 0;
+        if($timeSinceLastChange < 600) {
+            $consumptionDiff += ($oldVal + $pct) / 2 * $timeSinceLastChange / 3600;
+        }
+        EQ3BT_readingsSingleUpdateIfChanged($hash, "consumption", sprintf("%.3f", $consumption+$consumptionDiff));
+
+        my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
+        if($consumptionTodaySecSinceLastChange > ($hour*3600+$min*60+$sec)) {
+            readingsSingleUpdate($hash, "consumptionYesterday", $consumptionToday + $consumptionDiff/2, 1);
+            readingsSingleUpdate($hash, "consumptionToday", 0 + $consumptionDiff/2, 1);
+        } else {
+            EQ3BT_readingsSingleUpdateIfChanged($hash, "consumptionToday", sprintf("%.3f", $consumptionToday+$consumptionDiff));
+        }
+
+        readingsSingleUpdate($hash, "valvePosition", $pct, 1);
+        #changes below this line will set lastchangeby
+        readingsSingleUpdate($hash, "windowOpen", $wndOpen, 1);
+        readingsSingleUpdate($hash, "ecoMode", $eco, 1);
+        readingsSingleUpdate($hash, "battery", $batteryStr, 1);
+        readingsSingleUpdate($hash, "boost", $isBoost, 1);
+        readingsSingleUpdate($hash, "mode", $modeStr, 1);
+        readingsSingleUpdate($hash, "desiredTemperature", sprintf("%.1f", $temp), 1);
+    }
+    
+    return undef;
+}
+
+sub EQ3BT_readingsSingleUpdateIfChanged {
+  my ($hash, $reading, $value, $setLastChange) = @_;
+  my $curVal = ReadingsVal($hash->{NAME}, $reading, "");
+  
+  if($curVal ne $value) {
+      readingsSingleUpdate($hash, $reading, $value, 1);
+      if(defined($setLastChange)) {
+          readingsSingleUpdate($hash, "lastChangeBy", "Thermostat", 1);
+      }
+  }
+}
+
+sub EQ3BT_killGatttool($) {
+
+}
+
+sub EQ3BT_setDaymode($) {
+    my ($hash) = @_;
+}
+
+sub EQ3BT_setNightmode($) {
+    my ($hash) = @_;
+}
+
+sub EQ3BT_setChildlock($$) {
+    my ($hash, $desiredState) = @_;
+}
+
+sub EQ3BT_setHolidaymode($$) {
+    my ($hash, $holidayEndTime) = @_;
+}
+
+sub EQ3BT_setDatetime($$) {
+    my ($hash, $currentDatetime) = @_;
+}
+
+sub EQ3BT_setWindow($$) {
+    my ($hash, $desiredState) = @_;
+}
+
+sub EQ3BT_setProgram($$) {
+    my ($hash, $program) = @_;
+}
+
+sub EQ3BT_Undef($) {
+    my ($hash) = @_;
+
+    #remove internal timer
+    RemoveInternalTimer($hash);
+
+    return undef;
+}
+
+sub EQ3BT_Get($$) {
+    return undef;
+}
+
+1;
+
+=pod
+=item device
+=item summary Control EQ3 Bluetooth Smart Radiator Thermostat
+=item summary_DE Steuerung des EQ3 Bluetooth Thermostats
+=begin html
+
+<a name="EQ3BT"></a>
+<h3>EQ3BT</h3>
+<ul>
+  EQ3BT is used to control a EQ3 Bluetooth Smart Radiator Thermostat<br><br>
+	<b>Note:</b> The bluez package is required to run this module. Please check if gatttool executable is available on your system.
+		
+  <br>
+  <br>
+  <a name="EQ3BTdefine" id="EQ3BTdefine"></a>
+    <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; EQ3BT &lt;mac address&gt;</code><br>
+    <br>
+    Example:
+    <ul>
+      <code>define livingroom.thermostat EQ3BT 00:33:44:33:22:11</code><br>
+    </ul>
+  </ul>
+  
+  <br>
+
+  <a name="EQ3BTset" id="EQ3BTset"></a>
+  <b>Set</b>
+  <ul>
+    <code>set &lt;name&gt; &lt;command&gt; [&lt;parameter&gt;]</code><br>
+               The following commands are defined:<br><br>
+        <ul>
+          <li><code><b>desiredTemperature</b> [4.5...29.5]</code> &nbsp;&nbsp;-&nbsp;&nbsp; set the temperature</li>
+          <li><code><b>boost</b> on/off</code> &nbsp;&nbsp;-&nbsp;&nbsp; activate boost command</li>
+          <li><code><b>mode</b> manual/automatic</code> &nbsp;&nbsp;-&nbsp;&nbsp; set manual/automatic mode</li>
+          <li><code><b>updateStatus</b></code> &nbsp;&nbsp;-&nbsp;&nbsp; read current thermostat state and update readings</li>
+          <li><code><b>eco</b> </code> &nbsp;&nbsp;-&nbsp;&nbsp; set eco temperature</li>
+          <li><code><b>comfort</b> </code> &nbsp;&nbsp;-&nbsp;&nbsp; set comfort temperature</li>
+        </ul>
+    <br>
+    </ul>
+          
+    <a name="EQ3BTget" id="EQ3BTget"></a>
+       <b>Get</b>
+         <ul>
+           <code>n/a</code>
+        </ul>
+        <br>
+
+</ul>
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 20412 - 0
fhem/core/FHEM/10_EnOcean.pm


+ 683 - 0
fhem/core/FHEM/10_FBDECT.pm

@@ -0,0 +1,683 @@
+##############################################
+# $Id: 10_FBDECT.pm 13273 2017-01-29 17:35:45Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+sub FBDECT_Parse($$@);
+sub FBDECT_Set($@);
+sub FBDECT_Get($@);
+sub FBDECT_Cmd($$@);
+
+sub FBDECT_decodePayload($$$);
+
+my @fbdect_models = qw(Powerline546E Dect200 CometDECT);
+
+my %fbdect_payload = (
+   7 => { n=>"connected" },
+   8 => { n=>"disconnected" },
+  10 => { n=>"configChanged" },
+  15 => { n=>"state",       fmt=>'hex($pyld)?"on":"off"' },
+  16 => { n=>"relayTimes",  fmt=>'FBDECT_decodeRelayTimes($pyld)' },
+  18 => { n=>"current",     fmt=>'sprintf("%0.4f A", hex($pyld)/10000)' },
+  19 => { n=>"voltage",     fmt=>'sprintf("%0.3f V", hex($pyld)/1000)' },
+  20 => { n=>"power",       fmt=>'sprintf("%0.2f W", hex($pyld)/100)' },
+  21 => { n=>"energy",      fmt=>'sprintf("%0.0f Wh",hex($pyld))' },
+  22 => { n=>"powerFactor", fmt=>'sprintf("%0.3f", hex($pyld))' },
+  23 => { n=>"temperature", fmt=>'FBDECT_decodeTemp($pyld, $hash, $addReading)' },
+  35 => { n=>"options",     fmt=>'FBDECT_decodeOptions($pyld)' },
+  37 => { n=>"control",     fmt=>'FBDECT_decodeControl($pyld)' },
+);
+
+
+sub
+FBDECT_Initialize($)
+{
+  my ($hash) = @_;
+  $hash->{Match}     = ".*";
+  $hash->{SetFn}     = "FBDECT_Set";
+  $hash->{GetFn}     = "FBDECT_Get";
+  $hash->{DefFn}     = "FBDECT_Define";
+  $hash->{UndefFn}   = "FBDECT_Undef";
+  $hash->{ParseFn}   = "FBDECT_Parse";
+  $hash->{AttrList}  = 
+    "IODev do_not_notify:1,0 ignore:1,0 dummy:1,0 showtime:1,0 ".
+    "$readingFnAttributes " .
+    "model:".join(",", sort @fbdect_models);
+  $hash->{AutoCreate}= 
+        { "FBDECT.*" => { 
+             GPLOT => "power4:Power,",
+             FILTER => "%NAME:power\\x3a.*",
+             ATTR => "event-min-interval:power:120" } };
+}
+
+
+#############################
+sub
+FBDECT_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+  my $name   = shift @a;
+  my $type = shift(@a); # always FBDECT
+
+  my $u = "wrong syntax for $name: define <name> FBDECT [FBAHAname:]id props";
+  return $u if(int(@a) != 2);
+
+  my $ioNameAndId = shift @a;
+  my ($ioName, $id) = (undef, $ioNameAndId);
+  if($ioNameAndId =~ m/^([^:]*):(.*)$/) {
+    $ioName = $1; $id = $2;
+  }
+  $hash->{id} = $id;
+  $hash->{props} = shift @a;
+
+  $modules{FBDECT}{defptr}{$ioNameAndId} = $hash;
+  AssignIoPort($hash, $ioName);
+  return undef;
+}
+ 
+###################################
+sub
+FBDECT_SetHttp($@)
+{
+  my ($hash, @a) = @_;
+  my %cmd;
+  my $p = $hash->{props};
+
+  if($p =~ m/switch/) {
+    $cmd{off} = $cmd{on} = $cmd{toggle} = "noArg";
+  }
+  if($p =~ m/actuator/) {
+    $cmd{"desired-temp"} = "slider,8,0.5,28,1";
+    $cmd{open} = $cmd{closed} = "noArg";
+  }
+  if(!$cmd{$a[1]}) {
+    my $cmdList = join(" ", map { "$_:$cmd{$_}" } sort keys %cmd);
+    return SetExtensions($hash, $cmdList, @a)
+  }
+  SetExtensionsCancel($hash);
+
+  my $cmd = $a[1];
+  my $name = $hash->{NAME};
+  Log3 $name, 3, "FBDECT set $name $cmd";
+
+  if($cmd =~ m/^(on|off|toggle)$/) {
+    IOWrite($hash, ReadingsVal($name,"AIN",0), "setswitch$cmd");
+    my $state = ($cmd eq "toggle" ? ($hash->{state} eq "on" ? "off":"on"):$cmd);
+    readingsSingleUpdate($hash, "state", $state, 1);
+    return undef;
+  }
+
+  if($cmd =~ m/^(open|closed|desired-temp)$/) {
+    if($cmd eq "desired-temp") { 
+      return "Usage: set $name desired-temp value" if(int(@a) != 3);
+      return "desired-temp must be between 8 and 28"
+        if($a[2] !~ m/^[\d.]+$/ || $a[2] < 8 || $a[2] > 28)
+    }
+    my $val = ($cmd eq "open" ? 254 : ($cmd eq "closed" ? 253: int(2*$a[2])));
+    IOWrite($hash, ReadingsVal($name,"AIN",0),"sethkrtsoll&param=$val");
+    return undef;
+  }
+}
+
+###################################
+sub
+FBDECT_Set($@)
+{
+  my ($hash, @a) = @_;
+  my %sets = ("on"=>1, "off"=>1, "msgInterval"=>1);
+
+  return FBDECT_SetHttp($hash, @a)
+    if($hash->{IODev} && $hash->{IODev}{TYPE} eq "FBAHAHTTP");
+
+  my $ret = undef;
+  my $cmd = $a[1];
+  if(!$sets{$cmd}) {
+    my $usage =  join(" ", sort keys %sets);
+    return SetExtensions($hash, $usage, @a);
+  }
+  SetExtensionsCancel($hash);
+
+  my $name = $hash->{NAME};
+  Log3 $name, 3, "FBDECT set $name $cmd";
+
+  my $relay;
+  if($cmd eq "on" || $cmd eq "off") {
+    my $relay = sprintf("%08x%04x0000%08x", 15, 4, $cmd eq "on" ? 1 : 0);
+    my $msg = sprintf("%04x0000%08x$relay", $hash->{id}, length($relay)/2);
+    IOWrite($hash, "07", $msg);
+    readingsSingleUpdate($hash, "state", "set_$cmd", 1);
+  }
+  if($cmd eq "msgInterval") {
+    return "msgInterval needs seconds as parameter"
+        if(!defined($a[2]) || $a[2] !~ m/^\d+$/);
+    # Set timer for RELAY, CURRENT, VOLTAGE, POWER, ENERGY,
+    # POWER_FACTOR, TEMP, RELAY_TIMES, 
+    foreach my $i (24, 26, 27, 28, 29, 30, 31, 32) {
+      my $txt = sprintf("%08x%04x0000%08x", $i, 4, $a[2]);
+      my $msg = sprintf("%04x0000%08x$txt", $hash->{id}, length($txt)/2);
+      IOWrite($hash, "07", $msg);
+    }
+  }
+  return undef;
+}
+
+sub
+FBDECT_Get($@)
+{
+  my ($hash, @a) = @_;
+  my $ret = undef;
+  my $cmd = ($a[1] ? $a[1] : "");
+  my %gets = ("devInfo"=>1);
+
+  my $cmdList = ($hash->{IODev} && $hash->{IODev}{TYPE} eq "FBAHA") ? 
+                  join(" ", sort keys %gets) : "";
+  return "Unknown argument $cmd, choose one of $cmdList" if(!$gets{$cmd});
+
+  if($cmd eq "devInfo") {
+    my @answ = FBAHA_getDevList($hash->{IODev}, $hash->{id});
+    return $answ[0] if(@answ == 1);
+   
+    readingsBeginUpdate($hash);
+
+    if($answ[0] && 
+       $answ[0] =~ m/NAME:(.*), ID:(.*), (.*), TYPE:(.*) PROP:(.*)/) {
+      readingsBulkUpdate($hash, "FBNAME", $1, 1);
+      readingsBulkUpdate($hash, "FBTYPE", $4, 1);
+      readingsBulkUpdate($hash, "FBPROP", $5, 1);
+    }
+
+    my $d = pop @answ;
+    while($d) {
+      my ($ptyp, $plen, $pyld) = FBDECT_decodePayload($d, $hash, 0);
+      Log3 $hash, 4, "Payload: $d -> $ptyp: $pyld";
+      last if($ptyp eq "");
+      readingsBulkUpdate($hash, $ptyp, $pyld, 1);
+      push @answ, "  $ptyp: $pyld";
+      $d = substr($d, 16+$plen*2);
+    }
+    readingsEndUpdate($hash, 1);
+    return join("\n", @answ);
+  }
+
+  return undef;
+}
+
+my %fbhttp_readings = (
+   absenk          => 'sprintf("night-temp:%.1f C", $val/2)',
+   batterylow      => '"batterylow:$val"',
+   celsius         => 'sprintf("temperature:%.1f C (measured)", $val/10)',
+   energy          => 'sprintf("energy:%d Wh", $val)',
+   functionbitmask => '"FBPROP:$fbprop"',
+   fwversion       => '"fwversion:$val"',
+   id              => '"ID:$val"',
+   identifier      => '"AIN:$val"',
+   komfort         => 'sprintf("day-temp:%.1f C", $val/2)',
+   lock            => '"locked:".($val ? "yes":"no")',
+   mode            => '"mode:$val"',
+   name            => '"FBNAME:$val"',
+   offset          => 'sprintf("tempadjust:%.1f C", $val/10)', # ??
+   power           => 'sprintf("power:%.2f W", $val/1000)',
+   present         => '"present:".($val?"yes":"no")',
+   productname     => '"FBTYPE:$val"',
+   state           => '"state:".($val?"on":"off")',
+#  tist => 'sprintf("temperature:%.1f C (measured)", $val/2)', # Forum #57644
+   tsoll           => 'sprintf("desired-temp:%s", $val)',
+   members         => '"members:$val"',
+);
+
+sub
+FBDECT_ParseHttp($$$)
+{
+  my ($iodev, $msg, $type) = @_;
+  my $ioName = $iodev->{NAME};
+  my %h;
+
+  $msg =~ s,<([^/>]+?)>([^<]+?)<,$h{$1}=$2,ge; # Quick & Dirty: Tags
+  $msg =~ s, ([a-z]+?)="([^"]*)",$h{$1}=$2,ge; # Quick & Dirty: Attributes
+
+  my $ain = $h{identifier};
+  $ain =~ s/[-: ]/_/g;
+
+  my %ll = (6=>"actuator", 7=>"powerMeter", 8=>"tempSensor",
+            9=>"switch", 10=>"repeater");
+  my $lsn = int($h{functionbitmask});
+  my @fb;
+  map { push @fb, $ll{$_} if((1<<$_) & $lsn) } sort keys %ll;
+  my $fbprop = join(",", @fb);
+
+  my $dp = $modules{FBDECT}{defptr};
+  my $hash = $dp->{"$ioName:$ain"};
+  $hash = $dp->{$ain}             if(!$hash);
+  $hash = $dp->{"$ioName:$h{id}"} if(!$hash);
+  $hash = $dp->{$h{id}}           if(!$hash);
+
+  if(!$hash) {
+    my $ret = "UNDEFINED FBDECT_${ioName}_$ain FBDECT $ioName:$ain $fbprop";
+    Log3 $ioName, 3, "$ret, please define it";
+    DoTrigger("global", $ret);
+    return "";
+  }
+
+  $hash->{props} = $fbprop; # replace values from define
+  readingsBeginUpdate($hash);
+  Log3 $hash, 5, $hash->{NAME};
+  foreach my $n (keys %h) {
+    Log3 $hash, 5, "   $n = $h{$n}";
+    next if(!$fbhttp_readings{$n});
+    my $val = $h{$n};
+    $val = ($val==254 ? "on": ($val==253 ? "off" : sprintf("%0.1f C",$val/2)))
+      if($n eq "tsoll");
+    $val = $type if($n eq "productname" && $val eq "");
+    my ($ptyp,$pyld) = split(":", eval $fbhttp_readings{$n}, 2);
+    readingsBulkUpdate($hash, "state", "$ptyp: $pyld") if($n eq "tsoll");
+    readingsBulkUpdate($hash, $ptyp, $pyld);
+  }
+  readingsEndUpdate($hash, 1);
+
+  return $hash->{NAME};
+}
+
+sub
+FBDECT_renameIoDev($$)  # Called from FBAHAHTTP
+{
+  my ($new, $old) = @_;
+  my $dp = $modules{FBDECT}{defptr};
+  for my $ok (keys %{$dp}) {
+    my $nk = $ok;
+    $nk =~ s/^$old:/$new:/;
+    next if($nk eq $ok);
+    $dp->{$nk} = $dp->{$ok};
+    delete $dp->{$ok};
+  }
+}
+
+###################################
+sub
+FBDECT_Parse($$@)
+{
+  my ($iodev, $msg, $local) = @_;
+
+  return FBDECT_ParseHttp($iodev, $msg, $1) if($msg =~ m/^<(device|group) /);
+
+  my $mt = substr($msg, 0, 2);
+  my $ioName = $iodev->{NAME};
+  if($mt ne "07" && $mt ne "04") {
+    Log3 $ioName, 1, "FBDECT: unknown message type $mt";
+    return "";  # Nobody else is able to handle this
+  }
+
+  my $id = hex(substr($msg, 16, 4));
+  my $hash = $modules{FBDECT}{defptr}{"$ioName:$id"};
+  $hash = $modules{FBDECT}{defptr}{$id} if(!$hash);
+  if(!$hash) {
+    my $ret = "UNDEFINED FBDECT_${ioName}_$id FBDECT $ioName:$id switch";
+    Log3 $ioName, 3, "$ret, please define it";
+    DoTrigger("global", $ret);
+    return "";
+  }
+
+  readingsBeginUpdate($hash);
+
+  if($mt eq "07") {
+    my $d = substr($msg, 32);
+    while($d) {
+      my ($ptyp, $plen, $pyld) = FBDECT_decodePayload($d, $hash, 1);
+      Log3 $hash, 4, "Payload: $d -> $ptyp: $pyld";
+      last if($ptyp eq "");
+      readingsBulkUpdate($hash, $ptyp, $pyld);
+      $d = substr($d, 16+$plen*2);
+    }
+  }
+  if($mt eq "04") {
+    my @answ = FBAHA_configInd(substr($msg,16), $id);
+    my $state = "";
+    if($answ[0] =~ m/ inactive,/) {
+      $state = "inactive";
+
+    } else {
+      my $d = pop @answ;
+      while($d) {
+        if(length($d) <= 16) {
+          push @answ, "FBDECT_DECODE_ERROR:short payload $d";
+          last;
+        }
+        my ($ptyp, $plen, $pyld) = FBDECT_decodePayload($d, $hash, 1);
+        last if($ptyp eq "");
+        push @answ, "  $ptyp: $pyld";
+        $d = substr($d, 16+$plen*2);
+      }
+      Log3 $iodev, 4, "FBDECT PARSED: ".join(" / ", @answ);
+      # Ignore the rest, is too confusing.
+      @answ = grep /state:/, @answ;
+      (undef, $state) = split(": ", $answ[0], 2) if(@answ > 0);
+    }
+    readingsBulkUpdate($hash, "state", $state) if($state);
+  }
+
+  readingsEndUpdate($hash, 1);
+  Log3 $iodev, 5, "FBDECT_Parse for device $hash->{NAME} done";
+  return $hash->{NAME};
+}
+
+sub
+FBDECT_decodeRelayTimes($)
+{
+  my ($p) = @_;
+  return "unknown"  if(length($p) < 16);
+  return "disabled" if(substr($p, 12, 4) eq "0000");
+  return $p;
+}
+
+sub
+FBDECT_decodeTemp($$$)
+{
+  my ($p, $hash, $addReading) = @_;
+
+  my $v = hex(substr($p,0,8));
+  $v = -(4294967296-$v) if($v > 2147483648);
+  $v /= 10;
+  if(hex(substr($p,8,8))+0) {
+    readingsBulkUpdate($hash, "tempadjust", sprintf("%0.1f C", $v))
+        if($addReading);
+    return "";
+  }
+  return sprintf("%0.1f C (measured)", $v);
+}
+
+sub
+FBDECT_decodeOptions($)
+{
+  my ($p) = @_;
+  my @opts;
+
+  return "uninitialized" if($p eq "0000ffff");
+  if(length($p) >= 8) {
+    my $o = hex(substr($p,0,8));
+    push @opts, "powerOnState:".($o==0 ? "off" : ($o==1?"on" : "last"));
+  }
+  if(length($p) >= 16) {
+    my $o = hex(substr($p,8,8));
+    my @lo;
+    push @lo, "none" if($o == 0);
+    push @lo, "webUi"    if($o & 1);
+    push @lo, "remoteFB" if($o & 2);
+    push @lo, "button"   if($o & 4);
+    push @opts, "lock:".join(",", @lo);
+  }
+  return join(",", @opts);
+}
+
+sub
+FBDECT_decodeControl($)
+{
+  my ($p) = @_;
+  my @ctrl;
+
+  for(my $off=8; $off+28<=length($p)/2; $off+=28) {
+
+    if(substr($p,($off+ 8)*2,24) eq "000000050000000000000000") {
+      push @ctrl, "disabled";
+      next;
+    }
+
+    my ($n, $s);
+    $s = "on";
+
+    $n = hex(substr($p,($off+ 4)*2,8));
+    $s .= " ".($fbdect_payload{$n} ? $fbdect_payload{$n}{n} : "fn=$n");
+
+    my %tbl = (3=>">", 4=>"=>", 5=>"<", 6=>"<=");
+    $n = hex(substr($p,($off+ 8)*2,8));
+    $s .= " ".($tbl{$n} ? $tbl{$n} : "rel=$n");
+
+    $n = hex(substr($p,($off+12)*2,8));
+    $s .= sprintf(" %0.2f", $n/100);
+
+    $n = hex(substr($p,($off+16)*2,8));
+    $s .= " delay:${n}sec";
+
+    $n = hex(substr($p,($off+20)*2,8));
+    $s .= " do:".($fbdect_payload{$n} ? $fbdect_payload{$n}{n} : "fn=$n");
+
+    $n = hex(substr($p,($off+24)*2,8));
+    $s .= " ".($n==0 ? "off" : "on");
+
+    push @ctrl, $s;
+  }
+
+  return join(",", @ctrl);
+}
+
+sub
+FBDECT_decodePayload($$$)
+{
+  my ($d, $hash, $addReading) = @_;
+  if(length($d) < 12) {
+    Log3 $hash, 4, "FBDECT ignoring payload: data too short";
+    return ("", "", "");
+  }
+
+  my $ptyp = hex(substr($d, 0, 8));
+  my $plen = hex(substr($d, 8, 4));
+  if(length($d) < 16+$plen*2) {
+    Log3 $hash, 4, "FBDECT ignoring payload: data shorter than given length($plen)";
+    return ("", "", "");
+  }
+  my $pyld = substr($d, 16, $plen*2);
+
+  if($fbdect_payload{$ptyp}) {
+    $cmdFromAnalyze = $fbdect_payload{$ptyp}{fmt};
+    $pyld = eval $cmdFromAnalyze if($cmdFromAnalyze);
+    $cmdFromAnalyze = undef;
+
+    $ptyp = ($pyld ? $fbdect_payload{$ptyp}{n} : "");
+  }
+  return ($ptyp, $plen, $pyld);
+}
+
+#####################################
+sub
+FBDECT_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  my $homeId = $hash->{homeId};
+  my $id = $hash->{id};
+  delete $modules{FBDECT}{defptr}{$id};
+  return undef;
+}
+
+1;
+
+=pod
+=item summary    DECT devices connected via the Fritz!OS AHA Server
+=item summary_DE &uuml;ber den Fritz!OS AHA Server angebundene DECT Ger&auml;te
+=begin html
+
+<a name="FBDECT"></a>
+<h3>FBDECT</h3>
+<ul>
+  This module is used to control AVM FRITZ!DECT devices via FHEM, see also the
+  <a href="#FBAHA">FBAHA</a> or <a href="#FBAHAHTTP">FBAHAHTTP</a> module for
+  the base.
+  <br><br>
+  <a name="FBDECTdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FBDECT [&lt;FBAHAname&gt;:]&lt;id&gt; props</code>
+  <br>
+  <br>
+  Example:
+  <ul>
+    <code>define lamp FBDECT 16 switch,powerMeter</code><br>
+  </ul>
+  <b>Note:</b>Usually the device is created via
+  <a href="#autocreate">autocreate</a>. If you rename the corresponding FBAHA
+  device, take care to modify the FBDECT definitions, as it is not done
+  automatically.
+  </ul>
+  <br>
+  <br>
+
+  <a name="FBDECTset"></a>
+  <b>Set</b>
+  <ul>
+  <li>on/off<br>
+    set the device on or off.
+    </li>
+
+  <li>desired-temp &lt;value&gt;<br>
+    set the desired temp on a Comet DECT (FBAHAHTTP IOdev only)
+    </li>
+
+  <li><a href="#setExtensions">set extensions</a> are supported.
+   </li>
+
+  <li>msgInterval &lt;sec&gt;<br>
+    Number of seconds between the sensor messages (FBAHA IODev only).
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBDECTget"></a>
+  <b>Get</b>
+  <ul>
+  <li>devInfo<br>
+    report device information (FBAHA IODev only)
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBDECTattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#dummy">dummy</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#model">model</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+  <a name="FBDECTevents"></a>
+  <b>Generated events:</b>
+  <ul>
+    <li>on</li>
+    <li>off</li>
+    <li>set_on</li>
+    <li>set_off</li>
+    <li>current: $v A</li>
+    <li>voltage: $v V</li>
+    <li>power: $v W</li>
+    <li>energy: $v Wh</li>
+    <li>powerFactor: $v"</li>
+    <li>temperature: $v C (measured)</li>
+    <li>tempadjust: $v C</li>
+    <li>options: uninitialized</li>
+    <li>options: powerOnState:[on|off|last],lock:[none,webUi,remoteFb,button]</li>
+    <li>control: disabled</li>
+    <li>control: on power < $v delay:$d sec do:state [on|off]</li>
+    <li>relaytimes: disabled</li>
+    <li>relaytimes: HEX</li>
+  </ul>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="FBDECT"></a>
+<h3>FBDECT</h3>
+<ul>
+  Dieses Modul wird verwendet, um AVM FRITZ!DECT Ger&auml;te via FHEM zu
+  steuern, siehe auch das <a href="#FBAHA">FBAHA</a> oder <a
+  href="#FBAHAHTTP">FBAHAHTTP</a> Modul f&uumlr die Anbindung an das FRITZ!Box.
+  <br><br>
+  <a name="FBDECTdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FBDECT [&lt;FBAHAname&gt;:]&lt;id&gt; props</code>
+  <br>
+  <br>
+  Beispiel:
+  <ul>
+    <code>define lampe FBDECT 16 switch,powerMeter</code><br>
+  </ul>
+  <b>Achtung:</b>FBDECT Eintr&auml;ge werden normalerweise per 
+  <a href="#autocreate">autocreate</a> angelegt. Falls sie die zugeordnete 
+  FBAHA oder FBAHAHTTP Instanz umbenennen, dann muss die FBDECT Definition
+  manuell angepasst werden.
+  </ul>
+  <br>
+  <br
+
+  <a name="FBDECTset"></a>
+  <b>Set</b>
+  <ul>
+  <li>on/off<br>
+    Ger&auml;t einschalten bzw. ausschalten.</li>
+  <li>desired-temp &lt;value&/gt;<br>
+    Gew&uuml;nschte Temperatur beim Comet DECT setzen (nur mit FBAHAHTTP als
+    IODev).
+    </li>
+  <li>
+    Die <a href="#setExtensions">set extensions</a> werden
+    unterst&uuml;tzt.
+    </li>
+  <li>msgInterval &lt;sec&gt;<br>
+    Anzahl der Sekunden zwischen den Sensornachrichten (nur mit FBAHA als
+    IODev).
+    </li>
+  </ul>
+  <br>
+
+  <a name="FBDECTget"></a>
+  <b>Get</b>
+  <ul>
+  <li>devInfo<br>
+  meldet Ger&auml;te-Informationen (nur mit FBAHA als IODev)</li>
+  </ul>
+  <br>
+
+  <a name="FBDECTattr"></a>
+  <b>Attribute</b>
+  <ul>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#dummy">dummy</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#model">model</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+  <a name="FBDECTevents"></a>
+  <b>Generierte events:</b>
+  <ul>
+    <li>on</li>
+    <li>off</li>
+    <li>set_on</li>
+    <li>set_off</li>
+    <li>current: $v A</li>
+    <li>voltage: $v V</li>
+    <li>power: $v W</li>
+    <li>energy: $v Wh</li>
+    <li>powerFactor: $v"</li>
+    <li>temperature: $v C ([measured|corrected])</li>
+    <li>options: uninitialized</li>
+    <li>options: powerOnState:[on|off|last],lock:[none,webUi,remoteFb,button]</li>
+    <li>control: disabled</li>
+    <li>control: on power < $v delay:$d sec do:state [on|off]</li>
+    <li>relaytimes: disabled</li>
+    <li>relaytimes: HEX</li>
+  </ul>
+</ul>
+=end html_DE
+
+=cut

ファイルの差分が大きいため隠しています
+ 1183 - 0
fhem/core/FHEM/10_FRM.pm


+ 932 - 0
fhem/core/FHEM/10_FS20.pm

@@ -0,0 +1,932 @@
+##############################################
+# $Id: 10_FS20.pm 12688 2016-11-29 20:40:24Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;
+
+my %codes = (
+  "00" => "off",
+  "01" => "dim06%",
+  "02" => "dim12%",
+  "03" => "dim18%",
+  "04" => "dim25%",
+  "05" => "dim31%",
+  "06" => "dim37%",
+  "07" => "dim43%",
+  "08" => "dim50%",
+  "09" => "dim56%",
+  "0a" => "dim62%",
+  "0b" => "dim68%",
+  "0c" => "dim75%",
+  "0d" => "dim81%",
+  "0e" => "dim87%",
+  "0f" => "dim93%",
+  "10" => "dim100%",
+  "11" => "on",		# Set to previous dim value (before switching it off)
+  "12" => "toggle",	# between off and previous dim val
+  "13" => "dimup",
+  "14" => "dimdown",
+  "15" => "dimupdown",
+  "16" => "timer",
+  "17" => "sendstate",
+  "18" => "off-for-timer",
+  "19" => "on-for-timer",
+  "1a" => "on-old-for-timer",
+  "1b" => "reset",
+  "1c" => "ramp-on-time",      #time to reach the desired dim value on dimmers
+  "1d" => "ramp-off-time",     #time to reach the off state on dimmers
+  "1e" => "on-old-for-timer-prev", # old val for timer, then go to prev. state
+  "1f" => "on-100-for-timer-prev", # 100% for timer, then go to previous state
+
+);
+
+my %readonly = (
+  "thermo-on" => 1,
+  "thermo-off" => 1,
+);
+
+use vars qw(%fs20_c2b);		# Peter would like to access it from outside
+
+my $fs20_simple ="off off-for-timer on on-for-timer reset timer toggle";
+my %models = (
+    fs20fms     => 'sender',
+    fs20hgs     => 'sender',
+    fs20irl     => 'sender',
+    fs20kse     => 'sender',
+    fs20ls      => 'sender',
+    fs20pira    => 'sender',
+    fs20piri    => 'sender',
+    fs20piru    => 'sender',
+    fs20s16     => 'sender',
+    fs20s20     => 'sender',
+    fs20s4      => 'sender',
+    fs20s4a     => 'sender',
+    fs20s4m     => 'sender',
+    fs20s4u     => 'sender',
+    fs20s4ub    => 'sender',
+    fs20s8      => 'sender',
+    fs20s8m     => 'sender',
+    fs20sd      => 'sender',    # Sensor: Daemmerung
+    fs20sn      => 'sender',    # Sensor: Naeherung
+    fs20sr      => 'sender',    # Sensor: Regen
+    fs20ss      => 'sender',    # Sensor: Sprache
+    fs20str     => 'sender',    # Sensor: Thermostat+Regelung
+    fs20tc1     => 'sender',
+    fs20tc6     => 'sender',    # TouchControl x 6
+    fs20tfk     => 'sender',    # TuerFensterKontakt
+    fs20tk      => 'sender',    # TuerKlingel
+    fs20uts     => 'sender',    # Universal Thermostat Sender
+    fs20ze      => 'sender',    # FunkTimer (ZeitEinheit?)
+    fs20bf      => 'sender',    # BodenFeuchte
+    fs20bs      => 'sender',    # Beschattung
+    fs20si3     => 'sender',    # 3 Kanal Schaltinterface
+    dummySender => 'sender',
+
+    fs20di      => 'dimmer',
+    fs20di10    => 'dimmer',
+    fs20du      => 'dimmer',
+    dummyDimmer => 'dimmer',
+
+    fs20as1     => 'simple',
+    fs20as4     => 'simple',
+    fs20ms2     => 'simple',
+    fs20rgbsa   => 'simple',
+    fs20rst     => 'simple',
+    fs20rsu     => 'simple',
+    fs20sa      => 'simple',
+    fs20sig     => 'simple',
+    fs20sm4     => 'simple',
+    fs20sm8     => 'simple',
+    fs20st      => 'simple',
+    fs20st2     => 'simple',
+    fs20su      => 'simple',
+    fs20sv      => 'simple',
+    fs20ue1     => 'simple',
+    fs20usr     => 'simple',
+    fs20ws1     => 'simple',
+    dummySimple => 'simple',
+
+);
+
+sub hex2four($);
+sub four2hex($$);
+
+sub
+FS20_Initialize($)
+{
+  my ($hash) = @_;
+
+  foreach my $k (keys %codes) {
+    $fs20_c2b{$codes{$k}} = $k;
+  }
+  $hash->{Match}     = "^81..(04|0c)..0101a001";
+  $hash->{SetFn}     = "FS20_Set";
+  $hash->{DefFn}     = "FS20_Define";
+  $hash->{UndefFn}   = "FS20_Undef";
+  $hash->{ParseFn}   = "FS20_Parse";
+  $hash->{AttrList}  = "IODev follow-on-for-timer:1,0 follow-on-timer ".
+                       "do_not_notify:1,0 ignore:1,0 dummy:1,0 showtime:1,0 ".
+                       "$readingFnAttributes " .
+                       "model:".join(",", sort keys %models);
+}
+
+sub
+FS20_Follow($$$$)
+{
+  my ($name, $arg, $na, $val) = @_;
+
+  ###########################################
+  # Set the state of a device to off if on-for-timer is called
+  if($modules{FS20}{ldata}{$name}) {
+    CommandDelete(undef, $name . "_timer");
+    delete $modules{FS20}{ldata}{$name};
+    delete $defs{$name}->{TIMED_OnOff} if( $defs{$name} );
+  }
+
+  my $newState="";
+  my $onTime = AttrVal($name, "follow-on-timer", undef);
+
+  ####################################
+  # following timers
+  if(($arg eq "on" || $arg =~ m/dim/) && $na == 2 && $onTime) {
+    $newState = "off";
+    $val = $onTime;
+
+  } elsif($arg =~ m/(on|off).*-for-timer/ && $na == 3 &&
+     AttrVal($name, "follow-on-for-timer", undef)) {
+    $newState = ($1 eq "on" ? "off" : "on");
+
+  }
+
+  if($newState) {
+    my $to = sprintf("%02d:%02d:%02d", $val/3600, ($val%3600)/60, $val%60);
+    $modules{FS20}{ldata}{$name} = $to;
+    Log3 $name, 4, "Follow: +$to setstate $name $newState";
+    CommandDefine(undef, $name."_timer at +$to ".
+      "{readingsSingleUpdate(\$defs{'$name'},'state','$newState', 1);".
+      "delete \$defs{'$name'}->{TIMED_OnOff}; undef}");
+
+    if($defs{$name}) {
+      $defs{$name}->{TIMED_OnOff} = {
+        START=>time(),
+        START_FMT=>TimeNow(),
+        DURATION=>$val,
+        CMD=>$arg 
+      }
+    }
+  }
+}
+
+###################################
+sub
+FS20_Set($@)
+{
+  my ($hash, @a) = @_;
+  my $ret = undef;
+  my $na = int(@a);
+
+  return "no set value specified" if($na < 2);
+  return "Readonly value $a[1]" if(defined($readonly{$a[1]}));
+
+  if($na > 2 && $a[1] eq "dim") {
+    $a[1] = ($a[2] eq "0" ? "off" : sprintf("dim%02d%%",$a[2]) );
+    splice @a, 2, 1;
+    $na = int(@a);
+  }
+
+  my $c = $fs20_c2b{$a[1]};
+  my $name = $a[0];
+  if(!defined($c)) {
+
+    # Model specific set arguments
+    my $list;
+    if(defined($attr{$name}) && defined($attr{$name}{"model"})) {
+      my $mt = $models{$attr{$name}{"model"}};
+      $list = "" if($mt && $mt eq "sender");
+      $list = $fs20_simple if($mt && $mt eq "simple");
+    }
+    $list = (join(" ", sort keys %fs20_c2b) . " dim:slider,0,6.25,100")
+        if(!defined($list));
+    return SetExtensions($hash, $list, @a);
+  }
+  SetExtensionsCancel($hash);
+
+  return "Bad time spec" if($na == 3 && $a[2] !~ m/^\d*\.?\d+$/);
+
+  my $v = join(" ", @a);
+  Log3 $name, 3, "FS20 set $v";
+  (undef, $v) = split(" ", $v, 2);	# Not interested in the name...
+
+  my $val;
+
+  if($na == 3) {                                # Timed command.
+    $c = sprintf("%02X", (hex($c) | 0x20)); # Set the extension bit
+
+    ########################
+    # Calculating the time.
+    LOOP: for(my $i = 0; $i <= 12; $i++) {
+      for(my $j = 0; $j <= 15; $j++) {
+        $val = (2**$i)*$j*0.25;
+        if($val >= $a[2]) {
+          if($val != $a[2]) {
+            Log3 $name, 2, "$name: changing timeout to $val from $a[2]";
+          }
+          $c .= sprintf("%x%x", $i, $j);
+          last LOOP;
+        }
+      }
+    }
+    return "Specified timeout too large, max is 15360" if(length($c) == 2);
+  }
+
+  IOWrite($hash, "04", "010101" . $hash->{XMIT} . $hash->{BTN} . $c);
+
+  ##########################
+  # Look for all devices with the same code, and set state, timestamp
+  my $code = "$hash->{XMIT} $hash->{BTN}";
+  my $defptr = $modules{FS20}{defptr}{$code};
+  foreach my $n (keys %{ $defptr }) {
+    FS20_Follow($defptr->{$n}->{NAME}, $a[1], $na, $val);
+    readingsSingleUpdate($defptr->{$n}, "state", $v, 1);
+  }
+  return $ret;
+}
+
+#############################
+sub
+FS20_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  my $u = "wrong syntax: define <name> FS20 housecode " .
+                        "addr [fg addr] [lm addr] [gm FF]";
+
+  return $u if(int(@a) < 4);
+  return "Define $a[0]: wrong housecode format: specify a 4 digit hex value ".
+         "or an 8 digit quad value"
+  		if( ($a[2] !~ m/^[a-f0-9]{4}$/i) && ($a[2] !~ m/^[1-4]{8}$/i) );
+
+  return "Define $a[0]: wrong btn format: specify a 2 digit hex value " .
+         "or a 4 digit quad value"
+  		if( ($a[3] !~ m/^[a-f0-9]{2}$/i) && ($a[3] !~ m/^[1-4]{4}$/i) );
+
+  my $housecode = $a[2];
+  $housecode = four2hex($housecode,4) if (length($housecode) == 8);
+
+  my $btncode = $a[3];
+  $btncode = four2hex($btncode,2) if (length($btncode) == 4);
+
+  $hash->{XMIT} = lc($housecode);
+  $hash->{BTN}  = lc($btncode);
+
+  my $code = lc("$housecode $btncode");
+  my $ncode = 1;
+  my $name = $a[0];
+  $hash->{CODE}{$ncode++} = $code;
+  $modules{FS20}{defptr}{$code}{$name}   = $hash;
+
+  for(my $i = 4; $i < int(@a); $i += 2) {
+
+    return "No address specified for $a[$i]" if($i == int(@a)-1);
+
+    $a[$i] = lc($a[$i]);
+    if($a[$i] eq "fg") {
+      return "Bad fg address for $name, see the doc"
+        if( ($a[$i+1] !~ m/^f[a-f0-9]$/) && ($a[$i+1] !~ m/^44[1-4][1-4]$/));
+    } elsif($a[$i] eq "lm") {
+      return "Bad lm address for $name, see the doc"
+        if( ($a[$i+1] !~ m/^[a-f0-9]f$/) && ($a[$i+1] !~ m/^[1-4][1-4]44$/));
+    } elsif($a[$i] eq "gm") {
+      return "Bad gm address for $name, must be ff"
+        if( ($a[$i+1] ne "ff") && ($a[$i+1] ne "4444"));
+    } else {
+      return $u;
+    }
+
+    my $grpcode = $a[$i+1];
+    if (length($grpcode) == 4) {
+       $grpcode = four2hex($grpcode,2);
+    }
+
+    $code = "$housecode $grpcode";
+    $hash->{CODE}{$ncode++} = $code;
+    $modules{FS20}{defptr}{$code}{$name}   = $hash;
+  }
+  AssignIoPort($hash);
+}
+
+#############################
+sub
+FS20_Undef($$)
+{
+  my ($hash, $name) = @_;
+
+  foreach my $c (keys %{ $hash->{CODE} } ) {
+    $c = $hash->{CODE}{$c};
+
+    # As after a rename the $name my be different from the $defptr{$c}{$n}
+    # we look for the hash.
+    foreach my $dname (keys %{ $modules{FS20}{defptr}{$c} }) {
+      delete($modules{FS20}{defptr}{$c}{$dname})
+        if($modules{FS20}{defptr}{$c}{$dname} == $hash);
+    }
+  }
+  return undef;
+}
+
+sub
+FS20_Parse($$)
+{
+  my ($hash, $msg) = @_;
+
+  # Msg format: 
+  # 81 0b 04 f7 0101 a001 HHHH 01 00 11
+
+  my $dev = substr($msg, 16, 4);
+  my $btn = substr($msg, 20, 2);
+  my $cde = substr($msg, 24, 2);
+
+
+  my $dur = 0;
+  my $cx = hex($cde);
+  if($cx & 0x20) {      # Timed command
+    $dur = hex(substr($msg, 26, 2));
+    my $i = ($dur & 0xf0) / 16;
+    my $j = ($dur & 0xf);
+    $dur = (2**$i)*$j*0.25;
+    $cde = sprintf("%02x", $cx & ~0x20);
+  }
+
+  my $v = $codes{$cde};
+  $v = "unknown_$cde" if(!defined($v));
+  $v .= " $dur" if($dur);
+
+
+  my $def = $modules{FS20}{defptr}{"$dev $btn"};
+  if($def) {
+    my @list;
+    foreach my $n (keys %{ $def }) {
+      my $lh = $def->{$n};
+      $n = $lh->{NAME};        # It may be renamed
+
+      return "" if(IsIgnored($n));   # Little strange.
+
+      readingsSingleUpdate($lh, "state", $v, 1);
+      Log3 $n, 4, "FS20 $n $v";
+
+      if($modules{FS20}{ldata}{$n}) {
+        CommandDelete(undef, $n . "_timer");
+        delete $modules{FS20}{ldata}{$n};
+        delete $lh->{TIMED_OnOff};
+      }
+
+      my $newState = "";
+      if($v =~ m/(on|off).*-for-timer/ && $dur &&
+        AttrVal($n, "follow-on-for-timer", undef)) {
+        $newState = ($1 eq "on" ? "off" : "on");
+
+      } elsif($v eq "on" && (my $d = AttrVal($n, "follow-on-timer", undef))) {
+        $dur = $d;
+        $newState = "off";
+
+      }
+
+      if($newState) {
+        my $to = sprintf("%02d:%02d:%02d", $dur/3600, ($dur%3600)/60, $dur%60);
+        Log3 $n, 4, "Follow: +$to setstate $n $newState";
+        CommandDefine(undef, $n."_timer at +$to ".
+          "{readingsSingleUpdate(\$defs{'$n'},'state','$newState', 1); ".
+          "delete \$defs{'$n'}->{TIMED_OnOff}; undef}");
+        $modules{FS20}{ldata}{$n} = $to;
+        $lh->{TIMED_OnOff} = {
+          START=>time(),
+          START_FMT=>TimeNow(),
+          DURATION=>$dur,
+          CMD=>$v
+        };
+      }
+
+      push(@list, $n);
+    }
+    return @list;
+
+  } else {
+    # Special FHZ initialization parameter. In Multi-FHZ-Mode we receive
+    # it by the second FHZ
+    return "" if($dev eq "0001" && $btn eq "00" && $cde eq "00");
+
+    my $dev_four = hex2four($dev);
+    my $btn_four = hex2four($btn);
+    Log3 $hash, 3, "FS20 Unknown device $dev ($dev_four), " .
+                "Button $btn ($btn_four) Code $cde ($v), please define it";
+    return "UNDEFINED FS20_$dev$btn FS20 $dev $btn";
+  }
+
+}
+
+#############################
+sub
+hex2four($)
+{
+  my $v = shift;
+  my $r = "";
+  foreach my $x (split("", $v)) {
+    $r .= sprintf("%d%d", (hex($x)/4)+1, (hex($x)%4)+1);
+  }
+  return $r;
+}
+
+#############################
+sub
+four2hex($$)
+{
+  my ($v,$len) = @_;
+  my $r = 0;
+  foreach my $x (split("", $v)) {
+    $r = $r*4+($x-1);
+  }
+  return sprintf("%0*x", $len,$r);
+}
+
+
+1;
+
+=pod
+=item summary    devices communicating via the ELV FS20 protocol
+=item summary_DE Anbindung von FS20 Ger&auml;ten
+=begin html
+
+<a name="FS20"></a>
+<h3>FS20</h3>
+<ul>
+  The FS20 protocol is used by a wide range of devices, which are either of
+  the sender/sensor category or the receiver/actuator category.  The radio
+  (868.35 MHz) messages are either received through an <a href="#FHZ">FHZ</a>
+  or an <a href="#CUL">CUL</a> device, so this must be defined first.
+
+  <br><br>
+
+  <a name="FS20define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FS20 &lt;housecode&gt; &lt;button&gt;
+    [fg &lt;fgaddr&gt;] [lm &lt;lmaddr&gt;] [gm FF] </code>
+    <br><br>
+
+   The values of housecode, button, fg, lm, and gm can be either defined as
+   hexadecimal value or as ELV-like "quad-decimal" value with digits 1-4. We
+   will reference this ELV-like notation as ELV4 later in this document. You
+   may even mix both hexadecimal and ELV4 notations, because FHEM can detect
+   the used notation automatically by counting the digits.<br>
+
+   <ul>
+   <li><code>&lt;housecode&gt;</code> is a 4 digit hex or 8 digit ELV4 number,
+     corresponding to the housecode address.</li>
+   <li><code>&lt;button&gt;</code> is a 2 digit hex or 4 digit ELV4 number,
+     corresponding to a button of the transmitter.</li>
+   <li>The optional <code>&lt;fgaddr&gt;</code> specifies the function group.
+     It is a 2 digit hex or 4 digit ELV address. The first digit of the hex
+     address must be F or the first 2 digits of the ELV4 address must be
+     44.</li>
+   <li>The optional <code>&lt;lmaddr&gt;</code> specifies the local
+     master. It is a 2 digit hex or 4 digit ELV address.  The last digit of the
+     hex address must be F or the last 2 digits of the ELV4 address must be
+     44.</li>
+   <li>The optional gm specifies the global master, the address must be FF if
+     defined as hex value or 4444 if defined as ELV4 value.</li>
+   </ul>
+   <br>
+
+    Examples:
+    <ul>
+      <code>define lamp FS20 7777 00 fg F1 gm F</code><br>
+      <code>define roll1 FS20 7777 01</code><br>
+      <code>define otherlamp FS20 24242424 1111 fg 4412 gm 4444</code><br>
+      <code>define otherroll1 FS20 24242424 1114</code>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="FS20set"></a>
+  <b>Set </b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt]</code>
+    <br><br>
+    where <code>value</code> is one of:<br>
+    <ul><code>
+      dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50%<br>
+      dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%<br>
+      dimdown<br>
+      dimup<br>
+      dimupdown<br>
+      off<br>
+      off-for-timer<br>
+      on                # dimmer: set to value before switching it off<br>
+      on-for-timer      # see the note<br>
+      on-old-for-timer  # set to previous (before switching it on)<br>
+      ramp-on-time      # time to reach the desired dim value on dimmers<br>
+      ramp-off-time     # time to reach the off state on dimmers<br>
+      reset<br>
+      sendstate<br>
+      timer<br>
+      toggle            # between off and previous dim val<br>
+    </code></ul>
+    The <a href="#setExtensions"> set extensions</a> are also supported.<br>
+    <br>
+    Examples:
+    <ul>
+      <code>set lamp on</code><br>
+      <code>set lamp1,lamp2,lamp3 on</code><br>
+      <code>set lamp1-lamp3 on</code><br>
+      <code>set lamp on-for-timer 12</code><br>
+    </ul>
+    <br>
+
+    Notes:
+    <ul>
+      <li>Use reset with care: the device forgets even the housecode.
+      </li>
+      <li>As the FS20 protocol needs about 0.22 seconds to transmit a
+      sequence, a pause of 0.22 seconds is inserted after each command.
+      </li>
+      <li>The FS20ST switches on for dim*%, dimup. It does not respond to
+          sendstate.</li>
+      <li>If the timer is set (i.e. it is not 0) then on, dim*,
+          and *-for-timer will take it into account (at least by the FS20ST).
+      </li>
+      <li>The <code>time</code> argument ranges from 0.25sec to 4 hours and 16
+          minutes.  As the time is encoded in one byte there are only 112
+          distinct values, the resolution gets coarse with larger values. The
+          program will report the used timeout if the specified one cannot be
+          set exactly.  The resolution is 0.25 sec from 0 to 4 sec, 0.5 sec
+          from 4 to 8 sec, 1 sec from 8 to 16 sec and so on. If you need better
+          precision for large values, use <a href="#at">at</a> which has a 1
+          sec resolution.</li>
+    </ul>
+  </ul>
+  <br>
+
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="FS20attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <a name="IODev"></a>
+    <li>IODev<br>
+        Set the IO or physical device which should be used for sending signals
+        for this "logical" device. An example for the physical device is an FHZ
+        or a CUL. Note: Upon startup FHEM assigns each logical device
+        (FS20/HMS/KS300/etc) the last physical device which can receive data
+        for this type of device. The attribute IODev needs to be used only if
+        you attached more than one physical device capable of receiving signals
+        for this logical device.</li><br>
+
+    <a name="eventMap"></a>
+    <li>eventMap<br>
+        Replace event names and set arguments. The value of this attribute
+        consists of a list of space separated values, each value is a colon
+        separated pair. The first part specifies the "old" value, the second
+        the new/desired value. If the first character is slash(/) or komma(,)
+        then split not by space but by this character, enabling to embed spaces.
+        Examples:<ul><code>
+        attr store eventMap on:open off:closed<br>
+        attr store eventMap /on-for-timer 10:open/off:closed/<br>
+        set store open
+        </code></ul>
+        </li><br>
+
+    <a name="attrdummy"></a>
+    <li>dummy<br>
+    Set the device attribute dummy to define devices which should not
+    output any radio signals. Associated notifys will be executed if
+    the signal is received. Used e.g. to react to a code from a sender, but
+    it will not emit radio signal if triggered in the web frontend.
+    </li><br>
+
+    <a name="follow-on-for-timer"></a>
+    <li>follow-on-for-timer<br>
+    schedule a "setstate off;trigger off" for the time specified as argument to
+    the on-for-timer command. Or the same with on, if the command is
+    off-for-timer.
+    </li><br>
+
+    <a name="follow-on-timer"></a>
+    <li>follow-on-timer<br>
+    Like with follow-on-for-timer schedule a "setstate off;trigger off", but
+    this time for the time specified as argument in seconds to this attribute.
+    This is used to follow the pre-programmed timer, which was set previously
+    with the timer command or manually by pressing the button on the device,
+    see your manual for details. Works for on and dim commands.
+    </li><br>
+
+
+    <a name="model"></a>
+    <li>model<br>
+        The model attribute denotes the model type of the device.
+        The attributes will (currently) not be used by the fhem.pl directly.
+        It can be used by e.g. external programs or web interfaces to
+        distinguish classes of devices and send the appropriate commands
+        (e.g. "on" or "off" to a fs20st, "dim..%" to fs20du etc.).
+        The spelling of the model names are as quoted on the printed
+        documentation which comes which each device. This name is used
+        without blanks in all lower-case letters. Valid characters should be
+        <code>a-z 0-9</code> and <code>-</code> (dash),
+        other characters should be ommited. Here is a list of "official"
+        devices:<br><br>
+          <b>Sender/Sensor</b>: fs20fms fs20hgs fs20irl fs20kse fs20ls
+          fs20pira fs20piri fs20piru fs20s16 fs20s20 fs20s4  fs20s4a fs20s4m
+          fs20s4u fs20s4ub fs20s8 fs20s8m fs20sd  fs20sn  fs20sr fs20ss
+          fs20str fs20tc1 fs20tc6 fs20tfk fs20tk  fs20uts fs20ze fs20bf fs20si3<br><br>
+
+          <b>Dimmer</b>: fs20di  fs20di10 fs20du<br><br>
+
+          <b>Receiver/Actor</b>: fs20as1 fs20as4 fs20ms2 fs20rgbsa fs20rst
+          fs20rsu fs20sa fs20sig fs20sm4 fs20sm8 fs20st fs20su fs20sv fs20ue1
+          fs20usr fs20ws1
+    </li><br>
+
+
+    <a name="ignore"></a>
+    <li>ignore<br>
+        Ignore this device, e.g. if it belongs to your neighbour. The device
+        won't trigger any FileLogs/notifys, issued commands will silently
+        ignored (no RF signal will be sent out, just like for the <a
+        href="#attrdummy">dummy</a> attribute). The device won't appear in the
+        list command (only if it is explicitely asked for it), nor will it
+        appear in commands which use some wildcard/attribute as name specifiers
+        (see <a href="#devspec">devspec</a>). You still get them with the
+        "ignored=1" special devspec.
+        </li><br>
+
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+
+  </ul>
+  <br>
+
+  <a name="FS20events"></a>
+  <b>Generated events:</b>
+  <ul>
+     From an FS20 device you can receive one of the following events.
+     <li>on</li>
+     <li>off</li>
+     <li>toggle</li>
+     <li>dimdown</li>
+     <li>dimup</li>
+     <li>dimupdown</li>
+     <li>on-for-timer</li>
+     Which event is sent is device dependent and can sometimes configured on
+     the device.
+  </ul>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="FS20"></a>
+<h3>FS20</h3>
+<ul>
+  Das FS20 Protokoll wird von einem gro&szlig;en Spektrum an Ger&auml;ten
+  verwendet.  Diese stammen entweder aus der Kategorie Sensor/Sender oder
+  Aktor/Empf&auml;nger.  Die Funknachrichten (868.35 MHz) k&ouml;nnen mit einem
+  <a href="#FHZ">FHZ</a> oder einem <a href="#CUL">CUL</a> empfangen werden.
+  Dieses muss daher zuerst definiert werden.
+  <br><br>
+
+  <a name="FS20define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FS20 &lt;housecode&gt; &lt;button&gt;
+    [fg &lt;fgaddr&gt;] [lm &lt;lmaddr&gt;] [gm FF] </code>
+    <br><br>
+
+   Die Werte housecode, button, fg, lm, und gm k&ouml;nnen entweder hexadezimal
+   oder in der ELV-typischen quatern&auml;ren Notation (Zahlen von 1-4)
+   eingegeben werden.
+   Hier und auch in sp&auml;teren Beispielen wird als Referenz die ELV4
+   Notation verwendet. Die Notationen k&ouml;nnen auch gemischt werden da FHEM
+   die verwendete Notation durch z&auml;hlen der Zeichen erkennt.<br>
+
+   <ul>
+   <li><code>&lt;housecode&gt;</code> ist eine 4 stellige Hex oder 8 stellige
+     ELV4 Zahl, entsprechend der Hauscode Adresse.</li>
+
+   <li><code>&lt;button&gt;</code> ist eine 2 stellige Hex oder 4 stellige ELV4
+     Zahl, entsprechend dem Button des Transmitters.</li>
+
+   <li>Optional definiert <code>&lt;fgaddr&gt;</code> die Funktionsgruppe mit
+     einer 2 stelligen Hex oder 4 stelligen  ELV4 Adresse. Bei Hex muss die
+     erste Stelle F, bei ELV4 die ersten zwei Stellen 44 sein.</li>
+
+   <li>Optional definiert <code>&lt;lmaddr&gt;</code> definiert einen local
+     master mit einer 2 stelligen Hex oder 4 stelligen  ELV4 Adresse. Bei Hex
+     muss die letzte Stelle F, bei ELV4 die letzten zwei Stellen 44 sein.</li>
+
+   <li>Optional definiert  gm den global master. Die Adresse muss FF bei HEX
+     und 4444 bei ELV4 Notation sein.</li>
+
+   </ul>
+   <br>
+
+    Beispiele:
+    <ul>
+      <code>define lamp FS20 7777 00 fg F1 gm F</code><br>
+      <code>define roll1 FS20 7777 01</code><br>
+      <code>define otherlamp FS20 24242424 1111 fg 4412 gm 4444</code><br>
+      <code>define otherroll1 FS20 24242424 1114</code>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="FS20set"></a>
+  <b>Set </b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt]</code>
+    <br><br>
+    Wobei <code>value</code> einer der folgenden Werte sein kann:<br>
+    <ul><code>
+      dim06% dim12% dim18% dim25% dim31% dim37% dim43% dim50%<br>
+      dim56% dim62% dim68% dim75% dim81% dim87% dim93% dim100%<br>
+      dimdown<br>
+      dimup<br>
+      dimupdown<br>
+      off<br>
+      off-for-timer<br>
+      on                # dimmer: Setze auf diesen Wert vor dem Ausschalten<br>
+      on-for-timer      # Siehe Hinweise<br>
+      on-old-for-timer  # Setze zum vorherigen (vor dem Einschalten)<br>
+      ramp-on-time      # Zeit bis zum erreichen des gew&uuml;nschten Dim-Wertes<br>
+      ramp-off-time     # Zeit bis zum Ausschalten bei Dimmern<br>
+      reset<br>
+      sendstate<br>
+      timer<br>
+      toggle            # zwischen aus und dem letztern Dim-Wert<br>
+    </code></ul><br>
+    Die<a href="#setExtensions"> set extensions</a> sind ebenfalls
+    unterst&uuml;tzt.<br>
+    <br>
+    Beispiele:
+    <ul>
+      <code>set lamp on</code><br>
+      <code>set lamp1,lamp2,lamp3 on</code><br>
+      <code>set lamp1-lamp3 on</code><br>
+      <code>set lamp on-for-timer 12</code><br>
+    </ul>
+    <br>
+
+    Hinweise:
+    <ul>
+      <li>reset nur mit Vorsicht verwenden: Auch der Hauscode wird
+        gel&ouml;scht.  </li>
+
+      <li>Da das FS20 Protokoll 0.22Sek f&uuml;r eine Funksequenz ben&ouml;tigt
+        wird nach jeder Ausf&uuml;hrung eine Pause von 0.22Sek eingef&uuml;gt.
+        </li>
+
+      <li>Das FS20ST schaltet f&uuml;r dim*% und dimup ein. Es reagiert nicht
+        auf sendstate.</li>
+
+      <li>Wenn ein Timer gesetzt ist (und dieser nicht 0 ist) werden on, dim*,
+        und *-for-timer ber&uuml;cksichtigt (zumindest beim FS20ST).  </li>
+
+      <li>Das <code>time</code> Argument geht von 0.25Sek bis 4Std und 16Min.
+        Da <code>time</code> nur mit einem Byte dargestellt wird ergeben sich
+        hieraus nur 112 eindeutige Zeit-Werte die mit ansteigender
+        gr&ouml;&szlig;e immer gr&ouml;ber aufgel&ouml;st werden. Das Programm
+        zeigt die exakte Restzeit an wenn die gew&auml;hlte Aufl&ouml;sung
+        nicht eindeutig war.  Die Aufl&ouml;sung ist is 0.25Sek von 0 bis 4
+        Sekunden, 0.5Sek von 4 bis 8Sek, 1Sek von 8 bis 16 Sek und so weiter.
+        Wenn eine h&ouml;here Genauigkeit bei gro&szlig;en Werten gebraucht
+        wird, dann hilft <a href="#at">at</a> mit einer Aufl&ouml;sung von
+        1Sek.</li>
+    </ul>
+  </ul>
+  <br>
+
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="FS20attr"></a>
+  <b>Attribute</b>
+  <ul>
+    <a name="IODev"></a>
+    <li>IODev<br>
+      Setzt das IO oder das physische Device welches zum Senden der Signale an
+      dieses logische Device verwendet werden soll (Beispielsweise FHZ oder
+      CUL).  Hinweis: Beim Start weist FHEM jedem logischen Device das letzte
+      physische Device zu, das Daten von diesem Typ empfangen kann.  Das
+      Attribut IODev muss nur gesetzt werden wenn mehr als ein physisches
+      Device f&auml;hig ist Signale von diesem logischen Device zu empfangen.
+      </li><br>
+
+    <a name="eventMap"></a>
+    <li>eventMap<br>
+      Ersetze Event Namen und setze Argumente. Der Wert dieses Attributes
+      besteht aus einer Liste von durch Leerzeichen getrennte Werten. Jeder
+      Wert ist ein durch Doppelpunkt getrenntes Paar. Der erste Teil stellt den
+      "alten" Wert, der zweite Teil den "neuen" Wert dar. Wenn der erste Wert
+      ein Slash (/) oder ein Komma (,) ist, dann wird nicht durch Leerzeichen
+      sondern durch das vorgestellte Zeichen getrennt.
+      Beispiele:
+      <ul><code>
+        attr store eventMap on:open off:closed<br>
+        attr store eventMap /on-for-timer 10:open/off:closed/<br>
+        set store open
+      </code></ul>
+      </li><br>
+
+    <a name="attrdummy"></a>
+    <li>dummy<br>
+      Setzt das Attribut dummy um Devices zu definieren, die keine Funksignale
+      absetzen.  Zugeh&ouml;rige notifys werden ausgef&uuml;hrt wenn das Signal
+      empfangen wird.  Wird beispielsweise genutzt um auf Code eines Sender zu
+      reagieren, dennoch wird es auch dann kein Signal senden wenn es im Web
+      Frontend getriggert wird.
+      </li><br>
+
+    <a name="follow-on-for-timer"></a>
+    <li>follow-on-for-timer<br>
+      Plant ein "setstate off;trigger off" f&uuml;r die angegebene Zeit als
+      Argument zum on-for-timer Command. Oder das gleiche mit "on" wenn der
+      Befehl "follow-off-for-timer" war.
+      </li><br>
+
+    <a name="follow-on-timer"></a>
+    <li>follow-on-timer<br>
+      Wie follow-on-for-timer plant es ein "setstate off;trigger off", aber
+      diesmal als Argument in Sekunden zum Attribut.  Wird verwendet um dem
+      vorprogrammierten Timer zu folgen welcher vorher durch den timer-Befehl,
+      oder manuell durch Dr&uuml;cken des Buttons gesetzt wurde. Im Handbuch
+      finden sich noch mehr Informationen. Beachtet bei on und dim Befehlen.
+      </li><br>
+
+
+    <a name="model"></a>
+    <li>model<br>
+      Das "model" Attribut bezeichnet den Modelltyp des Ger&auml;tes.  Dieses
+      Attribut wird (derzeit) nicht direkt durch fhem.pl genutzt.  Es kann
+      beispielsweise von externen Programmen oder Webinterfaces genutzt werden
+      um Ger&auml;teklassen zu unterscheiden und dazu passende Befehle zu senden
+      (z.B. "on" oder "off" an ein fs20st, "dim..%" an ein fs20du etc.).  Die
+      Schreibweise des Modellnamens ist wie die in Anf&uuml;hrungszeichen in
+      der Anleitung gedruckte Bezeichnung die jedem Ger&auml;t beiliegt.
+      Dieser Name wird ohne Leerzeichen ausschlie&szlig;lich in Kleinbuchstaben
+      verwendet.  G&uuml;ltige Zeichen sind <code>a-z 0-9</code> und
+      <code>-</code>, andere Zeichen sind zu vermeiden. Hier ist eine Liste der
+      "offiziellen" Devices:<br><br>
+
+      <b>Sender/Sensor</b>: fs20fms fs20hgs fs20irl fs20kse fs20ls
+      fs20pira fs20piri fs20piru fs20s16 fs20s20 fs20s4  fs20s4a fs20s4m
+      fs20s4u fs20s4ub fs20s8 fs20s8m fs20sd  fs20sn  fs20sr fs20ss
+      fs20str fs20tc1 fs20tc6 fs20tfk fs20tk  fs20uts fs20ze fs20bf fs20si3<br><br>
+
+      <b>Dimmer</b>: fs20di  fs20di10 fs20du<br><br>
+
+      <b>Empf&auml;nger/Aktor</b>: fs20as1 fs20as4 fs20ms2 fs20rgbsa fs20rst
+      fs20rsu fs20sa fs20sig fs20sm4 fs20sm8 fs20st fs20su fs20sv fs20ue1
+      fs20usr fs20ws1
+      </li><br>
+
+
+    <a name="ignore"></a>
+    <li>ignore<br>
+      Ignoriere dieses Ger&auml;t, beispielsweise wenn es dem Nachbar
+      geh&ouml;rt.  Das Ger&auml;t wird keine FileLogs/notifys triggern,
+      empfangene Befehle werden stillschweigend ignoriert (es wird kein
+      Funksignal gesendet, wie auch beim <a href="#attrdummy">dummy</a>
+      Attribut). Das Ger&auml;t wird weder in der Device-List angezeigt (es sei
+      denn, es wird explizit abgefragt), noch wird es in Befehlen mit
+      "Wildcard"-Namenspezifikation (siehe <a href="#devspec">devspec</a>)
+      erscheinen.  Es kann mit dem "ignored=1" devspec dennoch erreicht werden.
+      </li><br>
+
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+
+  </ul>
+  <br>
+
+  <a name="FS20events"></a>
+  <b>Erzeugte Events:</b>
+  <ul>
+     Von einem FS20 Ger&auml;t k&ouml;nnen folgende Events empfangen werden:
+     <li>on</li>
+     <li>off</li>
+     <li>toggle</li>
+     <li>dimdown</li>
+     <li>dimup</li>
+     <li>dimupdown</li>
+     <li>on-for-timer</li>
+     Welches Event gesendet wird ist Ger&auml;teabh&auml;ngig und kann manchmal
+     auf dem Device konfiguriert werden.
+  </ul>
+</ul>
+
+=end html_DE
+
+=cut

+ 306 - 0
fhem/core/FHEM/10_HXBDevice.pm

@@ -0,0 +1,306 @@
+# $Id: 10_HXBDevice.pm 7686 2015-01-24 11:54:59Z borisneubert $
+##############################################################################
+#
+#     10_HXBDevice.pm
+#     Copyright 2014 by Dr. Boris Neubert
+#     e-mail: omega at online dot de
+#
+#     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/>.
+#
+##############################################################################
+
+# Debian: libdigest-crc-perl
+
+package main;
+
+use strict;
+use warnings;
+
+use Digest::CRC;
+
+#############################
+
+
+my %HXB_PTYPES= (
+  	HXB_PTYPE_ERROR   => 0x00, # An error occured -- check the error code field for more information
+	HXB_PTYPE_INFO    => 0x01, # Endpoint provides information
+	HXB_PTYPE_QUERY   => 0x02, # Endpoint is requested to provide information
+	HXB_PTYPE_WRITE   => 0x04, # Endpoint is requested to set its value
+	HXB_PTYPE_EPINFO  => 0x09, # Endpoint metadata
+	HXB_PTYPE_EPQUERY => 0x0A, # Request endpoint metadata
+);
+
+#print Dumper \%HXB_PTYPES;
+my %HXB_PTYPES_r = reverse %HXB_PTYPES;
+
+my %HXB_DTYPES= (
+	HXB_DTYPE_UNDEFINED => 0x00, # Undefined: Nonexistent data type
+	HXB_DTYPE_BOOL      => 0x01, # Boolean. Value still represented by 8 bits, but may only be HXB_TRUE or HXB_FALSE
+	HXB_DTYPE_UINT8     => 0x02, # Unsigned 8 bit integer
+	HXB_DTYPE_UINT32    => 0x03, # Unsigned 32 bit integer
+	HXB_DTYPE_DATETIME  => 0x04, # Date and time
+	HXB_DTYPE_FLOAT     => 0x05, # 32bit floating point
+	HXB_DTYPE_128STRING => 0x06, # 128char fixed length string
+	HXB_DTYPE_TIMESTAMP => 0x07, # timestamp - used for measuring durations, time differences and so on - uint32; seconds
+	HXB_DTYPE_65BYTES   => 0x08, # raw 65 byte array, e.g. state machine data.
+	HXB_DTYPE_16BYTES   => 0x09, # raw 16 byte array, e.g. state machine ID.
+);
+my %HXB_DTYPES_r = reverse %HXB_DTYPES;
+
+my %HXB_FLAGS= (
+	HXB_FLAG_NONE => 0x00, # No flags set
+);
+my %HXB_FLAGS_r = reverse %HXB_FLAGS;
+
+my %EP= (
+	EP_DEVICE_DESCRIPTOR => 0,                                                   
+	EP_POWER_SWITCH => 1,                                                        
+	EP_POWER_METER => 2,                                                         
+	EP_TEMPERATURE => 3,                                                         
+	EP_BUTTON => 4,                                                              
+	EP_HUMIDITY => 5,                                                            
+	EP_PRESSURE => 6,                                                            
+	EP_ENERGY_METER_TOTAL => 7,                                                  
+	EP_ENERGY_METER => 8,                                                        
+	EP_SM_CONTROL => 9,                                                          
+	EP_SM_UP_RECEIVER => 10,                                                     
+	EP_SM_UP_ACKNAK => 11,                                                       
+	EP_SM_RESET_ID => 12,                                                        
+	EP_ANALOGREAD => 22,                                                         
+	EP_SHUTTER => 23,                                                            
+	EP_HEXAPUSH_PRESSED => 24,                                                   
+	EP_HEXAPUSH_CLICKED => 25,                                                   
+	EP_PRESENCE_DETECTOR => 26,                                                  
+	EP_HEXONOFF_SET => 27,                                                       
+	EP_HEXONOFF_TOGGLE => 28,                                                    
+	EP_LIGHTSENSOR => 29,                                                        
+	EP_IR_RECEIVER => 30,                                                        
+	EP_LIVENESS => 31,                                                           
+	EP_EXT_DEV_DESC_1 => 32,                                                     
+	EP_GENERIC_DIAL_0 => 33,                                                     
+	EP_GENERIC_DIAL_1 => 34,                                                     
+	EP_GENERIC_DIAL_2 => 35,
+	EP_GENERIC_DIAL_3 => 36,
+	EP_GENERIC_DIAL_4 => 37,
+	EP_GENERIC_DIAL_5 => 38,
+	EP_GENERIC_DIAL_6 => 39,
+	EP_GENERIC_DIAL_7 => 40,
+	EP_PV_PRODUCTION => 41,
+	EP_POWER_BALANCE => 42,
+	EP_BATTERY_BALANCE => 43,
+	EP_HEATER_HOT => 44,
+	EP_HEATER_COLD => 45,
+	EP_HEXASENSE_BUTTON_STATE => 46,
+	EP_FLUKSO_L1 => 47,
+	EP_FLUKSO_L2 => 48,
+	EP_FLUKSO_L3 => 49,
+	EP_FLUKSO_S01 => 50,
+	EP_FLUKSO_S02 => 51,
+	EP_GL_IMPORT_L1 => 52,
+	EP_GL_IMPORT_L2 => 53,
+	EP_GL_IMPORT_L3 => 54,
+	EP_GL_EXPORT_POWER => 55,
+	EP_GL_EXPORT_L1 => 56,
+	EP_GL_EXPORT_L2 => 57,
+	EP_GL_EXPORT_L3 => 58,
+	EP_GL_IMPORT_ENERGY => 59,
+	EP_GL_EXPORT_ENERGY => 60,
+	EP_GL_FIRMWARE => 61,
+	EP_GL_CURRENT_L1 => 62,
+	EP_GL_CURRENT_L2 => 63,
+	EP_GL_CURRENT_L3 => 65,
+	EP_GL_VOLTAGE_L1 => 66,
+	EP_GL_VOLTAGE_L2 => 67,
+	EP_GL_VOLTAGE_L3 => 68,
+	EP_GL_POWER_FACTOR_L1 => 69,
+	EP_GL_POWER_FACTOR_L2 => 70,
+	EP_GL_POWER_FACTOR_L3 => 71,
+	EP_METERING_RMS_CURRENT => 72,
+	EP_METERING_RMS_VOLTAGE => 73,
+	EP_METERING_FREQUENCY => 74,
+	EP_METERING_REACTIVE_POWER => 75,
+	EP_METERING_POWER_FACTOR => 76,
+	EP_METERING_APPARENT_POWER => 77,
+	EP_METERING_FUNDAMENTAL_ACTIVE_POWER => 78,
+	EP_METERING_FUNDAMENTAL_REACTIVE_POWER => 79,
+	EP_DIMMER_MODE => 80,
+	EP_DIMMER_BRIGHTNESS => 81,
+);
+my %EP_r= reverse %EP;
+
+
+#############################
+sub
+HXBDevice_Define($$)
+{
+        my ($hash, $def) = @_;
+        my @a = split("[ \t]+", $def);
+
+        return "Usage: define <name> HXBDevice <ipv6>"  if($#a != 2);
+
+        my $name= $a[0];
+        my $ipv6= $a[2];
+        
+        $hash->{fhem}{ipv6}= $ipv6;
+        AssignIoPort($hash);
+        
+        my @devarray= ();
+        my $devarrayref= $modules{$hash->{TYPE}}{defptr}{"$ipv6"};
+        if(defined($devarrayref)) {
+	  @devarray= @{$devarrayref};
+	}
+        push @devarray, $hash;
+        $modules{$hash->{TYPE}}{defptr}{"$ipv6"}= \@devarray;
+       
+        return undef;
+        
+        # Todo: HXBDevice_Undefine
+}
+
+###################################
+sub
+HXBDevice_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "HX0C.+"; 
+  
+  #$hash->{GetFn}     = "HXBDevice_Get";
+  #$hash->{SetFn}     = "HXBDevice_Set";
+  $hash->{DefFn}     = "HXBDevice_Define";
+  $hash->{ParseFn}   = "HXBDevice_Parse";
+
+  #$hash->{AttrFn}    = "HXBDevice_Attr";
+  $hash->{AttrList}  =  $readingFnAttributes;
+}
+
+#####################################
+
+sub
+crc16Kermit($) {
+  my ($raw)= @_;
+  my $ctx= Digest::CRC->new(width=>16, init=>0x0000, xorout=>0x0000, 
+                          refout=>1, poly=>0x1021, refin=>1, cont=>1);
+  $ctx->add($raw);
+  return $ctx->digest;
+}
+
+#############################
+sub
+HXBDevice_Parse($$)
+{
+
+  # we never come here if $msg does not match $IOhash->{MATCH} in the first place
+
+  my ($IOhash, $data) = @_;        # IOhash points to the HXB, not to the HXBDevice
+
+  my $socket= $IOhash->{TCPDev};
+  my $ipv6= $socket->peerhost;
+  
+  my $hash;
+  
+  # array of device hash with that IPv6 address
+  my @devices= ();
+
+  # matching devices
+  my @devarray= ();
+  my $devarrayref= $modules{"HXBDevice"}{defptr}{"$ipv6"};
+  if(defined($devarrayref)) {
+    @devarray= @{$devarrayref};
+  }
+  return "UNDEFINED HXB_$ipv6 HXBDevice $ipv6" if($#devarray< 0);
+  
+  foreach $hash (@devarray) {
+    
+    my $n= length($data);
+    return undef if($n< 8);
+    
+    my ($magic, $ptype, $flags, $payload, $crc)= unpack("A4CCa" . ($n-8) . "n", $data);
+    my $raw= unpack("a" . ($n-2), $data);
+    return undef unless($crc = crc16Kermit($raw));
+    my $hxb_ptype= $HXB_PTYPES_r{$ptype};
+    my $hxb_flag= $HXB_FLAGS_r{$flags};
+    if($hxb_ptype eq "HXB_PTYPE_INFO") {
+      my ($eid, $dtype, $value)= unpack("NCa*", $payload);
+      my $ep= $EP_r{$eid};
+      my $hxb_dtype= $HXB_DTYPES_r{$dtype};
+      my $v= "<unknown>";
+      if($hxb_dtype eq "HXB_DTYPE_BOOL") {
+	  $v= unpack("b", $value);
+      } elsif($hxb_dtype eq "HXB_DTYPE_UINT8") {
+	  $v= unpack("C", $value);
+      } elsif($hxb_dtype eq "HXB_DTYPE_UINT32") {
+	  $v= unpack("N", $value);
+      } elsif($hxb_dtype eq "HXB_DTYPE_DATETIME") {
+	  $v= "?";
+      } elsif($hxb_dtype eq "HXB_DTYPE_FLOAT") {
+	  #Debug unpack "V", $value;
+	  $v= unpack "f", pack "N", unpack "V", $value; #unpack("f", $value);
+      } elsif($hxb_dtype eq "HXB_DTYPE_128STRING") {
+	  $v= "?";
+      } elsif($hxb_dtype eq "HXB_DTYPE_TIMESTAMP") {
+	  $v= "?";
+      } elsif($hxb_dtype eq "HXB_DTYPE_65BYTES") {
+	  $v= "?";
+      } elsif($hxb_dtype eq "HXB_DTYPE_16BYTES") {
+	  $v= "?";
+      }
+      Log3 $hash,5, sprintf("%s: %s %s %s %s %s= %s", 
+	$hash->{NAME}, $hxb_ptype, $hxb_flag, 
+	$ep, $hxb_dtype, unpack("H*", $value), $v);
+  
+      my $fmtDateTime= readingsBeginUpdate($hash);
+      readingsBulkUpdate($hash, "state", $fmtDateTime, 1); # we do not want an extra event for state
+      readingsBulkUpdate($hash, $ep, $v, 1);
+      readingsEndUpdate($hash, 1);
+      
+      push @devices, $hash->{NAME};
+    }
+     
+  }
+  return @devices;
+  
+}
+
+#############################
+1;
+#############################
+
+=pod
+=begin html
+
+<a name="HXBDevice"></a>
+<h3>HXBDevice</h3>
+<ul>
+  <br>
+
+  <a name="HXB"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; HXB &lt;IPv6Address&gt;</code><br>
+    <br>
+    Defines a Hexabus device at the IPv6 address &lt;IPv6Address&gt;. You need one <a href="#HXB">Hexabus</a>
+    to receive multicast messages from Hexabus devices.
+    Have a look at the <a href="https://github.com/mysmartgrid/hexabus/wiki">Hexabus wiki</a> for more information on Hexabus.
+    <br><br>
+    Example:
+    <code>define myPlug fd01:1::50:c4ff:fe04:81ad</code>
+  </ul>  
+
+</ul>
+
+
+=end html

ファイルの差分が大きいため隠しています
+ 1735 - 0
fhem/core/FHEM/10_IT.pm


+ 162 - 0
fhem/core/FHEM/10_Itach_IR.pm

@@ -0,0 +1,162 @@
+################################################################################
+# $Id: 10_Itach_IR.pm 10723 2016-02-04 18:13:54Z ulimaass $
+# 10_Itach_IR
+#
+################################################################################
+#
+#  Copyright notice
+#
+#  (c) 2014 Copyright: Ulrich Maass
+#
+#  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/>.
+#
+#  Disclaimer: The Author takes no responsibility whatsoever 
+#  for damages potentially done by this program.
+#
+################################################################################
+#
+# This module serves as communication layer for 88_Itach_IRDevice
+#
+################################################################################
+
+package main;
+use strict;
+use warnings;
+use IO::Socket::INET;
+
+#########################
+# Forward declaration
+sub IIR_Define();
+sub IIR_Send($$);
+sub IIR_Write($@);
+
+
+#####################################
+# Initialize module
+sub
+Itach_IR_Initialize($)
+{
+  my ($hash) = @_;
+  # provider
+#  $hash->{ReadFn}   = "IIR_Write";
+  $hash->{WriteFn}  = "IIR_Write";
+  $hash->{Clients}  = ":Itach_IRDevice:";
+  #consumer
+  $hash->{DefFn}    = "IIR_Define";
+  $hash->{AttrList} = "verbose:0,1,2,3,4,5,6 timeout";
+}
+
+
+#####################################
+# Initialize every new instance
+sub
+IIR_Define() 
+{
+  my ($hash, $def) = @_;
+  my @args = split("[ \t]+", $def);
+  return "Usage: define <name> Itach_IR <host>"  if($#args != 2);
+  
+  my ($name, $type, $host) = @args;
+  $hash->{STATE}       = "Initialized";
+  $hash->{IPADR}       = $host if ($host);  #format-check required
+  my $cmdret= CommandAttr(undef,"$name room ItachIR") if (!AttrVal($name,'room',undef));
+  return undef;
+}
+
+
+#####################################
+# Execute IOWrite-calls from clients
+sub IIR_Write($@) {
+  my ($hash,$name,$cmd,$IRcode)= @_;
+  Log3 $name, 5, "IIR_Write called with $name,$cmd";
+  my $newstate = $name.':'.$cmd;
+  readingsSingleUpdate($hash,"lastcommand",$newstate,0);
+  my $ret=IIR_Send($hash,$IRcode);
+  return undef;
+}
+
+#####################################
+# Send IR-code
+sub
+IIR_Send($$){
+  my ($hash,$IR)=@_;
+  if (!$IR) {
+	Log3 $hash->{NAME}, 2, "Called without IR-code to send.";
+	return;
+  }
+  my $socket = new IO::Socket::INET (
+     PeerHost => $hash->{IPADR},
+     PeerPort => '4998',
+     Proto    => 'tcp',
+     Timeout  => AttrVal($hash->{NAME},'timeout','2.0'),
+  );
+  # send Itach command
+  if ($socket) {
+    my @codes = split(';',$IR);
+	foreach my $IRcode (@codes) {
+      my $data = $IRcode."\r\n";
+      $socket->send($data);
+      select(undef, undef, undef, 0.3); #pause 0.3 seconds
+	  Log3 $hash->{NAME}, 5, 'Sent to '.$hash->{IPADR}.' : '.$IRcode;
+	}
+	$socket->close();
+	readingsSingleUpdate($hash,"state",'Initialized',0) if ($hash->{STATE} ne 'Initialized');
+  } else {
+	Log3 $hash->{NAME}, 1, 'Could not open socket with '.$hash->{IPADR}.' : '.$@;
+	readingsSingleUpdate($hash,"state",$@,0);
+  }
+  return undef;
+}
+
+1;
+
+
+=pod
+=begin html
+
+<a name="Itach_IR"></a>
+<h3>Itach_IR</h3>
+<ul>
+  Defines a device representing a physical Itach IR. Serves as communication layer for <a href="#Itach_IRDevice">Itach_IRDevice</a>.<br>
+  For more information, check the <a href="http://www.fhemwiki.de/wiki/ITach">Wiki page</a>.<br>
+  
+  <a name="Itach_IRdefine"></a><br>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; Itach_IR &lt;IP-address&gt;</code><br>
+	Example:<br>
+    <code>define Itach Itach_IR 192.168.1.2</code>
+  </ul>
+
+  <a name="Itach_IRset"></a><br>
+  <b>Set</b><br><ul>N/A</ul><br>
+
+  <a name="Itach_IRDeviceget"></a><br>
+  <b>Get</b><br><ul>N/A</ul><br>
+
+  <a name="Itach_IRDeviceattr"></a><br>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#verbose">verbose</a></li>
+	<li>timeout<br>
+	Can be used to change the timeout-value for tcp-communication. Default is 2.0.</li>
+  </ul>
+</ul>
+
+=end html
+=cut
+
+

ファイルの差分が大きいため隠しています
+ 2095 - 0
fhem/core/FHEM/10_KNX.pm


+ 930 - 0
fhem/core/FHEM/10_KOPP_FC.pm

@@ -0,0 +1,930 @@
+# $Id: 10_KOPP_FC.pm 12860 2016-12-21 21:09:41Z RaspII $
+##########################################################################################################################################################################################
+#
+# Kopp Free Control protocol module for FHEM
+# (c) Claus M.
+#
+# This modul is currenly under construction and will only work if you flashed your CCD device with hexfile from: 
+# svn+ssh://raspii@svn.code.sf.net/p/culfw/code/branches/raspii/culfw/Devices/CCD/CCD.hex
+# which includes support for "K" command
+#
+# Published under GNU GPL License, v2
+#
+# Date		   Who				Comment																												   
+# ----------  -------------   	-------------------------------------------------------------------------------------------------------------------------------
+# 2016-12-19  RaspII			Test if new repository is working
+# 2016-03-20  RaspII			Added some information to Commandref, now attrib contains correct information
+# 2016-01-30  RaspII			Now also Blinds and Switches are implemented (all actuators I have). 
+# 2016-01-12  RaspII			Implemented Dimmer Commands for 1&3 key remote, removed toggle	
+# 2015-06-02  RaspII			Now can also Handle multiple devices with same code, next step: implement all commands (on, off, toggle... for KOPP_FC_Parse)
+#								Missing is also 2 key commands (e.g. key1=on Key2=off for Dimmmers, key1=up key2=down for blinds)
+# 2015-05-21  RaspII			Beim FS20 Modul sind die möglichen Set Commands abhängig vom "model" Attribute !! hier weitersuchen
+#								Seit die Return SerExtensions eingebaut ist, lässt sich bei Taste2Rad4 dass Commando Off nicht mehr absetzen (hat was mit dem Modul SerExtensions zu tun)
+# 2015-05-02  RaspII			Try now to receive Kopp Messages, also
+# 2015-04-13  RaspII 			Modified some typos (help section)
+# 2015-02-01  RaspII			use small "k" to start Kopp FW, "K" was already used for raw data
+# 2014-12-21  RaspII			V6 (fits to my FHEM.cfg V6) Removed timeout from define command, will add later to set command (best guess yet). 
+# 2014-12-13  RaspII			first version with command set: "on, off, toggle, dim, stop". Added new Parameter ("N" for do not print) 
+# 2014-12-08  RaspII			direct usage of set command @ FHEM.cfg works fine, but buttoms on/off do not appear, seems to be a setup/initialize issue in this routine 
+# 2014-09-01  RaspII			first Version
+#
+##########################################################################################################################################################################################
+
+package main;
+
+use strict;
+use warnings;
+use SetExtensions;				# 2015-05-21  Wird vermutlich benötig um später die möglichen "Set" Commandos zu definieren
+
+sub KOPP_FC_Initialize($);		##### Claus evt. nicht nötig
+sub KOPP_FC_Parse($$);			##### Claus evt. nicht nötig
+
+my %codes = (					# This Sheet contains all allowed codes, indevpendtly from Model type
+    "01" => "on",
+ 	"02" => "off",
+    "03" => "toggle",
+	"04" => "dimm",
+	"05" => "stop",
+	"06" => "up",
+	"07" => "down",
+	"08" => "top",
+	"09" => "bottom",
+);
+
+my %sets = (					# Do not know whether this list is needed (guess: no)
+	"on" => "",
+	"off" => "",
+	"stop" => "",
+	"toggle" => "",
+	"dimm" => "",
+	"up" => "",
+	"down" => "",
+	"top" => "",
+	"bottom" => ""
+);
+my %models = (
+    Switch_8080_01  	=> 'Switch',
+    Switch_8080_01_2Key => 'Switch_2KeyMode',
+    Blind_8080_02  		=> 'Blind',
+    Timer_8080_04  		=> 'TimerSwitch',
+    Dimm_8011_00  		=> 'Dimmer',
+	Dimm_8011_00_3Key  	=> 'Dimmer_3KeyMode',
+);
+
+my %kopp_fc_c2b;        # DEVICE_TYPE->hash (reverse of device_codes), ##Claus what does that mean?
+
+
+#############################
+
+sub KOPP_FC_Initialize($) 
+{
+	my ($hash) = @_;
+	
+	foreach my $k (keys %codes) {				## Claus needed if we wanna have the codes via commands
+    $kopp_fc_c2b{$codes{$k}} = $k;				# both lines not needed yet
+  }
+
+#	$hash->{Match}   = "^kr..................";  # evt. später nehmen, damit nur genau 19 Zeichen akzeptiert werden
+	$hash->{Match}   = "^kr.*";  
+	$hash->{SetFn}   = "KOPP_FC_Set";
+	$hash->{DefFn}   = "KOPP_FC_Define";
+	$hash->{UndefFn}   = "KOPP_FC_Undef";
+    $hash->{ParseFn}   = "KOPP_FC_Parse";
+	$hash->{AttrFn}  	= "KOPP_FC_Attr";		  # aus SOMFY Beispiel abgeleitet			
+
+    $hash->{AttrList} = "IODev ".
+	"model:".join(",", sort keys %models);
+
+#	  . " symbol-length"
+#	  . " enc-key"
+#	  . " rolling-code"
+#	  . " repetition"
+#	  . " switch_rfmode:1,0"
+#	  . " do_not_notify:1,0"
+#	  . " ignore:0,1"
+#	  . " dummy:1,0"
+#	  . " model:somfyblinds"
+#	  . " loglevel:0,1,2,3,4,5,6";
+
+}
+
+
+#############################
+sub KOPP_FC_Define($$) 
+{
+	my ( $hash, $def ) = @_;
+	my @a = split( "[ \t][ \t]*", $def );
+	
+    my $name = $hash->{NAME};## neu 14.5.
+	
+	my $u = "wrong syntax: define <name> KOPP_FC keycode(Byte) transmittercode1(2Byte) transmittercode2(Byte)";
+	my $keycode2 = "";
+	my $keycode3 = "";
+	
+	# fail early and display syntax help
+	if ( int(@a) < 4 ) {
+		return $u;
+	}
+
+	# check keycode format (2 hex digits)
+	if ( ( $a[2] !~ m/^[a-fA-F0-9]{2}$/i ) ) {
+		return "Define $a[0]: wrong keycode format: specify a 2 digit hex value "
+	}
+
+	my $keycode = $a[2];
+	$hash->{KEYCODE} = uc($keycode);
+
+	# check transmittercode1 format (4 hex digits)
+	if ( ( $a[3] !~ m/^[a-fA-F0-9]{4}$/i ) ) {
+		return "Define $a[0]: wrong transmittercode1 format: specify a 4 digit hex value "
+	}
+
+	my $transmittercode1 = $a[3];
+	$hash->{TRANSMITTERCODE1} = uc($transmittercode1);
+
+	# check transmittercode2 format (2 hex digits)
+	if ( ( $a[4] !~ m/^[a-fA-F0-9]{2}$/i ) ) {
+		return "Define $a[0]: wrong transmittercode2 format: specify a 2 digit hex value "
+	}
+	my $transmittercode2 = $a[4];
+	$hash->{TRANSMITTERCODE2} = uc($transmittercode2);
+
+	# check keycode2 (optional) format (2 hex digits)
+	if (defined $a[5]){ 
+	 if ( ( $a[5] !~ m/^[a-fA-F0-9]{2}$/i ) ) {			#Default: Keycode2 is empty
+	 }
+	 else {
+	 $keycode2 = $a[5];
+	 $hash->{KEYCODE2} = uc($keycode2);	
+	 }
+	}
+	
+	# check keycode3 (optional) format (2 hex digits)
+	if (defined $a[6]){ 
+	 if ( ( $a[5] !~ m/^[a-fA-F0-9]{2}$/i ) ) {			#Default: Keycode3 is empty
+	 }
+	 else {
+	 $keycode3 = $a[6];
+	 $hash->{KEYCODE3} = uc($keycode3);	
+	 }
+	}
+#Remove check for timeout
+	# check timeout (5 dec digits)
+#	if ( ( $a[5] !~ m/^[0-9]{5}$/i ) ) {
+#		return "Define $a[0]: wrong timeout format: specify a 5 digits decimal value"
+#	}
+	
+#   removed next lines, may be will move timeout to set command (on-for-timer) or something like that
+#	my $timeout = $a[5];
+#	$hash->{TIMEOUT} = uc($timeout);
+	$hash->{TIMEOUT} = "00000";												#Default timeout = 0
+	
+
+# group devices by their address
+	my $code  = uc("$transmittercode1 $keycode");
+	my $ncode = 1;
+#	my $name  = $a[0];			# see above, already defined
+
+	$hash->{CODE}{ $ncode++ } = $code;										## 6.1.2016: Code jetzt mit Referenz verlinken
+#	$hash->{CODE} = $code;													## dafür diese Zeile raus
+	$modules{KOPP_FC}{defptr}{$code}{$name} = $hash;						## neu 30.5. mit name vermutlich wird hierüber das Device eindeutig identifiziert 
+
+
+# Noch die 2te Taste definieren, falls vorhanden
+	if ( $keycode2 ne "") {
+	my $code  = uc("$transmittercode1 $keycode2");
+	$hash->{CODE}{ $ncode++ } = $code;
+	$modules{KOPP_FC}{defptr}{$code}{$name} = $hash;						## neu 30.5. mit name vermutlich wird hierüber das Device eindeutig identifiziert 
+		
+	}
+
+# Noch die 3te Taste definieren, falls vorhanden
+	if ( $keycode3 ne "") {
+	my $code  = uc("$transmittercode1 $keycode3");
+	$hash->{CODE}{ $ncode++ } = $code;
+	$modules{KOPP_FC}{defptr}{$code}{$name} = $hash;						## neu 30.5. mit name vermutlich wird hierüber das Device eindeutig identifiziert 
+		
+	}
+
+# Noch so, der "Stop Code" = "F7" nach langem Tastendruck bekommt auch noch einen Eintrag
+	$code  = uc("$transmittercode1 F7");
+	$hash->{CODE}{"stop"} = $code;
+	$modules{KOPP_FC}{defptr}{$code}{$name} = $hash;						## neu 30.5. mit name vermutlich wird hierüber das Device eindeutig identifiziert 
+		
+
+	Log3 $name, 2, "KOPP_FC_Define: Modules: $modules{KOPP_FC}{defptr}{$code}{$name} Name: $name a[0]: $a[0] Transmittercode1: $transmittercode1 Keycode: $keycode Keycode2: $keycode2  $keycode Keycode3: $keycode3 Hash: $hash";  # kann wieder Raus !!!! ### Claus
+
+
+
+#	$hash->{move} = 'on';
+	
+# ohne die folgende Zeile gibts beim Speichern von FHEM.cfg die Fehlermeldung Dimmer2 (wenn Dimmer2 als Name festgelegt werden soll)	
+	AssignIoPort($hash);
+}
+
+
+#############################
+sub KOPP_FC_Undef($$)
+{
+  my ($hash, $name) = @_;
+
+  foreach my $c (keys %{ $hash->{CODE} } ) 
+  {
+    $c = $hash->{CODE}{$c};
+
+    # As after a rename the $name my be different from the $defptr{$c}{$n}
+    # we look for the hash.
+    foreach my $dname (keys %{ $modules{KOPP_FC}{defptr}{$c} }) 
+	{
+      delete($modules{KOPP_FC}{defptr}{$c}{$dname}) if($modules{KOPP_FC}{defptr}{$c}{$dname} == $hash);
+	
+# No entry to log file (only for test)
+#	 if($modules{KOPP_FC}{defptr}{$c}{$dname} == $hash)
+#     {
+#	  my $m=$modules{KOPP_FC}{defptr}{$c}{$dname}; 
+#      Log3 $name, 3, "KOPP_FC_Undef: Name: $name, Code: $c, $m deleted"; 
+#     } 
+
+    }
+  }
+  return undef;
+}
+
+##############################
+sub KOPP_FC_Attr(@) {
+# write new Attributes to global $attr variable if attribute name is model
+
+    my ($cmd,$name,$aName,$aVal) = @_;
+	my $hash = $defs{$name};
+
+	return "\"KOPP_FC Attr: \" $name does not exist" if (!defined($hash));
+
+	# $cmd can be "del" or "set"
+	# $name is device name
+	# aName and aVal are Attribute name and value
+
+	if ($cmd eq "set") {
+		if ($aName eq 'model') {
+  	        $attr{$name}{$aName} = $aVal;
+		}
+    }
+return undef;	
+}
+
+
+#####################################
+sub KOPP_FC_SendCommand($@)
+{
+	my ($hash, @args) = @_;
+	my $ret = undef;
+	my $keycodehex = $args[0];
+	my $cmd = $args[1];
+	my $message;
+	my $name = $hash->{NAME};
+	my $numberOfArgs  = int(@args);
+
+	my $io = $hash->{IODev};
+
+#	return $keycodehex
+	
+	$message = "s"
+	  . $keycodehex				
+	  . $hash->{TRANSMITTERCODE1}
+	  . $hash->{TRANSMITTERCODE2}
+	  . $hash->{TIMEOUT}
+	  . "N";								# N for do not print messages (FHEM will write error messages to log files if CCD/CUL sends status info
+
+    ## Send Message to IODev using IOWrite
+	IOWrite( $hash, "k", $message );
+
+	return $ret;
+} 
+# end sub KOPP_FC_SendCommand
+#############################
+
+
+#############################
+sub KOPP_FC_Set($@)
+{
+	my ( $hash, $name, @args ) = @_;							# Aufbau hash: Name, Command
+	my $numberOfArgs  = int(@args);
+	my $keycodedez;
+	my $keycodehex;
+	my $lh;
+	my $modl;
+	
+#	my $message;
+
+	if ( $numberOfArgs < 1 ) 
+	{
+	 return "no set value specified" ;
+	}
+	my $cmd = lc($args[0]);
+	
+
+
+
+	my $c = $kopp_fc_c2b{$args[0]};
+    if(!defined($c)) 																		# if set command was not yet defined in %codes provide command list
+																							# $c contains the first argument of %codes, "01" for "on", "02" for "off" ..
+     {														
+     my $list;
+     if(defined($attr{$name}) && defined($attr{$name}{"model"})) 
+	  {
+       my $mt = $models{$attr{$name}{"model"}};												# Model specific set arguments will be defined here (maybe move later to variable above)
+       $list = "dimm stop on off" if($mt && $mt eq "Dimmer"); 								# --------------------------------------------------------------------------------------
+	   $list = "dimm stop on off" if($mt && $mt eq "Dimmer_3KeyMode");						# "$mt &&...", damit wird Inhalt von $mt nur geprüft wenn $mt initialisiert ist
+       $list = "on off short" if($mt && $mt eq "TimerSwitch"); 								# on means long key presure
+       $list = "top bottom up down stop" if($mt && $mt eq "Blind"); 						# up/down means long key presure
+       $list = "on off" if($mt && $mt eq "Switch"); 										# (no difference between long/short preasure)
+       $list = "on off" if($mt && $mt eq "Switch_2KeyMode");								# (no difference between long/short preasure)
+      }
+	   
+	 $list = (join(" ", sort keys %kopp_fc_c2b) . " Claus") if(!defined($list));			# if list not defined model specific, allow whole default list
+
+	 return "[Kopp_FC_Set] unknown command <$cmd>, choose one of " . join(" ", $list);		# no more text after "choose one of  " allowed
+
+
+    }
+																	
+	if(defined($attr{$name}) && defined($attr{$name}{"model"})) 							# Falls Model spezifiziert ist, Model ermitteln -> $mt
+	  {																						# ----------------------------------------------------
+       $modl = $models{$attr{$name}{"model"}};								 
+#	   Log3 $name, 2, "KOPP_FC_Set: Device Name: $name Index auf codes: $c Model: $modl"; 						# kann wieder Raus !!!! ### Claus  	
+      }
+
+
+
+#	readingsSingleUpdate($hash, "state","$cmd", 1);		# update also Readings
+#	$hash->{STATE} = $cmd;								# update device state
+
+
+# Look for all devices with the same code, and update readings (state), (timestamp, not yet)
+# ------------------------------------------------------------------------------------------------------
+# some hints: if same code is used within config file for differen models update of state makes no sense
+# and may fail because command is not available for a different model. So we only update devices which are 
+# of the same model as the original one.
+#
+#	my $tn = TimeNow();
+#	my $defptr = $modules{KOPP_FC}{defptr}{transmittercode1}{keycode};
+#	foreach my $n (keys %{ $defptr }) 
+#	{
+#    readingsSingleUpdate($defptr->{$n}, "state","$cmd", 1);
+#	}
+
+
+	my $code  = $hash->{CODE}{1};																# Load Devices code1 (typically key code short preasure)
+	my $rhash = $modules{KOPP_FC}{defptr}{$code};												# Load Hash of Devices with same code 
+
+#	my @list;																					# Do (Why) I need this @lists (incl. return @list)? 
+	foreach my $n (keys %{ $rhash }) 
+    {
+     $lh = $rhash->{$n};
+     $n = $lh->{NAME};        																	# It may be renamed, n now contains name of defined device, e.g. Dimmer....  
+#    return "" if(IsIgnored($n));   															# Little strange.
+
+
+	if(defined ($modl) && defined($attr{$n}) && defined($attr{$n}{"model"})) 					 
+	{
+	 my $m=$models{$attr{$n}{"model"}};															
+     Log3 $name, 3, "KOPP_FC_Set: Device Name: $n, command: $cmd, Model: $m, Transm.-/KeyCode: $code"; 
+	 
+																								# Falls auch dieses Model spezifiziert ist und Modell identisch dem Model aus Originalaufruf,
+	 if($m eq $modl) 																			# ------------------------------------------------------------------------------------------
+     {																							# dann den Status dieses Devices ebenfalls updaten	
+#	 Log3 $name, 2, "KOPP_FC_Set: Orig Model: $modl, Status Update also for Model: $m"; 		# kann wieder Raus !!!! ### Claus  	
+	 $lh->{STATE} = $cmd;																		# update device state
+     readingsSingleUpdate($lh, "state", $cmd, 1);												# update also Readings
+     }
+	 else
+	 {
+#	  Log3 $name, 2, "KOPP_FC_Set: Orig Model: $modl, wrong model: $m  no Status Update done"; 	# kann wieder Raus !!!! ### Claus  	
+#	  Log3 $name, 2, "             dont define same remote key for different actuators models"; # kann wieder Raus !!!! ### Claus  	
+	 }
+    }
+
+#	 push(@list, $n);
+	 
+	}
+ #	return @list;
+#	return"";		
+    		
+
+ 
+	if($cmd eq 'stop')  																		# command = stop
+	{																							# --------------
+	  $keycodehex = "F7";																		# Stop means F7 will be sent several times										
+	}																							# (e.g. to end  "dimm" end or "up" / "down" )
+
+
+ 	elsif($cmd eq 'dimm')																		# independent of Dimmer Type  									
+	{																							# ---------------------------
+	  $keycodehex = $hash->{KEYCODE};															# use Keycode+0x80 for long key pressure = dimmer up/down
+	  $keycodedez = hex $keycodehex ;															# without moving to $keycodehex and addition in second line it does not work !?	
+	  $keycodedez = $keycodedez + 128;															#
+	  $keycodehex = uc sprintf "%x", $keycodedez;												# 
+	}
+
+    elsif($modl && $modl eq "Dimmer")															# if model defined and equal Dimmer
+	{																							# ---------------------------------
+	  if($cmd eq 'on'||$cmd eq 'off')															# 														
+   	  {																							#
+	    $keycodehex = $hash->{KEYCODE};				    										# -> use Keycode to send "on" or "off" command (=toggle)
+	  }
+ 	}
+
+															
+															
+    elsif($modl && $modl eq "Dimmer_3KeyMode")													# if model defined and equal 3-key Dimmer
+	{																							# ---------------------------------------
+	  if($cmd eq 'on' && $hash->{KEYCODE2} ne "")												# Command = on
+	  {																							#
+   	    $keycodehex = $hash->{KEYCODE2};				    									# -> use Keycode 2 to send "on" command
+	  }																							#
+	  if($cmd eq 'off' && $hash->{KEYCODE3} ne "") 												# Command = off
+	  {																							#														
+   	    $keycodehex = $hash->{KEYCODE3};				    									# -> use Keycode 3 to send "on" command
+	  }
+									
+ 	}
+
+    elsif($modl && $modl eq "Switch")															# if model defined and equal Switch
+	{																							# ---------------------------------
+	  if($cmd eq 'on' || $cmd eq 'off')															# 														
+   	  {																							#
+	    $keycodehex = $hash->{KEYCODE};				    										# -> use Keycode to send "on" or "off" command (=toggle)
+	  }																							#
+ 	}
+
+    elsif($modl && $modl eq "Switch_2KeyMode") 													# if model defined and equal Switch controlled by 2 Keys
+	{																							# ------------------------------------------------------
+	  if($cmd eq 'on')																			# 														
+   	  {																							#
+	    $keycodehex = $hash->{KEYCODE};				    										# -> use Keycode to send "on" or "off" command (=toggle)
+	  }																							#
+	  elsif($cmd eq 'off' && $hash->{KEYCODE2} ne "")											# 														
+   	  {																							#
+	    $keycodehex = $hash->{KEYCODE2};				   										# -> use Keycode to send "on" or "off" command (=toggle)
+	  }			
+ 	}
+
+	elsif($modl && $modl eq "Blind")															# if model defined and equal  Blind:  									# 
+	{																							# ----------------------------------------	
+	  if($cmd eq 'top')																			#
+	  {																							# -> use Keycode to send "top" command
+   	    $keycodehex = $hash->{KEYCODE};					    									#
+	  }																							#
+	  elsif($cmd eq 'bottom' && $hash->{KEYCODE2} ne "")										# 
+	  {																							# -> use Keycode2 to send "bottom" command
+   	    $keycodehex = $hash->{KEYCODE2};				    									# 
+	  }																							#
+	  elsif($cmd eq 'up')																		# 
+	  {																							#
+   	    $keycodehex = $hash->{KEYCODE};					    									#
+  	    $keycodedez = hex $keycodehex ;															# -> use Keycode+0x80 to send "up" command	
+	    $keycodedez = $keycodedez + 128;														#	
+	    $keycodehex = uc sprintf "%x", $keycodedez;												# 
+																								# 
+	  }																							#
+	  elsif($cmd eq 'down' && $hash->{KEYCODE2} ne "")											# 
+	  {																							# -> use Keycode2+0x80 to send "dowm" command	
+	    $keycodehex = $hash->{KEYCODE2};														#
+        $keycodedez = hex $keycodehex ;															#	
+        $keycodedez = $keycodedez + 128;														#	
+	    $keycodehex = uc sprintf "%x", $keycodedez;												# 
+	  }
+	}      
+
+	elsif($cmd eq 'on'|| 'off' || 'toggle')														# If model not known just allow on, off, toggle
+	{																							# ---------------------------------------------
+	  $keycodehex = $hash->{KEYCODE};															# -> use Keycode
+	}
+
+
+
+	
+	else																						# should never happen :-) 
+	{																							# -----------------------
+	return "unknown command" ;
+	}  
+
+
+	
+	KOPP_FC_SendCommand($hash, $keycodehex, @args);		
+#	KOPP_FC_SendCommand($hash, @args);		
+
+
+
+
+	
+
+
+
+
+#	$hash->{STATE} = 'off';	
+
+#return SetExtensions($hash,'toggle', @a);
+return undef;
+
+} 
+# end sub Kopp_FC_setFN
+###############################
+
+
+#############################
+# 
+sub KOPP_FC_Parse($$) {																			# wird von fhem.pl dispatch getriggert
+																								# Example receive Message: kr07FA5E7114CC0F02AD
+																								# 07: block length; FA5E: Transmitter Code 1; 71: Key counter(next key pressed); 14: Key Code;
+																								# CC0F: unknown, but always the same; 
+																								# 02: Transmiter Code 2; (content depends on transmitter, changed value seems not to change anything; AD: Checksum)
+	my ($hash, $msg) = @_;
+	my $name = $hash->{NAME};																	# Here: Device Hash (e.g. to CUL), e.g. $name = "CUL_0" 
+	my $state;																					# means receive command = new state
+	my $keycodedez;
+	my $specialkey	= "short";																	# Default: short key
+	my $code;
+	my $devicefound;
+
+
+if( $msg =~ m/^kr/ ) {																			# if first two char's are "kr" then we are right here (KOPP Message received)
+
+	# Msg format:
+	# kr.. rest to be defined later
+#	if (substr($msg, 0, 16) eq "krS-ReceiveStart" || substr($msg, 0, 14) eq "krE-ReceiveEnd") 
+	if (substr($msg, 0, 2) eq "kr") 
+	  { 
+
+        # get Transtmitter Code 1
+	    my $transmittercode1 = uc(substr($msg, 4, 4));											# Example above: FA5E
+
+        # get Transtmitter Code 2
+	    my $transmittercode2 = uc(substr($msg, 16, 2));											# Example above: 02
+
+        # get Key Code
+	    my $keycode = uc(substr($msg, 10, 2));													# Example above: 14
+		$keycodedez = hex $keycode ;															# If Keycode > 128 and not equal "long key end" (F7) then Long Key pressure
+
+
+
+		if ($keycode eq "F7")																	# If end of long keypressure (stop) we need special handling 
+		{																						# ----------------------------------------------------------
+	    $code  = uc("$transmittercode1 F7");
+		$specialkey = "stop";
+
+		}
+		
+		else
+		{										
+		  if ($keycodedez >= 128 && $keycode ne "F7")									
+		  {																						# If long key pressure:
+		    $keycodedez = ($keycodedez - 128);													# ---------------------
+		    $specialkey = "long";
+		    $keycode = uc sprintf "%02x", $keycodedez;											# %02 damit auch eine "0"... zweistellig wird
+		  }
+
+	    $code  = uc("$transmittercode1 $keycode");
+		}
+
+		my $rhash = $modules{KOPP_FC}{defptr}{$code};											# neu 30.5. rhash war nicht eindeutig 
+
+#		my $rname = $rhash->{NAME};																# $rhash is hash to corresponding device as calculated from receive data
+
+		Log3 $name, 2, "KOPP_FC_Parse: name: $name code: $code Specialkey:$specialkey"; 		# kann wieder Raus !!!! ### Claus rname wird müll, da Hash mehrere Namen verlinkt?
+																								# rname funktioniert nur wenn $name in Zeile 149/150 nicht angehängt ist
+
+
+
+#		my $tn = TimeNow();
+# Look for all devices with the same code, and set state, (timestamp, not yet) 
+# ----------------------------------------------------------------------------
+      if($rhash) 
+      {
+ 
+		my @list;
+		foreach my $n (keys %{ $rhash }) 
+	    {
+        my $lh = $rhash->{$n};
+        $n = $lh->{NAME};        																# It may be renamed, n now contains name of defined device, e.g. Dimmer....  
+        return "" if(IsIgnored($n));   															# Little strange.
+
+
+		my $oldstate = ReadingsVal($n, "state","");											    #
+
+# Je nach Model die Aktion triggern.
+
+# 3 Key Dimmer:
+#==============
+		
+		$devicefound=1;																			# Default: wir kennen das Device
+		
+		if ($attr{$n}{model} && ($attr{$n}{model} eq 'Dimm_8011_00_3Key')) 						# Wenn Device = 3 Key Dimmer
+		{																						# ==========================
+			
+			
+			 if ($specialkey eq 'short' && $keycode eq $lh->{KEYCODE}) 							# Taste 1 kurz gedrückt: dann toggeln
+			 {																					# -----------------------------------
+			   if($oldstate eq 'off') {$state = "on";}			 							 	 # off -> on	
+			   elsif($oldstate eq 'on') {$state = "off";}		 							 	 # on -> off
+			   elsif($oldstate eq 'stop') {$state = "off";}						 				 # stop -> off	
+			   else {$state = "on";}							 							 	 # Weder noch? dann Neuer Zustand = on (wird dann wohl aus gewesen sein)	
+			 }
+			 elsif ($specialkey eq "long" && $keycode eq $lh->{KEYCODE}) 						# Taste 1 lang gedrückt: dann dimmen
+			 {																					# ----------------------------------
+			   $state = "dimm";	
+			 }
+			 elsif ($specialkey eq "stop") 														# Ende der lang gedrückten Taste dann dimmen stoppen
+			 {																					# --------------------------------------------------
+			   $state = "stop" if($oldstate eq 'dimm' || $oldstate eq 'stop');					# falls dimmen aktiv war oder bereits gestoppt wurde  
+			 }
+			 
+			 elsif ($keycode eq $lh->{KEYCODE2}) {$state = "on";}								# Taste 2: (kurz oder lang) -> On  
+			 elsif ($keycode eq $lh->{KEYCODE3}) {$state = "off";}								# Taste 3: (kurz oder lang) -> Off 
+			 else {}
+			  																	
+			
+		}
+# 1 Key Dimmer:
+#==============
+		elsif ($attr{$n}{model} && ($attr{$n}{model} eq 'Dimm_8011_00')) 						# Wenn Device = 1 Key Dimmer
+		{																						# ==========================
+			
+			
+			 if ($specialkey eq 'short' && $keycode eq $lh->{KEYCODE}) 							# Taste 1 kurz gedrückt: dann toggeln
+			 {																					# -----------------------------------
+			   if($oldstate eq 'off') {$state = "on";}			 							 	 # off -> on	
+			   elsif($oldstate eq 'on') {$state = "off";}		 							 	 # on -> off
+			   elsif($oldstate eq 'stop') {$state = "off";}						 				 # stop -> off	
+			   else {$state = "on";}							 							 	 # Weder noch? dann Neuer Zustand = on (wird dann wohl aus gewesen sein)	
+			 }
+			 elsif ($specialkey eq "long" && $keycode eq $lh->{KEYCODE}) 						# Taste 1 lang gedrückt: dann dimmen
+			 {																					# ----------------------------------
+			   $state = "dimm";	
+			 }
+			 elsif ($specialkey eq "stop") 														# Ende der lang gedrückten Taste dann dimmen stoppen
+			 {																					# --------------------------------------------------
+			   $state = "stop" if($oldstate eq 'dimm' || $oldstate eq 'stop');					# falls dimmen aktiv war oder bereits gestoppt wurde  
+			 }
+			 
+			 
+		}
+# Blind:
+#=======
+		elsif ($attr{$n}{model} && ($attr{$n}{model} eq 'Blind_8080_02'))		 				# Wenn Device = Blind/Rolladen
+		{																						# ============================
+			
+			
+			 if ($specialkey eq 'short' && $keycode eq $lh->{KEYCODE}) 							# Taste 1 kurz gedrückt: dann Endzustand top/oben anfahren
+			 {																					# --------------------------------------------------------
+			   $state = "top";									 							 	# 	
+			 }
+			 elsif ($specialkey eq "long" && $keycode eq $lh->{KEYCODE}) 						# Taste 1 lang gedrückt: dann nach oben fahren (bis zu stop)
+			 {																					# ----------------------------------------------------------
+			   $state = "up";	
+			 }
+			 if ($specialkey eq 'short' && $keycode eq $lh->{KEYCODE2}) 						# Taste 2 kurz gedrückt: dann Endzustand bottom/unten anfahren
+			 {																					# ------------------------------------------------------------
+			   $state = "bottom";									 							 	# 	
+			 }
+			 elsif ($specialkey eq "long" && $keycode eq $lh->{KEYCODE2}) 						# Taste 2 lang gedrückt: dann nach runter fahren (bis zu stop)
+			 {																					# -----------------------------------------------------------
+			   $state = "down";	
+			 }
+			 elsif ($specialkey eq "stop") 														# Ende der lang gedrückten Taste dann Fahrt stoppen
+			 {																					# --------------------------------------------------
+			   $state = "stop" if($oldstate eq 'up' || $oldstate eq 'down' || $oldstate eq 'stop');	# falls fahrt aktiv war oder bereits gestoppt wurde  
+			 }
+			 
+			 else {}
+			  																	
+			
+		}		
+
+# 2 Key Switch:
+#==============
+		elsif ($attr{$n}{model} && ($attr{$n}{model} eq 'Switch_8080_01_2Key'))		 			# Wenn Device = 2 Key Switch
+		{																						# ==============================
+			
+			
+			 if ($keycode eq $lh->{KEYCODE}) 													# Taste 1 gedrückt (no matter if short or long) -> State = on
+			 {																					# -----------------------------------------------------------
+			   $state = "on";									 							 	# 	
+			 }
+			 elsif ($keycode eq $lh->{KEYCODE2}) 												# Taste 2 gedrückt (no matter if short or long) -> State = off
+			 {																					# ------------------------------------------------------------
+			   $state = "off";									 								# 	
+			 }
+			 else {}
+		}		
+# Für 1 Key Switch und alle anderen Modelle gilt: egal ob kurz oder langer Tastendruck: toggeln zwischen on und off !
+# =================================================================================================================== 
+	    elsif ($specialkey ne 'stop') 															# Bei Ende der lang gedrückten Taste: nothing to do in this case 
+		{
+			if($oldstate eq 'off') {$state = "on";}												# off -> on	
+			elsif($oldstate eq 'on') {$state = "off";}											# on -> off
+			else {$state = "on";}																# Weder noch? dann Neuer Zustand = on (wird dann wohl aus gewesen sein)
+		}
+		else 						 															# Bei unbekanntem Device/Aktion keine weitere Aktion
+		{
+		$devicefound=0;																			# das Device ist nicht bekannt
+		}
+
+		if ($devicefound == 1)																	# Update Readings if Device/Action found
+		  {
+#			Log3 $name, 2, "KOPP_FC_Parse: Model $attr{$n}{model} gefunden ";  					# kann wieder Raus !!!! ### Claus  	    
+#			Log3 $name, 2, "KOPP_FC_Parse: Code1: $lh->{CODE}{1} "; 	 						# kann wieder Raus !!!! ### Claus  	    
+#			Log3 $name, 2, "KOPP_FC_Parse: Code2: $lh->{CODE}{2} ";  							# kann wieder Raus !!!! ### Claus  	    
+#			Log3 $name, 2, "KOPP_FC_Parse: Code3: $lh->{CODE}{3} "; 							# kann wieder Raus !!!! ### Claus  	    
+#			Log3 $name, 2, "KOPP_FC_Parse: KeyCode1: $lh->{KEYCODE} ";  						# kann wieder Raus !!!! ### Claus  	    
+#			Log3 $name, 2, "KOPP_FC_Parse: KeyCode2: $lh->{KEYCODE2} ";  						# kann wieder Raus !!!! ### Claus  	    
+#			Log3 $name, 2, "KOPP_FC_Parse: KeyCode3: $lh->{KEYCODE3} ";  						# kann wieder Raus !!!! ### Claus  	    
+
+
+			
+           readingsSingleUpdate($lh, "state", $state, 1);										# $state mit "" oder ohne?
+
+		
+#		   Log3 $name, 2, "KOPP_FC_Parse lh: $lh n: $n  oldstate: $oldstate state: $state"; 	 # kann wieder Raus !!!! ### Claus
+
+		   push(@list, $n);
+		  }   
+        }
+		return @list;
+	   }
+	   else 
+	   {
+	   	Log3 $name, 2, "KOPP_FC_Parse: Device not defined for message $msg";  
+	   }
+
+
+	  }
+
+    else 
+	  {
+   	   Log3 $name, 2, "$name: KOPP_FC_Parse: nicht gefunden 01 $msg";   # kann wieder Raus !!!! ### Claus
+	   return "KOPP_Parse: Command not known";
+      }
+} else {
+    DoTrigger($name, "UNKNOWNCODE $msg");
+  	Log3 $name, 2, "$name: KOPP_FC.PM Kopp nicht gefunden 02 $msg";   # kann wieder Raus !!!! ### Claus
+    Log3 $name, 3, "$name: Unknown code $msg, help me [KOPP_FC]!";
+    return undef;
+  }
+}
+
+# end sub Kopp_FC_Parse
+###############################
+
+
+1;
+
+=pod
+=item device
+=item summary controls "Kopp Free Control" Devices via 868 Mhz CUL, CCD...
+=item summary_DE steuert "Kopp Free Control" Devices via 868 Mhz CULs, CCD...
+=begin html
+
+<a name="KOPP_FC"></a>
+<h3>Kopp Free Control protocol</h3>
+<ul>
+  <b>Please take into account: this protocol is under construction. Commands may change </b>
+  <br><br>
+
+  The Kopp Free Control protocol is used by Kopp receivers/actuators and senders.
+  This module is able to send commands to Kopp actuators and receive commands from Kopp transmitters. Currently supports devices: dimmers, switches and blinds.
+  The communication is done via a <a href="#CUL">CUL</a> or compatible device (e.g. CCD...). 
+  This devices must be defined before using this protocol.
+
+  <br><br>
+  <b>Assign the Kopp Free Control protocol to a CUL or compatible device</b>
+  <ul>
+   <code>define CUL_0 CUL /dev/ttyAMA0@38400 1234</code><br>
+   <code>attr CUL_0 rfmode KOPP_FC</code>
+   <br>
+   This attribute ("rfmode KOPP_FC") assigns the Kopp protocol to device CUL_0<br>
+   You may <b>not</b> assign/use a second protocol on this device
+   <br> 
+  </ul>
+  <br>
+  <a name="KOPP_FCdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; KOPP_FC &lt;Keycode&gt; &lt;Transmittercode1&gt; &lt;Transmittercode2&gt; [&lt;Keycode2&gt] [&lt;Keycode3&gt]</code>
+ 
+  <br>
+   <br><li><code>&lt;name&gt;</code></li>
+   name is the identifier (name) you plan to assign to your specific device (actuator) as done for any other FHEM device
+   
+   <br><br><li><code>&lt;Keycode&gt;</code></li>
+   Keycode is a 2 digit hex code (1Byte) which reflects the transmitters key
+  
+   <br><br><li><code>&lt;Transmittercode1&gt;</code></li>
+   Transmittercode1 is a 4 digit hex code. This code is specific for the transmitter itself.
+   
+   <br><br><li><code>&lt;Transmittercode2&gt;</code></li>
+   Transmittercode2 is a 2 digit hex code and also specific for the transmitter, but I didn't see any difference while modifying this code.
+   (seems this code don't matter the receiver). 
+   
+   <br>Both codes (Transmittercode1/2) are also used to pair the transmitter with the receivers (remote switch, dimmer, blind..)
+  
+   <br><br><li><code>[&lt;Keycode2&gt;]</code></li>
+   Keycode2 is an opional 2 digit hex code (1Byte) which reflects a second transmitters key
+  
+   <br><br><li><code>[&lt;Keycode3&gt;]</code></li>
+   Keycode3 is an opional 2 digit hex code (1Byte) which reflects a third transmitters key
+   <br>
+   Some receivers like dimmers can be paired with two addional keys, which allow to switch the dimmer directly on or off.
+   That means FHEM will always know the current state, which is not the case in one key mode (toggling between on and off)
+   
+   <br><br>
+   Pairing is done by setting the receiver in programming mode by pressing the program button at the receiver<br>
+   (small buttom, typically inside a hole).<br>
+   Once the receiver is in programming mode send a command (or two, see dimmer above) from within FHEM to complete the pairing.
+   For more details take a look to the data sheet of the corresponding receiver type.
+   <br>
+   You are now able to control the receiver from FHEM, the receiver handles FHEM just linke another remote control.
+</ul>         
+
+  <br>
+  <a name="KOPP_FCset"></a>
+  <b>Set</b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt</code>
+    <br>
+ 
+   <br><li><code>&lt;value&gt;</code></li>
+	value is one of:
+    <ul>
+    <code>on</code><br>
+	<code>off</code><br>
+	<code>dimm</code><br>
+	<code>stop</code><br>
+	</ul>    
+	
+    <pre>Examples:
+    <code>set Dimmer on</code>          # will toggle dimmer device on/off for 1Key remote control, 
+    <code> </code>                        will switch on for 3 key remote control
+    <code>set Dimmer off</code>         # will switch dimmer device off (3 key remote control)
+    <code>set Dimmer dimm</code>        # will start dimming process
+    <code>set Dimmer stop</code>        # will stop dimming process
+   	</pre>
+  </ul>
+
+  <a name="KOPP_FCattrib"></a>
+  <b>Attributes</b>
+  <ul>
+    <code>attr &lt;name&gt; model &lt;value&gt</code> 
+    <br>
+    <br><li><code>&lt;value&gt;</code></li>
+	value is one of:
+    <ul>
+	<code>Switch_8080_01</code><br>
+    <code>Switch_8080_01_2Key</code><br>
+    <code>Blind_8080_02</code><br>
+    <code>Timer_8080_04</code><br>
+    <code>Dimm_8011_00</code><br>
+	<code>Dimm_8011_00_3Key</code><br>
+	</ul>    
+    
+  </ul>
+  <br>
+  
+  <b>Examples</b>
+  
+  <ul>
+   <br>FHEM Config for Dimmer via 1 Key remote control:
+   <ul>
+      <code>define Dimmer KOPP_FC 65 FA5E 02</code><br>
+	  <code>attr Dimmer IODev CUL_0</code><br>
+	  <code>attr Dimmer devStateIcon OnOff:toggle:dimm dimm:dim50%:stop stop:on:dimm off:toggle:dimm</code><br>
+	  <code>attr Dimmer eventMap on:OnOff dimm:dimm stop:stop</code><br>
+	  <code>attr Dimmer group TestGroup</code><br>
+  	  <code>attr Dimmer model Dimm_8011_00</code><br>
+	  <code>attr Dimmer room TestRoom</code><br>
+	  <code>attr Dimmer webCmd OnOff:dimm:stop</code><br>
+   </ul>
+ 
+   <br>FHEM Config for Dimmer via 3 Key remote control:
+   <ul>
+	  <code>define SDimmer KOPP_FC 65 FA5E 02 55 75</code><br>
+	  <code>attr SDimmer IODev CUL_0</code><br>
+	  <code>attr SDimmer devStateIcon dimm:dim50%:stop stop:on:off on:on:off off:off:on</code><br>
+	  <code>attr SDimmer group TestGroup</code><br>
+	  <code>attr SDimmer icon light_pendant_light</code><br>
+	  <code>attr SDimmer model Dimm_8011_00_3Key</code><br>
+	  <code>attr SDimmer room TestRoom</code><br>
+	  <code>attr SDimmer webCmd on:dimm:stop:off</code><br>
+   </ul>	  
+ </ul>  
+
+ <br><br>
+ <b>Additional Information you can find in corresponding FHEM Wiki</b>
+ <ul><li><a href="http://www.fhemwiki.de/w/index.php?title=Kopp_Allgemein&redirect=no">Kopp Allgemein</a></li></ul>
+ <br><br>
+
+
+</ul>
+  
+ 
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1223 - 0
fhem/core/FHEM/10_MAX.pm


+ 239 - 0
fhem/core/FHEM/10_MQTT_BRIDGE.pm

@@ -0,0 +1,239 @@
+##############################################
+#
+# fhem bridge to mqtt (see http://mqtt.org)
+#
+# Copyright (C) 2017 Stephan Eisler
+# Copyright (C) 2014 - 2016 Norbert Truchsess
+#
+#     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: 10_MQTT_BRIDGE.pm 13318 2017-02-03 09:42:52Z eisler $
+#
+##############################################
+
+use strict;
+use warnings;
+
+my %sets = (
+);
+
+my %gets = (
+  "version"   => "",
+  "readings"  => ""
+);
+
+sub MQTT_BRIDGE_Initialize($) {
+
+  my $hash = shift @_;
+
+  # Consumer
+  $hash->{DefFn}    = "MQTT::Client_Define";
+  $hash->{UndefFn}  = "MQTT::Client_Undefine";
+  $hash->{GetFn}    = "MQTT::BRIDGE::Get";
+  $hash->{NotifyFn} = "MQTT::BRIDGE::Notify";
+  $hash->{AttrFn}   = "MQTT::BRIDGE::Attr";
+  
+  $hash->{AttrList} =
+    "IODev ".
+    "qos:".join(",",keys %MQTT::qos)." ".
+    "retain:0,1 ".
+    "publish-topic-base ".
+    "publishState ".
+    "publishReading_.* ".
+    "subscribeSet ".
+    "subscribeSet_.* ".
+    $main::readingFnAttributes;
+
+    main::LoadModule("MQTT");
+}
+
+package MQTT::BRIDGE;
+
+use strict;
+use warnings;
+use GPUtils qw(:all);
+
+use Net::MQTT::Constants;
+
+BEGIN {
+  MQTT->import(qw(:all));
+
+  GP_Import(qw(
+    AttrVal
+    CommandAttr
+    readingsSingleUpdate
+    Log3
+    DoSet
+  ))
+};
+
+sub Get($$@) {
+  my ($hash, $name, $command) = @_;
+  return "Need at least one parameters" unless (defined $command);
+  return "Unknown argument $command, choose one of " . join(" ", sort keys %gets)
+    unless (defined($gets{$command}));
+
+  COMMAND_HANDLER: {
+    # populate dynamically from keys %{$defs{$sdev}{READINGS}}
+    $command eq "readings" and do {
+      my $base = AttrVal($name,"publish-topic-base","/$hash->{DEF}/");
+      foreach my $reading (keys %{$main::defs{$hash->{DEF}}{READINGS}}) {
+        unless (defined AttrVal($name,"publishReading_$reading",undef)) {
+          CommandAttr($hash,"$name publishReading_$reading $base$reading");
+        }
+      };
+      last;
+    };
+  };
+}
+
+sub Notify() {
+  my ($hash,$dev) = @_;
+
+  Log3($hash->{NAME},5,"Notify for $dev->{NAME}");
+  foreach my $event (@{$dev->{CHANGED}}) {
+    $event =~ /^([^:]+)(: )?(.*)$/;
+    Log3($hash->{NAME},5,"$event, '".((defined $1) ? $1 : "-undef-")."', '".((defined $3) ? $3 : "-undef-")."'");
+    my $msgid;
+    if (defined $3 and $3 ne "") {
+      if (defined $hash->{publishReadings}->{$1}) {
+        $msgid = send_publish($hash->{IODev}, topic => $hash->{publishReadings}->{$1}, message => $3, qos => $hash->{qos}, retain => $hash->{retain});
+        readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1);
+      }
+    } else {
+      if (defined $hash->{publishState}) {
+        $msgid = send_publish($hash->{IODev}, topic => $hash->{publishState}, message => $1, qos => $hash->{qos}, retain => $hash->{retain});
+        readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1);
+      }
+    }
+    $hash->{message_ids}->{$msgid}++ if defined $msgid;
+  }
+}
+
+sub Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+
+  my $hash = $main::defs{$name};
+  ATTRIBUTE_HANDLER: {
+    $attribute =~ /^subscribeSet(_?)(.*)/ and do {
+      if ($command eq "set") {
+        unless (defined $hash->{subscribeSets}->{$value} and $hash->{subscribeSets}->{$value} eq $2) {
+          unless (defined $hash->{subscribeSets}->{$value}) {
+            client_subscribe_topic($hash,$value);
+          }
+          $hash->{subscribeSets}->{$value} = $2;
+        }
+      } else {
+        foreach my $topic (keys %{$hash->{subscribeSets}}) {
+          if ($hash->{subscribeSets}->{$topic} eq $2) {
+            client_unsubscribe_topic($hash,$topic);
+            delete $hash->{subscribeSets}->{$topic};
+            last;
+          }
+        }
+      }
+      last;
+    };
+    $attribute eq "publishState" and do {
+      if ($command eq "set") {
+        $hash->{publishState} = $value;
+      } else {
+        delete $hash->{publishState};
+      }
+      last;
+    };
+    $attribute =~ /^publishReading_(.+)$/ and do {
+      if ($command eq "set") {
+        $hash->{publishReadings}->{$1} = $value;
+      } else {
+        delete $hash->{publishReadings}->{$1};
+      }
+      last;
+    };
+    client_attr($hash,$command,$name,$attribute,$value);
+  }
+}
+
+sub onmessage($$$) {
+  my ($hash,$topic,$message) = @_;
+  if (defined (my $command = $hash->{subscribeSets}->{$topic})) {
+    my @args = split ("[ \t]+",$message);
+    if ($command eq "") {
+      Log3($hash->{NAME},5,"calling DoSet($hash->{DEF}".(@args ? ",".join(",",@args) : ""));
+      DoSet($hash->{DEF},@args);
+    } else {
+      Log3($hash->{NAME},5,"calling DoSet($hash->{DEF},$command".(@args ? ",".join(",",@args) : ""));
+      DoSet($hash->{DEF},$command,@args);
+    }
+  }
+}
+1;
+
+=pod
+=item [device]
+=item summary MQTT_BRIDGE acts as a bridge in between an fhem-device and mqtt-topics
+=begin html
+
+<a name="MQTT_BRIDGE"></a>
+<h3>MQTT_BRIDGE</h3>
+<ul>
+  <p>acts as a bridge in between an fhem-device and <a href="http://mqtt.org/">mqtt</a>-topics.</p>
+  <p>requires a <a href="#MQTT">MQTT</a>-device as IODev<br/>
+     Note: this module is based on <a href="https://metacpan.org/pod/distribution/Net-MQTT/lib/Net/MQTT.pod">Net::MQTT</a> which needs to be installed from CPAN first.</p>
+  <a name="MQTT_BRIDGEdefine"></a>
+  <p><b>Define</b></p>
+  <ul>
+    <p><code>define &lt;name&gt; MQTT_BRIDGE &lt;fhem-device-name&gt;</code></p>
+    <p>Specifies the MQTT device.<br/>
+       &lt;fhem-device-name&gt; is the fhem-device this MQTT_BRIDGE is linked to.</p>
+  </ul>
+  <a name="MQTT_BRIDGEget"></a>
+  <p><b>Get</b></p>
+  <ul>
+    <li>
+      <p><code>get &lt;name&gt; readings</code><br/>
+         retrieves all existing readings from fhem-device and configures (default-)topics for them.<br/>
+         attribute 'publish-topic-base' is prepended if set.</p>
+    </li>
+  </ul>
+  <a name="MQTT_BRIDGEattr"></a>
+  <p><b>Attributes</b></p>
+  <ul>
+    <li>
+      <p><code>attr &lt;name&gt; subscribeSet &lt;topic&gt;</code><br/>
+         configures a topic that will issue a 'set &lt;message&gt; whenever a message is received</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; subscribeSet_&lt;reading&gt; &lt;topic&gt;</code><br/>
+         configures a topic that will issue a 'set &lt;reading&gt; &lt;message&gt; whenever a message is received</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; publishState &lt;topic&gt;</code><br/>
+         configures a topic such that a message is sent to topic whenever the device state changes.</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; publishReading_&lt;reading&gt; &lt;topic&gt;</code><br/>
+         configures a topic such that a message is sent to topic whenever the device readings value changes.</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; publish-topic-base &lt;topic&gt;</code><br/>
+         this is used as base path when issueing 'get &lt;device&gt; readings' to construct topics to publish to based on the devices existing readings</p>
+    </li>
+  </ul>
+</ul>
+
+=end html
+=cut

+ 244 - 0
fhem/core/FHEM/10_MQTT_DEVICE.pm

@@ -0,0 +1,244 @@
+##############################################
+#
+# fhem bridge to mqtt (see http://mqtt.org)
+#
+# Copyright (C) 2017 Stephan Eisler
+# Copyright (C) 2014 - 2016 Norbert Truchsess
+#
+#     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: 10_MQTT_DEVICE.pm 13318 2017-02-03 09:42:52Z eisler $
+#
+##############################################
+
+use strict;
+use warnings;
+
+my %gets = (
+  "version"   => "",
+);
+
+sub MQTT_DEVICE_Initialize($) {
+
+  my $hash = shift @_;
+
+  # Consumer
+  $hash->{DefFn}    = "MQTT::DEVICE::Define";
+  $hash->{UndefFn}  = "MQTT::Client_Undefine";
+  $hash->{SetFn}    = "MQTT::DEVICE::Set";
+  $hash->{AttrFn}   = "MQTT::DEVICE::Attr";
+  
+  $hash->{AttrList} =
+    "IODev ".
+    "qos:".join(",",keys %MQTT::qos)." ".
+    "retain:0,1 ".
+    "publishSet ".
+    "publishSet_.* ".
+    "subscribeReading_.* ".
+    "autoSubscribeReadings ".
+    $main::readingFnAttributes;
+    
+    main::LoadModule("MQTT");
+}
+
+package MQTT::DEVICE;
+
+use strict;
+use warnings;
+use GPUtils qw(:all);
+
+use Net::MQTT::Constants;
+
+BEGIN {
+  MQTT->import(qw(:all));
+
+  GP_Import(qw(
+    CommandDeleteReading
+    CommandAttr
+    readingsSingleUpdate
+    Log3
+  ))
+};
+
+sub Define() {
+  my ( $hash, $def ) = @_;
+  $hash->{sets} = {};
+  return MQTT::Client_Define($hash,$def);
+};
+
+sub Set($$$@) {
+  my ($hash,$name,$command,@values) = @_;
+  return "Need at least one parameters" unless defined $command;
+  return "Unknown argument $command, choose one of " . join(" ", map {$hash->{sets}->{$_} eq "" ? $_ : "$_:".$hash->{sets}->{$_}} sort keys %{$hash->{sets}})
+    if(!defined($hash->{sets}->{$command}));
+  my $msgid;
+  if (@values) {
+    my $value = join " ",@values;
+    $msgid = send_publish($hash->{IODev}, topic => $hash->{publishSets}->{$command}->{topic}, message => $value, qos => $hash->{qos}, retain => $hash->{retain});
+    readingsSingleUpdate($hash,$command,$value,1);
+  } else {
+    $msgid = send_publish($hash->{IODev}, topic => $hash->{publishSets}->{""}->{topic}, message => $command, qos => $hash->{qos}, retain => $hash->{retain});
+    readingsSingleUpdate($hash,"state",$command,1);
+  }
+  $hash->{message_ids}->{$msgid}++ if defined $msgid;
+  readingsSingleUpdate($hash,"transmission-state","outgoing publish sent",1);
+  return undef;
+}
+
+sub Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+
+  my $hash = $main::defs{$name};
+  ATTRIBUTE_HANDLER: {
+    $attribute =~ /^subscribeReading_(.+)/ and do {
+      if ($command eq "set") {
+        unless (defined $hash->{subscribeReadings}->{$value} and $hash->{subscribeReadings}->{$value} eq $1) {
+          unless (defined $hash->{subscribeReadings}->{$value}) {
+            client_subscribe_topic($hash,$value);
+          }
+          $hash->{subscribeReadings}->{$value} = $1;
+        }
+      } else {
+        foreach my $topic (keys %{$hash->{subscribeReadings}}) {
+          if ($hash->{subscribeReadings}->{$topic} eq $1) {
+            client_unsubscribe_topic($hash,$topic);
+            delete $hash->{subscribeReadings}->{$topic};
+            CommandDeleteReading(undef,"$hash->{NAME} $1");
+            last;
+          }
+        }
+      }
+      last;
+    };
+    $attribute eq "autoSubscribeReadings" and do {
+      if ($command eq "set") {
+        unless (defined $hash->{'.autoSubscribeTopic'} and $hash->{'.autoSubscribeTopic'} eq $value) {
+          if (defined $hash->{'.autoSubscribeTopic'}) {
+            client_unsubscribe_topic($hash,$hash->{'.autoSubscribeTopic'});
+          }
+          $hash->{'.autoSubscribeTopic'} = $value;
+          $hash->{'.autoSubscribeExpr'} = topic_to_regexp($value);
+          client_subscribe_topic($hash,$value);
+        }
+      } else {
+        if (defined $hash->{'.autoSubscribeTopic'}) {
+          client_unsubscribe_topic($hash,$hash->{'.autoSubscribeTopic'});
+          delete $hash->{'.autoSubscribeTopic'};
+          delete $hash->{'.autoSubscribeExpr'};
+        }
+      }
+      last;
+    };
+    $attribute =~ /^publishSet(_?)(.*)/ and do {
+      if ($command eq "set") {
+        my @values = split ("[ \t]+",$value);
+        my $topic = pop @values;
+        $hash->{publishSets}->{$2} = {
+          'values' => \@values,
+          topic    => $topic,
+        };
+        if ($2 eq "") {
+          foreach my $set (@values) {
+            $hash->{sets}->{$set}="";
+          }
+        } else {
+          $hash->{sets}->{$2}=join(",",@values);
+        }
+      } else {
+        if ($2 eq "") {
+          foreach my $set (@{$hash->{publishSets}->{$2}->{'values'}}) {
+            delete $hash->{sets}->{$set};
+          }
+        } else {
+          CommandDeleteReading(undef,"$hash->{NAME} $2");
+          delete $hash->{sets}->{$2};
+        }
+        delete $hash->{publishSets}->{$2};
+      }
+      last;
+    };
+    client_attr($hash,$command,$name,$attribute,$value);
+  }
+}
+
+sub onmessage($$$) {
+  my ($hash,$topic,$message) = @_;
+  if (defined (my $reading = $hash->{subscribeReadings}->{$topic})) {
+    Log3($hash->{NAME},5,"calling readingsSingleUpdate($hash->{NAME},$reading,$message,1");
+    readingsSingleUpdate($hash,$reading,$message,1);
+  } elsif ($topic =~ $hash->{'.autoSubscribeExpr'}) {
+    Log3($hash->{NAME},5,"calling readingsSingleUpdate($hash->{NAME},$1,$message,1");
+    CommandAttr(undef,"$hash->{NAME} subscribeReading_$1 $topic");
+    readingsSingleUpdate($hash,$1,$message,1);
+  }
+}
+
+1;
+
+=pod
+=item [device]
+=item summary MQTT_DEVICE acts as a fhem-device that is mapped to mqtt-topics
+=begin html
+
+<a name="MQTT_DEVICE"></a>
+<h3>MQTT_DEVICE</h3>
+<ul>
+  <p>acts as a fhem-device that is mapped to <a href="http://mqtt.org/">mqtt</a>-topics.</p>
+  <p>requires a <a href="#MQTT">MQTT</a>-device as IODev<br/>
+     Note: this module is based on <a href="https://metacpan.org/pod/distribution/Net-MQTT/lib/Net/MQTT.pod">Net::MQTT</a> which needs to be installed from CPAN first.</p>
+  <a name="MQTT_DEVICEdefine"></a>
+  <p><b>Define</b></p>
+  <ul>
+    <p><code>define &lt;name&gt; MQTT_DEVICE</code><br/>
+       Specifies the MQTT device.</p>
+  </ul>
+  <a name="MQTT_DEVICEset"></a>
+  <p><b>Set</b></p>
+  <ul>
+    <li>
+      <p><code>set &lt;name&gt; &lt;command&gt;</code><br/>
+         sets reading 'state' and publishes the command to topic configured via attr publishSet</p>
+    </li>
+    <li>
+      <p><code>set &lt;name&gt; &lt;h;reading&gt; &lt;value&gt;</code><br/>
+         sets reading &lt;h;reading&gt; and publishes the command to topic configured via attr publishSet_&lt;h;reading&gt;</p>
+    </li>
+  </ul>
+  <a name="MQTT_DEVICEattr"></a>
+  <p><b>Attributes</b></p>
+  <ul>
+    <li>
+      <p><code>attr &lt;name&gt; publishSet [&lt;commands&gt;] &lt;topic&gt;</code><br/>
+         configures set commands that may be used to both set reading 'state' and publish to configured topic</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; publishSet_&lt;reading&gt; [&lt;values&gt;] &lt;topic&gt;</code><br/>
+         configures reading that may be used to both set 'reading' (to optionally configured values) and publish to configured topic</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; autoSubscribeReadings &lt;topic&gt;</code><br/>
+         specify a mqtt-topic pattern with wildcard (e.c. 'myhouse/kitchen/+') and MQTT_DEVICE automagically creates readings based on the wildcard-match<br/>
+         e.g a message received with topic 'myhouse/kitchen/temperature' would create and update a reading 'temperature'</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; subscribeReading_&lt;reading&gt; &lt;topic&gt;</code><br/>
+         mapps a reading to a specific topic. The reading is updated whenever a message to the configured topic arrives</p>
+    </li>
+  </ul>
+</ul>
+
+=end html
+=cut

+ 669 - 0
fhem/core/FHEM/10_MYSENSORS_DEVICE.pm

@@ -0,0 +1,669 @@
+##############################################
+#
+# fhem bridge to MySensors (see http://mysensors.org)
+#
+# Copyright (C) 2014 Norbert Truchsess
+#
+#     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: 10_MYSENSORS_DEVICE.pm 10967 2016-02-29 20:37:40Z ntruchsess $
+#
+##############################################
+
+use strict;
+use warnings;
+
+my %gets = (
+  "version"   => "",
+);
+
+sub MYSENSORS_DEVICE_Initialize($) {
+
+  my $hash = shift @_;
+
+  # Consumer
+  $hash->{DefFn}    = "MYSENSORS::DEVICE::Define";
+  $hash->{UndefFn}  = "MYSENSORS::DEVICE::UnDefine";
+  $hash->{SetFn}    = "MYSENSORS::DEVICE::Set";
+  $hash->{AttrFn}   = "MYSENSORS::DEVICE::Attr";
+  
+  $hash->{AttrList} =
+    "config:M,I ".
+    "mode:node,repeater ".
+    "version:1.4 ".
+    "setCommands ".
+    "setReading_.+ ".
+    "mapReadingType_.+ ".
+    "mapReading_.+ ".
+    "requestAck:1 ". 
+    "IODev ".
+    "showtime:0,1 ".
+    $main::readingFnAttributes;
+
+  main::LoadModule("MYSENSORS");
+}
+
+package MYSENSORS::DEVICE;
+
+use strict;
+use warnings;
+use GPUtils qw(:all);
+
+use Device::MySensors::Constants qw(:all);
+use Device::MySensors::Message qw(:all);
+use SetExtensions qw/ :all /;
+
+BEGIN {
+  MYSENSORS->import(qw(:all));
+
+  GP_Import(qw(
+    AttrVal
+    readingsSingleUpdate
+    CommandAttr
+    CommandDeleteAttr
+    CommandDeleteReading
+    AssignIoPort
+    Log3
+    SetExtensions
+    ReadingsVal
+  ))
+};
+
+my %static_types = (
+  S_DOOR                  => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Door and window sensors
+  S_MOTION                => { receives => [], sends => [V_TRIPPED] }, # MotionSensor
+  S_SMOKE                 => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Smoke sensor
+  S_LIGHT                 => { receives => [V_STATUS,V_WATT], sends => [V_STATUS,V_WATT] }, # Binary device (on/off), Alias for S_BINARY
+  S_DIMMER                => { receives => [V_STATUS,V_PERCENTAGE,V_WATT], sends => [V_STATUS,V_PERCENTAGE,V_WATT] }, # Dimmable device of some kind
+  S_COVER                 => { receives => [V_UP,V_DOWN,V_STOP,V_PERCENTAGE], sends => [V_PERCENTAGE] }, # Window covers or shades
+  S_TEMP                  => { receives => [], sends => [V_TEMP] }, # Temperature sensor
+  S_HUM                   => { receives => [], sends => [V_HUM] }, # Humidity sensor
+  S_BARO                  => { receives => [], sends => [V_PRESSURE,V_FORECAST] }, # Barometer sensor (Pressure)
+  S_WIND                  => { receives => [], sends => [V_WIND,V_GUST] }, # Wind sensor
+  S_RAIN                  => { receives => [], sends => [V_RAIN,V_RAINRATE] }, # Rain sensor
+  S_UV                    => { receives => [], sends => [V_UV] }, # UV Sensor
+  S_WEIGHT                => { receives => [], sends => [V_WEIGHT,V_IMPEDANCE] }, # Weight sensor for scales etc.
+  S_POWER                 => { receives => [V_VAR1], sends => [V_WATT,V_KWH,V_VAR1] }, # Power measuring device, like power meters
+  S_HEATER                => { receives => [], sends => [V_HVAC_SETPOINT_HEAT,V_HVAC_FLOW_STATE,V_TEMP] }, # Heater device
+  S_DISTANCE              => { receives => [], sends => [V_DISTANCE] }, # Distance sensor
+  S_LIGHT_LEVEL           => { receives => [], sends => [V_LIGHT_LEVEL] }, # Light sensor
+  S_ARDUINO_NODE          => { receives => [], sends => [] }, # Arduino node device
+  S_ARDUINO_REPEATER_NODE => { receives => [], sends => [] }, # Arduino repeating node device
+  S_LOCK                  => { receives => [V_LOCK_STATUS], sends => [V_LOCK_STATUS] }, # Lock device
+  S_IR                    => { receives => [V_IR_SEND], sends => [V_IR_RECEIVE] }, # Ir sender/receiver device
+  S_WATER                 => { receives => [V_VAR1], sends => [V_FLOW,V_VOLUME,V_VAR1] }, # Water meter
+  S_AIR_QUALITY           => { receives => [], sends => [V_LEVEL,V_UNIT_PREFIX] }, # Air quality sensor e.g. MQ-2
+  S_CUSTOM                => { receives => [V_VAR1,V_VAR2,V_VAR3,V_VAR4,V_VAR5], sends => [V_VAR1,V_VAR2,V_VAR3,V_VAR4,V_VAR5] }, # Use this for custom sensors where no other fits.
+  S_DUST                  => { receives => [], sends => [V_LEVEL,V_UNIT_PREFIX] }, # Dust level sensor
+  S_SCENE_CONTROLLER      => { receives => [], sends => [V_SCENE_ON,V_SCENE_OFF] }, # Scene controller device
+  S_RGB_LIGHT             => { receives => [V_RGB,V_WATT], sends => [V_RGB,V_WATT] }, # RGB light
+  S_RGBW_LIGHT            => { receives => [V_RGBW,V_WATT], sends => [V_RGBW,V_WATT] }, # RGBW light (with separate white component)
+  S_COLOR_SENSOR          => { receives => [V_RGB], sends => [V_RGB] }, # Color sensor
+  S_HVAC                  => { receives => [], sends => [V_HVAC_SETPOINT_HEAT,V_HVAC_SETPOINT_COOL,V_HVAC_FLOW_STATE,V_HVAC_FLOW_MODE,V_HVAC_SPEED] }, # Thermostat/HVAC device
+  S_MULTIMETER            => { receives => [], sends => [V_VOLTAGE,V_CURRENT,V_IMPEDANCE] }, # Multimeter device
+  S_SPRINKLER             => { receives => [], sends => [V_STATUS,V_TRIPPED] }, # Sprinkler device
+  S_WATER_LEAK            => { receives => [], sends => [V_TRIPPED,V_ARMED] }, # Water leak sensor
+  S_SOUND                 => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Sound sensor
+  S_VIBRATION             => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Vibration sensor
+  S_MOISTURE              => { receives => [], sends => [V_LEVEL,V_TRIPPED,V_ARMED] }, # Moisture sensor
+);
+
+my %static_mappings = (
+  V_TEMP        => { type => "temperature" },
+  V_HUM         => { type => "humidity" },
+  V_STATUS      => { type => "status", val => { 0 => 'off', 1 => 'on' }},
+  V_PERCENTAGE  => { type => "percentage", range => { min => 0, step => 1, max => 100 }},
+  V_PRESSURE    => { type => "pressure" },
+  V_FORECAST    => { type => "forecast", val => { # PressureSensor, DP/Dt explanation
+                                                  0 => 'stable',      # 0 = "Stable Weather Pattern"
+                                                  1 => 'sunny',       # 1 = "Slowly rising Good Weather", "Clear/Sunny"
+                                                  2 => 'cloudy',      # 2 = "Slowly falling L-Pressure ", "Cloudy/Rain"
+                                                  3 => 'unstable',    # 3 = "Quickly rising H-Press",     "Not Stable"
+                                                  4 => 'thunderstorm',# 4 = "Quickly falling L-Press",    "Thunderstorm"
+                                                  5 => 'unknown' }},  # 5 = "Unknown (More Time needed)
+  V_RAIN        => { type => "rain" },
+  V_RAINRATE    => { type => "rainrate" },
+  V_WIND        => { type => "wind" },
+  V_GUST        => { type => "gust" },
+  V_DIRECTION   => { type => "direction" },
+  V_UV          => { type => "uv" },
+  V_WEIGHT      => { type => "weight" },
+  V_DISTANCE    => { type => "distance" },
+  V_IMPEDANCE   => { type => "impedance" },
+  V_ARMED       => { type => "armed", val => { 0 => 'off', 1 => 'on' }},
+  V_TRIPPED     => { type => "tripped", val => { 0 => 'off', 1 => 'on' }},
+  V_WATT        => { type => "power" },
+  V_KWH         => { type => "energy" },
+  V_SCENE_ON    => { type => "button_on" },
+  V_SCENE_OFF   => { type => "button_off" },
+  V_HVAC_FLOW_STATE => { type => "hvacflowstate" },
+  V_HVAC_SPEED  => { type => "hvacspeed" },
+  V_LIGHT_LEVEL => { type => "brightness", range => { min => 0, step => 1, max => 100 }},
+  V_VAR1        => { type => "value1" },
+  V_VAR2        => { type => "value2" },
+  V_VAR3        => { type => "value3" },
+  V_VAR4        => { type => "value4" },
+  V_VAR5        => { type => "value5" },
+  V_UP          => { type => "up" },
+  V_DOWN        => { type => "down" },
+  V_STOP        => { type => "stop" },
+  V_IR_SEND     => { type => "ir_send" },
+  V_IR_RECEIVE  => { type => "ir_receive" },
+  V_FLOW        => { type => "flow" },
+  V_VOLUME      => { type => "volume" },
+  V_LOCK_STATUS => { type => "lockstatus", val => { 0 => 'off', 1 => 'on' }},
+  V_LEVEL       => { type => "level" },
+  V_VOLTAGE     => { type => "voltage" },
+  V_CURRENT     => { type => "current" },
+  V_RGB         => { type => "rgb" },
+  V_RGBW        => { type => "rgbw" },
+  V_ID          => { type => "id" },
+  V_UNIT_PREFIX => { type => "unitprefix" },
+  V_HVAC_SETPOINT_COOL => { type => "hvacsetpointcool" },
+  V_HVAC_SETPOINT_HEAT => { type => "hvacsetpointheat" },
+  V_HVAC_FLOW_MODE => { type => "hvacflowmode" },
+);
+
+sub Define($$) {
+  my ( $hash, $def ) = @_;
+  my ($name, $type, $radioId) = split("[ \t]+", $def);
+  return "requires 1 parameters" unless (defined $radioId and $radioId ne "");
+  $hash->{radioId} = $radioId;
+  $hash->{sets} = {
+    'time' => "",
+    reboot => "",
+  };
+  $hash->{ack} = 0;
+  $hash->{typeMappings} = {map {variableTypeToIdx($_) => $static_mappings{$_}} keys %static_mappings};
+  $hash->{sensorMappings} = {map {sensorTypeToIdx($_) => $static_types{$_}} keys %static_types};
+
+  $hash->{readingMappings} = {};
+  AssignIoPort($hash);
+};
+
+sub UnDefine($) {
+  my ($hash) = @_;
+
+  return undef;
+}
+
+sub Set($@) {
+  my ($hash,$name,$command,@values) = @_;
+  return "Need at least one parameters" unless defined $command;
+  if(!defined($hash->{sets}->{$command})) {
+    my $list = join(" ", map {$hash->{sets}->{$_} ne "" ? "$_:$hash->{sets}->{$_}" : $_} sort keys %{$hash->{sets}});
+    return grep (/(^on$)|(^off$)/,keys %{$hash->{sets}}) == 2 ? SetExtensions($hash, $list, $name, $command, @values) : "Unknown argument $command, choose one of $list";
+  }
+  COMMAND_HANDLER: {
+    $command eq "clear" and do {
+      sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_CHILDREN, payload => "C");
+      last;
+    };
+    $command eq "time" and do {
+      sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_TIME, payload => time);
+      last;
+    };
+    $command eq "reboot" and do {
+      sendClientMessage($hash, childId => 255, cmd => C_INTERNAL, subType => I_REBOOT);
+      last;
+    };
+    (defined ($hash->{setcommands}->{$command})) and do {
+      my $setcommand = $hash->{setcommands}->{$command};
+      eval {
+        my ($type,$childId,$mappedValue) = mappedReadingToRaw($hash,$setcommand->{var},$setcommand->{val});
+        sendClientMessage($hash,
+          childId => $childId,
+          cmd => C_SET,
+          subType => $type,
+          payload => $mappedValue,
+        );
+        readingsSingleUpdate($hash,$setcommand->{var},$setcommand->{val},1) unless ($hash->{ack} or $hash->{IODev}->{ack});
+      };
+      return "$command not defined: ".GP_Catch($@) if $@;
+      last;
+    };
+    my $value = @values ? join " ",@values : "";
+    eval {
+      my ($type,$childId,$mappedValue) = mappedReadingToRaw($hash,$command,$value);
+      sendClientMessage($hash, childId => $childId, cmd => C_SET, subType => $type, payload => $mappedValue);
+      readingsSingleUpdate($hash,$command,$value,1) unless ($hash->{ack} or $hash->{IODev}->{ack});
+    };
+    return "$command not defined: ".GP_Catch($@) if $@;
+  }
+}
+
+sub Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+
+  my $hash = $main::defs{$name};
+  ATTRIBUTE_HANDLER: {
+    $attribute eq "config" and do {
+      if ($main::init_done) {
+        sendClientMessage($hash, cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => $command eq 'set' ? $value : "M");
+      }
+      last;
+    };
+    $attribute eq "mode" and do {
+      if ($command eq "set" and $value eq "repeater") {
+        $hash->{repeater} = 1;
+        $hash->{sets}->{clear} = "";
+      } else {
+        $hash->{repeater} = 0;
+        delete $hash->{sets}->{clear};
+      }
+      last;
+    };
+    $attribute eq "version" and do {
+      if ($command eq "set") {
+        $hash->{protocol} = $value;
+      } else {
+        delete $hash->{protocol};
+      }
+      last;
+    };
+    $attribute eq "setCommands" and do {
+      foreach my $set (keys %{$hash->{setcommands}}) {
+        delete $hash->{sets}->{$set};
+      }
+      $hash->{setcommands} = {};
+      if ($command eq "set" and $value) {
+        foreach my $setCmd (split ("[, \t]+",$value)) {
+          if ($setCmd =~ /^(.+):(.+):(.+)$/) {
+            $hash->{sets}->{$1}="";
+            $hash->{setcommands}->{$1} = {
+              var => $2,
+              val => $3,
+            };
+          } else {
+            return "unparsable value in setCommands for $name: $setCmd";
+          }
+        }
+      }
+      last;
+    };
+    $attribute =~ /^setReading_(.+)$/ and do {
+      if ($command eq "set") {
+        $hash->{sets}->{$1}= (defined $value) ? join(",",split ("[, \t]+",$value)) : "";
+      } else {
+        CommandDeleteReading(undef,"$hash->{NAME} $1");
+        delete $hash->{sets}->{$1};
+      }
+      last;
+    };
+    $attribute =~ /^mapReadingType_(.+)/ and do {
+      my $type = variableTypeToIdx("V_$1");
+      if ($command eq "set") {
+        my @values = split ("[, \t]",$value);
+        $hash->{typeMappings}->{$type}={
+          type => shift @values,
+          val => {map {$_ =~ /^(.+):(.+)$/; $1 => $2} @values},
+        }
+      } else {
+        if ($static_mappings{"V_$1"}) {
+          $hash->{typeMappings}->{$type}=$static_mappings{"V_$1"};
+        } else {
+          delete $hash->{typeMappings}->{$type};
+        }
+        my $readings = $hash->{READINGS};
+        my $readingMappings = $hash->{readingMappings};
+        foreach my $todelete (map {$readingMappings->{$_}->{name}} grep {$readingMappings->{$_}->{type} == $type} keys %$readingMappings) {
+          CommandDeleteReading(undef,"$hash->{NAME} $todelete"); #TODO do propper remap of existing readings
+        }
+      }
+      last;
+    };
+    $attribute =~ /^mapReading_(.+)/ and do {
+      my $readingMappings = $hash->{readingMappings};
+      FIND: foreach my $id (keys %$readingMappings) {
+        my $readingsForId = $readingMappings->{$id};
+        foreach my $type (keys %$readingsForId) {
+          if (($readingsForId->{$type}->{name} // "") eq $1) {
+            delete $readingsForId->{$type};
+            unless (keys %$readingsForId) {
+              delete $readingMappings->{$id};
+            }
+            last FIND;
+          }
+        }
+      }
+      if ($command eq "set") {
+        my ($id,$typeStr,@values) = split ("[, \t]",$value);
+        my $typeMappings = $hash->{typeMappings};
+        if (my @match = grep {$typeMappings->{$_}->{type} eq $typeStr} keys %$typeMappings) {
+          my $type = shift @match;
+          $readingMappings->{$id}->{$type}->{name} = $1;
+          if (@values) {
+            $readingMappings->{$id}->{$type}->{val} = {map {$_ =~ /^(.+):(.+)$/; $1 => $2} @values}; #TODO range?
+          }
+        } else {
+          return "unknown reading type $typeStr";
+        }
+      } else {
+        CommandDeleteReading(undef,"$hash->{NAME} $1");
+      }
+      last;
+    };
+    $attribute eq "requestAck" and do {
+      if ($command eq "set") {
+        $hash->{ack} = 1;
+      } else {
+        $hash->{ack} = 0;
+      }
+      last;
+    };
+  }
+}
+
+sub onGatewayStarted($) {
+  my ($hash) = @_;
+}
+
+sub onPresentationMessage($$) {
+  my ($hash,$msg) = @_;
+  my $name = $hash->{NAME};
+  my $nodeType = $msg->{subType};
+  my $id = $msg->{childId};
+  if ($id == 255) { #special id
+    NODETYPE: {
+      $nodeType == S_ARDUINO_NODE and do {
+        CommandAttr(undef, "$name mode node");
+        last;
+      };
+      $nodeType == S_ARDUINO_REPEATER_NODE and do {
+        CommandAttr(undef, "$name mode repeater");
+        last;
+      };
+    };
+    CommandAttr(undef, "$name version $msg->{payload}");
+  };
+
+  my $readingMappings = $hash->{readingMappings};
+  my $typeMappings = $hash->{typeMappings};
+  if (my $sensorMappings = $hash->{sensorMappings}->{$nodeType}) {
+    my $idStr = ($id > 0 ? $id : "");
+    my @ret = ();
+    foreach my $type (@{$sensorMappings->{sends}}) {
+      next if (defined $readingMappings->{$id}->{$type});
+      my $typeStr = $typeMappings->{$type}->{type};
+      if ($hash->{IODev}->{'inclusion-mode'}) {
+        if (my $ret = CommandAttr(undef,"$name mapReading_$typeStr$idStr $id $typeStr")) {
+          push @ret,$ret;
+        }
+      } else {
+        push @ret,"no mapReading for $id, $typeStr";
+      }
+    }
+    foreach my $type (@{$sensorMappings->{receives}}) {
+      my $typeMapping = $typeMappings->{$type};
+      my $typeStr = $typeMapping->{type};
+      next if (defined $hash->{sets}->{"$typeStr$idStr"});
+      if ($hash->{IODev}->{'inclusion-mode'}) {
+        my @values = ();
+        if ($typeMapping->{range}) {
+          @values = ('slider',$typeMapping->{range}->{min},$typeMapping->{range}->{step},$typeMapping->{range}->{max});
+        } elsif ($typeMapping->{val}) {
+          @values = values %{$typeMapping->{val}};
+        }
+        if (my $ret = CommandAttr(undef,"$name setReading_$typeStr$idStr".(@values ? " ".join (",",@values) : ""))) {
+          push @ret,$ret;
+        }
+      } else {
+        push @ret,"no setReading for $id, $typeStr";
+      }
+    }
+    Log3 ($hash->{NAME},4,"MYSENSORS_DEVICE $hash->{NAME}: errors on C_PRESENTATION-message for childId $id, subType ".sensorTypeToStr($nodeType)." ".join (", ",@ret)) if @ret;
+  }
+}
+
+sub onSetMessage($$) {
+  my ($hash,$msg) = @_;
+  if (defined $msg->{payload}) {
+    eval {
+      my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload});
+      readingsSingleUpdate($hash,$reading,$value,1);
+    };
+    Log3 ($hash->{NAME},4,"MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@;
+  } else {
+    Log3 ($hash->{NAME},5,"MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload");
+  }
+}
+
+sub onRequestMessage($$) {
+  my ($hash,$msg) = @_;
+
+  eval {
+    my ($readingname,$val) = rawToMappedReading($hash, $msg->{subType}, $msg->{childId}, $msg->{payload});
+    sendClientMessage($hash,
+      childId => $msg->{childId},
+      cmd => C_SET,
+      subType => $msg->{subType},
+      payload => ReadingsVal($hash->{NAME},$readingname,$val)
+    );
+  };
+  Log3 ($hash->{NAME},4,"MYSENSORS_DEVICE $hash->{NAME}: ignoring C_REQ-message ".GP_Catch($@)) if $@;
+}
+
+sub onInternalMessage($$) {
+  my ($hash,$msg) = @_;
+  my $name = $hash->{NAME};
+  my $type = $msg->{subType};
+  my $typeStr = internalMessageTypeToStr($type);
+  INTERNALMESSAGE: {
+    $type == I_BATTERY_LEVEL and do {
+      readingsSingleUpdate($hash,"batterylevel",$msg->{payload},1);
+      Log3 ($name,4,"MYSENSORS_DEVICE $name: batterylevel $msg->{payload}");
+      last;
+    };
+    $type == I_TIME and do {
+      if ($msg->{ack}) {
+        Log3 ($name,4,"MYSENSORS_DEVICE $name: response to time-request acknowledged");
+      } else {
+        sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_TIME, payload => time);
+        Log3 ($name,4,"MYSENSORS_DEVICE $name: update of time requested");
+      }
+      last;
+    };
+    $type == I_VERSION and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_ID_REQUEST and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_ID_RESPONSE and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_INCLUSION_MODE and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_CONFIG and do {
+      if ($msg->{ack}) {
+        Log3 ($name,4,"MYSENSORS_DEVICE $name: response to config-request acknowledged");
+      } else {
+        readingsSingleUpdate($hash,"parentId",$msg->{payload},1);
+        sendClientMessage($hash,cmd => C_INTERNAL, childId => 255, subType => I_CONFIG, payload => AttrVal($name,"config","M"));
+        Log3 ($name,4,"MYSENSORS_DEVICE $name: respond to config-request, node parentId = " . $msg->{payload});
+      }
+      last;
+    };
+    $type == I_FIND_PARENT and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_FIND_PARENT_RESPONSE and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_LOG_MESSAGE and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_CHILDREN and do {
+      readingsSingleUpdate($hash,"state","routingtable cleared",1);
+      Log3 ($name,4,"MYSENSORS_DEVICE $name: routingtable cleared");
+      last;
+    };
+    $type == I_SKETCH_NAME and do {
+      $hash->{$typeStr} = $msg->{payload};
+      readingsSingleUpdate($hash,"SKETCH_NAME",$msg->{payload},1);
+      last;
+    };
+    $type == I_SKETCH_VERSION and do {
+      $hash->{$typeStr} = $msg->{payload};
+      readingsSingleUpdate($hash,"SKETCH_VERSION",$msg->{payload},1);
+      last;
+    };
+    $type == I_REBOOT and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_GATEWAY_READY and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_REQUEST_SIGNING and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_GET_NONCE and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+    $type == I_GET_NONCE_RESPONSE and do {
+      $hash->{$typeStr} = $msg->{payload};
+      last;
+    };
+  }
+}
+
+sub sendClientMessage($%) {
+  my ($hash,%msg) = @_;
+  $msg{radioId} = $hash->{radioId};
+  $msg{ack} = $hash->{ack} unless defined $msg{ack};
+  sendMessage($hash->{IODev},%msg);
+}
+
+sub rawToMappedReading($$$$) {
+  my($hash, $type, $childId, $value) = @_;
+
+  my $name;
+  if (defined (my $mapping = $hash->{readingMappings}->{$childId}->{$type})) {
+    my $val = $mapping->{val} // $hash->{typeMappings}->{$type}->{val};
+    return ($mapping->{name},defined $val ? ($val->{$value} // $value) : $value);
+  }
+  die "no reading-mapping for childId $childId, type ".($hash->{typeMappings}->{$type}->{type} ? $hash->{typeMappings}->{$type}->{type} : variableTypeToStr($type));
+}
+
+sub mappedReadingToRaw($$$) {
+  my ($hash,$reading,$value) = @_;
+  
+  my $readingsMapping = $hash->{readingMappings};
+  foreach my $id (keys %$readingsMapping) {
+    my $readingTypesForId = $readingsMapping->{$id};
+    foreach my $type (keys %$readingTypesForId) {
+      if (($readingTypesForId->{$type}->{name} // "") eq $reading) {
+        if (my $valueMappings = $readingTypesForId->{$type}->{val} // $hash->{typeMappings}->{$type}->{val}) {
+          if (my @mappedValues = grep {$valueMappings->{$_} eq $value} keys %$valueMappings) {
+            return ($type,$id,shift @mappedValues);
+          }
+        }
+        return ($type,$id,$value);
+      }
+    }
+  }
+  die "no mapping for reading $reading";
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="MYSENSORS_DEVICE"></a>
+<h3>MYSENSORS_DEVICE</h3>
+<ul>
+  <p>represents a mysensors sensor attached to a mysensor-node</p>
+  <p>requires a <a href="#MYSENSOR">MYSENSOR</a>-device as IODev</p>
+  <a name="MYSENSORS_DEVICEdefine"></a>
+  <p><b>Define</b></p>
+  <ul>
+    <p><code>define &lt;name&gt; MYSENSORS_DEVICE &lt;Sensor-type&gt; &lt;node-id&gt;</code><br/>
+      Specifies the MYSENSOR_DEVICE device.</p>
+  </ul>
+  <a name="MYSENSORS_DEVICEset"></a>
+  <p><b>Set</b></p>
+  <ul>
+    <li>
+      <p><code>set &lt;name&gt; clear</code><br/>
+         clears routing-table of a repeater-node</p>
+    </li>
+    <li>
+      <p><code>set &lt;name&gt; time</code><br/>
+         sets time for nodes (that support it)</p>
+    </li>
+    <li>
+      <p><code>set &lt;name&gt; reboot</code><br/>
+         reboots a node (requires a bootloader that supports it).<br/>
+         Attention: Nodes that run the standard arduino-bootloader will enter a bootloop!<br/>
+         Dis- and reconnect the nodes power to restart in this case.</p>
+    </li>
+  </ul>
+  <a name="MYSENSORS_DEVICEattr"></a>
+  <p><b>Attributes</b></p>
+  <ul>
+    <li>
+      <p><code>attr &lt;name&gt; config [&lt;M|I&gt;]</code><br/>
+         configures metric (M) or inch (I). Defaults to 'M'</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; setCommands [&lt;command:reading:value&gt;]*</code><br/>
+         configures one or more commands that can be executed by set.<br/>
+         e.g.: <code>attr &lt;name&gt; setCommands on:switch_1:on off:switch_1:off</code><br/>
+         if list of commands contains both 'on' and 'off' <a href="#setExtensions">set extensions</a> are supported</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; setReading_&lt;reading&gt; [&lt;value&gt;]*</code><br/>
+         configures a reading that can be modified by set-command<br/>
+         e.g.: <code>attr &lt;name&gt; setReading_switch_1 on,off</code></p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; mapReading_&lt;reading&gt; &lt;childId&gt; &lt;readingtype&gt; [&lt;value&gt;:&lt;mappedvalue&gt;]*</code><br/>
+         configures the reading-name for a given childId and sensortype<br/>
+         E.g.: <code>attr xxx mapReading_aussentemperatur 123 temperature</code></p>
+    </li>
+    <li>
+      <p><code>att &lt;name&gt; requestAck</code><br/>
+         request acknowledge from nodes.<br/>
+         if set the Readings of nodes are updated not before requested acknowledge is received<br/>
+         if not set the Readings of nodes are updated immediatly (not awaiting the acknowledge).<br/>
+         May also be configured on the gateway for all nodes at once</p>
+    </li>
+    <li>
+      <p><code>attr &lt;name&gt; mapReadingType_&lt;reading&gt; &lt;new reading name&gt; [&lt;value&gt;:&lt;mappedvalue&gt;]*</code><br/>
+         configures reading type names that should be used instead of technical names<br/>
+         E.g.: <code>attr xxx mapReadingType_LIGHT switch 0:on 1:off</code>
+         to be used for mysensor Variabletypes that have no predefined defaults (yet)</p>
+    </li>
+  </ul>
+</ul>
+
+=end html
+=cut
+

+ 825 - 0
fhem/core/FHEM/10_OWServer.pm

@@ -0,0 +1,825 @@
+# $Id: 10_OWServer.pm 13114 2017-01-16 19:25:59Z neubert $
+################################################################
+#
+#  Copyright notice
+#
+#  (c) 2012 Copyright: Dr. Boris Neubert & Martin Fischer
+#  e-mail: omega at online dot de
+#  e-mail: m_fischer at gmx dot de
+#
+#  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/>.
+#
+################################################################################
+
+package main;
+
+use strict;
+use warnings;
+# this must be the latest OWNet from
+#  http://owfs.cvs.sourceforge.net/viewvc/owfs/owfs/module/ownet/perl5/OWNet/lib/OWNet.pm
+# the version at CPAN is outdated and malfunctioning as at 2012-12-19
+use lib::OWNet;
+
+use vars qw(%owfamily);
+# 1-Wire devices (order by family code)
+# http://owfs.sourceforge.net/family.html
+%owfamily = (
+  "01"  => "DS2401 DS1990A",
+  "05"  => "DS2405",
+  "10"  => "DS18S20 DS1920",
+  "12"  => "DS2406 DS2507",
+  "1B"  => "DS2436",
+  "1D"  => "DS2423",
+  "20"  => "DS2450",
+  "22"  => "DS1822",
+  "23"  => "DS2433",
+  "24"  => "DS2415 DS1904",
+  "26"  => "DS2438",
+  "27"  => "DS2417",
+  "28"  => "DS18B20",
+  "29"  => "DS2408",
+  "3A"  => "DS2413",
+  "3B"  => "DS1825",
+  "7E"  => "EDS000XX",
+  "81"  => "DS1420",
+  "FF"  => "LCD",
+);
+
+use vars qw(%gets %sets);
+%gets = (
+  "/settings/timeout/directory"        => "",
+  "/settings/timeout/ftp"              => "",
+  "/settings/timeout/ha7"              => "",
+  "/settings/timeout/network"          => "",
+  "/settings/timeout/presence"         => "",
+  "/settings/timeout/serial"           => "",
+  "/settings/timeout/server"           => "",
+  "/settings/timeout/stable"           => "",
+  "/settings/timeout/uncached"         => "",
+  "/settings/timeout/usb"              => "",
+  "/settings/timeout/volatile"         => "",
+  "/settings/timeout/w1"               => "",
+  "/settings/units/pressure_scale"     => "",
+  "/settings/units/temperature_scale"  => "",
+);
+
+%sets = (
+  "timeout/directory"        => "",
+  "timeout/ftp"              => "",
+  "timeout/ha7"              => "",
+  "timeout/network"          => "",
+  "timeout/presence"         => "",
+  "timeout/serial"           => "",
+  "timeout/server"           => "",
+  "timeout/stable"           => "",
+  "timeout/uncached"         => "",
+  "timeout/usb"              => "",
+  "timeout/volatile"         => "",
+  "timeout/w1"               => "",
+  "units/pressure_scale"     => "",
+  "units/temperature_scale"  => "",
+);
+
+#####################################
+sub
+OWServer_Initialize($)
+{
+  my ($hash) = @_;
+
+# Provider
+  $hash->{WriteFn} = "OWServer_Write";
+  $hash->{ReadFn}  = "OWServer_Read";
+  $hash->{DirFn}   = "OWServer_Dir";
+  $hash->{FindFn}  = "OWServer_Find";
+  $hash->{Clients} = ":OWDevice:OWAD:OWCOUNT:OWMULTI:OWSWITCH:OWTHERM:";
+
+# Consumer
+  $hash->{DefFn}   = "OWServer_Define";
+  $hash->{NotifyFn}= "OWServer_Notify";
+  $hash->{NotifyOrderPrefix}= "50a-";
+  $hash->{UndefFn} = "OWServer_Undef";
+  $hash->{GetFn}   = "OWServer_Get";
+  $hash->{SetFn}   = "OWServer_Set";
+# $hash->{AttrFn}  = "OWServer_Attr";
+  $hash->{AttrList}= "nonblocking " . $readingFnAttributes;
+}
+
+#####################################
+sub
+OWServer_Define($$)
+{
+  my ($hash, $def) = @_;
+
+  my @a = split("[ \t]+", $def, 3);
+  my $name = $a[0];
+  if(@a < 3) {
+    my $msg = "wrong syntax for $name: define <name> OWServer <protocol>";
+    Log 2, $msg;
+    return $msg;
+  }
+
+  my $protocol = $a[2];
+
+  $hash->{fhem}{protocol}= $protocol;
+  
+  $hash->{NOTIFYDEV} = "global";
+ 
+
+  if( $init_done ) {
+    OWServer_OpenDev($hash);
+  }
+
+  return undef;
+}
+
+
+#####################################
+sub
+OWServer_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  my $name = $hash->{NAME};
+
+  foreach my $d (sort keys %defs) {
+    if(defined($defs{$d}) &&
+       defined($defs{$d}{IODev}) &&
+       $defs{$d}{IODev} == $hash)
+      {
+        my $lev = ($reread_active ? 4 : 2);
+        Log3 $name, $lev, "deleting OWServer for $d";
+        delete $defs{$d}{IODev};
+      }
+  }
+
+  OWServer_CloseDev($hash);
+  return undef;
+}
+
+#####################################
+sub
+OWServer_CloseDev($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  return unless(defined($hash->{fhem}{owserver}));
+  delete $hash->{fhem}{owserver};
+  readingsSingleUpdate($hash, "state", "DISCONNECTED", 1);
+}
+
+########################
+sub
+OWServer_OpenDev($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+
+  OWServer_CloseDev($hash);
+  my $protocol= $hash->{fhem}{protocol};
+  Log3 $name, 3, "$name: Opening connection to OWServer $protocol...";
+  my $owserver= OWNet->new($protocol);
+  if($owserver) {
+    Log3 $name, 3, "$name: Successfully connected to $protocol.";
+    $hash->{fhem}{owserver}= $owserver;
+    readingsSingleUpdate($hash, "state", "CONNECTED", 1);
+    my $ret  = OWServer_DoInit($hash);
+  }
+  return $owserver
+}
+
+#####################################
+sub
+OWServer_Notify($$)
+{
+  my ($hash,$dev) = @_;
+  my $name  = $hash->{NAME};
+  my $type  = $hash->{TYPE};
+
+  return if($dev->{NAME} ne "global");
+  return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
+
+  return if($attr{$name} && $attr{$name}{disable});
+
+  OWServer_OpenDev($hash);
+
+  return undef;
+}
+
+#####################################
+sub
+OWServer_DoInit($)
+{
+  my $hash = shift;
+  my $name = $hash->{NAME};
+
+  my $owserver= $hash->{fhem}{owserver};
+  foreach my $reading (sort keys %gets) {
+    readingsBeginUpdate($hash);
+    readingsBulkUpdate($hash,"$reading",$owserver->read("$reading"));
+    readingsEndUpdate($hash,1);
+  }
+  readingsSingleUpdate($hash, "state", "Initialized", 1);
+  OWServer_Autocreate($hash);
+  return undef;
+}
+
+#####################################
+sub
+OWServer_Read($@)
+{
+  my ($hash,$path)= @_;
+
+  return undef unless(defined($hash->{fhem}{owserver}) || $hash->{LAST_READ_FAILED});
+
+  my $ret= undef;
+
+  if(AttrVal($hash->{NAME},"nonblocking",undef) && $init_done) {
+    $hash->{".path"}= $path;
+    pipe(READER,WRITER);
+    #READER->autoflush(1);
+    WRITER->autoflush(1);
+
+    my $pid= fork;
+    if(!defined($pid)) {
+      Log3 $hash, 1, "OWServer: Cannot fork: $!";
+      return undef;
+    }
+
+    InternalTimer(gettimeofday()+6, "OWServer_TimeoutChild", $pid, 0);
+    if($pid == 0) {
+      close READER;
+      $ret= OWNet::read($hash->{DEF},$path);
+      $ret =~ s/^\s+//g if(defined($ret));
+      my $r= defined($ret) ? $ret : "<undefined>";
+      Log3 $hash, 5, "OWServer child read $path: $r";
+      delete $hash->{".path"};
+      print WRITER $ret if(defined($ret)); 
+      close WRITER;
+      # see http://forum.fhem.de/index.php?t=tree&goto=94670
+      # changed from
+      # exit 0;
+      # to
+      POSIX::_exit(0);
+    }
+
+    Log3 $hash, 5, "OWServer child ID for reading '$path' is $pid";
+    close WRITER;
+    # http://forum.fhem.de/index.php/topic,16945.0/topicseen.html#msg110673
+    my ($rout,$rin, $eout,$ein) = ('','', '','');
+    vec($rin, fileno(READER),  1) = 1;
+    $ein = $rin;
+    my $nfound = select($rout=$rin, undef, $eout=$ein, 4);
+    if( $nfound ) {
+      $ret= <READER>;
+      if(defined($ret)) {
+        chomp($ret) } else {
+        Log3 $hash, 5, "OWServer: undefined response from child $pid";
+      }
+      RemoveInternalTimer($pid);
+      OWServer_OpenDev($hash) if( $hash->{LAST_READ_FAILED} );
+      $hash->{LAST_READ_FAILED} = 0;
+    } else {
+      Log3 undef, 1, "OWServer: read timeout for child $pid";
+      $hash->{NR_READ_FAILED} = 0 if( !$hash->{NR_READ_FAILED} );
+      $hash->{NR_READ_FAILED}++;
+      OWServer_CloseDev($hash) if( !$hash->{LAST_READ_FAILED} );
+      $hash->{LAST_READ_FAILED} = 1;
+    }
+    close READER;
+
+  } else {
+    $ret= $hash->{fhem}{owserver}->read($path);
+    $ret =~ s/^\s+//g if(defined($ret));
+    $hash->{LAST_READ_FAILED} = 0;
+  }
+
+  # if a device does not exist, the server returns undef
+  # therefore it's not a good idea to blame the connection
+  # and remove the server in such a case.
+  #if(!defined($ret)) { OWServer_CloseDev($hash); }
+  return $ret;
+}
+
+#####################################
+sub
+OWServer_TimeoutChild($)
+{
+  my $pid= shift;
+  Log3 undef, 1, "OWServer: Terminated child $pid" if($pid && kill(9, $pid));
+}
+
+#####################################
+sub
+OWServer_Write($@)
+{
+  my ($hash,$path,$value)= @_;
+
+  return undef if($hash->{LAST_READ_FAILED});
+
+  return undef unless(defined($hash->{fhem}{owserver}));
+
+  return $hash->{fhem}{owserver}->write($path,$value);
+}
+
+#####################################
+sub
+OWServer_Dir($@)
+{
+  my ($hash,$path)= @_;
+
+  return undef if($hash->{LAST_READ_FAILED});
+
+  return undef unless(defined($hash->{fhem}{owserver}));
+
+  $path= ($path) ? $path : "/";
+  return $hash->{fhem}{owserver}->dir($path);
+}
+
+#####################################
+sub
+OWServer_Find($@)
+{
+  my ($hash,$slave)= @_;
+
+  return undef if($hash->{LAST_READ_FAILED});
+
+  return undef unless(defined($hash->{fhem}{owserver}));
+
+  my $owserver= $hash->{fhem}{owserver};
+  my $fulldir= $owserver->dir("/");
+  return undef unless(defined($fulldir));
+  my @dir= split(",", $fulldir);
+  my $path= undef;
+  for my $entry (@dir) {
+    $entry = substr($entry,1);
+    next if($entry !~ m/^bus.\d+/m);
+    my @busdir= split(",",$owserver->dir("/$entry"));
+    $path= (grep { m/$slave/i } @busdir) ? $entry : undef;
+    last if($path)
+  }
+  return $path;
+}
+
+#####################################
+sub
+OWServer_Autocreate($)
+{
+  my ($hash)= @_;
+  my $name = $hash->{NAME};
+
+  my $acdname= "";
+  foreach my $d (keys %defs) {
+    next if($defs{$d}{TYPE} ne "autocreate");
+    $acdname= $defs{$d}{NAME};
+    return undef if(AttrVal($acdname,"disable",undef));
+  }
+  return undef unless($acdname ne "");
+  
+  my $owserver= $hash->{fhem}{owserver};
+
+  my @dir= split(",", $owserver->dir("/"));
+  my @devices= grep { m/^\/[0-9a-f]{2}.[0-9a-f]{12}$/i } @dir;
+
+  my %defined = ();
+  foreach my $d (keys %defs) {
+    next if($defs{$d}{TYPE} !~ /^OW(Device|AD|ID|MULTI|COUNT|LCD|SWITCH|THERM)$/);
+    if(defined($defs{$d}{fhem}) && defined($defs{$d}{fhem}{address})) {
+      $defined{$defs{$d}{fhem}{address}} = $d; 
+    } elsif(defined($defs{$d}{OW_ID}) and defined($defs{$d}{OW_FAMILY})) {
+      $defined{"$defs{$d}{OW_FAMILY}.$defs{$d}{OW_ID}"} = $d;
+    }
+  }
+
+  my $created = 0;
+  for my $device (@devices) {
+    my $address= substr($device,1);
+    my $family= substr($address,0,2);
+    if(!exists $owfamily{$family}) {
+      Log3 $name, 2, "$name: Autocreate: unknown familycode '$family' found. Please report this!";
+      next;
+    } else {
+      my $type= $owserver->read($device . "/type");
+      my $owtype= $owfamily{$family};
+      if($owtype !~ m/$type/) {
+        Log3 $name, 2, "$name: Autocreate: type '$type' not defined in familycode '$family'. Please report this!";
+        next;
+      } elsif( defined($defined{$address}) ) {
+        Log3 $name, 5, "$name address '$address' already defined as '$defined{$address}'";
+        next;
+      } else {
+        my $id= substr($address,3);
+        my $devname= $type . "_" . $id;
+        Log3 $name, 5, "$name create new device '$devname' for address '$address'";
+        my $interval= ($family eq "81") ? "" : " 60";
+        my $define= "$devname OWDevice $address" . $interval;
+        my $cmdret;
+        $cmdret= CommandDefine(undef,$define);
+        if($cmdret) {
+          Log3 $name, 1, "$name: Autocreate: An error occurred while creating device for address '$address': $cmdret";
+        } else {
+          $created++;
+          $cmdret= CommandAttr(undef,"$devname room OWDevice");
+        }
+      }
+    }
+  }
+
+  CommandSave(undef,undef) if( $created && AttrVal($acdname, "autosave", 1 ) );
+
+  return undef;
+}
+
+#####################################
+sub
+OWServer_Get($@)
+{
+  my ($hash, @a) = @_;
+
+  my $name = $a[0];
+
+  return "$name: get needs at least one parameter" if(@a < 2);
+
+  my $cmd= $a[1];
+  #my $arg = ($a[2] ? $a[2] : "");
+  #my @args= @a; shift @args; shift @args;
+
+  my $owserver= $hash->{fhem}{owserver};
+
+  if($cmd eq "devices") {
+        my @dir= split(",", $owserver->dir("/"));
+        my @devices= grep { m/^\/[0-9a-f]{2}.[0-9a-f]{12}$/i } @dir;
+        my $ret;
+        for my $device (@devices) {
+          my $name= "";
+          my $address= substr($device,1);
+          my $type= $owserver->read($device . "/type");
+          foreach my $p (keys %defs) {
+             $name= concatc(", ", $name, $p) if($defs{$p}{TYPE} eq "OWDevice" and $defs{$p}{fhem}{address} eq $address);
+          }
+          $ret .= sprintf("%s %10s %s\n", $address, $type, $name);
+        }
+        return $ret;
+  } elsif($cmd eq "errors") {
+        my $path= "statistics/errors";
+        my @dir= split(",", $owserver->dir($path));
+        my $wide= (reverse sort { $a <=> $b } map { length($_) } @dir)[0];
+        $wide= $wide-length($path);
+        my $ret= "=> $path:\n";
+        for my $error (@dir) {
+          my $stat= $owserver->read("$path/$error");
+          my (undef, $str) = $error =~ m|^(.*[/\\])([^/\\]+?)$|;
+          $str =~ s/_/ /g;
+          $ret .= sprintf("%-*s %d\n",$wide,$str,($stat) ? $stat : 0);
+        }
+        return $ret;
+  } elsif(defined($gets{$cmd})) {
+        my $ret;
+        my $value= $owserver->read($cmd);
+        readingsSingleUpdate($hash,$cmd,$value,1);
+        return "$cmd => $value";
+
+  } else {
+        return "Unknown argument $cmd, choose one of devices ".join(" ", sort keys %gets);
+  }
+
+}
+
+#####################################
+sub
+OWServer_Set($@)
+{
+        my ($hash, @a) = @_;
+        my $name = $a[0];
+
+        # usage check
+        #my $usage= "Usage: set $name classdef <classname> <filename> OR set $name reopen";
+        my $usage= "Unknown argument $a[1], choose one of reopen ".join(" ", sort keys %sets);
+        return $usage if($a[1] ne "reopen" && !defined($sets{$a[1]}));
+
+        if((@a == 2) && ($a[1] eq "reopen")) {
+                OWServer_OpenDev($hash);
+                return undef;
+        } elsif(@a == 3) {
+          my $cmd= $a[1];
+          my $value= $a[2];
+          my $owserver= $hash->{fhem}{owserver};
+          my $ret= $owserver->write("/settings/$cmd",$value);
+          #return $ret if($ret);
+          readingsSingleUpdate($hash,"/settings/$cmd",$value,1);
+        }
+        return undef;
+
+}
+#####################################
+
+
+1;
+
+
+=pod
+=item device
+=item summary controls a One-Wire (1Wire) server instance
+=item summary_DE steuert eine Ausgabe eines One-Wire (1Wire) Servers
+=begin html
+
+<a name="OWServer"></a>
+<h3>OWServer</h3>
+<ul>
+  <br>
+  <a name="OWServerdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; OWServer &lt;protocol&gt;</code>
+    <br><br>
+
+    Defines a logical OWServer device. OWServer is the server component of the
+    <a href="http://owfs.org">1-Wire Filesystem</a>. It serves as abstraction layer
+    for any 1-wire devices on a host. &lt;protocol&gt; has
+    format &lt;hostname&gt;:&lt;port&gt;. For details see
+    <a href="http://owfs.org/index.php?page=owserver_protocol">owserver documentation</a>.
+    <p>
+    You need <a href="http://owfs.cvs.sourceforge.net/viewvc/owfs/owfs/module/ownet/perl5/OWNet/lib/OWNet.pm">OWNet.pm from owfs.org on Sourceforge</a>, which is normally deployed with FHEM. As at 2012-12-23 the OWNet module
+    on CPAN has an issue which renders it useless for remote connections.
+    <p>
+    The ow* version 2.9 packages provided with Debian Jessie in combination with OWNet.pm as deployed with FHEM have issues. 
+    For Debian Jessie please either unzip 
+    <a href="http://forum.fhem.de/index.php?action=dlattach;topic=12219.0;attach=2463">owfs_2.8p17-1_all.zip</a> and install
+    owserver, dependencies and what else you require with <code>dpkg -i &lt;package&gt;.deb</code> or use the latest OWNet.pm from Sourceforge. 
+    <p>
+    A typical working configuration file <code>/etc/owfs.conf</code> looks as follows:<p>
+    <code>
+      # server uses device /dev/onewire<br>
+      server: device = /dev/onewire<br>
+      # clients other than server use server<br>
+      ! server: server = localhost:4304<br>
+      # port<br>
+      server: port = 4304<br>
+      # owhttpd<br>
+      http: port = 2121<br>
+      # owftpd<br>
+      ftp: port = 2120<br>
+    </code>
+    <p>
+    The actual 1-wire devices are defined as <a href="#OWDevice">OWDevice</a> devices.
+    If <a href="#autocreate">autocreate</a> is enabled, all the devices found are created at
+    start of FHEM automatically.
+    <br><br>
+    This module is completely unrelated to the 1-wire modules with names all in uppercase.
+    <br><br>
+    Examples:
+    <ul>
+      <code>define myLocalOWServer OWServer localhost:4304</code><br>
+      <code>define myRemoteOWServer OWServer raspi:4304</code><br>
+    </ul>
+    <br><br>
+    Notice: if you get no devices add both <code>localhost</code> and the FQDN of your owserver as server directives
+    to the owserver configuration file
+    on the remote host.
+    <br><br>
+
+  </ul>
+
+  <a name="OWServerset"></a>
+  <b>Set</b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt;</code>
+    <br><br>
+    where <code>value</code> is one of<br><br>
+    <li><code>reopen</code><br>
+      Reopens the connection to the owserver.
+    </li>
+    <li>owserver (OWFS) specific settings:
+      <ul>
+        <li><code>timeout/directory</code></li>
+        <li><code>timeout/ftp</code></li>
+        <li><code>timeout/ha7</code></li>
+        <li><code>timeout/network</code></li>
+        <li><code>timeout/presence</code></li>
+        <li><code>timeout/serial</code></li>
+        <li><code>timeout/server</code></li>
+        <li><code>timeout/stable</code></li>
+        <li><code>timeout/uncached</code></li>
+        <li><code>timeout/usb</code></li>
+        <li><code>timeout/volatile</code></li>
+        <li><code>timeout/w1</code></li>
+        <li><code>units/pressure_scale</code></li>
+        <li><code>units/temperature_scale</code></li>
+      </ul>
+    </li>
+    For further informations have look on <a href="http://owfs.org/uploads/owserver.1.html#sect41">owserver manual</a>).
+    <br>
+  </ul>
+  <br><br>
+
+
+  <a name="OWServerget"></a>
+  <b>Get</b>
+  <ul>
+    <code>get &lt;name&gt; &lt;value&gt;</code>
+    <br><br>
+    where <code>value</code> is one of<br><br>
+    <li><code>devices</code><br>
+      Lists the addresses and types of all 1-wire devices provided by the owserver. Also shows
+      the corresponding <a href="#OWDevice">OWDevice</a> if one is defined for the respective 1-wire devices.
+    </li>
+    <li><code>errors</code><br>
+      List a view of error statistics.</li>
+    <li>owserver (OWFS) specific settings:
+      <ul>
+        <li><code>/settings/timeout/directory</code></li>
+        <li><code>/settings/timeout/ftp</code></li>
+        <li><code>/settings/timeout/ha7</code></li>
+        <li><code>/settings/timeout/network</code></li>
+        <li><code>/settings/timeout/presence</code></li>
+        <li><code>/settings/timeout/serial</code></li>
+        <li><code>/settings/timeout/server</code></li>
+        <li><code>/settings/timeout/stable</code></li>
+        <li><code>/settings/timeout/uncached</code></li>
+        <li><code>/settings/timeout/usb</code></li>
+        <li><code>/settings/timeout/volatile</code></li>
+        <li><code>/settings/timeout/w1</code></li>
+        <li><code>/settings/units/pressure_scale</code></li>
+        <li><code>/settings/units/temperature_scale</code></li>
+      </ul>
+    </li>
+    For further informations have look on <a href="http://owfs.org/uploads/owserver.1.html#sect41">owserver manual</a>).
+    <br>
+  </ul>
+  <br><br>
+
+
+  <a name="OWServerattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li>nonblocking<br>
+    Get all readings (OWServer / <a href="#OWDevice">OWDevice</a>) via a child process. This ensures, that FHEM
+    is not blocked during communicating with the owserver.<br>
+    Example:<br>
+    <code> attr &lt;name&gt; nonblocking 1</code>
+    </li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br><br>
+  Note: unset <code>nonblocking</code> if you experience lockups of FHEM.
+
+</ul>
+
+=end html
+=begin html_DE
+
+<a name="OWServer"></a>
+<h3>OWServer</h3>
+<ul>
+  <br>
+  <a name="OWServerdefine"></a>
+  <b>Definition</b>
+  <ul>
+    <code>define &lt;name&gt; OWServer &lt;protocol&gt;</code>
+    <br><br>
+
+    Definiert eine logische OWServer- Instanz. OWServer ist die Serverkomponente des
+    <a href="http://owfs.org">1-Wire Dateisystems</a>. Sie ermöglicht den Zugriff auf 
+    alle 1-Wire- Busteilnehmer eines Systems.<br><br>
+        &lt;protocol&gt; hat das Format &lt;hostname&gt;:&lt;port&gt;  Nähere Informationen dazu gibt es in der <a href="http://owfs.org/index.php?page=owserver_protocol">owserver Dokumentation</a>.
+        <br><br>
+    Voraussetzung innerhalb von FHEM ist das Modul <a href="http://owfs.cvs.sourceforge.net/viewvc/owfs/owfs/module/ownet/perl5/OWNet/lib/OWNet.pm">OWNet.pm von owfs.org</a>, welches bereits mit FHEM ausgeliefert wird. 
+        Das auf CPAN erhältliche OWNet- Modul beinhaltet seit dem 23.12.2012 einen Fehler, der es für Fernzugriffe unbrauchbar macht.<p>
+        Auf dem Computer, an dem der 1-Wire- Bus angeschlossen ist, muss die Software "owserver" installiert sein. Zusätzlich sollte auf diesem Rechner die Konfigurationsdatei "owfs.conf" eingesehen bzw. angepasst werden. <a href="http://www.fhemwiki.de/wiki/OWServer_%26_OWDevice#Tipps_und_Tricks"> Einen WIKI- Artikel dazu gibt es hier.</a>
+    <br><br>
+    Die ow*-Pakete in der Version 2.9 von Debian Jessie haben Probleme. Bitte entpacke f&uuml;r Debian Jessie entweder
+    <a href="http://forum.fhem.de/index.php?action=dlattach;topic=12219.0;attach=2463">owfs_2.8p17-1_all.zip</a> und installiere
+    owserver, Abh&auml;ngigkeiten und was Du sonst noch brauchst mit <code>dpkg -i &lt;package&gt;.deb</code>, oder benutze die neueste Version von OWNet.pm von Sourceforge.
+    <p>
+    Eine typische funktionierende Konfigurationsdatei <code>/etc/owfs.conf</code> sieht so aus:<p>
+    <code>
+      # server uses device /dev/onewire<br>
+      server: device = /dev/onewire<br>
+      # clients other than server use server<br>
+      ! server: server = localhost:4304<br>
+      # port<br>
+      server: port = 4304<br>
+      # owhttpd<br>
+      http: port = 2121<br>
+      # owftpd<br>
+      ftp: port = 2120<br>
+    </code>
+    <p>
+    Die vorhandenen 1-Wire- Busteilnehmer werden als <a href="#OWDevice">OWDevice</a> -Geräte definiert.
+    Wenn <a href="#autocreate">autocreate</a> aktiviert ist, werden beim Start von FHEM alle Geräte automatisch erkannt und eingerichtet.
+    <br><br>
+        <b>Achtung: Dieses Modul ist weder verwandt noch verwendbar mit den 1-Wire Modulen, deren Namen nur aus Großbuchstaben bestehen!</b>
+    <br><br>
+    Beispiele für die Einrichtung:
+    <ul>
+      <code>define myLocalOWServer OWServer localhost:4304</code><br>
+      <code>define myRemoteOWServer OWServer 192.168.1.100:4304</code><br>
+          <code>define myRemoteOWServer OWServer raspi:4304</code><br>
+    </ul>
+    <br>
+    Hinweis: Sollten keine Geräte erkannt werden, kann man versuchen in der owserver- Konfigurationsdatei (owfs.conf) zwei Servereinträge anzulegen:
+        Einen mit <code>localhost</code> und einen mit dem "FQDN", bzw. dem Hostnamen, oder der  IP-Adresse des Computers, auf dem die Software "owserver" läuft.
+    <br><br>
+
+  </ul>
+
+  <a name="OWServerset"></a>
+  <b>Set- Befehle</b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt;</code>
+    <br><br>
+    wobei <code>value</code> für einen der folgenden Befehle steht:<br><br>
+    <li><code>reopen</code><br>
+      Erneuert die Verbindung zum owserver.
+    </li>
+    <li>owserver (OWFS) -spezifische Einstellungen:
+      <ul>
+        <li><code>timeout/directory</code></li>
+        <li><code>timeout/ftp</code></li>
+        <li><code>timeout/ha7</code></li>
+        <li><code>timeout/network</code></li>
+        <li><code>timeout/presence</code></li>
+        <li><code>timeout/serial</code></li>
+        <li><code>timeout/server</code></li>
+        <li><code>timeout/stable</code></li>
+        <li><code>timeout/uncached</code></li>
+        <li><code>timeout/usb</code></li>
+        <li><code>timeout/volatile</code></li>
+        <li><code>timeout/w1</code></li>
+        <li><code>units/pressure_scale</code></li>
+        <li><code>units/temperature_scale</code></li>
+      </ul>
+    </li>
+    Nähere Informationen zu diesen Einstellungen gibt es im <a href="http://owfs.org/uploads/owserver.1.html#sect41">owserver- Manual</a>.
+    <br>
+  </ul>
+  <br><br>
+
+
+  <a name="OWServerget"></a>
+  <b>Get- Befehle</b>
+  <ul>
+    <code>get &lt;name&gt; &lt;value&gt;</code>
+    <br><br>
+    wobei <code>value</code> für einen der folgenden Befehle steht:<br><br>
+    <li><code>devices</code><br>
+      Gibt eine Liste der Adressen und Typen aller von owserver erkannten Geräte aus. Außerdem
+          werden die entsprechenden <a href="#OWDevice">OWDevice-</a> Namen angezeigt, soweit sie bereits definiert sind.
+    </li>
+    <li><code>errors</code><br>
+      Liefert eine Fehlerstatistik zurück.</li>
+    <li>owserver (OWFS) -spezifische Einstellungen:
+      <ul>
+        <li><code>/settings/timeout/directory</code></li>
+        <li><code>/settings/timeout/ftp</code></li>
+        <li><code>/settings/timeout/ha7</code></li>
+        <li><code>/settings/timeout/network</code></li>
+        <li><code>/settings/timeout/presence</code></li>
+        <li><code>/settings/timeout/serial</code></li>
+        <li><code>/settings/timeout/server</code></li>
+        <li><code>/settings/timeout/stable</code></li>
+        <li><code>/settings/timeout/uncached</code></li>
+        <li><code>/settings/timeout/usb</code></li>
+        <li><code>/settings/timeout/volatile</code></li>
+        <li><code>/settings/timeout/w1</code></li>
+        <li><code>/settings/units/pressure_scale</code></li>
+        <li><code>/settings/units/temperature_scale</code></li>
+      </ul>
+    </li>
+    Nähere Informationen zu diesen Einstellungen gibt es im <a href="http://owfs.org/uploads/owserver.1.html#sect41">owserver- Manual</a>.
+    <br>
+  </ul>
+  <p>
+
+  <a name="OWServerattr"></a>
+  <b>Attribute</b>
+  <ul>
+    <li>nonblocking<br>
+    Holt alle readings (OWServer / <a href="#OWDevice">OWDevice</a>) über einen Tochterprozess. Dieses Verfahren stellt sicher,
+    dass FHEM während der Kommunikation mit owserver nicht angehalten wird.<br>
+    Beispiel:<br>
+    <code> attr &lt;name&gt; nonblocking 1</code>
+    </li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+  Hinweis: Falls in FHEM trotzdem ungewöhnliche Stillstände auftreten, sollte das Attribut <code>nonblocking</code> wieder deaktiviert werden.<br>  
+
+</ul>
+
+=end html_DE
+=cut

ファイルの差分が大きいため隠しています
+ 2327 - 0
fhem/core/FHEM/10_RESIDENTS.pm


ファイルの差分が大きいため隠しています
+ 1716 - 0
fhem/core/FHEM/10_SOMFY.pm


+ 534 - 0
fhem/core/FHEM/10_UNIRoll.pm

@@ -0,0 +1,534 @@
+###################LoTT Uniroll###################
+# First release by D. Fuchs and rudolfkoenig
+# improved by C_Herrmann
+# $Id: 10_UNIRoll.pm 12819 2016-12-18 14:27:48Z C_Herrmann $
+# 
+# UNIRoll:no synchronisation, the message protocoll begins directly with datas
+# group address  16 Bit like an housecode
+# channel address 4 Bit up to 16 devices
+# command         4 Bit up: E(1110), stop: D(1101), down: B(1011)
+# end off         1 Bit, zero or one it doesnot matter
+# whole length   25 Bit
+#time intervall:
+#Bit-digit 0     high ca. 1,6 ms        equal 100(h64) 16us steps
+#                low  ca. 0,576 ms             36(h24)
+#Bit-digit 1     high ca. 0,576 ms             36(h24)
+#                low  ca. 1,6 ms              100(h64)
+#timespace ca.   100 - 170 ms
+#binary : 1010 1011 1100 1101 1110 1
+#hexa:    a    b    c    d    e    (an additional one bit)
+#the message is sent with the general cul-command: G
+#G0031A364242464abcd6e8   :  00 synchbits 3 databytes, 1 databit, HHLLLLHH, data
+
+package main;
+
+use strict;
+use warnings;
+
+# Stings für Anfang und Ende des Raw-Kommandos
+# Die Zeiten für die Impulslänge wurden anhand meiner 1-Kanal-FB etwas angepasst.
+# Sie können durch Veränderung der letzten 4 Hex-Bytes in $rawpre geändert werden.
+# siehe auch: http://culfw.de/commandref.html#cmd_G
+# Seit der Entwickler-Version 1.58 vom 29.03.2014 gibt es einen UNIRoll-Send-Befehl "U".
+my $rawpre_old = "G0036E368232368";  # geänderte Timings und 1 Bit am Ende
+                                     # culfw bis einschl. 1.58
+my $rawpre = "U";                    # Nutzt UNIRoll-Send ab FW 1.58
+my $rawpost_old = "80";              # ein 1-Bit senden, culfw bis einschl. 1.58
+my $rawpost = "";                    # ein 1-Bit wird mit aktueller culfw automatisch gesendt
+my $rPos;
+my $tm;
+
+my %codes = (
+  "e" => "up",       #1110 e
+  "d" => "stop",     #1101 d
+  "b" => "down",     #1011 b
+  "a" => "pos",      # Pseudobefehl: gezielt eine Position anfahren
+);
+
+use vars qw(%UNIRoll_c2b);   # Peter would like to access it from outside
+
+my $UNIRoll_simple = "up stop down pos";
+
+my %models = (
+    R_23700 => 'simple',
+    dummySimple => 'simple',
+);
+
+#############################
+sub
+UNIRoll_Initialize($)
+{
+  my ($hash) = @_;
+
+  foreach my $k (keys %codes) {
+    $UNIRoll_c2b{$codes{$k}} = $k;   # c2b liest das allgmeine Array der Gerätegruppe
+  }
+# print "UNIRoll_Initialize \n";
+  $hash->{Match}     = "^(G|U).*";
+  $hash->{SetFn}     = "UNIRoll_Set";
+#  $hash->{StateFn}   = "UNIRoll_SetState";
+  $hash->{DefFn}     = "UNIRoll_Define";
+  $hash->{UndefFn}   = "UNIRoll_Undef";
+  $hash->{ParseFn}   = "UNIRoll_Parse";
+  $hash->{AttrFn}    = "UNIRoll_Attr";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ".
+                        "ignore:1,0 showtime:1,0 ".
+                        "rMin:slider,0,1,120 rMax:slider,0,1,120 ".
+                        "rPos:slider,0,1,120 useRolloPos:1,0 " .
+                        "sendStopBeforeCmd:1,0,2,3 " .
+                        "model:".join(",", sort keys %models);
+}
+## Neues Attribut sendStopBeforeCmd hinzugefügt. Default ist 1 - Stop wird gesendet.
+## Bei 0 wird kein Stop-Befehl vor dem auf/ab-Befehl gesendet.
+## Bei 2 wird Stop nur vor "auf" und bei 3 nur vor "ab" gesendet.
+## Hier ging der auf-Befehl immer zuverlässig. Ab funktionierte
+## nur sporadisch, insbesondere wenn es von einem "at" gesendet wurde.
+
+#####################################
+# sub
+# UNIRoll_SetState($$$$)   # 4 Skalare Parameter
+# {
+#  return undef;
+# }
+
+###################################
+sub
+UNIRoll_Set($@)
+{
+  my ($hash, @a) = @_; # Eingabewerte nach define name typ devicecode channelcode
+  my $ret = undef;
+  my $na = int(@a);    #na Anzahl Felder in a
+ # print "UNIRoll_Set \n";
+  return "no set value specified" if($na < 2 || $na > 3);
+  my $c = $UNIRoll_c2b{$a[1]}; # Wert des Kommandos: up stop down pos
+  my $name = $a[0];            # Gerätename
+  $tm = 0 if(defined($c));     # optionaler Zeitwert
+  if($na == 3) {
+    $tm = $a[2];
+    return "Argument for <time> must be a number" if($tm !~ m/^\d*\.?\d*$/);
+  }
+  my $tPos = $tm;
+  if(!defined($c)) {
+    # Model specific set arguments
+    my $mt = AttrVal($name, "model", undef);
+    return "Unknown argument $a[1], choose one of $UNIRoll_simple"
+            if($mt && $mt eq "simple");
+    return "Unknown argument $a[1], choose one of " .
+            join(" ", sort keys %UNIRoll_c2b);
+  }
+# RolloPos ausführen, wenn aktiviert
+  if(AttrVal($name, "useRolloPos", "0") eq "1") {
+    ($ret, $c, $tPos) = UNIRoll_RolloPos($hash, $name, $c, $tPos, $a[1]);
+	return $ret if(defined($ret) || !defined($c));
+  } else {
+    return "Please set useRolloPos to 1 to use pos commands with $name." if($c eq "a");
+  }
+  my $v = join(" ", @a);
+  Log3 $name, 3, "UNIRoll set $v";
+  (undef, $v) = split(" ", $v, 2);  # Not interested in the name...
+
+# CUL-Kommandos ermitteln und Sendestrings anpassen
+  my $culcmds = $hash->{IODev}->{CMDS}; # BCFiAZEGMKURTVWXefmltux
+  if(!defined($culcmds) || $culcmds !~ m/U/) {
+    $rawpre = $rawpre_old;
+    $rawpost = $rawpost_old;
+  }
+
+# G0030A364242464abcd6e8   :  00 Synchbits 3 Datenbytes, 1 Datenbit, HHLLLLHH, Daten
+# Damit kein Befehl einen zufälligen Betrieb stoppt 
+# vorher ein gezielter Stopp Befehl
+# Abschaltbar mit sendStopBeforeCmd 0, 2, 3
+
+  my $stop = "d";
+  my $sendstop = AttrVal($name, "sendStopBeforeCmd", "1");
+  if($sendstop eq "1" || ($sendstop eq "2" && $c eq "e") || ($sendstop eq "3" && $c eq "b") || $c eq $stop) {
+    IOWrite($hash, "",$rawpre.$hash->{XMIT}.$hash->{BTN}.$stop.$rawpost);
+    sleep(0.1);
+  }
+  IOWrite($hash, "",$rawpre.$hash->{XMIT}.$hash->{BTN}.$c.$rawpost) if($c ne $stop); # Auf-/Ab-Befehl ausführen
+
+# XMIT: Gerätegruppe, BTN: Kanalnummer, c: Commando
+
+# Zeit für up/down pausieren, dann Stop-Befehl ausführen und Reading aktualisieren
+  InternalTimer(gettimeofday()+$tPos,"UNIRoll_Timer",$hash,0) if($c ne $stop);
+  sleep(0.3);
+
+  ##########################
+  # Look for all devices with the same code, and set state, timestamp
+  my $code = "$hash->{XMIT} $hash->{BTN}";
+  my $tn = TimeNow();
+  my $defptr = $modules{UNIRoll}{defptr};
+  foreach my $n (keys %{ $defptr->{$code} }) {
+    my $lh = $defptr->{$code}{$n};
+    $lh->{CHANGED}[0] = $v;
+    $lh->{STATE} = $v;
+    $lh->{READINGS}{state}{TIME} = $tn;
+    $lh->{READINGS}{state}{VAL} = $v;
+    my $lhname = $lh->{NAME};
+    if($name ne $lhname) {
+      DoTrigger($lhname, undef);
+    }
+  }
+
+  return $ret;
+}
+
+#############################
+sub
+UNIRoll_Define($$)
+
+# Gerät anmelden hash: Hash-adresse, def: Eingabe bei define .....
+# Hauscode, Kanalnummer aufbereiten prüfen
+ {
+  my ($hash, $def) = @_;
+#  my $name = $a[0];
+
+  my @a = split("[ \t][ \t]*", $def);
+  my $u = "wrong syntax: define <name> UNIRoll device adress " .
+                        "addr [fg addr] [lm addr] [gm FF]";
+# print "UNIRoll_Define \n";
+
+  return $u if(int(@a) < 4);
+  return "Define $a[0]: wrong device address format: specify a 4 digit hex value "
+        if( ($a[2] !~ m/^[a-f0-9]{4}$/i) );
+
+  return "Define $a[0]: wrong chanal format: specify a 1 digit hex value " 
+        if( ($a[3] !~ m/^[a-f0-9]{1}$/i) );
+
+  my $devcode = $a[2];
+  my $chacode = $a[3];
+
+  $hash->{XMIT} = lc($devcode);             # hex Kleinschreibung ?
+  $hash->{BTN}  = lc($chacode);
+
+# Gerätedaten aufbauen, 
+# defptr: device pointer global
+
+  my $code = lc("$devcode $chacode");              #lc lowercase Kleinschreibung
+  my $ncode = 1;                                   #?
+  my $name = $a[0];                                #Gerätename
+  $hash->{CODE}{$ncode++} = $code;
+  $modules{UNIRoll}{defptr}{$code}{$name}   = $hash;
+
+# print "Test IoPort $hash def $def code $code.\n";
+
+  $attr{$name}{"webCmd"} = "up:stop:down";
+
+  AssignIoPort($hash);  # Gerät anmelden
+}
+
+#############################
+sub
+UNIRoll_Undef($$)
+{
+  my ($hash, $name) = @_;
+
+  foreach my $c (keys %{ $hash->{CODE} } ) {
+    $c = $hash->{CODE}{$c};
+# print "UNIRoll_Undef \n";
+    # As after a rename the $name my be different from the $defptr{$c}{$n}
+    # we look for the hash.
+    foreach my $dname (keys %{ $modules{UNIRoll}{defptr}{$c} }) {
+      delete($modules{UNIRoll}{defptr}{$c}{$dname})
+        if($modules{UNIRoll}{defptr}{$c}{$dname} == $hash);
+    }
+  }
+  return undef;
+}
+
+#############################
+sub
+UNIRoll_Parse($$)
+{
+ #  print "UNIRoll_Parse \n";
+}
+
+#############################
+sub
+UNIRoll_Attr(@)
+{
+  return if(!$init_done);  # AttrFn erst nach Initialisierung ausführen
+  my ($cmd,$name,$aName,$aVal) = @_;
+  if($aName eq "useRolloPos") {
+    if(defined($aVal) && $aVal == 1) {
+      my $st = ReadingsVal($name, "state", "");
+      return "Please set $name to the topmost position before activating RolloPos!"
+          if($st ne "up");
+      CommandSetReading(undef, "$name oldstate $st 0");
+      CommandSetReading(undef, "$name oldPos 0");
+      $attr{$name}{"useRolloPos"} = "1";
+      $attr{$name}{"rPos"} = 0;
+      $attr{$name}{"rMin"} = 0 if(!defined(AttrVal($name, "rMin", undef)));
+      $attr{$name}{"rMax"} = 0 if(!defined(AttrVal($name, "rMax", undef)));
+      return "Please set time for min and max position for $name!" if($attr{$name}{"rMax"} eq "0");
+    } else {
+      $attr{$name}{"useRolloPos"} = "0";
+      CommandDeleteReading(undef, "$name old.*");
+    }
+  }
+  return "This attribute cannot be deletet if useRolloPos is activated"
+        if(($aName eq "rMax" || $aName eq "rMin") && $cmd eq "del" && AttrVal($name, "useRolloPos", undef) == 1);
+        
+  return "This attribute is read-only and must not be changed!"
+        if($aName eq "rPos" && AttrVal($name,"useRolloPos","") eq "1");
+}
+
+#############################
+sub
+UNIRoll_Timer($)
+{
+  my $hash = shift;
+  my $stop = "d";
+  IOWrite($hash, "",$rawpre.$hash->{XMIT}.$hash->{BTN}.$stop.$rawpost) if($tm ne "0");
+  readingsSingleUpdate($hash, "oldPos", $rPos, 1);
+}
+
+#############################
+sub
+UNIRoll_RolloPos($$$$$)
+{
+# RolloPos - Position Speichern und Positionsbefehle in up/down umwandeln
+# Variablen einlesen
+    my($hash, $name, $c, $tPos, $nstate) = @_;
+	my $ret;
+	my $rMax = AttrVal($name, "rMax", "0");
+    my $rMin = AttrVal($name, "rMin", "0");
+    return ("Please check rMin and rMax values in attributes", undef, undef) if ($rMax eq "0" || $rMax <= $rMin);
+    $rPos = AttrVal($name, "rPos", "0");
+    my $oldPos = ReadingsVal($name, "oldPos", "0");
+
+# Zeit und Fahrtrichtung des letzten Befehls ermitteln, falls neuer Befehl vor
+# Beendigung der letzten Fahrt abgesetzt wurde, nur Stop zulassen.
+# rPos entsprechend anpassen!
+    my $tdiff = int(gettimeofday()) - time_str2num(ReadingsTimestamp($name,"state",""));  # Zeit seit letztem Befehl in Sekunden
+    my ($lastcmd, $lasttime) = split(" ", ReadingsVal($name,"oldstate",""));  # letzter Befehl z.B. down 9
+    if(!defined($lastcmd)) {
+      my $nst = ReadingsVal($name, "state", "0");
+      $lasttime = 0;
+      readingsSingleUpdate($hash, "oldstate", "$nst 0", 1 );
+    }
+    if($lasttime > $tdiff) {  # wenn letzter Befehl noch nicht abgeschlossen
+      return (undef, undef, undef) if($c ne "d");  # wenn kein Stop -> return
+      RemoveInternalTimer($hash);
+      $rPos = $oldPos + $tdiff if($lastcmd eq "down");
+      $rPos = $oldPos - $tdiff if($lastcmd eq "up");
+      $oldPos = $rPos;
+      goto DOCMD;
+    }
+# Befehl ohne Zeitangabe
+    if($tm == "0") {
+      if($c eq "b") { # ab
+        $tPos = $rMax - $rPos;
+        $rPos = $rMax;
+      } elsif($c eq "e") { # auf
+        $tPos = $rPos - $rMin;
+        $rPos = $rMin;
+      }
+      goto DOCMD;
+    }
+# Befehl mit Zeitangabe
+    if($c eq "b") { # ab
+      return (undef, undef, undef) if($rPos >= $rMax);
+      if($tm >= $rMax - $rPos) {
+        $tPos = $rMax - $rPos;
+        $rPos = $rMax;
+        $tm = "0";
+      } else {
+        $rPos = $rPos + $tm;
+      }
+    } elsif($c eq "e") { # auf
+      return (undef, undef, undef) if($rPos <= $rMin);
+      if($tm > $rPos) {
+        $tPos = $rPos - $rMin;
+        $rPos = $rMin;
+        $tm = "0";
+      } else {
+        $rPos = $rPos - $tm;
+	  }
+    } elsif($c eq "a") { # pos
+      return (undef, undef, undef) if($rPos eq $tm);
+      return ("Invalid position $tm for $name. Maximum value is $rMax.", undef, undef) if($tm > $rMax);
+      if($rPos > $tm) { # neue Position kleiner
+        $c = "e";
+        $tPos = $rPos - $tm;
+      } elsif($rPos < $tm) { # neue Position größer
+        $c = "b";
+        $tPos = $tm - $rPos;
+      }
+      $rPos = $tm;
+      $tm = $tPos;
+    }
+DOCMD:
+    $attr{$name}{"rPos"} = $rPos;
+#    my $nstate = $a[1];
+    $nstate = "down" if($c eq "b");
+    $nstate = "up" if($c eq "e");
+    $nstate = "$nstate $tPos";
+### state ändern!
+    readingsBeginUpdate($hash);
+    readingsBulkUpdate($hash, "state", $nstate, 1 );
+    readingsBulkUpdate($hash, "oldPos", $oldPos, 1 );
+    readingsBulkUpdate($hash, "oldstate", $nstate, 1 );
+    readingsEndUpdate($hash, 1);
+    return (undef, $c, $tPos);
+  }
+# Ende RolloPos
+
+1;
+
+
+=pod
+=item device
+=item summary	controls UNIRoll devices with wireless extension
+=item summary_DE	Steuert UNIRoll-Gurtwickler mit Fernbedienungs-Modul
+
+=begin html_DE
+
+<a name="UNIRoll"></a>
+<h3>UNIRoll</h3>
+Deutsche Version der Doku nicht vorhanden. Englische Version unter 
+
+ <a href='http://fhem.de/commandref.html#<UNIRoll>'>UNIRoll</a> &nbsp;
+
+=end html_DE
+
+=begin html
+
+<a name="UNIRoll"></a>
+<h3>UNIRoll</h3>
+<ul>
+  The protocol is used by the Lott UNIROLL R-23700 reciever. The radio
+  (868.35 MHz) messages are either received through an <a href="#FHZ">FHZ</a>
+  or an <a href="#CUL">CUL</a> device, so this must be defined first.
+  Recieving sender messages is not integrated jet.
+  The CUL has to allow working with zero synchbits at the beginning of a raw-message.
+  This is possible with culfw 1.49 or higher.
+  <br><br>
+
+  <a name="UNIRolldefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; UNIRoll &lt;devicegroup&gt; &lt;deviceaddress&gt; </code>
+    <br><br>
+
+   The values of devicegroup address (similar to the housecode) and device address (button)
+   has to be defined as hexadecimal value.
+   There is no master or group code integrated.
+   <br>
+
+   <ul>
+   <li><code>&lt;devicecode&gt;</code> is a 4 digit hex number,
+     corresponding to the housecode address.</li>
+   <li><code>&lt;channel&gt;</code> is a 1 digit hex number,
+     corresponding to a button of the transmitter.</li>
+   </ul>
+   <br>
+
+    Example:
+    <ul>
+      <code>define roll UNIRoll 7777 0</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="UNIRollset"></a>
+  <b>Set </b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt]</code>
+    <br><br>
+    where <code>value</code> is one of:<br>
+    <pre>
+    up
+    stop
+    down
+    pos  (The attribute useRolloPos has to be set to 1 to use this.)
+    [&lt;time&gt] in seconds for up, down or pos
+    </pre>
+    Examples:
+    <ul>
+      <code>set roll up</code><br>
+      <code>set roll up 10</code><br>
+      <code>set roll1,roll2,roll3 up</code><br>
+      <code>set roll1-roll3 up</code><br>
+    </ul>
+    <br></ul>
+
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="UNIRollattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <a name="IODev"></a>
+    <li>IODev<br>
+        Set the IO or physical device which should be used for sending signals
+        for this "logical" device. An example for the physical device is an FHZ
+        or a CUL. The device will not work without this entry.</li><br>
+
+    <a name="eventMap"></a>
+    <li>eventMap<br>
+        Replace event names and set arguments. The value of this attribute
+        consists of a list of space separated values, each value is a colon
+        separated pair. The first part specifies the "old" value, the second
+        the new/desired value. If the first character is slash(/) or komma(,)
+        then split not by space but by this character, enabling to embed spaces.<br><br>
+        Examples:<ul><code>
+        attr device eventMap up:open down:closed<br>
+        set device open
+        </code></ul>
+        </li><br>
+
+    <li><a href="#showtime">showtime</a></li><br>
+
+    <a name="sendStopBeforeCmd"></a>
+    <li>sendStopBeforeCmd &lt;value&gt;<br>
+        Before any up/down-command a stop-command will be sent to stop a random
+        operation. This might cause failure in some situations. This attribute
+        can be used to switch off the stop-command by setting it to these values.<br><br>
+        where <code>value</code> is one of:<br>
+    <pre>
+        1 - send always stop (default)
+        0 - send no stop
+        2 - send stop only before up
+        3 - send stop only before down
+        </pre></li>
+
+    <a name="useRolloPos"></a>
+    <li>useRolloPos &lt;value&gt;<br>
+        The position of each device can be stored. By this it is possible to move from
+        any position to any other position. As this feature is software-based, a
+        manual operation will not be recognized. To set the device into a definite
+        state, a up or down command will reset the counter for the position.<br><br>
+        where <code>value</code> is one of:<br>
+    <pre>
+        1 - RolloPos will be used
+        0 - RolloPos is not used (default)
+        </pre><br>
+        These attributes will be created automatical if useRolloPos is set to 1.
+        They will not be deleted, if the value is set to 0 or the attribut is deleted.
+    <pre>
+        rMin - Time in seconds for the topmost position
+        rMax - Time in seconds until the device is fully closed
+        rPos - This is an internal value and must not be changed!
+        </pre></li>
+
+    <a name="model"></a>
+    <li>model<br>
+        The model attribute denotes the model type of the device.
+        The attributes will (currently) not be used by the fhem.pl directly.
+        It can be used by e.g. external programs or web interfaces to
+        distinguish classes of devices and send the appropriate commands.
+        The spelling of the model names are as quoted on the printed
+        documentation which comes which each device. This name is used
+        without blanks in all lower-case letters. Valid characters should be
+        <code>a-z 0-9</code> and <code>-</code> (dash),
+        other characters should be ommited. Here is a list of "official"
+        devices:<br><br>
+
+          <b>Receiver/Actor</b>: there is only one reciever: R_23700
+    </li><br>
+
+  </ul>
+  <br>
+
+
+</ul>

ファイルの差分が大きいため隠しています
+ 6337 - 0
fhem/core/FHEM/10_ZWave.pm


ファイルの差分が大きいため隠しています
+ 1030 - 0
fhem/core/FHEM/10_pilight_ctrl.pm


ファイルの差分が大きいため隠しています
+ 1399 - 0
fhem/core/FHEM/11_FHT.pm


+ 293 - 0
fhem/core/FHEM/11_FHT8V.pm

@@ -0,0 +1,293 @@
+#############################################
+# $Id: 11_FHT8V.pm 11984 2016-08-19 12:47:50Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+
+sub
+FHT8V_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{DefFn}     = "FHT8V_Define";
+  $hash->{SetFn}     = "FHT8V_Set";
+  $hash->{GetFn}     = "FHT8V_Get";
+  $hash->{AttrList}  = "IODev dummy:1,0 ignore:1,0 ".
+                         $readingFnAttributes;
+}
+
+#############################
+sub
+FHT8V_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+  my $n = $a[0];
+
+  return "wrong syntax: define <name> FHT8V housecode [IODev|FHTID]" if(@a < 3);
+  return "wrong housecode format: specify a 4 digit hex value "
+  		if(($a[2] !~ m/^[a-f0-9]{4}$/i));
+
+  my $fhtid;
+  if(@a > 3 && $defs{$a[3]}) {
+    $hash->{IODev} = $defs{$a[3]};
+
+  } else {
+    AssignIoPort($hash);
+    $fhtid = $a[3] if($a[3]);
+  }
+
+  return "$n: No IODev found" if(!$hash->{IODev});
+  $fhtid = $hash->{IODev}->{FHTID} if(!$fhtid);
+
+  return "$n: Wrong IODev $hash->{IODev}{NAME}, has no FHTID" if(!$fhtid);
+
+  #####################
+  # Check if the address corresponds to the CUL
+  my $ioaddr = hex($fhtid);
+  my $myaddr = hex($a[2]);
+  my ($io1, $io0) = (int($ioaddr/255), $ioaddr % 256);
+  my ($my1, $my0) = (int($myaddr/255), $myaddr % 256);
+  if($my1 < $io1 || $my1 > $io1+7 || $io0 != $my0) {
+    my $vals = "";
+    for(my $m = 0; $m <= 7; $m++) {
+      $vals .= sprintf(" %2x%2x", $io1+$m, $io0);
+    }
+    return sprintf("Wrong housecode: must be one of$vals");
+  }
+
+  $hash->{addr} = uc($a[2]);
+  $hash->{idx}  = sprintf("%02X", $my1-$io1);
+  $hash->{STATE} = "defined";
+  return "";
+}
+
+
+sub
+FHT8V_Set($@)
+{
+  my ($hash, @a) = @_;
+  my $n = $hash->{NAME};
+
+  return "Need a parameter for set" if(@a < 2);
+  my $arg = $a[1];
+
+  if($arg eq "valve" ) {
+    return "Set valve needs a numeric parameter between 0 and 100"
+        if(@a != 3 || $a[2] !~ m/^\d+$/ || $a[2] < 0 || $a[2] > 100);
+    Log3 $n, 3, "FHT8V set $n $arg $a[2]";
+    $hash->{STATE} = sprintf("%d %%", $a[2]);
+    readingsSingleUpdate($hash, "valve", $a[2], 1);
+    IOWrite($hash, "", sprintf("T%s0026%02X", $hash->{addr}, $a[2]*2.55));
+
+  } elsif ($arg eq "pair" ) {
+    Log3 $n, 3, "FHT8V set $n $arg";
+    IOWrite($hash, "", sprintf("T%s002f00", $hash->{addr}));
+
+  } elsif ($arg eq "decalc" ) {
+    Log3 $n, 3, "FHT8V set $n $arg";
+    $hash->{STATE} = "lime-protection";
+    IOWrite($hash, "", sprintf("T%s000A00", $hash->{addr}));
+
+  } else {
+    return "Unknown argument $a[1], choose one of valve pair decalc"
+
+  }
+  return "";
+
+}
+
+sub
+FHT8V_Get($@)
+{
+  my ($hash, @a) = @_;
+  my $n = $hash->{NAME};
+
+  return "Need a parameter for get" if(@a < 2);
+  my $arg = $a[1];
+
+  if($arg eq "valve" ) {
+    my $io = $hash->{IODev};
+    my $msg = CallFn($io->{NAME}, "GetFn", $io, (" ", "raw", "T10"));
+    my $idx = $hash->{idx};
+    return int(hex($1)/2.55) if($msg =~ m/$idx:26(..)/);
+    return "N/A";
+
+  }
+  return "Unknown argument $a[1], choose one of valve"
+}
+
+
+1;
+
+=pod
+=item summary    module for the FHT8v controlled directly by a culfw device
+=item summary_DE Anbindung von FHT8v Ventilen &uuml;ber ein culfw Ger&auml;t
+=begin html
+
+<a name="FHT8V"></a>
+<h3>FHT8V</h3>
+<ul>
+  Fhem can directly control FHT8V type valves via a <a href="#CUL">CUL</a>
+  device without an intermediate FHT. This paragraph documents one of the
+  building blocks, the other is the <a href="#PID">PID</a> device.
+  <br>
+  <br>
+
+  <a name="FHT8Vdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FHT8V &lt;housecode&gt; [IODev|FHTID]</code>
+    <br><br>
+
+    <code>&lt;housecode&gt;</code> is a four digit hex number,
+    and must have the following relation to the housecode of the corresponding CUL
+    device:
+    <ul>given the CUL housecode as AABB, then this housecode must be
+    of the form CCBB, where CC is greater or equal to AA, but less then AA+8.
+    </ul>
+    This form is chosen so that the CUL can update all FHT8V valve states
+    within 2 minutes.
+    <br>
+    <br>
+    <code>&lt;IODev&gt;</code> must be specified if the last defined CUL device
+    is not the one to use. Usually this is done voa the <a
+    href="#IODev">IODev</a> attribute, but as the address checked is performed
+    at the definition, we must use an exception here.<br>
+
+    As an alternative you can specify the FHTID of the assigned IODev device
+    (instead of the IODev itself), this method is needed if you are using FHT8V
+    through FHEM2FHEM.
+    <br>
+
+    Examples:
+    <ul>
+      <code>define wz FHT8V 3232</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="FHT8Vset"></a>
+  <b>Set </b>
+  <ul>
+      <li>set &lt;name&gt; valve &lt;value;&gt;<br>
+        Set the valve to the given value (in percent, from 0 to 100).
+        </li>
+      <li>set &lt;name&gt; pair<br>
+        Pair the valve with the CUL.
+        </li>
+      <li>set &lt;name&gt; decalc<br>
+        Start a decalcifying cycle on the given valve
+        </li>
+  </ul>
+  <br>
+
+  <a name="FHT8Vget"></a>
+  <b>Get </b>
+  <ul>
+      <li>get &lt;name&gt; valve<br>
+      Read back the valve position from the CUL FHT buffer, and convert it to percent (from 0 to 100).
+      </li>
+  </ul>
+  <br>
+
+  <a name="FHT8Vattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#dummy">dummy</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="FHT8V"></a>
+<h3>FHT8V</h3>
+<ul>
+   
+  Fhem kann die Ventile vom Typ FHT8V durch einen <a href="#CUL">CUL</a> 
+  direkt, ohne zwischengeschalteten FHT, ansteuern. Dieser Abschnitt 
+  beschreibt einen der Bausteine, der andere ist das <a href="#PID">PID</a> Device.
+  <br>
+  <br>
+
+  <a name="FHT8Vdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; FHT8V &lt;Hauscode&gt; [IODev|FHTID]</code>
+    <br><br>
+
+    <code>&lt;Hauscode&gt;</code> ist eine vierstellige hexadezimale Zahl, die
+    folgende Beziehung zum zust&auml;ndigen CUL-Device aufweisen muss:
+
+    <ul>Bei gegebenem Hauscode des CUL als AABB muss dieser Hauscode die Form CCBB 
+    haben, wobei CC gr&ouml;&szlig;er oder gleich AA, aber kleiner AA+8 sein muss.
+    </ul>
+    
+    Diese Form wurde gew&auml;hlt, damit der CUL alle FHT8V-Ventilstellungen
+    innerhalb von zwei Minuten aktualisieren kann.  <br><br>
+
+    <code>&lt;IODev&gt;</code> mu&szlig; angegeben werden, wenn der als letzter
+    definierte CUL nicht der zust&auml;ndige ist. Normalerweise wird dies mit
+    dem <a href="#IODev">IODev</a>-Attribut gesetzt, da die
+    &Uuml;berpr&uuml;fung der Adresse aber w&auml;hrend der Definition erfolgt,
+    brauchen wir hier eine Ausnahme.  <br>
+
+    Als Alternative kann man die FHTID des zust&auml;ndigen IODev-Ger&auml;tes
+    (anstelle des IODev selbst) setzen. Diese Methode ist n&ouml;tig, wenn man
+    FHT8V &uuml;ber FHEM2FHEM betreibt.  <br>
+
+    Beispiel:
+    <ul>
+      <code>define wz FHT8V 3232</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="FHT8Vset"></a>
+  <b>Set </b>
+  <ul>
+      <li>set &lt;name&gt; valve &lt;Wert&gt;<br>
+        &Ouml;ffnet das Ventil auf den angegebenen Wert (in Prozent, von 0 bis 100).
+        </li>
+      <li>set &lt;name&gt; pair<br>
+        Verbindet das Ventil mit dem CUL.
+        </li>
+      <li>set &lt;name&gt; decalc<br>
+        Startet einen Entkalkungslauf des angegebenen Ventils.
+        </li>
+  </ul>
+  <br>
+
+  <a name="FHT8Vget"></a>
+  <b>Get </b>
+  <ul>
+      <li>get &lt;name&gt; valve<br>
+      Liest die Ventil&ouml;ffnung aus dem FHT-Puffer des CUL und wandelt sie
+      in Prozent (von 0 bis 100) um.
+      </li>
+  </ul>
+  <br>
+
+  <a name="FHT8Vattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#dummy">dummy</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+</ul>
+
+=end html_DE
+
+=cut

ファイルの差分が大きいため隠しています
+ 1065 - 0
fhem/core/FHEM/11_OWDevice.pm


+ 395 - 0
fhem/core/FHEM/12_HMS.pm

@@ -0,0 +1,395 @@
+##############################################
+# $Id: 12_HMS.pm 11984 2016-08-19 12:47:50Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+
+my %codes = (
+  "0" => "HMS100TF",
+  "1" => "HMS100T",
+  "2" => "HMS100WD",
+  "3" => "RM100-2",
+  "4" => "HMS100TFK", # Depending on the onboard jumper it is 4 or 5
+  "5" => "HMS100TFK",
+  "6" => "HMS100MG",
+  "8" => "HMS100CO",
+  "e" => "HMS100FIT",
+);
+
+
+#####################################
+sub
+HMS_Initialize($)
+{
+  my ($hash) = @_;
+
+#                        810e047e0510a001473a000000120233 HMS100TF
+#                        810e04b90511a0018e63000001100000 HMS100T
+#                        810e04e80212a001ec46000001000000 HMS100WD
+#                        810e04d70213a001b16d000003000000 RM100-2
+#                        810e047f0214a001a81f000001000000 HMS100TFK
+#                        810e048f0295a0010155000001000000 HMS100TFK (jumper)
+#                        810e04330216a001b4c5000001000000 HMS100MG
+#                        810e04210218a00186e0000000000000 HMS100CO
+#                        810e0448029ea00132d5000000000000 FI-Trenner
+
+  $hash->{Match}     = "^810e04....(1|5|9).a001";
+  $hash->{DefFn}     = "HMS_Define";
+  $hash->{UndefFn}   = "HMS_Undef";
+  $hash->{ParseFn}   = "HMS_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:0,1 showtime:0,1 model:hms100-t,hms100-tf,hms100-wd,hms100-mg,hms100-tfk,rm100-2,hms100-co,hms100-fit ignore:0,1 $readingFnAttributes";
+  $hash->{AutoCreate}= {
+      "HMS100TFK_.*" => 
+        { GPLOT => "fht80tf:Contact,", FILTER => "%NAME" },
+      "HMS100T[F]?_.*" =>
+        { GPLOT => "temp4hum6:Temp/Hum,", FILTER => "%NAME:T:.*" }
+  };
+}
+
+#####################################
+sub
+HMS_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> HMS CODE" if(int(@a) != 3);
+  $a[2] = lc($a[2]);
+  return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
+  		if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
+
+
+  $hash->{CODE} = $a[2];
+  $modules{HMS}{defptr}{$a[2]} = $hash;
+  AssignIoPort($hash);
+  return undef;
+}
+
+#####################################
+sub
+HMS_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{HMS}{defptr}{$hash->{CODE}})
+        if(defined($hash->{CODE}) &&
+           defined($modules{HMS}{defptr}{$hash->{CODE}}));
+  return undef;
+}
+
+#####################################
+sub
+HMS_Parse($$)
+{
+  my ($hash, $msg) = @_;
+
+  my $dev = substr($msg, 16, 4);
+  my $cde = substr($msg, 11, 1);
+#                        012345678901234567890123456789
+#                        810e047f0214a001a81f000001000000 HMS100TFK
+  my $val = substr($msg, 24, 8) if(length($msg) == 32);
+  if(!defined($val)) {
+    Log3 $hash, 3, "Strange HMS message $msg";
+    return "";
+  }
+
+  my $type = "";
+  foreach my $c (keys %codes) {
+    if($cde =~ m/$c/) {
+      $type = $codes{$c};
+      last;
+    }
+  }
+
+  # As the HMS devices change their id on each battery change, we offer
+  # a wildcard too for each type:  100<device-code>,
+  my $odev = $dev;
+  if(!defined($modules{HMS}{defptr}{$dev})) {
+    Log3 $hash, 4,
+        "HMS device $dev not defined, using the wildcard device 100$cde";
+    $dev = "100$cde";
+  }
+
+  if(!defined($modules{HMS}{defptr}{$dev})) {
+    Log3 $hash, 3, "Unknown HMS device $dev/$odev, please define it";
+    $type = "HMS" if(!$type);
+    $type =~ s/-//; # RM100-2, - is special in fhem names
+    return "UNDEFINED ${type}_$odev HMS $odev";
+  }
+
+  my $def = $modules{HMS}{defptr}{$dev};
+  my $name = $def->{NAME};
+  return "" if(IsIgnored($name));
+
+  my (@v, @txt);
+
+  # Used for HMS100TF & HMS100T
+  my $batstr1 = "ok";
+  my $status1 = hex(substr($val, 0, 1));
+  $batstr1 = "empty"    if( $status1 & 2 );
+  $batstr1 = "replaced" if( $status1 & 4 );
+
+  # Used for the other devices
+  my $batstr2 = "ok";
+  my $status = hex(substr($val, 1, 1));
+  my $status2 = hex(substr($msg, 10, 1));
+  $batstr2 = "empty"    if( $status2 & 4 );
+  $batstr2 = "replaced" if( $status2 & 8 );
+
+  if($type eq "HMS100TF") {
+
+    @txt = ( "temperature", "humidity", "battery");
+
+    # Codierung <s1><s0><t1><t0><f0><t2><f2><f1>
+    $v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10;
+    $v[0] =  -$v[0] if($status1 & 8);
+    $v[1] = int(substr($val, 6, 2) . substr($val, 4, 1))/10;
+    $v[2] = $batstr1;
+
+    $val = "T: $v[0]  H: $v[1]  Bat: $v[2]";
+    #$v[0] = "$v[0] (Celsius)";
+    #$v[1] = "$v[1] (%)";
+
+  } elsif ($type eq "HMS100T") {
+
+    @txt = ( "temperature", "battery");
+
+    $v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10;
+    $v[0] =  -$v[0] if($status1 & 8);
+    $v[1] = $batstr1;
+
+    $val = "T: $v[0]  Bat: $v[1]";
+    #$v[0] = "$v[0] (Celsius)";
+
+  } elsif ($type eq "HMS100WD") {
+
+    @txt = ( "water_detect", "battery");
+
+    $v[0] = ($status ? "on" : "off");
+    $v[1] = $batstr2;
+
+    $val = "Water Detect: $v[0]";
+
+ } elsif ($type eq "HMS100TFK") {    # By Peter P.
+
+    @txt = ( "switch_detect", "battery");
+
+    $v[0] = ($status ? "on" : "off");
+    $v[1] = $batstr2;
+
+    $val = "Switch Detect: $v[0]";
+
+ } elsif($type eq "RM100-2") {
+
+    @txt = ( "smoke_detect", "battery");
+
+    $v[0] = ($status ? "on" : "off");
+    $v[1] = $batstr2;
+
+    $val = "smoke_detect: $v[0]";
+
+  } elsif ($type eq "HMS100MG") {    # By Peter Stark
+
+    @txt = ( "gas_detect", "battery");
+
+    $v[0] = ($status ? "on" : "off");
+    $v[1] = $batstr2;                 # Battery conditions not yet verified
+
+    $val = "Gas Detect: $v[0]";
+
+  } elsif ($type eq "HMS100CO") {    # By PAN
+
+    @txt = ( "gas_detect", "battery");
+
+    $v[0] = ($status ? "on" : "off");
+    $v[1] = $batstr2;                 # Battery conditions not yet verified
+
+    $val = "CO Detect: $v[0]";
+
+ } elsif ($type eq "HMS100FIT") {    # By PAN
+
+    @txt = ( "fi_triggered", "battery");
+
+    $v[0] = ($status ? "on" : "off");
+    $v[1] = $batstr2;                 # Battery conditions not yet verified
+
+    $val = "FI triggered: $v[0]";
+
+  } else {
+
+    Log3 $name, 3, "HMS Device $dev (Unknown type: $type)";
+    return "";
+
+  }
+
+  Log3 $name, 4, "HMS Device $dev ($type: $val)";
+
+  readingsBeginUpdate($def);
+  my $max = int(@txt);
+  for( my $i = 0; $i < $max; $i++) {
+    readingsBulkUpdate($def, $txt[$i], $v[$i]);
+  }
+  readingsBulkUpdate($def, "type", $type);
+  readingsBulkUpdate($def, "state", $val);
+  readingsBulkUpdate($def, "ExactId", $odev) if($odev ne $dev);
+  readingsEndUpdate($def, 1);
+
+  return $name;
+}
+
+1;
+
+=pod
+=item summary    devices communicating via the ELV HMS protocol
+=item summary_DE Anbindung von ELV HMS Ger&auml;ten
+=begin html
+
+<a name="HMS"></a>
+<h3>HMS</h3>
+<ul>
+  <a name="HMSdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; HMS &lt;housecode&gt;</code>
+    <br><br>
+
+    <code>&lt;housecode&gt;</code> is a four digit hex number,
+    corresponding to the address of the HMS device.
+    <br>
+
+    Examples:
+    <ul>
+      <code>define temp HMS 1234</code><br>
+    </ul>
+    Notes:<br>
+    <ul>
+      <li>Currently supported devices are the HMS100-T HMS100-TF HMS100-WD
+          HMS100-MG HMS100-TFK HMS100-CO HMS100-FIT RM100-2 RM100-3</li>
+
+      <li>The housecode of the HMS devices may change if the battery is renewed.
+      In order to make life easier, you can define a "wildcard" device for each
+      type of HMS device. First the real device-id will be checked, then the
+      wildcard device id. The wildcards are:
+      <ul>
+    <li>1000 for the HMS100-TF</li>
+    <li>1001 for the HMS100-T</li>
+    <li>1002 for the HMS100-WD</li>
+    <li>1003 for the RM100-2</li>
+    <li>1004 for the HMS100-TFK</li>
+    <li>1006 for the HMS100-MG</li>
+    <li>1008 for the HMS100-CO</li>
+    <li>100e for the HMS100-FIT</li>
+      </ul>
+      </li>
+
+      <li>Some battery low notifications are not yet implemented (RM100,
+      HMS100WD).</li>
+      <li>Please test your installation before relying on the
+      functionality.</li>
+
+    </ul>
+    <br>
+  </ul>
+  <br>
+
+  <a name="HMSset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="HMSget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="HMSattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#model">model</a> (hms100-t hms100-tf hms100-wd hms100-mg
+        hms100-co hms100-tfk hms100-fit rm100-2)</li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="HMS"></a>
+<h3>HMS</h3>
+<ul>
+  <a name="HMSdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; HMS &lt;housecode&gt;</code>
+    <br><br>
+
+    Der <code>&lt;housecode&gt;</code> ist eine vierstellige HEX-Zahl,
+    entsprechend dem HMS Ger&auml;t.<br>
+
+    Beispiel:
+    <ul>
+      <code>define temp HMS 1234</code><br>
+    </ul>
+    Hinweise:<br>
+    <ul>
+      <li>Derzeit werden folgende Komponenten Unterst&uuml;tzt: HMS100-T
+          HMS100-TF HMS100-WD HMS100-MG HMS100-TFK HMS100-CO HMS100-FIT RM100-2
+          RM100-3</li>
+
+      <li>Der Hauscode kann sich &auml;ndern wenn die Batterie gewechselt wird.
+        Um sich das Leben einfacher zu machen kann man ein "Wildcard"
+        (Platzhalter) Device f&uuml;r jeden Typ von HMS  Ger&auml;t anlegen.
+        Zuerst wird die echte Device-ID gepr&uuml;ft, danach die Wildcard-ID.
+        Wildcards sind:
+      <ul>
+        <li>1000 f&uuml;r das HMS100-TF</li>
+        <li>1001 f&uuml;r das HMS100-T</li>
+        <li>1002 f&uuml;r das HMS100-WD</li>
+        <li>1003 f&uuml;r das RM100-2</li>
+        <li>1004 f&uuml;r das HMS100-TFK</li>
+        <li>1006 f&uuml;r das HMS100-MG</li>
+        <li>1008 f&uuml;r das HMS100-CO</li>
+        <li>100e f&uuml;r das HMS100-FIT</li>
+      </ul>
+      </li>
+
+      <li>Einige "Batteriestand niedrig" Benachrichtigungen sind noch nicht
+        implemeniert (RM100, HMS100WD).</li>
+
+      <li>Die Installation ist zu testen bevor man sich auf die
+        Funktionalit&auml;t verl&auml;sst.</li>
+
+    </ul>
+    <br>
+  </ul>
+  <br>
+
+  <a name="HMSset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="HMSget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="HMSattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#model">model</a> (hms100-t hms100-tf hms100-wd hms100-mg
+        hms100-co hms100-tfk hms100-fit rm100-2)</li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+</ul>
+
+=end html_DE
+
+=cut

+ 453 - 0
fhem/core/FHEM/13_KS300.pm

@@ -0,0 +1,453 @@
+##############################################
+# $Id: 13_KS300.pm 12699 2016-12-02 12:15:44Z rudolfkoenig $
+#
+# modified: 2014-02-16 - betateilchen
+#           - added new reading for windIndex (bft)
+#           - changed to readingFnAttributes
+#           - some minor code cleanups
+#
+
+package main;
+
+use strict;
+use warnings;
+
+sub KS300_windIndex($);
+
+#####################################
+sub
+KS300_Initialize($)
+{
+  my ($hash) = @_;
+
+  # Message is like
+  # 810d04f94027a00171212730000008
+  # 81 0d 04 f9 4027a00171 212730000008
+
+  $hash->{Match}     = "^810d04..4027a001";
+  $hash->{DefFn}     = "KS300_Define";
+  $hash->{UndefFn}   = "KS300_Undef";
+  $hash->{ParseFn}   = "KS300_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:0,1 showtime:0,1 model:ks300 ".
+                        "rainadjustment:0,1 ignore:0,1 ".
+                        $readingFnAttributes;
+  $hash->{AutoCreate}=
+    { "KS300.*" => {
+         GPLOT => "temp4rain10:Temp/Rain,hum6wind8:Wind/Hum,",
+         FILTER => "%NAME:T:.*" } };
+
+}
+
+#####################################
+sub
+KS300_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> KS300 <code> " .
+          "[ml/raincounter] [wind-factor]" if(int(@a) < 3 || int(@a) > 5);
+  $a[2] = lc($a[2]);
+  return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
+                if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
+
+#  $hash->{CODE} = $a[2];
+  my $rainunit = ((int(@a) > 3) ? $a[3] : 255);
+  my $windunit = ((int(@a) > 4) ? $a[4] : 1.0);
+  $hash->{CODE} = $a[2];
+  $hash->{RAINUNIT} = $rainunit;
+  $hash->{WINDUNIT} = $windunit;
+  $modules{KS300}{defptr}{$a[2]} = $hash;
+  AssignIoPort($hash);
+
+  readingsSingleUpdate($hash, 'state', 'defined', 0);
+
+  return undef;
+}
+
+#####################################
+sub
+KS300_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{KS300}{defptr}{$hash->{CODE}});
+  return undef;
+}
+
+#####################################
+# { Dispatch($defs{CUL},"810d04xx4027a00171240080009359", undef) }
+sub
+KS300_Parse($$)
+{
+  my ($hash,$msg) = @_;
+
+  ###############################
+  #          1          2
+  #0123456789012345 67890123456789
+  #
+  #810d04f94027a001 71212730000008
+  ###############################
+  my @a = split("", $msg);
+
+  ##########################
+  # I've seldom (1 out of 700) seen messages of length 10 and 11 with correct
+  # CRC, they seem to contain partial data (e.g. temp/wind/hum but not rain)
+  # They are suppressed as of now.
+  if(hex($a[3]) != 13) {
+    Log3 $hash, 4, "Strange KS300 message received, won't decode ($msg)";
+    return "";
+  }
+
+  if(int(keys %{ $modules{KS300}{defptr} })) {
+
+    my @arr = keys(%{ $modules{KS300}{defptr} }); # No code is known yet
+    my $dev = shift(@arr);
+    my $def = $modules{KS300}{defptr}{$dev};
+    my $haverain = 0;
+    my $name= $def->{NAME};
+    return "" if(IsIgnored($name));
+
+    readingsBeginUpdate($def);
+    my @v;
+    my @txt = ( "rain_raw", "rain", "wind", "humidity", "temperature",
+                "israining", "checksum", "type_raw", "unknown3", "windIndex");
+    my @sfx = ( "(counter)", "(l/m2)", "(km/h)", "(%)", "(Celsius)",
+        "(yes/no)", "","","","");
+    my %repchanged = ("rain"=>1, "wind"=>1, "humidity"=>1, "temperature"=>1,
+        "israining"=>1);
+
+    # time
+    my $tm = TimeNow();
+    my $tsecs= time();  # number of non-leap seconds since January 1, 1970, UTC
+
+    # preset current $rain_raw
+    $v[0] = hex("$a[28]$a[27]$a[26]");
+    my $rain_raw = $v[0];
+
+    # get previous rain_raw
+    my $rain_raw_prev = ReadingsVal($name, 'rain_raw', $rain_raw);
+    ($rain_raw_prev, undef) = split(" ", $rain_raw_prev); # cut off "(counter)"
+
+    my $rain_raw_adj = $rain_raw;       # unadjusted value as default
+    my $rain_raw_adj_prev = ReadingsVal($name, 'rain_raw_adj', $rain_raw);
+
+    if(AttrVal($name,"rainadjustment",0)) {
+
+      # The rain values delivered by my KS300 randomly switch between two
+      # different values. The offset between the two values follows no
+      # identifiable principle. It is even unclear whether the problem is
+      # caused by KS300 or by FHZ1300. ELV denies any problem with the KS300.
+      # The problem is known to several people. For instance, see
+      # http://www.ipsymcon.de/forum/showthread.php?t=3303&highlight=ks300+regen&page=3
+      # The following code detects and automatically corrects these offsets.
+
+
+      my $rain_raw_ofs_prev = ReadingsVal($name, 'rain_raw_ofs', 0);
+      my $rain_raw_ofs = $rain_raw_ofs_prev;
+      my $tsecs_prev = ReadingsVal($name, 'tsecs', 0);
+
+      # detect error condition
+      # delta is negative or delta is too large
+      # see http://de.wikipedia.org/wiki/Niederschlagsintensit??t#Niederschlagsintensit.C3.A4t
+      # during a thunderstorm in middle europe, 50l/m^2 rain may fall per hour
+      # 50l/(m^2*h) correspond to 200 ticks/h
+      # Since KS300 sends every 2,5 minutes, a maximum delta of 8 ticks would
+      # be reasonable. The observed deltas are in most cases 1 or 2 orders
+      # of magnitude larger.
+      # The code also handles counter resets after battery replacement
+
+      my $rain_raw_delta = $rain_raw - $rain_raw_prev;
+      my $deltatsecs= ($tsecs - $tsecs_prev); # we have observed two datagrams at the same second
+      $deltatsecs= 1 if($deltatsecs< 1); 
+      my $thours_delta = $deltatsecs/3600.0; # in hours
+      my $rain_raw_per_hour = $rain_raw_delta/$thours_delta;
+      if(($rain_raw_delta<0) || ($rain_raw_per_hour> 200.0)) {
+            $rain_raw_ofs = $rain_raw_ofs_prev-$rain_raw_delta;
+
+            # If the switch in the tick count occurs simultaneously with an
+            # increase due to rain, the tick is lost. We therefore assume that
+            # offsets between -5 and 0 are indeed rain.
+
+            if(($rain_raw_ofs>=-5) && ($rain_raw_ofs<0)) {
+            $rain_raw_ofs= 0;
+            }
+            readingsBulkUpdate($def, 'rain_raw_ofs', $rain_raw_ofs, 0);
+      }
+      $rain_raw_adj = $rain_raw + $rain_raw_ofs;
+
+    }
+
+    readingsBulkUpdate($def, 'tsecs', $tsecs, 0);
+    readingsBulkUpdate($def, 'rain_raw_adj', $rain_raw_adj, 0);
+
+    # KS300 has a sensor which detects any drop of rain and immediately
+    # sends out the israining message. The sensors consists of two parallel
+    # strips of metal separated by a small gap. The rain bridges the gap
+    # and closes the contact. If the KS300 pole is not perfectly vertical the
+    # drop runs along only one side and the contact is not closed. To get the
+    # israining information anyway, the respective flag is also set when the
+    # a positive amount of rain is detected.
+
+    $haverain = 1 if($rain_raw_adj != $rain_raw_adj_prev);
+
+    $v[1] = sprintf("%0.1f", $rain_raw_adj * $def->{RAINUNIT} / 1000);
+    $v[2] = sprintf("%0.1f", ("$a[25]$a[24].$a[23]"+0) * $def->{WINDUNIT});
+    $v[3] = "$a[22]$a[21]" + 0;
+    $v[4] = "$a[20]$a[19].$a[18]" + 0; $v[4] = "-$v[4]" if($a[17] eq "7");
+    $v[4] = sprintf("%0.1f", $v[4]);
+    $v[5] = ((hex($a[17]) & 0x2) || $haverain) ? "yes" : "no";
+    $v[6] = $a[29];
+    $v[7] = $a[16];
+    $v[8] = $a[17];
+    $v[9] = KS300_windIndex($v[2]);
+
+    # Negative temp
+    $v[4] = -$v[4] if(hex($v[8]) & 8);
+
+    Log3 $def, 4, "KS300 $dev: $msg";
+
+    my $max = int(@v);
+
+    # For logging/summary
+    my $val = "T: $v[4]  H: $v[3]  W: $v[2]  R: $v[1]  IR: $v[5]  Wi: $v[9]";
+    Log3 $def, 4, "KS300 $dev: $val";
+    readingsBulkUpdate($def,'state', $val);
+
+    for(my $i = 0; $i < $max; $i++) {
+      readingsBulkUpdate($def, $txt[$i], $v[$i],
+                            defined($repchanged{$txt[$i]}));
+    }
+
+
+    ###################################
+    # AVG computing
+
+    if(!ReadingsVal($name, 'cum_day', undef)) {
+      readingsBulkUpdate($def, 'cum_day', "$tm T: 0 H: 0 W: 0 R: $v[1]", 0);
+
+    } else {
+
+      my @cv = split(" ", ReadingsVal($name, 'cum_day',''));
+      my @cd = split("[ :-]", ReadingsTimestamp($name, 'cum_day',''));
+
+      my $csec = 3600*$cd[3] + 60*$cd[4] + $cd[5]; # Sec of last reading
+      my @d = split("[ :-]", $tm);
+      my $sec = 3600*$d[3] + 60*$d[4] + $d[5];     # Sec now
+
+      my @sd = split("[ :-]", "$cv[0] $cv[1]");
+      my $ssec = 3600*$sd[3] + 60*$sd[4] + $sd[5]; # Sec at start of day
+
+      my $difft = $sec - $csec;
+      $difft += 86400 if($d[2] != $cd[2]);         # Sec since last reading
+
+      my $t = $cv[3] + $difft * $v[4];
+      my $h = $cv[5] + $difft * $v[3];
+      my $w = $cv[7] + $difft * $v[2];
+      my $e = $cv[9];
+
+      $val = "$cv[0] $cv[1] T: $t  H: $h  W: $w  R: $e";
+      readingsBulkUpdate($def, 'cum_day', $val, 0);
+
+      $difft = $sec - $ssec;
+      $difft += 86400 if($d[2] != $sd[2]);       # Sec since last reading
+      $difft = 1 if(!$difft);                    # Don't want illegal division.
+      $t /= $difft; $h /= $difft; $w /= $difft; $e = $v[1] - $cv[9];
+
+      $val = sprintf("T: %.1f  H: %d  W: %.1f  R: %.1f", $t, $h, $w, $e);
+      readingsBulkUpdate($def, 'avg_day', $val, $d[2]!=$sd[2]);
+
+      if($d[2] != $sd[2]) {                      # Day changed
+        $val = "$tm T: 0 H: 0 W: 0 R: $v[1]";
+        readingsBulkUpdate($def, 'cum_day', $val, 0);
+
+        if(!ReadingsVal($name, 'cum_month', undef)) {
+          $val = "1 ".ReadingsVal($name, 'avg_day','');
+          readingsBulkUpdate($def, 'cum_month', $val, 0);
+
+        } else {
+          my @cmv = split(" ", ReadingsVal($name, 'cum_month',''));
+          $t += $cmv[2]; $w += $cmv[4]; $h += $cmv[6];
+          $cmv[0]++;
+          $val = sprintf("%d T: %.1f  H: %d  W: %.1f  R: %.1f",
+          $cmv[0], $t, $h, $w, $cmv[8]+$e);
+          readingsBulkUpdate($def, 'cum_month', $val, 0);
+
+          $val = sprintf("T: %.1f  H: %d  W: %.1f  R: %.1f",
+                        $t/$cmv[0], $h/$cmv[0], $w/$cmv[0], $cmv[8]+$e);
+          readingsBulkUpdate($def, 'avg_month', $val, $d[1]!=$sd[1]);
+          if($d[1] != $sd[1]) {                  # Month changed, report it
+            $val = "0 T: 0 H: 0 W: 0 R: 0";
+            readingsBulkUpdate($def, 'cum_month', $val, 0);
+          }
+        }
+      }
+    }
+    # AVG computing
+    ###################################
+
+    readingsEndUpdate($def,1);
+    return $name;
+
+  } else {
+
+    Log3 $hash, 4, "KS300 detected: $msg";
+    return "UNDEFINED KS300 KS300 1234";
+
+  }
+}
+
+sub
+KS300_windIndex($)
+{
+  #
+  #  convert km/h to bft as described by
+  #  http://www.meteotest.ch/wetterprognosen/prognosen_schweiz/windtabelle
+  #
+  my ($w) = @_;
+  return  "0" if($w < 1);
+  return  "1" if($w >=   1 && $w <    6);
+  return  "2" if($w >=   6 && $w <   12);
+  return  "3" if($w >=  12 && $w <   20);
+  return  "4" if($w >=  20 && $w <   29);
+  return  "5" if($w >=  29 && $w <   39);
+  return  "6" if($w >=  39 && $w <   50);
+  return  "7" if($w >=  50 && $w <   62);
+  return  "8" if($w >=  62 && $w <   75);
+  return  "9" if($w >=  75 && $w <   89);
+  return "10" if($w >=  89 && $w <  103);
+  return "11" if($w >= 103 && $w <= 117);
+  return "12" if($w > 117);
+}
+
+1;
+
+=pod
+=item summary    module for the ELV KS300 weather station
+=item summary_DE Anbindung der ELV KS300 Wetterstation
+=begin html
+
+<a name="KS300"></a>
+<h3>KS300</h3>
+<ul>
+  Fhem can receive the KS300 or KS555 radio messages through the <a
+  href="#FHZ">FHZ</a>, <a href="WS300">WS300</a> or <a href="#CUL">CUL</a>, so
+  one of them must be defined first.<br> This module services messages received
+  by the FHZ or CUL.<br> <br>
+
+  <a name="KS300define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; KS300 &lt;housecode&gt; [ml/raincounter [wind-factor]]</code>
+    <br><br>
+
+    <code>&lt;housecode&gt;</code> is a four digit hex number, it must be
+    specified foir historic reasons, and it is ignored.
+    The ml/raincounter defaults to 255 ml, and it must be specified if you wish
+    to set the wind factor, which defaults to 1.0.  <br>
+    Examples:
+    <ul>
+      <code>define ks1 KS300 1234</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="KS300set"></a>
+  <b>Set </b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="KS300get"></a>
+  <b>Get</b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="KS300attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#model">model</a> (ks300)</li>
+    <li>rainadjustment<br>
+        If this attribute is set, fhem automatically considers rain counter
+        resets after a battery change and random counter switches as
+        experienced by some users.  Default is 0 (off).</li>
+  </ul>
+  <br>
+
+</ul>
+
+=end html
+
+
+=begin html_DE
+
+<a name="KS300"></a>
+<h3>KS300</h3>
+<ul>
+  Fhem kann KS300 bzw. KS555 Funktelegramme mit einem <a href="#FHZ">FHZ</a>,
+  einem <a href="WS300">WS300</a> oder einem <a href="#CUL">CUL</a> empfangen.
+  Daher muss eines von diesen zuerst definiert sein.<br> Dieses Modul behandelt
+  Nachrichten die mittels CUL oder FHZ empfangen werden.<br>
+  <br>
+
+  <a name="KS300define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; KS300 &lt;housecode&gt; [ml/raincounter [wind-factor]]</code>
+    <br><br>
+
+    <code>&lt;housecode&gt;</code> ist ein vierstelliger HEX-Wert, der aus
+    historischen Gr&uuml;nden angegeben werden muss, es wird ignoriert.  Der
+    ml/raincounter hat einen Default-Wert von 255ml, und muss angegeben sein
+    wenn man den Wind-Faktor setzen will. Dieser hat einen Default-Wert von
+    1.0.<br>
+    Beispiele:
+    <ul>
+      <code>define ks1 KS300 1234</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="KS300set"></a>
+  <b>Set </b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="KS300get"></a>
+  <b>Get</b>
+  <ul>
+    N/A
+  </ul>
+  <br>
+
+  <a name="KS300attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#model">model</a> (ks300)</li>
+    <li>rainadjustment<br>
+        Wenn dieses Attribut gesetzt ist, Regenmesser resets werden automatisch
+        ber&uuml;cksichtigt.  Resets treten beim Wechsel der Batterie und nach
+        Beobachtung einiger Benutzer auch nach zuf&auml;lligen Schaltzyklen
+        auf. Die Voreinstellung ist 0 (aus).</li>
+  </ul>
+  <br>
+
+</ul>
+
+=end html_DE
+
+=cut

+ 774 - 0
fhem/core/FHEM/14_CUL_MAX.pm

@@ -0,0 +1,774 @@
+##############################################
+# $Id: 14_CUL_MAX.pm 12440 2016-10-26 20:24:45Z mgehre $
+# Written by Matthias Gehre, M.Gehre@gmx.de, 2012-2013
+package main;
+
+use strict;
+use warnings;
+use MaxCommon;
+use POSIX;
+
+sub CUL_MAX_BroadcastTime(@);
+sub CUL_MAX_Set($@);
+sub CUL_MAX_SendTimeInformation(@);
+sub CUL_MAX_GetTimeInformationPayload();
+sub CUL_MAX_Send(@);
+sub CUL_MAX_SendQueueHandler($$);
+
+my $pairmodeDuration = 60; #seconds
+
+my $ackTimeout = 3; #seconds
+
+my $maxRetryCnt = 3;
+
+sub
+CUL_MAX_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^Z";
+  $hash->{DefFn}     = "CUL_MAX_Define";
+  $hash->{Clients}   = ":MAX:";
+  my %mc = (
+    "1:MAX" => "^MAX",
+  );
+  $hash->{MatchList} = \%mc;
+  $hash->{UndefFn}   = "CUL_MAX_Undef";
+  $hash->{ParseFn}   = "CUL_MAX_Parse";
+  $hash->{SetFn}     = "CUL_MAX_Set";
+  $hash->{AttrFn}    = "CUL_MAX_Attr";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 " .
+                        "showtime:1,0 ".
+                        $readingFnAttributes;
+
+  $hash->{sendQueue} = [];
+}
+
+#############################
+
+sub
+CUL_MAX_SetupCUL($)
+{
+  my $hash = $_[0];
+  AssignIoPort($hash);
+  if(!defined($hash->{IODev})) {
+    Log3 $hash, 1, "$hash->{NAME}: did not find suitable IODev (CUL etc. in rfmode MAX)! You may want to execute 'attr $hash->{NAME} IODev SomeCUL'";
+    return 0;
+  }
+
+  my $version = CUL_MAX_Check($hash);
+  Log3 $hash, 3, "CUL_MAX_Check: Detected firmware version $version of the CUL-compatible IODev";
+  if($version >= 152) {
+    #Doing this on older firmware disables MAX mode
+    IOWrite($hash, "", "Za". $hash->{addr});
+    #Append to initString, so this is resend if cul disappears and then reappears
+    $hash->{IODev}{initString} .= "\nZa". $hash->{addr};
+  }
+  if($version >= 153) {
+    #Doing this on older firmware disables MAX mode
+    my $cmd = "Zw". CUL_MAX_fakeWTaddr($hash);
+    IOWrite($hash, "", $cmd);
+    $hash->{IODev}{initString} .= "\n".$cmd;
+  }
+  return 1
+}
+
+sub
+CUL_MAX_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> CUL_MAX <srcAddr>" if(@a<3);
+
+  if (length($a[2]) != 6) {
+	  Log3 $hash, 1, "The adress must be 6 hexadecimal digits";
+	  return "The adress must be 6 hexadecimal digits";
+  }
+
+  $hash->{addr} = lc($a[2]);
+  $hash->{STATE} = "Defined";
+  $hash->{cnt} = 0;
+  $hash->{pairmode} = 0;
+  $hash->{retryCount} = 0;
+  $hash->{sendQueue} = [];
+  CUL_MAX_SetupCUL($hash);
+
+  #This interface is shared with 00_MAXLAN.pm
+  $hash->{Send} = \&CUL_MAX_Send;
+
+  #Start broadcasting time after 30 seconds, so there is enough time to parse the config
+  InternalTimer(gettimeofday()+30, "CUL_MAX_BroadcastTime", $hash, 0);
+  return undef;
+}
+
+#####################################
+sub
+CUL_MAX_Undef($$)
+{
+  my ($hash, $name) = @_;
+  RemoveInternalTimer($hash);
+  return undef;
+}
+
+sub
+CUL_MAX_DisablePairmode($)
+{
+  my $hash = shift;
+  $hash->{pairmode} = 0;
+}
+
+sub
+CUL_MAX_Check($@)
+{
+  my ($hash) = @_;
+  if(!defined($hash->{IODev})) {
+    Log3 $hash, 1, "CUL_MAX_Check: No IODev found.";
+    return 0;
+  }
+
+  if(!defined($hash->{IODev}{VERSION})) {
+    Log3 $hash, 1, "CUL_MAX_Check: No IODev has no VERSION";
+    return 0;
+  }
+
+  my $version = $hash->{IODev}{VERSION};
+
+  if($version =~ m/.*a-culfw.*/) {
+      #a-culfw is compatibel to culfw 154
+      return 154;
+  }
+
+  #Looks like "V 1.49 CUL868"
+  if($version =~ m/V (.*)\.(.*) .*/) {
+    my ($major_version,$minorversion) = ($1, $2);
+    $version = 100*$major_version + $minorversion;
+    if($version < 154) {
+      Log3 $hash, 2, "CUL_MAX_Check: You are using an old version of the CUL firmware, which has known bugs with respect to MAX! support. Please update.";
+    }
+    return $version;
+  } else {
+    Log3 $hash, 1, "CUL_MAX_Check: Could not correctly parse IODev->{VERSION} = '$version'";
+    return 0;
+  }
+}
+
+sub
+CUL_MAX_Attr(@)
+{
+  my ($hash, $action, $name, $attr, $value) = @_;
+  if ($action eq "set") {
+    return "No such attribute" if($attr !~ ["fakeWTaddr", "fakeSCaddr", "IODev"]);
+    return "Invalid value" if(grep( /^\Q$attr\E$/, ("fakeWTaddr", "fakeSCaddr")) && $value !~ /^[0-9a-fA-F]{6}$/);
+  }
+}
+
+sub
+CUL_MAX_fakeWTaddr($)
+{
+  return lc(AttrVal($_[0]->{NAME}, "fakeWTaddr", "111111"));
+}
+
+sub
+CUL_MAX_fakeSCaddr($)
+{
+  return lc(AttrVal($_[0]->{NAME}, "fakeSCaddr", "222222"));
+}
+
+sub
+CUL_MAX_Set($@)
+{
+  my ($hash, $device, @a) = @_;
+  return "\"set MAXLAN\" needs at least one parameter" if(@a < 1);
+  my ($setting, @args) = @a;
+
+  if($setting eq "pairmode") {
+    $hash->{pairmode} = 1;
+    InternalTimer(gettimeofday()+$pairmodeDuration, "CUL_MAX_DisablePairmode", $hash, 0);
+
+  } elsif($setting eq "broadcastTime") {
+    CUL_MAX_BroadcastTime($hash, 1);
+
+  } elsif(grep /^\Q$setting\E$/, ("fakeSC", "fakeWT")) {
+    return "Invalid number of arguments" if(@args == 0);
+    my $dest = $args[0];
+    my $destname;
+    #$dest may be either a name or an address
+    if(exists($defs{$dest})) {
+      return "Destination is not a MAX device" if($defs{$dest}{TYPE} ne "MAX");
+      $destname = $dest;
+      $dest = $defs{$dest}{addr};
+    } else {
+      $dest = lc($dest); #address to lower-case
+      return "No MAX device with address $dest" if(!exists($modules{MAX}{defptr}{$dest}));
+      $destname = $modules{MAX}{defptr}{$dest}{NAME};
+    }
+
+    if($setting eq "fakeSC") {
+      return "Invalid number of arguments" if(@args != 2);
+      return "Invalid fakeSCaddr attribute set (must not be 000000)" if(CUL_MAX_fakeSCaddr($hash) eq "000000");
+
+      my $state = $args[1] ? "12" : "10";
+      my $groupid = ReadingsVal($destname,"groupid",0);
+      return CUL_MAX_Send($hash, "ShutterContactState",$dest,$state,
+                          groupId => sprintf("%02x",$groupid),
+                          flags => ( $groupid ? "04" : "06" ),
+                          src => CUL_MAX_fakeSCaddr($hash));
+
+    } elsif($setting eq "fakeWT") {
+      return "Invalid number of arguments" if(@args != 3);
+      return "desiredTemperature is invalid" if(!validTemperature($args[1]));
+      return "Invalid fakeWTaddr attribute set (must not be 000000)" if(CUL_MAX_fakeWTaddr($hash) eq "000000");
+
+      #Valid range for measured temperature is 0 - 51.1 degree
+      $args[2] = 0 if($args[2] < 0); #Clamp temperature to minimum of 0 degree
+
+      #Encode into binary form
+      my $arg2 = int(10*$args[2]);
+      #First bit is 9th bit of temperature, rest is desiredTemperature
+      my $arg1 = (($arg2&0x100)>>1) | (int(2*MAX_ParseTemperature($args[1]))&0x7F);
+      $arg2 &= 0xFF; #only take the lower 8 bits
+      my $groupid = ReadingsVal($destname,"groupid",0);
+
+      return CUL_MAX_Send($hash,"WallThermostatControl",$dest,
+        sprintf("%02x%02x",$arg1,$arg2), groupId => sprintf("%02x",$groupid),
+                flags => ( $groupid ? "04" : "00" ),
+                src => CUL_MAX_fakeWTaddr($hash));
+    }
+
+  } else {
+    return "Unknown argument $setting, choose one of pairmode broadcastTime";
+  }
+  return undef;
+}
+
+sub
+CUL_MAX_Parse($$)
+{
+  #Attention: there is a limit in the culfw firmware: It only receives messages shorter than 30 bytes (see rf_moritz.h)
+  # $hash is for the CUL instance
+  my ($hash, $rmsg) = @_;
+
+  my $shash = undef; #shash is for the CUL_MAX instance
+
+  #Find a CUL_MAX that has the CUL $hash as its IODev;
+  #if no matching is found, just use the last encountered CUL_MAX.
+  foreach my $d (keys %defs) {
+    if($defs{$d}{TYPE} eq "CUL_MAX") {
+      $shash = $defs{$d};
+      last if($defs{$d}{IODev} == $hash);
+    }
+  }
+
+  if(!defined($shash)) {
+    Log3 $hash, 2, "No CUL_MAX defined";
+    return "UNDEFINED CULMAX0 CUL_MAX 123456";
+  }
+
+  return () if($rmsg !~ m/Z(..)(..)(..)(..)(......)(......)(..)(.*)/);
+
+  my ($len,$msgcnt,$msgFlag,$msgTypeRaw,$src,$dst,$groupid,$payload) = ($1,$2,$3,$4,$5,$6,$7,$8);
+  $len = hex($len);
+  if(2*$len+3 != length($rmsg)) { #+3 = +1 for 'Z' and +2 for len field in hex
+    Log3 $hash, 1, "CUL_MAX_Parse: len mismatch";
+    return $shash->{NAME};
+  }
+
+  $groupid = hex($groupid);
+
+  #convert adresses to lower case
+  $src = lc($src);
+  $dst = lc($dst);
+  my $msgType = exists($msgId2Cmd{$msgTypeRaw}) ? $msgId2Cmd{$msgTypeRaw} : $msgTypeRaw;
+  Log3 $hash, 5, "CUL_MAX_Parse: len $len, msgcnt $msgcnt, msgflag $msgFlag, msgTypeRaw $msgType, src $src, dst $dst, groupid $groupid, payload $payload";
+
+  return $shash->{NAME} if (exists($modules{MAX}{defptr}{$src}) && IsIgnored($modules{MAX}{defptr}{$src}{NAME}));
+
+  my $isToMe = ($dst eq $shash->{addr}) ? 1 : 0; # $isToMe is true if that packet was directed at us
+
+  #Set RSSI on MAX device
+  if(exists($modules{MAX}{defptr}{$src}) && exists($hash->{RSSI})) {
+    Log3 $hash, 5, "CUL_MAX_Parse: rssi: $hash->{RSSI}";
+    $modules{MAX}{defptr}{$src}{RSSI} = $hash->{RSSI};
+  }
+
+  if(exists($msgId2Cmd{$msgTypeRaw})) {
+
+    if($msgType eq "Ack") {
+      #Ignore packets generated by culfw's auto-Ack
+      return $shash->{NAME} if($src eq $shash->{addr});
+      return $shash->{NAME} if($src eq CUL_MAX_fakeWTaddr($hash));
+      return $shash->{NAME} if($src eq CUL_MAX_fakeSCaddr($hash));
+
+      Dispatch($shash, "MAX,$isToMe,Ack,$src,$payload", {});
+
+      return $shash->{NAME} if(!@{$shash->{sendQueue}}); #we are not waiting for any Ack
+
+      for my $i (0 .. $#{$shash->{sendQueue}}) {
+        my $packet = $shash->{sendQueue}[$i];
+        if($packet->{src} eq $dst and $packet->{dst} eq $src and $packet->{cnt} == hex($msgcnt)) {
+          Log3 $hash, 5, "Got matching ack";
+          my $isnak = unpack("C",pack("H*",$payload)) & 0x80;
+          $packet->{sent} = $isnak ? 3 : 2;
+        }
+      }
+      #Handle outgoing messages to that ShutterContact. It is only awake shortly
+      #after sending an Ack to a PairPong
+      CUL_MAX_SendQueueHandler($shash, $src) if(exists($modules{MAX}{defptr}{$src}) && $modules{MAX}{defptr}{$src}{type} eq "ShutterContact");
+      return $shash->{NAME};
+
+    } elsif($msgType eq "TimeInformation") {
+      if($isToMe) {
+        #This is a request for TimeInformation send to us
+        Log3 $hash, 5, "Got request for TimeInformation, sending it";
+        CUL_MAX_SendTimeInformation($shash, $src);
+      } elsif(length($payload) > 0) {
+        my ($f1,$f2,$f3,$f4,$f5) = unpack("CCCCC",pack("H*",$payload));
+        #For all fields but the month I'm quite sure
+        my $year = $f1 + 2000;
+        my $day  = $f2;
+        my $hour = ($f3 & 0x1F);
+        my $min = $f4 & 0x3F;
+        my $sec = $f5 & 0x3F;
+        my $month = (($f4 >> 6) << 2) | ($f5 >> 6); #this is just guessed
+        my $unk1 = $f3 >> 5;
+        my $unk2 = $f4 >> 6;
+        my $unk3 = $f5 >> 6;
+        #I guess the unk1,2,3 encode if we are in DST?
+        Log3 $hash, 5, "CUL_MAX_Parse: Got TimeInformation: (in GMT) year $year, mon $month, day $day, hour $hour, min $min, sec $sec, unk ($unk1, $unk2, $unk3)";
+      }
+    } elsif($msgType eq "PairPing") {
+      my ($firmware,$type,$testresult,$serial) = unpack("CCCa*",pack("H*",$payload));
+      #What does testresult mean?
+      Log3 $hash, 5, "CUL_MAX_Parse: Got PairPing (dst $dst, pairmode $shash->{pairmode}), firmware $firmware, type $type, testresult $testresult, serial $serial";
+
+      #There are two variants of PairPing:
+      #1. It has a destination address of "000000" and can be paired to any device.
+      #2. It is sent after changing batteries or repressing the pair button (without factory reset) and has a destination address of the last paired device. We can answer it with PairPong and even get an Ack, but it will still not be paired to us. A factory reset (originating from the last paired device) is needed first.
+      if(($dst ne "000000") and !$isToMe) {
+        Log3 $hash,5 , "Device want's to be re-paired to $dst, not to us";
+        return $shash->{NAME};
+      }
+
+      #If $isToMe is true, this device is already paired and just wants to be reacknowledged
+      #If we already have the device created but it was reseted (batteries changed?), we directly re-pair (without pairmode)
+      if($shash->{pairmode} || $isToMe || exists($modules{MAX}{defptr}{$src})) {
+        Log3 $hash, 3, "CUL_MAX_Parse: " . ($isToMe ? "Re-Pairing" : "Pairing") . " device $src of type $device_types{$type} with serial $serial";
+        Dispatch($shash, "MAX,$isToMe,define,$src,$device_types{$type},$serial,0", {});
+
+        #Set firmware and testresult on device
+        my $dhash = CUL_MAX_DeviceHash($src);
+        if(defined($dhash)) {
+          readingsBeginUpdate($dhash);
+          readingsBulkUpdate($dhash, "firmware", sprintf("%u.%u",int($firmware/16),$firmware%16));
+          readingsBulkUpdate($dhash, "testresult", $testresult);
+          readingsEndUpdate($dhash, 1);
+        }
+
+        #Send after dispatch the define, otherwise Send will create an invalid device
+        CUL_MAX_Send($shash, "PairPong", $src, "00");
+
+        return $shash->{NAME} if($isToMe); #if just re-pairing, default values are not restored (I checked)
+
+        #This are the default values that a device has after factory reset or pairing
+        if($device_types{$type} =~ /HeatingThermostat.*/) {
+          Dispatch($shash, "MAX,$isToMe,HeatingThermostatConfig,$src,17,21,30.5,4.5,$defaultWeekProfile,80,5,0,12,15,100,0,0,12", {});
+        } elsif($device_types{$type} eq "WallMountedThermostat") {
+          Dispatch($shash, "MAX,$isToMe,WallThermostatConfig,$src,17,21,30.5,4.5,$defaultWeekProfile,80,5,0,12", {});
+        }
+      }
+    } elsif(grep /^$msgType$/, ("ShutterContactState", "WallThermostatState", "WallThermostatControl", "ThermostatState", "PushButtonState", "SetTemperature"))  {
+      Dispatch($shash, "MAX,$isToMe,$msgType,$src,$payload", {});
+    } else {
+      Log3 $hash,5 , "Unhandled message $msgType";
+    }
+  } else {
+    Log3 $hash, 2, "CUL_MAX_Parse: Got unhandled message type $msgTypeRaw";
+  }
+  return $shash->{NAME};
+}
+
+#All inputs are hex strings, $cmd is one from %msgCmd2Id
+sub
+CUL_MAX_Send(@)
+{
+  # $cmd is one of
+  my ($hash, $cmd, $dst, $payload, %opts) = @_;
+
+  my $flags = "00";
+  my $groupId = "00";
+  my $src = $hash->{addr};
+  my $callbackParam = undef;
+
+  $flags = $opts{flags} if(exists($opts{flags}));
+  $groupId = $opts{groupId} if(exists($opts{groupId}));
+  $src = $opts{src} if(exists($opts{src}));
+  $callbackParam = $opts{callbackParam} if(exists($opts{callbackParam}));
+
+  my $dhash = CUL_MAX_DeviceHash($dst);
+  $dhash->{READINGS}{msgcnt}{VAL} += 1;
+  $dhash->{READINGS}{msgcnt}{VAL} &= 0xFF;
+  $dhash->{READINGS}{msgcnt}{TIME} = TimeNow();
+  my $msgcnt = sprintf("%02x",$dhash->{READINGS}{msgcnt}{VAL});
+
+  my $packet = $msgcnt . $flags . $msgCmd2Id{$cmd} . $src . $dst . $groupId . $payload;
+
+  #prefix length in bytes
+  $packet = sprintf("%02x",length($packet)/2) . $packet;
+
+  Log3 $hash, 5, "CUL_MAX_Send: enqueuing $packet";
+  my $timeout = gettimeofday()+$ackTimeout;
+  my $aref = $hash->{sendQueue};
+  push(@{$aref},  { "packet" => $packet,
+                    "src" => $src,
+                    "dst" => $dst,
+                    "cnt" => hex($msgcnt),
+                    "time" => $timeout,
+                    "sent" => "0",
+                    "cmd" => $cmd,
+                    "callbackParam" => $callbackParam,
+                  });
+
+  #Call CUL_MAX_SendQueueHandler if we just enqueued the only packet
+  #otherwise it is already in the InternalTimer list
+  CUL_MAX_SendQueueHandler($hash,undef) if(@{$hash->{sendQueue}} == 1);
+  return undef;
+}
+
+sub
+CUL_MAX_DeviceHash($)
+{
+  my $addr = shift;
+  return $modules{MAX}{defptr}{$addr};
+}
+
+#This can be called for two reasons:
+#1. @sendQueue was empty, CUL_MAX_Send added a packet and then called us
+#2. We sent a packet from @sendQueue and now the ackTimeout is over.
+#   The packet my still be in @sendQueue (timed out) or removed when the Ack was received.
+# Arguments are hash and responseToShutterContact.
+# If SendQueueHandler was called after receiving a message from a shutter contact, responseToShutterContact
+# holds the address of the respective shutter contact. Otherwise, it is empty.
+sub
+CUL_MAX_SendQueueHandler($$)
+{
+  my $hash = shift;
+  my $responseToShutterContact = shift;
+
+  Log3 $hash, 5, "CUL_MAX_SendQueueHandler: " . @{$hash->{sendQueue}} . " items in queue";
+  return if(!@{$hash->{sendQueue}}); #nothing to do
+
+  my $timeout = gettimeofday(); #reschedule immediatly
+
+  #Check if we have an IODev
+  if(!defined($hash->{IODev})) {
+      Log3 $hash, 1, "$hash->{NAME}: did not find suitable IODev (CUL etc. in rfmode MAX), cannot send! You may want to execute 'attr $hash->{NAME} IODev SomeCUL'";
+      #Maybe some CUL will appear magically in some seconds
+      #At least we cannot quit here with an non-empty queue, so we have two alternatives:
+      #1. Delete the packet from queue and quit -> packet is lost
+      #2. Wait, recheck, wait, recheck ... -> a lot of logs
+
+      #InternalTimer($timeout+60, "CUL_MAX_SendQueueHandler", $hash, 0);
+      $hash->{sendQueue} = [];
+      return undef;
+  }
+
+  my ($packet, $pktIdx, $packetForShutterContactInQueue);
+  for($pktIdx = 0; $pktIdx < @{$hash->{sendQueue}}; $pktIdx += 1) {
+    $packet = $hash->{sendQueue}[$pktIdx];
+
+    if(defined($responseToShutterContact)) {
+      #Find a packet to the ShutterContact in $responseToShutterContact
+      last if($packet->{dst} eq $responseToShutterContact);
+    } else {
+      #We cannot sent packets to a ShutterContact directly, everything else is possible
+      last if($packet->{cmd} eq "PairPong"
+           || $packet->{sent} != 0
+           || $modules{MAX}{defptr}{$packet->{dst}}{type} ne "ShutterContact");
+      $packetForShutterContactInQueue = $modules{MAX}{defptr}{$packet->{dst}}{NAME};
+    }
+  }
+  if($pktIdx == @{$hash->{sendQueue}} && !defined($responseToShutterContact)) {
+    Log3 $hash, 2, "There is a packet for ShutterContact $packetForShutterContactInQueue in queue. Please trigger a window action (open or close the window) to wake up the respective ShutterContact and let it receive the packet.";
+    $timeout += 3;
+    InternalTimer($timeout, "CUL_MAX_SendQueueHandler", $hash, 0);
+    return undef;
+  }
+
+  if( $packet->{sent} == 0 ) { #Need to send it first
+    #We can use fast sending without preamble on culfw 1.53 and higher when the devices has been woken up
+    my $needPreamble = ((CUL_MAX_Check($hash) < 153)
+      || (!defined($responseToShutterContact) &&
+         (!defined($modules{MAX}{defptr}{$packet->{dst}}{wakeUpUntil})
+          || $modules{MAX}{defptr}{$packet->{dst}}{wakeUpUntil} < gettimeofday()))) ? 1 : 0;
+
+    #Send to CUL
+	my ($credit10ms) = (CommandGet("","$hash->{IODev}{NAME} credit10ms") =~ /[^ ]* [^ ]* => (.*)/);
+    if(!defined($credit10ms) || $credit10ms eq "No answer") {
+      Log3 $hash, 1, "Error in CUL_MAX_SendQueueHandler: CUL $hash->{IODev}{NAME} did not answer request for current credits. Waiting 5 seconds.";
+      $timeout += 5;
+    } else {
+      # We need 1000ms for preamble + len in bits (=hex len * 4) ms for payload. Divide by 10 to get credit10ms units
+      # keep this in sync with culfw's code in clib/rf_moritz.c!
+      my $necessaryCredit = ceil(100*$needPreamble + (length($packet->{packet})*4)/10);
+      Log3 $hash, 5, "needPreamble: $needPreamble, necessaryCredit: $necessaryCredit, credit10ms: $credit10ms";
+      if( defined($credit10ms) && $credit10ms < $necessaryCredit ) {
+        my $waitTime = $necessaryCredit-$credit10ms; #we get one credit10ms every second
+        $timeout += $waitTime + 1;
+        Log3 $hash, 2, "CUL_MAX_SendQueueHandler: Not enough credit! credit10ms is $credit10ms, but we need $necessaryCredit. Waiting $waitTime seconds. Currently " . @{$hash->{sendQueue}} . " messages are waiting to be sent.";
+      } else {
+        #Update TimeInformation payload. It should reflect the current time when sending,
+        #not the time when it was enqueued. A low credit10ms can defer such a packet for multiple
+        #minutes
+        if( $msgId2Cmd{substr($packet->{packet},6,2)} eq "TimeInformation" ) {
+          Log3 $hash, 5, "Updating TimeInformation payload";
+          substr($packet->{packet},22) = CUL_MAX_GetTimeInformationPayload();
+        }
+        IOWrite($hash, "", ($needPreamble ? "Zs" : "Zf") . $packet->{packet});
+
+        $packet->{sent} = 1;
+        $packet->{sentTime} = gettimeofday();
+        if(!defined($packet->{retryCnt})){
+           $packet->{retryCnt} = $maxRetryCnt;
+        }
+        $timeout += 0.5; #recheck for Ack
+      }
+    } # $credit10ms ne "No answer"
+
+  } elsif( $packet->{sent} == 1 ) { #Already sent it, got no Ack
+    if( $packet->{sentTime} + $ackTimeout < gettimeofday() ) {
+      # ackTimeout exceeded
+      if( $packet->{retryCnt} > 0 ) {
+          Log3 $hash, 5, "CUL_MAX_SendQueueHandler: Retry $packet->{dst} for $packet->{packet} count: $packet->{retryCnt}";
+          $packet->{sent} = 0;
+          $packet->{retryCnt}--;
+          $timeout += 3;
+      } else {
+          Log3 $hash, 2, "CUL_MAX_SendQueueHandler: Missing ack from $packet->{dst} for $packet->{packet}";
+          splice @{$hash->{sendQueue}}, $pktIdx, 1; #Remove from array
+          readingsSingleUpdate($hash, "packetsLost", ReadingsVal($hash->{NAME}, "packetsLost", 0) + 1, 1);
+     }
+    } else {
+      # Recheck for Ack
+      $timeout += 0.5;
+    }
+
+  } elsif( $packet->{sent} == 2 ) { #Got ack
+    if(defined($packet->{callbackParam})) {
+      Dispatch($hash, "MAX,1,Ack$packet->{cmd},$packet->{dst},$packet->{callbackParam}", {});
+    }
+    splice @{$hash->{sendQueue}}, $pktIdx, 1; #Remove from array
+
+  } elsif( $packet->{sent} == 3 ) { #Got nack
+    splice @{$hash->{sendQueue}}, $pktIdx, 1; #Remove from array
+  }
+
+  return if(!@{$hash->{sendQueue}}); #everything done
+  return if(defined($responseToShutterContact)); #this was not called from InternalTimer
+  InternalTimer($timeout, "CUL_MAX_SendQueueHandler", $hash, 0);
+}
+
+sub
+CUL_MAX_GetTimeInformationPayload()
+{
+  my ($sec,$min,$hour,$day,$mon,$year,$wday,$yday,$isdst) = localtime(time());
+  $mon += 1; #make month 1-based
+  #month encoding is just guessed
+  #perls localtime gives years since 1900, and we need years since 2000
+  return unpack("H*",pack("CCCCC", $year - 100, $day, $hour, $min | (($mon & 0x0C) << 4), $sec | (($mon & 0x03) << 6)));
+}
+
+sub
+CUL_MAX_SendTimeInformation(@)
+{
+  my ($hash,$addr,$payload) = @_;
+  $payload = CUL_MAX_GetTimeInformationPayload() if(!defined($payload));
+  Log3 $hash, 5, "Broadcast time to $addr";
+  CUL_MAX_Send($hash, "TimeInformation", $addr, $payload, flags => "04");
+}
+
+sub
+CUL_MAX_BroadcastTime(@)
+{
+  my ($hash,$manual) = @_;
+  my $payload = CUL_MAX_GetTimeInformationPayload();
+  Log3 $hash, 5, "CUL_MAX_BroadcastTime: payload $payload ";
+  my $i = 1;
+
+  my @used_slots = ( 0, 0, 0, 0, 0, 0 );
+
+  # First, lookup all thermstats for their current TimeInformationHour
+  foreach my $addr (keys %{$modules{MAX}{defptr}}) {
+    my $dhash = $modules{MAX}{defptr}{$addr};
+    if(exists($dhash->{IODev}) && $dhash->{IODev} == $hash
+          && $dhash->{type} =~ /.*Thermostat.*/ ) {
+
+      my $h = ReadingsVal($dhash->{NAME},"TimeInformationHour","");
+      $used_slots[$h]++ if( $h =~ /^[0-5]$/);
+    }
+  }
+
+  foreach my $addr (keys %{$modules{MAX}{defptr}}) {
+    my $dhash = $modules{MAX}{defptr}{$addr};
+    #Check that
+    #1. the MAX device dhash uses this MAX_CUL as IODev
+    #2. the MAX device is a Wall/HeatingThermostat
+    if(exists($dhash->{IODev}) && $dhash->{IODev} == $hash
+    && $dhash->{type} =~ /.*Thermostat.*/
+    && AttrVal($dhash->{NAME},"ignore","0") eq "0" ) {
+
+      my $h = ReadingsVal($dhash->{NAME},"TimeInformationHour",""); 
+      if( $h !~ /^[0-5]$/ ) {
+        #Find the used_slot with the smallest number of entries
+        $h = (sort { $used_slots[$a] cmp $used_slots[$b] } 0 .. 5)[0];
+        readingsSingleUpdate($dhash, "TimeInformationHour", $h, 1);
+        $used_slots[$h]++;
+      }
+
+      CUL_MAX_SendTimeInformation($hash, $addr, $payload) if( [gmtime()]->[2] % 6 == $h );
+    }
+  }
+
+  #Check again in 1 hour if some thermostats with the right TimeInformationHour need updating
+  InternalTimer(gettimeofday() + 3600, "CUL_MAX_BroadcastTime", $hash, 0) unless(defined($manual));
+}
+
+1;
+
+
+=pod
+=begin html
+
+<a name="CUL_MAX"></a>
+<h3>CUL_MAX</h3>
+<ul>
+  The CUL_MAX module interprets MAX! messages received by the CUL. It will be automatically created by autocreate, just make sure
+  that you set the right rfmode like <code>attr CUL0 rfmode MAX</code>.<br>
+  <br><br>
+
+  <a name="CUL_MAXdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_MAX &lt;addr&gt;</code>
+      <br><br>
+
+      Defines an CUL_MAX device of type &lt;type&gt; and rf address &lt;addr&gt. The rf address
+      must not be in use by any other MAX device.
+  </ul>
+  <br>
+
+  <a name="CUL_MAXset"></a>
+  <b>Set</b>
+  <ul>
+      <li>pairmode<br>
+      Sets the CUL_MAX into pairing mode for 60 seconds where it can be paired with 
+      other devices (Thermostats, Buttons, etc.). You also have to set the other device 
+      into pairing mode manually. (For Thermostats, this is pressing the "Boost" button 
+      for 3 seconds, for example).</li>
+      <li>fakeSC &lt;device&gt; &lt;open&gt;<br>
+      Sends a fake ShutterContactState message &lt;open&gt; must be 0 or 1 for 
+      "window closed" or "window opened". If the &lt;device&gt; has a non-zero groupId, 
+      the fake ShutterContactState message affects all devices with that groupId. 
+      Make sure you associate the target device(s) with fakeShutterContact beforehand.</li>
+      <li>fakeWT &lt;device&gt; &lt;desiredTemperature&gt; &lt;measuredTemperature&gt;<br>
+      Sends a fake WallThermostatControl message (parameters both may have one digit 
+      after the decimal point, for desiredTemperature it may only by 0 or 5). 
+      If the &lt;device&gt; has a non-zero groupId, the fake WallThermostatControl 
+      message affects all devices with that groupId. Make sure you associate the target 
+      device with fakeWallThermostat beforehand.</li>
+  </ul>
+  <br>
+
+  <a name="CUL_MAXget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_MAXattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#loglevel">loglevel</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+  <a name="CUL_MAXevents"></a>
+  <b>Generated events:</b>
+  <ul>N/A</ul>
+  <br>
+
+</ul>
+=end html
+=device
+=item summary Uses a CUL (or compatible) to control MAX! devices.
+=item summary_DE Benutzt einen CUL (oder kompatibles Gerät) um MAX! Geräte zu steuern.
+=begin html_DE
+
+<a name="CUL_MAX"></a>
+<h3>CUL_MAX</h3>
+<ul>
+  Das Modul CUL_MAX wertet von einem CUL empfangene MAX! Botschaften aus.
+  Es wird mit Hilfe von autocreate automatisch generiert, es muss nur sichergestellt 
+  werden, dass der richtige rfmode gesetzt wird, z.B. <code>attr CUL0 rfmode MAX</code>.<br>
+  <br>
+
+  <a name="CUL_MAXdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_MAX &lt;addr&gt;</code>
+      <br><br>
+
+      Definiert ein CUL_MAX Ger&auml;t des Typs &lt;type&gt; und der Adresse &lt;addr&gt.
+      Die Adresse darf nicht schon von einem anderen MAX! Ger&auml;t verwendet werden.
+  </ul>
+  <br>
+
+  <a name="CUL_MAXset"></a>
+  <b>Set</b>
+  <ul>
+      <li>pairmode<br>
+      Versetzt den CUL_MAX f&uuml;r 60 Sekunden in den Pairing Modus, w&auml;hrend dieser Zeit
+      kann das Ger&auml;t mit anderen Ger&auml;ten gepaart werden (Heizk&ouml;rperthermostate, 
+      Eco-Taster, etc.). Auch das zu paarende Ger&auml;t muss manuell in den Pairing Modus 
+      versetzt werden (z.B. beim Heizk&ouml;rperthermostat durch Dr&uuml;cken der "Boost" 
+      Taste f&uuml;r 3 Sekunden).</li>
+      <li>fakeSC &lt;device&gt; &lt;open&gt;<br>
+      Sendet eine fingierte <i>ShutterContactState</i> Meldung &lt;open&gt;, dies muss 0 bzw. 1 f&uuml;r
+      "Fenster geschlossen" bzw. "Fenster offen" sein. Wenn das &lt;device&gt; eine Gruppen-ID
+      ungleich Null hat, beeinflusst diese fingierte <i>ShutterContactState</i> Meldung alle Ger&auml;te
+      mit dieser Gruppen-ID. Es muss sichergestellt werden, dass vorher alle Zielger&auml;te 
+      mit <i>fakeShutterContact</i> verbunden werden.</li>
+      <li>fakeWT &lt;device&gt; &lt;desiredTemperature&gt; &lt;measuredTemperature&gt;<br>
+      Sendet eine fingierte <i>WallThermostatControl</i> Meldung (beide Parameter k&ouml;nnen
+      eine Nachkommastelle haben, f&uuml;r <i>desiredTemperature</i> darf die Nachkommastelle nur 0 bzw. 5 sein).
+      Wenn das &lt;device&gt; eine Gruppen-ID ungleich Null hat, beeinflusst diese fingierte 
+      <i>WallThermostatControl</i> Meldung alle Ger&auml;te mit dieser Gruppen-ID.
+      Es muss sichergestellt werden, dass vorher alle Zielger&auml;te 
+      mit <i>fakeWallThermostat</i> verbunden werden.</li>
+  </ul>
+  <br>
+
+  <a name="CUL_MAXget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_MAXattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#loglevel">loglevel</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+  <a name="CUL_MAXevents"></a>
+  <b>Events</b>
+  <ul>N/A</ul>
+  <br>
+
+</ul>
+
+=end html_DE
+=cut

+ 346 - 0
fhem/core/FHEM/14_CUL_REDIRECT.pm

@@ -0,0 +1,346 @@
+##############################################
+# From dancer0705
+#
+# Receive additional protocols received by cul
+#
+# Copyright (C) 2015 Bjoern Hempel
+#
+# This program 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.
+#
+# This program 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 
+# this program; if not, write to the 
+# Free Software Foundation, Inc., 
+# 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+#
+##############################################
+
+package main;
+
+
+use Data::Dumper;
+use strict;
+use warnings;
+
+use SetExtensions;
+use constant { TRUE => 1, FALSE => 0 };
+
+sub
+CUL_REDIRECT_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^o........";
+  $hash->{ParseFn}   = "CUL_REDIRECT_Parse";
+}
+
+#
+# Decode Oregon 2
+#
+sub decodeOrego2 {
+    my $msg = shift;
+    my $name = shift;
+    my @a = split("", $msg);
+
+    Log3 $name, 5, "CUL_REDIRECT decode Oregon 2 ($msg)"; 
+    my $newMSG = "";
+    my $bitData;
+    my $hlen = length($msg);
+	my $blen = $hlen * 4;
+	$bitData= unpack("B$blen", pack("H$hlen", $msg)); 
+	Log3 $name, 5, "bitdata: $bitData";
+    
+    if (index($bitData,"10011001") != -1) 
+    {  # Valid OSV2 detected!	
+	
+	    Log3 $name, 5, "OSV2 protocol detected ($msg)";
+	    
+	    my $preamble_pos=index($bitData,"10011001");
+	    my $message_end=index($bitData,"10011001",$preamble_pos+44);
+       	$message_end = length($bitData) if ($message_end == -1);
+		my $message_length = $message_end - $preamble_pos;
+        my $idx=0;
+		my $osv2bits="";
+		my $osv2hex ="";
+		
+		for ($idx=$preamble_pos;$idx<length($bitData);$idx=$idx+16)
+		{
+			if (length($bitData)-$idx  < 16 )
+			{
+			  last;
+			}
+			my $osv2byte = "";
+			$osv2byte=NULL;
+			$osv2byte=substr($bitData,$idx,16);
+
+			my $rvosv2byte="";
+			
+			for (my $p=1;$p<length($osv2byte);$p=$p+2)
+			{
+				$rvosv2byte = substr($osv2byte,$p,1).$rvosv2byte;
+			}
+			$osv2hex=$osv2hex.sprintf('%02X', oct("0b$rvosv2byte")) ;
+			$osv2bits = $osv2bits.$rvosv2byte;
+		}
+		$osv2hex = sprintf("%02X", length($osv2hex)*4).$osv2hex;
+		if (length($osv2hex)*4 == 88) {
+		    Log3 $name, 5, "CUL_REDIRECT: OSV2 protocol converted to hex: ($osv2hex) with length (".(length($osv2hex)*4).") bits \n";
+            return (1,$osv2hex);
+        } else {
+            Log3 $name, 5, "CUL_REDIRECT: ERROR: To short: OSV2 protocol converted to hex: ($osv2hex) with length (".(length($osv2hex)*4).") bits \n"; 
+            return (-1, "CUL_REDIRECT: ERROR: To short: OSV2 protocol converted to hex: ($osv2hex) with length (".(length($osv2hex)*4).") bits"); 
+        }  
+	}
+	return (-1, "Not a origon 2 protocol");
+}
+#
+# Decode Oregon 3
+#
+sub decodeOrego3 {
+    my $msg = shift;
+    my $name = shift;
+    my @a = split("", $msg);
+
+    Log3 $name, 5, "CUL_REDIRECT decode Oregon 3 ($msg)"; 
+    my $newMSG = "";
+    my $bitData;
+    my $hlen = length($msg);
+	my $blen = $hlen * 4;
+	$bitData= unpack("B$blen", pack("H$hlen", $msg)); 
+	Log3 $name, 5, "bitdata: $bitData";
+    
+    if (index($bitData,"11110101") != -1) 
+    {  # Valid OSV2 detected!	
+	
+	    Log3 $name, 5, "OSV3 protocol detected ($msg)";
+	    
+	    my $message_start=index($bitData,"0101");
+	    my $message_end=length($bitData)-8;
+       	
+		my $message_length = $message_end - $message_start;
+        my $idx=0;
+		my $osv2bits="";
+		my $osv2hex ="";
+		
+		
+		
+		for ($idx=$message_start; $idx<$message_end; $idx=$idx+8)
+		{
+		    if (length($bitData)-$idx  < 16 )
+			{
+			  last;
+			}
+			my $byte = "";
+			$byte= substr($bitData,$idx,8); ## Ignore every 9th bit
+			Log3 $name, 5, "$name: byte in order $byte ";
+			$byte = scalar reverse $byte;
+			Log3 $name, 5, "$name: byte reversed $byte , as hex: ".sprintf('%X', oct("0b$byte"))."\n";
+
+			#$osv2hex=$osv2hex.sprintf('%X', oct("0b$byte"));
+			$osv2hex=$osv2hex.sprintf('%2X', oct("0b$byte")) ;
+		}
+		$osv2hex = sprintf("%2X", length($osv2hex)*4).$osv2hex;
+		if (length($osv2hex)*4 > 87) {
+		    Log3 $name, 5, "CUL_REDIRECT: OSV3 protocol converted to hex: ($osv2hex) with length (".(length($osv2hex)*4).") bits \n";
+            return (1,$osv2hex);
+        } else {
+            Log3 $name, 5, "CUL_REDIRECT: ERROR: To short: OSV3 protocol converted to hex: ($osv2hex) with length (".(length($osv2hex)*4).") bits \n"; 
+            return (-1, "CUL_REDIRECT: ERROR: To short: OSV3 protocol converted to hex: ($osv2hex) with length (".(length($osv2hex)*4).") bits"); 
+        }  
+	}
+	return (-1, "Not a origon 3 protocol");
+}
+
+sub	decode_Hideki
+{
+    my $msg = shift;
+    my $name = shift;
+    my @a = split("", $msg);
+    
+    Log3 $name, 5, "CUL_REDIRECT decode Hideki ($msg)"; 
+    my $bitData;
+    my $hlen = length($msg);
+	my $blen = $hlen * 4;
+    $bitData= unpack("B$blen", pack("H$hlen", $msg)); 
+	
+    Log3 $name, 5, "$name: search in $bitData \n";
+	my $message_start = index($bitData,"10101110");
+	my $length_min = 72;
+    my $length_max = 104;
+	
+	if ($message_start >= 0 )   # 0x75 but in reverse order
+	{
+		Log3 $name, 5, "$name: Hideki protocol detected \n";
+
+		# Todo: Mindest Länge für startpunkt vorspringen 
+		# Todo: Wiederholung auch an das Modul weitergeben, damit es dort geprüft werden kann
+		my $message_end = index($bitData,"10101110",$message_start+18); # pruefen auf ein zweites 0x75,  mindestens 18 bit nach 1. 0x75
+        $message_end = length($bitData) if ($message_end == -1);
+        my $message_length = $message_end - $message_start;
+		
+		return (-1,"message is to short") if ($message_length < $length_min );
+		return (-1,"message is to long") if ($message_length > $length_max );
+
+		
+		my $hidekihex;
+		my $idx;
+		
+		for ($idx=$message_start; $idx<$message_end; $idx=$idx+9)
+		{
+			my $byte = "";
+			$byte= substr($bitData,$idx,8); ## Ignore every 9th bit
+			Log3 $name, 5, "$name: byte in order $byte ";
+			$byte = scalar reverse $byte;
+			Log3 $name, 5, "$name: byte reversed $byte , as hex: ".sprintf('%X', oct("0b$byte"))."\n";
+
+			$hidekihex=$hidekihex.sprintf('%02X', oct("0b$byte"));
+		}
+		Log3 $name, 4, "$name: hideki protocol converted to hex: $hidekihex with " .$message_length ." bits, messagestart $message_start";
+
+		return  (1,$hidekihex); ## Return only the original bits, include length
+	}
+	return (-1,"Not a hideki protocol");
+}
+
+# Function which dispatches a message if needed.
+sub CUL_REDIRECT_Dispatch($$$)
+{
+	my ($hash, $rmsg,$dmsg) = @_;
+	my $name = $hash->{NAME};
+	
+	Log3 $name, 5, "converted Data to ($dmsg)";
+	#if (!defined($hash->{DMSG})) {
+	#    $hash->{DMSG} = "";
+	#}
+	#Dispatch only if $dmsg is different from last $dmsg, or if 2 seconds are between transmits
+    if (($hash->{RAWMSG} ne $dmsg) || ($hash->{TIME}+1 < time()) ) { 
+		#$hash->{MSGCNT}++;
+		$hash->{TIME} = time();
+		#$hash->{DMSG} = $dmsg;
+		#$hash->{IODEV} = "RFXCOM";
+		my $OregonClientMatch=index($hash->{Clients},"OREGON");
+		if ($OregonClientMatch == -1) {
+		    # Append Clients and MatchList for CUL
+		    $hash->{Clients} = $hash->{Clients}.":OREGON:";
+		    $hash->{MatchList}{"C:OREGON"} = "^(3[8-9A-F]|[4-6][0-9A-F]|7[0-8]).*";
+		}
+		my $HidekiClientMatch=index($hash->{Clients},"Hideki");
+		if ($HidekiClientMatch == -1) {
+		    # Append Clients and MatchList for CUL
+		    $hash->{Clients} = $hash->{Clients}.":Hideki:";
+		    $hash->{MatchList}{"C:Hideki"} = "^P12#75[A-F0-9]{17,30}";
+		}
+		readingsSingleUpdate($hash, "state", $hash->{READINGS}{state}{VAL}, 0);
+		
+		$hash->{RAWMSG} = $rmsg;
+		my %addvals = (RAWMSG => $rmsg, DMSG => $dmsg);
+		Dispatch($hash, $dmsg, \%addvals);  ## Dispatch to other Modules 
+	}	else {
+		Log3 $name, 1, "Dropped ($dmsg) due to short time or equal msg";
+	}	
+}
+
+###################################
+sub
+CUL_REDIRECT_Parse($$)
+{
+
+    my ($hash, $msg) = @_;
+    $msg = substr($msg, 1);
+    my @a = split("", $msg);
+    my $name = $hash->{NAME};
+
+    my $rssi;
+    my $l = length($msg);
+    my $dmsg;
+    my $message_dispatched=FALSE;
+    $rssi = substr($msg, $l-2, 2);
+    undef($rssi) if ($rssi eq "00");
+
+    if (defined($rssi))
+    {
+        $rssi = hex($rssi);
+        $rssi = ($rssi>=128 ? (($rssi-256)/2-74) : ($rssi/2-74)) if defined($rssi);
+        Log3 $name, 5, "CUL_REDIRECT ($msg) length: $l RSSI: $rssi";
+    } else {
+        Log3 $name, 5, "CUL_REDIRECT ($msg) length: $l"; 
+    }
+
+    if ("$a[0]" eq "m") {
+        # Orego2
+        Log3 $name, 5, "CUL_REDIRECT ($msg) match Manchester COODE length: $l"; 
+        my ($rcode,$res) = decodeOrego2(substr($msg, 1), $name); 
+	    if ($rcode != -1) {
+			$dmsg = $res;	
+			Log3 $name, 5, "$name Dispatch now to Oregon Module.";	
+			CUL_REDIRECT_Dispatch($hash,$msg,$dmsg);
+			$message_dispatched=TRUE;
+		} 
+		($rcode,$res) = decodeOrego3(substr($msg, 1), $name); 
+	    if ($rcode != -1) {
+			$dmsg = $res;	
+			Log3 $name, 5, "$name Dispatch now to Oregon Module.";	
+			CUL_REDIRECT_Dispatch($hash,$msg,$dmsg);
+			$message_dispatched=TRUE;
+		} 
+		($rcode,$res) = decode_Hideki(substr($msg, 1), $name); 
+		if ($rcode != -1) {
+			$dmsg = 'P12#' . $res;	
+			Log3 $name, 5, "$name Dispatch now to Hideki Module.";	
+			CUL_REDIRECT_Dispatch($hash,$msg,$dmsg);
+			$message_dispatched=TRUE;
+		} 
+		if ($rcode == -1) {
+			Log3 $name, 5, "protocol does not match, ignore received package (" . substr($msg, 1) . ") Reason: $res";
+            return "";
+		}
+        
+    }
+    if ($message_dispatched == FALSE) {
+        return undef;
+    }
+    return "";
+    
+}
+
+1;
+
+
+=pod
+=begin html
+
+<a name="CUL_REDIRECT"></a>
+<h3>CUL_REDIRECT</h3>
+<ul>
+  The CUL_REDIRECT modul receive additional protocols from CUL<br>
+  and redirect them to other modules.
+  <br>
+  
+  <a name="CUL_REDIRECT_Parse"></a>
+
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="CUL_REDIRECT"></a>
+<h3>CUL_REDIRECT</h3>
+<ul>
+  Das CUL_REDIRECT Modul empfängt weitere Protokolle vom CUL<br>
+  und leitet diese an die entsprechenden Module weiter.
+  <br>
+  
+  <a name="CUL_REDIRECT_Parse"></a>
+
+</ul>
+
+=end html_DE
+=cut

ファイルの差分が大きいため隠しています
+ 1384 - 0
fhem/core/FHEM/14_CUL_TCM97001.pm


+ 205 - 0
fhem/core/FHEM/14_CUL_TX.pm

@@ -0,0 +1,205 @@
+##############################################
+# $Id: 14_CUL_TX.pm 12387 2016-10-20 08:07:56Z rudolfkoenig $
+package main;
+
+# From peterp
+# Lacrosse TX3-TH thermo/hygro sensor
+
+use strict;
+use warnings;
+
+sub
+CUL_TX_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^TX..........";        # Need TX to avoid FHTTK
+  $hash->{DefFn}     = "CUL_TX_Define";
+  $hash->{UndefFn}   = "CUL_TX_Undef";
+  $hash->{ParseFn}   = "CUL_TX_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
+                        $readingFnAttributes;
+  $hash->{AutoCreate}=
+        { "CUL_TX.*" => { GPLOT => "temp4hum4:Temp/Hum,", FILTER => "%NAME" } };
+}
+
+#############################
+sub
+CUL_TX_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> CUL_TX <code> [corr] [minsecs]"
+        if(int(@a) < 3 || int(@a) > 5);
+
+  $hash->{CODE} = $a[2];
+  $hash->{corr} = ((int(@a) > 3) ? $a[3] : 0);
+  $hash->{minsecs} = ((int(@a) > 4) ? $a[4] : 0);
+  $hash->{lastT} =  0;
+  $hash->{lastH} =  0;
+
+  $modules{CUL_TX}{defptr}{$a[2]} = $hash;
+  $hash->{STATE} = "Defined";
+
+  return undef;
+}
+
+#####################################
+sub
+CUL_TX_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{CUL_TX}{defptr}{$hash->{CODE}})
+     if(defined($hash->{CODE}) &&
+        defined($modules{CUL_TX}{defptr}{$hash->{CODE}}));
+  return undef;
+}
+
+###################################
+sub
+CUL_TX_Parse($$)
+{
+  my ($hash, $msg) = @_;
+  $msg = substr($msg, 1);
+  # Msg format: TXTHHXYZXY, see http://www.f6fbb.org/domo/sensors/tx3_th.php
+  my @a = split("", $msg);
+  my $id2 = hex($a[4]) & 1; #meaning unknown
+  my $id3 = (hex($a[3])<<3) + (hex($a[4])>>1);
+
+  if($a[5] ne $a[8] || $a[6] ne $a[9]) {
+    Log3 $hash, 4, "CUL_TX $id3 ($msg) data error";
+    return "";
+  }
+
+  my $def = $modules{CUL_TX}{defptr}{$id3};
+  if(!$def) {
+    Log3 $hash, 2, "CUL_TX Unknown device $id3, please define it";
+    return "UNDEFINED CUL_TX_$id3 CUL_TX $id3" if(!$def);
+  }
+  my $now = time();
+
+  my $name = $def->{NAME};
+  return "" if(IsIgnored($name));
+
+  Log3 $name, 4, "CUL_TX $name $id3 ($msg)";
+
+  my ($msgtype, $val);
+  my $valraw = (hex($a[5]).$a[6].".".$a[7]);
+  my $type = $a[2];
+  if($type eq "0") {
+    if($now - $def->{lastT} < $def->{minsecs} ) {
+      return ""; 
+    }
+    $def->{lastT} = $now;
+    $msgtype = "temperature";
+    $val = sprintf("%2.1f", ($valraw - 50 + $def->{corr}) );
+    Log3 $name, 4, "CUL_TX $msgtype $name $id3 T: $val UnknownFlag: $id2";
+
+  } elsif ($type eq "E") {
+    if($now - $def->{lastH} < $def->{minsecs} ) {
+      return ""; 
+    }
+    $def->{lastH} = $now;
+    $msgtype = "humidity";
+    $val = $valraw;
+    Log3 $name, 4, "CUL_TX $msgtype $name $id3 H: $val UnknownFlag: $id2";
+
+  } else {
+    Log3 $name, 2, "CUL_TX $type $name $id3 ($msg) unknown type";
+    return "";
+
+  }
+
+  # I suspect that humidity 0F.F is battery warning. Can someone verify?
+  if($val !~ m/^[0-9.-]*$/) {
+    Log3 $name, 5, "CUL_TX $type $name bogus value $val ($msg)";
+    return "";
+  }
+
+  my $state="";
+  my $t = ReadingsVal($name, "temperature", undef);
+  my $h = ReadingsVal($name, "humidity", undef);
+  if(defined($t) && defined($h)) {
+    $state="T: $t H: $h";
+
+  } elsif(defined($t)) {
+    $state="T: $t";
+
+  } elsif(defined($h)) {
+    $state="H: $h";
+
+  }
+
+  readingsBeginUpdate($def);
+  readingsBulkUpdate($def, "state", $state);
+  readingsBulkUpdate($def, $msgtype, $val);
+  readingsEndUpdate($def, 1);
+
+  return $name;
+}
+
+1;
+
+
+=pod
+=item summary    Some Lacrosse sensors (TX-3TH, etc)
+=item summary_DE Einige Lacrosse Sensoren (TX-3TH, usw.) 
+=begin html
+
+<a name="CUL_TX"></a>
+<h3>CUL_TX</h3>
+<ul>
+  The CUL_TX module interprets TX2/TX3 type of messages received by the CUL,
+  see also http://www.f6fbb.org/domo/sensors/tx3_th.php.
+  This protocol is used by the La Crosse TX3-TH thermo/hygro sensor and other
+  wireless themperature sensors. Please report the manufacturer/model of other
+  working devices.  <br><br>
+
+  <a name="CUL_TXdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_TX &lt;code&gt; [corr] [minsecs]</code> <br>
+
+    <br>
+    &lt;code&gt; is the code of the autogenerated address of the TX device (0
+    to 127)<br>
+    corr is a correction factor, which will be added to the value received from
+    the device.<br>
+    minsecs are the minimum seconds between two log entries or notifications
+    from this device. <br>E.g. if set to 300, logs of the same type will occure
+    with a minimum rate of one per 5 minutes even if the device sends a message
+    every minute. (Reduces the log file size and reduces the time to display
+    the plots)
+  </ul>
+  <br>
+
+  <a name="CUL_TXset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="CUL_TXget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_TXattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+
+  <a name="CUL_TXevents"></a>
+  <b>Generated events:</b>
+  <ul>
+     <li>temperature: $temp</li>
+     <li>humidity: $hum</li>
+  </ul>
+  <br>
+
+</ul>
+
+
+=end html
+=cut

+ 467 - 0
fhem/core/FHEM/14_CUL_WS.pm

@@ -0,0 +1,467 @@
+##############################################
+# $Id: 14_CUL_WS.pm 11984 2016-08-19 12:47:50Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+
+# Supports following devices:
+# KS300TH     (this is redirected to the more sophisticated 14_KS300 by 00_CUL)
+# S300TH  
+# WS2000/WS7000
+#
+
+#####################################
+sub
+CUL_WS_Initialize($)
+{
+  my ($hash) = @_;
+
+  # Message is like
+  # K41350270
+
+  $hash->{Match}     = "^K.....";
+  $hash->{DefFn}     = "CUL_WS_Define";
+  $hash->{UndefFn}   = "CUL_WS_Undef";
+  $hash->{AttrFn}    = "CUL_WS_Attr";
+  $hash->{ParseFn}   = "CUL_WS_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:0,1 showtime:0,1 ".
+                       "model:S300TH,KS300,ASH2200 ignore:0,1 ".
+                       $readingFnAttributes;
+  $hash->{AutoCreate}=
+    { "CUL_WS.*" => { GPLOT => "temp4hum6:Temp/Hum,",  FILTER=>"%NAME:T:.*" } };
+}
+
+
+#####################################
+sub
+CUL_WS_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> CUL_WS <code> [corr1...corr4]"
+            if(int(@a) < 3 || int(@a) > 7);
+  return "Define $a[0]: wrong CODE format: valid is 1-8"
+                if($a[2] !~ m/^[1-8]$/);
+
+  $hash->{CODE} = $a[2];
+  $hash->{corr1} = ((int(@a) > 3) ? $a[3] : 0);
+  $hash->{corr2} = ((int(@a) > 4) ? $a[4] : 0);
+  $hash->{corr3} = ((int(@a) > 5) ? $a[5] : 0);
+  $hash->{corr4} = ((int(@a) > 6) ? $a[6] : 0);
+  $modules{CUL_WS}{defptr}{$a[2]} = $hash;
+  AssignIoPort($hash);
+  return undef;
+}
+
+#####################################
+sub
+CUL_WS_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{CUL_WS}{defptr}{$hash->{CODE}}) if($hash && $hash->{CODE});
+  return undef;
+}
+
+
+#####################################
+sub
+CUL_WS_Parse($$)
+{
+  my ($hash,$msg) = @_;
+  my %tlist = ("0"=>"temp",
+               "1"=>"temp/hum",
+               "2"=>"rain",
+               "3"=>"wind",
+               "4"=>"temp/hum/press",
+               "5"=>"brightness",
+               "6"=>"pyro",
+               "7"=>"temp/hum");
+
+  # -wusel, 2010-01-24: *sigh* No READINGS set, bad for other modules. Trying
+  # to add setting READINGS as well as STATE ...
+  my $NotifyType;
+  my $NotifyHumidity;
+  my $NotifyTemperature;
+  my $NotifyRain;
+  my $NotifyIsRaining;
+  my $NotifyWind;
+  my $NotifyWindDir;
+  my $NotifyWindSwing;
+  my $NotifyBrightness;
+  my $NotifyPressure;
+  my %NotifyMappings = (
+      "T"      => "temperature",
+      "H"      => "humidity",
+      "R"      => "rain",
+      "IR"     => "is_raining",
+      "W"      => "wind",
+      "WD"     => "wind_direction",
+      "WS"     => "wind_swing",
+      "B"      => "brightness",
+      "P"      => "pressure",
+  );
+ 
+
+  my @a = split("", $msg);
+
+  my $firstbyte = hex($a[1]);
+  my $cde = ($firstbyte&7) + 1;
+  my $type = $tlist{$a[2]} ? $tlist{$a[2]} : "unknown";
+
+  # There are only 8 S300 devices. In order to enable more, we try to look up
+  # the name in connection with the receiver's name ("CUL868.1", "CUL433.1")
+  # See attr <name> IODev XX
+
+  my $def = $modules{CUL_WS}{defptr}{$hash->{NAME} . "." . $cde};
+  $def = $modules{CUL_WS}{defptr}{$cde} if(!$def);
+  if(!$def) {
+    Log3 $hash, 1, "CUL_WS UNDEFINED $type sensor detected, code $cde";
+    return "UNDEFINED CUL_WS_$cde CUL_WS $cde";
+  }
+
+  $hash = $def;
+  my $name = $hash->{NAME};
+  return "" if(IsIgnored($name));
+ 
+  my $typbyte = hex($a[2]) & 7;
+  my $sfirstbyte = $firstbyte & 7;
+  my $val = "";
+  my $devtype = "unknown";
+  my $family  = "unknown";
+  my ($sgn, $tmp, $rain, $hum, $prs, $wnd);
+
+  if($sfirstbyte == 7) {
+  
+    if($typbyte == 0 && int(@a) > 6) {           # temp
+      $sgn = ($firstbyte&8) ? -1 : 1;
+      $tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
+      $val = "T: $tmp";
+      $devtype = "Temp";
+      $NotifyType="T";
+      $NotifyTemperature=$tmp;
+    }
+
+    if($typbyte == 1 && int(@a) > 8) {           # temp/hum
+      $sgn = ($firstbyte&8) ? -1 : 1;
+      $tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
+      $hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
+      $val = "T: $tmp  H: $hum";
+      $devtype = "PS50";
+      $family = "WS300";
+      $NotifyType="T H";
+      $NotifyTemperature=$tmp;
+      $NotifyHumidity=$hum;
+    }
+
+    if($typbyte == 2 && int(@a) > 5) {           # rain
+      #my $more = ($firstbyte&8) ? 0 : 1000;
+      my $c = $hash->{corr1} ? $hash->{corr1} : 1;
+      $rain = hex($a[5].$a[3].$a[4]) * $c;
+      $val = "R: $rain";
+      $devtype =  "Rain";
+      $family = "WS7000";
+      $NotifyType="R";
+      $NotifyRain=$rain;
+   }
+
+    if($typbyte == 3 && int(@a) > 8) {           # wind
+      my $hun = ($firstbyte&8) ? 100 : 0;
+      $wnd = ($a[6].$a[3].".".$a[4])+$hun;
+      my $dir  = ((hex($a[7])&3).$a[8].$a[5])+0;
+      my $swing = (hex($a[7])&6) >> 2;
+      $val = "W: $wnd D: $dir A: $swing";
+      $devtype = "Wind";
+      $family = "WS7000";
+      $NotifyType="W WD WS";
+      $NotifyWind=$wnd;
+      $NotifyWindDir=$dir;
+      $NotifyWindSwing=$swing;
+    }
+
+    if($typbyte == 4 && int(@a) > 10) {          # temp/hum/press
+      $sgn = ($firstbyte&8) ? -1 : 1;
+      $tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
+      $hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
+      $prs = ($a[9].$a[10])+ 900 + $hash->{corr3};
+      if($prs < 930) {
+        $prs = $prs + 100;
+      }
+      $val = "T: $tmp  H: $hum  P: $prs";
+      $devtype = "Indoor";
+      $family = "WS7000";
+      $NotifyType="T H P";
+      $NotifyTemperature=$tmp;
+      $NotifyHumidity=$hum;
+      $NotifyPressure=$prs;
+    }
+
+    if($typbyte == 5 && int(@a) > 5) {           # brightness
+      my $fakt = 1;
+      my $rawfakt = ($a[6])+0;
+      if($rawfakt == 1) { $fakt =   10; }
+      if($rawfakt == 2) { $fakt =  100; }
+      if($rawfakt == 3) { $fakt = 1000; }
+     
+      my $br = (hex($a[5].$a[4].$a[3])*$fakt)  + $hash->{corr1};
+      $val = "B: $br";
+      $devtype = "Brightness";
+      $family = "WS7000";
+      $NotifyType="B";
+      $NotifyBrightness=$br;
+    }
+
+    if($typbyte == 6 && int(@a) > 0) {           # Pyro: wurde nie gebaut
+      $devtype = "Pyro";
+      $family = "WS7000";
+    }
+
+    if($typbyte == 7 && int(@a) > 8) {           # Temp/hum
+      $sgn = ($firstbyte&8) ? -1 : 1;
+      $tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
+      $hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
+      $val = "T: $tmp  H: $hum";
+      $devtype = "Temp/Hum";
+      $family = "WS7000";
+      $NotifyType="T H";
+      $NotifyTemperature=$tmp;
+      $NotifyHumidity=$hum;
+    }
+    
+  } else {                                      # $firstbyte not 7
+
+    if(@a == 9 && int(@a) > 8) {                 #  S300TH
+      # Sanity check
+      if (!($msg =~ /^K[0-9A-F]\d\d\d\d\d\d\d$/ )) {
+        Log3 $name, 1,
+            "Error: S300TH CUL_WS Cannot decode $msg (sanitycheck). Malformed";
+        return "";
+      }
+
+      $sgn = ($firstbyte&8) ? -1 : 1;
+      $tmp = sprintf("%0.1f", $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1});
+      $hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
+      $val = "T: $tmp  H: $hum";
+      $devtype = "S300TH";
+      $family = "WS300";
+      $NotifyType="T H";
+      $NotifyTemperature=$tmp;
+      $NotifyHumidity=$hum;
+
+    } elsif(@a == 15 && int(@a) > 14) {          # KS300/2
+      my $c = $hash->{corr4} ? $hash->{corr4} : 255;
+      $rain = sprintf("%0.1f", hex("$a[14]$a[11]$a[12]") * $c / 1000);
+      $wnd  = sprintf("%0.1f", "$a[9]$a[10].$a[7]" + $hash->{corr3});
+      $hum  = sprintf( "%02d", "$a[8]$a[5]" + $hash->{corr2});
+      $tmp  = sprintf("%0.1f", ("$a[6]$a[3].$a[4]"+ $hash->{corr1}),
+                             (($a[1] & 0xC) ? -1 : 1));
+      my $ir = ((hex($a[1]) & 2)) ? "yes" : "no";
+
+      $val = "T: $tmp  H: $hum  W: $wnd  R: $rain  IR: $ir";
+      $devtype = "KS300/2";
+      $family = "WS300";
+      $NotifyType="T H W R IR";
+      $NotifyTemperature=$tmp;
+      $NotifyHumidity=$hum;
+      $NotifyWind=$wnd;
+      $NotifyRain=$rain;
+      $NotifyIsRaining=$ir;
+
+   } elsif(int(@a) > 8) {                       # WS7000 Temp/Hum sensors
+      if(join("", @a[3..8]) =~ m/^\d*$/) {      # Forum 49125
+        $sgn = ($firstbyte&8) ? -1 : 1;
+        $tmp = $sgn * ($a[6].$a[3].".".$a[4]) + $hash->{corr1};
+        $hum = ($a[7].$a[8].".".$a[5]) + $hash->{corr2};
+        $val = "T: $tmp  H: $hum";
+        $devtype = "TH".$sfirstbyte;
+        $family = "WS7000";
+        $NotifyType="T H";
+        $NotifyTemperature=$tmp;
+        $NotifyHumidity=$hum;
+      }
+    }
+
+  }
+
+  if(!$val) {
+    Log3 $name, 1, "CUL_WS Cannot decode $msg";
+    return "";
+  }
+  Log3 $name, 4, "CUL_WS $devtype $name: $val";
+
+  # Sanity checks
+  if($NotifyTemperature &&
+     $hash->{READINGS}{temperature} &&
+     $hash->{READINGS}{temperature}{VAL}) {
+    my $tval = $hash->{READINGS}{strangetemp} ? 
+               $hash->{READINGS}{strangetemp}{VAL} : 
+               $hash->{READINGS}{temperature}{VAL};
+    my $diff = ($NotifyTemperature - $tval)+0;
+    if($diff < -15.0 || $diff > 15.0) {
+      Log3 $name, 2,
+        "$name: Temp difference ($diff) too large: $val, skipping it";
+      $hash->{READINGS}{strangetemp}{VAL} = $NotifyTemperature;
+      $hash->{READINGS}{strangetemp}{TIME} = TimeNow();
+      return "";
+    }
+  }
+  delete $hash->{READINGS}{strangetemp} if($hash->{READINGS});
+
+  if(defined($hum) && ($hum < 0 || $hum > 100)) {
+    Log3 $name, 1, "BOGUS: $name reading: $val, skipping it";
+    return "";
+  }
+
+  readingsBeginUpdate($hash);
+  readingsBulkUpdate($hash, "state", $val);
+
+  my $i=1;
+  my $j;
+  my @Notifies=split(" ", $NotifyType);
+
+  for($j=0; $j<int(@Notifies); $j++) {
+    my $val = "";
+         if($Notifies[$j] eq "T")  { $val = $NotifyTemperature;
+    } elsif($Notifies[$j] eq "H")  { $val = $NotifyHumidity;
+    } elsif($Notifies[$j] eq "R")  { $val = $NotifyRain;
+    } elsif($Notifies[$j] eq "W")  { $val = $NotifyWind;
+    } elsif($Notifies[$j] eq "WD") { $val = $NotifyWindDir;
+    } elsif($Notifies[$j] eq "WS") { $val = $NotifyWindSwing;
+    } elsif($Notifies[$j] eq "IR") { $val = $NotifyIsRaining;
+    } elsif($Notifies[$j] eq "B")  { $val = $NotifyBrightness;
+    } elsif($Notifies[$j] eq "P")  { $val = $NotifyPressure;
+    }
+    my $nm = $NotifyMappings{$Notifies[$j]};
+
+    readingsBulkUpdate($hash, $nm, $val);
+  }
+
+  readingsBulkUpdate($hash, "DEVTYPE", $devtype, 0);
+  readingsBulkUpdate($hash, "DEVFAMILY", $family, 0);
+  readingsEndUpdate($hash, 1); # Notify is done by Dispatch
+
+  return $name;
+}
+
+sub
+CUL_WS_Attr(@)
+{
+  my @a = @_;
+
+  # Make possible to use the same code for different logical devices when they
+  # are received through different physical devices.
+  return if($a[0] ne "set" || $a[2] ne "IODev");
+  my $hash = $defs{$a[1]};
+  my $iohash = $defs{$a[3]};
+  my $cde = $hash->{CODE};
+  delete($modules{CUL_WS}{defptr}{$cde});
+  $modules{CUL_WS}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
+  return undef;
+}
+
+
+1;
+
+=pod
+=item summary    devices communicating via the ELV WS protocol (S300TH, etc)
+=item summary_DE Anbindung von ELV Ger&auml;ten mit dem WS Protokoll (S300TH, usw.)
+=begin html
+
+<a name="CUL_WS"></a>
+<h3>CUL_WS</h3>
+<ul>
+  The CUL_WS module interprets S300 type of messages received by the CUL.
+  <br><br>
+
+  <a name="CUL_WSdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_WS &lt;code&gt; [corr1...corr4]</code> <br>
+    <br>
+    &lt;code&gt; is the code which must be set on the S300 device. Valid values
+    are 1 through 8.<br>
+    corr1..corr4 are up to 4 numerical correction factors, which will be added
+    to the respective value to calibrate the device. Note: rain-values will be
+    multiplied and not added to the correction factor.
+  </ul>
+  <br>
+
+  <a name="CUL_WSset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="CUL_WSget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_WSattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#IODev">IODev</a>
+      Note: by setting this attribute you can define different sets of 8
+      devices in FHEM, each set belonging to a CUL. It is important, however,
+      that a device is only received by the CUL defined, e.g. by using
+      different Frquencies (433MHz vs 868MHz)
+      </li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#model">model</a> (S300,KS300,ASH2200)</li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="CUL_WS"></a>
+<h3>CUL_WS</h3>
+<ul>
+  Das CUL_WS-Modul entschl&uuml;sselt die Nachrichten des Types S300, die von
+  dem CUL empfangen wurden.
+  <br><br>
+
+  <a name="CUL_WSdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_WS &lt;code&gt; [corr1...corr4]</code> <br>
+    <br>
+        &lt;code&gt; ist der Code, der an dem S300 eingestellt werden muss.
+        G&uuml;ltige Werte sind 1 bis 8 
+    <br>
+    corr1..corr4 entsprechen vier m&ouml;glichen Korrekturwerten, die den
+    jeweiligen Werten hinzuaddiert werden, um die Ger&auml;te zu kalibrieren.
+    Hinweis: Bei den Werten f&uuml;r Regenmengen werden die Korrekturwerte
+    nicht hinzuaddiert, sondern als Faktor mit dem Regenwert multipliziert.
+  </ul>
+  <br>
+
+  <a name="CUL_WSset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="CUL_WSget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_WSattr"></a>
+  <b>Attribute</b>
+  <ul>
+    <li><a href="#IODev">IODev (!)</a>
+      Achtung: mit diesem Attribut ist es m&ouml;glich mehrere 8-er Sets an
+      S300-er in FHEM zu definieren. Wichtige Voraussetzung allerdings ist,
+      dass nur das spezifizierte CUL das S300 empfangen kann, z.Bsp. durch
+      Frequenztrennung (433MHz vs. 868MHz).
+      </li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#model">model</a> (S300,KS300,ASH2200)</li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+</ul>
+=end html_DE
+
+=cut

+ 484 - 0
fhem/core/FHEM/14_Hideki.pm

@@ -0,0 +1,484 @@
+##############################################
+# $Id: 14_Hideki.pm 12722 2016-12-06 21:03:14Z mrsidey $
+# The file is taken from the SIGNALduino project
+# see http://www.fhemwiki.de/wiki/SIGNALduino
+# and was modified by a few additions
+# to support Hideki Sensors
+# S. Butzek & HJGode & Ralf9 2015-2016
+#
+
+package main;
+
+use strict;
+use warnings;
+use POSIX;
+
+#use Data::Dumper;
+
+#####################################
+sub
+Hideki_Initialize($)
+{
+  my ($hash) = @_;
+
+
+  $hash->{Match}     = "^P12#75[A-F0-9]{17,30}";   # Laenge (Anhahl nibbles nach 0x75 )noch genauer spezifizieren
+  $hash->{DefFn}     = "Hideki_Define";
+  $hash->{UndefFn}   = "Hideki_Undef";
+  $hash->{AttrFn}    = "Hideki_Attr";
+  $hash->{ParseFn}   = "Hideki_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:0,1 showtime:0,1 "
+                       ."ignore:0,1 "
+                      ." $readingFnAttributes";
+                      
+  $hash->{AutoCreate}=
+        { "Hideki.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"} };
+
+}
+
+
+#####################################
+sub
+Hideki_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> Hideki <code>".int(@a)
+		if(int(@a) < 3);
+
+  $hash->{CODE}    = $a[2];
+  $hash->{lastMSG} =  "";
+
+  my $name= $hash->{NAME};
+
+  $modules{Hideki}{defptr}{$a[2]} = $hash;
+  $hash->{STATE} = "Defined";
+
+  #AssignIoPort($hash);
+  return undef;
+}
+
+#####################################
+sub
+Hideki_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{Hideki}{defptr}{$hash->{CODE}}) if($hash && $hash->{CODE});
+  return undef;
+}
+
+
+#####################################
+sub
+Hideki_Parse($$)
+{
+	my ($iohash,$msg) = @_;
+	my (undef ,$rawData) = split("#",$msg);
+
+	my $name = $iohash->{NAME};
+	my @a = split("", $msg);
+	Log3 $iohash, 4, "Hideki_Parse $name incomming $msg";
+
+	# decrypt bytes
+	my $decodedString = decryptBytes($rawData); # decrpyt hex string to hex string
+
+	#convert dectypted hex str back to array of bytes:
+	my @decodedBytes  = map { hex($_) } ($decodedString =~ /(..)/g);
+
+	if (!@decodedBytes)
+	{
+		Log3 $iohash, 4, "$name decrypt failed";
+		return '';
+	}
+	
+	my $sensorTyp=getSensorType($decodedBytes[3]);
+	Log3 $iohash, 4, "Hideki_Parse SensorTyp = $sensorTyp decodedString = $decodedString";		
+
+	if (!Hideki_crc(\@decodedBytes))
+	{
+		Log3 $iohash, 4, "$name crc failed";
+		return '';
+	}
+
+	my $id=substr($decodedString,2,2);      # get the random id from the data
+	my $channel=0;
+	my $temp=0;
+	my $hum=0;
+	my $rain=0;
+	my $rc;
+	my $val;
+	my $bat;
+	my $deviceCode;
+	my $model= "Hideki_$sensorTyp";
+
+	## 1. Detect what type of sensor we have, then call specific function to decode
+	if ($sensorTyp==0x1E){
+		($channel, $temp, $hum) = decodeThermoHygro(\@decodedBytes); # decodeThermoHygro($decodedString);
+		$bat = ($decodedBytes[2] >> 6 == 3) ? 'ok' : 'low';			 # decode battery
+		$val = "T: $temp H: $hum Bat: $bat";
+	}elsif($sensorTyp==0x0E){
+		($channel, $rain) = decodeRain(\@decodedBytes); # decodeThermoHygro($decodedString);
+		$bat = ($decodedBytes[2] >> 6 == 3) ? 'ok' : 'low';			 # decode battery
+		$val = "R: $rain Bat: $bat";
+	}
+	else{
+		Log3 $iohash, 4, "$name Sensor Typ $sensorTyp not supported, please report sensor information!";
+		return "";
+	}
+	my $longids = AttrVal($iohash->{NAME},'longids',0);
+	if ( ($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/)))
+	{
+		$deviceCode=$model . "_" . $id . "." . $channel;
+		Log3 $iohash,4, "$name using longid: $longids model: $model";
+	} else {
+		$deviceCode = $model . "_" . $channel;
+	}
+
+	Log3 $iohash, 4, "$name decoded Hideki protocol model=$model, sensor id=$id, channel=$channel, temp=$temp, humidity=$hum, bat=$bat, rain=$rain";
+	Log3 $iohash, 5, "deviceCode: $deviceCode";
+
+	my $def = $modules{Hideki}{defptr}{$iohash->{NAME} . "." . $deviceCode};
+	$def = $modules{Hideki}{defptr}{$deviceCode} if(!$def);
+
+	if(!$def) {
+		Log3 $iohash, 1, "Hideki: UNDEFINED sensor $sensorTyp detected, code $deviceCode";
+		return "UNDEFINED $deviceCode Hideki $deviceCode";
+	}
+
+	my $hash = $def;
+	$name = $hash->{NAME};
+	return "" if(IsIgnored($name));
+
+	#Log3 $name, 4, "Hideki: $name ($msg)";
+	
+	
+	if (!defined(AttrVal($hash->{NAME},"event-min-interval",undef)))
+	{
+		my $minsecs = AttrVal($iohash->{NAME},'minsecs',0);
+		if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) {
+			Log3 $iohash, 4, "$deviceCode Dropped ($decodedString) due to short time. minsecs=$minsecs";
+		  	return "";
+		}
+	}
+	$hash->{lastReceive} = time();
+
+	$def->{lastMSG} = $decodedString;
+
+	#Log3 $name, 4, "Hideki update $name:". $name;
+
+	readingsBeginUpdate($hash);
+	readingsBulkUpdate($hash, "state", $val);
+	readingsBulkUpdate($hash, "battery", $bat)   if ($bat ne "");
+	readingsBulkUpdate($hash, "channel", $channel) if ($channel ne "");
+	if ($sensorTyp==0x1E){
+	  readingsBulkUpdate($hash, "humidity", $hum) if ($hum ne "");
+	  readingsBulkUpdate($hash, "temperature", $temp) if ($temp ne "");
+	}
+	elsif($sensorTyp==0x0E){
+	  readingsBulkUpdate($hash, "rain", $rain) if ($rain ne "");
+	}
+	readingsEndUpdate($hash, 1); # Notify is done by Dispatch
+
+	return $name;
+}
+
+# check crc for incoming message
+# in: hex string with encrypted, raw data, starting with 75
+# out: 1 for OK, 0 for failed
+# sample "75BDBA4AC2BEC855AC0A00"
+sub Hideki_crc{
+	#my $Hidekihex=shift;
+	#my @Hidekibytes=shift;
+
+	my @Hidekibytes = @{$_[0]};
+	#push @Hidekibytes,0x75; #first byte always 75 and will not be included in decrypt/encrypt!
+	#convert to array except for first hex
+	#for (my $i=1; $i<(length($Hidekihex))/2; $i++){
+    #	my $hex=Hideki_decryptByte(hex(substr($Hidekihex, $i*2, 2)));
+	#	push (@Hidekibytes, $hex);
+	#}
+
+	my $cs1=0; #will be zero for xor over all (bytes>>1)&0x1F except first byte (always 0x75)
+	#my $rawData=shift;
+	#todo add the crc check here
+
+	my $count=($Hidekibytes[2]>>1) & 0x1f;
+	my $b;
+	#iterate over data only, first byte is 0x75 always
+	for (my $i=1; $i<$count+2 && $i<scalar @Hidekibytes; $i++) {
+		$b =  $Hidekibytes[$i];
+		$cs1 = $cs1 ^ $b; # calc first chksum
+	}
+	if($cs1==0){
+		return 1;
+	}
+	else{
+		return 0;
+	}
+}
+
+# return decoded sensor type
+# in: one byte
+# out: one byte
+# Der Typ eines Sensors steckt in Byte 3:
+# Byte3 & 0x1F  Device
+# 0x0C	      Anemometer
+# 0x0D	      UV sensor
+# 0x0E	      Rain level meter
+# 0x1E	      Thermo/hygro-sensor
+sub getSensorType{
+	return $_[0] & 0x1F;
+}
+
+# decrypt bytes of hex string
+# in: hex string
+# out: decrypted hex string
+sub decryptBytes{
+	my $Hidekihex=shift;
+	#create array of hex string
+	my @Hidekibytes  = map { Hideki_decryptByte(hex($_)) } ($Hidekihex =~ /(..)/g);
+
+	my $result="75";  # Byte 0 is not encrypted
+	for (my $i=1; $i<scalar (@Hidekibytes); $i++){
+		$result.=sprintf("%02x",$Hidekibytes[$i]);
+	}
+	return $result;
+}
+
+sub Hideki_decryptByte{
+	my $byte = shift;
+	#printf("\ndecryptByte 0x%02x >>",$byte);
+	my $ret2 = ($byte ^ ($byte << 1) & 0xFF); #gives possible overflow to left so c3->145 instead of 45
+	#printf(" %02x\n",$ret2);
+	return $ret2;
+}
+
+# decode byte array and return channel, temperature and hunidity
+# input: decrypted byte array starting with 0x75, passed by reference as in mysub(\@array);
+# output <return code>, <channel>, <temperature>, <humidity>
+# was unable to get this working with an array ref as input, so switched to hex string input
+sub decodeThermoHygro {
+	my @Hidekibytes = @{$_[0]};
+
+	#my $Hidekihex = shift;
+	#my @Hidekibytes=();
+	#for (my $i=0; $i<(length($Hidekihex))/2; $i++){
+	#	my $hex=hex(substr($Hidekihex, $i*2, 2)); ## Mit split und map geht es auch ... $str =~ /(..?)/g;
+	#	push (@Hidekibytes, $hex);
+	#}
+	my $channel=0;
+	my $temp=0;
+	my $humi=0;
+
+	$channel = $Hidekibytes[1] >> 5;
+	# //Internally channel 4 is used for the other sensor types (rain, uv, anemo).
+	# //Therefore, if channel is decoded 5 or 6, the real value set on the device itself is 4 resp 5.
+	if ($channel >= 5) {
+		$channel--;
+	}
+	my $sensorId = $Hidekibytes[1] & 0x1f;  		# Extract random id from sensor
+	#my $devicetype = $Hidekibytes[3]&0x1f;
+	$temp = 100 * ($Hidekibytes[5] & 0x0f) + 10 * ($Hidekibytes[4] >> 4) + ($Hidekibytes[4] & 0x0f);
+	## // temp is negative?
+	if (!($Hidekibytes[5] & 0x80)) {
+		$temp = -$temp;
+	}
+
+	$humi = 10 * ($Hidekibytes[6] >> 4) + ($Hidekibytes[6] & 0x0f);
+
+	$temp = $temp / 10;
+	return ($channel, $temp, $humi);
+}
+
+# decode byte array and return channel and total rain in mm
+# input: decrypted byte array starting with 0x75, passed by reference as in mysub(\@array);
+# output <return code>, <channel>, <totalrain>
+# was unable to get this working with an array ref as input, so switched to hex string input
+sub decodeRain {
+	my @Hidekibytes = @{$_[0]};
+
+	#my $Hidekihex = shift;
+	#my @Hidekibytes=();
+	#for (my $i=0; $i<(length($Hidekihex))/2; $i++){
+	#	my $hex=hex(substr($Hidekihex, $i*2, 2)); ## Mit split und map geht es auch ... $str =~ /(..?)/g;
+	#	push (@Hidekibytes, $hex);
+	#}
+	my $channel=0;
+	my $rain=0;
+
+	my $tests=0;
+	#additional checks?
+	if($Hidekibytes[2]==0xCC){
+	  $tests+=1;
+	}
+	if($Hidekibytes[6]==0x66){
+	  $tests+=1;
+	}
+	# possibly test if $tests==2 for sanity check
+	#printf("SANITY CHECK tests=%i\n", $tests);
+	
+	$channel = $Hidekibytes[1] >> 5;
+	# //Internally channel 4 is used for the other sensor types (rain, uv, anemo).
+	# //Therefore, if channel is decoded 5 or 6, the real value set on the device itself is 4 resp 5.
+	if ($channel >= 5) {
+		$channel--;
+	}
+	my $sensorId = $Hidekibytes[1] & 0x1f;  		# Extract random id from sensor
+	
+	$rain = ($Hidekibytes[4] + $Hidekibytes[5]*0xff)*0.7;
+
+	return ($channel, $rain);
+}
+
+sub
+Hideki_Attr(@)
+{
+  my @a = @_;
+
+  # Make possible to use the same code for different logical devices when they
+  # are received through different physical devices.
+  return if($a[0] ne "set" || $a[2] ne "IODev");
+  my $hash = $defs{$a[1]};
+  my $iohash = $defs{$a[3]};
+  my $cde = $hash->{CODE};
+  delete($modules{Hideki}{defptr}{$cde});
+  $modules{Hideki}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
+  return undef;
+}
+
+
+1;
+
+=pod
+=item summary    Supports various rf sensors with hideki protocol
+=item summary_DE Unterst&uumltzt verschiedenen Funksensoren mit hideki Protokol
+=begin html
+
+<a name="Hideki"></a>
+<h3>Hideki</h3>
+<ul>
+  The Hideki module is a module for decoding weather sensors, which use the hideki protocol. Known brands are Bresser, Cresta, TFA and Hama.
+  <br><br>
+
+  <a name="Hideki_define"></a>
+  <b>Supported Brands</b>
+  <ul>
+  	<li>Hama</li>
+  	<li>Bresser</li>
+  	<li>TFA Dostman</li>
+  	<li>Arduinos with remote Sensor lib from Randy Simons</li>
+  	<li>Cresta</li>
+  	<li>Hideki</li>
+  	<li>all other devices, which use the Hideki protocol</li>
+  </ul>
+  Please note, currently temp/hum devices are implemented. Please report data for other sensortypes.
+
+  <a name="Hideki_define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; Hideki &lt;code&gt; </code> <br>
+
+    <br>
+    &lt;code&gt; is the address of the sensor device and
+	is build by the sensor type and the channelnumber (1 to 5) or if the attribute longid is specfied an autogenerated address build when inserting
+	the battery (this adress will change every time changing the battery).<br>
+	
+	If autocreate is enabled, the device will be defined via autocreate. This is also the preferred mode of defining such a device.
+
+  </ul>
+  <a name="Hideki_readings"></a>
+  <b>Generated readings</b>
+  <ul>
+  	<li>state (T:x H:y B:z)</li>
+	<li>temperature (&deg;C)</li>
+	<li>humidity (0-100)</li>
+	<li>battery (ok or low)</li>
+	<li>channel (The Channelnumber (number if)</li>
+  </ul>
+  
+  
+  <a name="Hideki_unset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="Hideki_unget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="Hideki_attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="Hideki"></a>
+<h3>Hideki</h3>
+<ul>
+  Das Hideki module dekodiert empfangene Nachrichten von Wettersensoren, welche das Hideki Protokoll verwenden. 
+  <br><br>
+  
+  <a name="Hideki_define"></a>
+  <b>Unterstuetzte Hersteller</b>
+  <ul>
+  	<li>Hama</li>
+  	<li>Bresser</li>
+  	<li>TFA Dostman</li>
+  	<li>Arduinos with remote Sensor lib from Randy Simons</li>
+  	<li>Cresta</li>
+  	<li>Hideki</li>
+  	<li>Alle anderen, welche das Hideki Protokoll verwenden</li>
+  </ul>
+  Hinweis, Aktuell sind nur temp/feuchte Sensoren implementiert. Bitte sendet uns Daten zu anderen Sensoren.
+  
+  <a name="Hideki_define"></a>
+  <b>Define</b>
+  <ul>
+  	<li><code>define &lt;name&gt; Hideki &lt;code&gt; </code></li>
+	<li>
+    <br>
+    &lt;code&gt; besteht aus dem Sensortyp und der Kanalnummer (1..5) oder wenn das Attribut longid im IO Device gesetzt ist aus einer Zufallsadresse, die durch den Sensor beim einlegen der
+	Batterie generiert wird (Die Adresse aendert sich bei jedem Batteriewechsel).<br>
+    </li>
+    <li>Wenn autocreate aktiv ist, dann wird der Sensor automatisch in FHEM angelegt. Das ist der empfohlene Weg, neue Sensoren hinzuzuf&uumlgen.</li>
+   
+  </ul>
+  <br>
+
+  <a name="Hideki_readings"></a>
+  <b>Erzeugte Readings</b>
+  <ul>
+  	<li>state (T:x H:y B:z)</li>
+	<li>temperature (&deg;C)</li>
+	<li>humidity (0-100)</li>
+	<li>battery (ok or low)</li>
+	<li>channel (Der Sensor Kanal)</li>
+  </ul>
+  <a name="Hideki_unset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="Hideki_unget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="Hideki_attr"></a>
+  <b>Attribute</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#eventMap">eventMap</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+</ul>
+
+=end html_DE
+=cut

+ 511 - 0
fhem/core/FHEM/14_SD_WS.pm

@@ -0,0 +1,511 @@
+##############################################
+# $Id: 14_SD_WS.pm 13215 2017-01-23 20:09:44Z Sidey $
+#
+# The purpose of this module is to support serval
+# weather sensors which use various protocol
+# Sidey79 & Ralf9  2016
+#
+
+package main;
+
+
+use strict;
+use warnings;
+
+#use Data::Dumper;
+
+
+sub SD_WS_Initialize($)
+{
+	my ($hash) = @_;
+
+	$hash->{Match}		= '^W\d+x{0,1}#.*';
+	$hash->{DefFn}		= "SD_WS_Define";
+	$hash->{UndefFn}	= "SD_WS_Undef";
+	$hash->{ParseFn}	= "SD_WS_Parse";
+	$hash->{AttrFn}		= "SD_WS_Attr";
+	$hash->{AttrList}	= "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
+				"$readingFnAttributes ";
+	$hash->{AutoCreate} =
+	{ 
+		"SD_WS37_TH.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,",  autocreateThreshold => "2:180"},
+		"SD_WS50_SM.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,",  autocreateThreshold => "2:180"},
+		"BresserTemeo.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,", autocreateThreshold => "2:180"}
+
+	};
+
+}
+
+
+
+
+#############################
+sub SD_WS_Define($$)
+{
+	my ($hash, $def) = @_;
+	my @a = split("[ \t][ \t]*", $def);
+
+	return "wrong syntax: define <name> SD_WS <code> ".int(@a) if(int(@a) < 3 );
+
+	$hash->{CODE} = $a[2];
+	$hash->{lastMSG} =  "";
+	$hash->{bitMSG} =  "";
+
+	$modules{SD_WS}{defptr}{$a[2]} = $hash;
+	$hash->{STATE} = "Defined";
+
+	my $name= $hash->{NAME};
+	return undef;
+}
+
+#####################################
+sub SD_WS_Undef($$)
+{
+	my ($hash, $name) = @_;
+	delete($modules{SD_WS}{defptr}{$hash->{CODE}})
+		if(defined($hash->{CODE}) && defined($modules{SD_WS}{defptr}{$hash->{CODE}}));
+	return undef;
+}
+
+
+###################################
+sub SD_WS_Parse($$)
+{
+	my ($iohash, $msg) = @_;
+	#my $rawData = substr($msg, 2);
+	my $name = $iohash->{NAME};
+	my ($protocol,$rawData) = split("#",$msg);
+	$protocol=~ s/^[WP](\d+)/$1/; # extract protocol
+	
+	
+	
+	my $dummyreturnvalue= "Unknown, please report";
+	my $hlen = length($rawData);
+	my $blen = $hlen * 4;
+	my $bitData = unpack("B$blen", pack("H$hlen", $rawData));
+	my $bitData2;
+	
+	my $model;	# wenn im elsif Abschnitt definiert, dann wird der Sensor per AutoCreate angelegt
+	my $SensorTyp;
+	my $id;
+	my $bat;
+	my $channel;
+	my $rawTemp;
+	my $temp;
+	my $hum;
+	my $trend;
+	
+	my %decodingSubs  = (
+    50    => # Protocol 50
+     # FF550545FF9E
+     # FF550541FF9A 
+	 # AABCDDEEFFGG
+ 	 # A = Preamble, always FF
+ 	 # B = TX type, always 5
+ 	 # C = Address (5/6/7) > low 2 bits = 1/2/3
+ 	 # D = Soil moisture 05% 
+ 	 # E = temperature 
+ 	 # F = security code, always F
+ 	 # G = Checksum 55+05+45+FF=19E CRC value = 9E
+        {   # subs to decode this
+        	sensortype => 'XT300',
+        	model => 'SD_WS_50_SM',
+			prematch => sub {my $msg = shift; return 1 if ($msg =~ /^FF5[0-9A-F]{5}FF[0-9A-F]{2}/); }, 		# prematch
+			crcok => sub {my $msg = shift; return 1 if ((hex(substr($msg,2,2))+hex(substr($msg,4,2))+hex(substr($msg,6,2))+hex(substr($msg,8,2))&0xFF) == (hex(substr($msg,10,2))) );  }, 	# crc
+			id => sub {my $msg = shift; return (hex(substr($msg,2,2)) &0x03 ); },   							#id
+			#temp => sub {my $msg = shift; return  (sprintf('%x',((hex(substr($msg,6,2)) <<4)/2/10)));  },		#temp
+			#temphex => sub {my $msg = shift; return  sprintf("%04X",((hex(substr($msg,6,2)))<<4)/2);  },			#temp
+			temp => sub {my $msg = shift; return  ((hex(substr($msg,6,2)))-40)  },								#temp
+			#hum => sub {my $msg = shift; return (printf('%02x',hex(substr($msg,4,2))));  }, 					#hum
+			hum => sub {my $msg = shift; return hex(substr($msg,4,2));  }, 										#hum
+			channel => sub {my (undef,$bitData) = @_; return ( SD_WS_binaryToNumber($bitData,12,15)&0x03 );  }, #channel
+        },	
+     33 =>
+   	 	 {
+     		sensortype => 's014/TFA 30.3200/TCM/Conrad',
+        	model =>	'SD_WS_33_TH',
+			prematch => sub {my $msg = shift; return 1 if ($msg =~ /^[0-9A-F]{10,11}/); }, 							# prematch
+			crcok => 	sub {return SD_WS_binaryToNumber($bitData,36,39);  }, 										# crc
+			id => 		sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,0,9); },   				# id
+	#		sendmode =>	sub {my (undef,$bitData) = @_; return SD_WS_binaryToNumber($bitData,10,11) eq "1" ? "manual" : "auto";  }
+			temp => 	sub {my (undef,$bitData) = @_; return (((SD_WS_binaryToNumber($bitData,22,25)*256 +  SD_WS_binaryToNumber($bitData,18,21)*16 + SD_WS_binaryToNumber($bitData,14,17)) *10 -12200) /18)/10;  },	#temp
+			hum => 		sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,30,33)*16 + SD_WS_binaryToNumber($bitData,26,29));  }, 					#hum
+			channel => 	sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,12,13)+1 );  }, 		#channel
+     		bat => 		sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,34) eq "1" ? "ok" : "critical");},
+    # 		sync => 	sub {my (undef,$bitData) = @_; return (SD_WS_binaryToNumber($bitData,35,35) eq "1" ? "true" : "false");},
+   	 	 }        
+        
+        
+    );
+    
+    	
+	Log3 $name, 4, "SD_WS_Parse: Protocol: $protocol, rawData: $rawData";
+	
+	if ($protocol eq "37")		# Bresser 7009994
+	{
+		# 0      7 8 9 10 12        22   25    31
+		# 01011010 0 0 01 01100001110 10 0111101 11001010
+		# ID      B? T Kan Temp       ?? Hum     Pruefsumme?
+		
+		# MU;P0=729;P1=-736;P2=483;P3=-251;P4=238;P5=-491;D=010101012323452323454523454545234523234545234523232345454545232345454545452323232345232340;CP=4;
+		
+		$model = "SD_WS37_TH";
+		$SensorTyp = "Bresser 7009994";
+	
+		$id = 		SD_WS_binaryToNumber($bitData,0,7);
+		#$bat = 	int(substr($bitData,8,1)) eq "1" ? "ok" : "low";
+		$channel = 	SD_WS_binaryToNumber($bitData,10,11);
+		$rawTemp = 	SD_WS_binaryToNumber($bitData,12,22);
+		$hum =		SD_WS_binaryToNumber($bitData,25,31);
+		
+		$id = sprintf('%02X', $id);           # wandeln nach hex
+		$temp = ($rawTemp - 609.93) / 9.014;
+		$temp = sprintf("%.1f", $temp);
+		
+		if ($hum < 10 || $hum > 99 || $temp < -30 || $temp > 70) {
+			return "";
+		}
+	
+		$bitData2 = substr($bitData,0,8) . ' ' . substr($bitData,8,4) . ' ' . substr($bitData,12,11);
+		$bitData2 = $bitData2 . ' ' . substr($bitData,23,2) . ' ' . substr($bitData,25,7) . ' ' . substr($bitData,32,8);
+		Log3 $iohash, 4, "$name converted to bits: " . $bitData2;
+		Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) sensor id=$id, channel=$channel, rawTemp=$rawTemp, temp=$temp, hum=$hum";
+	}
+	elsif  ($protocol eq "44" || $protocol eq "44x")	# BresserTemeo
+	{
+		# 0    4    8    12       20   24   28   32   36   40   44       52   56   60
+		# 0101 0111 1001 00010101 0010 0100 0001 1010 1000 0110 11101010 1101 1011 1110 110110010
+		# hhhh hhhh ?bcc iiiiiiii sttt tttt tttt xxxx xxxx ?BCC IIIIIIII Syyy yyyy yyyy
+
+		# - h humidity / -x checksum
+		# - t temp     / -y checksum
+		# - c Channel  /  C checksum
+		# - i 8 bit random id (aendert sich beim Batterie- und Kanalwechsel)  / - I checksum
+		# - b battery indicator (0=>OK, 1=>LOW)               / - B checksum
+		# - s Test/Sync (0=>Normal, 1=>Test-Button pressed)   / - S checksum
+	
+		$model= "BresserTemeo";
+		$SensorTyp = "BresserTemeo";
+		
+		#my $binvalue = unpack("B*" ,pack("H*", $rawData));
+		my $binvalue = $bitData;
+ 
+		if (length($binvalue) != 72) {
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: length error (72 bits expected)!!!";
+			return "";
+		}
+
+		# Check what Humidity Prefix (*sigh* Bresser!!!) 
+		if ($protocol eq "44")
+		{
+			$binvalue = "0".$binvalue;
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity <= 79  Flag";
+		}
+		else
+		{
+			$binvalue = "1".$binvalue;
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity > 79  Flag";
+		}
+		
+		Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: new bin $binvalue";
+	
+		my $checksumOkay = 1;
+		
+		my $hum1Dec = SD_WS_binaryToNumber($binvalue, 0, 3);
+		my $hum2Dec = SD_WS_binaryToNumber($binvalue, 4, 7);
+		my $checkHum1 = SD_WS_binaryToNumber($binvalue, 32, 35) ^ 0b1111;
+		my $checkHum2 = SD_WS_binaryToNumber($binvalue, 36, 39) ^ 0b1111;
+
+		if ($checkHum1 != $hum1Dec || $checkHum2 != $hum2Dec)
+		{
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Humidity";
+		}
+		else
+		{
+			$hum = $hum1Dec.$hum2Dec;
+			if ($hum < 1 || $hum > 100)
+			{
+				Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Humidity Error. Humidity=$hum";
+				return "";
+			}
+		}
+
+		my $temp1Dec = SD_WS_binaryToNumber($binvalue, 21, 23);
+		my $temp2Dec = SD_WS_binaryToNumber($binvalue, 24, 27);
+		my $temp3Dec = SD_WS_binaryToNumber($binvalue, 28, 31);
+		my $checkTemp1 = SD_WS_binaryToNumber($binvalue, 53, 55) ^ 0b111;
+		my $checkTemp2 = SD_WS_binaryToNumber($binvalue, 56, 59) ^ 0b1111;
+		my $checkTemp3 = SD_WS_binaryToNumber($binvalue, 60, 63) ^ 0b1111;
+		$temp = $temp1Dec.$temp2Dec.".".$temp3Dec;
+		
+		if ($checkTemp1 != $temp1Dec || $checkTemp2 != $temp2Dec || $checkTemp3 != $temp3Dec)
+		{
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Temperature";
+			$checksumOkay = 0;
+		}
+
+		if ($temp > 60)
+		{
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: Temperature Error. temp=$temp";
+			return "";
+		}
+		
+		$bat = substr($binvalue,9,1);
+		my $checkBat = substr($binvalue,41,1) ^ 0b1;
+		
+		if ($bat != $checkBat)
+		{
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Bat";
+			$bat = undef;
+		}
+		else
+		{
+			$bat = ($bat == 0) ? "ok" : "low";
+		}
+		
+		$channel = SD_WS_binaryToNumber($binvalue, 10, 11);
+		my $checkChannel = SD_WS_binaryToNumber($binvalue, 42, 43) ^ 0b11;
+		$id = SD_WS_binaryToNumber($binvalue, 12, 19);
+		my $checkId = SD_WS_binaryToNumber($binvalue, 44, 51) ^ 0b11111111;
+		
+		if ($channel != $checkChannel || $id != $checkId)
+		{
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error in Channel or Id";
+			$checksumOkay = 0;
+		}
+		
+		if ($checksumOkay == 0)
+		{
+			Log3 $iohash, 4, "SD_WS_Parse BresserTemeo: checksum error!!! These Values seem incorrect: temp=$temp, channel=$channel, id=$id";
+			return "";
+		}
+		
+		$id = sprintf('%02X', $id);           # wandeln nach hex
+		Log3 $iohash, 4, "$name SD_WS_Parse: model=$model, temp=$temp, hum=$hum, channel=$channel, id=$id, bat=$bat";
+		
+	}
+	elsif (defined($decodingSubs{$protocol}))		# durch den hash decodieren
+	{
+	 
+	 	   	$SensorTyp=$decodingSubs{$protocol}{sensortype};
+		    
+		    return "" && Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) prematch error" if (!$decodingSubs{$protocol}{prematch}->( $rawData ));
+		    return "" && Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) crc  error"  if (!$decodingSubs{$protocol}{crcok}->( $rawData ));
+		    
+	    	$id=$decodingSubs{$protocol}{id}->( $rawData,$bitData );
+	    	#my $temphex=$decodingSubs{$protocol}{temphex}->( $rawData,$bitData );
+	    	
+	    	$temp=$decodingSubs{$protocol}{temp}->( $rawData,$bitData );
+	    	$hum=$decodingSubs{$protocol}{hum}->( $rawData,$bitData );
+	    	$channel=$decodingSubs{$protocol}{channel}->( $rawData,$bitData );
+	    	$model = $decodingSubs{$protocol}{model};
+	    	$bat = $decodingSubs{$protocol}{bat};
+
+	    	Log3 $iohash, 4, "$name decoded protocolid: $protocol ($SensorTyp) sensor id=$id, channel=$channel, temp=$temp, hum=$hum";
+		
+	} 
+	else {
+		Log3 $iohash, 4, "SD_WS_Parse: unknown message, please report. converted to bits: $bitData";
+		return undef;
+	}
+
+
+	if (!defined($model)) {
+		return undef;
+	}
+	
+	my $deviceCode;
+	
+	my $longids = AttrVal($iohash->{NAME},'longids',0);
+	if (($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/)))
+	{
+		$deviceCode = $model . '_' . $id . $channel;
+		Log3 $iohash,4, "$name using longid: $longids model: $model";
+	} else {
+		$deviceCode = $model . "_" . $channel;
+	}
+	
+	#print Dumper($modules{SD_WS}{defptr});
+	
+	my $def = $modules{SD_WS}{defptr}{$iohash->{NAME} . "." . $deviceCode};
+	$def = $modules{SD_WS}{defptr}{$deviceCode} if(!$def);
+
+	if(!$def) {
+		Log3 $iohash, 1, 'SD_WS: UNDEFINED sensor ' . $model . ' detected, code ' . $deviceCode;
+		return "UNDEFINED $deviceCode SD_WS $deviceCode";
+	}
+	#Log3 $iohash, 3, 'SD_WS: ' . $def->{NAME} . ' ' . $id;
+	
+	my $hash = $def;
+	$name = $hash->{NAME};
+	return "" if(IsIgnored($name));
+	
+	Log3 $name, 4, "SD_WS: $name ($rawData)";  
+
+	if (!defined(AttrVal($hash->{NAME},"event-min-interval",undef)))
+	{
+		my $minsecs = AttrVal($iohash->{NAME},'minsecs',0);
+		if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) {
+			Log3 $hash, 4, "$deviceCode Dropped due to short time. minsecs=$minsecs";
+			return "";
+		}
+	}
+
+	$hash->{lastReceive} = time();
+	$hash->{lastMSG} = $rawData;
+	if (defined($bitData2)) {
+		$hash->{bitMSG} = $bitData2;
+	} else {
+		$hash->{bitMSG} = $bitData;
+	}
+
+	my $state = "T: $temp" . ($hum > 0 ? " H: $hum":"");
+
+	readingsBeginUpdate($hash);
+	readingsBulkUpdate($hash, "state", $state);
+	readingsBulkUpdate($hash, "temperature", $temp)  if (defined($temp));
+	readingsBulkUpdate($hash, "humidity", $hum)  if (defined($hum) && $hum > 0);
+	readingsBulkUpdate($hash, "battery", $bat) if (defined($bat));
+	readingsBulkUpdate($hash, "channel", $channel) if (defined($channel));
+	readingsBulkUpdate($hash, "trend", $trend) if (defined($trend));
+	
+	readingsEndUpdate($hash, 1); # Notify is done by Dispatch
+	
+	return $name;
+
+}
+
+sub SD_WS_Attr(@)
+{
+	my @a = @_;
+	
+	# Make possible to use the same code for different logical devices when they
+	# are received through different physical devices.
+	return  if($a[0] ne "set" || $a[2] ne "IODev");
+	my $hash = $defs{$a[1]};
+	my $iohash = $defs{$a[3]};
+	my $cde = $hash->{CODE};
+	delete($modules{SD_WS}{defptr}{$cde});
+	$modules{SD_WS}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
+	return undef;
+}
+
+
+sub SD_WS_binaryToNumber
+{
+	my $binstr=shift;
+	my $fbit=shift;
+	my $lbit=$fbit;
+	$lbit=shift if @_;
+	
+	return oct("0b".substr($binstr,$fbit,($lbit-$fbit)+1));
+}
+
+1;
+
+=pod
+=item summary    Supports various weather stations
+=item summary_DE Unterst&uumltzt verschiedene Funk Wetterstationen
+=begin html
+
+<a name="SD_WS"></a>
+<h3>Weather Sensors various protocols</h3>
+<ul>
+  The SD_WS module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc.<br>
+  <br>
+  <b>Known models:</b>
+  <ul>
+    <li>Bresser 7009994</li>
+    <li>Opus XT300</li>
+  </ul>
+  <br>
+  New received device are add in fhem with autocreate.
+  <br><br>
+
+  <a name="SD_WS_Define"></a>
+  <b>Define</b> 
+  <ul>The received devices created automatically.<br>
+  The ID of the defice is the cannel or, if the longid attribute is specified, it is a combination of channel and some random generated bits at powering the sensor and the channel.<br>
+  If you want to use more sensors, than channels available, you can use the longid option to differentiate them.
+  </ul>
+  <br>
+  <a name="SD_WS Events"></a>
+  <b>Generated readings:</b>
+  <br>Some devices may not support all readings, so they will not be presented<br>
+  <ul>
+  	 <li>State (T: H:)</li>
+     <li>temperature (&deg;C)</li>
+     <li>humidity: (The humidity (1-100 if available)</li>
+     <li>battery: (low or ok)</li>
+     <li>channel: (The Channelnumber (number if)</li>
+  </ul>
+  <br>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+
+  <a name="SD_WS_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="SD_WS"></a>
+<h3>SD_WS</h3>
+<ul>
+  Das SD_WS Modul verarbeitet von einem IO Ger&aumlt (CUL, CUN, SIGNALDuino, etc.) empfangene Nachrichten von Temperatur-Sensoren.<br>
+  <br>
+  <b>Unterst&uumltzte Modelle:</b>
+  <ul>
+    <li>Bresser 7009994</li>
+    <li>Opus XT300</li>
+    <li>BresserTemeo</li>
+  </ul>
+  <br>
+  Neu empfangene Sensoren werden in FHEM per autocreate angelegt.
+  <br><br>
+
+  <a name="SD_WS_Define"></a>
+  <b>Define</b> 
+  <ul>Die empfangenen Sensoren werden automatisch angelegt.<br>
+  Die ID der angelgten Sensoren ist entweder der Kanal des Sensors, oder wenn das Attribut longid gesetzt ist, dann wird die ID aus dem Kanal und einer Reihe von Bits erzeugt, welche der Sensor beim Einschalten zuf&aumlllig vergibt.<br>
+  </ul>
+  <br>
+  <a name="SD_WS Events"></a>
+  <b>Generierte Readings:</b>
+  <ul>
+  	 <li>State (T: H:)</li>
+     <li>temperature (&deg;C)</li>
+     <li>humidity: (Luftfeuchte (1-100)</li>
+     <li>battery: (low oder ok)</li>
+     <li>channel: (Der Sensor Kanal)</li>
+  </ul>
+  <br>
+  <b>Attribute</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+
+  <a name="SD_WS_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html_DE
+=cut

+ 313 - 0
fhem/core/FHEM/14_SD_WS07.pm

@@ -0,0 +1,313 @@
+##############################################
+# $Id: 14_SD_WS07.pm 13215 2017-01-23 20:09:44Z Sidey $
+# 
+# The purpose of this module is to support serval eurochron
+# weather sensors like eas8007 which use the same protocol
+# Sidey79 & Ralf9  2015-2016
+#
+
+package main;
+
+
+use strict;
+use warnings;
+
+#use Data::Dumper;
+
+
+sub
+SD_WS07_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^P7#[A-Fa-f0-9]{6}F[A-Fa-f0-9]{2}";    ## pos 7 ist aktuell immer 0xF
+  $hash->{DefFn}     = "SD_WS07_Define";
+  $hash->{UndefFn}   = "SD_WS07_Undef";
+  $hash->{ParseFn}   = "SD_WS07_Parse";
+  $hash->{AttrFn}	 = "SD_WS07_Attr";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
+                        "$readingFnAttributes ";
+  $hash->{AutoCreate} =
+        { "SD_WS07.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,",  autocreateThreshold => "2:180"} };
+
+
+}
+
+#############################
+sub
+SD_WS07_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> SD_WS07 <code> ".int(@a)
+        if(int(@a) < 3 );
+
+  $hash->{CODE} = $a[2];
+  $hash->{lastMSG} =  "";
+  $hash->{bitMSG} =  "";
+
+  $modules{SD_WS07}{defptr}{$a[2]} = $hash;
+  $hash->{STATE} = "Defined";
+  
+  my $name= $hash->{NAME};
+  return undef;
+}
+
+#####################################
+sub
+SD_WS07_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{SD_WS07}{defptr}{$hash->{CODE}})
+     if(defined($hash->{CODE}) &&
+        defined($modules{SD_WS07}{defptr}{$hash->{CODE}}));
+  return undef;
+}
+
+
+###################################
+sub
+SD_WS07_Parse($$)
+{
+  my ($iohash, $msg) = @_;
+  #my $rawData = substr($msg, 2);
+  my $name = $iohash->{NAME};
+  my (undef ,$rawData) = split("#",$msg);
+  #$protocol=~ s/^P(\d+)/$1/; # extract protocol
+
+  my $model = "SD_WS07";
+  my $hlen = length($rawData);
+  my $blen = $hlen * 4;
+  my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); 
+
+  Log3 $name, 4, "SD_WS07_Parse  $model ($msg) length: $hlen";
+  
+  #      4    8  9    12            24    28     36
+  # 0011 0110 1  010  000100000010  1111  00111000 0000  eas8007
+  # 0111 0010 1  010  000010111100  1111  00000000 0000  other device from anfichtn
+  #      ID  Bat CHN       TMP      ??   HUM
+  
+  #my $hashumidity = FALSE;
+  
+  ## Todo: Change decoding per model into a foreach  
+   #foreach $key (keys %models) {
+  #   ....
+  #}
+    my $bitData2 = substr($bitData,0,8) . ' ' . substr($bitData,8,1) . ' ' . substr($bitData,9,3);
+       $bitData2 = $bitData2 . ' ' . substr($bitData,12,12) . ' ' . substr($bitData,24,4) . ' ' . substr($bitData,28,8);
+    Log3 $iohash, 5, $model . ' converted to bits: ' . $bitData2;
+    
+    my $id = substr($rawData,0,2);
+    my $bat = int(substr($bitData,8,1)) eq "1" ? "ok" : "low";
+    my $channel = oct("0b" . substr($bitData,9,3)) + 1;
+    my $temp = oct("0b" . substr($bitData,12,12));
+    my $bit24bis27 = oct("0b".substr($bitData,24,4));
+    my $hum = oct("0b" . substr($bitData,28,8));
+    
+    if ($hum==0)
+    {
+    	$model=$model."_T";		
+    } else {
+    	$model=$model."_TH";		
+    	
+    	
+    }
+    
+    if ($hum > 100) {
+      return '';  # Eigentlich muesste sowas wie ein skip rein, damit ggf. spaeter noch weitre Sensoren dekodiert werden koennen.
+    }
+    
+    if ($temp > 700 && $temp < 3840) {
+      return '';
+    } elsif ($temp >= 3840) {        # negative Temperaturen, muss noch ueberprueft und optimiert werden 
+      $temp -= 4095;
+    }  
+    $temp /= 10;
+    
+    Log3 $iohash, 4, "$model decoded protocolid: 7 sensor id=$id, channel=$channel, temp=$temp, hum=$hum, bat=$bat";
+
+    my $deviceCode;
+    
+	my $longids = AttrVal($iohash->{NAME},'longids',0);
+	if ( ($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/)))
+	{
+		$deviceCode=$model.'_'.$id.$channel;
+		Log3 $iohash,4, "$name using longid: $longids model: $model";
+	} else {
+		$deviceCode = $model . "_" . $channel;
+	}
+    
+    #print Dumper($modules{SD_WS07}{defptr});
+    
+    my $def = $modules{SD_WS07}{defptr}{$iohash->{NAME} . "." . $deviceCode};
+    $def = $modules{SD_WS07}{defptr}{$deviceCode} if(!$def);
+
+    if(!$def) {
+		Log3 $iohash, 1, 'SD_WS07: UNDEFINED sensor ' . $model . ' detected, code ' . $deviceCode;
+		return "UNDEFINED $deviceCode SD_WS07 $deviceCode";
+    }
+        #Log3 $iohash, 3, 'SD_WS07: ' . $def->{NAME} . ' ' . $id;
+	
+	my $hash = $def;
+	$name = $hash->{NAME};
+	return "" if(IsIgnored($name));
+	
+	Log3 $name, 4, "SD_WS07: $name ($rawData)";  
+
+	if (!defined(AttrVal($hash->{NAME},"event-min-interval",undef)))
+	{
+		my $minsecs = AttrVal($iohash->{NAME},'minsecs',0);
+		if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) {
+			Log3 $hash, 4, "$deviceCode Dropped due to short time. minsecs=$minsecs";
+		  	return "";
+		}
+	}
+
+	$hash->{lastReceive} = time();
+	$hash->{lastMSG} = $rawData;
+	$hash->{bitMSG} = $bitData2; 
+
+    my $state = "T: $temp". ($hum>0 ? " H: $hum":"");
+    
+    readingsBeginUpdate($hash);
+    readingsBulkUpdate($hash, "state", $state);
+    readingsBulkUpdate($hash, "temperature", $temp)  if ($temp ne"");
+    readingsBulkUpdate($hash, "humidity", $hum)  if ($hum ne "" && $hum != 0 );
+    readingsBulkUpdate($hash, "battery", $bat) if ($bat ne "");
+    readingsBulkUpdate($hash, "channel", $channel) if ($channel ne "");
+
+    readingsEndUpdate($hash, 1); # Notify is done by Dispatch
+
+	return $name;
+
+}
+
+sub SD_WS07_Attr(@)
+{
+  my @a = @_;
+
+  # Make possible to use the same code for different logical devices when they
+  # are received through different physical devices.
+  return  if($a[0] ne "set" || $a[2] ne "IODev");
+  my $hash = $defs{$a[1]};
+  my $iohash = $defs{$a[3]};
+  my $cde = $hash->{CODE};
+  delete($modules{SD_WS07}{defptr}{$cde});
+  $modules{SD_WS07}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
+  return undef;
+}
+
+
+1;
+
+
+=pod
+=item summary    Supports weather sensors protocl 7 from SIGNALduino
+=item summary_DE Unterst&uumltzt Wettersensoren mit Protokol 7 vom SIGNALduino
+=begin html
+
+<a name="SD_WS07"></a>
+<h3>Wether Sensors protocol #7</h3>
+<ul>
+  The SD_WS07 module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc.<br>
+  <br>
+  <b>Known models:</b>
+  <ul>
+    <li>Eurochon EAS800z</li>
+    <li>Technoline WS6750/TX70DTH</li>
+  </ul>
+  <br>
+  New received device are add in fhem with autocreate.
+  <br><br>
+
+  <a name="SD_WS07_Define"></a>
+  <b>Define</b> 
+  <ul>The received devices created automatically.<br>
+  The ID of the defice is the cannel or, if the longid attribute is specified, it is a combination of channel and some random generated bits at powering the sensor and the channel.<br>
+  If you want to use more sensors, than channels available, you can use the longid option to differentiate them.
+  </ul>
+  <br>
+  <a name="SD_WS07 Events"></a>
+  <b>Generated readings:</b>
+  <br>Some devices may not support all readings, so they will not be presented<br>
+  <ul>
+  	 <li>State (T: H:)</li>
+     <li>temperature (&deg;C)</li>
+     <li>humidity: (The humidity (1-100 if available)</li>
+     <li>battery: (low or ok)</li>
+     <li>channel: (The Channelnumber (number if)</li>
+  </ul>
+  <br>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#model">model</a> ()</li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+
+  <a name="SD_WS07_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS07_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="SD_WS07"></a>
+<h3>SD_WS07</h3>
+<ul>
+  Das SD_WS07 Module verarbeitet von einem IO Geraet (CUL, CUN, SIGNALDuino, etc.) empfangene Nachrichten von Temperatur-Sensoren.<br>
+  <br>
+  <b>Unterst&uumltzte Modelle:</b>
+  <ul>
+    <li>Eurochon EAS800z</li>
+    <li>Technoline WS6750/TX70DTH</li>
+    <li>TFA 30320902</li>
+    <li>FreeTec Aussenmodul fuer Wetterstation NC-7344</li>
+  </ul>
+  <br>
+  Neu empfangene Sensoren werden in FHEM per autocreate angelegt.
+  <br><br>
+
+  <a name="SD_WS07_Define"></a>
+  <b>Define</b> 
+  <ul>Die empfangenen Sensoren werden automatisch angelegt.<br>
+  Die ID der angelgten Sensoren ist entweder der Kanal des Sensors, oder wenn das Attribut longid gesetzt ist, dann wird die ID aus dem Kanal und einer Reihe von Bits erzeugt, welche der Sensor beim Einschalten zufaellig vergibt.<br>
+  </ul>
+  <br>
+  <a name="SD_WS07 Events"></a>
+  <b>Generierte Readings:</b>
+  <ul>
+  	 <li>State (T: H:)</li>
+     <li>temperature (&deg;C)</li>
+     <li>humidity: (Luftfeuchte (1-100)</li>
+     <li>battery: (low oder ok)</li>
+     <li>channel: (Der Sensor Kanal)</li>
+  </ul>
+  <br>
+  <b>Attribute</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#model">model</a> ()</li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+
+  <a name="SD_WS071_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS07_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html_DE
+=cut

+ 496 - 0
fhem/core/FHEM/14_SD_WS09.pm

@@ -0,0 +1,496 @@
+    ##############################################
+    ##############################################
+    # $Id: 14_SD_WS09.pm 12233 2016-10-01 22:22:51Z mrsidey $
+    # 
+    # The purpose of this module is to support serval 
+    # weather sensors like WS-0101  (Sender 868MHz ASK   Epmfänger RX868SH-DV elv)
+    # Sidey79 & pejonp 2015  
+    #
+    package main;
+    
+    use strict;
+    use warnings;
+    use Digest::CRC qw(crc);
+    
+    #use Math::Round qw/nearest/;
+    
+    sub SD_WS09_Initialize($)
+    {
+      my ($hash) = @_;
+    
+      $hash->{Match}     = "^P9#[A-Fa-f0-9]+";    ## pos 7 ist aktuell immer 0xF
+      $hash->{DefFn}     = "SD_WS09_Define";
+      $hash->{UndefFn}   = "SD_WS09_Undef";
+      $hash->{ParseFn}   = "SD_WS09_Parse";
+      $hash->{AttrFn}	 = "SD_WS09_Attr";
+      $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 "
+                            ."windKorrektur:-3,-2,-1,0,1,2,3 "
+                            ."$readingFnAttributes ";
+      $hash->{AutoCreate} =
+            { "SD_WS09.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.* windKorrektur:.*:0 " , FILTER => "%NAME", GPLOT => "temp4hum4:Temp/Hum,",  autocreateThreshold => "2:180"} };
+    
+    
+    }
+    
+    #############################
+    sub SD_WS09_Define($$)
+    {
+      my ($hash, $def) = @_;
+      my @a = split("[ \t][ \t]*", $def);
+    
+      return "wrong syntax: define <name> SD_WS09 <code> ".int(@a)
+            if(int(@a) < 3 );
+    
+      $hash->{CODE} = $a[2];
+      $hash->{lastMSG} =  "";
+      $hash->{bitMSG} =  "";
+    
+      $modules{SD_WS09}{defptr}{$a[2]} = $hash;
+      $hash->{STATE} = "Defined";
+      
+      my $name= $hash->{NAME};
+      return undef;
+    }
+    
+    #####################################
+    sub SD_WS09_Undef($$)
+    {
+      my ($hash, $name) = @_;
+      delete($modules{SD_WS09}{defptr}{$hash->{CODE}})
+         if(defined($hash->{CODE}) &&
+            defined($modules{SD_WS09}{defptr}{$hash->{CODE}}));
+      return undef;
+    }
+    
+    
+    ###################################
+    sub SD_WS09_Parse($$)
+    {
+      my ($iohash, $msg) = @_;
+      my $name = $iohash->{NAME};
+      my (undef ,$rawData) = split("#",$msg);
+      my @winddir_name=("N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW");
+      my $hlen = length($rawData);
+      my $blen = $hlen * 4;
+      my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); 
+      my $bitData2;
+      my $bitData20;
+      my $rain = 0;
+      my $deviceCode = 0;
+      my $model = "undef";  # 0xFFA -> WS0101/WH1080 alles andere -> CTW600 
+      my $modelattr ;
+      my $modelid;
+      my $windSpeed = 0;
+      my $windguest =0;
+      my $sensdata;
+      my $id;
+      my $bat = 0;
+      my $temp;
+      my $hum;
+      my $windDirection ;
+      my $windDirectionText;
+      
+      
+      $modelattr = AttrVal($iohash->{NAME},'WS09_WSModel',0);
+      if ($modelattr eq '0'){
+      $modelattr = "undef";
+      }
+      
+      my $crcwh1080 = AttrVal($iohash->{NAME},'WS09_CRCAUS',0);
+      Log3 $iohash, 3, "$name: SD_WS09_Parse CRC_AUS:$crcwh1080 Model=$modelattr" ;
+    
+      my $syncpos= index($bitData,"11111110");  #7x1 1x0 preamble
+    	Log3 $iohash, 3, "$name: SD_WS09_Parse0 Bin=$bitData syncp=$syncpos length:".length($bitData) ;
+
+    		if ($syncpos ==-1 || length($bitData)-$syncpos < 78) 
+    		{
+    			Log3 $iohash, 3, "$name: SD_WS09_Parse EXIT: msg=$rawData syncp=$syncpos length:".length($bitData) ;
+    			return undef;
+    		}
+
+         my $wh = substr($bitData,0,8);
+         #CRC-Check bei WH1080/WS0101 WS09_CRCAUS=0 und WS09_WSModel = undef oder Wh1080 
+         if(($crcwh1080 == 0) &&  ($modelattr ne "CTW600")) {
+             if($wh == "11111111") {
+        	if ($syncpos == 0) 
+    		{
+          $hlen = length($rawData);
+          $blen = $hlen * 4;
+    	    $bitData2 = '11'.unpack("B$blen", pack("H$hlen", $rawData));
+          $bitData20 = substr($bitData2,0,length($bitData2)-2);
+          $blen = length($bitData20);
+          $hlen = $blen / 4;
+          $msg = 'P9#'.unpack("H$hlen", pack("B$blen", $bitData20));
+          $bitData = $bitData20;
+      	  Log3 $iohash, 3, "$name: SD_WS09_Parse sync1 msg=$msg syncp=$syncpos length:".length($bitData) ;
+       		}
+        
+        	if ($syncpos == 1) 
+    		{
+          $hlen = length($rawData);
+          $blen = $hlen * 4;
+    	    $bitData2 = '1'.unpack("B$blen", pack("H$hlen", $rawData));
+          $bitData20 = substr($bitData2,0,length($bitData2)-1);
+          $blen = length($bitData20);
+          $hlen = $blen / 4;
+          $msg = 'P9#'.unpack("H$hlen", pack("B$blen", $bitData20));
+          $bitData = $bitData20;           
+      	  Log3 $iohash, 3, "$name: SD_WS09_Parse sync2 msg=$msg syncp=$syncpos length:".length($bitData) ;
+       		}     
+             
+             
+                my $datacheck = pack( 'H*', substr($msg,5,length($msg)-5) );
+                my $crcmein = Digest::CRC->new(width => 8, poly => 0x31);
+                my $rr2 = $crcmein->add($datacheck)->hexdigest;
+                if ($rr2 eq "0"){
+                    $model = "WH1080";
+                    Log3 $iohash, 3, "$name: SD_WS09_Parse CRC_OK:  CRC=$rr2 Model=$model attr=$modelattr" ;
+                }else{
+                    Log3 $iohash, 3, "$name: SD_WS09_Parse CRC_Error:  msg=$msg CRC=$rr2 " ;
+                    return undef;
+                    }
+            }else{
+                $model = "CTW600";
+                 Log3 $iohash, 3, "$name: SD_WS09_Parse CTW600:   Model=$model attr=$modelattr" ; 
+                }
+            };
+                  
+         if( ($wh == "11111111") || ($model eq "WH1080")) {
+          if ($modelattr eq "CTW600"){
+                    Log3 $iohash, 3, "$name: SD_WS09_WH1080 off=$modelattr Model=$model " ;
+                    return undef;
+              } 
+            $sensdata = substr($bitData,8);
+            my $whid = substr($sensdata,0,4);
+            if(  $whid == "1010" ){ # A 
+           	  Log3 $iohash, 3, "$name: SD_WS09_Parse WH=$wh msg=$sensdata syncp=$syncpos length:".length($sensdata) ;
+              $model = "WH1080";
+              $id = SD_WS09_bin2dec(substr($sensdata,4,8));
+              $bat = (SD_WS09_bin2dec((substr($sensdata,64,4))) == 0) ? 'ok':'low' ; # decode battery = 0 --> ok
+              $temp = (SD_WS09_bin2dec(substr($sensdata,12,12)) - 400)/10;
+    		  $hum = SD_WS09_bin2dec(substr($sensdata,24,8));
+              $windDirection = SD_WS09_bin2dec(substr($sensdata,68,4));  
+              $windDirectionText = $winddir_name[$windDirection];
+              $windSpeed =  round((SD_WS09_bin2dec(substr($sensdata,32,8))* 34)/100,01);
+              Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." Windspeed bit: ".substr($sensdata,32,8)." Dec: " . $windSpeed ;
+              $windguest = round((SD_WS09_bin2dec(substr($sensdata,40,8)) * 34)/100,01);
+              Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." Windguest bit: ".substr($sensdata,40,8)." Dec: " . $windguest ;
+              $rain =  SD_WS09_bin2dec(substr($sensdata,56,8)) * 0.3;
+              Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." Rain bit: ".substr($sensdata,56,8)." Dec: " . $rain ;
+            } else {
+            if(  $whid == "1011" ){ # B  DCF-77 Zeitmeldungen vom Sensor
+            my $hrs1 = substr($sensdata,16,8);
+            my $hrs;
+            my $mins; 
+            my $sec; 
+            my $mday;
+            my $month;
+            my $year;
+            $id = SD_WS09_bin2dec(substr($sensdata,4,8));
+            Log3 $iohash, 3, "$name: SD_WS09_Parse Zeitmeldung0: HRS1=$hrs1 id:$id" ;
+            $hrs = SD_WS09_BCD2bin(substr($sensdata,18,6) ) ; # Stunde
+            $mins = SD_WS09_BCD2bin(substr($sensdata,24,8)); # Minute 
+            $sec = SD_WS09_BCD2bin(substr($sensdata,32,8)); # Sekunde 
+            #day month year
+            $year = SD_WS09_BCD2bin(substr($sensdata,40,8)); # Jahr
+            $month = SD_WS09_BCD2bin(substr($sensdata,51,5)); # Monat
+            $mday = SD_WS09_BCD2bin(substr($sensdata,56,8)); # Tag
+            Log3 $iohash, 3, "$name: SD_WS09_Parse Zeitmeldung1:  msg=$rawData syncp=$syncpos length:".length($bitData) ;
+            Log3 $iohash, 3, "$name: SD_WS09_Parse Zeitmeldung2: HH:mm:ss - ".$hrs.":".$mins.":".$sec ;
+            Log3 $iohash, 3, "$name: SD_WS09_Parse Zeitmeldung3: dd.mm.yy - ".$mday.":".$month.":".$year ;
+            return $name;
+            }
+                Log3 $iohash, 3, "$name: SD_WS09_Parse Zeitmeldung4: msg=$rawData syncp=$syncpos length:".length($sensdata) ;
+    	          return undef;
+            }
+         }else{
+              if ($modelattr eq "WH1080"){
+                    Log3 $iohash, 3, "$name: SD_WS09_CTW600 off=$modelattr Model=$model " ;
+                    return undef;
+              } else {
+            # eine CTW600 wurde erkannt 
+            $sensdata = substr($bitData,$syncpos+8);
+            Log3 $iohash, 3, "$name: SD_WS09_Parse CTW WH=$wh msg=$sensdata syncp=$syncpos length:".length($sensdata) ;
+            $model = "CTW600";
+            my $nn1 = substr($sensdata,10,2);  # Keine Bedeutung
+            my $nn2 = substr($sensdata,62,4);  # Keine Bedeutung
+            $modelid = substr($sensdata,0,4);
+            Log3 $iohash, 3, "$name: SD_WS09_Parse Id: ".$modelid." NN1:$nn1 NN2:$nn2" ;
+            Log3 $iohash, 3, "$name: SD_WS09_Parse Id: ".$modelid." Bin-Sync=$sensdata syncp=$syncpos length:".length($sensdata) ;
+            $bat = SD_WS09_bin2dec((substr($sensdata,0,3))) ;
+            $id = SD_WS09_bin2dec(substr($sensdata,4,6));
+            $temp = (SD_WS09_bin2dec(substr($sensdata,12,10)) - 400)/10;
+    	      $hum = SD_WS09_bin2dec(substr($sensdata,22,8));
+            $windDirection = SD_WS09_bin2dec(substr($sensdata,66,4));  
+            $windDirectionText = $winddir_name[$windDirection];
+            $windSpeed =  round(SD_WS09_bin2dec(substr($sensdata,30,16))/240,01);
+            Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." Windspeed bit: ".substr($sensdata,32,8)." Dec: " . $windSpeed ;
+            $windguest = round((SD_WS09_bin2dec(substr($sensdata,40,8)) * 34)/100,01);
+            Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." Windguest bit: ".substr($sensdata,40,8)." Dec: " . $windguest ;
+            $rain =  round(SD_WS09_bin2dec(substr($sensdata,46,16)) * 0.3,01);
+            Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." Rain bit: ".substr($sensdata,46,16)." Dec: " . $rain ;
+            }
+         }
+        		
+        Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." id:$id :$sensdata ";
+        Log3 $iohash, 3, "$name: SD_WS09_Parse ".$model." id:$id, bat:$bat, temp=$temp, hum=$hum, winddir=$windDirection:$windDirectionText wS=$windSpeed, wG=$windguest, rain=$rain";
+    
+      if($hum > 100 || $hum < 0) {
+            	Log3 $iohash, 3, "$name: SD_WS09_Parse HUM: hum=$hum msg=$rawData " ;
+    			   return undef;
+         } 
+      if($temp > 60 || $temp < -40) {
+            	Log3 $iohash, 3, "$name: SD_WS09_Parse TEMP: Temp=$temp msg=$rawData " ;
+    			   return undef;
+         } 
+      
+          
+       my $longids = AttrVal($iohash->{NAME},'longids',0);
+    	if ( ($longids ne "0") && ($longids eq "1" || $longids eq "ALL" || (",$longids," =~ m/,$model,/)))
+    	{
+    	 $deviceCode=$model."_".$id;
+     		Log3 $iohash,4, "$name: SD_WS09_Parse using longid: $longids model: $model";
+    	} else {
+    		$deviceCode = $model;
+    	}
+       
+        my $def = $modules{SD_WS09}{defptr}{$iohash->{NAME} . "." . $deviceCode};
+        $def = $modules{SD_WS09}{defptr}{$deviceCode} if(!$def);
+    
+        if(!$def) {
+    		Log3 $iohash, 1, 'SD_WS09_Parse UNDEFINED sensor ' . $model . ' detected, code ' . $deviceCode;
+    		return "UNDEFINED $deviceCode SD_WS09 $deviceCode";
+        }
+    
+      my $hash = $def;
+    	$name = $hash->{NAME};	    	
+    	Log3 $name, 4, "SD_WS09_Parse: $name ($rawData)";  
+    
+    
+        my $windkorr = AttrVal($hash->{NAME},'windKorrektur',0);
+        if ($windkorr != 0 )      
+        {
+        my $oldwinddir = $windDirection; 
+        $windDirection = $windDirection + $windkorr; 
+        $windDirectionText = $winddir_name[$windDirection];
+        Log3 $iohash, 3, "SD_WS09_Parse ".$model." Faktor:$windkorr wD:$oldwinddir  Korrektur wD:$windDirection:$windDirectionText" ;
+        }    
+    
+    	if (!defined(AttrVal($hash->{NAME},"event-min-interval",undef)))
+    	{
+    		my $minsecs = AttrVal($iohash->{NAME},'minsecs',0);
+    		if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) {
+    			Log3 $hash, 4, "SD_WS09_Parse $deviceCode Dropped due to short time. minsecs=$minsecs";
+    		  	return "";
+    		}
+    	}
+    
+    	$def->{lastMSG} = $rawData;
+   
+        my $state = "T: $temp ". ($hum>0 ? " H: $hum ":" ")." Ws: $windSpeed "." Wg: $windguest "." Wd: $windDirectionText "." R: $rain";
+       
+        readingsBeginUpdate($hash);
+        readingsBulkUpdate($hash, "state", $state);
+        readingsBulkUpdate($hash, "temperature", $temp)  if ($temp ne"");
+        readingsBulkUpdate($hash, "humidity", $hum)  if ($hum ne "" && $hum != 0 );
+        readingsBulkUpdate($hash, "battery", $bat)   if ($bat ne "");
+        readingsBulkUpdate($hash, "id", $id) if ($id ne "");
+        
+        #zusätzlich Daten für Wetterstation
+        readingsBulkUpdate($hash, "rain", $rain );
+        readingsBulkUpdate($hash, "windGust", $windguest );
+        readingsBulkUpdate($hash, "windSpeed", $windSpeed );
+        readingsBulkUpdate($hash, "windDirection", $windDirection );
+        readingsBulkUpdate($hash, "windDirectionDegree", $windDirection * 360 / 16);     
+        readingsBulkUpdate($hash, "windDirectionText", $windDirectionText );
+        readingsEndUpdate($hash, 1); # Notify is done by Dispatch
+    
+    	return $name;
+    
+    }
+    
+    sub SD_WS09_Attr(@)
+    {
+      my @a = @_;
+    
+      # Make possible to use the same code for different logical devices when they
+      # are received through different physical devices.
+      return  if($a[0] ne "set" || $a[2] ne "IODev");
+      my $hash = $defs{$a[1]};
+      my $iohash = $defs{$a[3]};
+      my $cde = $hash->{CODE};
+      delete($modules{SD_WS09}{defptr}{$cde});
+      $modules{SD_WS09}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
+      return undef;
+    }
+    
+    
+    sub SD_WS09_bin2dec($)
+    {
+      my $h = shift;
+      my $int = unpack("N", pack("B32",substr("0" x 32 . $h, -32))); 
+      return sprintf("%d", $int); 
+    }
+    sub SD_WS09_binflip($)
+    {
+      my $h = shift;
+      my $hlen = length($h);
+      my $i = 0;
+      my $flip = "";
+      
+      for ($i=$hlen-1; $i >= 0; $i--) {
+        $flip = $flip.substr($h,$i,1);
+      }
+    
+      return $flip;
+    }
+    
+    
+    sub SD_WS09_BCD2bin($) {
+      my $binary = shift;
+      my $int = unpack("N", pack("B32", substr("0" x 32 . $binary, -32)));
+      my $BCD = sprintf("%x", $int );
+      return $BCD;
+    }
+    
+    
+    
+    1;
+    
+    
+=pod
+=item summary    Supports weather sensors protocl 9 from SIGNALduino
+=item summary_DE Unterst&uumltzt Wettersensoren mit Protokol 9 vom SIGNALduino
+=begin html
+
+<a name="SD_WS09"></a>
+<h3>Wether Sensors protocol #9</h3>
+<ul>
+  The SD_WS09 module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc.<br>
+  Requires Perl-Modul Digest::CRC. <br>
+   <br> 
+  cpan install Digest::CRC    or   sudo apt-get install libdigest-crc-perl <br>
+   <br>
+  <br>
+  <b>Known models:</b>
+  <ul>
+    <li>WS-0101              --> Model: WH1080</li>
+    <li>TFA 30.3189 / WH1080 --> Model: WH1080</li>
+    <li>1073 (WS1080)        --> Model: WH1080</li>
+    <li>CTW600               --> Model: CTW600 (??) </li> 
+  </ul>
+  <br>
+  New received device are add in fhem with autocreate.
+  <br><br>
+
+  <a name="SD_WS09_Define"></a>
+  <b>Define</b> 
+  <ul>The received devices created automatically.<br>
+  The ID of the defice is the model or, if the longid attribute is specified, it is a combination of model and some random generated bits at powering the sensor.<br>
+  If you want to use more sensors, you can use the longid option to differentiate them.
+  </ul>
+  <br>
+  <a name="SD_WS09 Events"></a>
+  <b>Generated readings:</b>
+  <br>Some devices may not support all readings, so they will not be presented<br>
+  <ul>
+   <li>State (T: H: Ws: Wg: Wd: R: )  temperature, humidity, windSpeed, windGuest, windDirection, Rain</li>
+     <li>Temperature (&deg;C)</li>
+     <li>Humidity: (The humidity (1-100 if available)</li>
+     <li>Battery: (low or ok)</li>
+     <li>ID: (The ID-Number (number if)</li>
+     <li>windSpeed (m/s) and windDirection (N-O-S-W)</li>
+     <li>Rain (mm)</li>
+  </ul>
+  <br>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+    <li>Model<br>
+        WH1080, CTW600
+    </li><br>
+    <li>windKorrektur<br>
+      -3,-2,-1,0,1,2,3   
+    </li><br>
+   </ul>
+
+  <a name="SD_WS09_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS09_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html
+=begin html_DE
+
+<a name="SD_WS09"></a>
+<h3>SD_WS09</h3>
+<ul>
+  Das SD_WS09 Module verarbeitet von einem IO Gerät (CUL, CUN, SIGNALDuino, etc.) empfangene Nachrichten von Temperatur-Sensoren.<br>
+  <br>
+  Perl-Modul Digest::CRC erforderlich. <br>
+   <br>
+    cpan install Digest::CRC oder auch             <br>
+    sudo apt-get install libdigest-crc-perl         <br>
+   <br>
+  <br>
+  <b>Unterstütze Modelle:</b>
+  <ul>
+    <li>WS-0101              --> Model: WH1080</li>
+    <li>TFA 30.3189 / WH1080 --> Model: WH1080</li>
+    <li>1073 (WS1080)        --> Model: WH1080</li>
+    <li>CTW600               --> Model: CTW600 (nicht getestet) </li>    
+  </ul>
+  <br>
+  Neu empfangene Sensoren werden in FHEM per autocreate angelegt.
+  <br><br>
+
+  <a name="SD_WS09_Define"></a>
+  <b>Define</b> 
+  <ul>Die empfangenen Sensoren werden automatisch angelegt.<br>
+  Die ID der angelegten Sensoren wird nach jedem Batteriewechsel ge&aumlndert, welche der Sensor beim Einschalten zuf&aumlllig vergibt.<br>
+  CRC Checksumme wird zur Zeit noch nicht überpr&uumlft, deshalb werden Sensoren bei denen die Luftfeuchte < 0 oder > 100 ist, nicht angelegt.<br>
+  </ul>
+  <br>
+  <a name="SD_WS09 Events"></a>
+  <b>Generierte Readings:</b>
+  <ul>
+     <li>State (T: H: Ws: Wg: Wd: R: )  temperature, humidity, windSpeed, windGuest, windDirection, Rain</li>
+     <li>Temperature (&deg;C)</li>
+     <li>Humidity: (The humidity (1-100 if available)</li>
+     <li>Battery: (low or ok)</li>
+     <li>ID: (The ID-Number (number if)</li>
+     <li>windSpeed (m/s) and windDirection (N-O-S-W)</li>
+     <li>Rain (mm)</li>
+  </ul>
+  <br>
+  <b>Attribute</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+    <li>Model<br>
+        WH1080, CTW600
+    </li><br>
+    <li>windKorrektur<br>
+    Korrigiert die Nord-Ausrichtung des Windrichtungsmessers, wenn dieser nicht richtig nach Norden ausgerichtet ist. 
+      -3,-2,-1,0,1,2,3    
+    </li><br>
+   </ul>
+
+  <a name="SD_WS09_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS09_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html_DE
+=cut

+ 298 - 0
fhem/core/FHEM/14_SD_WS_Maverick.pm

@@ -0,0 +1,298 @@
+##############################################
+# $Id: 14_SD_WS_Maverick.pm 12233 2016-10-01 22:22:51Z mrsidey $
+# 
+# The purpose of this module is to support Maverick sensors
+# Sidey79 & Cruizer 2016
+#
+
+package main;
+
+
+use strict;
+use warnings;
+
+#use Data::Dumper;
+
+
+sub
+SD_WS_Maverick_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^P47#AA9995[A-Fa-f0-9]+";    
+  $hash->{DefFn}     = "SD_WS_Maverick_Define";
+  $hash->{UndefFn}   = "SD_WS_Maverick_Undef";
+  $hash->{ParseFn}   = "SD_WS_Maverick_Parse";
+  $hash->{AttrFn}	 = "SD_WS_Maverick_Attr";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
+                        "$readingFnAttributes ";
+  $hash->{AutoCreate} =
+        { "SD_WS_Maverick.*" => { ATTR => "event-min-interval:.*:300 event-on-change-reading:.*", FILTER => "%NAME",  autocreateThreshold => "2:180"} };
+## Todo: Pruefen der Autocreate Einstellungen
+
+}
+
+#############################
+sub
+SD_WS_Maverick_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> SD_WS_Maverick <model>  ".int(@a)
+        if(int(@a) < 3 );
+
+  $hash->{CODE} = $a[2];
+  $hash->{lastMSG} =  "";
+ # $hash->{bitMSG} =  "";
+
+  $modules{SD_WS_Maverick}{defptr}{$a[2]} = $hash;
+  $hash->{STATE} = "Defined";
+  
+  my $name= $hash->{NAME};
+  return undef;
+}
+
+#####################################
+sub
+SD_WS_Maverick_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{SD_WS_Maverick}{defptr}{$hash->{CODE}})
+     if(defined($hash->{CODE}) &&
+        defined($modules{SD_WS_Maverick}{defptr}{$hash->{CODE}}));
+  return undef;
+}
+
+
+###################################
+sub
+SD_WS_Maverick_Parse($$)
+{
+  my ($iohash, $msg) = @_;
+  #my $rawData = substr($msg, 2);
+  my $name = $iohash->{NAME};
+  my (undef ,$rawData) = split("#",$msg);
+  #$protocol=~ s/^P(\d+)/$1/; # extract protocol
+
+  my $model = "SD_WS_Maverick";
+  my $hlen = length($rawData);
+  #my $blen = $hlen * 4;
+  #my $bitData = unpack("B$blen", pack("H$hlen", $rawData)); 
+
+  Log3 $name, 3, "SD_WS_Maverick_Parse  $model ($msg) length: $hlen";
+  
+  #1      8     13    18       26 
+  #AA999559 55555 95999 A9A9A669  Sensor 1 =21 2Grad
+  #AA999559 95996 55555 95A65565  Sensor 2 =22 2Grad
+  #
+  #Header    Sen1  Sens2   
+  #my $hashumidity = FALSE;
+  
+  ## Todo: Change decoding per model into a foreach  
+   #foreach $key (keys %models) {
+  #   ....
+  #}
+
+    #
+	my $startup = substr($rawData,6,2);
+	my $temp_str1 = substr($rawData,8,5);
+	my $temp_str2 = substr($rawData,13,5);
+	my $unknown = substr($rawData,18);
+  
+    Log3 $iohash, 4, "$model decoded protocolid: 47 sensor startup=$startup, temp1=$temp_str1, temp2=$temp_str2, unknown=$unknown";
+    
+    # Convert 
+    $temp_str1 =~ tr/569A/0123/;
+    $temp_str2 =~ tr/569A/0123/;
+    
+    # Calculate temp from data
+    my $c;
+	my $temp1=-532;
+	for ( my $i = 0; $i < length($temp_str1); $i++ ) { 
+	    $c = substr( $temp_str1, $i, 1);
+	    $temp1 += $c*4**(4-$i);
+	}
+    
+    my $temp2=-532;
+	for ( my $i = 0; $i < length($temp_str2); $i++ ) { 
+	    $c = substr( $temp_str2, $i, 1);
+	    $temp2 += $c*4**(4-$i);
+	}
+    
+    
+    Log3 $iohash, 4, "$model decoded protocolid: temp1=$temp1, temp2=$temp2;";
+    
+    
+    
+  
+
+    
+    #print Dumper($modules{SD_WS_Maverick}{defptr});
+    
+    my $def = $modules{SD_WS_Maverick}{defptr}{$iohash->{NAME} };
+    $def = $modules{SD_WS_Maverick}{defptr}{$model} if(!$def);
+
+    if(!$def) {
+		Log3 $iohash, 1, 'SD_WS_Maverick: UNDEFINED sensor ' . $model;
+		return "UNDEFINED $model SD_WS_Maverick $model";
+    }
+        #Log3 $iohash, 3, 'SD_WS_Maverick: ' . $def->{NAME} . ' ' . $id;
+	
+	my $hash = $def;
+	$name = $hash->{NAME};
+	Log3 $name, 4, "SD_WS_Maverick: $name ($rawData)";  
+
+	if (!defined(AttrVal($hash->{NAME},"event-min-interval",undef)))
+	{
+		my $minsecs = AttrVal($iohash->{NAME},'minsecs',0);
+		if($hash->{lastReceive} && (time() - $hash->{lastReceive} < $minsecs)) {
+			Log3 $hash, 4, "$model Dropped due to short time. minsecs=$minsecs";
+		  	return "";
+		}
+	}
+
+	$hash->{lastReceive} = time();
+	$hash->{lastMSG} = $rawData;
+	#$hash->{bitMSG} = $bitData2; 
+
+    my $state = "T: $temp1"." T2: $temp2" ;
+    
+    readingsBeginUpdate($hash);
+    readingsBulkUpdate($hash, "state", $state);
+    readingsBulkUpdate($hash, "messageType", $startup);
+    
+    readingsBulkUpdate($hash, "temp1", $temp1)  if ($temp1 ne"");
+    readingsBulkUpdate($hash, "temp2", $temp2)  if ($temp2 ne"");
+ 
+    readingsEndUpdate($hash, 1); # Notify is done by Dispatch
+
+	return $name;
+
+}
+
+sub SD_WS_Maverick_Attr(@)
+{
+  my @a = @_;
+
+  # Make possible to use the same code for different logical devices when they
+  # are received through different physical devices.
+  return  if($a[0] ne "set" || $a[2] ne "IODev");
+  my $hash = $defs{$a[1]};
+  my $iohash = $defs{$a[3]};
+  my $cde = $hash->{CODE};
+  delete($modules{SD_WS_Maverick}{defptr}{$cde});
+  $modules{SD_WS_Maverick}{defptr}{$iohash->{NAME} . "." . $cde} = $hash;
+  return undef;
+}
+
+
+1;
+
+
+=pod
+=item summary    Supports maverick temperature sensors protocl 47 from SIGNALduino
+=item summary_DE Unterst&uumltzt Maverick Temperatursensoren mit Protokol 47 vom SIGNALduino
+=begin html
+
+<a name="SD_WS_Maverick"></a>
+<h3>Wether Sensors protocol #7</h3>
+<ul>
+  The SD_WS_Maverick module interprets temperature sensor messages received by a Device like CUL, CUN, SIGNALduino etc.<br>
+  <br>
+  <b>Known models:</b>
+  <ul>
+    <li>Eurochon EAS800z</li>
+    <li>Technoline WS6750/TX70DTH</li>
+  </ul>
+  <br>
+  New received device are add in fhem with autocreate.
+  <br><br>
+
+  <a name="SD_WS_Maverick_Define"></a>
+  <b>Define</b> 
+  <ul>The received devices created automatically.<br>
+  The ID of the defice is the cannel or, if the longid attribute is specified, it is a combination of channel and some random generated bits at powering the sensor and the channel.<br>
+  If you want to use more sensors, than channels available, you can use the longid option to differentiate them.
+  </ul>
+  <br>
+  <a name="SD_WS_Maverick Events"></a>
+  <b>Generated readings:</b>
+  <br>Some devices may not support all readings, so they will not be presented<br>
+  <ul>
+  	 <li>State (T: H:)</li>
+     <li>temperature (&deg;C)</li>
+     <li>humidity: (The humidity (1-100 if available)</li>
+     <li>battery: (low or ok)</li>
+     <li>channel: (The Channelnumber (number if)</li>
+  </ul>
+  <br>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#model">model</a> ()</li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+
+  <a name="SD_WS_Maverick_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS_Maverick_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="SD_WS_Maverick"></a>
+<h3>SD_WS_Maverick</h3>
+<ul>
+  Das SD_WS_Maverick Module verarbeitet von einem IO Geraet (CUL, CUN, SIGNALDuino, etc.) empfangene Nachrichten von Temperatur-Sensoren.<br>
+  <br>
+  <b>Unterst&uumltzte Modelle:</b>
+  <ul>
+    <li>Maverick</li>
+  </ul>
+  <br>
+  Neu empfangene Sensoren werden in FHEM per autocreate angelegt.
+  <br><br>
+
+  <a name="SD_WS_Maverick_Define"></a>
+  <b>Define</b> 
+  <ul>Die empfangenen Sensoren werden automatisch angelegt.<br>
+  Die ID der angelegten Sensoren ist entweder der Kanal des Sensors, oder wenn das Attribut longid gesetzt ist, dann wird die ID aus dem Kanal und einer Reihe von Bits erzeugt, welche der Sensor beim Einschalten zuf&aumlllig vergibt.<br>
+  </ul>
+  <br>
+  <a name="SD_WS_Maverick Events"></a>
+  <b>Generierte Readings:</b>
+  <ul>
+  	 <li>State (T: H:)</li>
+     <li>temperature (&deg;C)</li>
+     <li>humidity: (Luftfeuchte (1-100)</li>
+     <li>battery: (low oder ok)</li>
+     <li>channel: (Der Sensor Kanal)</li>
+  </ul>
+  <br>
+  <b>Attribute</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#ignore">ignore</a></li>
+    <li><a href="#model">model</a> ()</li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+
+  <a name="SD_WS_Maverick1_Set"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="SD_WS_Maverick_Parse"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+</ul>
+
+=end html_DE
+=cut

+ 467 - 0
fhem/core/FHEM/15_CUL_EM.pm

@@ -0,0 +1,467 @@
+##############################################
+# $Id: 15_CUL_EM.pm 12712 2016-12-04 10:08:23Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+
+# Adjust TOTAL to you meter:
+# {$defs{emwz}{READINGS}{basis}{VAL}=<meter>/<corr2>-<total_cnt> }
+
+#####################################
+sub
+CUL_EM_Initialize($)
+{
+  my ($hash) = @_;
+
+  # Message is like
+  # K41350270
+
+  $hash->{Match}     = "^E0.................\$";
+  $hash->{DefFn}     = "CUL_EM_Define";
+  $hash->{UndefFn}   = "CUL_EM_Undef";
+  $hash->{ParseFn}   = "CUL_EM_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:0,1 showtime:0,1 " .
+                        "model:EMEM,EMWZ,EMGZ ignore:0,1 ".
+                        "maxPeak CounterOffset ".
+                        $readingFnAttributes;
+  $hash->{AutoCreate}=
+        { "CUL_EM.*" => { GPLOT => "power8:Power,", FILTER => "%NAME:CNT.*" } };
+}
+
+#####################################
+sub
+CUL_EM_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> CUL_EM <code> ".
+                                "[corr1 corr2 CostPerUnit BasicFeePerMonth]"
+            if(int(@a) < 3 || int(@a) > 7);
+  return "Define $a[0]: wrong CODE format: valid is 1-12"
+                if($a[2] !~ m/^\d+$/ || $a[2] < 1 || $a[2] > 12);
+
+  $hash->{CODE} = $a[2];
+
+  if($a[2] >= 1 && $a[2] <= 4) {                # EMWZ: nRotation in 5 minutes
+    my $c = (int(@a) > 3 ? $a[3] : 150);
+    $hash->{corr1} = (12/$c);                   # peak/current
+    $c = (int(@a) > 4 ? $a[4] : 1800);
+    $hash->{corr2} = (12/$c);                   # total
+
+  } elsif($a[2] >= 5 && $a[2] <= 8) {           # EMEM
+    # corr1 is the correction factor for power
+    $hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01);
+    # corr2 is the correction factor for energy
+    $hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.001);
+
+  } elsif($a[2] >= 9 && $a[2] <= 12) {          # EMGZ: 0.01
+    $hash->{corr1} = (int(@a) > 3 ? $a[3] : 0.01);
+    $hash->{corr2} = (int(@a) > 4 ? $a[4] : 0.01);
+
+  } else {
+    $hash->{corr1} = 1;
+    $hash->{corr2} = 1;
+  }
+  $hash->{CostPerUnit} = (int(@a) > 5 ? $a[5] : 0);
+  $hash->{BasicFeePerMonth} = (int(@a) > 6 ? $a[6] : 0);
+
+  $modules{CUL_EM}{defptr}{$a[2]} = $hash;
+  AssignIoPort($hash);
+  return undef;
+}
+
+#####################################
+sub
+CUL_EM_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{CUL_EM}{defptr}{$hash->{CODE}});
+  return undef;
+}
+
+#####################################
+sub
+CUL_EM_Parse($$)
+{
+  my ($hash,$msg) = @_;
+
+  # 0123456789012345678
+  # E01012471B80100B80B -> Type 01, Code 01, Cnt 10
+  my @a = split("", $msg);
+  my $tpe = ($a[1].$a[2])+0;
+  my $cde = hex($a[3].$a[4]);
+
+  # seqno    =  number of received datagram in sequence, runs from 2 to 255
+  # total_cnt=  total (cumulated) value in ticks as read from the device
+  # basis_cnt=  correction to total (cumulated) value in ticks to account for
+  #             counter wraparounds
+  # total    =  total (cumulated) value in device units
+  # current_cnt  =  current value (average over latest 5 minutes) in device units
+  # peak     =  maximum value in device units
+
+  my $seqno = hex($a[5].$a[6]);
+  my $total_cnt = hex($a[ 9].$a[10].$a[ 7].$a[ 8]);
+  my $current_cnt = hex($a[13].$a[14].$a[11].$a[12]);
+  my $peak_cnt = hex($a[17].$a[18].$a[15].$a[16]);
+
+  # these are the raw readings from the device
+  my $val = sprintf("CNT: %d CUM: %d  5MIN: %d  TOP: %d",
+                         $seqno, $total_cnt, $current_cnt, $peak_cnt);
+
+  if($modules{CUL_EM}{defptr}{$cde}) {
+    my $def = $modules{CUL_EM}{defptr}{$cde};
+    $hash = $def;
+    my $n = $hash->{NAME};
+    return "" if(IsIgnored($n));
+
+    my $tn = TimeNow();                 # current time
+    my $c= 0;                           # count changes
+    my %readings;
+
+    Log3 $n, 5, "CUL_EM $n: $val";
+    $readings{RAW} = $val;
+
+    #
+    # calculate readings
+    #
+    # initialize total_cnt_last
+    my $total_cnt_last = 0;
+    if(defined($hash->{READINGS}{total_cnt})) {
+      $total_cnt_last= $hash->{READINGS}{total_cnt}{VAL};
+    }
+
+
+    # initialize basis_cnt_last
+    my $basis_cnt = 0;
+    if(defined($hash->{READINGS}{basis})) {
+      $basis_cnt = $hash->{READINGS}{basis}{VAL};
+    }
+
+
+    #
+    # translate into device units
+    #
+    my $corr1 = $hash->{corr1}; # EMEM power correction factor
+    my $corr2 = $hash->{corr2}; # EMEM energy correction factor
+
+    my $peak;
+
+    if($tpe ne 2) {
+        $peak = $current_cnt && $peak_cnt ? 3000/$peak_cnt*$corr1 : 0;
+        # when EM detection toggles/glitches somewhere the internal
+        # EM-Counter increments by one and the device registers a
+        # very hi peak value
+        # Here we fix this by checking against a maximum peak
+        # level, removing the wrong counter increment and
+        # setting peak to the current value.
+        my $maxpeak = $attr{$n}{"maxPeak"};
+        if(defined $maxpeak and $peak > $maxpeak){
+            Log3 $n, 2, 
+            "CUL_EM $n: max peak detected: $peak kW > $maxpeak kW";
+            $current_cnt--;
+            # as total_cnt is "owned" by EM we decrement our basis_cnt
+            $basis_cnt--; 
+            $readings{basis} = $basis_cnt;
+            $peak = $current_cnt*$corr1;
+            $peak_cnt = $peak ? int(3000*$corr1/$peak) : 0;
+        }
+    } else {
+        $peak = $peak_cnt*$corr1;
+    }
+
+    # correct counter wraparound
+    if($total_cnt < $total_cnt_last) {
+      # check: real wraparound or reset only
+      $basis_cnt += ($total_cnt_last > 65000 ? 65536 : $total_cnt_last);
+      $readings{basis} = $basis_cnt;
+    }
+    my $counter_offset = AttrVal($n,"CounterOffset",0);
+
+    my $total    = (($basis_cnt+$total_cnt)*$corr2)+$counter_offset;
+    my $current  = $current_cnt*$corr1;
+
+    $val = sprintf("CNT: %d CUM: %0.3f  5MIN: %0.3f  TOP: %0.3f",
+                   $seqno, $total, $current, $peak);
+
+    readingsBeginUpdate($hash);
+    readingsBulkUpdate($hash, "state", $val);
+
+    $readings{total_cnt}   = $total_cnt;
+    $readings{current_cnt} = $current_cnt;
+    $readings{peak_cnt}    = $peak_cnt;
+    $readings{seqno}       = $seqno;
+    $readings{total}       = $total;
+    $readings{current}     = $current;
+    $readings{peak}        = $peak;
+
+
+    ###################################
+    # Start CUMULATE day and month
+    Log3 $n, 4, "CUL_EM $n: $val";
+    my $tsecs_prev;
+
+    #----- get previous tsecs
+    if(defined($hash->{READINGS}{tsecs})) {
+      $tsecs_prev= $hash->{READINGS}{tsecs}{VAL};
+    } else {
+      $tsecs_prev= 0; # 1970-01-01
+    }
+
+    #----- save actual tsecs
+    my $tsecs= time();  # number of non-leap seconds since January 1, 1970, UTC
+    $readings{tsecs}       = $tsecs;
+
+    #----- get cost parameter
+    my $cost = $hash->{CostPerUnit};
+    my $basicfee = $hash->{BasicFeePerMonth};
+
+    #----- check whether day or month was changed
+    if(!defined($hash->{READINGS}{cum_day})) {
+      #----- init cum_day if it is not set
+      $val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f", 0,$total,0);
+      $readings{cum_day}   = $val;
+
+    } else {
+
+      if( (localtime($tsecs_prev))[3] != (localtime($tsecs))[3] ) {
+        #----- day has changed (#3)
+        my @cmv = split(" ", $hash->{READINGS}{cum_day}{VAL});
+        $val = sprintf("CUM_DAY: %0.3f CUM: %0.3f COST: %0.2f",
+                        $total-$cmv[3], $total, ($total-$cmv[3])*$cost);
+        $readings{cum_day} = $val;
+        Log3 $n, 3, "CUL_EM $n: $val";
+
+
+        if( (localtime($tsecs_prev))[4] != (localtime($tsecs))[4] ) {
+
+          #----- month has changed (#4)
+          if(!defined($hash->{READINGS}{cum_month})) {
+            # init cum_month if not set
+            $val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f",
+                       0, $total, 0);
+            $readings{cum_month} = $val;
+
+          } else {
+            @cmv = split(" ", $hash->{READINGS}{cum_month}{VAL});
+            $val = sprintf("CUM_MONTH: %0.3f CUM: %0.3f COST: %0.2f",
+                       $total-$cmv[3], $total,($total-$cmv[3])*$cost+$basicfee);
+            $readings{cum_month} = $val;
+            Log3 $n, 3, "CUL_EM $n: $val";
+
+          }
+        }
+      }
+    }
+    # End CUMULATE day and month
+    ###################################
+
+
+    foreach my $k (keys %readings) {
+      readingsBulkUpdate($hash, $k, $readings{$k});
+    }
+    readingsEndUpdate($hash, 1);
+    return $hash->{NAME};
+
+  } else {
+
+    Log3 $hash, 1, "CUL_EM detected, Code $cde $val";
+    return "UNDEFINED CUL_EM_$cde CUL_EM $cde";
+
+  }
+
+}
+
+1;
+
+
+=pod
+=item summary    devices communicating via the ELV EM protocol (EM1000WZ, etc)
+=item summary_DE Anbindung von ELV Ger&auml;ten mit dem EM Protokoll (EM1000WZ, usw.)
+=begin html
+
+<a name="CUL_EM"></a>
+<h3>CUL_EM</h3>
+<ul>
+  The CUL_EM module interprets EM type of messages received by the CUL, notably
+  from EMEM, EMWZ or EMGZ devices.
+  <br><br>
+
+  <a name="CUL_EMdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_EM &lt;code&gt; [corr1 corr2
+                CostPerUnit BasicFeePerMonth]</code> <br>
+    <br>
+    &lt;code&gt; is the code which must be set on the EM device. Valid values
+    are 1 through 12. 1-4 denotes EMWZ, 5-8 EMEM and 9-12 EMGZ devices.<br><br>
+
+    <b>corr1</b> is used to correct the current number, <b>corr2</b>
+    for the total number.
+    <ul>
+      <li>for EMWZ devices you should specify the rotation speed (R/kW)
+          of your watt-meter (e.g. 150) for corr1 and 12 times this value for
+          corr2</li>
+      <li>for EMEM devices the corr1 value is 0.01, and the corr2 value is
+          0.001 </li>
+    </ul>
+    <br>
+
+    <b>CostPerUnit</b> and <b>BasicFeePerMonth</b> are used to compute your
+    daily and monthly fees. Your COST will appear in the log, generated once
+    daily (without the basic fee) or month (with the bassic fee included). Your
+    definition should look like e.g.:
+    <ul><code>
+    define emwz 1 75 900 0.15 12.50<br>
+    </code></ul>
+    and the Log looks like:
+    <ul><code>
+    CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02<br>
+    CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34<br>
+    </code></ul>
+
+    Tip: You can configure your EMWZ device to show in the CUM column of the
+    STATE reading the current reading of your meter. For this purpose: multiply
+    the current reading (from the real device) with the corr1 value (RperKW),
+    and subtract the RAW CUM value from it. Now set the basis reading of your
+    EMWZ device (named emwz) to this value.<br>
+
+  </ul>
+  <br>
+
+  <a name="CUL_EMset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="CUL_EMget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_EMattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#model">model</a> (EMEM,EMWZ,EMGZ)</li><br>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
+    <li><a name="maxPeak">maxPeak</a> &lt;number&gt;<br>
+      Specifies the maximum possible peak value for the EM meter
+      ("TOP:" value in logfile). Peak values greater than this value
+      are considered as EM read errors and are ignored.
+      For example if it's not possible to consume more than 40kW of
+      power set maxPeak to 40 to make the readings of the power meter
+      more robust.
+    </li><br>
+
+    <li><a name="CounterOffset">CounterOffset</a><br>
+      Specifies the difference between true (gas) meter value and 
+      value reported by the EMGZ.<br>
+      CounterOffset = true Value - Reading "total"<br>
+      Example:
+      <ul>
+        <code>attr Gaszaehler CounterOffset 15427.434</code><br>
+      </ul>
+    </li>
+
+  </ul>
+  <br>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="CUL_EM"></a>
+<h3>CUL_EM</h3>
+<ul>
+  Das Modul CUL_EM wertet von einem CUL empfange Botschaften des Typs EM aus, 
+  dies sind aktuell Botschaften von EMEM, EMWZ bzw. EMGZ Ger&auml;ten.
+  <br><br>
+
+  <a name="CUL_EMdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_EM &lt;code&gt; [corr1 corr2
+                CostPerUnit BasicFeePerMonth]</code> <br>
+    <br>
+    &lt;code&gt; ist der Code, der am EM Ger&auml;t eingestellt wird.
+    G&uuml;tige Werte sind 1 bis 12. 1-4 gilt f&uuml;r EMWZ, 5-8 f&uuml;r EMEM
+    und 9-12 f&uuml;r EMGZ Ger&auml;te.<br><br>
+
+    <b>corr1</b> ist der Kalibrierfaktor f&uuml;r den Momentanverbrauch,
+    <b>corr2</b> f&uuml;r den Gesamtverbrauch.
+
+    <ul>
+      <li>f&uuml;r EMWZ Ger&auml;te wird die Umdrehungsgeschwindigkeit (U/kW)
+          des verwendeten Stromz&auml;hlers (z.B. 150) f&uuml;r corr1 und 12 mal 
+          diesen Wert f&uuml;r corr2 verwendet</li>
+      <li>f&uuml;r EMEM devices ist corr1 mit 0.01 und corr2 mit 0.001
+      anzugeben</li>
+    </ul>
+    <br>
+
+    <b>CostPerUnit</b> und <b>BasicFeePerMonth</b> werden dazu verwendet, die 
+    t&auml;gliche bzw. monatliche Kosten zu berechnen. Die Kosten werden in der 
+    Logdatei einmal t&auml;glich (ohne Fixkosten) bzw. monatlich (mit Fixkosten) 
+    generiert und angezeigt.
+    Die Definition sollte in etwa so aussehen:
+    <ul><code>
+    define emwz 1 75 900 0.15 12.50<br>
+    </code></ul>
+    und in der Logdatei sollten diese Zeilen erscheinen:
+    <ul><code>
+    CUM_DAY: 6.849 CUM: 60123.4 COST: 1.02<br>
+    CUM_MONTH: 212.319 CUM: 60123.4 COST: 44.34<br>
+    </code></ul>
+
+    Tipp: Das EMWZ Ger&auml;t kann so konfiguriert werden, dass es in der CUM
+    Spalte des STATE Wertes den aktuellen Wert des Stromz&auml;hlers anzeigt.
+    Hierf&uuml;r muss der aktuell am Stromz&auml;hler abgelesene Wert mit corr1
+    (U/kW) multipliziert werden und der CUM Rohwert aus der aktuellen fhem
+    Messung ('reading') davon abgezogen werden. Dann muss dieser Wert als
+    Basiswert des EMWZ Ger&auml;tes (im Beispiel emwz) gesetzt werden.<br>
+
+  </ul>
+  <br>
+
+  <a name="CUL_EMset"></a>
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="CUL_EMget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_EMattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <li><a href="#showtime">showtime</a></li><br>
+    <li><a href="#model">model</a> (EMEM,EMWZ,EMGZ)</li><br>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#eventMap">eventMap</a></li><br>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
+    <li><a name="maxPeak">maxPeak</a> &lt;number&gt;<br>
+      Gibt den maximal m&ouml;glichen Spitzenwert f&uumlr das EM-Meter an 
+      ("TOP:"-Wert in Logdatei). Spitzenwerte gr&ouml;&szlig;er als dieser 
+      Wert gelten als EM-Lesefehler und werden ignoriert.
+      Wenn es z.B. nicht m&ouml;glich ist mehr zu 40kW Leistung 
+      zu beziehen setzt man maxPeak auf 40 um das Auslesen des 
+      Stromz&auml;hlers robuster zu machen.
+      </li><br>
+      <li><a name="CounterOffset">CounterOffset</a><br>
+      Gibt den Unterschied zwischen dem tats&auml;chlichen Z&auml;hlerstand und
+      dem vom EMGZ gemeldeten Wert an.<br>
+      CounterOffset = tats&auml;chlicher Z&auml;hlerstand - Reading "total"<br>
+   Beispiel:
+    <ul>
+      <code>attr Gaszaehler CounterOffset 15427.434</code><br>
+    </ul>
+    </li>
+  </ul>
+  <br>
+</ul>
+
+=end html_DE
+=cut

+ 228 - 0
fhem/core/FHEM/16_CUL_RFR.pm

@@ -0,0 +1,228 @@
+##############################################
+# $Id: 16_CUL_RFR.pm 11984 2016-08-19 12:47:50Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+
+#####################################
+sub
+CUL_RFR_Initialize($)
+{
+  my ($hash) = @_;
+
+  # Message is like
+  # K41350270
+
+  $hash->{Match}     = "^[0-9A-F]{4}U.";
+  $hash->{DefFn}     = "CUL_RFR_Define";
+  $hash->{FingerprintFn} = "RFR_FingerprintFn";
+  $hash->{UndefFn}   = "CUL_RFR_Undef";
+  $hash->{ParseFn}   = "CUL_RFR_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:0,1 model:CUL,CUN,CUR " .
+                       "ignore:0,1 addvaltrigger";
+
+  $hash->{WriteFn}   = "CUL_RFR_Write";
+  $hash->{GetFn}     = "CUL_Get";
+  $hash->{SetFn}     = "CUL_Set";
+  $hash->{noRawInform} = 1;     # Our message was already sent as raw.
+  $hash->{AddPrefix} = "CUL_RFR_AddPrefix"; 
+  $hash->{DelPrefix} = "CUL_RFR_DelPrefix"; 
+  $hash->{noAutocreatedFilelog} = 1;
+}
+
+
+sub
+RFR_FingerprintFn($$)
+{
+  my ($name, $msg) = @_;
+ 
+  # Store only the "relevant" part, as the CUL won't compute the checksum
+  $msg = substr($msg, 8) if($msg =~ m/^81/ && length($msg) > 8);
+ 
+  return ($name, $msg);
+}
+
+#####################################
+sub
+CUL_RFR_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> CUL_RFR <id> <routerid>"
+            if(int(@a) != 4 ||
+               $a[2] !~ m/[0-9A-F]{2}/i ||
+               $a[3] !~ m/[0-9A-F]{2}/i);
+  $hash->{ID} = $a[2];
+  $hash->{ROUTERID} = $a[3];
+  $modules{CUL_RFR}{defptr}{"$a[2]$a[3]"} = $hash;
+  $hash->{STATE} = "Defined";
+  AssignIoPort($hash);
+  return undef;
+}
+
+#####################################
+sub
+CUL_RFR_Write($$)
+{
+  my ($hash,$fn,$msg) = @_;
+
+  ($fn, $msg) = CUL_WriteTranslate($hash, $fn, $msg);
+  return if(!defined($fn));
+  $msg = $hash->{ID} . $hash->{ROUTERID} . $fn . $msg;
+  IOWrite($hash, "u", $msg);
+}
+
+#####################################
+sub
+CUL_RFR_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{CUL_RFR}{defptr}{$hash->{ID} . $hash->{ROUTERID}});
+  return undef;
+}
+
+#####################################
+sub
+CUL_RFR_Parse($$)
+{
+  my ($iohash,$msg) = @_;
+
+  # 0123456789012345678
+  # E01012471B80100B80B -> Type 01, Code 01, Cnt 10
+  $msg =~ m/^([0-9AF]{2})([0-9AF]{2})U(.*)/;
+  my ($rid, $id, $smsg) = ($1,$2,$3);
+  my $cde = "${id}${rid}";
+
+  if(!$modules{CUL_RFR}{defptr}{$cde}) {
+    Log3 $iohash, 1, "CUL_RFR detected, Id $id, Router $rid, MSG $smsg";
+    return "UNDEFINED CUL_RFR_$id CUL_RFR $id $rid";
+  }
+  my $hash = $modules{CUL_RFR}{defptr}{$cde};
+  my $name = $hash->{NAME};
+  return "" if(IsIgnored($name));
+
+  $hash->{Clients}   = $iohash->{Clients};
+  $hash->{MatchList} = $iohash->{MatchList};
+
+  my @m = split(";", $smsg, -1);  # process only messages terminated with ;
+  for(my $i = 0; $i < $#m; $i++) {
+    my $m = $m[$i];
+
+    # Compressed FHT messages
+    while($m =~ m/^T(....)(..)(..)(..)(..)(..)(.*)(..)$/) {
+      my ($fhtid, $cmd, $source, $val, $cmd2, $val2, $rest, $rssi) =
+         ($1, $2, $3, $4, $5, $6, $7, $8);
+      my $firstmsg = "T$fhtid$cmd$source$val$rssi";
+      $m = "T$fhtid$cmd2$source$val2$rest$rssi";
+      CUL_Parse($hash, $iohash, $hash->{NAME}, $firstmsg);
+    }
+
+    CUL_Parse($hash, $iohash, $hash->{NAME}, $m);
+       if($m =~ m/^T/) { $hash->{NR_TMSG}++ }
+    elsif($m =~ m/^F/) { $hash->{NR_FMSG}++ }
+    elsif($m =~ m/^E/) { $hash->{NR_EMSG}++ }
+    elsif($m =~ m/^K/) { $hash->{NR_KMSG}++ }
+    else               { $hash->{NR_RMSG}++ }
+  }
+  return "";
+}
+
+sub
+CUL_RFR_DelPrefix($$)
+{
+  my ($hash, $msg) = @_;
+  $msg = $1 if($msg =~ m/^\d{4}U(.*)$/);
+  $msg =~ s/;([\r\n]*)$/$1/; # ???
+  return $msg;
+}
+
+sub
+CUL_RFR_AddPrefix($$)
+{
+  my ($hash, $msg) = @_;
+  return "u" . $hash->{ID} . $hash->{ROUTERID} . $msg;
+}
+
+1;
+
+
+=pod
+=item summary    devices communicating over culfw RFR (SlowRF repeater)
+=item summary_DE Anbindung von Ger&auml;ten &uuml;ber ein culfw RFR (SlowRF repeater)
+=begin html
+
+<a name="CUL_RFR"></a>
+<h3>CUL_RFR</h3>
+<ul>
+  <table>
+  <tr><td>
+  The CUL_RFR  module is used to "attach" a second CUL to your base CUL, and
+  use it as a repeater / range extender. RFR is shorthand for RF_ROUTER.
+  Transmission of the data uses the CC1101 packet capabilities with GFSK
+  modulation at 250kBaud after pinging the base CUL at the usual 1kBaud. When
+  configured, the RFR device can be used like another CUL connected directly to
+  fhem.
+
+
+  <br><br>
+  Before you can use this feature in fhem, you have to enable/configure RF
+  ROUTING in both CUL's:
+  <ul>
+    <li>First give your base CUL (which remains connected to the PC) an RFR ID
+    by issuing the fhem command "set MyCUL raw ui0100". With this command
+    the base CUL will get the ID 01, and it will not relay messages to other
+    CUL's (as the second number is 00).</li>
+    <li>Now replace the base CUL with the RFR CUL, and set its id by issuing
+    the fhem command "set MyCUL raw ui0201". Now remove this CUL and attach the
+    original, base CUL again. The RFR CUL got the id 02, and will relay every
+    message to the base CUL with id 01.</li>
+    <li>Take the RFR CUL, and attach it to an USB power supply, as seen on
+    the image. As the configured base id is not 00, it will activate RF
+    reception on boot, and will start sending messages to the base CUL.</li>
+    <li>Now you have to define this RFR cul as a fhem device:</li>
+  </ul>
+
+  </td><td>
+  <img src="cul_rfr.jpg"/>
+  </td></tr>
+  </table>
+  <br>
+
+  <a name="CUL_RFRdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_RFR &lt;own-id&gt; &lt;base-id&gt;</code> <br>
+    <br>
+    &lt;own-id&gt; is the id of the RFR CUL <b>not</b> connected to the PC,
+    &lt;base-id&gt; is the id of the CUL connected to the PC. Both parameters
+    have two characters, each representing a one byte hex number.<br>
+    Example:
+    <ul>
+      <code>set MyCUL raw ui0100</code><br>
+      # Now replace the base CUL with the RFR CUL<br>
+      <code>set MyCUL raw ui0201</code><br>
+      # Reattach the base CUL to the PC and attach the RFR CUL to a
+      USB power supply<br>
+      <code>define MyRFR CUL_RFR 02 01</code><br>
+    </ul>
+    </ul> <br>
+
+  <a name="CUL_RFRset"></a>
+  <b>Set</b> <ul>Same as for the <a href="#CULset">CUL</a>.</ul><br>
+
+  <a name="CUL_RFRget"></a>
+  <b>Get</b> <ul>Same as for the <a href="#CULget">CUL</a>.</ul><br>
+
+  <a name="CUL_RFRattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#ignore">ignore</a></li><br>
+    <li><a href="#IODev">IODev</a></li><br>
+    The rest of the attributes is the same as for the <a href="#CUL">CUL</a>.</ul><br>
+  </ul>
+  <br>
+
+=end html
+=cut

+ 323 - 0
fhem/core/FHEM/16_STACKABLE_CC.pm

@@ -0,0 +1,323 @@
+##############################################
+# $Id: 16_STACKABLE_CC.pm 12973 2017-01-06 10:01:25Z rudolfkoenig $
+package main;
+use strict;
+use warnings;
+
+#####################################
+sub
+STACKABLE_CC_Initialize($)
+{
+  my ($hash) = @_;
+  LoadModule("CUL");
+
+  $hash->{Match}     = "^\\*";
+  $hash->{DefFn}     = "STACKABLE_CC_Define";
+  $hash->{UndefFn}   = "STACKABLE_CC_Undef";
+  $hash->{ParseFn}   = "STACKABLE_CC_Parse";
+  $hash->{NotifyFn}  = "STACKABLE_CC_Notify";
+  $hash->{AttrFn}    = "CUL_Attr";
+  $hash->{AttrList}  = "IODev ignore:0,1 ".$modules{CUL}{AttrList};
+
+  $hash->{WriteFn}   = "STACKABLE_CC_Write";
+  $hash->{GetFn}     = "CUL_Get";
+  $hash->{SetFn}     = "CUL_Set";
+  $hash->{AddPrefix} = "STACKABLE_CC_AddPrefix"; 
+  $hash->{DelPrefix} = "STACKABLE_CC_DelPrefix"; 
+  $hash->{noRawInform} = 1;     # Our message was already sent as raw.
+  $hash->{noAutocreatedFilelog} = 1;
+
+  $hash->{IOOpenFn}  = "STACKABLE_IOOpenFn";
+  $hash->{IOReadFn}  = "STACKABLE_IOReadFn";
+  $hash->{IOWriteFn} = "STACKABLE_IOWriteFn";
+}
+
+#####################################
+sub
+STACKABLE_CC_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  $hash->{TCM} = pop @a if(int(@a) == 4 && $a[3] eq "TCM");
+
+  return "wrong syntax: define <name> STACKABLE_CC [CUL|SCC] [TCM]"
+    if(int(@a) != 3);
+
+  my $io = $defs{$a[2]};
+  return "$a[2] is not a CUL/STACKABLE_CC"
+    if(!$io || $io->{TYPE} !~ m/^(CUL|TSCUL|STACKABLE_CC|TSSTACKED)$/);
+
+  return "$io->{NAME} has alread a stacked device: $io->{STACKED}"
+    if($io->{STACKED});
+
+  $io->{STACKED} = $hash->{NAME};
+  $hash->{IODev} = $io;
+  delete($io->{".clientArray"}); # Force a recompute
+
+  if(!$hash->{TCM}) {
+    $hash->{initString} = $io->{initString};
+    $hash->{CMDS} = "";
+    $hash->{Clients} = $io->{Clients};
+    $hash->{MatchList} = $io->{MatchList};
+    CUL_DoInit($hash);
+  }
+
+  $hash->{StackLevel} = $io->{StackLevel} ? $io->{StackLevel}+1 : 1;
+  $hash->{STATE} = "Defined";
+
+  notifyRegexpChanged($hash, $a[2]);
+
+  return undef;
+}
+
+sub
+STACKABLE_CC_DoNotify($)
+{
+  my ($ntfy) = @_;
+  DoTrigger($ntfy->{NAME}, $ntfy->{TriggerText});
+  delete $ntfy->{TriggerText};
+}
+
+sub
+STACKABLE_CC_Notify($$)
+{
+  my ($ntfy, $dev) = @_;
+  my $events = deviceEvents($dev, 0);
+
+  for(my $i = 0; $i < @{$events}; $i++) {
+
+    if($events->[$i] eq "DISCONNECTED") {
+      $ntfy->{STATE} = "disconnected";
+      setReadingsVal($ntfy, "state", "disconnected", TimeNow());
+      $ntfy->{TriggerText} = $events->[$i];
+      InternalTimer(gettimeofday()+0.1, "STACKABLE_CC_DoNotify", $ntfy, 0);
+
+    } elsif($events->[$i] eq "CONNECTED") {
+      CUL_DoInit($ntfy);
+      $ntfy->{TriggerText} = $events->[$i];
+      InternalTimer(gettimeofday()+0.001, "STACKABLE_CC_DoNotify", $ntfy, 0);
+
+    }
+  }
+}
+
+#####################################
+sub
+STACKABLE_CC_Write($$)
+{
+  my ($hash,$fn,$msg) = @_;
+
+  ($fn, $msg) = CUL_WriteTranslate($hash, $fn, $msg);
+  return if(!defined($fn));
+  IOWrite($hash, "", ($hash->{TCM} ? "%":"*")."$fn$msg"); # No more translations
+}
+
+#####################################
+sub
+STACKABLE_CC_Parse($$)
+{
+  my ($iohash,$msg) = @_;
+
+  $msg =~ s/^.//; # Cut off prefix *
+  my $name = $iohash->{STACKED} ? $iohash->{STACKED} : "";
+
+  my $id = $iohash->{StackLevel} ? $iohash->{StackLevel}+1 : 1;
+  return "UNDEFINED STACKABLE_CC_$id STACKABLE_CC $iohash->{NAME}"
+    if(!$name);
+
+  return "" if(IsIgnored($name));
+
+  my $sh = $defs{$name};
+  if($sh && $sh->{TCM}) {
+    my $th = $sh->{TCMHash};
+    if($th) {
+      delete $th->{IOReadFn};
+      $th->{IODevRxBuffer} = pack("H*", $msg);
+      CallFn($th->{NAME}, "ReadFn", $th);
+      $th->{IOReadFn} = "STACKABLE_IOReadFn";
+    } else {
+      Log 1, "$name: no TCM device assigned";
+    }
+    
+  } else {
+    CUL_Parse($defs{$name}, $iohash, $name, $msg);
+  }
+  return "";
+}
+
+sub
+STACKABLE_CC_DelPrefix($$)
+{
+  my ($hash, $msg) = @_;
+  $msg =~ s/^[^A-Z0-9]//i;
+  return $msg;
+}
+
+sub
+STACKABLE_CC_AddPrefix($$)
+{
+  my ($hash, $msg) = @_;
+  return "*$msg";
+}
+
+sub
+STACKABLE_CC_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  CUL_SimpleWrite($hash, "X00");
+  delete $hash->{IODev}{STACKED};
+  return undef;
+}
+
+sub
+STACKABLE_IOOpenFn($)
+{
+  my ($hash) = @_;
+  $hash->{FD} = $hash->{IODev}{IODev}{FD};     # Lets fool the TCM
+  $hash->{IODev}{TCMHash} = $hash;
+  $hash->{IOReadFn} = "STACKABLE_IOReadFn";
+  return 1;
+}
+
+sub
+STACKABLE_IOReadFn($)
+{
+  my ($hash) = @_;
+  my $me = $hash->{IODev};
+  my $buf = "";
+  while($buf !~ m/\n/) {
+    $buf .= DevIo_SimpleRead($me->{IODev}); # may block
+  }
+  $buf =~ s/[\r\n]//g;
+  $buf = STACKABLE_CC_DelPrefix($me, $buf);
+  return pack("H*",$buf);
+}
+
+sub
+STACKABLE_IOWriteFn($$)
+{
+  my ($hash, $msg) = @_;
+  return IOWrite($hash, "", unpack("H*",$msg));
+}
+
+1;
+
+
+=pod
+=item summary    Busware Stackable CC (SCC) base module
+=item summary_DE Busware Stackabble CC (SCC) basis Modul
+=begin html
+
+<a name="STACKABLE_CC"></a>
+<h3>STACKABLE_CC</h3>
+<ul>
+  This module handles the stackable CC1101 devices for the Raspberry PI from
+  busware.de. You can attach a lot of CUL-Type devices to a single RPi this way.
+  The first device is defined as a CUL, the rest of them as STACKABLE_CC.
+  <br><br>
+
+  <a name="STACKABLE_CCdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; STACKABLE_CC &lt;Base-Device-Name&gt;</code> <br>
+    <br>
+    &lt;Base-Device-Name&gt; is the name of the device, which this device is
+    attached on, the first one has to be defined as a CUL device<br>
+    Example:
+    <ul><code>
+      define SCC0 CUL /dev/ttyAMA0@38400<br>
+      attr SCC0 rfmode SlowRF<br>
+      define SCC1 STACKABLE_CC SCC0<br>
+      attr SCC1 rfmode HomeMatic<br>
+      define SCC2 STACKABLE_CC SCC1<br>
+      attr SCC2 rfmode Max<br>
+    </code></ul>
+    <b>Important:</b>
+    <ul>
+      <li>The rfmode has to be specified explicitely (valid for the STACKABLE_CC
+        types only, not for the first, which is defined as a CUL).</li>
+      <li>In case of SlowRF, the FHTID has to be specified explicitely with the
+        command "set SCCX raw T01HHHH". Again, this is valid for the STACKABLE_CC
+        types only.</li>
+      <li>If you rename the base CUL or a STACKABLE_CC, which is a base for
+        another one, the define of the next one has to be adjusted, and FHEM has to be
+        restarted.</li>
+    </ul>
+  </ul>
+
+  <a name="STACKABLE_CCset"></a>
+  <b>Set</b> <ul>Same as for the <a href="#CULset">CUL</a>.</ul><br>
+
+  <a name="STACKABLE_CCget"></a>
+  <b>Get</b> <ul>Same as for the <a href="#CULget">CUL</a>.</ul><br>
+
+  <a name="STACKABLE_CCattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#ignore">ignore</a></li><br>
+    The rest of the attributes is the same as for the <a href="#CULattr">CUL</a>.
+  </ul>
+</ul>
+
+=end html
+
+=begin html_DE
+
+<a name="STACKABLE_CC"></a>
+<h3>STACKABLE_CC</h3>
+<ul>
+  Mit Hilfe dieses Moduls kann man die "Stackable CC" Ger&auml;te von busware.de in
+  FHEM integrieren. Diese Ger&auml;te erm&ouml;glichen eine Menge von CULs an einem RPi
+  anzuschliessen.
+  Das erste Ger&auml;t wird als CUL definiert, alle nachfolgenden als STACKABLE_CC.
+  <br><br>
+
+  <a name="STACKABLE_CCdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; STACKABLE_CC &lt;Base-Device-Name&gt;</code> <br>
+    <br>
+    &lt;Base-Device-Name&gt; ist der Name des Ger&auml;tes, der als Basis f&uuml;r das
+    aktuelle Ger&auml;t dient.<br>
+    Beispiel:
+    <ul><code>
+      define SCC0 CUL /dev/ttyAMA0@38400<br>
+      attr SCC0 rfmode SlowRF<br>
+      define SCC1 STACKABLE_CC SCC0<br>
+      attr SCC1 rfmode HomeMatic<br>
+      define SCC2 STACKABLE_CC SCC1<br>
+      attr SCC2 rfmode Max<br>
+    </code></ul>
+    <b>Wichtig:</b>
+    <ul>
+      <li>Das rfmode Attribut muss explizit spezifiziert werden. Das gilt nur
+        f&uuml;r die STACKABLE_CC Definitionen, und nicht f&uuml;r die erste, die
+        als CUL definiert wurde.</li>
+      <li>Falls SlowRF spezifiziert wurde, dann muss das FHTID explizit gesetzt
+        werden, mit folgendem Kommando: "set SCCX raw T01HHHH". Auch das ist nur
+        f&uuml;r die STACKABLE_CC n&ouml;tig.</li>
+      <li>Falls ein Ger&auml;t umbenannt wird, was als Basis f&uuml;r ein STACKABLE_CC
+        dient, dann muss es auch in der Definition des abh&auml;ngigen Ger&auml;tes
+        umbenannt werden, und FHEM muss neugestartet werden.</li>
+    </ul>
+  </ul>
+
+  <a name="STACKABLE_CCset"></a>
+  <b>Set</b> <ul>Die gleichen wie f&uuml;r das <a href="#CULset">CUL</a>.</ul><br>
+
+  <a name="STACKABLE_CCget"></a>
+  <b>Get</b> <ul>Die gleichen wie f&uuml;r das <a href="#CULget">CUL</a>.</ul><br>
+
+  <a name="STACKABLE_CCattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#ignore">ignore</a></li><br>
+    Die anderen Attribute sind die gleichen wie f&uuml;r das <a href="#CULattr">CUL</a>.
+  </ul>
+</ul>
+=end html_DE
+
+=cut

+ 455 - 0
fhem/core/FHEM/17_EGPM2LAN.pm

@@ -0,0 +1,455 @@
+############################################## 
+# $Id: 17_EGPM2LAN.pm 12092 2016-08-30 10:39:50Z alexus2033 $
+#
+#  based / modified Version 98_EGPMS2LAN from ericl
+#
+#  (c) 2013, 2014 Copyright: Alex Storny (moselking at arcor dot de)
+#  All rights reserved
+#
+#  This script 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
+#  any later version.
+#
+#  The GNU General Public License can be found at
+#  http://www.gnu.org/copyleft/gpl.html.
+#
+#  This script 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.
+#
+################################################################
+#  -> Module 70_EGPM.pm (for a single Socket) needed.
+################################################################
+package main; 
+
+use strict; 
+use warnings; 
+use HttpUtils;
+
+sub 
+EGPM2LAN_Initialize($) 
+{ 
+  my ($hash) = @_; 
+  $hash->{Clients}   = ":EGPM:";
+  $hash->{GetFn}     = "EGPM2LAN_Get";
+  $hash->{SetFn}     = "EGPM2LAN_Set"; 
+  $hash->{DefFn}     = "EGPM2LAN_Define"; 
+  $hash->{AttrList}  = "loglevel:0,1,2,3,4,5,6 stateDisplay:sockNumber,sockName autocreate:on,off"; 
+} 
+
+###################################
+sub
+EGPM2LAN_Get($@)
+{
+    my ($hash, @a) = @_;
+    my $getcommand;
+
+    return "argument is missing" if(int(@a) != 2);
+    
+    $getcommand = $a[1];
+    
+    if($getcommand eq "state")
+    {
+      if(defined($hash->{STATE})) {
+          return $hash->{STATE}; }
+    } 
+    elsif($getcommand eq "lastcommand")
+    {
+      if(defined($hash->{READINGS}{lastcommand}{VAL})) { 
+          return $hash->{READINGS}{lastcommand}{VAL}; }
+    }
+    else
+    {
+         return "Unknown argument $getcommand, choose one of state:noArg lastcommand:noArg".(exists($hash->{READINGS}{output})?" output:noArg":"");
+    }
+    return "";
+}
+
+################################### 
+sub 
+EGPM2LAN_Set($@) 
+{ 
+  my ($hash, @a) = @_; 
+
+  return "no set value specified" if(int(@a) < 2); 
+  return "Unknown argument $a[1], choose one of on:1,2,3,4,all off:1,2,3,4,all toggle:1,2,3,4 clearreadings:noArg statusrequest:noArg" if($a[1] eq "?"); 
+
+  my $name = shift @a; 
+  my $setcommand = shift @a; 
+  my $params = join(" ", @a);
+  my $logLevel = GetLogLevel($name,4); 
+  Log $logLevel, "EGPM2LAN set $name (". $hash->{IP}. ") $setcommand $params";
+ 
+  EGPM2LAN_Login($hash, $logLevel); 
+  
+  if($setcommand eq "on" || $setcommand eq "off") 
+  { 
+    if($params eq "all")
+	  { #switch all Sockets; thanks to eric!
+  	  for (my $count = 1; $count <= 4; $count++)
+      {
+   	    EGPM2LAN_Switch($hash, $setcommand, $count, $logLevel);
+      }
+	  }
+	  else
+	  {  #switch single Socket
+       EGPM2LAN_Switch($hash, $setcommand, $params, $logLevel);
+    }
+    EGPM2LAN_Statusrequest($hash, $logLevel, 1); 
+  }   
+  elsif($setcommand eq "toggle") 
+  { 
+    my $currentstate = EGPM2LAN_Statusrequest($hash, $logLevel, 1);
+    if(defined($currentstate))
+    {
+    	my @powerstates = split(",", $currentstate);
+    	my $newcommand="off";
+    	if($powerstates[$params-1] eq "0")
+    	{
+    	   $newcommand="on";
+    	}
+      EGPM2LAN_Switch($hash, $newcommand, $params, $logLevel);
+	    EGPM2LAN_Statusrequest($hash, $logLevel, 0); 
+    } 
+  } 
+  elsif($setcommand eq "statusrequest") 
+  { 
+	   EGPM2LAN_Statusrequest($hash, $logLevel, 1); 
+  }
+  elsif($setcommand eq "clearreadings") 
+  { 
+	   delete $hash->{READINGS};
+  } 
+  else 
+  { 
+     return "unknown argument $setcommand, choose one of on, off, toggle, statusrequest, clearreadings"; 
+  } 
+  
+  EGPM2LAN_Logoff($hash, $logLevel); 
+
+  $hash->{CHANGED}[0] = $setcommand; 
+  $hash->{READINGS}{lastcommand}{TIME} = TimeNow(); 
+  $hash->{READINGS}{lastcommand}{VAL} = $setcommand." ".$params; 
+  
+  return undef; 
+} 
+
+################################
+sub EGPM2LAN_Switch($$$$) { 
+  my ($hash, $state, $port, $logLevel) = @_; 
+  $state = ($state eq "on" ? "1" : "0");
+  
+  my $fritz = 0; #may be important for FritzBox-users
+  my $data = "cte1=" . ($port == "1" ? $state : "") . "&cte2=" . ($port == "2" ? $state : "") . "&cte3=" . ($port == "3" ? $state : "") . "&cte4=". ($port == "4" ? $state : ""); 
+  Log $logLevel, "EGPM2LAN $data"; 
+  eval {
+    # Parameter:    $url, $timeout, $data, $noshutdown, $loglevel
+    GetFileFromURL("http://".$hash->{IP}."/", 5,$data ,$fritz ,$logLevel);
+  }; 
+  if ($@){ 
+    ### catch block 
+    Log $logLevel, "EGPM2LAN error: $@"; 
+  }; 
+
+  return 1; 
+} 
+
+################################
+sub EGPM2LAN_Login($$) { 
+  my ($hash, $logLevel) = @_; 
+
+  Log $logLevel,"EGPM2LAN try to Login @".$hash->{IP};
+
+  eval{
+      GetFileFromURLQuiet("http://".$hash->{IP}."/login.html", 5,"pw=" . (defined($hash->{PASSWORD}) ? $hash->{PASSWORD} : ""),0 ,$logLevel);
+  }; 
+  if ($@){ 
+      ### catch block 
+      Log 1, "EGPM2LAN Login error: $@";
+      return 0; 
+  }; 
+
+  Log $logLevel,"EGPM2LAN Login successful!";
+    
+return 1; 
+} 
+
+################################
+sub EGPM2LAN_GetDeviceInfo($$) { 
+  my ($hash, $input) = @_;
+  my $logLevel = GetLogLevel($hash->{NAME},4); 
+
+  #try to read Device Name
+  my ($devicename) = $input =~ m/<h2>(.+)<\/h2><\/div>/si;
+  $hash->{DEVICENAME} = trim($devicename);
+
+  #try to read Socket Names
+  my @socketlist; 
+  while ($input =~ m/<h2 class=\"ener\">(.+?)<\/h2>/gi) 
+  { 
+    my $socketname = trim($1);
+    $socketname =~ s/ /_/g;    #remove spaces
+    push(@socketlist, $socketname); 
+  }
+
+  #check 4 dublicate Names
+  my %seen;
+  foreach my $entry (@socketlist)
+  {
+	next unless $seen{$entry}++;
+        Log $logLevel, "EGPM2LAN Sorry! Can't use devicenames. ".trim($entry)." is duplicated.";
+	@socketlist = qw(Socket_1 Socket_2 Socket_3 Socket_4);
+  } 
+  if(int(@socketlist) < 4)
+  {
+	@socketlist = qw(Socket_1 Socket_2 Socket_3 Socket_4);
+  }
+  return @socketlist; 
+}
+
+################################
+sub EGPM2LAN_Statusrequest($$$) { 
+  my ($hash, $logLevel, $autoCr) = @_;
+  my $name = $hash->{NAME}; 
+  
+  my $response = GetFileFromURL("http://".$hash->{IP}."/", 5,"" , 0 ,$logLevel);
+  #Log 1,$response;
+	if(defined($response) && $response =~ /.,.,.,./) 
+        { 
+          my $powerstatestring = $&; 
+          Log $logLevel, "EGPM2LAN Powerstate: " . $powerstatestring; 
+          my @powerstates = split(",", $powerstatestring);
+
+          if(int(@powerstates) == 4) 
+          { 
+            my $index;
+            my $newstatestring;
+            my @socketlist = EGPM2LAN_GetDeviceInfo($hash,$response);
+            readingsBeginUpdate($hash);
+            
+	    foreach my $powerstate (@powerstates)
+            {
+                $index++;
+		if(length(trim($socketlist[$index-1]))==0)
+		{
+		  $socketlist[$index-1]="Socket_".$index;	
+		}
+                if(AttrVal($name, "stateDisplay", "sockNumber") eq "sockName") {
+                  $newstatestring .= $socketlist[$index-1].": ".($powerstates[$index-1] ? "on" : "off")." ";
+		} else {
+            	  $newstatestring .= $index.": ".($powerstates[$index-1] ? "on" : "off")." ";
+		}
+
+                #Create Socket-Object if not available
+                my $defptr = $modules{EGPM}{defptr}{$name.$index};
+
+                if($autoCr && AttrVal($name, "autocreate", "on") eq "on" && not defined($defptr))
+		{
+		   if(Value("autocreate") eq "active")
+		   {
+		  	Log $logLevel, "EGPM2LAN: Autocreate EGPM for Socket $index";
+	                CommandDefine(undef, $name."_".$socketlist[$index-1]." EGPM $name $index");
+		   }
+		   else
+		   {
+			Log 2, "EGPM2LAN: Autocreate disabled in globals section";
+                        $attr{$name}{autocreate} = "off"; 
+		   }
+		}
+
+		#Write state 2 related Socket-Object
+		if (defined($defptr))
+		{
+		   if (ReadingsVal($defptr->{NAME},"state","") ne ($powerstates[$index-1] ? "on" : "off"))
+		   {  #check for chages and update -> trigger event
+		 		  Log $logLevel, "Update State of ".$defptr->{NAME};
+          readingsSingleUpdate($defptr, "state", ($powerstates[$index-1] ? "on" : "off") ,1);
+       }
+		   $defptr->{DEVICENAME} = $hash->{DEVICENAME};
+		   $defptr->{SOCKETNAME} = $socketlist[$index-1];
+   	}
+
+         	readingsBulkUpdate($hash, $index."_".$socketlist[$index-1], ($powerstates[$index-1] ? "on" : "off"));
+        } 
+        readingsBulkUpdate($hash, "state", $newstatestring);
+        readingsEndUpdate($hash, 0);
+
+	    #everything is fine
+	    return $powerstatestring;
+          } 
+          else 
+          { 
+            Log $logLevel, "EGPM2LAN: Failed to parse powerstate";
+          } 
+        }
+	else
+	{
+     $hash->{STATE} = "Login failed";
+	   Log $logLevel, "EGPM2LAN: Login failed";
+	}
+   #something went wrong :-( 
+   return undef; 
+} 
+
+sub EGPM2LAN_Logoff($$) {
+  my ($hash, $logLevel) = @_; 
+
+  GetFileFromURL("http://".$hash->{IP}."/login.html", 5,"" ,0 ,$logLevel);
+  return 1; 
+} 
+
+sub 
+EGPM2LAN_Define($$) 
+{ 
+  my ($hash, $def) = @_; 
+  my @a = split("[ \t][ \t]*", $def); 
+  
+  my $u = "wrong syntax: define <name> EGPM2LAN IP Password"; 
+  return $u if(int(@a) < 2); 
+    
+  $hash->{IP} = $a[2];
+  if(int(@a) == 4) 
+  { 
+    $hash->{PASSWORD} = $a[3];  
+    }
+  else 
+  { 
+    $hash->{PASSWORD} = "";
+  } 
+  my $result = EGPM2LAN_Login($hash, 3);
+  if($result == 1)
+  { 
+    $hash->{STATE} = "initialized";
+    EGPM2LAN_Statusrequest($hash, 4, 0);
+    EGPM2LAN_Logoff($hash, 4); 
+  }
+
+  return undef; 
+} 
+
+1;
+
+=pod
+=item device
+=item summary    controls a LAN-Socket device from Gembird
+=item summary_DE steuert eine LAN-Steckdosenleiste von Gembird
+=begin html
+
+<a name="EGPM2LAN"></a>
+<h3>EGPM2LAN</h3>
+<ul>
+  <br>
+  <a name="EGPM2LANdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; EGPM2LAN &lt;IP-Address&gt; [&lt;Password&gt;]</code><br>
+    <br>
+    Creates a Gembird &reg; <a href="http://energenie.com/item.aspx?id=7557" >Energenie EG-PM2-LAN</a> device to switch up to 4 sockets over the network.
+    If you have more than one device, it is helpful to connect and set names for your sockets over the web-interface first.
+    The name settings will be adopted to FHEM and helps you to identify the sockets. Please make sure that you&acute;re logged off from the Energenie web-interface otherwise you can&acute;t control it with FHEM at the same time.<br>
+    <b>EG-PMS2-LAN with surge protector feature was not tested until now.</b>
+</ul><br>
+  <a name="EGPM2LANset"></a>
+  <b>Set</b>
+  <ul>
+    <code>set &lt;name&gt; &lt;[on|off|toggle]&gt &lt;socketnr.&gt;</code><br>
+    Switch the socket on or off.<br>
+    <br>
+    <code>set &lt;name&gt; &lt;[on|off]&gt &lt;all&gt;</code><br>
+    Switch all available sockets on or off.<br>
+    <br>
+    <code>set &lt;name&gt; &lt;staterequest&gt;</code><br>
+    Update the device information and the state of all sockets.<br>
+    If <a href="#autocreate">autocreate</a> is enabled, an <a href="#EGPM">EGPM</a> device will be created for each socket.<br>
+    <br>
+    <code>set &lt;name&gt; &lt;clearreadings&gt;</code><br>
+    Removes all readings from the list to get rid of old socketnames.
+  </ul>
+  <br>
+  <a name="EGPM2LANget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="EGPM2LANattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li>stateDisplay</li>
+	  Default: <b>socketNumer</b> changes between <b>socketNumer</b> and <b>socketName</b> in front of the current state. Call <b>set statusrequest</b> to update all states.
+    <li>autocreate</li>
+    Default: <b>on</b> <a href="#EGPM">EGPM</a>-devices will be created automatically with a <b>set</b>-command.
+	  Change this attribute to value <b>off</b> to avoid that mechanism.
+    <li><a href="#loglevel">loglevel</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+<br>
+   <br>
+
+    Example:
+    <ul>
+      <code>define mainswitch EGPM2LAN 10.192.192.20 SecretGarden</code><br>
+      <code>set mainswitch on 1</code><br>
+    </ul>
+</ul>
+
+=end html
+=begin html_DE
+
+<a name="EGPM2LAN"></a>
+<h3>EGPM2LAN</h3>
+<ul>
+  <br>
+  <a name="EGPM2LANdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; EGPM2LAN &lt;IP-Address&gt; [&lt;Password&gt;]</code><br>
+    <br>
+    Das Modul erstellt eine Verbindung zu einer Gembird &reg; <a href="http://energenie.com/item.aspx?id=7557" >Energenie EG-PM2-LAN</a> Steckdosenleiste und steuert 4 angeschlossene Ger&auml;te..
+    Falls mehrere Steckdosenleisten &uuml;ber das Netzwerk gesteuert werden, ist es ratsam, diese zuerst &uuml;ber die Web-Oberfl&auml;che zu konfigurieren und die einzelnen Steckdosen zu benennen. Die Namen werden dann automatisch in die
+    Oberfl&auml;che von FHEM &uuml;bernommen. Bitte darauf achten, die Weboberfl&auml;che mit <i>Logoff</i> wieder zu verlassen, da der Zugriff sonst blockiert wird.
+</ul><br>
+  <a name="EGPM2LANset"></a>
+  <b>Set</b>
+  <ul>
+    <code>set &lt;name&gt; &lt;[on|off|toggle]&gt &lt;socketnr.&gt;</code><br>
+    Schaltet die gew&auml;hlte Steckdose ein oder aus.<br>
+    <br>
+    <code>set &lt;name&gt; &lt;[on|off]&gt &lt;all&gt;</code><br>
+    Schaltet alle Steckdosen gleichzeitig ein oder aus.<br>
+    <br>
+    <code>set &lt;name&gt; &lt;staterequest&gt;</code><br>
+    Aktualisiert die Statusinformation der Steckdosenleiste.<br>
+    Wenn das globale Attribut <a href="#autocreate">autocreate</a> aktiviert ist, wird f&uuml;r jede Steckdose ein <a href="#EGPM">EGPM</a>-Eintrag erstellt.<br>
+    <br>
+    <code>set &lt;name&gt; &lt;clearreadings&gt;</code><br>
+    L&ouml;scht alle ung&uuml;ltigen Eintr&auml;ge im Abschnitt &lt;readings&gt;.
+  </ul>
+  <br>
+  <a name="EGPM2LANget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="EGPM2LANattr"></a>
+  <b>Attribute</b>
+  <ul>
+    <li>stateDisplay</li>
+	  Default: <b>socketNumer</b> wechselt zwischen <b>socketNumer</b> and <b>socketName</b> f&uuml;r jeden Statuseintrag. Verwende <b>set statusrequest</b>, um die Anzeige zu aktualisieren.
+    <li>autocreate</li>
+    Default: <b>on</b> <a href="#EGPM">EGPM</a>-Eintr&auml;ge werden automatisch mit dem <b>set</b>-command erstellt.
+    <li><a href="#loglevel">loglevel</a></li>
+    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+  </ul>
+  <br>
+<br>
+   <br>
+
+    Beispiel:
+    <ul>
+      <code>define sleiste EGPM2LAN 10.192.192.20 SecretGarden</code><br>
+      <code>set sleiste on 1</code><br>
+    </ul>
+</ul>
+=end html_DE
+
+=cut
+

+ 362 - 0
fhem/core/FHEM/17_SIS_PMS.pm

@@ -0,0 +1,362 @@
+################################################################
+#
+#  Copyright notice
+#
+#  (c) 2009 Copyright: Kai 'wusel' Siering (wusel+fhem at uu dot org)
+#  All rights reserved
+#
+#  This code 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.
+#
+#  The GNU General Public License can be found at
+#  http://www.gnu.org/copyleft/gpl.html.
+#  A copy is found in the textfile GPL.txt and important notices to the license
+#  from the author is found in LICENSE.txt distributed with these scripts.
+#
+#  This script 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.
+#
+#  This copyright notice MUST APPEAR in all copies of the script!
+###############################################
+
+###########################
+# 17_SIS_PMS.pm
+# Module for FHEM
+#
+# Contributed by Kai 'wusel' Siering <wusel+fhem@uu.org> in 2010
+# Based in part on work for FHEM by other authors ...
+# $Id: 17_SIS_PMS.pm 2076 2012-11-04 13:49:43Z rudolfkoenig $
+###########################
+
+package main;
+
+use strict;
+use warnings;
+
+my $SIS_PMS_cmds ="off on on-till off-till toggle";
+
+sub
+SIS_PMS_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^socket ..:..:..:..:.. .+ state o.*";
+  $hash->{SetFn}     = "SIS_PMS_Set";
+#  $hash->{StateFn}   = "SIS_PMS_SetState";
+  $hash->{DefFn}     = "SIS_PMS_Define";
+  $hash->{UndefFn}   = "SIS_PMS_Undef";
+  $hash->{ParseFn}   = "SIS_PMS_Parse";
+}
+
+
+#############################
+sub
+SIS_PMS_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  my $u = "wrong syntax: define <name> SIS_PMS <serial> <socket>";
+
+  return $u if(int(@a) < 4);
+
+  my $serial = $a[2];
+  my $socketnr = $a[3];
+  my $name = $a[0];
+  my $serialnocolon=$serial;
+  $serialnocolon =~ s/:/_/g;
+
+  $modules{SIS_PMS}{defptr}{$name}  = $hash;
+  $hash->{SERIAL} = $serial;
+  $hash->{SOCKET} = $socketnr;
+  $hash->{NAME} = $name;
+  $modules{SIS_PMS}{defptr}{$serialnocolon . $socketnr} = $hash;
+  $hash->{PREV}{STATE} = "undefined";
+  AssignIoPort($hash);
+}
+
+
+#############################
+sub
+SIS_PMS_Undef($$)
+{
+  my ($hash, $name) = @_;
+#
+#  foreach my $c (keys %{ $hash->{CODE} } ) {
+#    $c = $hash->{CODE}{$c};
+#
+#    # As after a rename the $name my be different from the $defptr{$c}{$n}
+#    # we look for the hash.
+#    foreach my $dname (keys %{ $modules{SIS_PMS}{defptr}{$c} }) {
+#      delete($modules{SIS_PMS}{defptr}{$c}{$dname})
+#        if($modules{SIS_PMS}{defptr}{$c}{$dname} == $hash);
+#    }
+#  }
+  return undef;
+}
+
+
+#############################
+sub
+SIS_PMS_Parse($$)
+{
+    my ($hash, $msg) = @_;
+    my $serial;
+    my $socknr;
+    my $sockst;
+    my $dummy;
+    my $serialnocolon;
+    
+    
+    # Msg format: 
+    # ^socket ..:..:..:..:.. . state o.*";
+
+    ($dummy, $serial, $socknr, $dummy, $sockst) = split(' ', $msg);
+    $serialnocolon=$serial;
+    $serialnocolon =~ s/:/_/g;
+
+    my $def = $modules{SIS_PMS}{defptr}{$serialnocolon . $socknr};
+    if($def) {
+	Log 5, "SIS_PMS: Found device as " . $def->{NAME};
+	if($def->{STATE} ne $sockst) {
+	    $def->{READINGS}{PREVSTATE}{TIME} = TimeNow();
+	    $def->{READINGS}{PREVSTATE}{VAL} = $def->{STATE};
+	    Log 3, "SIS_PMS " . $def->{NAME} ." state changed from " . $def->{STATE} . " to $sockst";
+	    $def->{PREV}{STATE} = $def->{STATE};
+	    $def->{CHANGED}[0] = $sockst;
+	    DoTrigger($def->{NAME}, undef);
+	}
+	$def->{STATE} = $sockst;
+	$def->{READINGS}{state}{TIME} = TimeNow();
+	$def->{READINGS}{state}{VAL} = $sockst;
+	Log 5, "SIS_PMS " . $def->{NAME} ." state $sockst";
+
+	return $def->{NAME};
+    } else {
+	my $devname=$serial;
+	
+	$devname =~ s/:/_/g;
+	Log 3, "SIS_PMS Unknown device $serial $socknr, please define it";
+	if(defined($hash->{TMPLABEL})) {
+	    $devname=$hash->{TMPLABEL};
+	    return "UNDEFINED $devname SIS_PMS $serial $socknr";
+	} else {
+	    return "UNDEFINED SIS_PMS_$devname.$socknr SIS_PMS $serial $socknr";
+	}
+    }
+}
+
+
+#############################
+sub
+SIS_PMS_Do_On_Till($@)
+{
+  my ($hash, @a) = @_;
+  return "Timespec (HH:MM[:SS]) needed for the on-till command" if(@a != 3);
+
+  my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
+  return $err if($err);
+
+  my @lt = localtime;
+  my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
+  my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
+  if($hms_now ge $hms_till) {
+    Log 4, "on-till: won't switch as now ($hms_now) is later than $hms_till";
+    return "";
+  }
+
+  my @b = ($a[0], "on");
+  SIS_PMS_Set($hash, @b);
+  my $tname = $hash->{NAME} . "_till";
+  CommandDelete(undef, $tname) if($defs{$tname});
+  CommandDefine(undef, "$tname at $hms_till set $a[0] off");
+
+}
+
+#############################
+sub
+SIS_PMS_Do_Off_Till($@)
+{
+  my ($hash, @a) = @_;
+  return "Timespec (HH:MM[:SS]) needed for the off-till command" if(@a != 3);
+
+  my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
+  return $err if($err);
+
+  my @lt = localtime;
+  my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
+  my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
+  if($hms_now ge $hms_till) {
+    Log 4, "off-till: won't switch as now ($hms_now) is later than $hms_till";
+    return "";
+  }
+
+  my @b = ($a[0], "off");
+  SIS_PMS_Set($hash, @b);
+  my $tname = $hash->{NAME} . "_till";
+  CommandDelete(undef, $tname) if($defs{$tname});
+  CommandDefine(undef, "$tname at $hms_till set $a[0] on");
+
+}
+
+
+###################################
+sub
+SIS_PMS_Set($@)
+{
+    my ($hash, @a) = @_;
+    my $ret = undef;
+    my $na = int(@a);
+    
+    my $what = lc($a[1]);
+
+    return "no set value specified" if($na < 2 || $na > 3);
+
+    my @cmds=split(" ", $SIS_PMS_cmds);
+    my $ncmds=int(@cmds);
+    my $i;
+    my $known_cmd=0;
+
+    for($i=0; $i<$ncmds; $i++) {
+	if($cmds[$i] eq $what) {
+	    $known_cmd++;
+	}
+    }
+    if($known_cmd==0) {
+	return "Unknown argument $what, choose one of $SIS_PMS_cmds";
+    }
+
+    return SIS_PMS_Do_On_Till($hash, @a) if($a[1] eq "on-till");
+    return SIS_PMS_Do_Off_Till($hash, @a) if($a[1] eq "off-till");
+
+    my $prevstate=$hash->{STATE};
+    my $currstate=$what;
+
+    if($what eq "toggle") {
+	if($prevstate eq "on") {
+	    $currstate="off";
+	} elsif($prevstate eq "off") {
+	    $currstate="on";
+	}
+    }
+    
+    if($prevstate ne $currstate) {
+	$hash->{READINGS}{PREVSTATE}{TIME} = TimeNow();
+	$hash->{READINGS}{PREVSTATE}{VAL} = $prevstate;
+	Log 3, "SIS_PMS " . $hash->{NAME} ." state changed from $prevstate to $currstate";
+	$hash->{PREV}{STATE} = $prevstate;
+	$hash->{CHANGED}[0] = $currstate;
+	$hash->{STATE} = $currstate;
+	$hash->{READINGS}{state}{TIME} = TimeNow();
+	$hash->{READINGS}{state}{VAL} = $currstate;
+#	DoTrigger($hash->{NAME}, undef);
+    }
+
+    my $msg;
+    $msg=sprintf("%s %s %s", $hash->{SERIAL}, $hash->{SOCKET}, $what);
+
+    IOWrite($hash, $what, $msg);
+
+    return $ret;
+}
+
+
+1;
+
+=pod
+=begin html
+
+<a name="SIS_PMS"></a>
+
+<h3>SIS_PMS</h3>
+<ul>
+  This module is responsible for handling the actual sockets (power on,
+  power off, toggle) on a "Silver Shield Power Manager", see <a href="#SISPM">SISPM</a>
+  for how to define access to one (SIS_PMS stands for "Silver Shield Power Manager Socket").
+  <br><br>
+
+  <a name="SIS_PMSdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; SIS_PMS &lt;serial&gt; &lt;socket&gt;</code>
+    <br><br>
+
+   To securely distinguish multiple attached Power Manager devices, the
+   serial number of those is used. You get these with "sispmctl -s"&nbsp;- or
+   just let autocreate define the sockets attached for you.<br>
+
+   <ul>
+   <li><code>&lt;serial&gt;</code> is the serial number of the Power Manager device, see above.</li>
+   <li><code>&lt;socket&gt;</code> is a number between 1 and 4 (for a 4 socket model)</li>
+   </ul>
+   <br>
+
+    Examples:
+    <ul>
+      <code>define lamp SIS_PMS 01:02:03:04:05 1</code><br>
+      <code>define otherlamp SIS_PMS 01:02:03:04:05 3</code><br>
+      <code>define tv SIS_PMS 01:01:38:44:55 1</code>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="SIS_PMSset"></a>
+  <b>Set </b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt; [&lt;time&gt;]</code>
+    <br><br>
+    where <code>value</code> is one of:<br>
+    <pre>
+    off
+    on
+    toggle
+    on-till           # Special, see the note
+    off-till          # Special, see the note
+    </pre>
+    Examples:
+    <ul>
+      <code>set lamp on</code><br>
+      <code>set lamp1,lamp2,lamp3 on</code><br>
+      <code>set lamp1-lamp3 on</code><br>
+      <code>set hql_lamp on-till 18:45</code><br>
+    </ul>
+    <br>
+    Notes:
+    <ul>
+      <li>As an external program is used, a noticeable delay may occur.</li>
+      <li>*-till requires an absolute time in the "at" format (HH:MM:SS, HH:MM
+      or { &lt;perl code&gt; }, where the perl-code returns a time
+          specification).
+      If the current time is greater than the specified time, then the
+      command is ignored, else an "on" or "off" command, respectively, is
+          generated, and for the given time an "off"/"on" command is
+      scheduleld via the at command.</li>
+    </ul>
+  </ul>
+  <br>
+
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="SIS_PMSattributes"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li><br>
+    <a name="attrdummy"></a>
+    <li>dummy<br>
+    Set the device attribute dummy to define devices which should not
+    output any signals. Associated notifys will be executed if the signal
+    is received. Used e.g. to react to a code from a sender, but it will
+    not actually switch if triggered in the web frontend.
+    </li><br>
+
+    <li><a href="#loglevel">loglevel</a></li><br>
+  </ul>
+</ul>
+
+
+=end html
+=cut

+ 127 - 0
fhem/core/FHEM/18_CUL_HOERMANN.pm

@@ -0,0 +1,127 @@
+##############################################
+# $Id: 18_CUL_HOERMANN.pm 12655 2016-11-25 19:20:44Z rudolfkoenig $
+package main;
+
+use strict;
+use warnings;
+
+sub
+CUL_HOERMANN_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{Match}     = "^R..........";
+  $hash->{DefFn}     = "CUL_HOERMANN_Define";
+  $hash->{ParseFn}   = "CUL_HOERMANN_Parse";
+  $hash->{SetFn}     = "CUL_HOERMANN_Set";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 ".
+                       "disable:0,1 disabledForIntervals ";
+}
+
+#############################
+sub
+CUL_HOERMANN_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  my $u = "wrong syntax: define <name> CUL_HOERMANN housecode " .
+                        "addr [fg addr] [lm addr] [gm FF]";
+
+  return "wrong syntax: define <name> CUL_HOERMANN 10-digit-hex-code"
+        if(int(@a) != 3 || $a[2] !~ m/^[a-f0-9]{10}$/i);
+
+  $modules{CUL_HOERMANN}{defptr}{$a[2]} = $hash;
+  $hash->{STATE} = "Defined";
+  AssignIoPort($hash);
+  return undef;
+}
+
+sub
+CUL_HOERMANN_Parse($$)
+{
+  my ($hash, $msg) = @_;
+
+  # Msg format: R0123456789
+  my $cde = substr($msg, 1, 10);
+  my $def = $modules{CUL_HOERMANN}{defptr}{$cde};
+
+  if($def) {
+    my $name = $def->{NAME};
+    $def->{CHANGED}[0] = "toggle";
+    $def->{READINGS}{state}{TIME} = TimeNow();
+    $def->{READINGS}{state}{VAL} = "toggle";
+    Log3 $name, 4, "CUL_HOERMANN $name toggle";
+    return $name;
+
+  } else {
+    Log3 $hash, 3, "CUL_HOERMANN Unknown device $cde, please define it";
+    return "UNDEFINED CUL_HOERMANN_$cde CUL_HOERMANN $cde";
+  }
+}
+
+sub
+CUL_HOERMANN_Set($@)
+{
+  my ($hash, @a) = @_;
+
+  return "no set argument specified" if(int(@a) < 2);
+  return "Unknown argument $a[1], choose one of toggle:noArg"
+    if($a[1] ne "toggle");
+
+  return if(IsDisabled($hash->{NAME}));
+
+  IOWrite($hash, "", "hn".$hash->{DEF})
+}
+
+1;
+
+=pod
+=item summary    Hoermann Garage door opener
+=item summary_DE Hoermann Garagenfernbedienung
+=begin html
+
+<a name="CUL_HOERMANN"></a>
+<h3>CUL_HOERMANN</h3>
+<ul>
+  The CUL_HOERMANN module registers the 868MHz Hoermann Garage-Door-Opener
+  signals received by the CUL. <b>Note</b>: As the structure of this signal is
+  not understood, no checksum is verified, so it is likely to receive bogus
+  messages.
+  <br><br>
+
+  <a name="CUL_HOERMANNdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; CUL_HOERMANN &lt;10-digit-hex-code&gt;</code>
+    <br>
+  </ul>
+  <br>
+
+  <a name="CUL_HOERMANNset"></a>
+  <b>Set</b>
+  <ul>
+    <li>toggle<br>
+        Send a signal, which, depending on the status of the door, opens it,
+        closes it or stops the current movement. NOTE: needs culfw 1.67+
+        </li>
+  </ul><br>
+
+  <a name="CUL_HOERMANNget"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="CUL_HOERMANNattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#IODev">IODev</a></li>
+    <li><a href="#disable">disable</a></li>
+    <li><a href="#disabledForIntervals">disabledForIntervals</a></li>
+  </ul>
+  <br>
+</ul>
+
+
+=end html
+=cut

+ 162 - 0
fhem/core/FHEM/19_Revolt.pm

@@ -0,0 +1,162 @@
+##############################################
+#                                            #
+# Written by Martin Paulat, 2013             #
+#                                            #
+##############################################
+
+package main;
+
+use strict;
+use warnings;
+use Date::Parse;
+
+
+
+#####################################
+sub
+Revolt_Initialize($)
+{
+  my ($hash) = @_;
+
+#                        r00C5E100303203C85921FF
+  $hash->{Match}     = "^r......................\$";
+  $hash->{DefFn}     = "Revolt_Define";
+  $hash->{UndefFn}   = "Revolt_Undef";
+  $hash->{ParseFn}   = "Revolt_Parse";
+  $hash->{AttrList}  = "IODev ".
+                       $readingFnAttributes;
+}
+
+#####################################
+sub
+Revolt_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> Revolt <id>" if(int(@a) != 3);
+  $a[2] = lc($a[2]);
+  return "Define $a[0]: wrong <id> format: specify a 4 digit hex value"
+  		if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
+
+  $hash->{ID} = $a[2];
+  #$hash->{STATE} = "Initialized";
+  $modules{REVOLT}{defptr}{$a[2]} = $hash;
+  AssignIoPort($hash);
+  return undef;
+}
+
+#####################################
+sub
+Revolt_Undef($$)
+{
+  my ($hash, $name) = @_;
+  delete($modules{REVOLT}{defptr}{$hash->{ID}})
+        if(defined($hash->{ID}) &&
+           defined($modules{REVOLT}{defptr}{$hash->{ID}}));
+  return undef;
+}
+
+#####################################
+sub
+Revolt_Parse($$)
+{
+  my ($hash, $msg) = @_;
+
+  $msg = lc($msg);
+  my $seq = substr($msg, 1, 2);
+  my $dev = substr($msg, 3, 4);
+  my $cde = substr($msg, 7, 4);
+  my $val = substr($msg, 11, 22);
+  my $id       = substr($msg, 1, 4);
+  my $voltage  = hex(substr($msg, 5, 2));
+  my $current  = hex(substr($msg, 7, 4))*0.01;
+  my $freq     = hex(substr($msg, 11, 2));
+  my $power    = hex(substr($msg, 13, 4))*0.1;
+  my $pf       = hex(substr($msg, 17, 2))*0.01;
+  my $energy   = hex(substr($msg, 19, 4))*0.01;
+  my $lastval;
+  my $avg;
+  
+  my $type = "";
+  
+  if(!defined($modules{REVOLT}{defptr}{$id})) {
+    Log3 undef,3, "Unknown Revolt device $id, please define it";
+    $type = "Revolt" if(!$type);
+    return "UNDEFINED ${type}_$id Revolt $id";
+  }
+
+  my $def = $modules{REVOLT}{defptr}{$id};
+  my $name = $def->{NAME};
+  return "" if(IsIgnored($name));
+  
+  my $state;
+  $state="P: ".sprintf("%5.1f",$power)." E: ".sprintf("%6.2f",$energy)." V: ".sprintf("%3d",$voltage)." C: ".sprintf("%6.2f",$current)." F: $freq Pf: ".sprintf("%4.2f",$pf);
+  
+  readingsBeginUpdate($def);
+  
+  if (defined($def->{READINGS}{".lastenergy"})) {
+    $lastval=$def->{READINGS}{".lastenergy"}{VAL};
+    if ($lastval != $energy) {
+      $avg=(($lastval-$energy)*1000.0*3600.0)/(str2time($def->{READINGS}{".lastenergy"}{TIME})-gettimeofday());
+      readingsBulkUpdate($def,".lastenergy", $energy,1);
+      readingsBulkUpdate($def,"avgpower", sprintf("%.2f",$avg),1);
+    }
+  } else {
+    readingsBulkUpdate($def,".lastenergy", $energy,1);
+  }
+
+  readingsBulkUpdate($def,"state", $state,1);
+  Log3  $name,4, "$name: $state";
+  readingsBulkUpdate($def,"voltage", $voltage,1);
+  #Log3  $def,3, "$name:voltage $voltage";
+  readingsBulkUpdate($def,"current", $current,1);
+  #Log3  $def,3, "$name:current $current";
+  readingsBulkUpdate($def,"frequency", $freq,1);
+  #Log3  $def,3, "$name:frequency $freq";
+  readingsBulkUpdate($def,"power", $power,1);
+  #Log3  $def,3, "$name:power $power";
+  readingsBulkUpdate($def,"pf", $pf,1);
+  #Log3  $def,3, "$name:Pf $pf";
+  readingsBulkUpdate($def,"energy", $energy,1);
+  #Log3  $def,3, "$name:energy $energy";
+  
+  readingsEndUpdate($def, 1);
+
+  return $name;
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="Revolt"></a>
+<h3>Revolt NC-5462</h3>
+<ul>
+  Provides voltage, current, frequency, power, pf, energy readings for Revolt NC-5462 devices via CUL.
+  <br><br>
+
+  <a name="RevoltDefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; Revolt &lt;id&gt;</code>
+    <br><br>
+    &lt;id&gt; is a 4 digit hex number to identify the NC-5462 device.<br>
+    Note: devices are autocreated on reception of the first message.<br>
+  </ul>
+  <br>
+  <a name="RevoltReadings"></a>
+  <b>Readings</b>
+  <ul>
+    <li>energy    [kWh]</li>
+    <li>power     [W]</li>
+    <li>voltage   [V]</li>
+    <li>current   [A]</li>
+    <li>frequency [Hz]</li>
+    <li>Pf</li>
+  </ul>
+
+</ul>
+=end html
+=cut

+ 297 - 0
fhem/core/FHEM/19_VBUSIF.pm

@@ -0,0 +1,297 @@
+##############################################
+# $Id: 19_VBUSIF.pm 12980 2017-01-06 12:36:39Z Tobias.Faust $
+#
+# VBUS LAN Adapter Device
+# 19_VBUSIF.pm
+#
+# (c) 2014 Arno Willig <akw@bytefeed.de>
+# (c) 2015 Frank Wurdinger <frank@wurdinger.de>
+# (c) 2015 Adrian Freihofer <adrian.freihofer gmail com>
+# (c) 2016 Tobias Faust <tobias.faust gmx net>
+# (c) 2016 Jörg (pejonp)
+##############################################  
+
+
+package main;
+
+use strict;
+use warnings;
+use POSIX;
+use Data::Dumper;
+use Device::SerialPort;
+
+sub VBUSIF_Read($@);
+sub VBUSIF_Write($$$);
+sub VBUSIF_Ready($);
+sub VBUSIF_getDevList($$);
+
+
+sub VBUSIF_Initialize($)
+{
+	my ($hash) = @_;
+	require "$attr{global}{modpath}/FHEM/DevIo.pm";
+
+# Provider
+	$hash->{ReadFn}     = "VBUSIF_Read";
+	$hash->{WriteFn}    = "VBUSIF_Write";
+	$hash->{ReadyFn}    = "VBUSIF_Ready";
+	$hash->{UndefFn}    = "VBUSIF_Undef";
+	$hash->{ShutdownFn} = "VBUSIF_Undef";
+
+# Normal devices
+	$hash->{DefFn}      = "VBUSIF_Define";
+	$hash->{AttrList}   = "dummy:1,0"
+                        ."$readingFnAttributes ";
+  $hash->{AutoCreate}	= { "VBUSDEF.*" => { ATTR => "event-min-interval:.*:120 event-on-change-reading:.* ",FILTER => "%NAME"} };
+  
+}
+
+######################################
+sub VBUSIF_Define($$)
+{
+	my ($hash, $def) = @_;
+	my @a = split("[ \t]+", $def);
+
+	if(@a != 3) {
+		my $msg = "wrong syntax: define <name> VBUSIF [<hostname:7053> or <dev>]";
+    Log3 $hash, 2, $msg;
+    return $msg;
+	}
+
+#  if(@a != 3) {
+#		return "wrong syntax: define <name> VBUSIF [<hostname:7053> or <dev>]";
+#	}
+
+
+	my $name = $a[0];
+	my $dev = $a[2];
+	$hash->{Clients} = ":VBUSDEV:";
+	my %matchList = ( "1:VBUSDEV" => ".*" );
+	$hash->{MatchList} = \%matchList;
+
+ 	Log3 $hash, 4,"$name: VBUSIF_Define: $hash->{MatchList} ";
+
+	DevIo_CloseDev($hash);
+	$hash->{DeviceName} = $dev;
+	my @dev_name = split('@', $dev);
+	if ( -c ${dev_name}[0]) {
+		$hash->{DeviceType} = "Serial";
+	} else {
+		$hash->{DeviceType} = "Net";
+	}
+
+	my $ret = DevIo_OpenDev($hash, 0, "VBUSIF_DoInit");
+	return $ret;
+}
+
+###############################
+sub VBUSIF_DoInit($)
+{
+	my $hash = shift;
+	if ($hash->{DeviceType} eq "Net" ) {
+		my $name = $hash->{NAME};
+		delete $hash->{HANDLE}; # else reregister fails / RELEASE is deadly
+
+		my $conn = $hash->{TCPDev};
+		$conn->autoflush(1);
+		$conn->getline();
+		$conn->write("PASS vbus\n");
+		$conn->getline();
+		$conn->write("DATA\n");
+		$conn->getline();
+	}
+    Log3 $hash, 4,"VBUSIF_DoInit ";
+	return undef;
+}
+
+sub VBUSIF_Undef($@)
+{
+	my ($hash, $arg) = @_;
+	if ($hash->{DeviceType} eq "Net" ) {
+		VBUSIF_Write($hash, "QUIT\n", "");  # RELEASE
+	}
+	DevIo_CloseDev($hash);
+	return undef;
+}
+
+sub VBUSIF_Write($$$)
+{
+	my ($hash,$fn,$msg) = @_;
+	DevIo_SimpleWrite($hash, $msg, 1);
+}
+
+
+sub VBUSIF_Read($@)
+{
+	my ($hash, $local, $regexp) = @_;
+	my $buf = ($local ? $local : DevIo_SimpleRead($hash));
+	
+	return "" if(!defined($buf));
+	
+	my $name = $hash->{NAME};
+	$buf = unpack('H*', $buf);
+	my $data = ($hash->{PARTIAL} ? $hash->{PARTIAL} : "");
+	
+	Log3 $hash->{NAME}, 5, ,"received buffer: $buf";
+	$data .= $buf;
+  
+	my $msg;
+  	my $msg2;
+	my $idx;
+  	my $muster = "aa";
+	$idx = index($data, $muster);
+  	Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read0: index = $data";
+	
+	if ($idx>=0) {
+    	$msg2 = $data;
+		$data = substr($data,$idx); # Cut off beginning
+	  	$idx = index($data,$muster,2); # Find next message
+
+		if ($idx>0) {
+
+			$idx +=1 if (substr($data,$idx,3) eq "aaa"); # Message endet mit a
+			
+			$msg = substr($data,0,$idx);
+			$data = substr($data,$idx);
+			my $protoVersion = substr($msg,10,2);
+    		Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read1: protoVersion : $protoVersion";
+      
+  	  		if ($protoVersion == "10" && length($msg)>=20) {
+			  	my $frameCount = hex(substr($msg,16,2));
+				my $headerCRC  = hex(substr($msg,18,2));
+  				my $crc = 0;
+	  			for (my $j = 1; $j<=8;$j++) {
+		  			$crc += hex(substr($msg,$j*2,2));
+			  	}
+				$crc = ($crc ^ 0xff) & 0x7f;
+				if ($headerCRC != $crc) {
+	            	Log3 $hash, 3, "$name: VBUSIF_Read2: Wrong checksum: $crc != $headerCRC";
+				} else {
+					my $len = 20+12*$frameCount;
+               		Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read2a Len: ".$len." Counter: ".$frameCount;
+
+      				if ($len != length($msg)) {
+               			# Fehler bei aa1000277310000103414a7f1300071c00001401006a62023000016aaa000021732000050000000000000046a
+               			#                                                                   ^ hier wird falsch getrennt
+                  		$msg = substr($msg2,0,$len);
+                   		Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read2b MSG: ".$msg;
+               		}
+               
+               		if ($len != length($msg)) {
+			     		#if ($len != length($msg) && length($msg) != 247) {
+                		Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read3: Wrong message length: $len != ".length($msg);
+					} else {
+                   		Log3 $hash->{NAME}, 5,"$name:  VBUSIF_Read4: OK message length: $len : ".length($msg);
+					    if(length($msg) == 247) {
+						    $msg = $msg."a";
+                       		Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read5: message + a : ".$msg;
+						}
+						my $payload = VBUSIF_DecodePayload($hash,$msg);
+						if (defined $payload) {
+						    $msg = substr($msg,0,20).$payload;
+                     		Log3 $hash, 4,"$name: VBUSIF_Read6 MSG: ".$msg." Payload: ".$payload;
+					        $hash->{"${name}_MSGCNT"}++;
+					        $hash->{"${name}_TIME"} = TimeNow();
+				        	$hash->{RAWMSG} = $msg;
+					        my %addvals = (RAWMSG => $msg);
+					        Dispatch($hash, $msg, \%addvals) if($init_done);
+	  					}
+					}
+        		}
+   			}
+      
+			if ($protoVersion == "20") {
+				my $command    = substr($msg,14,2).substr($msg,12,2);
+				my $dataPntId  = substr($msg,16,4);
+				my $dataPntVal = substr($msg,20,8);
+				my $septet     = substr($msg,28,2);
+				my $checksum   = substr($msg,30,2);
+        		
+        		Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read7: protoVersion : $protoVersion";
+#				TODO use septet
+#				TODO validate checksum
+#				TODO Understand protocol
+			}
+	      	Log3 $hash->{NAME}, 5,"$name: VBUSIF_Read8: raus ";
+		}
+	}
+
+	$hash->{PARTIAL} = $data;
+#	return $msg if(defined($local));
+	return undef;
+}
+
+sub VBUSIF_Ready($)
+{
+	my ($hash) = @_;
+	return DevIo_OpenDev($hash, 1, "VBUSIF_DoInit") if($hash->{STATE} eq "disconnected");
+	return 0;
+}
+
+
+sub VBUSIF_DecodePayload($@)
+{
+	my ($hash, $msg) = @_;
+	my $name = $hash->{NAME};
+
+	my $frameCount = hex(substr($msg,16,2));
+	my $payload = "";
+	for (my $i = 0; $i < $frameCount; $i++) {
+		my $septet   = hex(substr($msg,28+$i*12,2));
+		my $frameCRC = hex(substr($msg,30+$i*12,2));
+
+		my $crc = (0x7f - $septet) & 0x7f;
+		for (my $j = 0; $j<4;$j++) {
+			my $ch = hex(substr($msg,20+$i*12+$j*2,2));
+			$ch |= 0x80 if ($septet & (1 << $j));
+			$crc = ($crc - $ch) & 0x7f;
+			$payload .= chr($ch);
+		}
+
+		if ($crc != $frameCRC) {
+		   Log3 $hash, 4,"$name: VBUSIF_DecodePayload0: Wrong checksum: $crc != $frameCRC";
+			return undef;
+		}
+	}
+	return unpack('H*', $payload);
+}
+
+1;
+
+=pod
+=item device
+=item summary    connects to the RESOL VBUS LAN or Serial Port adapter 
+=item summary_DE verbindet sich mit einem RESOL VBUS LAN oder Seriell Adapter
+=begin html
+
+<a name="VBUSIF"></a>
+<h3>VBUSIF</h3>
+<ul>
+  This module connects to the RESOL VBUS LAN or Serial Port adapter.
+  It serves as the "physical" counterpart to the <a href="#VBUSDevice">VBUSDevice</a>
+  devices.
+  <br /><br />
+  <a name="VBUSIF_Define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; VBUS &lt;device&gt;</code>
+  <br />
+  <br />
+  &lt;device&gt; is a &lt;host&gt;:&lt;port&gt; combination, where
+  &lt;host&gt; is the address of the RESOL LAN Adapter and &lt;port&gt; 7053.
+  <br />
+  Please note: the password of RESOL Device must be unchanged at &lt;host&gt;
+  <br />
+  Examples:
+  <ul>
+    <code>define vbus VBUSIF 192.168.1.69:7053</code>
+    </ul>
+    <ul>
+    <code>define vbus VBUSIF  /dev/ttyS0</code>
+  </ul>
+  </ul>
+  <br />
+</ul>
+
+=end html
+=cut

+ 203 - 0
fhem/core/FHEM/20_FRM_AD.pm

@@ -0,0 +1,203 @@
+##############################################
+# $Id: 20_FRM_AD.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+
+#####################################
+
+my %gets = (
+  "reading" => "",
+  "state"   => "",
+  "alarm-upper-threshold"   => "off",
+  "alarm-lower-threshold"   => "off",
+);
+
+sub
+FRM_AD_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{AttrFn}    = "FRM_AD_Attr";
+  $hash->{GetFn}     = "FRM_AD_Get";
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_AD_Init";
+  
+  $hash->{AttrList}  = "IODev upper-threshold lower-threshold $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_AD_Init($$)
+{
+	my ($hash,$args) = @_;
+	my $ret = FRM_Init_Pin_Client($hash,$args,PIN_ANALOG);
+	return $ret if (defined $ret);
+	my $firmata = $hash->{IODev}->{FirmataDevice};
+	my $name = $hash->{NAME};
+	$firmata->observe_analog($hash->{PIN},\&FRM_AD_observer,$hash);
+	$main::defs{$name}{resolution}=$firmata->{metadata}{analog_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{analog_resolutions});
+	if (! (defined AttrVal($name,"stateFormat",undef))) {
+		$main::attr{$name}{"stateFormat"} = "reading";
+	}
+	if (! (defined AttrVal($name,"event-min-interval",undef))) {
+		$main::attr{$name}{"event-min-interval"} = 5;
+	}
+	main::readingsSingleUpdate($hash,"state","Initialized",1);
+	return undef;
+}
+
+sub
+FRM_AD_observer
+{
+	my ($pin,$old,$new,$hash) = @_;
+	my $name = $hash->{NAME};
+	Log3 $name,6,"onAnalogMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
+	main::readingsBeginUpdate($hash);
+	main::readingsBulkUpdate($hash,"reading",$new,1);
+	my $upperthresholdalarm = ReadingsVal($name,"alarm-upper-threshold","off");
+    if ( $new < AttrVal($name,"upper-threshold",1024) ) {
+      if ( $upperthresholdalarm eq "on" ) {
+    	main::readingsBulkUpdate($hash,"alarm-upper-threshold","off",1);
+      }
+      my $lowerthresholdalarm = ReadingsVal($name,"alarm-lower-threshold","off"); 
+      if ( $new > AttrVal($name,"lower-threshold",-1) ) {
+        if ( $lowerthresholdalarm eq "on" ) {
+          main::readingsBulkUpdate($hash,"alarm-lower-threshold","off",1);
+        }
+      } else {
+      	if ( $lowerthresholdalarm eq "off" ) {
+          main::readingsBulkUpdate($hash,"alarm-lower-threshold","on",1);
+      	}
+      }
+    } else {
+      if ( $upperthresholdalarm eq "off" ) {
+    	main::readingsBulkUpdate($hash,"alarm-upper-threshold","on",1);
+      }
+	};
+	main::readingsEndUpdate($hash,1);
+}
+
+sub
+FRM_AD_Get($)
+{
+  my ($hash,@a) = @_;
+  my $name = shift @a;
+  my $cmd = shift @a;
+  my $ret;
+  ARGUMENT_HANDLER: {
+    $cmd eq "reading" and do {
+      eval {
+        return FRM_Client_FirmataDevice($hash)->analog_read($hash->{PIN});
+      };
+      return $@;
+    };
+    ( $cmd eq "alarm-upper-threshold" or $cmd eq "alarm-lower-threshold" or $cmd eq "state" ) and do {
+      return main::ReadingsVal($name,"count",$gets{$cmd});
+    };
+  }
+  return undef;
+}
+
+sub
+FRM_AD_Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+      }
+    }
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error setting $attribute to $value: ".$1;
+    return "cannot $command attribute $attribute to $value for $name: ".$1;
+  }
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_AD"></a>
+<h3>FRM_AD</h3>
+<ul>
+  represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
+  configured for analog input.<br>
+  The value read is stored in reading 'state'. Range is from 0 to 1023 (10 Bit)<br>
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_ADdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_AD &lt;pin&gt;</code> <br>
+  Defines the FRM_AD device. &lt;pin&gt; is the arduino-pin to use.
+  </ul>
+  
+  <br>
+  <a name="FRM_ADset"></a>
+  <b>Set</b><br>
+  <ul>
+    N/A<br>
+  </ul><br>
+  <a name="FRM_ADget"></a>
+  <b>Get</b><br>
+  <ul>
+    <li>reading<br>
+    returns the voltage-level read on the arduino-pin. Values range from 0 to 1023.</li>
+    <li>alarm-upper-threshold<br>
+    returns the current state of 'alarm-upper-threshold'. Values are 'on' and 'off' (Defaults to 'off')<br>
+    'alarm-upper-threshold' turns 'on' whenever the 'reading' is higher than the attribute 'upper-threshold'<br>
+    it turns 'off' again as soon 'reading' falls below 'alarm-upper-threshold'</li>
+    <li>alarm-lower-threshold<br>
+    returns the current state of 'alarm-lower-threshold'. Values are 'on' and 'off' (Defaults to 'off')<br>
+    'alarm-lower-threshold' turns 'on' whenever the 'reading' is lower than the attribute 'lower-threshold'<br>
+    it turns 'off' again as soon 'reading rises above 'alarm-lower-threshold'</li>
+    <li>state<br>
+    returns the 'state' reading</li>
+  </ul><br>
+  <a name="FRM_ADattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li>upper-threshold<br>
+      sets the 'upper-threshold'. Whenever the 'reading' exceeds this value 'alarm-upper-threshold' is set to 'on'<br>
+      As soon 'reading' falls below the 'upper-threshold' 'alarm-upper-threshold' turns 'off' again<br>
+      Defaults to 1024.</li>
+      <li>lower-threshold<br>
+      sets the 'lower-threshold'. Whenever the 'reading' falls below this value 'alarm-lower-threshold' is set to 'on'<br>
+      As soon 'reading' rises above the 'lower-threshold' 'alarm-lower-threshold' turns 'off' again<br>
+      Defaults to -1.</li>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 137 - 0
fhem/core/FHEM/20_FRM_I2C.pm

@@ -0,0 +1,137 @@
+##############################################
+# $Id: 20_FRM_I2C.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+
+#####################################
+sub
+FRM_I2C_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_I2C_Init";
+  $hash->{UndefFn}   = "FRM_Client_Undef";
+  $hash->{AttrFn}    = "FRM_I2C_Attr";
+  
+  $hash->{AttrList}  = "IODev $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_I2C_Init($)
+{
+	my ($hash,$args) = @_;
+ 	my $u = "wrong syntax: define <name> FRM_I2C address register numbytes";
+
+	return $u if(int(@$args) < 3);
+  
+	$hash->{"i2c-address"} = @$args[0];
+	$hash->{"i2c-register"} = @$args[1];
+	$hash->{"i2c-bytestoread"} = @$args[2];
+
+	eval {
+		FRM_Client_AssignIOPort($hash);
+		FRM_Client_FirmataDevice($hash)->i2c_read(@$args[0],@$args[1],@$args[2]);
+	};
+	if ($@) {
+		$@ =~ /^(.*)( at.*FHEM.*)$/;
+		$hash->{STATE} = "error initializing: ".$1;
+		return "error initializing '".$hash->{NAME}."': ".$1;
+	}
+
+	return "error calling i2c_read: ".$@ if ($@);
+	if (! (defined AttrVal($hash->{NAME},"event-min-interval",undef))) {
+		$main::attr{$hash->{NAME}}{"event-min-interval"} = 5;
+	}
+	return undef;
+}
+
+sub
+FRM_I2C_Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+      }
+    }
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error setting $attribute to $value: ".$1;
+    return "cannot $command attribute $attribute to $value for $name: ".$1;
+  }
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_I2C"></a>
+<h3>FRM_I2C</h3>
+<ul>
+  represents an integrated curcuit connected to the i2c-pins of an <a href="http://www.arduino.cc">Arduino</a>
+  running <a href="http://www.firmata.org">Firmata</a><br>
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br>
+  this FRM-device has to be configures for i2c by setting attr 'i2c-config' on the FRM-device<br>
+  it reads out the ic-internal storage in intervals of 'sampling-interval' as set on the FRM-device<br><br> 
+  
+  <a name="FRM_I2Cdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_I2C &lt;i2c-address&gt; &lt;register&gt; &lt;bytes-to-read&gt;</code> <br>
+  Specifies the FRM_I2C device.<br>
+  <li>i2c-address is the (device-specific) address of the ic on the i2c-bus</li>
+  <li>register is the (device-internal) address to start reading bytes from.</li>
+  <li>bytes-to-read is the number of bytes read from the ic</li>
+  </ul>
+  
+  <br>
+  <a name="FRM_I2Cset"></a>
+  <b>Set</b><br>
+  <ul>
+  N/A<br>
+  </ul>
+  <a name="FRM_I2Cget"></a>
+  <b>Get</b><br>
+  <ul>
+  N/A<br>
+  </ul><br>
+  <a name="FRM_I2Cattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 310 - 0
fhem/core/FHEM/20_FRM_IN.pm

@@ -0,0 +1,310 @@
+##############################################
+# $Id: 20_FRM_IN.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+
+#####################################
+
+my %sets = (
+  "alarm" => "",
+  "count" => 0,
+);
+
+my %gets = (
+  "reading" => "",
+  "state"   => "",
+  "count"   => 0,
+  "alarm"   => "off"
+);
+
+sub
+FRM_IN_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{SetFn}     = "FRM_IN_Set";
+  $hash->{GetFn}     = "FRM_IN_Get";
+  $hash->{AttrFn}    = "FRM_IN_Attr";
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_IN_Init";
+  $hash->{UndefFn}   = "FRM_Client_Undef";
+  
+  $hash->{AttrList}  = "IODev count-mode:none,rising,falling,both count-threshold reset-on-threshold-reached:yes,no internal-pullup:on,off activeLow:yes,no $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_IN_Init($$)
+{
+	my ($hash,$args) = @_;
+	my $ret = FRM_Init_Pin_Client($hash,$args,PIN_INPUT);
+	return $ret if (defined $ret);
+	eval {
+      my $firmata = FRM_Client_FirmataDevice($hash);
+      my $pin = $hash->{PIN};
+      if (defined (my $pullup = AttrVal($hash->{NAME},"internal-pullup",undef))) {
+        $firmata->digital_write($pin,$pullup eq "on" ? 1 : 0);
+      }
+      $firmata->observe_digital($pin,\&FRM_IN_observer,$hash);
+	};
+	return FRM_Catch($@) if $@;
+	if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
+		$main::attr{$hash->{NAME}}{"stateFormat"} = "reading";
+	}
+	main::readingsSingleUpdate($hash,"state","Initialized",1);
+	return undef;
+}
+
+sub
+FRM_IN_observer
+{
+	my ($pin,$old,$new,$hash) = @_;
+	my $name = $hash->{NAME};
+	Log3 $name,5,"onDigitalMessage for pin ".$pin.", old: ".(defined $old ? $old : "--").", new: ".(defined $new ? $new : "--");
+	if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
+		$old = $old == PIN_LOW ? PIN_HIGH : PIN_LOW if (defined $old);
+		$new = $new == PIN_LOW ? PIN_HIGH : PIN_LOW;
+	}
+	my $changed = ((!(defined $old)) or ($old != $new));
+	main::readingsBeginUpdate($hash);
+	if ($changed) {
+  	if (defined (my $mode = main::AttrVal($name,"count-mode",undef))) {
+  		if (($mode eq "both")
+  		or (($mode eq "rising") and ($new == PIN_HIGH))
+  		or (($mode eq "falling") and ($new == PIN_LOW))) {
+  	    	my $count = main::ReadingsVal($name,"count",0);
+  	    	$count++;
+  	    	if (defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
+  	    		if ( $count > $threshold ) {
+  	    			if (AttrVal($name,"reset-on-threshold-reached","no") eq "yes") {
+  	    			  $count=0;
+  	    			  main::readingsBulkUpdate($hash,"alarm","on",1);
+  	    			} elsif ( main::ReadingsVal($name,"alarm","off") ne "on" ) {
+  	    			  main::readingsBulkUpdate($hash,"alarm","on",1);
+  	    			}
+  	    		}
+  	    	}
+  	    	main::readingsBulkUpdate($hash,"count",$count,1);
+  	    } 
+  	};
+	}
+	main::readingsBulkUpdate($hash,"reading",$new == PIN_HIGH ? "on" : "off", $changed);
+	main::readingsEndUpdate($hash,1);
+}
+
+sub
+FRM_IN_Set
+{
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
+  	if(!defined($sets{$a[1]}));
+  my $command = $a[1];
+  my $value = $a[2];
+  COMMAND_HANDLER: {
+    $command eq "alarm" and do {
+      return undef if (!($value eq "off" or $value eq "on"));
+      main::readingsSingleUpdate($hash,"alarm",$value,1);
+      last;
+    };
+    $command eq "count" and do {
+      main::readingsSingleUpdate($hash,"count",$value,1);
+      last;
+    };
+  }
+}
+
+sub
+FRM_IN_Get($)
+{
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", sort keys %gets)
+  	if(!defined($gets{$a[1]}));
+  my $name = shift @a;
+  my $cmd = shift @a;
+  ARGUMENT_HANDLER: {
+    $cmd eq "reading" and do {
+      eval {
+        return FRM_Client_FirmataDevice($hash)->digital_read($hash->{PIN}) == PIN_HIGH ? "on" : "off";
+      };
+      return $@;
+    };
+    ( $cmd eq "count" or $cmd eq "alarm" or $cmd eq "state" ) and do {
+      return main::ReadingsVal($name,"count",$gets{$cmd});
+    };
+  }
+  return undef;
+}
+
+sub
+FRM_IN_Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  my $pin = $hash->{PIN};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+        $attribute eq "count-mode" and do {
+          if ($value ne "none" and !defined main::ReadingsVal($name,"count",undef)) {
+            main::readingsSingleUpdate($main::defs{$name},"count",$sets{count},1);
+          }
+          last;
+        }; 
+        $attribute eq "reset-on-threshold-reached" and do {
+          if ($value eq "yes"
+          and defined (my $threshold = main::AttrVal($name,"count-threshold",undef))) {
+            if (main::ReadingsVal($name,"count",0) > $threshold) {
+              main::readingsSingleUpdate($main::defs{$name},"count",$sets{count},1);
+            }
+          }
+          last;
+        };
+        $attribute eq "count-threshold" and do {
+          if (main::ReadingsVal($name,"count",0) > $value) {
+            main::readingsBeginUpdate($hash);
+            if (main::ReadingsVal($name,"alarm","off") ne "on") {
+              main::readingsBulkUpdate($hash,"alarm","on",1);
+            }
+            if (main::AttrVal($name,"reset-on-threshold-reached","no") eq "yes") {
+              main::readingsBulkUpdate($main::defs{$name},"count",0,1);
+            }
+            main::readingsEndUpdate($hash,1);
+          }
+          last;
+        };
+        $attribute eq "internal-pullup" and do {
+          if ($main::init_done) {
+            my $firmata = FRM_Client_FirmataDevice($hash);
+            $firmata->digital_write($pin,$value eq "on" ? 1 : 0);
+            #ignore any errors here, the attribute-value will be applied next time FRM_IN_init() is called.
+          }
+          last;
+        };
+        $attribute eq "activeLow" and do {
+          my $oldval = AttrVal($hash->{NAME},"activeLow","no");
+          if ($oldval ne $value) {
+            $main::attr{$hash->{NAME}}{activeLow} = $value;
+            if ($main::init_done) {
+              my $firmata = FRM_Client_FirmataDevice($hash);
+              FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
+            }
+          };
+          last;
+        };
+      }
+    } elsif ($command eq "del") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "internal-pullup" and do {
+          my $firmata = FRM_Client_FirmataDevice($hash);
+          $firmata->digital_write($pin,0);
+          last;
+        };
+        $attribute eq "activeLow" and do {
+          if (AttrVal($hash->{NAME},"activeLow","no") eq "yes") {
+            delete $main::attr{$hash->{NAME}}{activeLow};
+            my $firmata = FRM_Client_FirmataDevice($hash);
+            FRM_IN_observer($pin,undef,$firmata->digital_read($pin),$hash);
+          };
+          last;
+        };
+      }
+    }
+  };
+  if (my $error = FRM_Catch($@)) {
+    $hash->{STATE} = "error setting $attribute to $value: ".$error;
+    return "cannot $command attribute $attribute to $value for $name: ".$error;
+  }
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_IN"></a>
+<h3>FRM_IN</h3>
+<ul>
+  represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
+  configured for digital input.<br>
+  The current state of the arduino-pin is stored in reading 'state'. Values are 'on' and 'off'.<br>
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_INdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_IN &lt;pin&gt;</code> <br>
+  Defines the FRM_IN device. &lt;pin&gt> is the arduino-pin to use.
+  </ul>
+  
+  <br>
+  <a name="FRM_INset"></a>
+  <b>Set</b><br>
+  <ul>
+    <li>alarm on|off<br>
+    set the alarm to on or off. Used to clear the alarm.<br>
+    The alarm is set to 'on' whenever the count reaches the threshold and doesn't clear itself.</li>
+  </ul>
+  <a name="FRM_INget"></a>
+  <b>Get</b>
+  <ul>
+    <li>reading<br>
+    returns the logical state of the arduino-pin. Values are 'on' and 'off'.<br></li>
+    <li>count<br>
+    returns the current count. Contains the number of toggles of the arduino-pin.<br>
+    Depending on the attribute 'count-mode' every rising or falling edge (or both) is counted.</li>
+    <li>alarm<br>
+    returns the current state of 'alarm'. Values are 'on' and 'off' (Defaults to 'off')<br>
+    'alarm' doesn't clear itself, has to be set to 'off' eplicitly.</li>
+    <li>state<br>
+    returns the 'state' reading</li>
+  </ul><br>
+  <a name="FRM_INattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li>activeLow &lt;yes|no&gt;</li>
+      <li>count-mode none|rising|falling|both<br>
+      Determines whether 'rising' (transitions from 'off' to 'on') of falling (transitions from 'on' to 'off')<br>
+      edges (or 'both') are counted. Defaults to 'none'</li>
+      <li>count-threshold &lt;number&gt;<br>
+      sets the theshold-value for the counter. Whenever 'count' reaches the 'count-threshold' 'alarm' is<br>
+      set to 'on'. Use 'set alarm off' to clear the alarm.</li>
+      <li>reset-on-threshold-reached yes|no<br>
+      if set to 'yes' reset the counter to 0 when the threshold is reached (defaults to 'no').
+      </li>
+      <li>internal-pullup on|off<br>
+      allows to switch the internal pullup resistor of arduino to be en-/disabled. Defaults to off.
+      </li>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 49 - 0
fhem/core/FHEM/20_FRM_LCD.pm

@@ -0,0 +1,49 @@
+##############################################
+# $Id: 20_FRM_LCD.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+sub
+FRM_LCD_Initialize($)
+{
+  my ($hash) = @_;
+  main::LoadModule("I2C_LCD");
+  I2C_LCD_Initialize($hash);
+  $hash->{DefFn}  = "FRM_LCD_Define";
+  $hash->{InitFn} = "FRM_LCD_Init";
+};
+
+sub
+FRM_LCD_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+  shift @a;
+  return I2C_LCD_Define($hash,join(' ',@a));
+}
+
+sub
+FRM_LCD_Init($)
+{
+  my ($hash,$args) = @_;
+  my $u = "wrong syntax: define <name> FRM_LCD i2c <size-x> <size-y> [<address>]";
+  return $u if(int(@$args) < 3);
+  shift @$args;
+  return I2C_LCD_Init($hash,$args);
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_LCD"></a>
+<h3>FRM_LCD</h3>
+<ul>
+  deprecated, use <a href="#I2C_LCD">I2C_LCD</a>
+</ul>
+=end html
+=cut

+ 165 - 0
fhem/core/FHEM/20_FRM_OUT.pm

@@ -0,0 +1,165 @@
+##############################################
+# $Id: 20_FRM_OUT.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+use SetExtensions;
+
+#####################################
+sub
+FRM_OUT_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{SetFn}     = "FRM_OUT_Set";
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_OUT_Init";
+  $hash->{UndefFn}   = "FRM_Client_Undef";
+  $hash->{AttrFn}    = "FRM_OUT_Attr";
+  $hash->{StateFn}   = "FRM_OUT_State";
+  
+  $hash->{AttrList}  = "restoreOnReconnect:on,off restoreOnStartup:on,off activeLow:yes,no IODev $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_OUT_Init($$)
+{
+	my ($hash,$args) = @_;
+	my $ret = FRM_Init_Pin_Client($hash,$args,PIN_OUTPUT);
+	return $ret if (defined $ret);
+	my $name = $hash->{NAME};
+	if (! (defined AttrVal($name,"stateFormat",undef))) {
+		$main::attr{$name}{"stateFormat"} = "value";
+	}
+	my $value = ReadingsVal($name,"value",undef);
+	if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
+		FRM_OUT_Set($hash,$name,$value);
+	}
+	main::readingsSingleUpdate($hash,"state","Initialized",1);
+	return undef;
+}
+
+sub
+FRM_OUT_Set($$$)
+{
+  my ($hash, $name, $cmd, @a) = @_;
+  my $value;
+  my $invert = AttrVal($hash->{NAME},"activeLow","no");
+  if ($cmd eq "on") {
+  	$value = $invert eq "yes" ? PIN_LOW : PIN_HIGH;
+  } elsif ($cmd eq "off") {
+  	$value = $invert eq "yes" ? PIN_HIGH : PIN_LOW;
+  } else {
+  	my $list = "on off";
+    return SetExtensions($hash, $list, $name, $cmd, @a);
+  }
+  eval {
+    FRM_Client_FirmataDevice($hash)->digital_write($hash->{PIN},$value);
+    main::readingsSingleUpdate($hash,"value",$cmd, 1);
+  };
+  return $@;
+}
+
+sub FRM_OUT_State($$$$)
+{
+	my ($hash, $tim, $sname, $sval) = @_;
+	
+STATEHANDLER: {
+		$sname eq "value" and do {
+			if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") { 
+				FRM_OUT_Set($hash,$hash->{NAME},$sval);
+			}
+			last;
+		}
+	}
+}
+
+sub
+FRM_OUT_Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+      }
+    }
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error setting $attribute to $value: ".$1;
+    return "cannot $command attribute $attribute to $value for $name: ".$1;
+  }
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_OUT"></a>
+<h3>FRM_OUT</h3>
+<ul>
+  represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
+  configured for digital output.<br>
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_OUTdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_OUT &lt;pin&gt;</code> <br>
+  Defines the FRM_OUT device. &lt;pin&gt> is the arduino-pin to use.
+  </ul>
+  
+  <br>
+  <a name="FRM_OUTset"></a>
+  <b>Set</b><br>
+  <ul>
+  <code>set &lt;name&gt; on|off</code><br><br>
+  </ul>
+  <ul>
+  <a href="#setExtensions">set extensions</a> are supported<br>
+  </ul>
+  <a name="FRM_OUTget"></a>
+  <b>Get</b><br>
+  <ul>
+  N/A
+  </ul><br>
+  <a name="FRM_OUTattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li>restoreOnStartup &lt;on|off&gt;</li>
+      <li>restoreOnReconnect &lt;on|off&gt;</li>
+      <li>activeLow &lt;yes|no&gt;</li>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 349 - 0
fhem/core/FHEM/20_FRM_PWM.pm

@@ -0,0 +1,349 @@
+##############################################
+# $Id: 20_FRM_PWM.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+use SetExtensions qw/ :all /;
+
+#####################################
+
+my %gets = (
+  "dim"           => 0,
+  "value"         => 0,
+  "devStateIcon"  => 0,
+);
+
+my %sets = (
+  "on"                  => 0,
+  "off"                 => 0,
+  "toggle"              => 0,
+  "value"               => 1,
+  "dim:slider,0,1,100"  => 1,
+  "fadeTo"              => 2,
+  "dimUp"               => 0,
+  "dimDown"             => 0,
+);
+
+sub
+FRM_PWM_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{SetFn}     = "FRM_PWM_Set";
+  $hash->{GetFn}     = "FRM_PWM_Get";
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_PWM_Init";
+  $hash->{UndefFn}   = "FRM_Client_Undef";
+  $hash->{AttrFn}    = "FRM_PWM_Attr";
+  $hash->{StateFn}   = "FRM_PWM_State";
+  
+  $hash->{AttrList}  = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_PWM_Init($$)
+{
+	my ($hash,$args) = @_;
+	my $ret = FRM_Init_Pin_Client($hash,$args,PIN_PWM);
+	return $ret if (defined $ret);
+	my $firmata = $hash->{IODev}->{FirmataDevice};
+	my $name = $hash->{NAME};
+	my $resolution = $firmata->{metadata}{pwm_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{pwm_resolutions});
+	$hash->{".max"} = defined $resolution ? (1<<$resolution)-1 : 255;
+	$hash->{".dim"} = 0;
+	$hash->{".toggle"} = "off"; 
+	if (! (defined AttrVal($name,"stateFormat",undef))) {
+		$main::attr{$name}{"stateFormat"} = "value";
+	}
+	my $value = ReadingsVal($name,"value",undef);
+	if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
+		FRM_PWM_Set($hash,$name,$value);
+	}
+	main::readingsSingleUpdate($hash,"state","Initialized",1);
+	return undef;
+}
+
+sub
+FRM_PWM_Set($@)
+{
+  my ($hash, $name, $cmd, @a) = @_;
+  
+  my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
+  #-- check argument
+  return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
+  return "$cmd expects $sets{$match[0]} parameters" unless (@a eq $sets{$match[0]});
+
+  eval {
+    SETHANDLER: {
+      my $value = $a[0] if @a; 
+      $cmd eq "on" and do {
+        FRM_PWM_writeOut($hash,$hash->{".max"});
+        $hash->{".toggle"} = "on";
+        last;
+      };
+      $cmd eq "off" and do {
+        FRM_PWM_writeOut($hash,0);
+        $hash->{".toggle"} = "off";
+        last;
+      };
+      $cmd eq "toggle" and do {
+        my $toggle = $hash->{".toggle"};
+        TOGGLEHANDLER: {
+          $toggle eq "off" and do {
+            FRM_PWM_writeOut($hash,$hash->{".dim"});
+            $hash->{".toggle"} = "up";
+            last;    
+          };
+          $toggle eq "up" and do {
+            FRM_PWM_writeOut($hash,$hash->{".max"});
+            $hash->{".toggle"} = "on";
+            last;
+          };
+          $toggle eq "on" and do {
+            FRM_PWM_writeOut($hash,$hash->{".dim"});
+            $hash->{".toggle"} = "down";
+            last;    
+          };
+          $toggle eq "down" and do {
+            FRM_PWM_writeOut($hash,0);
+            $hash->{".toggle"} = "off";
+            last;
+          };
+        };
+        last;
+      };
+      $cmd eq "value" and do {
+        my $max = $hash->{".max"};
+        die "maximum value of $max exceeded: $value" if ($value > $max);
+        FRM_PWM_writeOut($hash,$value);
+        TOGGLEHANDLER: {
+          $value == $max and do {
+            $hash->{".toggle"} = "on";
+            last;
+          };
+          $value == 0 and do {
+            $hash->{".toggle"} = "off";
+            last;
+          };
+          $hash->{".toggle"} = "up" unless $hash->{".toggle"} eq "down";
+          $hash->{".dim"} = $value;
+        };
+        last;
+      };
+      $cmd eq "dim" and do {
+        die "maximum value of 100 exceeded: $value" if ($value > 100);
+        my $dim = int($hash->{".max"}*$value/100);
+        FRM_PWM_writeOut($hash,$dim);
+        TOGGLEHANDLER: {
+          $value == 100 and do {
+            $hash->{".toggle"} = "on";
+            last;
+          };
+          $value == 0 and do {
+            $hash->{".toggle"} = "off";
+            last;
+          };
+          $hash->{".toggle"} = "up" unless $hash->{".toggle"} eq "down";
+          $hash->{".dim"} = $dim;
+        };
+        last;
+      };
+      $cmd eq "fadeTo" and do {
+        die "fadeTo not implemented yet";
+      };
+      $cmd eq "dimUp" and do {
+        my $dim = $hash->{".dim"};
+        my $max = $hash->{".max"};
+        if ($dim > $max * 0.9) {
+          $dim = $max;
+          $hash->{".toggle"} = "on";
+        } else {
+          $dim = $dim + $max / 10;
+          $hash->{".toggle"} = "up" unless $hash->{".toggle"} eq "down";
+        }
+        FRM_PWM_writeOut($hash,$dim);
+        $hash->{".dim"} = $dim;
+        last;
+      };
+      $cmd eq "dimDown" and do {
+        my $step = $hash->{".max"} / 10;
+        my $dim = $hash->{".dim"};
+        if ($dim < $step) {
+          $dim = 0;
+          $hash->{".toggle"} = "off";
+        } else {
+          $dim = $dim - $step;
+          $hash->{".toggle"} = "down" unless $hash->{".toggle"} eq "up";
+        }
+        FRM_PWM_writeOut($hash,$dim);
+        $hash->{".dim"} = $dim;
+        last;
+      };
+    }
+  };
+	if ($@) {
+  	$@ =~ /^(.*)( at.*FHEM.*)$/;
+  	$hash->{STATE} = "error setting '$cmd': ".(defined $1 ? $1 : $@);
+		return "error setting '$hash->{NAME} $cmd': ".(defined $1 ? $1 : $@);
+	}
+	return undef;
+}
+
+sub
+FRM_PWM_writeOut($$)
+{
+  my ($hash,$value) = @_;
+  FRM_Client_FirmataDevice($hash)->analog_write($hash->{PIN},$value);
+  readingsBeginUpdate($hash);
+  readingsBulkUpdate($hash,"value",$value, 1);
+  readingsBulkUpdate($hash,"dim",int($value*100/$hash->{".max"}), 1);
+  readingsEndUpdate($hash, 1);
+}
+
+sub
+FRM_PWM_Get($@)
+{
+  my ($hash, $name, $cmd, @a) = @_;
+  
+  return "FRM_PWM: Get with unknown argument $cmd, choose one of ".join(" ", sort keys %gets)
+    unless defined($gets{$cmd});
+    
+  GETHANDLER: {
+    $cmd eq 'dim' and do {
+      return ReadingsVal($name,"dim",undef);
+    };
+    $cmd eq 'value' and do {
+      return ReadingsVal($name,"value",undef);
+    };
+    $cmd eq 'devStateIcon' and do {
+      return return "not implemented yet";
+    };
+  }
+}
+
+sub
+FRM_PWM_State($$$$)
+{
+  my ($hash, $tim, $sname, $sval) = @_;
+  if ($sname eq "value") {
+    FRM_PWM_Set($hash,$hash->{NAME},$sname,$sval);
+  }
+}
+
+sub
+FRM_PWM_Attr($$$$)
+{
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+      }
+    }
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error setting $attribute to $value: ".$1;
+    return "cannot $command attribute $attribute to $value for $name: ".$1;
+  }
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_PWM"></a>
+<h3>FRM_PWM</h3>
+<ul>
+  represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
+  configured for analog output.<br>
+  The value set will be output by the specified pin as a pulse-width-modulated signal.<br> 
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_PWMdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_PWM &lt;pin&gt;</code> <br>
+  Defines the FRM_PWM device. &lt;pin&gt> is the arduino-pin to use.
+  </ul>
+  
+  <br>
+  <a name="FRM_PWMset"></a>
+  <b>Set</b><br>
+  <ul>
+  <code>set &lt;name&gt; on</code><br>
+  sets the pulse-width to 100%<br>
+  </ul>
+  <ul>
+  <code>set &lt;name&gt; off</code><br>
+  sets the pulse-width to 0%<br>
+  </ul>
+  <ul>
+  <a href="#setExtensions">set extensions</a> are supported<br>
+  </ul>
+  <ul>
+  <code>set &lt;name&gt; toggle</code><br>
+  toggles the pulse-width in between to the last value set by 'value' or 'dim' and 0 respectivly 100%<br>
+  </ul>
+  <ul>
+  <code>set &lt;name&gt; value &lt;value&gt;</code><br>
+  sets the pulse-width to the value specified<br>
+  Range is from 0 to 255 (for 8-bit resolution) (see <a href="http://arduino.cc/en/Reference/AnalogWrite">analogWrite()</a> for details)<br>
+  </ul>
+  <ul>
+  <code>set &lt;name&gt; dim &lt;value&gt;</code><br>
+  sets the pulse-width to the value specified in percent<br>
+  Range is from 0 to 100<br>
+  </ul>
+  <ul>
+  <code>set &lt;name&gt; dimUp</code><br>
+  increases the pulse-width by 10%<br>
+  </ul>
+  <ul>
+  <code>set &lt;name&gt; dimDown</code><br>
+  decreases the pulse-width by 10%<br>
+  </ul>
+  <a name="FRM_PWMget"></a>
+  <b>Get</b><br>
+  <ul>
+  N/A
+  </ul><br>
+  <a name="FRM_PWMattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li>restoreOnStartup &lt;on|off&gt;</li>
+      <li>restoreOnReconnect &lt;on|off&gt;</li>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 368 - 0
fhem/core/FHEM/20_FRM_RGB.pm

@@ -0,0 +1,368 @@
+##############################################
+# $Id: 20_FRM_RGB.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use vars qw{%attr %defs $readingFnAttributes};
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+use Color qw/ :all /;
+use SetExtensions qw/ :all /;
+
+#####################################
+
+my %gets = (
+  "rgb"           => 0,
+  "RGB"           => 0,
+  "pct"           => 0,
+  "devStateIcon"  => 0,
+);
+
+my %sets = (
+  "on"                  => 0,
+  "off"                 => 0,
+  "toggle"              => 0,
+  "rgb:colorpicker,RGB" => 1,
+  "pct:slider,0,1,100"  => 1,
+  "fadeTo"              => 2,
+  "dimUp"               => 0,
+  "dimDown"             => 0,
+);
+
+sub
+FRM_RGB_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{SetFn}     = "FRM_RGB_Set";
+  $hash->{GetFn}     = "FRM_RGB_Get";
+  $hash->{DefFn}     = "FRM_RGB_Define";
+  $hash->{InitFn}    = "FRM_RGB_Init";
+  $hash->{UndefFn}   = "FRM_Client_Undef";
+  $hash->{AttrFn}    = "FRM_RGB_Attr";
+  $hash->{StateFn}   = "FRM_RGB_State";
+  
+  $hash->{AttrList}  = "restoreOnReconnect:on,off restoreOnStartup:on,off IODev loglevel:0,1,2,3,4,5 $readingFnAttributes";
+  
+  LoadModule("FRM");
+  FHEM_colorpickerInit();  
+}
+
+sub
+FRM_RGB_Define($$)
+{
+  my ($hash, $def) = @_;
+  $attr{$hash->{NAME}}{webCmd} = "rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:toggle:on:off";
+  return FRM_Client_Define($hash,$def);
+}
+
+sub
+FRM_RGB_Init($$)
+{
+  my ($hash,$args) = @_;
+  my $name = $hash->{NAME};
+  my $ret = FRM_Init_Pin_Client($hash,$args,PIN_PWM);
+  return $ret if (defined $ret);
+  my @pins = ();
+  eval {
+    my $firmata = FRM_Client_FirmataDevice($hash);
+    $hash->{PIN} = "";
+    foreach my $pin (@{$args}) {
+      $firmata->pin_mode($pin,PIN_PWM);
+      push @pins,{
+        pin     => $pin,
+        "shift" => defined $firmata->{metadata}{pwm_resolutions} ? $firmata->{metadata}{pwm_resolutions}{$pin}-8 : 0,
+      };
+      $hash->{PIN} .= $hash->{PIN} eq "" ? $pin : " $pin";
+    }
+    $hash->{PINS} = \@pins;
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error initializing: ".$1;
+    return "error initializing '".$hash->{NAME}."': ".$1;
+  }
+  if (! (defined AttrVal($name,"stateFormat",undef))) {
+    $attr{$name}{"stateFormat"} = "rgb";
+  }
+  my $value = ReadingsVal($name,"rgb",undef);
+  if (defined $value and AttrVal($hash->{NAME},"restoreOnReconnect","on") eq "on") {
+    FRM_RGB_Set($hash,$name,"rgb",$value);
+  }
+  $hash->{toggle} = "off";
+  $hash->{".dim"} = {
+    bri => 50,
+    channels => [(255) x @{$hash->{PINS}}],    
+  };
+  readingsSingleUpdate($hash,"state","Initialized",1);
+  return undef;  
+}
+
+sub
+FRM_RGB_Set($@)
+{
+  my ($hash, $name, $cmd, @a) = @_;
+  
+  my @match = grep( $_ =~ /^$cmd($|:)/, keys %sets );
+  #-- check argument
+  return SetExtensions($hash, join(" ", keys %sets), $name, $cmd, @a) unless @match == 1;
+  return "$cmd expects $sets{$match[0]} parameters" unless (@a eq $sets{$match[0]});
+
+  eval {
+    SETHANDLER: {
+      $cmd eq "on" and do {
+        FRM_RGB_SetChannels($hash,(0xFF) x scalar(@{$hash->{PINS}}));
+        $hash->{toggle} = "on";
+        last;
+      };
+      $cmd eq "off" and do {
+        FRM_RGB_SetChannels($hash,(0x00) x scalar(@{$hash->{PINS}}));
+        $hash->{toggle} = "off";
+        last;
+      };
+      $cmd eq "toggle" and do {
+        my $toggle = $hash->{toggle};
+        TOGGLEHANDLER: {
+          $toggle eq "off" and do {
+            $hash->{toggle} = "up";
+            FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
+            last;    
+          };
+          $toggle eq "up" and do {
+            FRM_RGB_SetChannels($hash,(0xFF) x @{$hash->{PINS}});
+            $hash->{toggle} = "on";
+            last;
+          };
+          $toggle eq "on" and do {
+            $hash->{toggle} = "down";
+            FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
+            last;    
+          };
+          $toggle eq "down" and do {
+            FRM_RGB_SetChannels($hash,(0x0) x @{$hash->{PINS}});
+            $hash->{toggle} = "off";
+            last;
+          };
+        };
+        last;
+      };
+      $cmd eq "rgb" and do {
+        my $arg = $a[0];
+        my $numPins = scalar(@{$hash->{PINS}});
+        my $nybles = $numPins << 1;
+        die "$arg is not the right format" unless( $arg =~ /^[\da-f]{$nybles}$/i );
+        my @channels = RgbToChannels($arg,$numPins);
+        FRM_RGB_SetChannels($hash,@channels);
+        RGBHANDLER: {
+          $arg =~ /^0{$nybles}$/ and do {
+            $hash->{toggle} = "off";
+            last;
+          };
+          $arg =~ /^f{$nybles}$/i and do {
+            $hash->{toggle} = "on";
+            last;
+          };
+          $hash->{toggle} = "up";
+        };
+        $hash->{".dim"} = ChannelsToBrightness(@channels);
+        last;
+      };
+      $cmd eq "pct" and do {
+        $hash->{".dim"}->{bri} = $a[0];
+        FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
+        last;
+      };
+      $cmd eq "dimUp" and do {
+        $hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} > 90 ? 100 : $hash->{".dim"}->{bri}+10;
+        FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"})); 
+        last; 
+      };
+      $cmd eq "dimDown" and do {
+        $hash->{".dim"}->{bri} = $hash->{".dim"}->{bri} < 10 ? 0 : $hash->{".dim"}->{bri}-10;
+        FRM_RGB_SetChannels($hash,BrightnessToChannels($hash->{".dim"}));
+        last;
+      };
+    }
+  };
+	if ($@) {
+		$@ =~ /^(.*)( at.*FHEM.*)$/;
+		$hash->{STATE} = "error setting '$cmd': ".(defined $1 ? $1 : $@);
+		return "error setting '$hash->{NAME} $cmd': ".(defined $1 ? $1 : $@);
+	}
+	return undef;
+}
+
+sub
+FRM_RGB_Get($@)
+{
+  my ($hash, $name, $cmd, @a) = @_;
+  
+  return "FRM_RGB: Get with unknown argument $cmd, choose one of ".join(" ", sort keys %gets)
+    unless defined($gets{$cmd});
+    
+  GETHANDLER: {
+    $cmd eq 'rgb' and do {
+      return ReadingsVal($name,"rgb",undef);
+    };
+    $cmd eq 'RGB' and do {
+      return ChannelsToRgb(@{$hash->{".dim"}->{channels}});
+    };
+    $cmd eq 'pct' and do {
+      return $hash->{".dim"}->{bri};
+      return undef;
+    };
+  }
+}
+
+sub
+FRM_RGB_SetChannels($$)
+{
+  my ($hash,@channels) = @_;
+
+  my $firmata = FRM_Client_FirmataDevice($hash);
+  my @pins = @{$hash->{PINS}};
+  my @values = @channels;
+  
+  while(@values) {
+    my $pin = shift @pins;
+    my $value = shift @values;
+    if ($pin->{"shift"} < 0) {
+      $value >>= -$pin->{"shift"};
+    } else {
+      $value <<= $pin->{"shift"};
+    }
+    $firmata->analog_write($pin->{pin},$value);
+  };
+  readingsBeginUpdate($hash);
+  readingsBulkUpdate($hash,"rgb",ChannelsToRgb(@channels),1);
+  readingsBulkUpdate($hash,"pct",(ChannelsToBrightness(@channels))->{bri},1);
+  readingsEndUpdate($hash, 1);
+}
+
+sub
+FRM_RGB_State($$$$)
+{
+  my ($hash, $tim, $sname, $sval) = @_;
+  if ($sname eq "rgb") {
+    FRM_RGB_Set($hash,$hash->{NAME},$sname,$sval);
+  }
+}
+
+sub
+FRM_RGB_Attr($$$$)
+{
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+      }
+    }
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error setting $attribute to $value: ".$1;
+    return "cannot $command attribute $attribute to $value for $name: ".$1;
+  }
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_RGB"></a>
+<h3>FRM_RGB</h3>
+<ul>
+  allows to drive LED-controllers and other multichannel-devices that use PWM as input by an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
+  <br>
+  The value set will be output by the specified pins as pulse-width-modulated signals.<br> 
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_RGBdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_RGB &lt;pin&gt; &lt;pin&gt; &lt;pin&gt; [pin...]</code> <br>
+  Defines the FRM_RGB device. &lt;pin&gt> are the arduino-pin to use.<br>
+  For rgb-controlled devices first pin drives red, second pin green and third pin blue.
+  </ul>
+  
+  <br>
+  <a name="FRM_RGBset"></a>
+  <b>Set</b><br>
+  <ul>
+  <code>set &lt;name&gt; on</code><br>
+  sets the pulse-width of all configured pins to 100%</ul><br>
+  <ul>
+  <code>set &lt;name&gt; off</code><br>
+  sets the pulse-width of all configured pins to 0%</ul><br>
+  <ul>
+  <a href="#setExtensions">set extensions</a> are supported</ul><br>
+  <ul>
+  <code>set &lt;name&gt; toggle</code><br>
+  toggles in between the last dimmed value, 0% and 100%. If no dimmed value was set before defaults to pulsewidth 50% on all channels</ul><br>
+  <ul>
+  <code>set &lt;name&gt; rgb &lt;value&gt;</code><br>
+  sets the pulse-width of all channels at once. Also sets the value toggle can switch to<br>
+  Value is encoded as hex-string, 2-digigs per channel (e.g. FFFFFF for reguler rgb)</ul><br>
+  <ul>
+  <code>set &lt;name&gt; pct &lt;value&gt;</code><br>
+  dims all channels at once while leving the ratio in between the channels unaltered.<br>
+  Range is 0-100 ('pct' stands for 'percent')</ul><br>
+  <ul>
+  <code>set &lt;name&gt; dimUp</code><br>
+  dims up by 10%</ul><br>
+  <ul>
+  <code>set &lt;name&gt; dimDown</code><br>
+  dims down by 10%</ul><br>
+  <a name="FRM_RGBget"></a>
+  <b>Get</b><br>
+  <ul>
+  <code>get &lt;name&gt; rgb</code><br>
+  returns the values set for all channels. Format is hex, 2 nybbles per channel.
+  </ul><br>
+  <ul>
+  <code>get &lt;name&gt; RGB</code><br>
+  returns the values set for all channels in normalized format. Format is hex, 2 nybbles per channel. 
+  Values are scaled such that the channel with the highest value is set to FF. The real values are calculated
+  by multipying each byte with the value of 'pct'.
+  </ul><br>
+  <ul>
+  <code>get &lt;name&gt; pct</code><br>
+  returns the value of the channel with the highest value scaled to the range of 0-100 (percent).
+  </ul><br>
+  <a name="FRM_RGBattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li>restoreOnStartup &lt;on|off&gt;</li>
+      <li>restoreOnReconnect &lt;on|off&gt;</li>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 275 - 0
fhem/core/FHEM/20_FRM_ROTENC.pm

@@ -0,0 +1,275 @@
+##############################################
+# $Id: 20_FRM_ROTENC.pm 7111 2014-12-01 14:13:26Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+
+#####################################
+
+my %sets = (
+  "reset" => "noArg",
+  "offset"=> "",
+);
+
+my %gets = (
+  "position" => "noArg",
+  "offset"   => "noArg",
+  "value"    => "noArg",
+);
+
+sub
+FRM_ROTENC_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{SetFn}     = "FRM_ROTENC_Set";
+  $hash->{GetFn}     = "FRM_ROTENC_Get";
+  $hash->{AttrFn}    = "FRM_ROTENC_Attr";
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_ROTENC_Init";
+  $hash->{UndefFn}   = "FRM_ROTENC_Undef";
+  $hash->{StateFn}   = "FRM_ROTENC_State";
+
+  $hash->{AttrList}  = "IODev $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_ROTENC_Init($$)
+{
+	my ($hash,$args) = @_;
+
+	my $u = "wrong syntax: define <name> FRM_ROTENC pinA pinB [id]";
+  	return $u unless defined $args and int(@$args) > 1;
+ 	my $pinA = @$args[0];
+ 	my $pinB = @$args[1];
+ 	my $encoder = defined @$args[2] ? @$args[2] : 0;
+ 	my $name = $hash->{NAME};
+ 	
+	$hash->{PINA} = $pinA;
+	$hash->{PINB} = $pinB;
+	
+	$hash->{ENCODERNUM} = $encoder;
+	
+	eval {
+		FRM_Client_AssignIOPort($hash);
+		my $firmata = FRM_Client_FirmataDevice($hash);
+		$firmata->encoder_attach($encoder,$pinA,$pinB);
+		$firmata->observe_encoder($encoder, \&FRM_ROTENC_observer, $hash );
+	};
+	if ($@) {
+		$@ =~ /^(.*)( at.*FHEM.*)$/;
+		$hash->{STATE} = "error initializing: ".$1;
+		return "error initializing '$name': $1";
+	}
+
+	if (! (defined AttrVal($name,"stateFormat",undef))) {
+		$main::attr{$name}{"stateFormat"} = "position";
+	}
+
+  $hash->{offset} = ReadingsVal($name,"position",0);
+
+	main::readingsSingleUpdate($hash,"state","Initialized",1);
+	return undef;
+}
+
+sub
+FRM_ROTENC_observer
+{
+	my ( $encoder, $value, $hash ) = @_;
+	my $name = $hash->{NAME};
+	Log3 ($name,5,"onEncoderMessage for pins ".$hash->{PINA}.",".$hash->{PINB}." encoder: ".$encoder." position: ".$value."\n");
+	main::readingsBeginUpdate($hash);
+	main::readingsBulkUpdate($hash,"position",$value+$hash->{offset}, 1);
+	main::readingsBulkUpdate($hash,"value",$value, 1);
+	main::readingsEndUpdate($hash,1);
+}
+
+sub
+FRM_ROTENC_Set
+{
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  my $command = $a[1];
+  my $value = $a[2];
+  if(!defined($sets{$command})) {
+  	my @commands = ();
+    foreach my $key (sort keys %sets) {
+      push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
+    }
+    return "Unknown argument $a[1], choose one of " . join(" ", @commands);
+  }
+  COMMAND_HANDLER: {
+    $command eq "reset" and do {
+      eval {
+        FRM_Client_FirmataDevice($hash)->encoder_reset_position($hash->{ENCODERNUM});
+      };
+      main::readingsBeginUpdate($hash);
+      main::readingsBulkUpdate($hash,"position",$hash->{offset},1);
+      main::readingsBulkUpdate($hash,"value",0,1);
+      main::readingsEndUpdate($hash,1);
+      last;
+    };
+    $command eq "offset" and do {
+      $hash->{offset} = $value;
+      readingsSingleUpdate($hash,"position",ReadingsVal($hash->{NAME},"value",0)+$value,1);
+      last;
+    };
+  }
+}
+
+sub
+FRM_ROTENC_Get($)
+{
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  my $command = $a[1];
+  my $value = $a[2];
+  if(!defined($gets{$command})) {
+  	my @commands = ();
+    foreach my $key (sort keys %gets) {
+      push @commands, $gets{$key} ? $key.":".join(",",$gets{$key}) : $key;
+    }
+    return "Unknown argument $a[1], choose one of " . join(" ", @commands);
+  }
+  my $name = shift @a;
+  my $cmd = shift @a;
+  ARGUMENT_HANDLER: {
+    $cmd eq "position" and do {
+      return ReadingsVal($hash->{NAME},"position","0");
+    };
+    $cmd eq "offset" and do {
+      return $hash->{offset};
+    };
+    $cmd eq "value" and do {
+      return ReadingsVal($hash->{NAME},"value","0");
+    };
+  }
+  return undef;
+}
+
+sub
+FRM_ROTENC_Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+      }
+    }
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error setting $attribute to $value: ".$1;
+    return "cannot $command attribute $attribute to $value for $name: ".$1;
+  }
+}
+
+sub
+FRM_ROTENC_Undef($$)
+{
+  my ($hash, $name) = @_;
+  my $pinA = $hash->{PINA};
+  my $pinB = $hash->{PINB};
+  eval {
+    my $firmata = FRM_Client_FirmataDevice($hash);
+    $firmata->encoder_detach($hash->{ENCODERNUM});
+    $firmata->pin_mode($pinA,PIN_ANALOG);
+    $firmata->pin_mode($pinB,PIN_ANALOG);
+  };
+  if ($@) {
+    eval {
+      my $firmata = FRM_Client_FirmataDevice($hash);
+      $firmata->pin_mode($pinA,PIN_INPUT);
+      $firmata->digital_write($pinA,0);
+      $firmata->pin_mode($pinB,PIN_INPUT);
+      $firmata->digital_write($pinB,0);
+    };
+  }
+  return undef;
+}
+
+sub
+FRM_ROTENC_State($$$$)
+{
+  my ($hash, $tim, $sname, $sval) = @_;
+  if ($sname eq "position") {
+    $hash->{offset} = $sval;
+  }
+  return undef;
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_ROTENC"></a>
+<h3>FRM_ROTENC</h3>
+<ul>
+  represents a rotary-encoder attached to two pins of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a><br>
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_ROTENCdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_ROTENC &lt;pinA&gt; &lt;pinB&gt; [id]</code> <br>
+  Defines the FRM_ROTENC device. &lt;pinA&gt> and &lt;pinA&gt> are the arduino-pins to use.<br>
+  [id] is the instance-id of the encoder. Must be a unique number per FRM-device (rages from 0-4 depending on Firmata being used, optional if a single encoder is attached to the arduino).<br>
+  </ul>
+  
+  <br>
+  <a name="FRM_ROTENCset"></a>
+  <b>Set</b><br>
+    <li>reset<br>
+    resets to value of 'position' to 0<br></li>
+    <li>offset &lt;value&gt;<br>
+    set offset value of 'position'<br></li>
+  <a name="FRM_ROTENCget"></a>
+  <b>Get</b>
+  <ul>
+    <li>position<br>
+    returns the position of the rotary-encoder attached to pinA and pinB of the arduino<br>
+    the 'position' is the sum of 'value' and 'offset'<br></li>
+    <li>offset<br>
+    returns the offset value<br>
+    on shutdown of fhem the latest position-value is saved as new offset.<br></li>
+    <li>value<br>
+    returns the raw position value as it's reported by the rotary-encoder attached to pinA and pinB of the arduino<br>
+    this value is reset to 0 whenever Arduino restarts or Firmata is reinitialized<br></li>
+  </ul><br>
+  <a name="FRM_ROTENCattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 163 - 0
fhem/core/FHEM/20_FRM_SERVO.pm

@@ -0,0 +1,163 @@
+##############################################
+# $Id: 20_FRM_SERVO.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+
+#####################################
+
+my %sets = (
+  "angle" => "",
+);
+
+sub
+FRM_SERVO_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{SetFn}     = "FRM_SERVO_Set";
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_SERVO_Init";
+  $hash->{UndefFn}   = "FRM_Client_Undef";
+  $hash->{AttrFn}    = "FRM_SERVO_Attr";
+  
+  $hash->{AttrList}  = "min-pulse max-pulse IODev $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_SERVO_Init($$)
+{
+  my ($hash,$args) = @_;
+  my $ret = FRM_Init_Pin_Client($hash,$args,PIN_SERVO);
+  return $ret if (defined $ret);
+  eval {
+    my $firmata = FRM_Client_FirmataDevice($hash);
+    $hash->{resolution}=$firmata->{metadata}{servo_resolutions}{$hash->{PIN}} if (defined $firmata->{metadata}{servo_resolutions});
+    FRM_SERVO_apply_attribute($hash,"max-pulse"); #sets min-pulse as well
+  };
+  return FRM_Catch($@) if $@;
+  main::readingsSingleUpdate($hash,"state","Initialized",1);
+  return undef;
+}
+
+sub
+FRM_SERVO_Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+        ($attribute eq "min-pulse" || $attribute eq "max-pulse") and do {
+          if ($main::init_done) {
+          	$main::attr{$name}{$attribute}=$value;
+            FRM_SERVO_apply_attribute($hash,$attribute);
+          }
+          last;
+        };
+      }
+    }
+  };
+  my $ret = FRM_Catch($@) if $@;
+  if ($ret) {
+    $hash->{STATE} = "error setting $attribute to $value: ".$ret;
+    return "cannot $command attribute $attribute to $value for $name: ".$ret;
+  }
+  return undef;
+}
+
+sub FRM_SERVO_apply_attribute {
+  my ($hash,$attribute) = @_;
+  if ( $attribute eq "min-pulse" || $attribute eq "max-pulse" ) {
+    my $name = $hash->{NAME};
+    # defaults are taken from: http://arduino.cc/en/Reference/ServoAttach
+    FRM_Client_FirmataDevice($hash)->servo_config($hash->{PIN},{min_pulse => main::AttrVal($name,"min-pulse",544), max_pulse => main::AttrVal($name,"max-pulse",2400)});
+  }
+}
+
+sub
+FRM_SERVO_Set($@)
+{
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
+  	if(!defined($sets{$a[1]}));
+  my $command = $a[1];
+  my $value = $a[2];
+  eval {
+    FRM_Client_FirmataDevice($hash)->servo_write($hash->{PIN},$value);
+    main::readingsSingleUpdate($hash,"state",$value, 1);
+  };
+  return $@;
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_SERVO"></a>
+<h3>FRM_SERVO</h3>
+<ul>
+  represents a pin of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a>
+  configured to drive a pwm-controlled servo-motor.<br>
+  The value set will be drive the shaft of the servo to the specified angle. see <a href="http://arduino.cc/en/Reference/ServoWrite">Servo.write</a> for values and range<br> 
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_SERVOdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_SERVO &lt;pin&gt;</code> <br>
+  Defines the FRM_SERVO device. &lt;pin&gt> is the arduino-pin to use.
+  </ul>
+  
+  <br>
+  <a name="FRM_SERVOset"></a>
+  <b>Set</b><br>
+  <ul>
+  <code>set &lt;name&gt; angle &lt;value&gt;</code><br>sets the angle of the servo-motors shaft to the value specified (in degrees).<br>
+  </ul>
+  <a name="FRM_SERVOget"></a>
+  <b>Get</b><br>
+  <ul>
+  N/A
+  </ul><br>
+  <a name="FRM_SERVOattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li>min-pulse<br>
+      sets the minimum puls-width to use. Defaults to 544. For most servos this translates into a rotation of 180° counterclockwise.</li>
+      <li>max-pulse<br>
+      sets the maximum puls-width to use. Defaults to 2400. For most servos this translates into a rotation of 180° clockwise</li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

+ 333 - 0
fhem/core/FHEM/20_FRM_STEPPER.pm

@@ -0,0 +1,333 @@
+##############################################
+# $Id: 20_FRM_STEPPER.pm 5927 2014-05-21 21:56:37Z ntruchsess $
+##############################################
+package main;
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though...
+BEGIN {
+	if (!grep(/FHEM\/lib$/,@INC)) {
+		foreach my $inc (grep(/FHEM$/,@INC)) {
+			push @INC,$inc."/lib";
+		};
+	};
+};
+
+use Device::Firmata::Constants  qw/ :all /;
+
+#####################################
+
+my %sets = (
+  "reset"    => "noArg",
+  "position" => "",
+  "step"     => "",
+);
+
+my %gets = (
+  "position" => "noArg",
+);
+
+sub
+FRM_STEPPER_Initialize($)
+{
+  my ($hash) = @_;
+
+  $hash->{SetFn}     = "FRM_STEPPER_Set";
+  $hash->{GetFn}     = "FRM_STEPPER_Get";
+  $hash->{DefFn}     = "FRM_Client_Define";
+  $hash->{InitFn}    = "FRM_STEPPER_Init";
+  $hash->{UndefFn}   = "FRM_Client_Undef";
+  $hash->{AttrFn}    = "FRM_STEPPER_Attr";
+  $hash->{StateFn}   = "FRM_STEPPER_State";
+  
+  $hash->{AttrList}  = "restoreOnReconnect:on,off restoreOnStartup:on,off speed acceleration deceleration IODev $main::readingFnAttributes";
+  main::LoadModule("FRM");
+}
+
+sub
+FRM_STEPPER_Init($$)
+{
+	my ($hash,$args) = @_;
+
+	my $u = "wrong syntax: define <name> FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] directionPin stepPin [motorPin3 motorPin4] stepsPerRev [id]";
+	return $u unless defined $args;
+	
+	my $driver = shift @$args;
+	
+	return $u unless ( $driver eq 'DRIVER' or $driver eq 'TWO_WIRE' or $driver eq 'FOUR_WIRE' );
+	return $u if (($driver eq 'DRIVER' or $driver eq 'TWO_WIRE') and (scalar(@$args) < 3 or scalar(@$args) > 4));
+	return $u if (($driver eq 'FOUR_WIRE') and (scalar(@$args) < 5 or scalar(@$args) > 6));
+	
+	$hash->{DRIVER} = $driver;
+	
+	$hash->{PIN1} = shift @$args;
+	$hash->{PIN2} = shift @$args;
+	
+	if ($driver eq 'FOUR_WIRE') {
+		$hash->{PIN3} = shift @$args;
+		$hash->{PIN4} = shift @$args;
+	}
+	
+	$hash->{STEPSPERREV} = shift @$args;
+	$hash->{STEPPERNUM} = shift @$args;
+	
+	eval {
+		FRM_Client_AssignIOPort($hash);
+		my $firmata = FRM_Client_FirmataDevice($hash);
+		$firmata->stepper_config(
+			$hash->{STEPPERNUM},
+			$driver,
+			$hash->{STEPSPERREV},
+			$hash->{PIN1},
+			$hash->{PIN2},
+			$hash->{PIN3},
+			$hash->{PIN4});
+		$firmata->observe_stepper(0, \&FRM_STEPPER_observer, $hash );
+	};
+	if ($@) {
+		$@ =~ /^(.*)( at.*FHEM.*)$/;
+		$hash->{STATE} = "error initializing: ".$1;
+		return "error initializing '".$hash->{NAME}."': ".$1;
+	}
+	$hash->{POSITION} = 0;
+	$hash->{DIRECTION} = 0;
+	$hash->{STEPS} = 0;
+	if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
+		$main::attr{$hash->{NAME}}{"stateFormat"} = "position";
+	}
+	main::readingsSingleUpdate($hash,"state","Initialized",1);
+	return undef;
+}
+
+sub
+FRM_STEPPER_observer
+{
+	my ( $stepper, $hash ) = @_;
+	my $name = $hash->{NAME};
+	Log3 $name,5,"onStepperMessage for pins ".$hash->{PIN1}.",".$hash->{PIN2}.(defined ($hash->{PIN3}) ? ",".$hash->{PIN3} : ",-").(defined ($hash->{PIN4}) ? ",".$hash->{PIN4} : ",-")." stepper: ".$stepper;
+	my $position = $hash->{DIRECTION} ? $hash->{POSITION} - $hash->{STEPS} : $hash->{POSITION} + $hash->{STEPS};
+	$hash->{POSITION} = $position;
+	$hash->{DIRECTION} = 0;
+	$hash->{STEPS} = 0;
+	main::readingsSingleUpdate($hash,"position",$position,1);
+}
+
+sub
+FRM_STEPPER_Set
+{
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  shift @a;
+  my $name = $hash->{NAME};
+  my $command = shift @a;
+  if(!defined($sets{$command})) {
+  	my @commands = ();
+    foreach my $key (sort keys %sets) {
+      push @commands, $sets{$key} ? $key.":".join(",",$sets{$key}) : $key;
+    }
+    return "Unknown argument $command, choose one of " . join(" ", @commands);
+  }
+  COMMAND_HANDLER: {
+    $command eq "reset" and do {
+      $hash->{POSITION} = 0;
+      main::readingsSingleUpdate($hash,"position",0,1);
+      last;
+    };
+    $command eq "position" and do {
+      my $position = $hash->{POSITION};
+      my $value = shift @a;
+      my $direction = $value < $position ? 1 : 0;
+      my $steps = $direction ? $position - $value : $value - $position;
+      my $speed = shift @a;
+      $speed = AttrVal($name,"speed",30) unless (defined $speed);
+      my $accel = shift @a;
+      $accel = AttrVal($name,"acceleration",undef) unless (defined $accel);
+      my $decel = shift @a;
+      $decel = AttrVal($name,"deceleration",undef) unless (defined $decel);
+      $hash->{DIRECTION} = $direction;
+      $hash->{STEPS} = $steps;
+      eval {
+      # $stepperNum, $direction, $numSteps, $stepSpeed, $accel, $decel
+        FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel);
+      };
+      last;
+    };
+    $command eq "step" and do {
+      my $value = shift @a;
+      my $direction = $value < 0 ? 1 : 0;
+      my $steps = abs $value;
+      my $speed = shift @a;
+      $speed = AttrVal($name,"speed",100) unless (defined $speed);
+      my $accel = shift @a;
+      $accel = AttrVal($name,"acceleration",undef) unless (defined $accel);
+      my $decel = shift @a;
+      $decel = AttrVal($name,"deceleration",undef) unless (defined $decel);
+      $hash->{DIRECTION} = $direction;
+      $hash->{STEPS} = $steps;
+      eval {
+      # $stepperNum, $direction, $numSteps, $stepSpeed, $accel, $decel
+        FRM_Client_FirmataDevice($hash)->stepper_step($hash->{STEPPERNUM},$direction,$steps,$speed,$accel,$decel);
+      };
+      last;
+    };
+  }
+}
+
+sub
+FRM_STEPPER_Get
+{
+  my ($hash, @a) = @_;
+  return "Need at least one parameters" if(@a < 2);
+  shift @a;
+  my $name = $hash->{NAME};
+  my $command = shift @a;
+  return "Unknown argument $command, choose one of " . join(" ", sort keys %gets) unless defined($gets{$command});
+}
+
+
+sub FRM_STEPPER_State($$$$)
+{
+	my ($hash, $tim, $sname, $sval) = @_;
+	
+STATEHANDLER: {
+		$sname eq "value" and do {
+			if (AttrVal($hash->{NAME},"restoreOnStartup","on") eq "on") { 
+				FRM_STEPPER_Set($hash,$hash->{NAME},$sval);
+			}
+			last;
+		}
+	}
+}
+
+sub
+FRM_STEPPER_Attr($$$$) {
+  my ($command,$name,$attribute,$value) = @_;
+  my $hash = $main::defs{$name};
+  eval {
+    if ($command eq "set") {
+      ARGUMENT_HANDLER: {
+        $attribute eq "IODev" and do {
+          if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $value)) {
+            FRM_Client_AssignIOPort($hash,$value);
+            FRM_Init_Client($hash) if (defined ($hash->{IODev}));
+          }
+          last;
+        };
+      }
+    }
+  };
+  if ($@) {
+    $@ =~ /^(.*)( at.*FHEM.*)$/;
+    $hash->{STATE} = "error setting $attribute to $value: ".$1;
+    return "cannot $command attribute $attribute to $value for $name: ".$1;
+  }
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="FRM_STEPPER"></a>
+<h3>FRM_STEPPER</h3>
+<ul>
+  represents a stepper-motor attached to digital-i/o pins of an <a href="http://www.arduino.cc">Arduino</a> running <a href="http://www.firmata.org">Firmata</a><br>
+  Requires a defined <a href="#FRM">FRM</a>-device to work.<br><br> 
+  
+  <a name="FRM_STEPPERdefine"></a>
+  <b>Define</b>
+  <ul>
+  <code>define &lt;name&gt; FRM_STEPPER [DRIVER|TWO_WIRE|FOUR_WIRE] &lt;directionPin&gt &lt;stepPin&gt [motorPin3 motorPin4] stepsPerRev [stepper-id]</code><br>
+  Defines the FRM_STEPPER device.
+  <li>[DRIVER|TWO_WIRE|FOUR_WIRE] defines the control-sequence being used to drive the motor.
+    <ul>
+      <li>DRIVER: motor is attached via a smart circuit that is controlled via two lines: 1 line defines the direction to turn, the other triggers one step per impluse.</li>
+      <li>FOUR_WIRE: motor is attached via four wires each driving one coil individually.</li>
+      <li>TWO_WIRE: motor is attached via two wires. This mode makes use of the fact that at any time two of the four motor
+coils are the inverse of the other two so by using an inverting circuit to drive the motor the number of control connections can be reduced from 4 to 2.</li>
+    </ul>
+  </li>
+  <li>
+    <ul>
+      <li>The sequence of control signals for 4 control wires is as follows:<br>
+<br>
+<code>
+Step C0 C1 C2 C3<br>
+   1  1  0  1  0<br>
+   2  0  1  1  0<br>
+   3  0  1  0  1<br>
+   4  1  0  0  1<br>
+</code>
+      </li>
+      <li>The sequence of controls signals for 2 control wires is as follows:<br>
+(columns C1 and C2 from above):<br>
+<br>
+<code>
+Step C0 C1<br>
+   1  0  1<br>
+   2  1  1<br>
+   3  1  0<br>
+   4  0  0<br>
+</code>
+      </li>
+    </ul>
+  </li>
+  <li>
+  If your stepper-motor does not move or does move but only in a single direction you will have to rearrage the pin-numbers to match the control sequence.<br>
+  that can be archived either by rearranging the physical connections, or by mapping the connection to the pin-definitions in FRM_STEPPERS define:<br>
+  e.g. the widely used cheap 28byj-48 you can get for few EUR on eBay including a simple ULN2003 driver interface may be defined by<br>
+  <code>define stepper FRM_STEPPER FOUR_WIRE 7 5 6 8 64 0</code><br>
+  when being connected to the arduio with:<br>
+  <code>motor pin1 <-> arduino pin5<br>
+  motor pin2 <-> arduino pin6<br>
+  motor pin3 <-> arduino pin7<br>
+  motor pin4 <-> arduino pin8<br>
+  motor pin5 <-> ground</code><br>
+  </li>
+  </ul>
+  
+  <br>
+  <a name="FRM_STEPPERset"></a>
+  <b>Set</b><br>
+  <ul>
+  <code>set &lt;name&gt; reset</code>
+  <li>resets the reading 'position' to 0 without moving the motor</li>
+  <br>
+  <code>set &lt;name&gt; position &lt;position&gt; [speed] [acceleration] [deceleration]</code>
+  <li>moves the motor to the absolute position specified. positive or negative integer<br>
+  speed (10 * revolutions per minute, optional), defaults to 30, higher numbers are faster) At 2048 steps per revolution (28byj-48) a speed of 30 results in 3 rev/min<br>
+  acceleration and deceleration are optional.<br>
+  </li>
+  <br>
+  <code>set &lt;name&gt; step &lt;stepstomove&gt; [speed] [accel] [decel]</code>
+  <li>moves the motor the number of steps specified. positive or negative integer<br>
+  speed, accelleration and deceleration are optional.<br>
+  </li>
+  </ul>
+  <a name="FRM_STEPPERget"></a>
+  <b>Get</b><br>
+  <ul>
+  N/A
+  </ul><br>
+  <a name="FRM_STEPPERattr"></a>
+  <b>Attributes</b><br>
+  <ul>
+      <li>restoreOnStartup &lt;on|off&gt;</li>
+      <li>restoreOnReconnect &lt;on|off&gt;</li>
+      <li><a href="#IODev">IODev</a><br>
+      Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
+      than one FRM-device defined.)
+      </li>
+      <li>>speed (same meaning as in 'set position')</li>
+      <li>acceleration (same meaning as in 'set position')</li>
+      <li>deceleration (same meaning as in 'set position')</li>
+      <li><a href="#eventMap">eventMap</a><br></li>
+      <li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
+    </ul>
+  </ul>
+<br>
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1865 - 0
fhem/core/FHEM/20_GUEST.pm


+ 519 - 0
fhem/core/FHEM/20_N4HBUS.pm

@@ -0,0 +1,519 @@
+#############################################################################
+#
+# 20_N4HBUS.pm
+#
+# net4home Busconnector Device
+#
+# (c) 2014-2016 Oliver Koerber <koerber@net4home.de>
+#
+#
+# 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: 20_N4HBUS.pm 12459 2016-10-28 19:00:08Z okoerber $
+#
+##############################################################################
+
+package main;
+
+use strict;
+use warnings;
+use POSIX;
+use Data::Dumper;
+
+my $n4hbus_Version = "1.0.1.2 - 27.10.2016";
+
+sub N4HBUS_Read($@);
+sub N4HBUS_Write($$$$);
+sub N4HBUS_Ready($);
+sub N4HBUS_getDevList($$);
+sub N4HBUS_decompSection($$$);
+sub N4HBUS_CompressSection($$);
+sub N4HBUS_Initialize($)
+{
+	my ($hash) = @_;
+	require "$attr{global}{modpath}/FHEM/DevIo.pm";
+
+# Provider
+	$hash->{ReadFn}		= "N4HBUS_Read";
+	$hash->{WriteFn}	= "N4HBUS_Write";
+	$hash->{ReadyFn}	= "N4HBUS_Ready";
+
+# Normal devices
+	$hash->{UndefFn}	= "N4HBUS_Undef";
+	$hash->{DefFn}		= "N4HBUS_Define";
+	$hash->{AttrList}	= "dummy:1,0 ".
+						  "OBJADR ".
+						  "MI ";
+}
+
+#################################################################################
+sub N4HBUS_Define($$) {
+#################################################################################
+
+	my ($hash, $def) = @_;
+	my @a = split("[ \t]+", $def);
+
+	if(@a != 3) {
+     my $msg = "wrong syntax: define <name> N4HBUS hostname:port";
+     Log3 $hash, 2, $msg;
+	 return $msg;
+	}
+    DevIo_CloseDev($hash);
+
+	my $name = $a[0];
+	my $dev  = $a[2];
+	
+	$hash->{VERSION}	= $n4hbus_Version;
+	$hash->{DeviceName} = $dev;
+	$hash->{Clients} 	= ":N4HMODULE:";
+	my %matchList = ( "1:N4HMODULE" => ".*" );
+	$hash->{MatchList} = \%matchList;
+
+	Log3 $hash, 3, "N4HBUS_Define -> $name at $dev";
+
+	if($dev eq "none") {
+		Log3 $hash, 1, "N4HBUS device is none, commands will be echoed only";
+		$attr{$name}{dummy} = 1;
+		return undef;
+	}
+  
+	my $ret = DevIo_OpenDev($hash, 0, "N4HBUS_DoInit");
+	return $ret;
+}
+
+#################################################################################
+sub N4HBUS_DoInit($) {
+#################################################################################
+
+	my $hash = shift;
+	my $name = $hash->{NAME};
+	delete $hash->{HANDLE}; 
+
+	# set OBJ and MI if not defined as attributes
+	if (!defined($hash->{OBJADR}) ) {
+		$attr{$name}{OBJADR} = 32700;
+	}
+		
+	if (!defined($hash->{MI}) ) {
+		$attr{$name}{MI}  = 65281;
+	}
+
+	my $sendmsg = "190000000002ac0f400a000002bc02404600000487000000c000000200";
+	DevIo_SimpleWrite($hash, $sendmsg, 1);
+	return undef;
+}
+
+#################################################################################
+sub N4HBUS_Undef($@) {
+#################################################################################
+
+	my ( $hash, $arg ) = @_;       
+	my $name = $hash->{NAME};
+	
+    Log3 $hash, 2, "close port for $name";
+	DevIo_CloseDev($hash);         
+	
+	return undef;  	
+}
+
+##################################################################################
+sub decode_d2b (@) {
+##################################################################################
+
+	my $w = sprintf("%04x\n", @_);   
+	my $ret = substr($w,2,2).substr($w,0,2);
+
+	return $ret;
+}
+
+#################################################################################
+sub N4HBUS_CompressSection($$) {
+#################################################################################
+
+	my ($hash, $pUnCompressed) = @_;
+	
+	my ($cs, $x) = 0;
+	my $pCompressed = "";
+	my $sizeRaw = length($pUnCompressed)/2;
+	
+    for(my $i=0;$i < (length($pUnCompressed)/2); $i++) {
+	 $cs = $cs + hex(substr($pUnCompressed,$i*2,2));
+	}
+
+	my $len = length($pUnCompressed)/2;
+	my $hi = $len >> 8;
+	my $lo = $len & 0b0000000011111111;				
+	$pCompressed = sprintf ("%02X%02X", $hi,$lo);
+			
+	my $p = 0;
+	while ($len>0){
+	 $pCompressed = $pCompressed.(substr($pUnCompressed,$p*2,2));
+	 $len--;	
+	 $p++;
+	}
+
+	$pCompressed = $pCompressed."C0";
+	$pCompressed = $pCompressed.sprintf ("%02X", ($cs>>24) );	
+	$pCompressed = $pCompressed.sprintf ("%02X", ($cs>>16) );	
+	$pCompressed = $pCompressed.sprintf ("%02X", ($cs>>8) );	
+	$pCompressed = $pCompressed.sprintf ("%02X", ( ($cs>>0) & 0xff ) );	
+	
+	$pCompressed = sprintf ("%02X", (length($pCompressed)/2))."000000".$pCompressed;	
+	return $pCompressed;
+}	
+
+#################################################################################
+sub N4HBUS_decompSection($$$) {
+#################################################################################
+  my ($hash, $p2, $fs) = @_;
+
+	my $ret = 0;
+	
+	my $inBlock;
+	my $gPoutPos = 0;
+	my $zaehler = 0;
+	my $err = 0;
+	my $ende = 0;
+	my $gPout = "";
+	my $bb = 0;
+	my $bc = 0;
+	my $csCalc = 0;
+	my $csRx;
+	my $maxoutlen = 372;
+	my @ar2k;
+	
+	my $decompressor_err = -4;
+	my $decompressor_errAdr = 0;
+
+	
+  while (($zaehler<$fs) && ($gPoutPos < $maxoutlen) && ($ende != 1) && ($err != 1)) {
+
+	$bb = substr($p2,$zaehler*2,2);
+	my $bbout = hex($bb) & 192;
+ 
+  if ( (hex($bb) & 192) == 192) {
+		# Ende ist gefunden
+		$ende = 1;
+		
+	} elsif ((hex($bb) & 192) == 0) {
+	
+		$bc = substr($p2,(($zaehler+1)*2),2);
+		$inBlock =  (hex(substr($p2,$zaehler*2,2))*256) + hex($bc);
+		$zaehler = $zaehler+2;
+		
+        while ($inBlock > 0) {
+			$inBlock--;
+			$gPout = $gPout.substr($p2,$zaehler*2,2);
+			$zaehler++;
+		}
+
+	} elsif ((hex($bb) & 192) == 64) {
+
+		$bc = substr($p2,(($zaehler+1)*2),2);
+		$inBlock =  ((hex(substr($p2,$zaehler*2,2))*256) + hex($bc)) & 16383;
+		$bb = substr($p2,($zaehler+2)*2,2);
+
+		$zaehler = $zaehler+3;
+
+        while ($inBlock > 0) {
+			$inBlock--;
+			$gPout = $gPout.$bb;
+		}
+	
+	} elsif ($bb & 0xC0 == 0x80) {
+		$err = 1;
+		$decompressor_err = -2;
+		$zaehler++;
+	}
+	
+    $decompressor_errAdr = $zaehler;
+  }
+  
+  if (($err != 1) and ($ende == 1)) {
+   $ret = $gPout;
+  }
+	return $ret;
+}
+
+##################################################################################
+sub N4HBUS_bin2text ($) {
+##################################################################################
+# Umwandlung der empfangenen oder zu sendenden Daten in lesbares Format 
+
+	my ( $data ) = @_;       
+	my $ret = "";
+	my $sub;
+
+    if (length($data)>60) {
+	# Kenne ich noch nicht
+	$ret = "(".substr($data,0,12).")\t";
+	# ptype 
+	$sub = hex(substr($data,14,2).substr($data,12,2));
+	$ret = $ret."ptype=$sub\t";
+	# payloadlen
+	$sub = hex(substr($data,18,2).substr($data,16,2));
+	$ret = $ret."payloadlen=$sub\t";
+	# MMS oder BIN
+	$sub = hex(substr($data,22,2).substr($data,20,2));
+	$ret = $ret."IP=$sub\t";
+	# Kenne ich noch nicht
+	$sub = substr($data,24, 6);
+	
+	$ret = $ret."($sub)\t";
+	# type8
+	$sub = hex(substr($data,30,2));
+	$ret = $ret."type8=$sub\t";
+	# ipsrc
+	$sub = (substr($data,34,2).substr($data,32,2));
+	$ret = $ret."MI=$sub\t";
+	# ipdst
+	$ret = $ret."ipdst=".hex(substr($data,38,2).substr($data,36,2))."\t";
+	# objsrc
+	$ret = $ret."objsrc=".hex(substr($data,42,2).substr($data,40,2))."\t";
+	# datalen
+	my $datalen = hex((substr($data,44,2)));
+	$ret = $ret."datalen=".$datalen."\t";
+	# ddata
+	$ret = $ret."ddata=".substr($data,46, ($datalen*2))."\t";
+	my $pos = $datalen*2+46;
+	
+	my $csRX	= hex(substr($data,$pos,2));
+	my $csCalc	= hex(substr($data,$pos+2,2));
+	my $len		= hex(substr($data,$pos+4,2));
+	my $posb	= hex(substr($data,$pos+6,2));
+	
+	$ret = $ret."($csRX/$csCalc/$len/$posb)"
+	}
+	
+	return $ret;
+}
+
+##################################################################################
+sub N4HBUS_Write($$$$) {
+##################################################################################
+
+	my ($hash,$ipdst,$ddata,$objsource) = @_;
+	my $name = $hash->{NAME};
+	
+	return if(!$hash || AttrVal($hash->{NAME}, "dummy", 0) != 0);
+	
+	my $sendmsg = "";
+	my $sendbus = "";
+	my $msg = "";
+	
+	my $objsrc = AttrVal($name, "OBJADR", 32700); 
+	my $ipsrc  = AttrVal($name, "MI", 65281); 
+	
+	#  A10F 0000 4E00 0000 00 -  00 5902 0D62 2D65 03 32 00 64 03D416070102201500003120736563742000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004A4A030000
+
+	# payload type
+	$sendbus = "A10F0000";
+	# payload len0
+	$sendbus = $sendbus."4E000000";
+	#??
+	$sendbus = $sendbus."00";
+
+	# typ8
+	$sendbus = $sendbus."00";
+
+	$sendbus = $sendbus.decode_d2b($ipsrc);
+	$sendbus = $sendbus.decode_d2b($ipdst);
+
+	if ($objsource !=0 ) {
+	 $objsrc = $objsource;
+	}
+	
+	$sendbus = $sendbus.decode_d2b($objsrc);
+	
+	# ddata
+	while (length($ddata)<128) {
+	 $ddata = $ddata."00";
+	}
+	$sendbus = $sendbus.$ddata;
+	
+	# csRx, csCalc, len, posb
+	$sendbus = $sendbus."00000000";
+
+	$sendmsg = N4HBUS_CompressSection($hash, $sendbus);
+    Log3 $hash, 5, "N4HBUS (+++): $sendmsg";
+
+		
+	if(defined($hash)) {
+		DevIo_SimpleWrite($hash, $sendmsg, 1);
+	}
+}
+
+##################################################################################
+sub N4HBUS_Read($@) {
+#################################################################################
+	my ($hash, $local, $regexp) = @_;
+    my $buf = DevIo_SimpleRead($hash);
+
+	return "" if(!defined($buf));
+	my $name = $hash->{NAME};
+ 
+	my $recdata = unpack('H*', $buf);
+	my $data;
+
+	my $len  = 0;
+	$len  = substr($recdata,6,2); 
+	$len  = $len.substr($recdata,4,2); 
+	$len  = $len.substr($recdata,2,2); 
+	$len  = $len.substr($recdata,0,2); 
+	$len  =~ s/^0+//;
+
+	my $test = "";
+	$recdata  = substr($recdata,8,hex($len)*2); 
+	
+	
+	while (length($recdata) >= ( hex($len) *2)) {
+	
+	$data 	= substr($recdata,0, hex($len)*2); 
+	Log3 $hash, 5, "N4HBUS (DECOMP): Länge (".hex($len)." bytes)-$data";
+	
+#	Log3 $hash, 1, "(DECOMP1): Länge (".hex($len)." bytes)-$data";
+
+# 0005 a10f 0000 04 40 05 0000 1c 7302 ff7f a86a 05 65 09 05 01 c2 		4d617263656c20476f6572747a6b6175403000c000000b24
+# 0005 a10f 0000 04 40 06 0000 1b 01   bc7f 1127 04 41 00 01 04 		624d617263656c20476f6572747a6b6175403000c000000904
+#                              1b 01   ffff 1127 03 ff ff ff 			64b44d617263656c20476f6572747a6b6175403000c000000d33
+# 0005 a10f 0000 04 40 05 0000 1f 2600 ffff 2c0a 03 ff ff ff 			017f2127100320160100271003201601006f00a19c400aff402300c000001333
+
+	my $idx;
+	$idx = index($data,"a10f");
+
+	if ($idx>=0) {
+		$data = substr($data,$idx); # Cut off beginning - Der Anfang interessiert erst mal nicht...
+
+		my $msg 	= substr($data,18,length($data));
+		my $type8   = hex(substr($msg,0,2));
+
+		# Es ist ein "normales" Status-Paket
+		
+		if ($type8>=0) {
+
+			$hash->{"${name}_MSGCNT"}++;
+			$hash->{"${name}_TIME"} = TimeNow();
+			$hash->{RAWMSG} = $msg;
+
+			my %addvals = (RAWMSG => $data);
+			Dispatch($hash, $msg, \%addvals) if($init_done);
+		}
+	} # a10f - Statuspaket
+	
+  	$recdata = substr($recdata,( hex($len) *2)); 
+	} # while
+	return undef;
+}
+
+#################################################################################
+sub N4HBUS_Ready($) {
+#################################################################################
+
+	my ($hash) = @_;
+	
+	Log3 $hash, 1, "N4HBUS_Ready";
+	return DevIo_OpenDev($hash, 1, "N4HBUS_DoInit");
+}
+
+# function decompSection(p2:pbyte; offset, fs:dword; p2out:pbyte; MaxOutLen:dword; useCS:boolean):dword;
+#################################################################################
+1;
+
+=pod
+=item device
+=item summary Connector to net4home bus via IP
+=item summary_DE Konnektor zum net4home Bus über IP
+=begin html
+
+<a name="N4HBUS"></a>
+<h3>N4HBUS</h3>
+  This module connects fhem to the net4home Bus. You need to define ojects with <a href="#N4MODULE">N4MODULE</a> to set or read 
+  data of th net4home bus.
+  <br /><br />
+  Further technical information can be found at the <a href="http://www.net4home.de">net4home.de</a> Homepage  
+  <br /><br />
+  <a name="N4HBUS_Define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; N4HBUS &lt;device&gt;</code>
+  <br />  <br />
+  &lt;device&gt; is a combination of &lt;host&gt;:&lt;port&gt;, where &lt;host&gt; is the IP address of the net4home Busconnector and &lt;port&gt; (default:3478).
+  <br />  <br />
+  Example:
+  <ul>
+    <code>define net4home N4HBUS 192.168.1.69:3478</code>
+  </ul>
+  <br />  
+   The device can also be connected to the busconnector on the same machine. <br />
+   Default Port for communication is 3478. In case you need to change this please change also the Port in the conf of busconnector service.
+  </ul>
+  <br />
+  <a name="N4HBUS_Readings"></a>
+  <b>Readings</b>
+  <ul>
+    <li>state - current state of the Bus connection</li>
+  </ul><br />
+  
+  <a name="N4HBUS_Attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li>MI - a unique MI in the net4home environment (default:65281)</li>
+    <li>OBJADR - a unique OBJADR in the net4home environment (default:32700)</li>
+  </ul>
+
+=end html
+
+=begin html_DE
+
+<a name="N4HBUS"></a>
+<h3>N4HBUS</h3>
+  Dieses Modul verbindet fhem &uuml;ber IP mit dem net4home Bus. Zus&auml;tzlich m&uuml;ssen Objekte &uuml;ber den Typ 
+  <a href="#N4MODULE">N4MODULE</a> definiert werden, um Daten an den net4home-Bus zu senden oder zu lesen.
+  <br /><br />
+  Weitere technische Informationen gibt es auf der Homepage unter <a href="http://www.net4home.de">net4home.de</a>  
+  <br /><br />
+  <a name="N4HBUS_Define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; N4HBUS &lt;device&gt;</code>
+  <br />  <br />
+  &lt;device&gt; ist eine Kombination aus IP Adresse des net4home Busconnectors und dem Port (default:3478).
+  <br />  <br />
+  Beispiel:
+  <ul>
+    <code>define net4home N4HBUS 192.168.1.69:3478</code>
+  </ul>
+  <br />  
+  Das Device kann sich auch mit dem Busconnector auf dem selben System verbinden. 
+  Der Default-Port für die Kommunikation ist 3478. Sollte es n&ouml;tig sein den Port zu ver&auml;ndern, so muss dies ebenfalls
+  in der Konfiguration des Services durchgef&uuml;hrt werden.<br />
+  </ul>
+  <br /> 
+  
+  <a name="N4HBUS_Readings"></a>
+  <b>Readings</b>
+  <ul>
+    <li>state - aktueller Status der Verbindung zum net4home Busconnector</li>
+  </ul><br />
+  <a name="N4HBUS_Attr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li>MI - die eindeutige MI in der net4home Umgebung (default:65281)</li>
+    <li>OBJADR - die eindeutige OBJADR in der net4home Umgebung (default:32700)</li>
+  </ul>
+
+=end html_DE
+
+=cut
+

+ 372 - 0
fhem/core/FHEM/20_OWFS.pm

@@ -0,0 +1,372 @@
+################################################################
+#
+#  Copyright notice
+#
+#  (c) 2008 Copyright: Martin Fischer (m_fischer at gmx dot de)
+#  All rights reserved
+#
+#  This script 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.
+#
+#  The GNU General Public License can be found at
+#  http://www.gnu.org/copyleft/gpl.html.
+#  A copy is found in the textfile GPL.txt and important notices to the license
+#  from the author is found in LICENSE.txt distributed with these scripts.
+#
+#  This script 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.
+#
+################################################################
+# $Id: 20_OWFS.pm 2516 2013-01-14 10:41:48Z mfr69bs $
+package main;
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+use OW;
+
+my %models = (
+  "DS1420"     => "",
+  "DS9097"     => "",
+);
+my %fc = (
+  "1:DS9420"   => "01",
+  "2:DS1420"   => "81",
+  "3:DS1820"   => "10",
+);
+
+my %gets = (
+  "address"     => "",
+  "alias"       => "",
+  "crc8"        => "",
+  "family"      => "",
+  "id"          => "",
+  "locator"     => "",
+  "present"     => "",
+#  "r_address"   => "",
+#  "r_id"        => "",
+#  "r_locator"   => "",
+  "type"        => "",
+);
+
+##############################################
+sub
+OWFS_Initialize($)
+{
+  my ($hash) = @_;
+
+# Provider
+  $hash->{WriteFn}    = "OWFS_Write";
+  $hash->{Clients}    = ":OWTEMP:";
+
+# Normal devices
+  $hash->{DefFn}      = "OWFS_Define";
+  $hash->{UndefFn}    = "OWFS_Undef";
+  $hash->{GetFn}      = "OWFS_Get";
+  #$hash->{SetFn}      = "OWFS_Set";
+  $hash->{AttrList}   = "IODev do_not_notify:1,0 dummy:1,0 temp-scale:C,F,K,R ".
+                        "showtime:1,0 loglevel:0,1,2,3,4,5,6"; }
+
+#####################################
+sub
+OWFS_Get($$)
+{
+  my ($hash,@a) = @_;
+
+  return "argument is missing @a" if (@a != 2);
+  return "Passive Adapter defined. No Get function implemented."
+    if(!defined($hash->{OW_ID}));
+  return "Unknown argument $a[1], choose one of " . join(",", sort keys %gets)
+    if(!defined($gets{$a[1]}));
+
+  my $ret = OWFS_GetData($hash,$a[1]);
+
+  return "$a[0] $a[1] => $ret"; 
+}
+
+#####################################
+sub
+OWFS_GetData($$)
+{
+  my ($hash,$query) = @_;
+  my $name = $hash->{NAME};
+  my $path = $hash->{OW_PATH};
+  my $ret = undef;
+  
+  $ret = OW::get("/uncached/$path/$query");
+  if ($ret) {
+    # strip spaces
+    $ret =~ s/^\s+//g;
+    Log 4, "OWFS $name $query $ret";
+    $hash->{READINGS}{$query}{VAL} = $ret;
+    $hash->{READINGS}{$query}{TIME} = TimeNow();
+    return $ret;
+  } else {
+    return undef;
+  }
+}
+
+#####################################
+sub
+OWFS_DoInit($)
+{
+  my ($hash) = @_;
+  my $name = $hash->{NAME};
+  my $path;
+  my $ret;
+
+  if (defined($hash->{OWFS_ID})) {
+    $path = $hash->{OW_FAMILY}.".".$hash->{OWFS_ID};
+ 
+    foreach my $q (sort keys %gets) {
+      $ret = OWFS_GetData($hash,$q);
+    }
+  }
+
+  $hash->{STATE} = "Initialized" if (!$hash->{STATE});  
+  return undef;
+}
+
+#####################################
+sub
+OWFS_Define($$)
+{
+  my ($hash, $def) = @_;
+
+  # define <name> OWFS <owserver:port> <model> <id>
+  # define foo OWFS 127.0.0.1:4304 DS1420 93302D000000
+
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> OWFS <owserver:port> <model> [<id>]"
+    if (@a < 2 && int(@a) > 5);
+
+  my $name  = $a[0];
+  my $dev   = $a[2];
+#  return "wrong device format: use ip:port"
+#    if ($device !~ m/^(.+):(0-9)+$/);
+
+  my $model = $a[3];
+  return "Define $name: wrong model: specify one of " . join ",", sort keys %models
+    if (!grep { $_ eq $model } keys %models);
+
+  if (@a > 4) {
+    my $id     = $a[4];
+    return "Define $name: wrong ID format: specify a 12 digit value"
+      if (uc($id) !~ m/^[0-9|A-F]{12}$/); 
+
+    $hash->{FamilyCode} = \%fc;
+    my $fc = $hash->{FamilyCode};
+    if (defined ($fc)) {
+      foreach my $c (sort keys %{$fc}) {
+        if ($c =~ m/$model/) {
+          $hash->{OW_FAMILY} = $fc->{$c};
+        }
+      }
+    }
+    delete ($hash->{FamilyCode});
+    $hash->{OW_ID} = $id;
+    $hash->{OW_PATH} = $hash->{OW_FAMILY}.".".$hash->{OW_ID};
+  }
+
+  $hash->{STATE} = "Defined";
+
+  # default temperature-scale: C
+  # C: Celsius, F: Fahrenheit, K: Kelvin, R: Rankine
+  $attr{$name}{"temp-scale"} = "C";
+
+  if ($dev eq "none") {
+    $attr{$name}{dummy} = 1;
+    Log 1, "OWFS device is none, commands will be echoed only";
+    return undef;
+  }
+
+  Log 3, "OWFS opening OWFS device $dev";
+
+  my $po;
+  $po = OW::init($dev);
+
+  return "Can't connect to $dev: $!" if(!$po);
+
+  Log 3, "OWFS opened $dev for $name";
+
+  Log 1, "OWFS ########################################";
+  Log 1, "OWFS # IMPORTANT NOTE:";
+  Log 1, "OWFS # This module is deprecated and will be removed in a future release!";
+  Log 1, "OWFS # Please use OWServer / OWDevice.";
+  Log 1, "OWFS ########################################";
+
+  $hash->{DeviceName} = $dev;
+  $hash->{STATE}="";
+  my $ret  = OWFS_DoInit($hash);
+  return undef;
+}
+
+#####################################
+sub
+OWFS_Undef($$)
+{
+  my ($hash, $arg) = @_;
+  my $name = $hash->{NAME};
+
+  foreach my $d (sort keys %defs) {
+    if (defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash) {
+      my $lev = ($reread_active ? 4 : 2);
+      Log GetLogLevel($name,$lev), "deleting port for $d";
+      delete $defs{$d}{IODev};
+    }
+  }
+  return undef;
+}
+
+1;
+
+=pod
+=begin html
+
+<a name="OWFS"></a>
+<h3>OWFS</h3>
+<ul>
+  OWFS is a suite of programs that designed to make the 1-wire bus and its
+  devices easily accessible. The underlying priciple is to create a virtual
+  filesystem, with the unique ID being the directory, and the individual
+  properties of the device are represented as simple files that can be read
+  and written.<br><br>
+
+  Note: You need the owperl module from
+  <a href="http://owfs.org/index.php?page=owperl">http://owfs.org/</a>.
+  <br><br>
+
+  <a name="OWFSdefine"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; OWFS &lt;owserver-ip:port&gt; &lt;model&gt; [&lt;id&gt;]</code>
+    <br><br>
+
+    Define a 1-wire device to communicate with an OWFS-Server.<br><br>
+
+    <code>&lt;owserver-ip:port&gt;</code>
+    <ul>
+      IP-address:port from OW-Server.
+    </ul>
+    <code>&lt;model&gt;</code>
+    <ul>
+      Define the <a href="#owfs_type">type</a> of the input device.
+      Currently supportet: <code>DS1420, DS9097 (for passive Adapter)</code>
+    </ul>
+    <code>&lt;id&gt;</code>
+    <ul>
+      Corresponding to the <a href="#owfs_id">id</a> of the input device. Only for active Adapter.
+      <br><br>
+    </ul>
+
+    Note:<br>
+    If the <code>owserver-ip:port</code> is called <code>none</code>, then
+    no device will be opened, so you can experiment without hardware attached.<br><br>
+
+    Example:
+    <ul>
+      <code>#define an active Adapter:<br>
+      define DS9490R OWFS 127.0.0.1:4304 DS1420 93302D000000</code><br>
+    </ul>
+    <br>
+    <ul>
+      <code>#define a passive Adapter:<br>
+      define DS9097 OWFS 127.0.0.1:4304 DS9097</code><br>
+    </ul>
+    <br>
+  </ul>
+
+  <b>Set</b> <ul>N/A</ul><br>
+
+  <a name="OWFSget"></a>
+  <b>Get</b>
+  <ul>
+    <code>get &lt;name&gt; &lt;value&gt;</code>
+    <br><br>
+    where <code>value</code> is one of (not supported by passive Devices e.g. DS9097):<br>
+    <ul>
+      <li><a name="owfs_address"></a>
+        <code>address</code> (read-only)<br>
+        The entire 64-bit unique ID. address starts with the family code.<br>
+        Given as upper case hexidecimal digits (0-9A-F).
+      </li>
+      <li><a name="owfs_crc8"></a>
+        <code>crc8</code> (read-only)<br>
+        The 8-bit error correction portion. Uses cyclic redundancy check. Computed
+        from the preceeding 56 bits of the unique ID number.<br>
+        Given as upper case hexidecimal digits (0-9A-F).
+      </li>
+      <li><a name="owfs_family"></a>
+        <code>family</code> (read-only)<br>
+        The 8-bit family code. Unique to each type of device.<br>
+        Given as upper case hexidecimal digits (0-9A-F).
+      </li>
+      <li><a name="owfs_id"></a>
+        <code>id</code> (read-only)<br>
+        The 48-bit middle portion of the unique ID number. Does not include the
+        family code or CRC.<br>
+        Given as upper case hexidecimal digits (0-9A-F).
+      </li>
+      <li><a name="owfs_locator"></a>
+        <code>locator</code> (read-only)<br>
+        Uses an extension of the 1-wire design from iButtonLink company that
+        associated 1-wire physical connections with a unique 1-wire code. If
+        the connection is behind a Link Locator the locator will show a unique
+        8-byte number (16 character hexidecimal) starting with family code FE.<br>
+        If no Link Locator is between the device and the master, the locator
+        field will be all FF.
+      </li>
+      <li><a name="owfs_present"></a>
+        <code>present</code> (read-only)<br>
+        Is the device currently present on the 1-wire bus?
+      </li>
+      <li><a name="owfs_type"></a>
+        <code>type</code> (read-only)<br>
+        Part name assigned by Dallas Semi. E.g. DS2401 Alternative packaging
+       (iButton vs chip) will not be distiguished.
+      </li>
+      <br>
+    </ul>
+    Examples:
+    <ul>
+      <code>get DS9490R type</code><br>
+      <code>DS9490R type => DS1420</code><br><br>
+      <code>get DS9490R address</code><br>
+      <code>DS9490R address => 8193302D0000002B</code>
+    </ul>
+    <br>
+  </ul>
+
+  <a name="OWFSattr"></a>
+  <b>Attributes</b>
+  <ul>
+    <li><a href="#attrdummy">dummy</a></li>
+    <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#loglevel">loglevel</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a name="owfs_temp-scale"></a>
+      temp-scale<br>
+      Specifies the temperature-scale unit:
+      <ul>
+        <li><code>C</code><br>
+          Celsius. This is the default.</li>
+        <li><code>F</code><br>
+          Fahrenheit</li>
+        <li><code>K</code><br>
+          Kelvin</li>
+        <li><code>R</code><br>
+          Rankine</li>
+      </ul>
+    </li>
+  </ul>
+  <br>
+
+</ul>
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1850 - 0
fhem/core/FHEM/20_ROOMMATE.pm


+ 652 - 0
fhem/core/FHEM/20_X10.pm

@@ -0,0 +1,652 @@
+################################################################
+#
+#  Copyright notice
+#
+#  (c) 2008 Dr. Boris Neubert (omega@online.de)
+#
+#  This script 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.
+#
+#  The GNU General Public License can be found at
+#  http://www.gnu.org/copyleft/gpl.html.
+#  A copy is found in the textfile GPL.txt and important notices to the license
+#  from the author is found in LICENSE.txt distributed with these scripts.
+#
+#  This script 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.
+#
+#  This copyright notice MUST APPEAR in all copies of the script!
+#
+################################################################
+# $Id: 20_X10.pm 10401 2016-01-07 19:26:18Z borisneubert $
+
+
+#
+# Internals introduced in this module:
+#	MODEL	distinguish between different X10 device types
+#	BRIGHT	brightness level of dimmer devices in units of microdims (0..210)
+#
+# Readings introduced in this module:
+#	state	function and argument of last command
+#	onoff	inherited from switch interface (0= on, 1= off)
+#	dimmer	inherited from dimmer interface (0= dark, 100= bright)
+#
+# Setters introduced in this module:
+#	on	inherited from switch interface
+#	off	inherited from switch interface
+#	dimmer  inherited from dimmer interface (0= dark, 100= bright)
+#	dimdown	inherited from dimmer interface
+#	dimup	inherited from dimmer interface
+#
+
+package main;
+
+use strict;
+use warnings;
+
+my %functions  = (  ALL_UNITS_OFF           => "all_units_off",
+                    ALL_LIGHTS_ON           => "all_lights_on",
+                    ON                      => "on",
+                    OFF                     => "off",
+                    DIM                     => "dimdown",
+                    BRIGHT                  => "dimup",
+                    ALL_LIGHTS_OFF          => "all_lights_off",
+                    EXTENDED_CODE           => "",
+                    HAIL_REQUEST            => "",
+                    HAIL_ACK                => "",
+                    PRESET_DIM1             => "",
+                    PRESET_DIM2             => "",
+                    EXTENDED_DATA_TRANSFER  => "",
+                    STATUS_ON               => "",
+                    STATUS_OFF              => "",
+                    STATUS_REQUEST          => "",
+                );
+
+my %snoitcnuf;  # the reverse of the above
+
+my %functions_rewrite = ( "all_units_off"  => "off",
+                          "all_lights_on"  => "on",
+                          "all_lights_off" => "off",
+                        );
+
+my %functions_snd = qw(  ON  0010
+                         OFF 0011
+                         DIM 0100
+                         BRIGHT 0101 );
+
+my %housecodes_snd = qw(A 0110  B 1110  C 0010  D 1010
+                        E 0001  F 1001  G 0101  H 1101
+                        I 0111  J 1111  K 0011  L 1011
+                        M 0000  N 1000  O 0100  P 1100);
+
+my %unitcodes_snd  = qw( 1 0110   2 1110   3 0010   4 1010
+                         5 0001   6 1001   7 0101   8 1101
+                         9 0111  10 1111  11 0011  12 1011
+                        13 0000  14 1000  15 0100  16 1100);
+
+
+my %functions_set = ( "on"      => 0,
+                      "off"     => 0,
+                      "dimup"   => 1,
+                      "dimdown" => 1,
+		      "dimto"   => 1,
+                      "on-till" => 1,
+		      "on-for-timer" => 1,
+                    );
+
+
+my %models = (
+    lm12	=> 'dimmer',
+    lm15        => 'switch',
+    am12        => 'switch',
+    tm13        => 'switch',
+);
+
+my %interfaces = (
+    lm12        => 'dimmer',
+    lm15        => 'switch_passive',
+    am12        => 'switch_passive',
+    tm13        => 'switch_passive',
+);
+
+my @lampmodules = ('lm12','lm15'); # lamp modules
+
+
+sub
+X10_Initialize($)
+{
+  my ($hash) = @_;
+
+  foreach my $k (keys %functions) {
+    $snoitcnuf{$functions{$k}}= $k;
+  }
+
+  $hash->{Match}     = "^X10:[A-P];";
+  $hash->{SetFn}     = "X10_Set";
+  $hash->{StateFn}   = "X10_SetState";
+  $hash->{DefFn}     = "X10_Define";
+  $hash->{UndefFn}   = "X10_Undef";
+  $hash->{ParseFn}   = "X10_Parse";
+  $hash->{AttrList}  = "IODev do_not_notify:1,0 " .
+                       "dummy:1,0 showtime:1,0 model:lm12,lm15,am12,tm13";
+
+}
+
+#####################################
+sub
+X10_SetState($$$$)
+{
+  my ($hash, $tim, $vt, $val) = @_;
+  return undef;
+}
+
+#############################
+sub
+X10_StateMachine($$$$)
+{
+  my($hash, $time, $function, $argument)= @_;
+
+  # the following changes between (onoff,bright) states were
+  # experimentally observed for a Busch Timac Ferndimmer 2265
+  # bright and argument are measured in brightness steps
+  # from 0 (0%) to 210 (100%).
+  # for convenience, we connect the off state with a 210 bright state
+  #
+  #	initial		on		off		dimup d 	dimdown d
+  #	-------------------------------------------------------------------------
+  #	(on,x)	  ->	(on,x)		(off,210)	(on,x+d)	(on,x-d)
+  #	(off,210) ->	(on,210)	(off,210)	(on,210)	(on,210-d)
+
+  my $onoff;
+  my $bright;
+
+
+  if(defined($hash->{ONOFF})) {
+    $onoff= $hash->{ONOFF};
+  } else {
+    $onoff= 0; }
+  if(defined($hash->{BRIGHT})) {
+    $bright= $hash->{BRIGHT};
+  } else {
+    $bright= 0; }
+  #Log3 $hash, 1, $hash->{NAME} . " initial state ($onoff,$bright)";
+
+  if($onoff) {
+    # initial state (on,bright)
+    if($function eq "on") {
+    } elsif($function eq "off") {
+      $onoff= 0; $bright= 210;
+    } elsif($function eq "dimup") {
+      $bright+= $argument;
+      if($bright> 210) { $bright= 210 };
+    } elsif($function eq "dimdown") {
+      $bright-= $argument;
+      if($bright< 0) { $bright= 0 };
+    }
+  } else {
+    # initial state (off,bright)
+    if($function eq "on") {
+      $onoff= 1; $bright= 210;
+    } elsif($function eq "off") {
+      $onoff= 0; $bright= 210;
+    } elsif($function eq "dimup") {
+      $onoff= 1; $bright= 210;
+    } elsif($function eq "dimdown") {
+      $onoff= 1;
+      $bright= 210-$argument;
+      if($bright< 0) { $bright= 0 };
+    }
+  }
+  #Log3 $hash,  1, $hash->{NAME} . " final state ($onoff,$bright)";
+
+  $hash->{ONOFF}= $onoff;
+  $hash->{BRIGHT}= $bright;
+  $hash->{READINGS}{onoff}{TIME}= $time;
+  $hash->{READINGS}{onoff}{VAL}= $onoff;
+  $hash->{READINGS}{dimmer}{TIME}= $time;
+  $hash->{READINGS}{dimmer}{VAL}= int(1000.0*$bright/210.0+0.5)/10.0;
+}
+
+#############################
+sub
+X10_LevelToDims($)
+{
+  # 22= 100%
+  my ($level)= @_;
+  my $dim= int(22*$level/100.0+0.5);
+  return $dim;
+}
+
+
+#############################
+sub
+X10_Do_On_Till($@)
+{
+  my ($hash, @a) = @_;
+  return "Timespec (HH:MM[:SS]) needed for the on-till command" if(@a != 3);
+
+  my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
+  return $err if($err);
+
+  my @lt = localtime;
+  my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
+  my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
+  if($hms_now ge $hms_till) {
+    Log3 $hash, 4, "on-till: won't switch as now ($hms_now) is later than $hms_till";
+    return "";
+  }
+
+  if($modules{X10}{ldata}{$a[0]}) {
+    CommandDelete(undef, $a[0] . "_timer");
+    delete $modules{FS20}{ldata}{$a[0]};
+  }
+  $modules{X10}{ldata}{$a[0]} = "$hms_till";
+
+  my @b = ($a[0], "on");
+  X10_Set($hash, @b);
+  CommandDefine(undef, $hash->{NAME} . "_timer at $hms_till set $a[0] off");
+
+}
+#############################
+sub
+X10_Do_On_For_Timer($@)
+{
+  my ($hash, @a) = @_;
+  return "Timespec (HH:MM[:SS]) needed for the on-for-timer command" if(@a != 3);
+
+  my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]);
+  return $err if($err);
+
+  my $hms_for_timer = sprintf("+%02d:%02d:%02d", $hr, $min, $sec);
+
+  if($modules{X10}{ldata}{$a[0]}) {
+    CommandDelete(undef, $a[0] . "_timer");
+    delete $modules{FS20}{ldata}{$a[0]};
+  }
+  $modules{X10}{ldata}{$a[0]} = "$hms_for_timer";
+
+  my @b = ($a[0], "on");
+  X10_Set($hash, @b);
+  CommandDefine(undef, $hash->{NAME} . "_timer at $hms_for_timer set $a[0] off");
+
+}
+
+###################################
+
+sub
+X11_Write($$$)
+{
+  my ($hash, $function, $dim)= @_;
+  my $name     = $hash->{NAME};
+  my $housecode= $hash->{HOUSE};
+  my $unitcode = $hash->{UNIT};
+  my $x10func  = $snoitcnuf{$function};
+  undef $function; # do not use after this point
+  my $prefix= "X10 device $name:";
+
+  Log3 $name, 5, "$prefix sending X10:$housecode;$unitcode;$x10func $dim";
+
+  my ($hc_b, $hu_b, $hf_b);
+  my ($hc, $hu, $hf);
+
+  # Header:Code, Address
+  $hc_b  = "00000100"; # 0x04
+  $hc    = pack("B8", $hc_b);
+  $hu_b  = $housecodes_snd{$housecode} . $unitcodes_snd{$unitcode};
+  $hu    = pack("B8", $hu_b);
+  IOWrite($hash, $hc, $hu);
+
+  # Header:Code, Function
+  $hc_b   = substr(unpack('B8', pack('C', $dim)), 3) . # dim, 0..22
+            "110";                                     # always 110
+  $hc     = pack("B8", $hc_b);
+  $hf_b   = $housecodes_snd{$housecode} . $functions_snd{$x10func};
+  $hf     = pack("B8", $hf_b);
+  IOWrite($hash, $hc, $hf);
+}
+
+###################################
+sub
+X10_Set($@)
+{
+  my ($hash, @a) = @_;
+  my $ret = undef;
+  my $na = int(@a);
+
+  # initialization and sanity checks
+  return "no set value specified" if($na < 2);
+
+  my $name= $hash->{NAME};
+  my $function= $a[1];
+  my $nrparams= $functions_set{$function};
+  return "Unknown argument $function, choose one of " .
+          join(" ", sort keys %functions_set) if(!defined($nrparams));
+  return "Wrong number of parameters"  if($na != 2+$nrparams);
+
+  # special for on-till
+  return X10_Do_On_Till($hash, @a) if($function eq "on-till");
+
+  # special for on-for-timer
+  return X10_Do_On_For_Timer($hash, @a) if($function eq "on-for-timer");
+
+
+
+  # argument evaluation
+  my $model= $hash->{MODEL};
+
+  my $dim= 0;
+  if($function =~ m/^dim/) {
+    return "Cannot dim $name (model $model)" if($models{$model} ne "dimmer");
+    my $arg= $a[2];
+    return "Wrong argument $arg, use 0..100" if($arg !~ m/^[0-9]{1,3}$/);
+    return "Wrong argument $arg, use 0..100" if($arg>100);
+    if($function eq "dimto") {
+      # translate dimmer command to dimup/dimdown command
+      my $bright= 210;
+      if(defined($hash->{BRIGHT})) { $bright= $hash->{BRIGHT} };
+      $arg= $arg-100.0*$bright/210.0;
+      if($arg> 0) {
+	$function= "dimup";
+	$dim= X10_LevelToDims($arg);
+      } else {
+	$function= "dimdown";
+	$dim= X10_LevelToDims(-$arg);
+      }
+    } else {
+      $dim= X10_LevelToDims($arg);
+    }
+
+    # the meaning of $dim= 0, 1 is unclear
+    # if we encounter the need for dimming by such a small amount, we
+    # ignore it
+    if($dim< 2) { return "Dim amount too small" };
+  };
+
+  # send command to CM11
+  X11_Write($hash, $function, $dim) if(!IsDummy($a[0]));
+
+  my $v = join(" ", @a);
+  Log3 $a[0], 2, "X10 set $v";
+  (undef, $v) = split(" ", $v, 2);      # Not interested in the name...
+
+  my $tn = TimeNow();
+
+  $hash->{CHANGED}[0] = $v;
+  $hash->{STATE} = $v;
+  $hash->{READINGS}{state}{TIME} = $tn;
+  $hash->{READINGS}{state}{VAL} = $v;
+  X10_StateMachine($hash, $tn, $function, int(210.0*$dim/22.0+0.5));
+
+  return undef;
+}
+
+#############################
+sub
+X10_Define($$)
+{
+  my ($hash, $def) = @_;
+  my @a = split("[ \t][ \t]*", $def);
+
+  return "wrong syntax: define <name> X10 model housecode unitcode"
+                if(int(@a)!= 5);
+
+  my $model= $a[2];
+  return "Define $a[0]: wrong model: specify one of " .
+            join ",", sort keys %models
+                if(!grep { $_ eq $model} keys %models);
+
+  my $housecode = $a[3];
+  return "Define $a[0]: wrong housecode format: specify a value ".
+         "from A to P"
+  		if($housecode !~ m/^[A-P]$/i);
+
+  my $unitcode = $a[4];
+  return "Define $a[0]: wrong unitcode format: specify a value " .
+         "from 1 to 16"
+  		if( ($unitcode<1) || ($unitcode>16) );
+
+
+  $hash->{MODEL}  = $model;
+  $hash->{HOUSE}  = $housecode;
+  $hash->{UNIT}   = $unitcode;
+
+  $hash->{internals}{interfaces}= $interfaces{$model};
+
+  if(defined($modules{X10}{defptr}{$housecode}{$unitcode})) {
+    return "Error: duplicate X10 device $housecode $unitcode definition " .
+           $hash->{NAME} . " (previous: " .
+           $modules{X10}{defptr}{$housecode}{$unitcode}->{NAME} .")";
+  }
+
+  $modules{X10}{defptr}{$housecode}{$unitcode}= $hash;
+
+  AssignIoPort($hash);
+}
+
+#############################
+sub
+X10_Undef($$)
+{
+  my ($hash, $name) = @_;
+  if( defined($hash->{HOUSE}) && defined($hash->{UNIT}) ) {
+    delete($modules{X10}{defptr}{$hash->{HOUSE}}{$hash->{UNIT}});
+  }
+  return undef;
+}
+
+#############################
+sub
+X10_Parse($$)
+{
+  my ($hash, $msg) = @_;
+
+  # message example: X10:N;1 12;OFF
+  (undef, $msg)= split /:/, $msg, 2; # strip off "X10"
+  my ($housecode,$unitcodes,$command)= split /;/, $msg, 4;
+
+  my @list;   # list of selected devices
+
+  #
+  # command evaluation
+  #
+  my ($x10func,$arg)= split / /, $command, 2;
+  my $function= $functions{$x10func}; # translate, eg BRIGHT -> dimup
+  undef $x10func; # do not use after this point
+
+  # the following code sequence converts an all on/off command into
+  # a sequence of simple on/off commands for all defined devices
+  my $all_lights= ($function=~ m/^all_lights_/);
+  my $all_units= ($function=~ m/^all_units_/);
+  if($all_lights || $all_units) {
+    $function= $functions_rewrite{$function}; # translate, all_lights_on -> on
+    $unitcodes= "";
+    foreach my $unitcode (keys %{ $modules{X10}{defptr}{$housecode} } ) {
+      my $h= $modules{X10}{defptr}{$housecode}{$unitcode};
+      my $islampmodule= grep { $_ eq $h->{MODEL} } @lampmodules;
+      if($all_units || $islampmodule ) {
+        $unitcodes.= " " if($unitcodes ne "");
+        $unitcodes.= $h->{UNIT};
+      }
+    }
+    # no units for that housecode
+    if($unitcodes eq "") {
+      Log3 $hash, 3, "X10 No units with housecode $housecode, command $command, " .
+             "please define one";
+      push(@list,
+             "UNDEFINED X10_$housecode X10 lm15 $housecode ?");
+      return @list;
+    }
+  }
+
+  # apply to each unit in turn
+  my @unitcodes= split / /, $unitcodes;
+
+  if(!int(@unitcodes)) {
+    # command without unitcodes, this happens when a single on/off is sent
+    # but no unit was previously selected
+    Log3 $hash, 3, "X10 No unit selected for housecode $housecode, command $command";
+    push(@list,
+             "UNDEFINED X10_$housecode X10 lm15 $housecode ?");
+    return @list;
+  }
+
+  # function rewriting
+  my $value= $function;
+  return @list if($value eq "");  # function not evaluated
+
+  # function determined, add argument
+  if( defined($arg) ) {
+    # received dims from 0..210
+    my $dim= $arg;
+    $value = "$value $dim" ;
+  }
+
+  my $unknown_unitcodes= '';
+  my $tn= TimeNow();
+  foreach my $unitcode (@unitcodes) {
+    my $h= $modules{X10}{defptr}{$housecode}{$unitcode};
+    if($h) {
+        my $name= $h->{NAME};
+        $h->{CHANGED}[0] = $value;
+        $h->{STATE} = $value;
+        $h->{READINGS}{state}{TIME} = $tn;
+        $h->{READINGS}{state}{VAL} = $value;
+	X10_StateMachine($h, $tn, $function, $arg);
+        Log3 $hash, 2, "X10 $name $value";
+        push(@list, $name);
+    } else {
+        Log3 $hash, 3, "X10 Unknown device $housecode $unitcode, command $command, " .
+               "please define it";
+        push(@list,
+             "UNDEFINED X10_$housecode X10 lm15 $housecode $unitcode");
+    }
+  }
+  return @list;
+
+}
+
+
+1;
+
+=pod
+=begin html
+
+<a name="X10"></a>
+<h3>X10</h3>
+<ul>
+  <a name="X10define"></a>
+  <b>Define</b>
+  <ul>
+    <code>define &lt;name&gt; X10 &lt;model&gt; &lt;housecode&gt;
+          &lt;unitcode&gt;</code>
+    <br><br>
+
+   Defines an X10 device via its model, housecode and unitcode.<br><br>
+
+   Notes:
+   <ul>
+   <li><code>&lt;model&gt;</code> is one of
+      <ul>
+        <li><code>lm12</code>: lamp module, dimmable</li>
+        <li><code>lm15</code>: lamp module, not dimmable</li>
+        <li><code>am12</code>: appliance module, not dimmable</li>
+        <li><code>tm12</code>: tranceiver module, not dimmable. Its
+            unitcode is 1.</li>
+      </ul>
+      Model determines whether a dim command is reasonable to be sent
+      or not.</li>
+   <li><code>&lt;housecode&gt;</code> ranges from A to P.</li>
+   <li><code>&lt;unitcode&gt;</code> ranges from 1 to 16.</li>
+   </ul>
+   <br>
+
+    Examples:
+    <ul>
+      <code>define lamp1 X10 lm12 N 10</code><br>
+      <code>define pump X10 am12 B 7</code><br>
+      <code>define lamp2 X10 lm15 N 11</code><br>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="X10set"></a>
+  <b>Set </b>
+  <ul>
+    <code>set &lt;name&gt; &lt;value&gt; [&lt;argument&gt]</code>
+    <br><br>
+    where <code>value</code> is one of:<br>
+    <pre>
+    dimdown           # requires argument, see the note
+    dimup             # requires argument, see the note
+    off
+    on
+    on-till           # Special, see the note
+    on-for-timer      # Special, see the note
+    </pre>
+    Examples:
+    <ul>
+      <code>set lamp1 dimup 10</code><br>
+      <code>set lamp1,lamp2 off</code><br>
+      <code>set pump off</code><br>
+      <code>set lamp2 on-till 19:59</code><br>
+      <code>set lamp2 on-for-timer 00:02:30</code><br>
+    </ul>
+    <br>
+    Notes:
+    <ul>
+      <li>Only switching and dimming are supported by now.</li>
+      <li>Dimming is valid only for a dimmable device as specified by
+          the <code>model</code> argument in its <code>define</code>
+          statement.</li>
+      <li>An X10 device has 210 discrete brightness levels. If you use a
+          X10 sender, e.g. a remote control or a wall switch to dim, a
+          brightness step is 100%/210.</li>
+      <li><code>dimdown</code> and <code>dimup</code> take a number in the
+          range from 0 to 22 as argument. It is assumed that argument 1 is
+          a 1% brightness change (microdim) and arguments 2 to 22 are
+          10%..100% brightness changes. The meaning of argument 0 is
+          unclear.</li>
+      <li>This currently leads to some confusion in the logs as the
+          <code>dimdown</code> and <code>dimup</code> codes are logged with
+          different meaning of the arguments depending on whether the commands
+          were sent from the PC or from a remote control or a wall switch.</li>
+      <li><code>dimdown</code> and <code>dimup</code> from on and off states may
+          have unexpected results. This seems to be a feature of the X10
+          devices.</li>
+      <li><code>on-till</code> requires an absolute time in the "at" format
+          (HH:MM:SS, HH:MM) or { &lt;perl code&gt; }, where the perl code
+          returns a time specification).
+          If the current time is greater than the specified time, then the
+          command is ignored, else an "on" command is generated, and for the
+          given "till-time" an off command is scheduleld via the at command.
+          </li>
+      <li><code>on-for-timer</code> requires a relative time in the "at" format
+          (HH:MM:SS, HH:MM) or { &lt;perl code&gt; }, where the perl code
+          returns a time specification).
+          </li>
+    </ul>
+  </ul>
+  <br>
+
+  <a name="X10get"></a>
+  <b>Get</b> <ul>N/A</ul><br>
+
+  <a name="X10attr"></a>
+  <b>Attributes</b>
+  <ul>
+  <li><a href="#do_not_notify">do_not_notify</a></li>
+    <li><a href="#attrdummy">dummy</a></li>
+    <li><a href="#showtime">showtime</a></li>
+    <li><a href="#model">model</a> (lm12,lm15,am12,tm13)</li>
+    <li><a href="#IODev">IODev</a></li><br>
+    <li><a href="#eventMap">eventMap</a></li><br>
+  </ul>
+  <br>
+</ul>
+
+=end html
+=cut

ファイルの差分が大きいため隠しています
+ 1032 - 0
fhem/core/FHEM/21_N4HMODULE.pm


ファイルの差分が大きいため隠しています
+ 1856 - 0
fhem/core/FHEM/21_OWAD.pm


ファイルの差分が大きいため隠しています
+ 2229 - 0
fhem/core/FHEM/21_OWCOUNT.pm


+ 539 - 0
fhem/core/FHEM/21_OWID.pm

@@ -0,0 +1,539 @@
+########################################################################################
+#
+# OWID.pm
+#
+# FHEM module to commmunicate with general 1-Wire ID-ROMS
+#
+# Prof. Dr. Peter A. Henning
+# Norbert Truchsess
+#
+# $Id: 21_OWID.pm 11196 2016-04-06 18:56:28Z pahenning $
+#
+########################################################################################
+#
+# define <name> OWID <FAM_ID> <ROM_ID> [interval] or OWID <FAM_ID>.<ROM_ID> [interval] 
+#
+# where <name> may be replaced by any name string 
+#   
+#       <FAM_ID> is a 2 character (1 byte) 1-Wire Family ID
+#  
+#       <ROM_ID> is a 12 character (6 byte) 1-Wire ROM ID 
+#                without Family ID, e.g. A2D90D000800 
+#       [interval] is an optional query interval in seconds
+#
+# set <name> interval => set query interval for checking presence
+#
+# get <name> id       => FAM_ID.ROM_ID.CRC 
+# get <name> present  => 1 if device present, 0 if not
+# get <name> version  => OWX version number
+#
+#
+########################################################################################
+#
+#  This programm 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.
+#
+#  The GNU General Public License can be found at
+#  http://www.gnu.org/copyleft/gpl.html.
+#  A copy is found in the textfile GPL.txt and important notices to the license
+#  from the author is found in LICENSE.txt distributed with these scripts.
+#
+#  This script 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.
+#
+########################################################################################
+package main;
+
+use vars qw{%attr %defs %modules $readingFnAttributes $init_done};
+use Time::HiRes qw(gettimeofday);
+
+use strict;
+use warnings;
+
+#add FHEM/lib to @INC if it is not already included. Should rather be in fhem.pl than here though...
+BEGIN {
+  if (!grep(/FHEM\/lib$/,@INC)) {
+    foreach my $inc (grep(/FHEM$/,@INC)) {
+      push @INC,$inc."/lib";
+    };
+  };
+};
+
+use GPUtils qw(:all);
+use ProtoThreads;
+no warnings 'deprecated';
+sub Log3($$$);
+
+my $owx_version="6.01";
+#-- declare variables
+my %gets = (
+  "present"     => "",
+  "interval"    => "",
+  "id"          => "",
+  "version"     => ""
+);
+my %sets    = (
+  "interval"    => ""
+);
+my %updates = (
+ "present"    => ""
+);
+ 
+########################################################################################
+#
+# The following subroutines are independent of the bus interface
+#
+# Prefix = OWID
+#
+########################################################################################
+#
+# OWID_Initialize
+#
+# Parameter hash = hash of device addressed
+#
+########################################################################################
+
+sub OWID_Initialize ($) {
+  my ($hash) = @_;
+
+  $hash->{DefFn}    = "OWID_Define";
+  $hash->{UndefFn}  = "OWID_Undef";
+  $hash->{GetFn}    = "OWID_Get";
+  $hash->{SetFn}    = "OWID_Set";
+  $hash->{AttrFn}   = "OWID_Attr";
+  $hash->{NotifyFn} = "OWID_Notify";
+  $hash->{InitFn}   = "OWID_Init";
+  $hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 model interval".
+                      $readingFnAttributes;
+
+  #--make sure OWX is loaded so OWX_CRC is available if running with OWServer
+  main::LoadModule("OWX");	
+}
+
+#########################################################################################
+#
+# OWID_Define - Implements DefFn function
+# 
+# Parameter hash = hash of device addressed, def = definition string
+#
+#########################################################################################
+
+sub OWID_Define ($$) {
+  my ($hash, $def) = @_;
+  
+  #-- define <name> OWID <FAM_ID> <ROM_ID>
+  my @a = split("[ \t][ \t]*", $def);
+  
+  my ($name,$interval,$model,$fam,$id,$crc,$ret);
+  
+  #-- default
+  $name          = $a[0];
+  $interval      = 300;
+  $ret           = "";
+
+  #-- check syntax
+  return "OWID: Wrong syntax, must be define <name> OWID [<model>] <id> [interval] or OWAD <fam>.<id> [interval]"
+       if(int(@a) < 2 || int(@a) > 5);
+          
+  #-- different types of definition allowed
+  my $a2 = $a[2];
+  my $a3 = defined($a[3]) ? $a[3] : "";
+  #-- no model, 2+12 characters
+  if(  $a2 =~ m/^[0-9|a-f|A-F]{2}\.[0-9|a-f|A-F]{12}$/ ) {
+    $fam   = substr($a[2],0,2);
+    $id    = substr($a[2],3);
+    if(int(@a)>=4) { $interval = $a[3]; }
+    if( $fam eq "01" ){
+      $model = "DS2401";
+      CommandAttr (undef,"$name model DS2401"); 
+    }elsif( $fam eq "09" ){
+      $model = "DS2502";
+      CommandAttr (undef,"$name model DS2502"); 
+    }else{
+      $model = "unknown";
+      CommandAttr (undef,"$name model unknown"); 
+    }
+  #-- model or family id, 12 characters
+  } elsif(  $a3 =~ m/^[0-9|a-f|A-F]{12}$/ ) {
+    $id  = $a[3];
+    if(int(@a)>=5) { $interval = $a[4]; }
+    #-- family id, 2 characters
+    if(  $a2 =~ m/^[0-9|a-f|A-F]{2}$/ ) {
+      $fam   = $a[2];
+      if( $fam eq "01" ){
+        $model = "DS2401";
+        CommandAttr (undef,"$name model DS2401"); 
+      }elsif( $fam eq "09" ){
+        $model = "DS2502";
+        CommandAttr (undef,"$name model DS2502"); 
+      }else{
+        $model = "unknown";
+        CommandAttr (undef,"$name model unknown"); 
+      }
+    }else{
+      $model   = $a[2];
+      if( $model eq "DS2401" ){
+        $fam = "01";
+        CommandAttr (undef,"$name model DS2401"); 
+      }elsif( $model eq "DS2502" ){
+        $fam = "09";
+        CommandAttr (undef,"$name model DS2502"); 
+      }else{
+        return "OWID: Unknown 1-Wire device model $model";
+      }
+    }
+  } else {    
+    return "OWID: $a[0] ID $a[2] invalid, specify a 12 or 2.12 digit value";
+  }
+  
+  #-- determine CRC Code 
+  $crc = sprintf("%02X",OWX_CRC($fam.".".$id."00"));
+  
+  #-- Define device internals
+  $hash->{ROM_ID}     = "$fam.$id.$crc";
+  $hash->{OW_ID}      = $id;
+  $hash->{OW_FAMILY}  = $fam;
+  $hash->{PRESENT}    = 0;
+  $hash->{INTERVAL}   = $interval;
+  
+  #-- Couple to I/O device
+  AssignIoPort($hash);
+  if( !defined($hash->{IODev}) or !defined($hash->{IODev}->{NAME}) ){
+    return "OWID: Warning, no 1-Wire I/O device found for $name.";
+  } else {
+    $hash->{ASYNC} = $hash->{IODev}->{TYPE} eq "OWX_ASYNC" ? 1 : 0; #-- false for now
+  }
+
+  $modules{OWID}{defptr}{$id} = $hash;
+  #--
+  readingsSingleUpdate($hash,"state","Defined",1);
+  Log3 $name,1, "OWID:     Device $name defined."; 
+
+  $hash->{NOTIFYDEV} = "global";
+
+  if ($init_done) {
+    return OWID_Init($hash);
+  }
+  return undef;
+}
+
+#########################################################################################
+#
+# OWID_Notify - Implements NotifyFn function
+# 
+# Parameter hash = hash of device addressed, dev = device name
+#
+#########################################################################################
+
+sub OWID_Notify ($$) {
+  my ($hash,$dev) = @_;
+  if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
+    OWID_Init($hash);
+  } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
+  }
+}
+
+#########################################################################################
+#
+# OWID_Init - Implements InitFn function
+# 
+# Parameter hash = hash of device addressed
+#
+#########################################################################################
+
+sub OWID_Init ($) {
+  my ($hash)=@_;
+  #-- Start timer for updates
+  RemoveInternalTimer($hash);
+  InternalTimer(gettimeofday()+10, "OWID_GetValues", $hash, 0);
+  #--
+  readingsSingleUpdate($hash,"state","Initialized",1);
+  
+  if (! (defined AttrVal($hash->{NAME},"stateFormat",undef))) {
+    $main::attr{$hash->{NAME}}{"stateFormat"} = "{ReadingsVal(\$name,\"present\",0) ? \"present\" : \"not present\"}";
+  }
+   
+  return undef; 
+}
+
+#######################################################################################
+#
+# OWID_Attr - Set one attribute value for device
+#
+#  Parameter hash = hash of device addressed
+#            a = argument array
+#
+########################################################################################
+
+sub OWID_Attr(@) {
+  my ($do,$name,$key,$value) = @_;
+  
+  my $hash = $defs{$name};
+  my $ret;
+  
+  if ( $do eq "set") {
+    ARGUMENT_HANDLER: {
+      #-- interval modified at runtime
+      $key eq "interval" and do {
+        #-- check value
+        return "OWID: Set with short interval, must be > 1" if(int($value) < 1);
+        #-- update timer
+        $hash->{INTERVAL} = $value;
+        if ($init_done) {
+          RemoveInternalTimer($hash);
+          InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWID_GetValues", $hash, 0);
+        }
+        last;
+      };
+      $key eq "IODev" and do {
+        AssignIoPort($hash,$value);
+        if( defined($hash->{IODev}) ) {
+          $hash->{ASYNC} = $hash->{IODev}->{TYPE} eq "OWX_ASYNC" ? 1 : 0;
+          if ($init_done) {
+            OWID_Init($hash);
+          }
+        }
+        last;
+      }
+    }
+  }
+  return $ret;
+}
+
+########################################################################################
+#
+# OWID_Get - Implements GetFn function 
+#
+#  Parameter hash = hash of device addressed, a = argument array
+#
+########################################################################################
+
+sub OWID_Get($@) {
+  my ($hash, @a) = @_;
+  
+  my $reading = $a[1];
+  my $name    = $hash->{NAME};
+  my $model   = $hash->{OW_MODEL};
+  my $value   = undef;
+  my $ret     = "";
+  my $offset;
+  my $factor;
+
+   #-- check syntax
+  return "OWID: Get argument is missing @a"
+    if(int(@a) != 2);
+    
+  #-- check argument
+  return "OWID: Get with unknown argument $a[1], choose one of ".join(" ", sort keys %gets)
+    if(!defined($gets{$a[1]}));
+
+  #-- get id
+  if($a[1] eq "id") {
+    $value = $hash->{ROM_ID};
+     return "$name.id => $value";
+  } 
+  
+  #-- get interval
+  if($a[1] eq "interval") {
+    $value = $hash->{INTERVAL};
+     return "$name.interval => $value";
+  } 
+  
+  #-- get present
+  if($a[1] eq "present") {
+    #-- hash of the busmaster
+    my $master       = $hash->{IODev};
+    #-- asynchronous mode
+    if( $hash->{ASYNC} ){
+      eval {
+        OWX_ASYNC_RunToCompletion($hash,OWX_ASYNC_PT_Verify($hash));
+      };
+      return GP_Catch($@) if $@;
+      return "$name.present => ".ReadingsVal($name,"present","unknown");
+    } else {
+      $value = OWX_Verify($master,$hash->{ROM_ID});
+    }
+    if( $value == 0 ){
+      readingsSingleUpdate($hash,"present",0,$hash->{PRESENT}); 
+    } else {
+      readingsSingleUpdate($hash,"present",1,!$hash->{PRESENT}); 
+    }
+    $hash->{PRESENT} = $value;
+    return "$name.present => $value";
+  } 
+  
+  #-- get version
+  if( $a[1] eq "version") {
+    return "$name.version => $owx_version";
+  }
+}
+
+########################################################################################
+#
+# OWID_GetValues - Updates the reading from one device
+#
+#  Parameter hash = hash of device addressed
+#
+########################################################################################
+
+sub OWID_GetValues($) {
+  my $hash    = shift;
+  
+  my $name    = $hash->{NAME};
+  my $value   = 0;
+  my $ret     = "";
+  my $offset;
+  my $factor;
+  
+  #-- restart timer for updates
+  RemoveInternalTimer($hash);
+  InternalTimer(time()+$hash->{INTERVAL}, "OWID_GetValues", $hash, 0);
+  
+  #-- hash of the busmaster
+  my $master       = $hash->{IODev};
+  
+  if( $hash->{ASYNC} ){
+    eval {
+      OWX_ASYNC_Schedule($hash,OWX_ASYNC_PT_Verify($hash));
+    };
+    return GP_Catch($@) if $@;
+    return undef;
+  } else {
+    $value = OWX_Verify($master,$hash->{ROM_ID});
+  }
+
+  #-- generate an event only if presence has changed
+  if( $value == 0 ){
+    readingsSingleUpdate($hash,"present",0,$hash->{PRESENT}); 
+  } else {
+    readingsSingleUpdate($hash,"present",1,!$hash->{PRESENT}); 
+  }
+  $hash->{PRESENT} = $value;
+}
+
+#######################################################################################
+#
+# OWID_Set - Set one value for device
+#
+#  Parameter hash = hash of device addressed
+#            a = argument array
+#
+########################################################################################
+
+sub OWID_Set($@) {
+  my ($hash, @a) = @_;
+  
+  my $key     = $a[1];
+  my $value   = $a[2];
+  
+  #-- for the selector: which values are possible
+  if (@a == 2){
+    my $newkeys = join(" ", keys %sets);
+    return $newkeys ;    
+  }
+  
+  #-- check syntax
+  return "OWID: Set needs at least one parameter"
+    if( int(@a)<3 );
+  #-- check argument
+  if( !defined($sets{$a[1]}) ){
+        return "OWID: Set with unknown argument $a[1]";
+  }
+  
+  my $name    = $hash->{NAME};
+  
+  #-- set new timer interval
+  if($key eq "interval") {
+    # check value
+    return "OWID: Set with short interval, must be > 1"
+      if(int($value) < 1);
+    # update timer
+    $hash->{INTERVAL} = $value;
+    RemoveInternalTimer($hash);
+    InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWID_GetValues", $hash, 0);
+    return undef;
+  }
+}
+
+########################################################################################
+#
+# OWID_Undef - Implements UndefFn function
+#
+# Parameter hash = hash of device addressed
+#
+########################################################################################
+
+sub OWID_Undef ($) {
+  my ($hash) = @_;
+  delete($modules{OWID}{defptr}{$hash->{OW_ID}});
+  RemoveInternalTimer($hash);
+  return undef;
+}
+
+1;
+
+=pod
+=begin html
+
+ <a name="OWID"></a>
+        <h3>OWID</h3>
+        <p>FHEM module for 1-Wire devices that know only their unique ROM ID<br />
+            <br />This 1-Wire module works with the OWX interface module or with the OWServer interface module
+            Please define an <a href="#OWX">OWX</a> device or <a href="#OWServer">OWServer</a> device first. <br /></p>
+        <br /><h4>Example</h4><br />
+        <p>
+            <code>define ROM1 OWX_ID OWCOUNT 09.CE780F000000 10</code>
+            <br />
+        </p><br />
+        <a name="OWIDdefine"></a>
+        <h4>Define</h4>
+        <p>
+            <code>define &lt;name&gt; OWID &lt;fam&gt; &lt;id&gt; [&lt;interval&gt;]</code> or <br/>
+            <code>define &lt;name&gt; OWID &lt;fam&gt;.&lt;id&gt; [&lt;interval&gt;]</code>
+            <br /><br /> Define a 1-Wire device.<br /><br />
+        </p>
+        <ul>
+            <li>
+                <code>&lt;fam&gt;</code>
+                <br />2-character unique family id, see above 
+            </li>
+            <li>
+                <code>&lt;id&gt;</code>
+                <br />12-character unique ROM id of the converter device without family id and CRC
+                code 
+            </li>
+            <li>
+                <code>&lt;interval&gt;</code>
+                <br />Interval in seconds for checking the presence of the device. The default is 300 seconds. </li>
+        </ul>
+         <br />
+        <a name="OWIDset"></a>
+        <h4>Set</h4>
+        <ul>
+            <li><a name="owid_interval">
+                    <code>set &lt;name&gt; interval &lt;int&gt;</code></a><br />
+                    Interval in seconds for checking the presence of the device. The default is 300 seconds. </li>
+        </ul>
+        <br />
+        <a name="OWIDget"></a>
+        <h4>Get</h4>
+        <ul>
+            <li><a name="owid_id">
+                    <code>get &lt;name&gt; id</code></a>
+                <br /> Returns the full 1-Wire device id OW_FAMILY.ROM_ID.CRC </li>
+            <li><a name="owid_present">
+                    <code>get &lt;name&gt; present</code>
+                </a>
+                <br /> Returns 1 if this 1-Wire device is present, otherwise 0. </li>
+        </ul>
+                <h4>Attributes</h4>
+        <ul>
+            <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
+        </ul>
+        
+=end html
+=cut

+ 0 - 0
fhem/core/FHEM/21_OWLCD.pm


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません