############################################## # $Id$ package main; use strict; use warnings; use DevIo; 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) = @_; # 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 FBAHA hostname:2002"; } my $name = $a[0]; my $dev = $a[2]; $hash->{Clients} = ":FBDECT:"; my %matchList = ( "1:FBDECT" => ".*" ); $hash->{MatchList} = \%matchList; $hash->{devioNoSTATE} = 1; 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(DevIo_getState($hash) 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

FBAHA

=end html =begin html_DE

FBAHA

=end html_DE =cut