diff --git a/CHANGED b/CHANGED index 4e03241a5..ab886942e 100644 --- a/CHANGED +++ b/CHANGED @@ -1,5 +1,9 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. - SVN + - feature: new modules JeeLink and PCA301 and for the ELV PCA 301 power + meter with a JeeLabs JeeLink as RF modem. The matching JeeNode + sketch can be found in .../contrib/36_PCA301-pcaSerial.zip + (by justme1968) - change: PRESENCE: changing ping method for Windows systems to "tcp" - feature: install FHEM as Windows service by T.E., see docs/HOWTO_Windows.txt - feature: new module 33_readingsGroup to display a collection of readings diff --git a/FHEM/36_JeeLink.pm b/FHEM/36_JeeLink.pm new file mode 100644 index 000000000..7834bf980 --- /dev/null +++ b/FHEM/36_JeeLink.pm @@ -0,0 +1,558 @@ + +# $Id$ + +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +sub JeeLink_Attr(@); +sub JeeLink_Clear($); +sub JeeLink_HandleWriteQueue($); +sub JeeLink_Parse($$$$); +sub JeeLink_Read($); +sub JeeLink_ReadAnswer($$$$); +sub JeeLink_Ready($); +sub JeeLink_Write($$); + +sub JeeLink_SimpleWrite(@); + +my $clientsJeeLink = ":PCA301:EC3000:RoomNode:"; + +my %matchListPCA301 = ( + "1:PCA301" => "^\\S+\\s+24", + "2:EC3000" => "^\\S+\\s+22", + "3:RoomNode" => "^\\S+\\s+11", +); + +sub +JeeLink_Initialize($) +{ + my ($hash) = @_; + + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + +# Provider + $hash->{ReadFn} = "JeeLink_Read"; + $hash->{WriteFn} = "JeeLink_Write"; + $hash->{ReadyFn} = "JeeLink_Ready"; + +# Normal devices + $hash->{DefFn} = "JeeLink_Define"; + $hash->{FingerprintFn} = "JeeLink_Fingerprint"; + $hash->{UndefFn} = "JeeLink_Undef"; + $hash->{GetFn} = "JeeLink_Get"; + $hash->{SetFn} = "JeeLink_Set"; + #$hash->{AttrFn} = "JeeLink_Attr"; + #$hash->{AttrList}= ""; + + $hash->{ShutdownFn} = "JeeLink_Shutdown"; +} +sub +JeeLink_Fingerprint($$) +{ +} + +##################################### +sub +JeeLink_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a != 3) { + my $msg = "wrong syntax: define JeeLink {devicename[\@baudrate] ". + "| devicename\@directio}"; + Log3 undef, 2, $msg; + return $msg; + } + + DevIo_CloseDev($hash); + + my $name = $a[0]; + + my $dev = $a[2]; + $dev .= "\@57600" if( $dev !~ m/\@/ ); + + $hash->{Clients} = $clientsJeeLink; + $hash->{MatchList} = \%matchListPCA301; + + $hash->{DeviceName} = $dev; + + $hash->{nonce} = 0; + + my $ret = DevIo_OpenDev($hash, 0, "JeeLink_DoInit"); + return $ret; +} + +##################################### +sub +JeeLink_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}; + } + } + + JeeLink_Shutdown($hash); + DevIo_CloseDev($hash); + return undef; +} + +##################################### +sub +JeeLink_Shutdown($) +{ + my ($hash) = @_; + ###JeeLink_SimpleWrite($hash, "X00"); + return undef; +} + +##################################### +sub +JeeLink_Set($@) +{ + my ($hash, @a) = @_; + + my $name = shift @a; + my $cmd = shift @a; + my $arg = join("", @a); + + my $list = "raw:noArg"; + return $list if( $cmd eq '?' ); + + if($cmd eq "raw") { + return "\"set JeeLink $cmd\" needs exactly one parameter" if(@_ != 4); + return "Expecting a even length hex number" if((length($arg)&1) == 1 || $arg !~ m/^[\dA-F]{12,}$/ ); + Log3 $name, 4, "set $name $cmd $arg"; + JeeLink_SimpleWrite($hash, $arg); + + } else { + return "Unknown argument $cmd, choose one of ".$list; + } + + return undef; +} + +##################################### +sub +JeeLink_Get($@) +{ + my ($hash, $name, $cmd ) = @_; + + my $list = "devices:noArg initJeeLink:noArg"; + + if( $cmd eq "devices" ) { + JeeLink_SimpleWrite($hash, "l"); + } elsif( $cmd eq "initJeeLink" ) { + JeeLink_SimpleWrite($hash, "0c"); + JeeLink_SimpleWrite($hash, "2c"); + } else { + return "Unknown argument $cmd, choose one of ".$list; + } + + return undef; +} + +sub +JeeLink_Clear($) +{ + my $hash = shift; + + # Clear the pipe + $hash->{RA_Timeout} = 0.1; + for(;;) { + my ($err, undef) = JeeLink_ReadAnswer($hash, "Clear", 0, undef); + last if($err && $err =~ m/^Timeout/); + } + delete($hash->{RA_Timeout}); +} + +##################################### +sub +JeeLink_DoInit($) +{ + my $hash = shift; + my $name = $hash->{NAME}; + my $err; + my $msg = undef; + + my $val; + + #JeeLink_Clear($hash); + + JeeLink_SimpleWrite($hash, "1a" ); # led on + JeeLink_SimpleWrite($hash, "1q" ); # quiet mode + JeeLink_SimpleWrite($hash, "0x" ); # hex mode + JeeLink_SimpleWrite($hash, "0a" ); # led off + + JeeLink_SimpleWrite($hash, "l"); # list known devices + + $hash->{STATE} = "Initialized"; + + # Reset the counter + delete($hash->{XMIT_TIME}); + delete($hash->{NR_CMD_LAST_H}); + return undef; +} + +##################################### +# This is a direct read for commands like get +# Anydata is used by read file to get the filesize +sub +JeeLink_ReadAnswer($$$$) +{ + my ($hash, $arg, $anydata, $regexp) = @_; + my $type = $hash->{TYPE}; + + return ("No FD", undef) + if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD}))); + + my ($mpandata, $rin) = ("", ''); + my $buf; + my $to = 3; # 3 seconds timeout + $to = $hash->{RA_Timeout} if($hash->{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(999); + 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("JeeLink_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($buf) { + Log3 $hash->{NAME}, 5, "JeeLink/RAW (ReadAnswer): $buf"; + $mpandata .= $buf; + } + + chop($mpandata); + chop($mpandata); + + return (undef, $mpandata) + } + +} + +##################################### +# Check if the 1% limit is reached and trigger notifies +sub +JeeLink_XmitLimitCheck($$) +{ + my ($hash,$fn) = @_; + my $now = time(); + + if(!$hash->{XMIT_TIME}) { + $hash->{XMIT_TIME}[0] = $now; + $hash->{NR_CMD_LAST_H} = 1; + return; + } + + my $nowM1h = $now-3600; + my @b = grep { $_ > $nowM1h } @{$hash->{XMIT_TIME}}; + + if(@b > 163) { # 163 comes from fs20. todo: verify if correct for JeeLink modulation + + my $name = $hash->{NAME}; + Log3 $name, 2, "JeeLink TRANSMIT LIMIT EXCEEDED"; + DoTrigger($name, "TRANSMIT LIMIT EXCEEDED"); + + } else { + + push(@b, $now); + + } + $hash->{XMIT_TIME} = \@b; + $hash->{NR_CMD_LAST_H} = int(@b); +} + +##################################### +sub +JeeLink_Write($$) +{ + my ($hash,$msg) = @_; + my $name = $hash->{NAME}; + + Log3 $name, 5, "$name sending $msg"; + + JeeLink_AddQueue($hash, $msg); + #JeeLink_SimpleWrite($hash, $msg); +} + +sub +JeeLink_SendFromQueue($$) +{ + my ($hash, $bstring) = @_; + my $name = $hash->{NAME}; + my $to = 0.05; + + if($bstring ne "") { + my $sp = AttrVal($name, "sendpool", undef); + if($sp) { # Is one of the JeeLink-fellows sending data? + my @fellows = split(",", $sp); + foreach my $f (@fellows) { + if($f ne $name && + $defs{$f} && + $defs{$f}{QUEUE} && + $defs{$f}{QUEUE}->[0] ne "") + { + unshift(@{$hash->{QUEUE}}, ""); + InternalTimer(gettimeofday()+$to, "JeeLink_HandleWriteQueue", $hash, 1); + return; + } + } + } + + JeeLink_XmitLimitCheck($hash,$bstring); + JeeLink_SimpleWrite($hash, $bstring); + } + + InternalTimer(gettimeofday()+$to, "JeeLink_HandleWriteQueue", $hash, 1); +} + +sub +JeeLink_AddQueue($$) +{ + my ($hash, $bstring) = @_; + if(!$hash->{QUEUE}) { + $hash->{QUEUE} = [ $bstring ]; + JeeLink_SendFromQueue($hash, $bstring); + + } else { + push(@{$hash->{QUEUE}}, $bstring); + } +} + +##################################### +sub +JeeLink_HandleWriteQueue($) +{ + my $hash = shift; + my $arr = $hash->{QUEUE}; + if(defined($arr) && @{$arr} > 0) { + shift(@{$arr}); + if(@{$arr} == 0) { + delete($hash->{QUEUE}); + return; + } + my $bstring = $arr->[0]; + if($bstring eq "") { + JeeLink_HandleWriteQueue($hash); + } else { + JeeLink_SendFromQueue($hash, $bstring); + } + } +} + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub +JeeLink_Read($) +{ + my ($hash) = @_; + + my $buf = DevIo_SimpleRead($hash); + return "" if(!defined($buf)); + + my $name = $hash->{NAME}; + + my $pandata = $hash->{PARTIAL}; + Log3 $name, 5, "JeeLink/RAW: $pandata/$buf"; + $pandata .= $buf; + + while($pandata =~ m/\n/) { + my $rmsg; + ($rmsg,$pandata) = split("\n", $pandata, 2); + $rmsg =~ s/\r//; + JeeLink_Parse($hash, $hash, $name, $rmsg) if($rmsg); + } + $hash->{PARTIAL} = $pandata; +} + +sub +JeeLink_Parse($$$$) +{ + my ($hash, $iohash, $name, $rmsg) = @_; + + my $dmsg = $rmsg; + #my $l = length($dmsg); + my $rssi; + #my $rssi = hex(substr($dmsg, 1, 2)); + #$rssi = ($rssi>=128 ? (($rssi-256)/2-74) : ($rssi/2-74)); + my $lqi; + #my $lqi = hex(substr($dmsg, 3, 2)); + #$dmsg = substr($dmsg, 6, $l-6); + #Log3, $name, 5, "$name: $dmsg $rssi $lqi"; + + next if(!$dmsg || length($dmsg) < 1); # Bogus messages + next if($dmsg =~ m/^-> ack/ ); # ignore send ack + + $hash->{"${name}_MSGCNT"}++; + $hash->{"${name}_TIME"} = TimeNow(); + $hash->{RAWMSG} = $rmsg; + my %addvals = (RAWMSG => $rmsg); + if(defined($rssi)) { + $hash->{RSSI} = $rssi; + $addvals{RSSI} = $rssi; + } + if(defined($lqi)) { + $hash->{LQI} = $lqi; + $addvals{LQI} = $lqi; + } + + if( $rmsg =~ m/(\S* )(\d+)(.*)/ ) { + my $node = $2 & 0x1F; #mask HDR -> it is handled by the skech + $dmsg = $1.$node.$3; + } + + Dispatch($hash, $dmsg, \%addvals); +} + + +##################################### +sub +JeeLink_Ready($) +{ + my ($hash) = @_; + + return DevIo_OpenDev($hash, 1, "JeeLink_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 +JeeLink_SimpleWrite(@) +{ + my ($hash, $msg, $nocr) = @_; + return if(!$hash); + + my $name = $hash->{NAME}; + Log3 $name, 5, "SW: $msg"; + + $msg .= "\n" unless($nocr); + + $hash->{USBDev}->write($msg) if($hash->{USBDev}); + syswrite($hash->{DIODev}, $msg) if($hash->{DIODev}); + + # Some linux installations are broken with 0.001, T01 returns no answer + select(undef, undef, undef, 0.01); +} + +sub +JeeLink_Attr(@) +{ + my @a = @_; + + return undef; +} + +1; + +=pod +=begin html + + +

JeeLink

+ + +=end html +=cut diff --git a/FHEM/36_PCA301.pm b/FHEM/36_PCA301.pm new file mode 100644 index 000000000..56f26219a --- /dev/null +++ b/FHEM/36_PCA301.pm @@ -0,0 +1,310 @@ + +# $Id: 34_PCA301.pm 3515 2013-07-28 09:00:56Z justme1968 $ +# +# TODO: + +package main; + +use strict; +use warnings; +use SetExtensions; + +sub PCA301_Parse($$); +sub PCA301_Send($$@); + +sub +PCA301_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^\\S+\\s+24"; + $hash->{SetFn} = "PCA301_Set"; + #$hash->{GetFn} = "PCA301_Get"; + $hash->{DefFn} = "PCA301_Define"; + $hash->{UndefFn} = "PCA301_Undef"; + $hash->{FingerprintFn} = "PCA301_Fingerprint"; + $hash->{ParseFn} = "PCA301_Parse"; + $hash->{AttrFn} = "PCA301_Attr"; + $hash->{AttrList} = "IODev". + " $readingFnAttributes"; +} + +sub +PCA301_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a != 4 ) { + my $msg = "wrong syntax: define PCA301 "; + Log3 undef, 2, $msg; + return $msg; + } + + $a[2] =~ m/^([\da-f]{6})$/i; + return "$a[2] is not a valid PCA301 address" if( !defined($1) ); + + $a[3] =~ m/^([\da-f]{2})$/i; + return "$a[3] is not a valid PCA301 channel" if( !defined($1) ); + + my $name = $a[0]; + my $addr = $a[2]; + my $channel = $a[3]; + + #return "$addr is not a 1 byte hex value" if( $addr !~ /^[\da-f]{2}$/i ); + #return "$addr is not an allowed address" if( $addr eq "00" ); + + return "PCA301 device $addr already used for $modules{PCA301}{defptr}{$addr}->{NAME}." if( $modules{PCA301}{defptr}{$addr} + && $modules{PCA301}{defptr}{$addr}->{NAME} ne $name ); + + $hash->{addr} = $addr; + $hash->{channel} = $channel; + + $modules{PCA301}{defptr}{$addr} = $hash; + + AssignIoPort($hash); + if(defined($hash->{IODev}->{NAME})) { + Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME}; + } else { + Log3 $name, 1, "$name: no I/O device"; + } + + $attr{$name}{devStateIcon} = 'on:on:toggle off:off:toggle .*:light_question:off' if( !defined( $attr{$name}{devStateIcon} ) ); + $attr{$name}{webCmd} = 'on:off:toggle:statusRequest' if( !defined( $attr{$name}{webCmd} ) ); + CommandAttr( undef, "$name userReadings consumptionTotal:consumption monotonic {ReadingsVal(\$name,'consumption',0)}" ) if( !defined( $attr{$name}{userReadings} ) ); + + #PCA301_Send($hash, $addr, "00" ); + + return undef; +} + +##################################### +sub +PCA301_Undef($$) +{ + my ($hash, $arg) = @_; + my $name = $hash->{NAME}; + my $addr = $hash->{addr}; + + delete( $modules{PCA301}{defptr}{$addr} ); + + return undef; +} + +##################################### +sub +PCA301_Set($@) +{ + my ($hash, $name, @aa) = @_; + + my $cnt = @aa; + + return "\"set $name\" needs at least one parameter" if($cnt < 1); + + my $cmd = $aa[0]; + my $arg = $aa[1]; + my $arg2 = $aa[2]; + my $arg3 = $aa[3]; + + my $list = "identify:noArg off:noArg on:noArg toggle:noArg reset:noArg statusRequest:noArg"; + + if( $cmd eq 'toggle' ) { + $cmd = ReadingsVal($name,"state","on") eq "off" ? "on" :"off"; + } + + if( $cmd eq 'off' ) { + readingsSingleUpdate($hash, "state", "set-$cmd", 1); + PCA301_Send( $hash, 0x05, 0x00 ); + } elsif( $cmd eq 'on' ) { + readingsSingleUpdate($hash, "state", "set-$cmd", 1); + PCA301_Send( $hash, 0x05, 0x01 ); + } elsif( $cmd eq 'statusRequest' ) { + readingsSingleUpdate($hash, "state", "set-$cmd", 1); + PCA301_Send( $hash, 0x04, 0x00 ); + } elsif( $cmd eq 'reset' ) { + readingsSingleUpdate($hash, "state", "set-$cmd", 1); + PCA301_Send( $hash, 0x04, 0x01 ); + } elsif( $cmd eq 'identify' ) { + PCA301_Send( $hash, 0x06, 0x00 ); + } else { + return SetExtensions($hash, $list, $name, @aa); + } + + return undef; +} + +##################################### +sub +PCA301_Get($@) +{ + my ($hash, $name, $cmd, @args) = @_; + + return "\"get $name\" needs at least one parameter" if(@_ < 3); + + my $list = ""; + + return "Unknown argument $cmd, choose one of $list"; +} + +sub +PCA301_Fingerprint($$) +{ + my ($name, $msg) = @_; + + return ( "", $msg ); +} + + +sub +PCA301_Parse($$) +{ + my ($hash, $msg) = @_; + my $name = $hash->{NAME}; + + #return undef if( $msg !~ m/^[\dA-F]{12,}$/ ); + + if( $msg =~ m/^L/ ) { + my @parts = split( ' ', substr($msg, 5), 4 ); + $msg = "OK 24 $parts[3]"; + } + + my( @bytes, $channel,$cmd,$addr,$data,$power,$consumption ); + if( $msg =~ m/^OK/ ) { + @bytes = split( ' ', substr($msg, 6) ); + + $channel = sprintf( "%02X", $bytes[0] ); + $cmd = $bytes[1]; + $addr = sprintf( "%02X%02X%02X", $bytes[2], $bytes[3], $bytes[4] ); + $data = $bytes[5]; + return "" if( $cmd == 0x04 && $bytes[6] == 170 && $bytes[7] == 170 && $bytes[8] == 170 && $bytes[9] == 170 ); # ignore commands from display unit + return "" if( $cmd == 0x05 && ( $bytes[6] != 170 || $bytes[7] != 170 || $bytes[8] != 170 || $bytes[9] != 170 ) ); # ignore commands not from the plug + } elsif ( $msg =~ m/^TX/ ) { + # ignore TX + return ""; + } else { + DoTrigger($name, "UNKNOWNCODE $msg"); + Log3 $name, 3, "$name: Unknown code $msg, help me!"; + return undef; + } + + my $raddr = $addr; + my $rhash = $modules{PCA301}{defptr}{$raddr}; + my $rname = $rhash?$rhash->{NAME}:$raddr; + + if( !$modules{PCA301}{defptr}{$raddr} ) { + Log3 $name, 3, "PCA301 Unknown device $rname, please define it"; + + return "UNDEFINED PCA301_$rname PCA301 $raddr $channel"; + } + + #CommandAttr( undef, "$rname userReadings consumptionTotal:consumption monotonic {ReadingsVal($rname,'consumption',0)}" ) if( !defined( $attr{$rname}{userReadings} ) ); + + my @list; + push(@list, $rname); + + $rhash->{PCA301_lastRcv} = TimeNow(); + + if( $cmd eq 0x04 ) { + my $state = $data==0x00?"off":"on"; + my $power = ($bytes[6]*256 + $bytes[7]) / 10.0; + my $consumption = ($bytes[8]*256 + $bytes[9]) / 100.0; + readingsBeginUpdate($rhash); + readingsBulkUpdate($rhash, "power", $power) if( $data != 0x00 ); + readingsBulkUpdate($rhash, "consumption", $consumption) if( $data != 0x00 ); + readingsBulkUpdate($rhash, "state", $state) if( Value($rname) ne $state ); + readingsEndUpdate($rhash,1); + } elsif( $cmd eq 0x05 ) { + my $state = $data==00?"off":"on"; + + readingsSingleUpdate($rhash, "state", $state, 1) + } + + return @list; +} +sub +PCA301_Send($$@) +{ + my ($hash, $cmd, $data) = @_; + + $hash->{PCA301_lastSend} = TimeNow(); + + my $msg = sprintf( "%i,%i,%i,%i,%i,%i,255,255,255,255s", hex($hash->{channel}), + $cmd, + hex(substr($hash->{addr},0,2)), hex(substr($hash->{addr},2,2)), hex(substr($hash->{addr},4,2)), + $data ); + + IOWrite( $hash, $msg ); +} + +sub +PCA301_Attr(@) +{ + my ($cmd, $name, $attrName, $attrVal) = @_; + + return undef; +} + +1; + +=pod +=begin html + + +

PCA301

+
    + + + The PCA301 is a RF controlled AC mains plug with integrated power meter functionality from ELV.

    + + It can be integrated in to FHEM via a JeeLink as the IODevice.

    + + The JeeNode sketch required for this module can be found in .../contrib/36_PCA301-pcaSerial.zip.

    + + + Define +
      + define <name> PCA301 <addr> <channel>
      +
      + addr is a 6 digit hex number to identify the PCA301 device. + channel is a 2 digit hex number to identify the PCA301 device.

      + Note: devices are autocreated on reception of the first message.
      +
    +
    + + + Set +
      +
    • on
    • +
    • off
    • +
    • identify
      + Blink the status led for ~5 seconds.
    • +
    • reset
      + Reset consumption counters
    • +
    • statusRequest
      + Request device status update.
    • +
    • set extensions are supported.
    • +

    + + + Get +
      +

    + + + Readings +
      +
    • power
    • +
    • consumption
    • +
    • consumptionTotal
      + will be created as a default user reading to have a continous consumption value that is not influenced + by the regualar reset or overflow of the normal consumption reading
    • +

    + + + Attributes +
      +

    +
+ +=end html +=cut diff --git a/MAINTAINER.txt b/MAINTAINER.txt index 4661775db..6fa20b9d0 100644 --- a/MAINTAINER.txt +++ b/MAINTAINER.txt @@ -89,6 +89,8 @@ FHEM/32_speedtest.pm justme1968 http://forum.fhem.de Sonstiges FHEM/34_panStamp.pm justme1968 http://forum.fhem.de Sonstiges Systeme FHEM/34_SWAP.pm justme1968 http://forum.fhem.de Sonstiges Systeme FHEM/35_SWAP_0000002200000003.pm justme1968 http://forum.fhem.de Sonstiges Systeme +FHEM/36_JeeLink.pm justme1968 http://forum.fhem.de Sonstiges Systeme +FHEM/36_PCA301.pm justme1968 http://forum.fhem.de Sonstiges Systeme FHEM/40_RFXCOM.pm wherzig http://forum.fhem.de RFXTRX FHEM/41_OREGON.pm wherzig http://forum.fhem.de Sonstiges FHEM/42_RFXMETER.pm wherzig http://forum.fhem.de RFXTRX diff --git a/contrib/arduino/36_PCA301-pcaSerial.zip b/contrib/arduino/36_PCA301-pcaSerial.zip new file mode 100644 index 000000000..4f8ac658f Binary files /dev/null and b/contrib/arduino/36_PCA301-pcaSerial.zip differ