From d51f8d37f5bdc1cb3927280ba94561d9d6d1b387 Mon Sep 17 00:00:00 2001
From: rudolfkoenig <>
Date: Wed, 3 Dec 2008 16:45:26 +0000
Subject: [PATCH] 4.5 prep

git-svn-id: https://svn.fhem.de/fhem/trunk@273 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
 fhem/FHEM/00_CUL.pm | 158 ++++++++++++++++++++++++++++----------------
 fhem/FHEM/00_FHZ.pm |  10 +--
 fhem/HISTORY        |  19 +++++-
 fhem/fhem.pl        |  56 +++++++++++-----
 4 files changed, 165 insertions(+), 78 deletions(-)

diff --git a/fhem/FHEM/00_CUL.pm b/fhem/FHEM/00_CUL.pm
index 1acff6165..279f606b4 100755
--- a/fhem/FHEM/00_CUL.pm
+++ b/fhem/FHEM/00_CUL.pm
@@ -1,22 +1,7 @@
 ##############################################
-# Implemented:
-# - Transmit limit trigger: Fire if more then 1% airtime 
-#   is used in the last hour
-# - reconnect
-# - message flow control (send one F message every 0.25 seconds)
-# - repeater/filtertimeout
-# - FS20 rcv
-# - FS20 xmit
-# - FHT rcv
-
 # TODO:
 # - FHT xmit
 # - HMS rcv
-# - KS300 rcv
-# - EMEM rcv
-# - EMWZ rcv
-# - EMGZ rcv
-# - S300TH rcv
 
 
 package main;
@@ -29,22 +14,18 @@ use Time::HiRes qw(gettimeofday);
 sub CUL_Write($$$);
 sub CUL_Read($);
 sub CUL_ReadAnswer($$);
-sub CUL_Ready($$);
+sub CUL_Ready($);
 
 my $initstr = "X01";    # Only translated messages, no RSSI
 my %msghist;		# Used when more than one CUL is attached
 my $msgcount = 0;
 my %gets = (
-  "ccreg"    => "C",
-  "eeprom"   => "R",
   "version"  => "V",
-  "time"     => "t",
   "raw"      => "",
   "ccconf"   => "=",
 );
 
 my %sets = (
-  "eeprom"    => "W",
   "raw"       => "",
   "verbose"   => "X",
   "freq"      => "=",
@@ -60,7 +41,7 @@ CUL_Initialize($)
   $hash->{ReadFn}  = "CUL_Read";
   $hash->{WriteFn} = "CUL_Write";
   $hash->{Clients} = ":FS20:FHT:KS300:CUL_EM:CUL_WS:";
-  $hash->{ReadyFn} = "CUL_Ready" if ($^O eq 'MSWin32');
+  $hash->{ReadyFn} = "CUL_Ready";
 
 # Normal devices
   $hash->{DefFn}   = "CUL_Define";
@@ -69,7 +50,7 @@ CUL_Initialize($)
   $hash->{SetFn}   = "CUL_Set";
   $hash->{StateFn} = "CUL_SetState";
   $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 filtertimeout repeater:1,0 " .
-                   "showtime:1,0 model:CUL loglevel:0,1,2,3,4,5,6";
+                   "showtime:1,0 model:CUL,CUR loglevel:0,1,2,3,4,5,6";
 }
 
 #####################################
@@ -79,13 +60,17 @@ CUL_Define($$)
   my ($hash, $def) = @_;
   my @a = split("[ \t][ \t]*", $def);
   my $po;
-  $hash->{STATE} = "Initialized";
+
+  return "wrong syntax: define <name> CUL devicename [mobile]"
+    if(@a < 3 || @a > 4);
 
   delete $hash->{PortObj};
   delete $hash->{FD};
 
   my $name = $a[0];
   my $dev = $a[2];
+  $hash->{MOBILE} = 1 if($a[3] && $a[3] eq "mobile");
+  $hash->{STATE} = "defined";
 
   $attr{$name}{savefirst} = 1;
   $attr{$name}{repeater} = 1;
@@ -96,6 +81,8 @@ CUL_Define($$)
     return undef;
   }
   
+  $hash->{DeviceName} = $dev;
+  $hash->{PARTIAL} = "";
   Log 3, "CUL opening CUL device $dev";
   if ($^O=~/Win/) {
    require Win32::SerialPort;
@@ -104,7 +91,13 @@ CUL_Define($$)
    require Device::SerialPort;
    $po = new Device::SerialPort ($dev);
   }
-  return "Can't open $dev: $!\n" if(!$po);
+  if(!$po) {
+    my $msg = "Can't open $dev: $!";
+    Log(3, $msg) if($hash->{MOBILE});
+    return $msg if(!$hash->{MOBILE});
+    $readyfnlist{"$name.$dev"} = $hash;
+    return "";
+  }
   Log 3, "CUL opened CUL device $dev";
 
   $hash->{PortObj} = $po;
@@ -115,9 +108,12 @@ CUL_Define($$)
     $readyfnlist{"$name.$dev"} = $hash;
   }
   
-  $hash->{DeviceName} = $dev;
-  $hash->{PARTIAL} = "";
-  return CUL_DoInit($hash);
+  my $ret  = CUL_DoInit($hash);
+  if($ret) {
+    delete($selectlist{"$name.$dev"});
+    delete($readyfnlist{"$name.$dev"});
+  }
+  return $ret;
 }
 
 #####################################
@@ -165,9 +161,9 @@ CUL_Set($@)
     my $msg = "Setting FREQ2..0 (0D,0E,0F) to $f2 $f1 $f0 = $arg MHz, ".
                 "verbose to $initstr";
     Log GetLogLevel($name,4), $msg;
-    CUL_SimpleWrite($hash, "W0D$f2");            # Will reprogram the CC1101
-    CUL_SimpleWrite($hash, "W0E$f1");
-    CUL_SimpleWrite($hash, "W0F$f0");
+    CUL_SimpleWrite($hash, "W0F$f2");            # Will reprogram the CC1101
+    CUL_SimpleWrite($hash, "W10$f1");
+    CUL_SimpleWrite($hash, "W11$f0");
     CUL_SimpleWrite($hash, $initstr);
     return $msg;
 
@@ -194,7 +190,7 @@ GOTBW:
     my $msg = "Setting MDMCFG4 (10) to $ob = $bw KHz, verbose to $initstr";
 
     Log GetLogLevel($name,4), $msg;
-    CUL_SimpleWrite($hash, "W10$ob");
+    CUL_SimpleWrite($hash, "W12$ob");
     CUL_SimpleWrite($hash, $initstr);
     return $msg;
 
@@ -228,21 +224,19 @@ CUL_Get($@)
   if($a[1] eq "ccconf") {
 
     my %r = ( "0D"=>1,"0E"=>1,"0F"=>1,"10"=>1,"1B"=>1,"1D"=>1,
-              "23"=>1,"24"=>1,"25"=>1,"26"=>1,"34"=>1) ;
+              "23"=>1,"24"=>1,"25"=>1,"26"=>1) ;
     foreach my $a (sort keys %r) {
       CUL_SimpleWrite($hash, "C$a");
       my @answ = split(" ", CUL_ReadAnswer($hash, "C$a"));
       $r{$a} = $answ[4];
     }
     $msg = sprintf("Freq:%.3fMHz Bwidth:%dKHz Ampl:%ddB " .
-                   "Sens:%ddB FSCAL:%02X%02X%02X%02X RSSI: %ddB", 
+                   "Sens:%ddB FSCAL:%02X%02X%02X%02X", 
         26*(($r{"0D"}*256+$r{"0E"})*256+$r{"0F"})/65536,                #Freq
         26000/(8 * (4+(($r{"10"}>>4)&3)) * (1 << (($r{"10"}>>6)&3))),   #Bw
         $r{"1B"}&7<4 ? 24+3*($r{"1B"}&7) : 36+2*(($r{"1B"}&7)-4),       #Ampl
         4+4*($r{"1D"}&3),                                               #Sens
-        $r{"23"}, $r{"24"}, $r{"25"}, $r{"26"},                         #FSCAL
-        $r{"34"}>=128 ? (($r{34}-256)/2-74) : ($r{34}/2-74)             #RSSI
-
+        $r{"23"}, $r{"24"}, $r{"25"}, $r{"26"}                          #FSCAL
         );
     
   } else {
@@ -292,10 +286,12 @@ CUL_DoInit($)
     return $msg;
   }
   CUL_SimpleWrite($hash, $initstr);
+  $hash->{STATE} = "Initialized";
 
   # Reset the counter
   delete($hash->{XMIT_TIME});
   delete($hash->{NR_CMD_LAST_H});
+  return undef;
 }
 
 #####################################
@@ -310,7 +306,7 @@ CUL_ReadAnswer($$)
   my $nfound;
   for(;;) {
     if($^O eq 'MSWin32') {
-      $nfound=CUL_Ready($hash, undef);
+      $nfound=CUL_Ready($hash);
     } else {
       vec($rin, $hash->{FD}, 1) = 1;
       my $to = 3;                                         # 3 seconds timeout
@@ -438,8 +434,8 @@ sub
 CUL_HandleWriteQueue($)
 {
   my $hash = shift;
-  my $cnt = --$hash->{QUEUECNT};
-  if($cnt > 0) {
+  if($hash->{QUEUECNT} > 0) {
+    $hash->{QUEUECNT}--;
     my $bstring = shift(@{$hash->{QUEUE}});
     CUL_XmitLimitCheck($hash,$bstring);
     $hash->{PortObj}->write($bstring);
@@ -465,24 +461,42 @@ CUL_Read($)
 
   if(!defined($buf) || length($buf) == 0) {
 
-    my $devname = $hash->{DeviceName};
-    Log 1, "USB device $devname disconnected, waiting to reappear";
+    my $dev = $hash->{DeviceName};
+    Log 1, "USB device $dev disconnected, waiting to reappear";
     $hash->{PortObj}->close();
-    for(;;) {
+
+    if($hash->{MOBILE}) {
+
+      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);
-      if ($^O eq 'MSWin32') {
-        $hash->{PortObj} = new Win32::SerialPort($devname);
-      }else{
-        $hash->{PortObj} = new Device::SerialPort($devname);  
-      }
-      
-      if($hash->{PortObj}) {
-        Log 1, "USB device $devname reappeared";
-        $hash->{FD} = $hash->{PortObj}->FILENO if !($^O eq 'MSWin32');
-        CUL_DoInit($hash);
-	return;
+
+      return "";
+
+    } else {
+
+      for(;;) {
+        sleep(5);
+        if ($^O eq 'MSWin32') {
+          $hash->{PortObj} = new Win32::SerialPort($dev);
+        }else{
+          $hash->{PortObj} = new Device::SerialPort($dev);
+        }
+
+        if($hash->{PortObj}) {
+          Log 1, "USB device $dev reappeared";
+          $hash->{FD} = $hash->{PortObj}->FILENO if !($^O eq 'MSWin32');
+          CUL_DoInit($hash);
+          return;
+        }
       }
     }
+
   }
 
   my $culdata = $hash->{PARTIAL};
@@ -618,11 +632,43 @@ NEXTMSG:
 
 #####################################
 sub
-CUL_Ready($$)           # Windows - only
+CUL_Ready($)           # Windows - only
 {
-  my ($hash, $dev) = @_;
+  my ($hash) = @_;
   my $po=$hash->{PortObj};
-  return undef if !$po;
+
+  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);
+
+    Log 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;
+    }
+    my $ret  = CUL_DoInit($hash);
+    if($ret) {
+      delete($selectlist{"$name.$dev"});
+      delete($readyfnlist{"$name.$dev"});
+    }
+    return $ret;
+
+  }
+
+  # This is relevant for windows only
   my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
   return ($InBytes>0);
 }
diff --git a/fhem/FHEM/00_FHZ.pm b/fhem/FHEM/00_FHZ.pm
index c8ef2eaf6..ccd3e4ca3 100755
--- a/fhem/FHEM/00_FHZ.pm
+++ b/fhem/FHEM/00_FHZ.pm
@@ -82,9 +82,9 @@ FHZ_Initialize($)
 }
 #####################################
 sub
-FHZ_Ready($$)
+FHZ_Ready($)
 {
-  my ($hash, $dev) = @_;
+  my ($hash) = @_;
   my $po=$hash->{PortObj};
   return undef if !$po;
   my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags)=$po->status;
@@ -407,7 +407,7 @@ FHZ_ReadAnswer($$)
   my $nfound;
   for(;;) {
     if($^O eq 'MSWin32') {
-      $nfound=FHZ_Ready($hash,$def);
+      $nfound=FHZ_Ready($hash);
     } else {
       vec($rin, $hash->{FD}, 1) = 1;
        $nfound = select($rin, undef, undef, 3); # 3 seconds timeout
@@ -535,8 +535,8 @@ sub
 FHZ_HandleWriteQueue($)
 {
   my $hash = shift;
-  my $cnt = --$hash->{QUEUECNT};
-  if($cnt > 0) {
+  if($hash->{QUEUECNT} > 0) {
+    $hash->{QUEUECNT}--;
     my $bstring = shift(@{$hash->{QUEUE}});
     FHZ_XmitLimitCheck($hash,$bstring);
     $hash->{PortObj}->write($bstring);
diff --git a/fhem/HISTORY b/fhem/HISTORY
index 641b810b4..306b6312b 100644
--- a/fhem/HISTORY
+++ b/fhem/HISTORY
@@ -306,7 +306,7 @@
     Todo: Test with IE+Adobe Plugin/Opera.
   - feature: HOWTO for webpgm2 (first chapter)
 
-Fri Jul 25 18:14:26 MEST 2008
+- Fri Jul 25 18:14:26 MEST 2008
   - Autoloading modules. In order to make module installation easier and
     to optimize memory usage, modules are loaded when the first device of a
     certain category is defined. Exceptions are the modules prefixed with 99,
@@ -323,6 +323,23 @@ Fri Jul 25 18:14:26 MEST 2008
     devices in fhem
   - feature: X10 support for pgm3
 
+- Sat Nov 15 10:23:56 MET 2008 (Rudi)
+  - Watchdog crash fixed: watchdog could insert itself more than once in the
+    internal timer queue. The first one deletes all occurances from the list,
+    but the loop over the list works on the cached keys -> the function/arg for
+    the second key is already removed.
+  - feature: X10 support for pgm3
+
 - Boris Sat Nov 15 CET 2008
   - bugfix: correct correction factors for EMEM in 15_CUL_EM.pm
 
+- Wed Dec  3 18:36:56 MET 2008 (Rudi)
+  - reorder commandref.html, so that all aspects of a device
+    (define/set/get/attributes) are in one block. This makes possible to
+    "outsource" device documentation
+  - added "mobile" flag to the CUL definition, intended for a CUR, which is
+    a remote with a battery, so it is not connected all the time to fhem.
+    Without the flag fhem will block when the CUR is disconnected.
+    Note: we have to sleep after disconnect for 5 seconds, else the Linux
+    kernel sends us a SIGSEGV, and the USB device is gone till the next reboot.
+  - the fhem CUL part documented
diff --git a/fhem/fhem.pl b/fhem/fhem.pl
index bd18b1c20..a2d727280 100755
--- a/fhem/fhem.pl
+++ b/fhem/fhem.pl
@@ -124,12 +124,15 @@ use vars qw(%defs);		# FHEM device/button definitions
 use vars qw(%attr);		# Attributes
 use vars qw(%selectlist);	# devices which want a "select"
 use vars qw(%readyfnlist);	# devices which want a "readyfn"
+use vars qw($readytimeout);	# Polling interval. UNIX: device search only
+$readytimeout = ($^O eq "MSWin32") ? 0.1 : 5.0;
 
 use vars qw(%value);		# Current values, see commandref.html
 use vars qw(%oldvalue);		# Old values, see commandref.html
 use vars qw($init_done);        #
 use vars qw($internal_data);    #
 
+
 my $server;			# Server socket
 my $currlogfile;		# logfile, without wildcards
 my $logopened = 0;              # logfile opened or using stdout
@@ -145,7 +148,7 @@ my $nextat;                     # Time when next timer will be triggered.
 my $intAtCnt=0;
 my $reread_active = 0;
 my $AttrList = "room comment";
-my $cvsid = '$Id: fhem.pl,v 1.57 2008-11-15 09:28:22 rudolfkoenig Exp $';
+my $cvsid = '$Id: fhem.pl,v 1.58 2008-12-03 16:42:48 rudolfkoenig Exp $';
 my $namedef =
   "where <name> is either:\n" .
   "- a single device name\n" .
@@ -290,7 +293,7 @@ while (1) {
   }
 
   my $timeout = HandleTimeout();
-  $timeout = 0.1 if(!defined($timeout) && keys %readyfnlist);
+  $timeout = $readytimeout if(!defined($timeout) && keys %readyfnlist);
   my $nfound = select($rout=$rin, undef, undef, $timeout);
 
   CommandShutdown(undef, undef) if($sig_term);
@@ -517,7 +520,9 @@ AnalyzeCommand($$)
     $cmd =~ s/\\\n/ /g;                 # Multi-line
     # Make life easier for oneliners:
     %value = ();
-    foreach my $d (keys %defs) { $value{$d} = $defs{$d}{STATE} }
+    foreach my $d (keys %defs) {
+      $value{$d} = $defs{$d}{STATE}
+    }
     my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime;
     my $we = (($wday==0 || $wday==6) ? 1 : 0);
     $month++;
@@ -588,11 +593,25 @@ AnalyzeCommand($$)
 sub
 devspec2array($)
 {
+  my %knownattr = ( "DEF"=>1, "STATE"=>1, "TYPE"=>1 );
+
   my ($name) = @_;
   return "" if(!defined($name));
   return $name if(defined($defs{$name}));
   my @ret;
 
+  if($name =~ m/(.*):(.*)/ && $knownattr{$1}) {
+    my $lattr = $1;
+    my $re = $2;
+    foreach my $l (sort keys %defs) {
+      push @ret, $l
+        if(!$re || ($defs{$l}{$lattr} && $defs{$l}{$lattr} =~ m/$re/));
+    }
+    return $name if(!@ret);               # No match, return the input
+    return @ret;
+  }
+
+
   foreach my $l (split(",", $name)) {   # List
     if($l =~ m/[*\[\]^\$]/) {           # Regexp
       push @ret, grep($_ =~ m/$l/, sort keys %defs);
@@ -812,6 +831,7 @@ CommandSave($$)
     my $r = $savefirst{$d};
     delete $rooms{$r}{$d};
     delete $rooms{$r} if(! %{$rooms{$r}});
+    next if(!$defs{$d});
     my $def = $defs{$d}{DEF};
     $def =~ s/;/;;/g;
     print SFH "define $d $defs{$d}{TYPE} $def\n";
@@ -824,6 +844,7 @@ CommandSave($$)
   foreach my $r (sort keys %rooms) {
     print SFH "\nsetdefaultattr" . ($r ne "~" ? " room $r" : "") . "\n";
     foreach my $d (sort keys %{$rooms{$r}} ) {
+      next if(!$defs{$d});
       next if($defs{$d}{TEMPORARY});
       next if($defs{$d}{VOLATILE});
       if($defs{$d}{DEF}) {
@@ -868,7 +889,8 @@ DoSet(@)
   my @a = @_;
 
   my $dev = $a[0];
-  return "No set implemented for $dev" if(!$modules{$defs{$dev}{TYPE}}{SetFn});
+  return "No set implemented for $dev"
+        if(!$defs{$dev} || !$modules{$defs{$dev}{TYPE}}{SetFn});
   my $ret = CallFn($dev, "SetFn", $defs{$dev}, @a);
   return $ret if($ret);
 
@@ -888,11 +910,6 @@ CommandSet($$)
   my @rets;
   foreach my $sdev (devspec2array($a[0])) {
 
-    if(!defined($defs{$sdev})) {
-      push @rets, "Please define $sdev first";
-      next;
-    }
-
     $a[0] = $sdev;
     my $ret = DoSet(@a);
     push @rets, $ret if($ret);
@@ -1166,17 +1183,22 @@ CommandList($$)
 
   } else {
 
-    foreach my $sdev (devspec2array($param)) {
+    my @list = devspec2array($param);
+    if(@list == 1) {
+      my $sdev = $list[0];
       if(!defined($defs{$sdev})) {
         $str .= "No device named $param found";
-        next;
+      } else {
+        $str .= "Internals:\n";
+        $str .= PrintHash($defs{$sdev}, 2);
+        $str .= "Attributes:\n";
+        $str .= PrintHash($attr{$sdev}, 2);
+      }
+    } else {
+      foreach my $sdev (@list) {
+        $str .= "$sdev\n";
       }
-      $str .= "Internals:\n";
-      $str .= PrintHash($defs{$sdev}, 2);
-      $str .= "Attributes:\n";
-      $str .= PrintHash($attr{$sdev}, 2);
     }
-
   }
 
   return $str;
@@ -1333,6 +1355,8 @@ sub
 getAllAttr($)
 {
   my $d = shift;
+  return "" if(!$defs{$d});
+
   my $list = $AttrList;
   $list .= " " . $modules{$defs{$d}{TYPE}}{AttrList}
         if($modules{$defs{$d}{TYPE}}{AttrList});