############################################## # $Id$ package main; # TODO: test multi-dev, test on the FB 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); 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 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; } return "define $name: wrong id ($id): need a number" if( $id !~ m/^\d+$/i ); $hash->{id} = $id; $hash->{props} = shift @a; $modules{FBDECT}{defptr}{$ioNameAndId} = $hash; AssignIoPort($hash, $ioName); return undef; } ################################### my %sets = ("on"=>1, "off"=>1, "msgInterval"=>1); sub FBDECT_Set($@) { my ($hash, @a) = @_; my $ret = undef; my $cmd = $a[1]; if(!$sets{$cmd}) { my $usage = join(" ", sort keys %sets); return SetExtensions($hash, $usage, @a); } 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; } my %gets = ("devInfo"=>1); sub FBDECT_Get($@) { my ($hash, @a) = @_; my $ret = undef; my $cmd = ($a[1] ? $a[1] : ""); if(!$gets{$cmd}) { return "Unknown argument $cmd, choose one of ".join(" ", sort keys %gets); } if($cmd eq "devInfo") { my @answ = FBAHA_getDevList($hash->{IODev}, $hash->{id}); return $answ[0] if(@answ == 1); my $d = pop @answ; my $state = "inactive" if($answ[0] =~ m/ inactive,/); while($d) { my ($ptyp, $plen, $pyld) = FBDECT_decodePayload($d, $hash, 0); Log3 $hash, 4, "Payload: $d -> $ptyp: $pyld"; last if($ptyp eq ""); if($ptyp eq "state" && ReadingsVal($hash->{NAME}, $ptyp, "") ne $pyld) { readingsSingleUpdate($hash, $ptyp, ($state ? $state : $pyld), 1); } push @answ, " $ptyp: $pyld"; $d = substr($d, 16+$plen*2); } return join("\n", @answ); } return undef; } ################################### sub FBDECT_Parse($$@) { my ($iodev, $msg, $local) = @_; my $ioName = $iodev->{NAME}; my $mt = substr($msg, 0, 2); 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_$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 =begin html

FBDECT

=end html =begin html_DE

FBDECT

=end html_DE =cut