From 8410288c1e215183b46d77a129271c2b33903970 Mon Sep 17 00:00:00 2001 From: rudolfkoenig <> Date: Mon, 26 Nov 2007 08:27:04 +0000 Subject: [PATCH] FHT softbuffer rewrite, module reorganization, support for M232 git-svn-id: https://svn.fhem.de/fhem/trunk@109 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/CHANGED | 18 +- fhem/FHEM/00_FHZ.pm | 34 +-- fhem/FHEM/11_FHT.pm | 502 +++++++++++-------------------- fhem/FHEM/60_EM.pm | 5 +- fhem/FHEM/61_EMWZ.pm | 19 +- fhem/FHEM/62_EMEM.pm | 11 +- fhem/FHEM/70_SCIVT.pm | 190 ++++++++++++ fhem/FHEM/80_M232.pm | 268 +++++++++++++++++ fhem/FHEM/81_M232Counter.pm | 176 +++++++++++ fhem/FHEM/99_Utils.pm | 19 ++ fhem/HISTORY | 2 +- fhem/contrib/README | 26 +- fhem/docs/commandref.html | 56 ++-- fhem/docs/faq.html | 37 +-- fhem/docs/fritzbox.html | 2 +- fhem/examples/06_at | 4 +- fhem/examples/07_sunset_windowed | 2 +- fhem/fhem.pl | 46 ++- 18 files changed, 966 insertions(+), 451 deletions(-) create mode 100644 fhem/FHEM/70_SCIVT.pm create mode 100644 fhem/FHEM/80_M232.pm create mode 100644 fhem/FHEM/81_M232Counter.pm create mode 100644 fhem/FHEM/99_Utils.pm diff --git a/fhem/CHANGED b/fhem/CHANGED index f4a776db3..635cef004 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -346,8 +346,18 @@ - bugfix: FHT mode holiday_short added (9.9, Dirk) - bugfix: Modifying a device from its own trigger crashes (Klaus, 10.9) - feature: webpgm2 output reformatted - - feature: webpgm2 can display multiple plots. + - feature: webpgm2 displaying multiple plots - feature: FHT lime-protection code discovered by Dirk (7.10) - - feature: Softwarebuffer for FHT devices with queuing unsent commands and repeating commands by transmission failure (Dirk 17.10) - - feature: FHT low temperatur warning and setting for lowtemp-offset (Dirk 17.10) - - change: Change naming for state into warnings (Dirk 17.10) \ No newline at end of file + - feature: Softwarebuffer for FHT devices (Dirk 17.10) + - feature: FHT low temperatur warning and offset (Dirk 17.10) + - change: Change FHT state into warnings (Dirk 17.10) + - feature: Softwarebuffer code simplified (Rudi 22.11) + - bugfix: bug #12327 doppeltes my + - bugfix: set STATE from trigger + - bugfix: readings state vs STATE problem (xmllist/trigger) + - change: SUNRISE doc changed (99_SUNRISE.pm -> 99_SUNRISE_EL.pm) + - feature: Support for the M232 ELV device (Boris, 25.11) + +- TODO + emem -2.5kW / getDevData for emwz -1 + dummy type / dummy attribute diff --git a/fhem/FHEM/00_FHZ.pm b/fhem/FHEM/00_FHZ.pm index 984f2c4ee..a8ebe0508 100755 --- a/fhem/FHEM/00_FHZ.pm +++ b/fhem/FHEM/00_FHZ.pm @@ -68,7 +68,9 @@ FHZ_Initialize($) $hash->{SetFn} = "FHZ_Set"; $hash->{StateFn} = "FHZ_SetState"; $hash->{ParseFn} = "FHZ_Parse"; - $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 filtertimeout repeater:1,0 showtime:1,0 model:fhz1000,fhz1300 loglevel:0,1,2,3,4,5,6 softbuffer softrepeat softmaxretry"; + $hash->{AttrList}= "do_not_notify:1,0 dummy:1,0 filtertimeout repeater:1,0 " . + "showtime:1,0 model:fhz1000,fhz1300 loglevel:0,1,2,3,4,5,6" . + "fhtsoftbuffer:1,0"; } ##################################### @@ -160,24 +162,6 @@ FHZ_Get($@) return "$a[0] $a[1] => $v"; } -##################################### -# get the FHZ hardwarebuffer without logentry -# and as decimal value -sub getFhzBuffer() -{ - my $msg = "Timeout"; - - while (index($msg,"Timeout") >= 0) { # try getting FHZ buffer until no Timeout occurs - FHZ_Write($defs{FHZ}, "04", "c90185") if(!IsDummy("FHZ")); - $msg = FHZ_ReadAnswer($defs{FHZ}, "fhtbuf"); - } - - my $v = substr($msg, 16, 2); - $v = hex $v; - - return "$v"; -} - ##################################### sub FHZ_SetState($$$$) @@ -220,11 +204,15 @@ FHZ_Define($$) delete $hash->{PortObj}; delete $hash->{FD}; + my $name = $a[0]; my $dev = $a[2]; - $attr{$a[0]}{savefirst} = 1; + + $attr{$name}{savefirst} = 1; + $attr{$name}{fhtsoftbuffer} = 1; if($dev eq "none") { Log 1, "FHZ device is none, commands will be echoed only"; + $attr{$name}{dummy} = 1; return undef; } @@ -255,7 +243,7 @@ FHZ_Define($$) $hash->{DeviceName} = $dev; $hash->{PARTIAL} = ""; - DoInit($a[0]); + DoInit($name); return undef; } @@ -423,7 +411,7 @@ FHZ_Write($$$) ############## # Write the next buffer not earlier than 0.22 seconds (= 65.6ms + 10ms + # 65.6ms + 10ms + 65.6ms), else it will be discarded by the FHZ1X00 PC - InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash); + InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash, 1); } elsif($hash->{QUEUECNT} == 1) { $hash->{QUEUE} = [ $bstring ]; } else { @@ -443,7 +431,7 @@ FHZ_HandleWriteQueue($) if($cnt > 0) { my $bstring = shift(@{$hash->{QUEUE}}); $hash->{PortObj}->write($bstring); - InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash); + InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash, 1); } } diff --git a/fhem/FHEM/11_FHT.pm b/fhem/FHEM/11_FHT.pm index 2ae01e246..1a9a3e651 100755 --- a/fhem/FHEM/11_FHT.pm +++ b/fhem/FHEM/11_FHT.pm @@ -4,6 +4,10 @@ package main; use strict; use warnings; +sub doSoftBuffer($); +sub softBufferTimer($); +sub sendCommand($$$$); + my %codes = ( "0000.6" => "actuator", "00002c" => "synctime", # Not verified @@ -100,14 +104,13 @@ my %cantset = ( ); my %nosetarg = ( - "help" => 1, "refreshvalues" => 1, ); my %priority = ( - "desired-temp" => 1, - "mode" => 2, - "refreshvalues" => 3, + "desired-temp"=> 1, + "mode" => 2, + "refreshvalues"=> 3, "holiday1" => 4, "holiday2" => 5, "day-temp" => 6, @@ -115,14 +118,14 @@ my %priority = ( ); my %c2m = (0 => "auto", 1 => "manual", 2 => "holiday", 3 => "holiday_short"); -my %m2c; # Reverse c2m -my %c2b; # command->button hash (reverse of codes) -my %c2bset; # Setteable values +my %m2c; # Reverse c2m +my %c2b; # command->button hash (reverse of codes) +my %c2bset; # Setteable values my %defptr; -my $timerCheckBufferIsRunning = 0; # set to 1 if the timer is running -my $minFhzHardwareBufferSpace = 10; # min. bytes free in hardware buffer before sending commands -my $fhzHardwareBufferSpace = 0; # actual hardware buffer space in fhz +my $minFhzHardwareBuffer = 10; # min fhtbuf free bytes before sending commands +my $retryafter = 240; # in seconds, only when softbuffer is active +my $cmdcount = 0; ##################################### sub @@ -144,169 +147,103 @@ FHT_Initialize($) # 810c04b3 0909a001 1111 44006900 # 810b0402 83098301 1111 41301d # 81090421 c409c401 1111 00 - # 810c0d20 0909a001 3232 7e006724 (NYI) - $hash->{Match} = "^81..(04|09|0d)..(0909a001|83098301|c409c401).."; $hash->{SetFn} = "FHT_Set"; $hash->{StateFn} = "FHT_SetState"; $hash->{DefFn} = "FHT_Define"; $hash->{UndefFn} = "FHT_Undef"; $hash->{ParseFn} = "FHT_Parse"; - $hash->{AttrList} = "do_not_notify:0,1 model;fht80b dummy:0,1 showtime:0,1 loglevel:0,1,2,3,4,5,6"; + $hash->{AttrList} = "do_not_notify:0,1 model;fht80b dummy:0,1 " . + "showtime:0,1 loglevel:0,1,2,3,4,5,6 retrycount"; } -# Parse the incomming commands and send them via sendCommand to the FHZ -# or via toSendbuffer in the Softwarebuffer (queue) -# -sub FHT_Set($@) + + +sub +FHT_Set($@) { my ($hash, @a) = @_; my $ret = undef; - my $arg = "020183" . $hash->{CODE} . $c2bset{$a[1]}; - my $val = $a[2]; return "\"set $a[0]\" needs two parameters" if(@a < 2); - return "Unknown argument $a[1], choose one of " . + + my $name = $a[0]; + my $cmd = $a[1]; + + return "Unknown argument $cmd, choose one of " . join(" ", sort {$c2bset{$a} cmp $c2bset{$b} } keys %c2bset) - if(!defined($c2bset{$a[1]})); + if(!defined($c2bset{$cmd})); return "\"set $a[0]\" needs two parameters" - if(@a != 3 && !(@a == 2 && $nosetarg{$a[1]})); + if(@a != 3 && !(@a == 2 && $nosetarg{$cmd})); + + my $val = $a[2]; + my $arg = "020183" . $hash->{CODE} . $c2bset{$cmd}; + + if ($cmd =~ m/-temp/) { - if($a[1] eq "refreshvalues") { - - } elsif ($a[1] =~ m/-temp/) { return "Invalid temperature, use NN.N" if($val !~ m/^\d*\.?\d+$/); - - # additional check for temperature - return "Invalid temperature, must between 5.5 and 30.5" if($val < 5.5 || $val > 30.5); - + return "Invalid temperature, must between 5.5 and 30.5" + if($val < 5.5 || $val > 30.5); my $a = int($val*2); $arg .= sprintf("%02x", $a); $ret = sprintf("Rounded temperature to %.1f", $a/2) if($a/2 != $val); - $val = sprintf("%.1f", $a/2) if($a/2 != $val); - $val = sprintf("%.1f", $val); + $val = sprintf("%.1f", $a/2); + + } elsif($cmd =~ m/-from/ || $cmd =~ m/-to/) { - } elsif($a[1] =~ m/-from/ || $a[1] =~ m/-to/) { return "Invalid timeformat, use HH:MM" if($val !~ m/^([0-2]\d):([0-5]\d)/); my $a = ($1*6) + ($2/10); $arg .= sprintf("%02x", $a); my $nt = sprintf("%02d:%02d", $1, ($2/10)*10); - $val = $nt if($nt ne $val); $ret = "Rounded time to $nt" if($nt ne $val); + $val = $nt; + + } elsif($cmd eq "mode") { - } elsif($a[1] eq "mode") { return "Invalid mode, use one of " . join(" ", sort keys %m2c) if(!defined($m2c{$val})); $arg .= sprintf("%02x", $m2c{$val}); - } elsif ($a[1] eq "lowtemp-offset") { - return "Invalid lowtemperature-offset, use N" if($val !~ m/^\d*\.?\d+$/); + } elsif ($cmd eq "lowtemp-offset") { - # additional check for temperature - return "Invalid lowtemperature-offset, must between 1 and 5" if($val < 1 || $val > 5); - - my $a = int($val); - $arg .= sprintf("%02x", $a); - $ret = sprintf("Rounded temperature to %d.0", $a) if($a != $val); - $val = "$a.0"; + return "Invalid lowtemperature-offset, must between 1 and 5" + if($val !~ m/^[1-5]$/); + $arg .= sprintf("%02x", $val); + $val = "$val.0"; } else { # Holiday1, Holiday2 - $arg .= sprintf("%02x", $val); + + $arg .= sprintf("%02x", $val) if(defined($val)); + } - - my $dev = $hash->{CODE}; - my $def = $defptr{$dev}; - my $name = $def->{NAME}; - my $type = $a[1]; - my $sbCount = keys(%{$def->{SENDBUFFER}}); # Count of sendbuffer - - # get firsttime hardware buffer of FHZ if $fhzHardwareBufferSpace not set - $fhzHardwareBufferSpace = getFhzBuffer () if ($fhzHardwareBufferSpace == 0); - - # set default values for config value attr FHZ softbuffer - $attr{FHZ}{softbuffer} = 1 if (!defined($attr{FHZ}{softbuffer})); $val = "" if (!defined($val)); - if ( ($sbCount == 0 && $fhzHardwareBufferSpace >= $minFhzHardwareBufferSpace) || $attr{FHZ}{softbuffer} == 0) { - sendCommand ($hash, $arg, $name, $type, $val); # send command direct to FHZ + my $ioname = $hash->{IODev}->{NAME}; + if($attr{$ioname} && $attr{$ioname}{fhtsoftbuffer}) { + + my $io = $hash->{IODev}; + my %h = (HASH => $hash, CMD => $cmd, VAL => $val, ARG => $arg); + + my $prio = $priority{$cmd}; + $prio = "9" if(!$prio); + my $key = $prio . ":" . gettimeofday() . ":" . $cmdcount++; + + $io->{SOFTBUFFER}{$key} = \%h; + doSoftBuffer($io); + } else { - Log GetLogLevel($name,2), "FHT set $name $type $val (Enqueue to buffer)" if ($fhzHardwareBufferSpace >= $minFhzHardwareBufferSpace); + sendCommand($hash, $cmd, $val, $arg); - Log GetLogLevel($name,2), "Can't send command set $name $type $val. " . - "No space left in FHZ hardware buffer." if($fhzHardwareBufferSpace < $minFhzHardwareBufferSpace); - - } - - # only if softbuffer not disabled via config - if ($attr{FHZ}{softbuffer} == 1) { - toSendbuffer ($hash, $type, $val, $arg, "", 0); # send command also to buffer - - if ($timerCheckBufferIsRunning == 0 && $init_done) { - $timerCheckBufferIsRunning = 1; # set $timerCheckBufferIsRunning to 1 to remeber a timer is running - InternalTimer(gettimeofday()+70, "timerCheckBuffer", $hash); # start internal Timer to periodical check the buffer - } } return $ret; } -# Send command to FHZ -# -sub sendCommand ($$$$$) -{ - - my ($hash, $arg, $name, $type, $val) = @_; - - if($type eq "refreshvalues") { - # This is special. Without the sleep the next FHT won't send its data - if(!IsDummy($name)) { - my $havefhz; - $havefhz = 1 if($hash->{IODev} && defined($hash->{IODev}->{FD})); - - IOWrite($hash, "04", $arg); - sleep(1) if($havefhz); - IOWrite($hash, "04", "c90185"); # Check the fht buffer - sleep(1) if($havefhz); - } - } else { - IOWrite($hash, "04", $arg) if(!IsDummy($name)); - } - - Log GetLogLevel($name,2), "FHT set $name $type $val"; - - # decrease $fhzHardwareBufferSpace for each command sending to the FHZ - $fhzHardwareBufferSpace = $fhzHardwareBufferSpace -5 if(!IsDummy($name)); -} - - -sub resendCommand ($) -{ - - my ($buffer) = @_; - my $hash = $buffer->{HASH}; - my $dev = $hash->{CODE}; - my $def = $defptr{$dev}; - my $nRetry = $buffer->{RETRY} + 1; - - if ($fhzHardwareBufferSpace > $minFhzHardwareBufferSpace) { - Log GetLogLevel($def->{NAME},2), "Resending command to FHT set " . $def->{NAME} . " " . $buffer->{TYPE} . " " . $buffer->{VAL} . - " (Retry $nRetry / ". $attr{FHZ}{softmaxretry} . ")"; - - sendCommand ($buffer->{HASH}, $buffer->{ARG}, $buffer->{NAME}, $buffer->{TYPE}, $buffer->{VAL}); - toSendbuffer ($buffer->{HASH}, $buffer->{TYPE}, $buffer->{VAL}, $buffer->{ARG}, $buffer->{KEY}, $nRetry); # send command also to buffer - - } else { - Log GetLogLevel($def->{NAME},2), "Can't send command \"set " . $def->{NAME} . " " . $buffer->{TYPE} . " " . $buffer->{VAL} . - "\". No space in FHZ hardware buffer left. Resending next time if free bufferspace available."; - } -} - - ##################################### sub FHT_SetState($$$$) @@ -331,8 +268,10 @@ FHT_Define($$) if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/i); + $hash->{CODE} = $a[2]; $hash->{CODE} = $a[2]; $defptr{$a[2]} = $hash; + $attr{$a[0]}{retrycount} = 3; AssignIoPort($hash); @@ -362,42 +301,29 @@ FHT_Parse($$) my $val = substr($msg, 26, 2) if(length($msg) > 26); my $confirm = 0; - $fhzHardwareBufferSpace = getFhzBuffer () if ($fhzHardwareBufferSpace == 0); - if(!defined($defptr{$dev})) { Log 3, "FHT Unknown device $dev, please define it"; return "UNDEFINED FHT $dev"; } my $def = $defptr{$dev}; + my $name = $def->{NAME}; # Unknown, but don't want report it. Should come with c409c401 return "" if($cde eq "00"); if(length($cde) < 6) { - my $name = $def->{NAME}; Log GetLogLevel($name,2), "FHT Unknown code from $name : $cde"; $def->{CHANGED}[0] = "unknown code $cde"; return $name; } - if(!$val) { # This is a confirmation message. We reformat it so that # it looks like a real message, and let the rest parse it - Log 4, "FHT $def->{NAME} confirmation: $cde)"; + Log 4, "FHT $name confirmation: $cde)"; $val = substr($cde, 2, 2); - - # get the free hardware buffer space in the FHZ after each confirmation message - $fhzHardwareBufferSpace = hex substr($cde, 4, 2); - - # increase $fhzHardwareBufferSpace at 5 because the confirmed command is deleted in the FHZ after confirmation - $fhzHardwareBufferSpace = $fhzHardwareBufferSpace + 5; - Log 4, "FHZ new FHT Buffer: $fhzHardwareBufferSpace"; - $cde = substr($cde, 0, 2) . "0069"; - - # set help var to remember this is a confirmation $confirm = 1; } @@ -412,9 +338,9 @@ FHT_Parse($$) $val = hex($val); if(!$type) { - Log 4, "FHT $def->{NAME} (Unknown: $cde => $val)"; + Log 4, "FHT $name (Unknown: $cde => $val)"; $def->{CHANGED}[0] = "unknown $cde: $val"; - return $def->{NAME}; + return $name; } my $tn = TimeNow(); @@ -427,7 +353,7 @@ FHT_Parse($$) } elsif($type eq "lime-protection") { $val = sprintf("(actuator: %02d%%)", int(100*$val/255 + 0.5)); } elsif($cde ge "140069" && $cde le "2f0069") { # Time specs - Log 5, "FHT $def->{NAME} ($type: $val)"; + Log 5, "FHT $name ($type: $val)"; return "" if($val == 144); # Empty, forget it my $hour = $val / 6; my $min = ($val % 6) * 10; @@ -463,21 +389,12 @@ FHT_Parse($$) } elsif($type eq "warnings") { - my @nVal; - $nVal[0] = "Battery low" if ($val & 1); - $nVal[1] = "Window open" if ($val & 32); - $nVal[2] = "Fault on window sensor" if ($val & 16); - $nVal[3] = "Temperature to low" if ($val & 2); - - if ($val > 0) { - $val = ""; - foreach (@nVal) { - $val .= "$_; " if (defined($_)); - } - $val = substr($val, 0, length($val)-2); - } else { - $val = "none"; - } + my $nVal; + if($val & 1) { $nVal = "Battery low"; } + if($val & 2) { $nVal .= "; " if($nVal); $nVal .= "Temperature too low"; } + if($val &32) { $nVal .= "; " if($nVal); $nVal .= "Window open"; } + if($val &16) { $nVal .= "; " if($nVal); $nVal .= "Fault on window sensor"; } + $val = $nVal? $nVal : "none"; } elsif($type eq "lowtemp-offset") { $val = sprintf("%d.0 (Celsius)", $val) @@ -489,194 +406,127 @@ FHT_Parse($$) $def->{READINGS}{$type}{TIME} = $tn; $def->{READINGS}{$type}{VAL} = $val; - - Log 4, "FHT $def->{NAME} ($type: $val)"; - - ########################################################################### - # here starts the processing the confirmation to control the softwarebuffer - # - - $attr{FHZ}{softbuffer} = 1 if (!defined($attr{FHZ}{softbuffer})); # set default values for config value attr FHZ softbuffer - - my $sbCount = keys(%{$def->{SENDBUFFER}}); # count the existing sendbuffer - my $nsCount = keys(%{$def->{NOTSEND}}); # count the existing failbuffer - - if ($confirm && ($sbCount > 0 || $nsCount > 0) && $attr{FHZ}{softbuffer} == 1) { - $type = "refreshvalues" if ($type eq "init"); - - my ($sbPr, $sbTs); - my $sbType = ""; - my $sbVal; - my $dKey; - - my ($val2) = split (/\s/, $val); - - # if the confirmation message for a command recive to late - # (the command moved to the notsend list yet) - # found the specific command ond delete them from the notsend list - foreach my $c (sort keys %{$def->{NOTSEND}}) { # go through the notsend list - ($sbPr, $sbTs, $sbType) = split (/:/, $c); - $sbVal = $def->{NOTSEND}->{$c}{VAL}; - $dKey = $c; - - $sbVal = $val2 if ($type eq "refreshvalues"); # refreshvalues have no value - if ($sbType eq $type && $sbVal eq $val2) { - - Log GetLogLevel($def->{NAME},2), "FHT $def->{NAME} late - confirmation ". - "($sbType: $sbVal) (delete from NOTSEND)"; - - delete($def->{NOTSEND}{$dKey}); # delete command from notsend list - last; # we can leave the loop because the command was deleted from the list - } - } - - # get the next entry from the buffer queue - foreach my $c (sort keys %{$def->{SENDBUFFER}}) { - ($sbPr, $sbTs, $sbType) = split (/:/, $c); - $sbVal = $def->{SENDBUFFER}->{$c}{VAL}; - $dKey = $c; - last; # exit foreach because we need the first entry only - } - - $sbVal = $val2 if ($type eq "refreshvalues"); # refreshvalues have no value - - # if the actual confirmation message part of the first command in the queue - if ($sbType eq $type && $sbVal eq $val2) { - delete($def->{SENDBUFFER}{$dKey}); # this buffer entry can deleted - - foreach my $c (sort keys %{$def->{SENDBUFFER}}) { # get the next buffer entry - my $nType = $def->{SENDBUFFER}->{$c}{TYPE}; - my $nArg = $def->{SENDBUFFER}->{$c}{ARG}; - my $nName = $def->{SENDBUFFER}->{$c}{NAME}; - my $nHash = $def->{SENDBUFFER}->{$c}{HASH}; - my $nVal = $def->{SENDBUFFER}->{$c}{VAL}; - my $nKey = $def->{SENDBUFFER}->{$c}{KEY}; - - sendCommand ($nHash, $nArg, $nName, $nType, $nVal); # nächsten Buffereintrag senden - toSendbuffer ($nHash, $nType, $nVal, $nArg, $nKey, 0); # send command also to buffer - - last; # exit foreach because we need the next entry only - } - } - } - - # - # end processing confirmation to control the softwarebuffer - ########################################################################### - $def->{CHANGED}[0] = "$type: $val"; $def->{STATE} = "$type: $val" if($type eq "measured-temp"); - return $def->{NAME}; -} -# check are commands in softwarebuffer -# ans send the next command to the FHZ -sub timerCheckBuffer ($) -{ + Log 4, "FHT $name ($type: $val)"; - Log 4, "Timer (Checking for unsend FHT commands)"; - - my ($hash) = @_; - my $bufCount = 0; # help counter - my $now = gettimeofday(); - my $ts = time; - - # set default values for config value attr FHZ softbuffer - $attr{FHZ}{softrepeat} = 240 if (!defined($attr{FHZ}{softrepeat})); - $attr{FHZ}{softmaxretry} = 3 if (!defined($attr{FHZ}{softmaxretry})); - - # loop to process all FHT devices - foreach my $d (keys %defptr) { - my $def = $defptr{$d}; # the actual FHT device - - # process all buffer entries - foreach my $c (sort keys %{$def->{SENDBUFFER}}) { - my ($rPr, undef, $rType) = split (/:/, $c); # priority and type - my $rVal = $def->{SENDBUFFER}{$c}{VAL}; # value - my $rTs = $def->{SENDBUFFER}{$c}{SENDTIME}; # the time of the sending moment to the FHT - my $rRetry = $def->{SENDBUFFER}{$c}{RETRY}; # retry counter - $rRetry ++ if ($fhzHardwareBufferSpace > $minFhzHardwareBufferSpace); # increase retrycounter if enough hardwarebuffer available - my $rKey = $c; # the bufferkey - - $rVal = "" if (!defined($rVal)); # set value to "" if value not defined (e.g. "refreshvalues" have no value) - $bufCount ++; # increase $bufCount - - my $buffer = $def->{SENDBUFFER}{$c}; # actual buffer entry - - # if the forst command in buffer to old, resend them again to the FHZ - if ($ts-$rTs > $attr{FHZ}{softrepeat}) { - if ($rRetry <= $attr{FHZ}{softmaxretry}) { # resend the command only if the max resend amount not reached - resendCommand ($buffer); # resend the actual command - } else { - # command resend fail after "softmaxretry" attempt to send - Log GetLogLevel($def->{NAME},2), $def->{NAME} . " $rType $rVal no confirmation after $rRetry retry"; - $def->{NOTSEND}{$rKey} = $def->{SENDBUFFER}{$rKey}; # put the buffer entry to the notsend list - $def->{NOTSEND}{$rKey}{RETRY} = $rRetry; - delete($def->{SENDBUFFER}{$rKey}); # delete command from buffer queue - } + ################################ + # Softbuffer: deleted confirmed commands + my $io = $hash->{IODev}; + if($confirm && keys(%{$io->{SOFTBUFFER}})) { + my $found; + foreach my $key (sort keys %{$io->{SOFTBUFFER}}) { + my $h = $io->{SOFTBUFFER}{$key}; + if($h->{HASH}->{NAME} eq $name && + $h->{CMD} eq $type) { + $found = $key; + last; } - last # exit foreach because we need only the first buffer value } + delete($io->{SOFTBUFFER}{$found}) if($found); } - if ($bufCount > 0) { - Log 4, "Refresh FHT resend timer"; - InternalTimer(gettimeofday()+70, "timerCheckBuffer", $hash); # restart the internal Timer if any buffer contains commands - } else { - $timerCheckBufferIsRunning = 0; # remember timer is not running anymore + return $name; +} + + +# Check the softwarebuffer and send/resend commands +sub +doSoftBuffer($) +{ + my ($io) = @_; + + my $now = gettimeofday(); + + my $count = 0; + foreach my $key (keys %{ $io->{SOFTBUFFER} }) { + + $count++; + my $h = $io->{SOFTBUFFER}{$key}; + my $name = $h->{HASH}->{NAME}; + + if($h->{NSENT}) { + next if($now-$h->{SENDTIME} < $retryafter); + my $retry = $attr{$name}{retrycount}; + if($h->{NSENT} > $retry) { + Log GetLogLevel($name,2), "$name set $h->{CMD} $h->{VAL}: ". + "no confirmation after $h->{NSENT} tries, giving up"; + delete($io->{SOFTBUFFER}{$key}); + next; + } + } + + next if(getFhzBuffer($io) < $minFhzHardwareBuffer); + sendCommand($h->{HASH}, $h->{CMD}, $h->{VAL}, $h->{ARG}); + $h->{SENDTIME} = $now; + $h->{NSENT}++; + + } + + if($count && !$io->{SOFTBUFFERTIMER}) { + $io->{SOFTBUFFERTIMER} = 1; + InternalTimer(gettimeofday()+30, "softBufferTimer", $io, 0); } } -# set given command tothe internal software buffer -# each command queued until the previous command become a confirmation -# -sub toSendbuffer ($$$$) +##################################### +# Wrapper for the InternalTimer +sub +softBufferTimer($) { + my ($io) = @_; + delete($io->{SOFTBUFFERTIMER}); + doSoftBuffer($io); +} - my ($hash, $type, $val, $arg, $nBufferKey, $retry) = @_; - if (!$init_done || $attr{FHZ}{softbuffer} == 0) { - return +##################################### +# get the FHZ hardwarebuffer without logentry as decimal value +sub +getFhzBuffer($) +{ + my ($io) = @_; + my $count = 0; + + return $minFhzHardwareBuffer if(IsDummy($io->{NAME})); + + Log 4, "getFhzBuffer"; + for(;;) { + FHZ_Write($io, "04", "c90185"); + + my $msg = FHZ_ReadAnswer($io, "fhtbuf"); + + return hex(substr($msg, 16, 2)) if($msg && $msg =~ m/^[0-9]+$/); + return 0 if($count++ > 5); } +} - my $dev = $hash->{CODE}; - my $def = $defptr{$dev}; - - my $tn = TimeNow(); # Readable time - my $ts = time; # Unix timestamp - my $pr = 9; # Default priority for command - my $sendTime = 0; # Timestamp for last sending command - my $sbCount = keys(%{$def->{SENDBUFFER}}); # Count of sendbuffer +##################################### +# Send FHZ command +sub +sendCommand($$$$) +{ + my ($hash, $cmd, $val, $arg) = @_; + my $name = $hash->{NAME}; - $pr = $priority{$type} if (defined($priority{$type})); # get priority for specific command type - $val = "" if (!defined($val)); + if($cmd eq "refreshvalues") { + + # This is special. Without the sleep the next FHT won't send its data + if(!IsDummy($name)) { + my $havefhz = ($hash->{IODev} && defined($hash->{IODev}->{FD})); + IOWrite($hash, "04", $arg); + sleep(1) if($havefhz); + IOWrite($hash, "04", "c90185"); # Check the fht buffer + sleep(1) if($havefhz); + } + + } else { + + IOWrite($hash, "04", $arg) if(!IsDummy($name)); - if ($sbCount == 0) { - $pr = 0; # First command in buffer have always priority 0 (highest) - $sendTime = $ts; } - - my $bufferKey = "$pr:$ts:$type"; #Default bufferkey - - # if bufferkey existing. delete the entry and save the entry with a new buffer - if ($nBufferKey ne "") { - $sendTime = $ts; - $bufferKey = $nBufferKey; - ($pr, $ts, $type) = split (/:/, $bufferKey); - delete($def->{SENDBUFFER}{$bufferKey}); # delete "old" bufferentry - - $bufferKey = "0:$ts:$type"; # new bufferkey für new bufferentry - } - - $def->{SENDBUFFER}{$bufferKey}{TIME} = $tn; - $def->{SENDBUFFER}{$bufferKey}{VAL} = $val; - $def->{SENDBUFFER}{$bufferKey}{NAME} = $def->{NAME}; - $def->{SENDBUFFER}{$bufferKey}{TYPE} = $type; - $def->{SENDBUFFER}{$bufferKey}{ARG} = $arg; - $def->{SENDBUFFER}{$bufferKey}{SENDTIME} = $sendTime; - $def->{SENDBUFFER}{$bufferKey}{RETRY} = $retry; - $def->{SENDBUFFER}{$bufferKey}{KEY} = $bufferKey; - $def->{SENDBUFFER}{$bufferKey}{HASH} = $hash; + Log GetLogLevel($name,2), "FHT set $name $cmd $val"; } 1; diff --git a/fhem/FHEM/60_EM.pm b/fhem/FHEM/60_EM.pm index 1b2e2cbde..53d21f29a 100755 --- a/fhem/FHEM/60_EM.pm +++ b/fhem/FHEM/60_EM.pm @@ -43,11 +43,14 @@ EM_Define($$) delete $hash->{PortObj}; delete $hash->{FD}; + my $name = $a[0]; my $dev = $a[2]; - $attr{$a[0]}{savefirst} = 1; + + $attr{$name}{savefirst} = 1; if($dev eq "none") { Log 1, "EM device is none, commands will be echoed only"; + $attr{$name}{dummy} = 1; return undef; } diff --git a/fhem/FHEM/61_EMWZ.pm b/fhem/FHEM/61_EMWZ.pm index 8fee902e1..2ffdab79a 100755 --- a/fhem/FHEM/61_EMWZ.pm +++ b/fhem/FHEM/61_EMWZ.pm @@ -31,15 +31,17 @@ EMWZ_GetStatus($) my ($hash) = @_; if(!$hash->{LOCAL}) { - InternalTimer(gettimeofday()+300, "EMWZ_GetStatus", $hash); + InternalTimer(gettimeofday()+300, "EMWZ_GetStatus", $hash, 0); } my $dnr = $hash->{DEVNR}; my $name = $hash->{NAME}; + return "Empty status: dummy IO device" if(IsIoDummy($name)); + my $d = IOWrite($hash, sprintf("7a%02x", $dnr-1)); if(!defined($d)) { - my $msg = "EMWZ $name read error"; + my $msg = "EMWZ $name read error (GetStatus 1)"; Log GetLogLevel($name,2), $msg; return $msg; } @@ -54,7 +56,7 @@ EMWZ_GetStatus($) my $pulses=w($d,13); my $ec=w($d,49) / 10; if($ec <= 0) { - my $msg = "EMWZ read error"; + my $msg = "EMWZ read error (GetStatus 2)"; Log GetLogLevel($name,2), $msg; return $msg; } @@ -124,10 +126,12 @@ EMWZ_Set($@) return $u if(int(@a) != 3); + my $name = $hash->{NAME}; + return "" if(IsIoDummy($name)); + my $v = $a[2]; my $d = $hash->{DEVNR}; my $msg; - my $name = $hash->{NAME}; if($a[1] eq "price") { $v *= 10000; # Make display and input the same @@ -141,9 +145,10 @@ EMWZ_Set($@) return $u; } + my $ret = IOWrite($hash, $msg); if(!defined($ret)) { - my $msg = "EMWZ $name read error"; + my $msg = "EMWZ $name read error (Set)"; Log GetLogLevel($name,2), $msg; return $msg; } @@ -170,11 +175,7 @@ EMWZ_Define($$) AssignIoPort($hash); - # InternalTimer blocks if init_done is not true - my $oid = $init_done; - $init_done = 1; EMWZ_GetStatus($hash); - $init_done = $oid; return undef; } diff --git a/fhem/FHEM/62_EMEM.pm b/fhem/FHEM/62_EMEM.pm index 90841c804..9daaa1ad9 100755 --- a/fhem/FHEM/62_EMEM.pm +++ b/fhem/FHEM/62_EMEM.pm @@ -6,7 +6,6 @@ use warnings; use Time::HiRes qw(gettimeofday); sub EMEM_Get($@); -sub EMEM_Set($@); sub EMEM_Define($$); sub EMEM_GetStatus($); @@ -29,15 +28,17 @@ EMEM_GetStatus($) my ($hash) = @_; if(!$hash->{LOCAL}) { - InternalTimer(gettimeofday()+300, "EMEM_GetStatus", $hash); + InternalTimer(gettimeofday()+300, "EMEM_GetStatus", $hash, 0); } my $dnr = $hash->{DEVNR}; my $name = $hash->{NAME}; + return "Empty status: dummy IO device" if(IsIoDummy($name)); + my $d = IOWrite($hash, sprintf("7a%02x", $dnr-1)); if(!defined($d)) { - my $msg = "EMEM $name read error"; + my $msg = "EMEM $name read error (GetStatus 1)"; Log GetLogLevel($name,2), $msg; return $msg; } @@ -122,11 +123,7 @@ EMEM_Define($$) AssignIoPort($hash); - # InternalTimer blocks if init_done is not true - my $oid = $init_done; - $init_done = 1; EMEM_GetStatus($hash); - $init_done = $oid; return undef; } diff --git a/fhem/FHEM/70_SCIVT.pm b/fhem/FHEM/70_SCIVT.pm new file mode 100644 index 000000000..cd4f743f2 --- /dev/null +++ b/fhem/FHEM/70_SCIVT.pm @@ -0,0 +1,190 @@ +############################################## +package main; + +use strict; +use warnings; +use Device::SerialPort; + +##################################### +sub +SCIVT_Initialize($) +{ + my ($hash) = @_; + +# Consumer + $hash->{DefFn} = "SCIVT_Define"; + $hash->{GetFn} = "SCIVT_Get"; + $hash->{AttrList}= "model:SCD loglevel:0,1,2,3,4,5,6"; +} + +##################################### +sub +SCIVT_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Define the serial device as a parameter, use none for a fake device" + if(@a != 3); + $hash->{STATE} = "Initialized"; + + my $dev = $a[2]; + + Log 1, "SCIVT device is none, commands will be echoed only" + if($dev eq "none"); + + if($dev ne "none") { + Log 2, "SCIVT opening device $dev"; + my $po = new Device::SerialPort ($dev); + return "Can't open $dev: $!" if(!$po); + Log 2, "SCIVT opened device $dev"; + $po->close(); + } + + $hash->{DeviceName} = $dev; + + SCIVT_GetStatus($hash); + return undef; +} + + +##################################### +sub +SCIVT_Get($@) +{ + my ($hash, @a) = @_; + + return "get for an SCIVT device needs exactly one parameter" if(@a != 2); + + my $v; + if($a[1] eq "data") { + $v = SCIVT_GetLine($hash->{DeviceName}); + $v =~ s/[\r\n]//g; # Delete the NewLine + } else { + return "Unknown argument $a[1], must be data"; + } + + $hash->{READINGS}{$a[1]}{VAL} = $v; + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + + return "$a[0] $a[1] => $v"; +} + +##################################### +sub +SCIVT_GetStatus($) +{ + my ($hash) = @_; + + # Call us in 5 minutes again. + InternalTimer(gettimeofday()+300, "SCIVT_GetStatus", $hash, 0); + + my $dnr = $hash->{DEVNR}; + my $name = $hash->{NAME}; + + my %vals; + my $result = SCIVT_GetLine($hash->{DeviceName}); + + if(!defined($result)) + { + Log GetLogLevel($name,2), "SCIVT read error, retry"; + $result = SCIVT_GetLine($hash->{DeviceName}); + } + + if(!defined($result)) + { + Log GetLogLevel($name,2), "SCIVT read error, abort"; + $hash->{STATE} = "timeout"; + return $hash->{STATE}; + } + if (length($result) < 10) + { + Log GetLogLevel($name,2), "SCIVT incomplete line ($result)"; + $hash->{STATE} = "incomplete"; + } + else + { + $result =~ s/^.*R://; + $result =~ s/[\r\n ]//g; + Log GetLogLevel($name,2), "SCIVT $result (raw)"; + $result=~ s/,/./g; + my @data = split(";", $result); + + my @names = ("Vs", "Is", "Temp", "minV", "maxV", "minI", "maxI"); + my $tn = TimeNow(); + for(my $i = 0; $i < int(@names); $i++) { + $hash->{CHANGED}[$i] = "$names[$i]: $data[$i]"; + $hash->{READINGS}{$names[$i]}{TIME} = $tn; + $hash->{READINGS}{$names[$i]}{VAL} = $data[$i]; + } + + DoTrigger($name, undef) if($init_done); + + $result =~ s/;/ /g; + $hash->{STATE} = "$result"; + } + + return $hash->{STATE}; +} + +##################################### +sub +SCIVT_GetLine($) +{ + my $retry = 0; + my ($dev) = @_; + + return "R:13,66; 0,0;30;13,62;15,09;- 0,2; 2,8;\n" + if($dev eq "none"); # Fake-mode + + my $serport = new Device::SerialPort ($dev); + if(!$serport) { + Log 1, "SCIVT: Can't open $dev: $!"; + return undef; + } + $serport->reset_error(); + $serport->baudrate(1200); + $serport->databits(8); + $serport->parity('none'); + $serport->stopbits(1); + $serport->handshake('none'); + + my $rm = "SCIVT timeout reading the answer"; + my $data=""; + + $serport->write('F'); + sleep(1); + + for(;;) + { + my ($rout, $rin) = ('', ''); + vec($rin, $serport->FILENO, 1) = 1; + my $nfound = select($rout=$rin, undef, undef, 3.0); + + if($nfound < 0) { + $rm = "SCIVT Select error $nfound / $!"; + goto DONE; + } + last if($nfound == 0); + + my $buf = $serport->input(); + if(!defined($buf) || length($buf) == 0) { + $rm = "SCIVT EOF on $dev"; + goto DONE; + } + + + $data .= $buf; + if($data =~ m/[\r\n]/) { # Newline received + $serport->close(); + return $data; + } + } + +DONE: + $serport->close(); + Log 3, "SCIVT $rm"; + return undef; +} + +1; diff --git a/fhem/FHEM/80_M232.pm b/fhem/FHEM/80_M232.pm new file mode 100644 index 000000000..98aa53589 --- /dev/null +++ b/fhem/FHEM/80_M232.pm @@ -0,0 +1,268 @@ +############################################## +package main; + +use strict; +use warnings; +use Device::SerialPort; + +sub M232Write($$); +sub M232GetData($$); + +##################################### +sub +M232_Initialize($) +{ + my ($hash) = @_; + +# Provider + $hash->{WriteFn} = "M232_Write"; + $hash->{Clients} = ":M232Counter:"; + +# Consumer + $hash->{DefFn} = "M232_Define"; + $hash->{UndefFn} = "M232_Undef"; + $hash->{GetFn} = "M232_Get"; + $hash->{SetFn} = "M232_Set"; + $hash->{AttrList}= "model:m232 loglevel:0,1,2,3,4,5"; +} + +##################################### +sub +M232_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + $hash->{STATE} = "Initialized"; + + delete $hash->{PortObj}; + delete $hash->{FD}; + + my $dev = $a[2]; + $attr{$a[0]}{savefirst} = 1; + + if($dev eq "none") { + Log 1, "M232 device is none, commands will be echoed only"; + return undef; + } + + Log 3, "M232 opening device $dev"; + my $po = new Device::SerialPort ($dev); + return "Can't open $dev: $!" if(!$po); + Log 3, "M232 opened device $dev"; + $po->close(); + + $hash->{DeviceName} = $dev; + return undef; +} + +##################################### +sub +M232_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) + { + Log GetLogLevel($name,2), "deleting port for $d"; + delete $defs{$d}{IODev}; + } + } + return undef; +} + + +##################################### +sub +M232_Set($@) +{ + my ($hash, @a) = @_; + my $u1 = "Usage: set auto \n" . + "set stop\n" . + "set start"; + + return $u1 if(int(@a) < 2); + my $msg; + + if($a[1] eq "auto") { + return $u1 if(int(@a) !=3); + my $value= $a[2]; + my @legal= (0..5,"none"); + if(!grep($value eq $_, @legal)) { + return "Illegal value $value, possible values: @legal"; + } + if($value eq "none") { $value= 0; } else { $value+=1; } + $msg= "M" . $value; + } + + elsif($a[1] eq "start") { + return $u1 if(int(@a) !=2); + $msg= "Z1"; + } + + elsif($a[1] eq "stop") { + return $u1 if(int(@a) !=2); + $msg= "Z0"; + } + + else { return $u1; } + + my $d = M232GetData($hash->{DeviceName}, $msg); + return "Read error" if(!defined($d)); + return $d; +} + +##################################### +sub +M232_Get($@) +{ + + my ($hash, @a) = @_; + my $u1 = "Usage: get [an0..an5]\n" . + "get [io0..io7]\n" . + "get octet\n" . + "get counter"; + + return $u1 if(int(@a) != 2); + + my $name= $a[0]; + my $reading= $a[1]; + my $msg; + my $retval; + + + if($reading eq "counter") { + $msg= "z"; + my $d = M232GetData($hash->{DeviceName}, $msg); + return "Read error" if(!defined($d)); + my $count= hex $d; + $retval= $count; + } + + elsif($reading =~ /^an[0-5]$/) { + $msg= "a" . substr($reading,2,1); + my $d = M232GetData($hash->{DeviceName}, $msg); + return "Read error" if(!defined($d)); + my $voltage= hex substr($d,0,3); + my $iscurrent= substr($d,3,1); + $retval= $voltage; # . " " . $iscurrent; + } + + elsif($reading =~ /^io[0-7]$/) { + $msg= "d" . substr($reading,2,1); + my $d = M232GetData($hash->{DeviceName}, $msg); + return "Read error" if(!defined($d)); + my $state= hex $d; + $retval= $state; + } + + elsif($reading eq "octet") { + $msg= "w"; + my $d = M232GetData($hash->{DeviceName}, $msg); + return "Read error" if(!defined($d)); + my $state= hex $d; + $retval= $state; + } + + else { return $u1; } + + $hash->{READINGS}{$reading}{VAL}= $retval; + $hash->{READINGS}{$reading}{TIME}= TimeNow(); + + return "$name $reading => $retval"; + +} + +##################################### +sub +M232_Write($$) +{ + my ($hash,$msg) = @_; + + return M232GetData($hash->{DeviceName}, $msg); +} + + +##################################### +sub +M232GetData($$) +{ + my ($dev, $d) = @_; + + my $MSGSTART= chr 1; + my $MSGEND= chr 13; + my $MSGACK= chr 6; + my $MSGNACK= chr 21; + + $d = $MSGSTART . $d . $MSGEND; + + my $serport = new Device::SerialPort ($dev); + if(!$serport) { + Log 3, "M232: Can't open $dev: $!"; + return undef; + } + $serport->reset_error(); + $serport->baudrate(2400); + $serport->databits(8); + $serport->parity('none'); + $serport->stopbits(1); + $serport->handshake('none'); + + Log 4, "M232: Sending $d"; + + my $rm = "M232: ?"; + + $serport->lookclear; + $serport->write($d); + + my $retval = ""; + my $status = ""; + + for(;;) { + my ($rout, $rin) = ('', ''); + vec($rin, $serport->FILENO, 1) = 1; + my $nfound = select($rout=$rin, undef, undef, 1.0); + + if($nfound < 0) { + $rm = "M232: Select error $nfound / $!"; + goto DONE; + } + last if($nfound == 0); + + my $out = $serport->read(1); + if(!defined($out) || length($out) == 0) { + $rm = "M232 EOF on $dev"; + goto DONE; + } + + if($out eq $MSGACK) { + $rm= "M232: acknowledged"; + Log 4, "M232: return value \'" . $retval . "\'"; + $status= "ACK"; + } elsif($out eq $MSGNACK) { + $rm= "M232: not acknowledged"; + $status= "NACK"; + $retval= undef; + } else { + $retval .= $out; + } + + if($status) { + $serport->close(); + Log 4, $rm; + return $retval; + } + + } + +DONE: + $serport->close(); + Log 4, $rm; + return undef; +} + +1; diff --git a/fhem/FHEM/81_M232Counter.pm b/fhem/FHEM/81_M232Counter.pm new file mode 100644 index 000000000..46011316e --- /dev/null +++ b/fhem/FHEM/81_M232Counter.pm @@ -0,0 +1,176 @@ +############################################## +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +sub M232Counter_Get($@); +sub M232Counter_Set($@); +sub M232Counter_SetBasis($@); +sub M232Counter_Define($$); +sub M232Counter_GetStatus($); + +################################### +sub +M232Counter_Initialize($) +{ + my ($hash) = @_; + + $hash->{GetFn} = "M232Counter_Get"; + $hash->{SetFn} = "M232Counter_Set"; + $hash->{DefFn} = "M232Counter_Define"; + + $hash->{AttrList} = "dummy:1,0 model;M232Counter loglevel:0,1,2,3,4,5"; +} + +################################### +sub +M232Counter_GetStatus($) +{ + my ($hash) = @_; + + if(!$hash->{LOCAL}) { + InternalTimer(gettimeofday()+60, "M232Counter_GetStatus", $hash); + } + + my $name = $hash->{NAME}; + + my $d = IOWrite($hash, "z"); + if(!defined($d)) { + my $msg = "M232Counter $name read error"; + Log GetLogLevel($name,2), $msg; + return $msg; + } + + my $tn = TimeNow(); + if(!defined($hash->{READINGS}{basis})) { + $hash->{READINGS}{basis}{VAL}= 0; + $hash->{READINGS}{basis}{TIME}= $tn; + } + if(!defined($hash->{READINGS}{count})) { + $hash->{READINGS}{count}{VAL}= 0; + $hash->{READINGS}{count}{TIME}= $tn; + } + my $count= hex $d; + if($count< $hash->{READINGS}{count}{VAL}) { + $hash->{READINGS}{basis}{VAL}+= 65536; + $hash->{READINGS}{basis}{TIME}= $tn; + } + my $value= ($hash->{READINGS}{basis}{VAL}+$count) * $hash->{FACTOR}; + + $hash->{READINGS}{count}{TIME} = $tn; + $hash->{READINGS}{count}{VAL} = $count; + $hash->{READINGS}{value}{TIME} = $tn; + $hash->{READINGS}{value}{VAL} = $value; + + $hash->{CHANGED}[0]= "value: $value"; + + if(!$hash->{LOCAL}) { + DoTrigger($name, undef) if($init_done); + } + + $hash->{STATE} = $value; + Log GetLogLevel($name,4), "M232Counter $name: $value $hash->{UNIT}"; + + return $hash->{STATE}; +} + +################################### +sub +M232Counter_Get($@) +{ + my ($hash, @a) = @_; + + return "argument is missing" if(int(@a) != 2); + + my $msg; + + if($a[1] ne "status") { + return "unknown get value, valid is status"; + } + $hash->{LOCAL} = 1; + my $v = M232Counter_GetStatus($hash); + delete $hash->{LOCAL}; + + return "$a[0] $a[1] => $v"; +} + +############################# +sub +M232Counter_Calibrate($@) +{ + my ($hash, $value) = @_; + my $rm= undef; + my $name = $hash->{NAME}; + + + # adjust basis + my $tn = TimeNow(); + $hash->{READINGS}{basis}{VAL}= $value / $hash->{FACTOR}; + $hash->{READINGS}{basis}{TIME}= $tn; + $hash->{READINGS}{count}{VAL}= 0; + $hash->{READINGS}{count}{TIME}= $tn; + + # recalculate value + $hash->{READINGS}{value}{VAL} = $value; + $hash->{READINGS}{value}{TIME} = $tn; + + # reset counter + my $ret = IOWrite($hash, "Z1"); + if(!defined($ret)) { + my $rm = "M232Counter $name read error"; + Log GetLogLevel($name,2), $rm; + } + + return $rm; +} + +############################# +sub +M232Counter_Set($@) +{ + my ($hash, @a) = @_; + my $u = "Usage: set value "; + + return $u if(int(@a) != 3); + my $reading= $a[1]; + my $value = $a[2]; + return $u unless($reading eq "value"); + + my $rm= M232Counter_Calibrate($hash, $value); + + return undef; +} + + +############################# +sub +M232Counter_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "syntax: define M232Counter [unit] [multiplicator]" + if(int(@a) < 2 && int(@a) > 4); + + my $unit= ((int(@a) > 2) ? $a[2] : "ticks"); + my $factor= ((int(@a) > 3) ? $a[3] : 1.0); + $hash->{UNIT}= $unit; + $hash->{FACTOR}= $factor; + + AssignIoPort($hash); + + # InternalTimer blocks if init_done is not true + my $oid = $init_done; + $init_done = 1; + + if(!$hash->{LOCAL}) { + InternalTimer(gettimeofday()+60, "M232Counter_GetStatus", $hash); + } + + $init_done = $oid; + return undef; +} + +1; diff --git a/fhem/FHEM/99_Utils.pm b/fhem/FHEM/99_Utils.pm new file mode 100644 index 000000000..5820ec6ae --- /dev/null +++ b/fhem/FHEM/99_Utils.pm @@ -0,0 +1,19 @@ +package main; +use strict; +use warnings; +use POSIX; + +sub +Utils_Initialize($$) +{ + my ($hash) = @_; +} + +sub +time_str2num($) +{ + my ($str) = @_; + my @a = split("[- :]", $str); + return mktime($a[5],$a[4],$a[3],$a[2],$a[1]-1,$a[0]-1900,0,0,-1); +} + diff --git a/fhem/HISTORY b/fhem/HISTORY index 2eb786a65..7d7b4ab2c 100644 --- a/fhem/HISTORY +++ b/fhem/HISTORY @@ -156,4 +156,4 @@ repeating commands by transmission failure - FHT low temperatur warning and setting for lowtemp-offset - Change naming for state into warnings - Tagged as dirkh_20071019_0 \ No newline at end of file + Tagged as dirkh_20071019_0 diff --git a/fhem/contrib/README b/fhem/contrib/README index cec8ef0fd..3d906ad32 100755 --- a/fhem/contrib/README +++ b/fhem/contrib/README @@ -1,12 +1,28 @@ +- 70_SCIVT.pm + Support for an SCD series solar controler device. Details see + http://english.ivt-hirschau.de/content.php?parent_id=CAT_64&doc_id=DOC_118 +- 80_M232.pm/81_M232Counter.pm + Support for the M232 device from ELV by Boris. - 91_DbLog.pm Example to log data in a (DBI supported) database (MySQL, Oracle, etc) +- 99_SUNRISE.pm + The original Sunrise/Sunset support. Uses DateTime::Event::Sunrise. Uses the + 99_SUNRISE_EL.pm module instead. +- 99_SUNRISE_EL.pm + Support foor computins sunrise/sunset times. +- 99_Utils.pm + skeleton for self-written perl funtions. - 99_ALARM.pm Example for a Low Budget ALARM System by Martin - checkmsg.pl - Check cwthe CRC of an FS20 hex message -- fhem - RC script by Stefan to be put into /etc/init.d and then symlinked - to /etc/rc3.d or similar. + Check header/function/crc of an FS20 hex message +- crc.pl + Computing CRC16 in perl +- em1010.pl + Standalone EM1010PC reader program +- init-scripts + RC scripts to be put into /etc/init.d and then symlinked to /etc/rc3.d or + similar. - four2hex Convert housecode from ELV notation (4) to fhem.pl notation (hex) - fs20_holidays.sh @@ -19,3 +35,5 @@ Martin's "don't lock me out" program: look at the comment - rrd Peter's RRD support. See the HOWTO +- serial.pm + Serial line analyzer diff --git a/fhem/docs/commandref.html b/fhem/docs/commandref.html index 422cb0e77..d9e89c3a7 100644 --- a/fhem/docs/commandref.html +++ b/fhem/docs/commandref.html @@ -433,26 +433,26 @@ split in multiple lines

  • skip_next
    Can be applied to at devices.
    - Used for at commands: skip the execution of the command the next time.

  • + Used for at commands: skip the execution of the command the next + time.
    - +
  • softbuffer
    - Can be applied to FHZ devices.
    - Used to disable the FHZ softbuffer for FHT deviced (enabled by default).
    - Note: By disabling the softbuffer FHEM works like in version 4.1. It is posible to lost commands to FHT devices by overflow the FHZ hardwarebuffer or on transmission failures.
    -
    -
  • -
  • softrepeat
    - Can be applied to FHZ devices.
    - Used to set the repeating time while FHEM tries to resend a FHT command which failed (default 240).
    - Note: Don't set this time to small. In this case it is posible that the FHZ Hardwarebuffer runs full in a short time. Than it is posible it takes long time to process the following commands. Normaly it should not necesary to change this value.
    -
    -
  • -
  • softmaxretry
    - Can be applied to FHZ devices.
    - Used to set the maximal retries in which FHEM try to resend a failed command to FHT devices (default 3).
    - Note: Don't set this value to height. In this case it is posible that the FHZ Hardwarebuffer runs full in a short time. Than it is posible it takes long time to process the following commands. Normaly it should not necesary to change this value.
    -
    + Can be applied to FHZ devices.
    + As the FHZ command buffer for FHT devices is limited, and commands + are only sent to the FHT devices every 150 seconds, the hardware + buffer may overflow and FHT commands get lost. Setting this attribute + to 1 implements an "unlimited" software buffer
    + Default is disabled (i.e. not set or set to 0).

  • + + +
  • retrycount
    + Can be applied to FHT devices.
    + If the softbuffer attribute is set, then + resend commands retrycount times if after 240 seconds + no confirmation message is rececived from the corresponding FHT + device.
    + Default is 3.

  • @@ -825,8 +825,8 @@ split in multiple lines

    define n1 notify piri:on.* define a8 at +*{3}00:00:02 set lamp on-for-timer 1 # Switch the lamp on from sunset to 11 PM - # Copy 99_SUNRISE.pm in the FHEM directory to have sunset_rel() - { sunrise_coord("8.686", "50.112", "Europe/Berlin") } + # Copy 99_SUNRISE_EL.pm in the FHEM directory to have sunset_rel() + { sunrise_coord("8.686", "50.112", "") } define a9 at +*{sunset_rel()} set lamp on define a10 at *23:00:00 set lamp off @@ -858,11 +858,11 @@ split in multiple lines

    (+) flag
  • In order to use the sunrise_rel()/sunset_rel() functions, copy the - 99_SUNRISE.pm file from the contrib into the modules (FHEM) - directory, and put { sunrise_coord(long, lat, tz) } into your config - file, as in the above example. If you are not using sunrise_coord, then - the coordinates for Frankfurt am Main, Germany will be used. - You also have to install the Datetime::Event::Sunrise perl module. + 99_SUNRISE_EL.pm file from the contrib into the modules (FHEM) + directory, and put { sunrise_coord(long, lat, "") } into your + lastinclude file, as in the above example. + If you are not using sunrise_coord, then the coordinates for + Frankfurt am Main, Germany will be used.
  • For even more complex date handling you either have to call fhem from @@ -1626,10 +1626,8 @@ must between 5.5 and 30.5 Celsius. Value 5.5 set the actuator to OFF, value 30.
  • - If you add the 99_SUNRISE.pm from the contrib directory to your module - directory (NOTE: you have to install the Perl module - DateTime::Event::Sunrise first), then you have access to the follwing - functions:
    + If you add the 99_SUNRISE_EL.pm from the contrib directory to your module + directory, then you have access to the following functions:
      sunset_rel()
      sunset_abs()
      diff --git a/fhem/docs/faq.html b/fhem/docs/faq.html index 4bcac6010..94470b16a 100644 --- a/fhem/docs/faq.html +++ b/fhem/docs/faq.html @@ -227,40 +227,21 @@ by fhem.pl? 11. I'd like to use this sunrise/sunset stuff, can you help me?
        - First you (most probably) have to install the DateTime::Event::Sunrise perl - module, as it is not part of the standard distributions. If it is not - installed and you copy the contrib/99_SUNRISE.pm into your module (FHEM) - directory, then the program will not start up, telling you that this module - is missing. - The (IMHO) easiest way to install it is via the following command (probably - as root):
        -
        -   perl -MCPAN -e shell
        -   cpan> install DateTime::Event::Sunrise
        - This will fetch the module from a CPAN archive, compile it and install it, - and will do the same with each perl module which is needed by this one.
        - + Copy contrib/99_SUNRISE_EL.pm into your FHEM directory. Next look for the geographic coordinates of your home, e.g with a GPS receiver or with googleearth. Compute the latitude/longitude as needed, and - enter them in your init file (fhem.cfg) with the command: -
        {sunrise_coord("", "", "Europe/Berlin") }
        - If you are living in a different timezone, then change the string above - according to the perldoc DateTime manpage.
        - - Now copy the contrib/99_SUNRISE.pm file into your module directory, and - restart the program. If everything is ok, typing + enter them in your lastinclude file with the command: +
        {sunrise_coord("", "", "") }
        + If everything is ok, typing
        { sunrise_abs() }
        in the telnet prompt, will return the time of the sunrise today, in a HH:MM:SS format.

        - Note: As fhem.cfg will be overwritten if you use the save command, - it is better to put the sunrise_coord command in the "lastinclude" file, - e.g. /home/fhem/fhem.cfg.static. This file will be read, if you set the - lastinclude attribute:

        - - attr global lastinclude /home/fhem/fhem.cfg.static


        - - and will not be overwritten by save. + 99_SUNRISE_EL.pm is the ExtraLight version of the original 99_SUNRISE.pm, + which needs the DateTime::Event::Sunrise perl module, which in turn is + usually difficult to install. If you still want to use the original module, + then the initialization string will be slightly different: +
        {sunrise_coord("", "", "Europe/Berlin") }
      diff --git a/fhem/docs/fritzbox.html b/fhem/docs/fritzbox.html index 71fa26f69..b50e5353d 100644 --- a/fhem/docs/fritzbox.html +++ b/fhem/docs/fritzbox.html @@ -22,13 +22,13 @@
    • FHZ1000 and FHZ1300 (both tested)
    • FS20, FHT and KS300-2 (tested)
    • HMS (untested, but should work)
    • +
    • SunSet/SunRise.

    What does not work right now:
    • Automatic startup after reboot of the fritzbox
    • -
    • SunSet/SunRise modules (planning a lightweight alternative).
    • HTML Frontend. Planning a webfrontend/pgm2 "fhem" module, as do not know how to start CGI programs from the builtin web browser.
    • Gnuplot. No idea how to replace it, perhaps we compile a fritzbox diff --git a/fhem/examples/06_at b/fhem/examples/06_at index f34f8ada6..0f7b9e445 100644 --- a/fhem/examples/06_at +++ b/fhem/examples/06_at @@ -16,9 +16,9 @@ define a7 at +*{3}00:00:02 set lamp on-for-timer 1 # Blink 3 times ################################## # Switch the lamp on from sunset to 11 PM each day -# You have to install 99_SUNRISE.pm in the FHEM directory to have sunset() +# You have to install 99_SUNRISE_EL.pm in the FHEM directory to have sunset() # We have to use the relative versions, as the next event is computed now -{ sunrise_coord("8.686", "50.112", "Europe/Berlin") } +{ sunrise_coord("8.686", "50.112", "") } define a8 at +*{sunset_rel()} set lamp on define a9 at *23:00:00 set lamp off diff --git a/fhem/examples/07_sunset_windowed b/fhem/examples/07_sunset_windowed index 2dbb7e221..234c04182 100755 --- a/fhem/examples/07_sunset_windowed +++ b/fhem/examples/07_sunset_windowed @@ -79,7 +79,7 @@ attr global userattr freigabe define FHZ FHZ /dev/ttyUSB0 -{ sunrise_coord("10.000", "53.550", "Europe/Berlin") } +{ sunrise_coord("10.000", "53.550", "") } # devices diff --git a/fhem/fhem.pl b/fhem/fhem.pl index 387e199a1..ec0cde85d 100755 --- a/fhem/fhem.pl +++ b/fhem/fhem.pl @@ -49,7 +49,7 @@ sub GetLogLevel(@); sub HandleTimeout(); sub HandleArchiving($); sub IOWrite($@); -sub InternalTimer($$$); +sub InternalTimer($$$$); sub Log($$); sub OpenLogfile($); sub ResolveDateWildcards($@); @@ -135,7 +135,7 @@ my %intAt; # Internal at timer hash. my $intAtCnt=0; my $reread_active = 0; my $AttrList = "room comment"; -my $cvsid = '$Id: fhem.pl,v 1.28 2007-10-21 11:35:58 rudolfkoenig Exp $'; +my $cvsid = '$Id: fhem.pl,v 1.29 2007-11-26 08:27:04 rudolfkoenig Exp $'; $init_done = 0; @@ -226,6 +226,9 @@ if(int(@ARGV) == 2) { # End of client code ################################################### + +################################################### +# Server initialization my $ret = CommandInclude(undef, $attr{global}{configfile}); die($ret) if($ret); @@ -244,20 +247,17 @@ if($attr{global}{statefile} && -r $attr{global}{statefile}) { } SignalHandling(); -################################################ -# Main loop - my $pfn = $attr{global}{pidfilename}; if($pfn) { die "$pfn: $!\n" if(!open(PID, ">$pfn")); print PID $$ . "\n"; close(PID); } - $init_done = 1; + Log 0, "Server started (version $attr{global}{version}, pid $$)"; - +################################################ # Main Loop while (1) { my ($rout, $rin) = ('', ''); @@ -327,12 +327,24 @@ while (1) { sub IsDummy($) { - my $dev = shift; + my $devname = shift; - return 1 if(defined($attr{$dev}) && defined($attr{$dev}{dummy})); + return 1 if(defined($attr{$devname}) && defined($attr{$devname}{dummy})); return 0; } +################################################ +sub +IsIoDummy($) +{ + my $name = shift; + + return IsDummy($defs{$name}{IODev}{NAME}) + if($defs{$name} && $defs{$name}{IODev}); + return 1; +} + + ################################################ sub GetLogLevel(@) @@ -1007,7 +1019,6 @@ PrintHash($$) my ($h, $lev) = @_; my ($str,$sstr) = ("",""); - my $str = ""; foreach my $c (sort keys %{$h}) { if(ref($h->{$c})) { @@ -1015,7 +1026,7 @@ PrintHash($$) if(defined($h->{$c}{TIME}) && defined($h->{$c}{VAL})) { $str .= sprintf("%*s %-19s %-15s %s\n", $lev," ", $h->{$c}{TIME},$c,$h->{$c}{VAL}); - } elsif($c eq "IODev") { + } elsif($c eq "IODev" || $c eq "HASH") { $str .= sprintf("%*s %-10s %s\n", $lev," ",$c, $h->{$c}{NAME}); } else { $sstr .= sprintf("%*s %s:\n", @@ -1464,11 +1475,11 @@ HandleTimeout() ##################################### sub -InternalTimer($$$) +InternalTimer($$$$) { - my ($tim, $fn, $arg) = @_; + my ($tim, $fn, $arg, $waitIfInitNotDone) = @_; - if(!$init_done) { + if(!$init_done && $waitIfInitNotDone) { select(undef, undef, undef, $tim-gettimeofday()); no strict "refs"; &{$fn}($arg); @@ -1583,9 +1594,14 @@ DoTrigger($$) } elsif(!defined($defs{$dev}{CHANGED})) { return ""; } + $defs{$dev}{STATE} = $defs{$dev}{CHANGED}[0]; + + # STATE && {READINGS}{state} should be the same + my $r = $defs{$dev}{READINGS}; + $r->{state}{VAL} = $defs{$dev}{STATE} if($r && $r->{state}); my $max = int(@{$defs{$dev}{CHANGED}}); - Log 5, "Triggering $dev ($max canges)"; + Log 5, "Triggering $dev ($max changes)"; return "" if(defined($attr{$dev}) && defined($attr{$dev}{do_not_notify})); ################