################################################################################# # 45_Plugwise.pm # # FHEM Module for Plugwise # # Copyright (C) 2014 Stefan Guttmann # # 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. # # Info on protocol: # http://roheve.wordpress.com/2011/05/15/plugwise-protocol-analysis-part-3/ # http://www.domoticaforum.eu/viewtopic.php?f=39&t=4319&start=30 # # The GNU General Public License may also be found at http://www.gnu.org/licenses/gpl-2.0.html . # # define myPlugwise Plugwise /dev/ttyPlugwise # # ########################### # # $Id$ package main; use strict; use warnings; use Time::HiRes qw(gettimeofday); use Digest::CRC qw(crc); #use Math::Round; use Data::Dumper; my $Make2Channels = 1; my $testcount=-1; my $firstrun=0; my $device=0; my $data=""; my $toggle=0; my $status=""; my ($self)=(); my $lastcircle=""; my $buffer=""; my $LastTime=time; my $lastSync=-1; my @buffer=(); my $initdone=0; my %PW_gets = ( "features" => 'Z' ); my %PW_sets = ( "Scan_Circles" => " ", "reOpen" => " ", "syncTime" => " ", # "pwPairForSec" => "" ); my %PWType = ( "00" => "PW_Circle", "01" => "PW_Circle", "02" => "PW_Circle", "03" => "PW_Switch", "04" => "PW_Switch", "05" => "PW_Sense", "06" => "PW_Scan" ); sub PW_Read($); sub PW_Ready($); sub PW_Undef($$); sub PW_Write; sub PW_Parse($$$$); sub PW_DoInit($); sub Plugwise_Initialize($$) { my ($hash) = @_; $self = bless { _buf => '', baud => 115200, device => '', _awaiting_stick_response => 0 }; require "$attr{global}{modpath}/FHEM/DevIo.pm"; # Provider $hash->{Clients} = ":PW_Circle:PW_Scan:PW_Switch:PW_Sense:"; my %mc = ( "1:PW_Circle" => "^PW_Circle", "2:PW_Scan" => "^PW_Scan", "3:PW_Switch" => "^PW_Switch", "4:PW_Sense" => "^PW_Sense" ); $hash->{MatchList} = \%mc; # Normal devices $hash->{ReadFn} = "PW_Read"; $hash->{WriteFn} = "PW_Write"; $hash->{ReadyFn} = "PW_Ready"; $hash->{DefFn} = "PW_Define"; $hash->{UndefFn} = "PW_Undef"; $hash->{SetFn} = "PW_Set"; $hash->{GetFn} = "PW_Get"; $hash->{StateFn} = "PW_SetState"; $hash->{AttrList}= "do_not_notify:1,0 interval circlecount WattFormat showCom autosync ". $readingFnAttributes;; $hash->{ShutdownFn} = "PW_Shutdown"; } ##################################### sub PW_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $inter = 10; if (@a != 3 ) { my $msg = "wrong syntax: define Plugwise devicename"; Log3 $hash, 2, $msg; return $msg; } my $name = $a[0]; my $dev = $a[2]; $hash->{NAME}=$name; DevIo_CloseDev($hash); $firstrun=0; $hash->{DeviceName} = $dev; # if( $init_done ) { # $attr{$name}{room}="Plugwise"; # $attr{$name}{interval}=10; # $attr{$name}{circlecount}=50; # $attr{$name}{WattFormat}="%0.f"; # } my $ret = DevIo_OpenDev( $hash, 0, undef); InternalTimer(gettimeofday()+5, "PW_GetUpdate", $hash, 0); return undef; } sub PW_GetUpdate($) { my $hash = shift; my $name = $hash->{NAME}; my ($Sekunden, $Minuten, $Stunden, $Monatstag, $Monat, $Jahr, $Wochentag, $Jahrestag, $Sommerzeit) = localtime(time); my $n=0; if ($firstrun==2) {return undef}; InternalTimer(gettimeofday()+$attr{$name}{interval}, "PW_GetUpdate", $hash, 1); # Log 3,Dumper($hash->{helper}); if ($firstrun==0) { PW_DoInit($hash); return undef; } delete $self->{_waiting}; foreach ( keys %{ $hash->{helper}->{circles} } ) { $n=$_; if (defined $hash->{helper}->{circles}->{$n}->{lastContact}) { if (time>$hash->{helper}->{circles}->{$n}->{lastContact} +960) { Log3 $hash,3,"Set Circle $n offline"; my %xplmsg = ( schema => 'plugwise.basic', ); my $saddr=$hash->{helper}->{circles}->{$n}->{name}; $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type}; $xplmsg{type} = 'output'; $xplmsg{text}= 'offline'; $xplmsg{short} = $saddr; PW_Parse($hash, $hash, $hash->{NAME}, \%xplmsg); delete $hash->{helper}->{circles}->{$n}; } elsif (time > $hash->{helper}->{circles}->{$n}->{lastContact} +900) { command($hash,'history',$hash->{helper}->{circles}->{$n}->{name},4); Log3 $hash,4,"GetLog offline Circle $n"; } } } foreach ( keys %{ $self->{_plugwise}->{circles} } ) { $n=$_; if (!defined $self->{_plugwise}->{circles}->{$n}->{type}) {$self->{_plugwise}->{circles}->{$n}->{type}=""}; if ($self->{_plugwise}->{circles}->{$n}->{type} eq "" || !defined $self->{_plugwise}->{circles}->{$n}->{type}) { command($hash,'status',$n); } if (defined $attr{$name}{autosync}) { if ($attr{$name}{autosync}>0 && time > $lastSync+$attr{$name}{autosync}) { $lastSync=time; command($hash,'syncTime',$n); } } } } sub MyRead($$) { my ($hash, $msg) = @_; return if (!defined $msg->{dest} ); PW_Parse($hash, $hash, $hash->{NAME}, $msg) if defined $msg; } sub PW_Write { my ($hash,$reciever,$fn,$a) = @_; my $name = $hash->{NAME}; return if(!defined($fn)); my $msg = "$hash->{NAME} sending $fn"; $msg .= " to Adress $a" if defined $a; if ($fn =~ /(on|off)/) { command($hash,$fn,$reciever,$a); } elsif ($fn =~ /(syncTime|ping|removeNode|livepower|status)/) { command($hash,$fn,$reciever) } elsif ($fn eq "getLog") { command($hash,'history',$reciever,$a) if($a ne -1); } else { Log3 $hash,4,$msg; } return undef; } ##################################### sub PW_Undef($$) { my ($hash, $arg) = @_; $firstrun=2; DevIo_CloseDev($hash); return undef; } ##################################### sub PW_Shutdown($) { my ($hash) = @_; DevIo_CloseDev($hash); return undef; } #NeedsEdit ##################################### sub PW_Set($@) { my ($hash, @a) = @_; my $msg; my $name=$a[0]; my $reading= $a[1]; my $n=1; return "\"set X\" needs at least one argument" if ( @a < 2 ); if ($reading eq "myTest") { $testcount=0; } elsif ($reading eq "myTestOff") { $testcount=-1; } elsif(!$PW_sets{$reading}) { my @cList = keys %PW_sets; return "Unknown argument $reading, choose one of " . join(" ", @cList); } if ($reading eq "Scan_Circles") { PW_DoInit($hash); #query_connected_circles($hash); } elsif ($reading eq "syncTime") { foreach ( keys %{ $self->{_plugwise}->{circles} } ) { $n=$_; command($hash,'syncTime',$n); } } elsif ($reading eq "reOpen") { DevIo_CloseDev($hash); $hash->{ERRCNT} = 0; $firstrun=0; my $ret = DevIo_OpenDev( $hash, 0, undef); RemoveInternalTimer($hash); InternalTimer(gettimeofday()+2, "PW_GetUpdate", $hash, 0); return undef; } elsif ($reading eq "pwPairForSec") { # RemoveInternalTimer("pairTimer"); # InternalTimer(gettimeofday()+$value, "PW_Circle_OnOffTimer", 'pairTimer'), 1); Mywrite( $hash, "000701" . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} ) ,1 ); } } sub PW_Get($@) { my ( $hash, @a ) = @_; my $n=1; return "\"get X\" needs at least one argument" if ( @a < 2 ); my $name = shift @a; my $opt = shift @a; if(!$PW_gets{$opt}) { my @cList = keys %PW_gets; return "Unknown argument $opt, choose one of " . join(" ", @cList); } if($opt eq 'features') { foreach ( keys %{ $self->{_plugwise}->{circles} } ) { $n=$_; command($hash,'feature',$n); } return undef; } } #NeedsEdit ##################################### sub PW_SetState($$$$) { my ($hash, $tim, $vt, $val) = @_; return undef; } ##################################### sub PW_DoInit($) { my $hash = shift; Mywrite($hash,"000A",1); return undef; } ##################################### # called from the global loop, when the select for hash->{FD} reports data sub PW_Read($) { my ($hash) = @_; my $name = $hash->{NAME}; my $char; my $body; # read from serial device my $buf = DevIo_SimpleRead($hash); my $buf2= $buf; $buf2=~s/\r\n/\|/g; return "" if ( !defined($buf) ); $hash->{helper}{buffer} .= $buf; return unless ( $hash->{helper}{buffer} =~ s/(.+)\r\n// ); do { my $v=$1; if ($v=~/\x83?\x05\x05\x03\x03(\w+)/) { $body = process_response($hash,$1); if (length $body) { # if ($body ne undef) { my $str2=AttrVal($hash->{NAME},"showCom","xyz"); my $showcom=qr/$str2/; if ($body->{showCom} =~ $showcom) {readingsSingleUpdate($hash,"communication", $body->{showCom},1)} if ($body->{text} eq "Found circle") { if ($body->{device} ne "FFFFFFFFFFFFFFFF") { PW_Write($hash,$body->{short},"getLog",1); command($hash,'status',$body->{short}); command($hash,'livepower',$body->{short}); } else {$body->{type}=""} } if ($body->{text} eq "Connected") {$hash->{"STATE"}="Connected";} if ($body->{type} =~ /output|power|sense|humtemp|energy|ping/) { if ($body->{dest} eq "PW_Switch" && $Make2Channels==1) { my $dest=$body->{short}; $body->{short}=$dest . "_Ch".$body->{val3}; MyRead($hash,$body); } else { MyRead($hash,$body); } } if ($body->{dest} eq "none" && $body->{type} eq "stat" && $body->{text} ne "ack") { readingsSingleUpdate($hash,"LastMsg", $body->{text}." - Device: ".$body->{code},1) ; } if ($body->{type} eq "err3") {Mywrite($hash, "0026" . $body->{device} ,1);} if ($body->{type} eq "err") {readingsSingleUpdate($hash,"LastMsg", $body->{text}." - Device: ".$body->{code},1) ;; my $tm=(time - $LastTime); if ($tm>600) {$hash->{"ERRCNT"}=0}; $LastTime=time; $hash->{"ERRCNT"}++; Log 3,Dumper($body); if ($hash->{"ERRCNT"} == 50) { Log3 $hash,3,"$name - Too many Errors, giving up. Use 'get $name reOpen' after correcting the Problem"; DevIo_CloseDev($hash); $firstrun=2; DevIo_CloseDev($hash); Log 3,Dumper($hash->{helper}{buffer}); Log 3,"$name Disconnected..........................................."; $hash->{"STATE"}="Disconnected"; @buffer=(); return undef; } } # } } } elsif ($v=~/\x23.*/) {} elsif ($v=~/[0-9A-F]{16}/){} else { Log3 $hash,3,"Not processed: $v"; } } while ( $hash->{helper}{buffer} =~ s/(.+)\r\n// ); $self->{_awaiting_stick_response} = 0; if (@buffer) {real_write($hash)}; return $body; } sub PW_Parse($$$$) { my ($hash, $iohash, $name, $rmsg) = @_; my %addvals; Log3 $hash, 5, "PW_Parse() ".Dumper($rmsg); $hash->{"MSGCNT"}++; $hash->{"TIME"} = TimeNow(); $hash->{RAWMSG} = $rmsg; %addvals = (RAWMSG => $rmsg); Dispatch($iohash, $rmsg->{'dest'}, \%addvals); } #NeedEdit ##################################### sub PW_Ready($) { my ($hash) = @_; return undef; return DevIo_OpenDev($hash, 1, "PW_Ready") if($hash->{STATE} eq "disconnected"); # This is relevant for windows/USB only my $po = $hash->{USBDev}; my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; return ($InBytes>0); } ############# Plugwise-Tools sub Mywrite { my ( $hash, $cmd, $cb, $pri ) = @_; my $packet = "\05\05\03\03" . $cmd . _plugwise_crc($cmd) . "\r\n"; # "\05\05\03\03" . real_write($hash,$packet,$pri); } sub _plugwise_crc { my ( $data ) = @_; sprintf( "%04X", crc( $data, 16, 0, 0, 0, 0x1021, 0, 0 ) ); } sub real_write { my ( $hash, $str, $pri ) = @_; if (!$pri) {$pri=0;} if (defined $str) { if ($pri==0) { push @buffer,$str; } else { unshift @buffer,$str; } } if ($self->{_awaiting_stick_response} == 1) { return undef; } $str=shift @buffer; DevIo_SimpleWrite($hash,$str,undef); $str=~s/\r\n//; $self->{_awaiting_stick_response} = 1; my $str2=AttrVal($hash->{NAME},"showCom","xyz"); my $showcom=qr/$str2/; $str=~s/....([[:xdigit:]]{4})([[:xdigit:]]{4})(.*)/$1 $2 $3/g; if ($str =~ $showcom) {readingsSingleUpdate($hash,"communication", ">> $str",1)} readingsSingleUpdate($hash,"Buffer",@buffer,1); } # Print the data in hex sub _hexdump { my $s = shift; my $r = unpack 'H*', $s; $s =~ s/[^ -~]/./g; $r . ' ' . $s; } # This function processes a response received from the USB stick. # # In a first step, the ACK response from the stick is handled. This means that the # communication sequence number is captured, and a new entry is made in the response queue. # # Second step, if we receive an error response from the stick, pass this message back # # Finally, of course, decode actual useful messages and return their value to the caller # # The input to this function is the message with CRC, with the header and trailing part removed sub process_response { # my ( $hash, $frame ) = @_; my $hash= shift; my $frame= shift; if (!defined $frame||$frame eq '') { return undef } $frame =~ s/\x83*//g; Log3 $hash,5, "Response-Processing '$frame'"; my $name=$hash->{NAME}; my %xplmsg = ( schema => 'plugwise.basic', ); # Check if the CRC matches if (!( _plugwise_crc( substr( $frame, 0, -4 ) ) eq substr( $frame, -4, 4 ) ) ) { # Send out notification... #$xpl->ouch("PLUGWISE: received a frame with an invalid CRC"); $xplmsg{dest}='none'; $xplmsg{type} = 'err'; $xplmsg{text}="Received frame with invalid CRC"; $xplmsg{code} = $frame; return \%xplmsg; } #Switch: if ($testcount>=0){ $testcount=$testcount+1; if ($testcount == 41) { $frame = "002403C1000D6F0002907DC90F09412E0006A17000856539070140264E0844C2030000";Log 3,"Insert 0024";} if ($testcount == 39) { $frame = "0061FFFE000D6F0002907DC90000";Log 3,"Insert 0061";} if ($testcount == 47) { $frame = "004FFFFE000D6F0002907DC9000000";Log 3,"Insert 004F";} if ($testcount == 53) { $frame = "0056FFFF000D6F0002907DC90101xxxx";Log 3,"Insert 0056";} if ($testcount == 56) { $frame = "0056FFFF000D6F0002907DC90100xxxx";Log 3,"Insert keypress";} if ($testcount == 62) { $frame = "0056FFFF000D6F0002907DC90201xxxx";Log 3,"Insert keypress";} if ($testcount == 69) { $frame = "0056FFFF000D6F0002907DC90200xxxx";Log 3,"Insert keypress";} # Circle # $testcount=$testcount+1; # if ($testcount == 40) { $frame = "0027527B000D6F0002907DC83F807E5AB5DCBDA03D74A00400000000xxxx";Log 3,"Insert Calibration";} # if ($testcount == 42) { $frame = "0024527C000D6F0002907DC80F097CA60006A95801856539070140264E0844C202xxxx";Log 3,"Insert Status";} # if ($testcount == 48) { $frame = "0013527E000D6F0002907DC800000000xxxx";Log 3,"Insert PowerInfo";} # if ($testcount == 54) { $frame = "0024527C000D6F0002907DC80F097CA60006A95800856539070140264E0844C202xxxx";Log 3,"Insert Status";} } # Strip CRC, we already know it is correct $frame =~ s/(.{4}$)//; # my $str2=AttrVal($hash->{NAME},"showCom","xyz"); # my $showcom=qr/$str2/; # if ($frame =~ $showcom) {readingsSingleUpdate($hash,"communication", "<< $frame",1)} Log3 $hash,4,"Frame after CRC-strip: $frame"; # After a command is sent to the stick, we first receive an 'ACK'. This 'ACK' contains a sequence number that we want to track and that notifies us of errors. if ( $frame =~ /^0000([[:xdigit:]]{4})([[:xdigit:]]{4})(.*)/ ) { # ack | seq. nr. || response code | my $seqnr = $1; $xplmsg{showCom}="<< 0000 $1 $2 $3"; $xplmsg{dest}='none'; $xplmsg{type} = 'stat'; $xplmsg{code} = $frame; if ( $2 eq "00C1" ) { $xplmsg{text}="ack"; return \%xplmsg;} elsif ( $2 eq "00C2" ) { $xplmsg{text}="Error on Ack-Signal"; $xplmsg{type} = 'err';Log 3,Dumper(%xplmsg);return \%xplmsg;} #Mywrite($hash,"000A"); elsif ( $2 eq "00E1" ) { $xplmsg{text}="Circle out of range"; $xplmsg{text}="ack";return \%xplmsg;} elsif ( $2 eq "00DE" ) { if ( $initdone == 0) {return undef}; my $saddr = _addr_l2s($3); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type}; $xplmsg{type} = 'output'; $xplmsg{text}= 'off'; $xplmsg{code} = $frame; $xplmsg{device} = $3; $xplmsg{short} = $saddr; return \%xplmsg; } elsif ( $2 eq "00D8" ) { if ( $initdone == 0) {return undef}; my $saddr = _addr_l2s($3); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type}; $xplmsg{type} = 'output'; $xplmsg{text}= 'on'; $xplmsg{code} = $frame; $xplmsg{device} = $3; $xplmsg{short} = $saddr; return \%xplmsg; } elsif ( $2 eq "00F9" ) { $xplmsg{text}="Clear group MAC-Table" ; return \%xplmsg;} elsif ( $2 eq "00FA" ) { $xplmsg{text}="Fill Switch-schedule" ; return \%xplmsg;} elsif ( $2 eq "00F7" ) { $xplmsg{text}="Request self-removal from network" ; return \%xplmsg;} elsif ( $2 eq "00F1" ) { $xplmsg{text}="Set broadcast-time interval" ; return \%xplmsg;} elsif ( $2 eq "00F5" ) { $xplmsg{text}="Set handle off" ; return \%xplmsg;} elsif ( $2 eq "00F4" ) { $xplmsg{text}="Set handle on" ; return \%xplmsg;} elsif ( $2 eq "00E6" ) { $xplmsg{text}="Set PN" ; return \%xplmsg;} elsif ( $2 eq "00F8" ) { $xplmsg{text}="Set powerrecording" ; return \%xplmsg;} elsif ( $2 eq "00BE" ) { $xplmsg{text}="Set scan-params ACK" ; return \%xplmsg;} elsif ( $2 eq "00BF" ) { $xplmsg{text}="Set scan-params NACK" ; $xplmsg{type} = 'err';return \%xplmsg;} elsif ( $2 eq "00B5" ) { $xplmsg{text}="Set sense-boundaries ACK" ; return \%xplmsg;} elsif ( $2 eq "00B6" ) { $xplmsg{text}="Set sense-boundaries NACK" ; $xplmsg{type} = 'err';return \%xplmsg;} elsif ( $2 eq "00B3" ) { $xplmsg{text}="Set sense-interval ACK" ; return \%xplmsg;} elsif ( $2 eq "00B4" ) { $xplmsg{text}="Set sense-interval NACK" ; $xplmsg{type} = 'err';return \%xplmsg;} elsif ( $2 eq "00F6" ) { $xplmsg{text}="Set sleep-behavior" ; return \%xplmsg;} elsif ( $2 eq "00E5" ) { $xplmsg{text}="Activate Switch-schedule on" ; return \%xplmsg;} elsif ( $2 eq "00E4" ) { $xplmsg{text}="Activate Switch-schedule off" ; return \%xplmsg;} elsif ( $2 eq "00DD" ) { $xplmsg{text}="Allow nodes to join ACK0" ; return \%xplmsg;} elsif ( $2 eq "00D9" ) { $xplmsg{text}="Allow nodes to join ACK1" ; return \%xplmsg;} elsif ( $2 eq "00C8" ) { $xplmsg{text}="Bootload aborted" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00C9" ) { $xplmsg{text}="Bootload done" ; return \%xplmsg;} elsif ( $2 eq "00D5" ) { $xplmsg{text}="Cancel read Powermeter-Info Logdata" ; return \%xplmsg;} elsif ( $2 eq "00C4" ) { $xplmsg{text}="Cannot join network" ; $xplmsg{type} = 'err';return \%xplmsg;} elsif ( $2 eq "00C3" ) { $xplmsg{text}="Command not allowed" ; $xplmsg{type} = 'err';return \%xplmsg;} elsif ( $2 eq "00D1" ) { $xplmsg{text}="Done reading Powermeter-Info Logdata" ; return \%xplmsg;} elsif ( $2 eq "00C0" ) { $xplmsg{text}="Ember stack error" ; $xplmsg{type} = 'err';return \%xplmsg;} elsif ( $2 eq "00C5" ) { $xplmsg{text}="Exceeding Tableindex" ;$xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00CF" ) { $xplmsg{text}="Flash erased" ; return \%xplmsg;} elsif ( $2 eq "00C6" ) { $xplmsg{text}="Flash error" ; $xplmsg{type} = 'err';return \%xplmsg;} elsif ( $2 eq "00ED" ) { $xplmsg{text}="Group-MAC added" ; return \%xplmsg;} elsif ( $2 eq "00EF" ) { $xplmsg{text}="Group-MAC not added" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00F0" ) { $xplmsg{text}="Group-MAC not removed" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00EE" ) { $xplmsg{text}="Group-MAC removed" ; return \%xplmsg;} elsif ( $2 eq "00E8" ) { $xplmsg{text}="Image activate ACK" ; return \%xplmsg;} elsif ( $2 eq "00CC" ) { $xplmsg{text}="Image check timeout" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00CB" ) { $xplmsg{text}="Image invalid" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00CA" ) { $xplmsg{text}="Image valid" ; return \%xplmsg;} elsif ( $2 eq "00C7" ) { $xplmsg{text}="Node-change accepted" ; return \%xplmsg;} elsif ( $2 eq "00CD" ) { $xplmsg{text}="Ping timeout 1sec" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00EB" ) { $xplmsg{text}="Pingrun busy" ; return \%xplmsg;} elsif ( $2 eq "00EC" ) { $xplmsg{text}="Pingrun finished" ; return \%xplmsg;} elsif ( $2 eq "00CE" ) { $xplmsg{text}="Public network-info complete" ; return \%xplmsg;} elsif ( $2 eq "00D0" ) { $xplmsg{text}="Remote flash erased" ; return \%xplmsg;} elsif ( $2 eq "00F3" ) { $xplmsg{text}="Reply role changed NOK" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00F2" ) { $xplmsg{text}="Reply role changed OK" ; return \%xplmsg;} elsif ( $2 eq "00E0" ) { $xplmsg{text}="Send switchblock NACK" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00DA" ) { $xplmsg{text}="Send calib-params ACK" ; return \%xplmsg;} elsif ( $2 eq "00E2" ) { $xplmsg{text}="Set relais denied" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00DF" ) { $xplmsg{text}="Set RTC-Data ACK" ; return \%xplmsg;} elsif ( $2 eq "00E7" ) { $xplmsg{text}="Set RTC-Data NACK" ; $xplmsg{type} = 'err'; return \%xplmsg;} elsif ( $2 eq "00D7" ) { $xplmsg{text}="Set year, month and flashadress DONE" ; return \%xplmsg;} elsif ( $2 eq "00BD" ) { $xplmsg{text}="Start Light-Calibration started" ; return \%xplmsg;} elsif ( $2 eq "00E9" ) { $xplmsg{text}="Start Pingrun ACK" ; return \%xplmsg;} elsif ( $2 eq "00EA" ) { $xplmsg{text}="Stop Pingrun ACK" ; return \%xplmsg;} elsif ( $2 eq "00DC" ) { $xplmsg{text}="Syncronize NC ACK" ; return \%xplmsg;} elsif ( $2 eq "00D6" ) { $xplmsg{text}="Timeout Powermeter Logdata" ; $xplmsg{type} = 'err'; return \%xplmsg;} else { if ( $initdone == 0) {return undef}; $xplmsg{schema} = 'log.basic'; # Default error message my $text = 'Received error response: $frame'; my $error = $2; my $msg_causing_error = $frame; if ( $msg_causing_error =~ /^0026([[:xdigit:]]{16}$)/ ) { my $device = _addr_l2s($1); $text = "No calibration response received for $device"; delete $self->{_plugwise}->{circles}->{$device}; } Log3 $hash,3, "Received error response: $frame"; $xplmsg{dest}='none'; $xplmsg{type} = 'stat'; $xplmsg{text}= $text; $xplmsg{code} = $frame . ":" . $error; return \%xplmsg; } } if ( $frame =~ /^0011([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})/ ) #0011 0063 000D6F00029014D 8010 14D0D6F00029C512 2BA4DFF4 6D2 # init resp | seq. nr.|| stick MAC addr || don't care || network key || short key { $hash->{"STICK_MAC"} = $2; $hash->{"NET_KEY"} = $4; $self->{_plugwise}->{stick_MAC} = _addr_l2s( $2 ); $self->{_plugwise}->{network_key} = $4; $self->{_plugwise}->{short_key} = $5; $self->{_plugwise}->{connected} = 1; $firstrun = 1; Log3 $hash,3, "PLUGWISE: Received a valid response to the init request from the Stick. Connected!"; query_connected_circles($hash); $xplmsg{dest}='none'; $xplmsg{type} = 'stat'; $xplmsg{text}= 'Connected'; $xplmsg{code} = $frame; $xplmsg{showCom}="<< 0011 $1 $2 $3 $4 $5"; return \%xplmsg; } ############################################################################## ##### Heartbeat ##### 0061 ???? Circle-MAC CRC ############################################################################## if ( $frame =~ /^0061([[:xdigit:]]{4})([[:xdigit:]]{16})$/ ) { if ( $initdone == 0) {return undef}; my $saddr = _addr_l2s($2); unless ($saddr ~~ $self->{_plugwise}->{circles}->{$saddr}) { Log3 $hash,3, "PLUGWISE:Heartbeat from Unknown Device $2"; $self->{_plugwise}->{circles}->{ _addr_l2s( $2 ) } = {}; Mywrite($hash, "0026" . $2 ,0); command($hash,'feature',_addr_l2s($2)); $xplmsg{dest}='none'; $xplmsg{type} = 'stat'; $xplmsg{text}= 'Found circle'; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{showCom}="<< 0061 $1 $2"; $xplmsg{short} = $saddr; } return \%xplmsg; } # Process the response on a powerinfo request # powerinfo resp | seq. nr. || Circle MAC || pulse1 || pulse8 | other stuff we don't care about #0013 0051 000D6F0000994CAA 0000 FFFF 00000000 FFFFF DB9000D if ( $frame =~ /^0013([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})([[:xdigit:]]{4})/ ) { my $saddr = _addr_l2s($2); my $pulse1 = $3; my $pulse8 = $4; # Assign the values to the data hash $self->{_plugwise}->{circles}->{$saddr}->{pulse1} = $pulse1; $self->{_plugwise}->{circles}->{$saddr}->{pulse8} = $pulse8; $xplmsg{showCom}="<< 0013 $1 $2 $3 $4"; if ($4 eq "FFFF") { $xplmsg{dest}='none'; $xplmsg{type} = 'ignore'; $xplmsg{text}= 'Ignored Spike'; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $saddr; return \%xplmsg; } # Ensure we have the calibration info before we try to calc the power, # if we don't have it, return an error reponse if ( !defined $self->{_plugwise}->{circles}->{$saddr}->{gainA} ) { $xplmsg{dest}='none'; $xplmsg{type} = 'err3'; $xplmsg{text}= 'Report power failed, calibration data not retrieved yet'; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $saddr; return \%xplmsg; } # Calculate the live power my ( $pow1, $pow8 ) = _calc_live_power($hash,$saddr); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type}; $xplmsg{type} = 'power'; $xplmsg{text}= ' '; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $saddr; $xplmsg{val1} = $pow1; $xplmsg{val2} = $pow8; $xplmsg{unit1} = 'W'; $xplmsg{unit2} = 'W'; return \%xplmsg; } # Process the response on a query known circles command # circle query resp| seq. nr. || Circle+ MAC || Circle MAC on || memory position if ( $frame =~ /^0019([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{2})$/ ) { $xplmsg{showCom}="<< 0019 $1 $2 $3 $4"; my $nr=sprintf( "%02X", AttrVal($hash->{NAME},"circlecount",50) -1); if ($4 eq $nr) {Log 3,$hash->{NAME} . "Init done, found ". (keys(%{$self->{_plugwise}->{circles}})) . " devices";$initdone=1} if ( $3 ne "FFFFFFFFFFFFFFFF" ) { $self->{_plugwise}->{circles}->{ _addr_l2s( $3 ) } = {}; Mywrite($hash, "0026" . $3 ,0); } $xplmsg{schema} = 'log.basic'; $xplmsg{dest}='none'; $xplmsg{type} = 'stat'; $xplmsg{text}='none'; $xplmsg{text}= 'Found circle' if (defined $self->{_plugwise}->{circles}->{_addr_l2s($3)}); $xplmsg{code} = $frame; $xplmsg{device} = $3; $xplmsg{short} = _addr_l2s($3); return \%xplmsg; } # Process the response on a status request # status response | seq. nr. || Circle+ MAC || year,mon, min || curr_log_addr || powerstate if ( $frame #0024 0050 000D6F0000994CAA 0F08595B 000440B 80 18 565390701402 24E0844C2 02 =~ /^0024([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{8})([[:xdigit:]]{8})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{12})([[:xdigit:]]{8})([[:xdigit:]]{2})/ ) { my $saddr = _addr_l2s($2); my $onoff = $5 eq '00' ? 'off' : 'on'; my $current = $5 eq '00' ? 'LOW' : 'HIGH'; $self->{_plugwise}->{circles}->{$saddr}->{onoff} = $onoff; if(!exists $PWType{$9}) { Log3 $name, 2, "$name: Autocreate: unknown familycode '$9' found. Please report this!"; next; } else { # Log 3,"Device $saddr now is $PWType{$9}"; $self->{_plugwise}->{circles}->{$saddr}->{type} = $PWType{$9}; } # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Circle' if (hex($9) <= 2); # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Switch' if (hex($9) == 3 || hex($9) == 4); # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Sense' if (hex($9) == 5); # $self->{_plugwise}->{circles}->{$saddr}->{type} = 'Scan' if (hex($9) == 6); $self->{_plugwise}->{circles}->{$saddr}->{curr_logaddr} = ( hex($4) - 278528 ) / 8; Log 3, "Unknown Device-Code: $frame" if (!defined $self->{_plugwise}->{circles}->{$saddr}->{type}); Log 3, "Unknown Circle: $saddr" if (!defined $self->{_plugwise}->{circles}->{$saddr}); my $circle_date_time = _tstamp2time($hash,$3); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$saddr}->{type}; $xplmsg{type} = 'output'; $xplmsg{text}= $onoff; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $saddr; $xplmsg{showCom}="<< 0024 $1 $2 $3 $4 $5 $6 $7 $8 $9"; $xplmsg{val1} = $self->{_plugwise}->{circles}->{$saddr}->{curr_logaddr}; $xplmsg{val2} = $circle_date_time; $xplmsg{val3} = 0; return \%xplmsg; } # Process the response on a calibration request if ( $frame =~ /^0027([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{8})([[:xdigit:]]{8})([[:xdigit:]]{8})([[:xdigit:]]{8})$/ ) { # calibration resp | seq. nr. || Circle+ MAC || gainA || gainB || offtot || offruis #print "Received for $2 calibration response!\n"; my $saddr = _addr_l2s($2); #print "Short address = $saddr\n"; Log3 $hash,5,"PLUGWISE: Received calibration reponse for circle $saddr"; $self->{_plugwise}->{circles}->{$saddr}->{gainA} = _hex2float($3); $self->{_plugwise}->{circles}->{$saddr}->{gainB} = _hex2float($4); $self->{_plugwise}->{circles}->{$saddr}->{offtot} = _hex2float($5); $self->{_plugwise}->{circles}->{$saddr}->{offruis} = _hex2float($6); Log3 $hash,5,"$2 - Calib: $3 - $4 - $5 - $6"; $lastcircle=$saddr; $xplmsg{dest}='none'; $xplmsg{type} = 'stat'; $xplmsg{text}= 'Calibration-Info received.'; $xplmsg{showCom}="<< 0027 $1 $2 $3 $4 $5 $6"; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $saddr; return \%xplmsg; } # Process a Feature-Request if ( $frame =~ /^0060([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})/ ) { my $s_id = _addr_l2s($2); $xplmsg{dest}='none'; $xplmsg{showCom}="<< 0060 $1 $2 $3"; $xplmsg{type} = 'stat'; $xplmsg{text}= 'Features'; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; $xplmsg{val1} = $3; Log3 $hash,5,Dumper(%xplmsg); Log3 $hash,3, "PLUGWISE: Features for $s_id are $3"; return \%xplmsg; } # Process the response of TempHum-Sensor if ( $frame =~ /^0105([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{4})([[:xdigit:]]{4})/ ) { my $s_id = _addr_l2s($2); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type}; $xplmsg{type} = 'humtemp'; $xplmsg{showCom}="<< 0105 $1 $2 $3 $4"; $xplmsg{text}= ' '; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; $xplmsg{val1} = (hex($3)-3145)/524.30; $xplmsg{val2} = (hex($4)-17473)/372.90; $xplmsg{unit1} = 'h'; $xplmsg{unit2} = 'C'; Log3 $hash,5,Dumper(%xplmsg); Log3 $hash,5, "PLUGWISE: Temperature for $s_id set"; return \%xplmsg; } ## RemoveNode response if ( $frame =~ /^001D([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{2})/ ) #001D 1026 000D6F00029C5122 000D6F00029C5122 00 { Log3 $hash,3,"Removed Node $3 (Index $4) from Network."; $xplmsg{showCom}="<< 0000 $1 $2 $3 $4"; $xplmsg{dest}='none'; $xplmsg{type} = 'stat'; $xplmsg{code} = $frame; $xplmsg{text} = 'Removed Node $3 (Index $4) from Network.'; return \%xplmsg; } ## Keypress on Switch #0056FFFF000D6F0002769C7D0101 if ( $frame =~ /^0056([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{2})([[:xdigit:]]{2})/ ) { Log3 $hash,4,"Keypress"; my $s_id = _addr_l2s($2); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type}; $xplmsg{type} = 'sense'; $xplmsg{showCom}="<< 0056 $1 $2 $3 $4"; $xplmsg{text}= ' '; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; $xplmsg{val1} = hex($4); if ($Make2Channels==0) { $xplmsg{val1} = hex($4); #+((hex($3)-1)<<1) if ($self->{_plugwise}->{circles}->{$s_id}->{type} eq "PW_Switch"); $xplmsg{val2} = (hex($3)-1)<<1; } else { $xplmsg{val3} = hex($3); } Log3 $hash,5,Dumper(%xplmsg); Log3 $hash,5, "PLUGWISE: Motion-Signal detected"; return \%xplmsg; } # Ping-Response if ( $frame =~ /^000E([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{4})/ ) { if ( $initdone == 0) {return undef}; Log3 $hash,4,"ping"; my $s_id = _addr_l2s($2); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type}; $xplmsg{type} = 'ping'; $xplmsg{showCom}="<< 004F $1 $2 $3 $4 $5"; $xplmsg{text}= ' '; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; $xplmsg{val1} = hex($3); $xplmsg{val2} = hex($4); $xplmsg{val3} = hex($5); Log3 $hash,5,Dumper(%xplmsg); return \%xplmsg; } # Pushbuttons if ( $frame =~ /^004F([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{2})/ ) { if ( $initdone == 0) {return undef}; Log3 $hash,4,"Keypress"; my $s_id = _addr_l2s($2); $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type}; $xplmsg{type} = 'press'; $xplmsg{showCom}="<< 004F $1 $2 $3"; $xplmsg{text}= ' '; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; $xplmsg{val1} = hex($3); Log3 $hash,5,Dumper(%xplmsg); return \%xplmsg; } # Process the response on a historic buffer readout if ( $frame =~ /^0049([[:xdigit:]]{4})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{16})([[:xdigit:]]{8})$/ ) { # history resp | seq. nr. || Circle+ MAC || info 1 || info 2 || info 3 || info 4 || address my $s_id = _addr_l2s($2); my $log_addr = ( hex($7) - 278528 ) / 8; $xplmsg{showCom}="<< 0049 $1 $2 $3 $4 $5 $6 $7"; #print "Received history response for $2 and address $log_addr!\n"; # Assign the values to the data hash $self->{_plugwise}->{circles}->{$s_id}->{history}->{logaddress} = $log_addr; $self->{_plugwise}->{circles}->{$s_id}->{history}->{info1} = $3; $self->{_plugwise}->{circles}->{$s_id}->{history}->{info2} = $4; $self->{_plugwise}->{circles}->{$s_id}->{history}->{info3} = $5; $self->{_plugwise}->{circles}->{$s_id}->{history}->{info4} = $6; # Ensure we have the calibration info before we try to calc the power, # if we don't have it, return an error reponse if ( !defined $self->{_plugwise}->{circles}->{$s_id}->{gainA} ) { #$xpl->ouch("Cannot report the power, calibration data not received yet for $s_id\n"); $xplmsg{dest}='none'; $xplmsg{type} = 'err'; $xplmsg{text}= 'Report power failed, calibration data not retrieved yet'; $xplmsg{showCom}="<< 0049 $1 $2 $3 $4 $5 $6 $7"; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; return \%xplmsg; } my ( $tstamp, $energy ) = _report_history($hash,$s_id); # If the timestamp is no good, we tried to retrieve a field that contains no valid data, generate an error response if ( $tstamp eq "000000000000" ) { #$xpl->ouch("Cannot report the power for interval $log_addr of circle $s_id, it is in the future\n"); $xplmsg{dest}='none'; $xplmsg{type} = 'err'; $xplmsg{text}= 'Report power failed, no valid data in time interval'; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; $xplmsg{showCom}="<< 0049 $1 $2 $3 $4 $5 $6 $7"; return \%xplmsg; } $xplmsg{dest}=$self->{_plugwise}->{circles}->{$s_id}->{type}; $xplmsg{type} = 'energy'; $xplmsg{text}= ' '; $xplmsg{code} = $frame; $xplmsg{device} = $2; $xplmsg{short} = $s_id; $xplmsg{val1} = $energy; $xplmsg{val2} = $tstamp; $xplmsg{val3} = $log_addr; $xplmsg{unit1} = 'kWh'; Log3 $hash,5,Dumper(%xplmsg); Log3 $hash,5, "PLUGWISE: Historic energy for $s_id [$log_addr] is $energy kWh on $tstamp"; return \%xplmsg; } # We should not get here unless we receive responses that are not implemented... #$xpl->ouch("Received unknown response: '$frame'"); Log3 $hash,3,"PLUGWISE: Unknown Frame received: $frame - Please report this"; $xplmsg{dest}='none'; $xplmsg{type} = 'err2'; $xplmsg{text}= 'Unknown Frame received'; $xplmsg{code} = $frame; return \%xplmsg; } sub command { my ( $hash, $command, $target, $parameter ) = @_; Log3 $hash,5,"Command=$command - Target=$target"; if ( !defined($command) || !defined($target) ) { Log3 $hash,3,"A command to the stick needs a command and a target ID as parameter"; return 0; } #Log 3,"Set $self->{_plugwise}->{circles}->{$target}->{type} $target $command to $parameter"; my $packet = ""; my $pri=0; if ( defined $target ) { # Commands that target a specific device might need to be sent multiple times # if multiple devices are defined my $circle = uc($target); if ( $command =~ /(on|off)/ ) { if ($self->{_plugwise}->{circles}->{$circle}->{type} eq "PW_Circle") { $packet = "0017" . _addr_s2l($circle) . ($1 eq 'on' ? '01' : '00'); } elsif($self->{_plugwise}->{circles}->{$circle}->{type} eq "PW_Switch"){ # Log 3,"Set Switch $circle $parameter to $1"; if ($parameter eq "left") { $packet = "0017" . _addr_s2l($circle) . "01" . $1 eq 'on' ? '01' : '00'; } elsif ($parameter eq "right") { $packet = "0017" . _addr_s2l($circle) . "02" . $1 eq 'on' ? '01' : '00'; } } $pri=1; } elsif ($command eq "feature") { $packet = "005F" . _addr_s2l($circle); } elsif ($command eq "ping") { $packet = "000D" . _addr_s2l($circle); } elsif ( $command eq 'syncTime' ) { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $packet = "0016" . _addr_s2l($circle) . sprintf( "%02X", $year-100) . sprintf( "%02X", $mon) . sprintf( "%04X", ($mday * 24 * 60) + ($hour * 60) + $min) . "FFFFFFFF" . sprintf( "%02X", $hour) . sprintf( "%02X", $min) . sprintf( "%02X", $sec) . sprintf( "%02X", $wday) ; # Log3 $hash,3,"syncTime-Frame: $packet"; # return (undef); } elsif ( $command eq 'status' ) { $packet = "0023" . _addr_s2l($circle); } elsif ($command eq 'removeNode') { $packet = "001C" . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} ) . _addr_s2l($circle); } elsif ( $command eq 'livepower' ) { # Ensure we have the calibration readings before we send the read command # because the processing of the response of the read command required the # calibration readings output to calculate the actual power # Log3 $hash,3,Dumper( $self->{_plugwise}); if (!defined( $self->{_plugwise}->{circles}->{$circle}->{offruis} ) ) { my $longaddr = _addr_s2l($circle); Mywrite( $hash,"0026" . $longaddr ,0) ; #, "Request calibration info"); } $packet = "0012" . _addr_s2l($circle); } elsif ( $command eq 'history' ) { # Ensure we have the calibration readings before we send the read command # because the processing of the response of the read command required the # calibration readings output to calculate the actual power if (!defined( $self->{_plugwise}->{circles}->{$circle}->{offruis} ) ) { my $longaddr = _addr_s2l($circle); Mywrite($hash, "0026" . $longaddr ,0) ; #, "Request calibration info"); } if ( !defined $parameter ) { Log3 $hash,3,"The 'history' command needs both a Circle ID and an address to read..."; return 0; } Log3 $hash,5,"requesting Log for $parameter"; my $address = ($parameter * 8) + 278528;#* 8 + 278528; $packet = "0048" . _addr_s2l($circle) . sprintf( "%08X", $address ); Log3 $hash,5,"Write command: $packet"; } else { Log3 $hash,3,"Received invalid command '$command'"; return 0; } # Send the packet to the stick! Log3 $hash,5,"Write command: $packet"; Mywrite($hash,$packet,$pri) if ( defined $packet ); } } # Interrogate the network coordinator (Circle+) for all connected Circles # This sub will generate the requests, and then the response parser function # will generate a hash with all known circles # When a circle is detected, a calibration request is sent to ge the relevant info # required to calculate the power information. # Circle info goes into a global hash like this: # $object->{_plugwise}->{circles} # A single circle entry contains the short id and the following info: # short_id => { gainA => xxx, # gainB => xxx, # offtot => xxx, # offruis => xxx } sub query_connected_circles { my ($hash) = @_; # In this code we will scan all connected circles to be able to add them to the $self->{_plugwise}->{circles} hash my $index = 0; Log3 $hash,5,$hash->{NAME} . " - Looking for Circles....."; # Interrogate the Circle+ and add its info into the circles hash $self->{_plugwise}->{coordinator_MAC} = _addr_l2s( $self->{_plugwise}->{network_key} ); $self->{_plugwise}->{circles} = {}; # Reset known circles hash $self->{_plugwise}->{circles}->{ _addr_l2s( $self->{_plugwise}->{network_key} ) } = {}; # Add entry for Circle+ Mywrite( $hash, "0026" . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} ) ,0); # Interrogate the first x connected devices while ( $index < AttrVal($hash->{NAME},"circlecount",50) ) { my $strindex = sprintf( "%02X", $index++ ); my $packet = "0018" . _addr_s2l( $self->{_plugwise}->{coordinator_MAC} ) . $strindex; Mywrite($hash,$packet,0); #, "Query connected device $strindex"); } return; } # Convert the long Circle address notation to short sub _addr_l2s { my ( $address ) = @_; my $saddr = substr( $address, -8, 8 ); # We will return at least 6 bytes, more if required # This is to keep compatibility with existing code that only supports 6 byte short addresses return sprintf( "%06X", hex($saddr) ); } # Convert the short Circle address notation to long sub _addr_s2l { my ( $address ) = @_; # Log 3,Dumper(caller) if ($address eq 0xffffffff); return "000D6F00" . sprintf( "%08X", hex($address) ); } # Convert hex values to float for power readout sub _hex2float { my ( $hexstr ) = @_; my $floater = unpack( 'f', reverse pack( 'H*', $hexstr ) ); return $floater; } sub _report_history { my ( $hash, $id ) = @_; # Get the first data entry my $data = $self->{_plugwise}->{circles}->{$id}->{history}->{info1}; my $energy = 0; my $tstamp = 0; if ( $data =~ /^([[:xdigit:]]{8})([[:xdigit:]]{8})$/ ) { # Calculate Wh my $corrected_pulses = _pulsecorrection( $hash,$id, hex($2) ); $energy = $corrected_pulses / 3600 / 468.9385193 * 1000; $tstamp = _tstamp2time($hash,$1); # Round to 1 Wh $energy = sprintf($attr{$hash->{NAME}}{WattFormat},$energy); # Report kWh # $energy = $energy / 1000; #print "info1 date: $tstamp, energy $energy kWh\n"; } return ( $tstamp, $energy ); } # Convert a Plugwise timestamp to a human-readable format sub _tstamp2time { my ( $hash, $tstamp ) = @_; # Return empty time on empty timestamp return "000000000000" if ( $tstamp eq "FFFFFFFF" ); # Convert if ( $tstamp =~ /([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{4})/ ) { my $circle_date = sprintf( "%04i-%02i-%02i", 2000 + hex($1), hex($2), int( hex($3) / 60 / 24 ) + 1 ); my $circle_time = hex($3) % ( 60 * 24 ); my $circle_hours = int( $circle_time / 60 ); my $circle_minutes = $circle_time % 60; $circle_time = sprintf( " %02i:%02i", $circle_hours, $circle_minutes ); return $circle_date . $circle_time; } else { return "000000000000"; } } # Calculate the live power consumption from the last report. sub _calc_live_power { my ( $hash, $id ) = @_; #my ($pulse1, $pulse8) = $self->pulsecorrection($id); my $pulse1 = _pulsecorrection( $hash,$id, hex( $self->{_plugwise}->{circles}->{$id}->{pulse1} ) ); my $pulse8 = _pulsecorrection( $hash,$id, hex( $self->{_plugwise}->{circles}->{$id}->{pulse8} ) /8 ); my $live1 = $pulse1 * 1000 / 468.9385193; my $live8 = $pulse8 * 1000 / 468.9385193; # Round $live1 = sprintf($attr{$hash->{NAME}}{WattFormat},$live1); $live8 = sprintf($attr{$hash->{NAME}}{WattFormat},$live8); return ( $live1, $live8 ); } # Correct the reported number of pulses based on the calibration values sub _pulsecorrection { my ( $hash, $id, $pulses ) = @_; # Get the calibration values for the circle my $offnoise = $self->{_plugwise}->{circles}->{$id}->{offruis}; my $offtot = $self->{_plugwise}->{circles}->{$id}->{offtot}; my $gainA = $self->{_plugwise}->{circles}->{$id}->{gainA}; my $gainB = $self->{_plugwise}->{circles}->{$id}->{gainB}; # Correct the pulses with the calibration data my $out = ( ( $pulses + $offnoise ) ^ 2 ) * $gainB + ( ( $pulses + $offnoise ) * $gainA ) + $offtot; # Never report negative values, can happen with really small values $out = 0 if ( $out < 0 ); return $out; } "Cogito, ergo sum."; =pod =item device =item summary Module for controling Plugwise-Devices =item summary_DE Modul für das Plugwise-System =begin html

Plugwise


Set

Attributes =end html =begin html_DE

Plugwise


Set

Attribute =end html_DE =cut