2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-01-31 12:49:34 +00:00

FHT softbuffer rewrite, module reorganization, support for M232

git-svn-id: https://svn.fhem.de/fhem/trunk@109 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2007-11-26 08:27:04 +00:00
parent e48b09cd2c
commit 8410288c1e
18 changed files with 966 additions and 451 deletions

View File

@ -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)
- 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

View File

@ -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);
}
}

View File

@ -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}));
if($a[1] eq "refreshvalues") {
my $val = $a[2];
my $arg = "020183" . $hash->{CODE} . $c2bset{$cmd};
if ($cmd =~ m/-temp/) {
} 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};
#####################################
# Send FHZ command
sub
sendCommand($$$$)
{
my ($hash, $cmd, $val, $arg) = @_;
my $name = $hash->{NAME};
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
if($cmd eq "refreshvalues") {
$pr = $priority{$type} if (defined($priority{$type})); # get priority for specific command type
$val = "" if (!defined($val));
# 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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

190
fhem/FHEM/70_SCIVT.pm Normal file
View File

@ -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;

268
fhem/FHEM/80_M232.pm Normal file
View File

@ -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 <name> auto <value>\n" .
"set <name> stop\n" .
"set <name> 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 <name> [an0..an5]\n" .
"get <name> [io0..io7]\n" .
"get <name> octet\n" .
"get <name> 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;

176
fhem/FHEM/81_M232Counter.pm Normal file
View File

@ -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 <name> value <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 <name> 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;

19
fhem/FHEM/99_Utils.pm Normal file
View File

@ -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);
}

View File

@ -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

View File

@ -433,26 +433,26 @@ split in multiple lines<br><br>
<a name="skip_next"></a>
<li>skip_next<br>
Can be applied to at devices.<br>
Used for at commands: skip the execution of the command the next time.</li><br />
Used for at commands: skip the execution of the command the next
time.</li><br>
<a name="softbuffer" id="softbuffer"></a>
<a name="softbuffer"></a>
<li>softbuffer<br />
Can be applied to FHZ devices.<br />
Used to disable the FHZ softbuffer for FHT deviced (enabled by default).<br />
<strong>Note:</strong> 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.<br />
<br />
<a name="softbuffer" id="softbuffer"></a> </li>
<li>softrepeat<br />
Can be applied to FHZ devices.<br />
Used to set the repeating time while FHEM tries to resend a FHT command which failed (default 240).<br />
<strong>Note:</strong> 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.<br />
<br />
<a name="softbuffer" id="softbuffer"></a> </li>
<li>softmaxretry<br />
Can be applied to FHZ devices.<br />
Used to set the maximal retries in which FHEM try to resend a failed command to FHT devices (default 3).<br />
<strong>Note:</strong> 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. <br />
<br>
Can be applied to FHZ devices.<br />
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<br>
Default is disabled (i.e. not set or set to 0).</li><br>
<a name="retrycount"></a>
<li>retrycount<br />
Can be applied to FHT devices.<br />
If the <a href="#softbuffer">softbuffer</a> attribute is set, then
resend commands <code>retrycount</code> times if after 240 seconds
no confirmation message is rececived from the corresponding FHT
device.<br>
Default is 3.</li><br>
</li>
</ul>
@ -825,8 +825,8 @@ split in multiple lines<br><br>
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<br><br>
(+) flag</li>
<li>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
<a href="#lastinclude">lastinclude</a> file, as in the above example.
If you are not using sunrise_coord, then the coordinates for
Frankfurt am Main, Germany will be used.
</li>
<li>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.
</li>
<li>
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: <br>
If you add the 99_SUNRISE_EL.pm from the contrib directory to your module
directory, then you have access to the following functions: <br>
<ul>
sunset_rel()<br>
sunset_abs()<br>

View File

@ -227,40 +227,21 @@ by fhem.pl?</b>
<a name="faq11"></a>
<b>11. I'd like to use this sunrise/sunset stuff, can you help me?</b>
<ul>
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):<br>
<pre>
perl -MCPAN -e shell
cpan> install DateTime::Event::Sunrise</pre>
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.<br>
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:
<pre>{sunrise_coord("<latitude>", "<longitude>", "Europe/Berlin") }</pre>
If you are living in a different timezone, then change the string above
according to the <code>perldoc DateTime</code> manpage.<br>
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:
<pre>{sunrise_coord("<latitude>", "<longitude>", "") }</pre>
If everything is ok, typing
<pre>{ sunrise_abs() }</pre>
in the telnet prompt, will return the time of the sunrise today, in a
HH:MM:SS format.<br><br>
<b>Note:</b> 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:<br><code><br>
attr global lastinclude /home/fhem/fhem.cfg.static</code><br><br>
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:
<pre>{sunrise_coord("<latitude>", "<longitude>", "Europe/Berlin") }</pre>
</ul>

View File

@ -22,13 +22,13 @@
<li>FHZ1000 and FHZ1300 (both tested)</li>
<li>FS20, FHT and KS300-2 (tested)</li>
<li>HMS (untested, but should work)</li>
<li>SunSet/SunRise.</li>
</ul>
<br>
What does not work right now:<br>
<ul>
<li>Automatic startup after reboot of the fritzbox</li>
<li>SunSet/SunRise modules (planning a lightweight alternative).</li>
<li>HTML Frontend. Planning a webfrontend/pgm2 "fhem" module, as do not
know how to start CGI programs from the builtin web browser.
<li>Gnuplot. No idea how to replace it, perhaps we compile a fritzbox

View File

@ -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

View File

@ -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

View File

@ -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}));
################