mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-02-24 21:34:51 +00:00
Renamed files
git-svn-id: https://svn.fhem.de/fhem/trunk@35 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
d5b4420c33
commit
127ddac343
369
fhem/FHEM/11_FHT.pm
Executable file
369
fhem/FHEM/11_FHT.pm
Executable file
@ -0,0 +1,369 @@
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %codes = (
|
||||
"0000.6" => "actuator",
|
||||
"00002c" => "synctime", # Not verified
|
||||
"0100.6" => "actuator1", # Not verified (1-8)
|
||||
"0200.6" => "actuator2",
|
||||
"0300.6" => "actuator3",
|
||||
"0400.6" => "actuator4",
|
||||
"0500.6" => "actuator5",
|
||||
"0600.6" => "actuator6",
|
||||
"0700.6" => "actuator7",
|
||||
"0800.6" => "actuator8",
|
||||
"140069" => "mon-from1",
|
||||
"150069" => "mon-to1",
|
||||
"160069" => "mon-from2",
|
||||
"170069" => "mon-to2",
|
||||
"180069" => "tue-from1",
|
||||
"190069" => "tue-to1",
|
||||
"1a0069" => "tue-from2",
|
||||
"1b0069" => "tue-to2",
|
||||
"1c0069" => "wed-from1",
|
||||
"1d0069" => "wed-to1",
|
||||
"1e0069" => "wed-from2",
|
||||
"1f0069" => "wed-to2",
|
||||
"200069" => "thu-from1",
|
||||
"210069" => "thu-to1",
|
||||
"220069" => "thu-from2",
|
||||
"230069" => "thu-to2",
|
||||
"240069" => "fri-from1",
|
||||
"250069" => "fri-to1",
|
||||
"260069" => "fri-from2",
|
||||
"270069" => "fri-to2",
|
||||
"280069" => "sat-from1",
|
||||
"290069" => "sat-to1",
|
||||
"2a0069" => "sat-from2",
|
||||
"2b0069" => "sat-to2",
|
||||
"2c0069" => "sun-from1",
|
||||
"2d0069" => "sun-to1",
|
||||
"2e0069" => "sun-from2",
|
||||
"2f0069" => "sun-to2",
|
||||
"3e0069" => "mode",
|
||||
"3f0069" => "holiday1", # Not verified
|
||||
"400069" => "holiday2", # Not verified
|
||||
"410069" => "desired-temp",
|
||||
"XX0069" => "measured-temp", # sum of next. two, never "really" sent
|
||||
"420069" => "measured-low",
|
||||
"430069" => "measured-high",
|
||||
"440069" => "state",
|
||||
"600069" => "year",
|
||||
"610069" => "month",
|
||||
"620069" => "day",
|
||||
"630069" => "hour",
|
||||
"640069" => "minute",
|
||||
"650069" => "init",
|
||||
"820069" => "day-temp",
|
||||
"840069" => "night-temp",
|
||||
"850069" => "unknown_85",
|
||||
"8a0069" => "windowopen-temp",
|
||||
|
||||
|
||||
"0000aa" => "code_0000aa",
|
||||
"0000ba" => "code_0000ba",
|
||||
"430079" => "code_430079",
|
||||
"440079" => "code_440079",
|
||||
"4b0067" => "code_4b0067",
|
||||
"4b0077" => "code_4b0077",
|
||||
"7e0067" => "code_7e0067",
|
||||
);
|
||||
|
||||
my %cantset = (
|
||||
"actuator" => 1,
|
||||
"actuator1" => 1,
|
||||
"actuator2" => 1,
|
||||
"actuator3" => 1,
|
||||
"actuator4" => 1,
|
||||
"actuator5" => 1,
|
||||
"actuator6" => 1,
|
||||
"actuator7" => 1,
|
||||
"actuator8" => 1,
|
||||
"synctime" => 1,
|
||||
"measured-temp" => 1,
|
||||
"measured-high" => 1,
|
||||
"measured-low" => 1,
|
||||
"state" => 1,
|
||||
"init" => 1,
|
||||
|
||||
"code_0000aa" => 1,
|
||||
"code_0000ba" => 1,
|
||||
"code_430079" => 1,
|
||||
"code_440079" => 1,
|
||||
"code_4b0067" => 1,
|
||||
"code_4b0077" => 1,
|
||||
"code_7e0067" => 1,
|
||||
);
|
||||
|
||||
my %nosetarg = (
|
||||
"help" => 1,
|
||||
"refreshvalues" => 1,
|
||||
);
|
||||
|
||||
my %c2m = (0 => "auto", 1 => "manual", 2 => "holiday");
|
||||
my %m2c = ("auto" => 0, "manual" => 1, "holiday" => 2);
|
||||
|
||||
my %defptr;
|
||||
my %c2b; # command->button hash (reverse of codes)
|
||||
my %c2bset; # Setteable values
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
foreach my $k (keys %codes) {
|
||||
my $v = $codes{$k};
|
||||
$c2b{$v} = $k;
|
||||
$c2bset{$v} = substr($k, 0, 2) if(!defined($cantset{$v}));
|
||||
}
|
||||
$c2bset{refreshvalues} = "65ff66ff";
|
||||
|
||||
# 810c0426 0909a001 1111 1600
|
||||
# 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";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
my $ret = undef;
|
||||
|
||||
return "\"set $a[0]\" needs two parameters" if(@a < 2);
|
||||
return "Unknown argument $a[1], choose one of " .
|
||||
join(" ", sort {$c2bset{$a} cmp $c2bset{$b} } keys %c2bset)
|
||||
if(!defined($c2bset{$a[1]}));
|
||||
return "\"set $a[0]\" needs two parameters"
|
||||
if(@a != 3 && !(@a == 2 && $nosetarg{$a[1]}));
|
||||
|
||||
Log GetLogLevel($a[0],2), "FHT set " . join(" ", @a);
|
||||
|
||||
my $arg = "020183" . $hash->{CODE} . $c2bset{$a[1]};
|
||||
|
||||
if($a[1] eq "refreshvalues") {
|
||||
|
||||
# This is special. Without the sleep the next FHT won't send its data
|
||||
if(!IsDummy($a[0])) {
|
||||
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);
|
||||
}
|
||||
return $ret;
|
||||
|
||||
} elsif($a[1] =~ m/-temp/) {
|
||||
return "Invalid temperature, use NN.N" if($a[2] !~ m/^\d*\.?\d+$/);
|
||||
my $a = int($a[2]*2);
|
||||
$arg .= sprintf("%02x", $a);
|
||||
$ret = "Rounded temperature to " . $a/2 if($a/2 != $a[2]);
|
||||
|
||||
} elsif($a[1] =~ m/-from/ || $a[1] =~ m/-to/) {
|
||||
return "Invalid timeformat, use HH:MM" if($a[2] !~ 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);
|
||||
$ret = "Rounded time to $nt" if($nt ne $a[2]);
|
||||
|
||||
} elsif($a[1] eq "mode") {
|
||||
return "Invalid mode, use one of " . join(" ", sort keys %m2c)
|
||||
if(!defined($m2c{$a[2]}));
|
||||
$arg .= sprintf("%02x", $m2c{$a[2]});
|
||||
|
||||
} else { # Holiday1, Holiday2
|
||||
$arg .= sprintf("%02x", $a[2]);
|
||||
|
||||
}
|
||||
|
||||
IOWrite($hash, "04", $arg) if(!IsDummy($a[0]));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_SetState($$$$)
|
||||
{
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
|
||||
return "Undefined type $vt" if(!defined($c2b{$vt}));
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> FHT CODE" if(int(@a) != 3);
|
||||
$a[2] = lc($a[2]);
|
||||
return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
|
||||
if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/i);
|
||||
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
$defptr{$a[2]} = $hash;
|
||||
|
||||
AssignIoPort($hash);
|
||||
|
||||
Log GetLogLevel($a[0],2),"Asking the FHT device $a[0]/$a[2] to send its data";
|
||||
FHT_Set($hash, ($a[0], "refreshvalues"));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FHT_Parse($$)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
my $dev = substr($msg, 16, 4);
|
||||
my $cde = substr($msg, 20, 6);
|
||||
my $val = substr($msg, 26, 2) if(length($msg) > 26);
|
||||
|
||||
if(!defined($defptr{$dev})) {
|
||||
Log 3, "FHT Unknown device $dev, please define it";
|
||||
return "UNDEFINED FHT $dev";
|
||||
}
|
||||
|
||||
|
||||
my $def = $defptr{$dev};
|
||||
|
||||
# Unknown, but don't want report it. Should come with c409c401
|
||||
if($cde eq "00") {
|
||||
return "";
|
||||
}
|
||||
|
||||
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)";
|
||||
$val = substr($cde, 2, 2);
|
||||
$cde = substr($cde, 0, 2) . "0069";
|
||||
}
|
||||
|
||||
my $type;
|
||||
foreach my $c (keys %codes) {
|
||||
if($cde =~ m/$c/) {
|
||||
$type = $codes{$c};
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
$val = hex($val);
|
||||
|
||||
if(!$type) {
|
||||
Log 4, "FHT $def->{NAME} (Unknown: $cde => $val)";
|
||||
$def->{CHANGED}[0] = "unknown $cde: $val";
|
||||
return $def->{NAME};
|
||||
}
|
||||
|
||||
|
||||
my $tn = TimeNow();
|
||||
|
||||
###########################
|
||||
# Reformat the values so they are readable
|
||||
|
||||
if($type eq "actuator") {
|
||||
$val = sprintf("%02d%%", int(100*$val/255 + 0.5));
|
||||
|
||||
} elsif($cde ge "140069" && $cde le "2f0069") { # Time specs
|
||||
Log 5, "FHT $def->{NAME} ($type: $val)";
|
||||
return "" if($val == 144); # Empty, forget it
|
||||
my $hour = $val / 6;
|
||||
my $min = ($val % 6) * 10;
|
||||
$val = sprintf("%02d:%02d", $hour, $min);
|
||||
|
||||
} elsif($type eq "mode") {
|
||||
$val = $c2m{$val} if(defined($c2m{$val}));
|
||||
|
||||
} elsif($type eq "measured-low") {
|
||||
|
||||
$def->{READINS}{$type}{TIME} = $tn;
|
||||
$def->{READINS}{$type}{VAL} = $val;
|
||||
return "";
|
||||
|
||||
} elsif($type eq "measured-high") {
|
||||
|
||||
$def->{READINS}{$type}{TIME} = $tn;
|
||||
$def->{READINS}{$type}{VAL} = $val;
|
||||
|
||||
if(defined($def->{READINGS}{"measured-low"}{VAL})) {
|
||||
|
||||
$val = $val*256 + $def->{READINGS}{"measured-low"}{VAL};
|
||||
$val /= 10;
|
||||
$val = sprintf("%.1f (Celsius)", $val);
|
||||
$type = "measured-temp"
|
||||
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
||||
} elsif($type =~ m/.*-temp/) {
|
||||
$val = sprintf("%.1f (Celsius)", $val / 2)
|
||||
|
||||
} elsif($type eq "state") {
|
||||
|
||||
my $nval;
|
||||
$nval = "Bat: " . (($val & 1) ? "empty" : "ok");
|
||||
$nval .= ", Window: " . (($val & 32) ? "open" : "closed");
|
||||
$nval .= ", Fault: " . (($val & 16) ? "yes" : "no");
|
||||
$val = $nval;
|
||||
|
||||
} elsif($type =~ m/echo_/) { # Ignore these messages
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
$def->{READINGS}{$type}{TIME} = $tn;
|
||||
$def->{READINGS}{$type}{VAL} = $val;
|
||||
|
||||
Log 4, "FHT $def->{NAME} ($type: $val)";
|
||||
$def->{CHANGED}[0] = "$type: $val";
|
||||
$def->{STATE} = "$type: $val" if($type eq "measured-temp");
|
||||
return $def->{NAME};
|
||||
}
|
||||
|
||||
1;
|
206
fhem/FHEM/12_HMS.pm
Executable file
206
fhem/FHEM/12_HMS.pm
Executable file
@ -0,0 +1,206 @@
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %codes = (
|
||||
"0" => "HMS100TF",
|
||||
"1" => "HMS100T",
|
||||
"2" => "HMS100WD",
|
||||
"3" => "RM100-2",
|
||||
"4" => "HMS100TFK", # Depending on the onboard jumper it is 4 or 5
|
||||
"5" => "HMS100TFK",
|
||||
"6" => "HMS100MG",
|
||||
);
|
||||
|
||||
my %defptr;
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# 810e047e0510a001473a000000120233 HMS100TF
|
||||
# 810e04b90511a0018e63000001100000 HMS100T
|
||||
# 810e04e80212a001ec46000001000000 HMS100WD
|
||||
# 810e04d70213a001b16d000003000000 RM100-2
|
||||
# 810e047f0214a001a81f000001000000 HMS100TFK
|
||||
# 810e048f0295a0010155000001000000 HMS100TFK (jumper)
|
||||
# 810e04330216a001b4c5000001000000 HMS100MG
|
||||
|
||||
$hash->{Match} = "^810e04....(1|5|9)[0-6]a001";
|
||||
$hash->{DefFn} = "HMS_Define";
|
||||
$hash->{UndefFn} = "HMS_Undef";
|
||||
$hash->{ParseFn} = "HMS_Parse";
|
||||
$hash->{AttrList} = "do_not_notify:0,1 showtime:0,1 model;hms100-t,hms100-tf,hms100-wd,hms100-mg,hms100-tfk,rm100-2 loglevel:0,1,2,3,4,5,6";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> HMS CODE" if(int(@a) != 3);
|
||||
$a[2] = lc($a[2]);
|
||||
return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
|
||||
if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
|
||||
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
$defptr{$a[2]} = $hash;
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
HMS_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
|
||||
my $dev = substr($msg, 16, 4);
|
||||
my $cde = substr($msg, 11, 1);
|
||||
my $val = substr($msg, 24, 8) if(length($msg) == 32);
|
||||
|
||||
my $type = "";
|
||||
foreach my $c (keys %codes) {
|
||||
if($cde =~ m/$c/) {
|
||||
$type = $codes{$c};
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# As the HMS devices change their id on each battery change, we offer
|
||||
# a wildcard too for each type: 100<device-code>,
|
||||
my $odev = $dev;
|
||||
if(!defined($defptr{$dev})) {
|
||||
Log 4, "HMS device $dev not defined, using the wildcard device 100$cde";
|
||||
$dev = "100$cde";
|
||||
}
|
||||
|
||||
if(!defined($defptr{$dev})) {
|
||||
Log 3, "Unknown HMS device $dev/$odev, please define it";
|
||||
$type = "HMS" if(!$type);
|
||||
return "UNDEFINED $type $odev";
|
||||
}
|
||||
|
||||
my $def = $defptr{$dev};
|
||||
|
||||
my (@v, @txt, @sfx);
|
||||
|
||||
if($type eq "HMS100TF") {
|
||||
|
||||
@txt = ( "temperature", "humidity", "battery");
|
||||
@sfx = ( "(Celsius)", "(%)", "");
|
||||
|
||||
# Codierung <s1><s0><t1><t0><f0><t2><f2><f1>
|
||||
my $status = hex(substr($val, 0, 1));
|
||||
$v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10;
|
||||
$v[1] = int(substr($val, 6, 2) . substr($val, 4, 1))/10;
|
||||
$v[2] = "ok";
|
||||
if ( $status & 2 ) { $v[2] = "empty"; }
|
||||
if ( $status & 4 ) { $v[2] = "replaced"; }
|
||||
if ( $status & 8 ) { $v[0] = -$v[0]; }
|
||||
|
||||
$val = "T: $v[0] H: $v[1] Bat: $v[2]";
|
||||
|
||||
} elsif ($type eq "HMS100T") {
|
||||
|
||||
@txt = ( "temperature", "battery");
|
||||
@sfx = ( "(Celsius)", "");
|
||||
|
||||
my $status = hex(substr($val, 0, 1));
|
||||
$v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10;
|
||||
$v[1] = "ok";
|
||||
if ( $status & 2 ) { $v[1] = "empty"; }
|
||||
if ( $status & 4 ) { $v[1] = "replaced"; }
|
||||
if ( $status & 8 ) { $v[0] = -$v[0]; }
|
||||
|
||||
$val = "T: $v[0] Bat: $v[1]";
|
||||
|
||||
} elsif ($type eq "HMS100WD") {
|
||||
|
||||
@txt = ( "water_detect", "battery");
|
||||
@sfx = ( "", "");
|
||||
|
||||
# Battery-low condition detect is not yet properly
|
||||
# implemented. As soon as my WD's batteries get low
|
||||
# I am willing to supply a patch ;-) SEP7-RIPE, 2006/05/13
|
||||
my $status = hex(substr($val, 1, 1));
|
||||
$v[1] = "ok";
|
||||
$v[0] = "off";
|
||||
if ( $status & 1 ) { $v[0] = "on"; }
|
||||
$val = "Water Detect: $v[0]";
|
||||
|
||||
} elsif ($type eq "HMS100TFK") { # By Peter P.
|
||||
|
||||
@txt = ( "switch_detect", "battery");
|
||||
@sfx = ( "", "");
|
||||
# Battery-low condition detect is not yet properly implemented.
|
||||
my $status = hex(substr($val, 1, 1));
|
||||
$v[0] = ($status ? "on" : "off");
|
||||
$v[1] = "off";
|
||||
$val = "Switch Detect: $v[0]";
|
||||
|
||||
} elsif($type eq "RM100-2") {
|
||||
|
||||
@txt = ( "smoke_detect", "battery");
|
||||
@sfx = ( "", "");
|
||||
|
||||
$v[0] = ( hex(substr($val, 1, 1)) != "0" ) ? "on" : "off";
|
||||
$v[1] = "unknown"; # Battery-low detect is _NOT_ implemented.
|
||||
$val = "smoke_detect: $v[0]";
|
||||
|
||||
} elsif ($type eq "HMS100MG") { # By Peter Stark
|
||||
|
||||
@txt = ( "gas_detect", "battery");
|
||||
@sfx = ( "", "");
|
||||
|
||||
# Battery-low condition detect is not yet properly
|
||||
# implemented.
|
||||
my $status = hex(substr($val, 1, 1));
|
||||
$v[0] = ($status != "0") ? "on" : "off";
|
||||
$v[1] = "off";
|
||||
if ($status & 1) { $v[0] = "on"; }
|
||||
$val = "Gas Detect: $v[0]";
|
||||
|
||||
} else {
|
||||
|
||||
Log 4, "HMS Device $dev (Unknown type: $type)";
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
my $now = TimeNow();
|
||||
Log GetLogLevel($def->{NAME},4), "HMS Device $dev ($type: $val)";
|
||||
|
||||
my $max = int(@txt);
|
||||
for( my $i = 0; $i < $max; $i++) {
|
||||
$def->{READINGS}{$txt[$i]}{TIME} = $now;
|
||||
my $v = "$v[$i] $sfx[$i]";
|
||||
$def->{READINGS}{$txt[$i]}{VAL} = $v;
|
||||
$def->{CHANGED}[$i] = "$txt[$i]: $v";
|
||||
}
|
||||
$def->{READINGS}{type}{TIME} = $now;
|
||||
$def->{READINGS}{type}{VAL} = $type;
|
||||
|
||||
$def->{STATE} = $val;
|
||||
$def->{CHANGED}[$max] = $val;
|
||||
return $def->{NAME};
|
||||
}
|
||||
|
||||
1;
|
253
fhem/FHEM/13_KS300.pm
Executable file
253
fhem/FHEM/13_KS300.pm
Executable file
@ -0,0 +1,253 @@
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my %defptr;
|
||||
my $negcount = 0;
|
||||
|
||||
######################
|
||||
# Note: this is just an empty hull.
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
# Message is like
|
||||
# 810d04f94027a00171212730000008
|
||||
# 81 0d 04 f9 4027a00171 212730000008
|
||||
|
||||
$hash->{Match} = "^810.04..402.a001";
|
||||
$hash->{DefFn} = "KS300_Define";
|
||||
$hash->{UndefFn} = "KS300_Undef";
|
||||
$hash->{ParseFn} = "KS300_Parse";
|
||||
$hash->{AttrList} = "do_not_notify:0,1 showtime:0,1 model:ks300 loglevel:0,1";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
return "wrong syntax: define <name> KS300 <code> " .
|
||||
"[ml/raincounter] [wind-factor]" if(int(@a) < 3 || int(@a) > 5);
|
||||
$a[2] = lc($a[2]);
|
||||
return "Define $a[0]: wrong CODE format: specify a 4 digit hex value"
|
||||
if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/);
|
||||
|
||||
$hash->{CODE} = $a[2];
|
||||
my $rainunit = ((int(@a) > 3) ? $a[3] : 255);
|
||||
my $windunit = ((int(@a) > 4) ? $a[4] : 1.0);
|
||||
$hash->{CODE} = $a[2];
|
||||
$hash->{RAINUNIT} = $rainunit;
|
||||
$hash->{WINDUNIT} = $windunit;
|
||||
$defptr{$a[2]} = $hash;
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
delete($defptr{$hash->{CODE}});
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
KS300_Parse($)
|
||||
{
|
||||
my ($hash,$msg) = @_;
|
||||
|
||||
if($msg !~ m/^810d04..4027a001/) {
|
||||
Log 4, "KS300 unknown message $msg";
|
||||
return "";
|
||||
}
|
||||
|
||||
###############################
|
||||
# 1 2
|
||||
#0123456789012345 67890123456789
|
||||
#
|
||||
#810d04f94027a001 71212730000008
|
||||
###############################
|
||||
my @a = split("", $msg);
|
||||
|
||||
##########################
|
||||
# I've seldom (1 out of 700) seen messages of length 10 and 11 with correct
|
||||
# CRC, they seem to contain partial data (e.g. temp/wind/hum but not rain)
|
||||
# They are suppressed as of now.
|
||||
if(hex($a[3]) != 13) {
|
||||
Log 4, "Strange KS400 message received, wont decode ($msg)";
|
||||
return "";
|
||||
}
|
||||
|
||||
if(int(keys %defptr)) {
|
||||
|
||||
my @arr = keys(%defptr); # No code is known yet
|
||||
my $dev = shift(@arr);
|
||||
my $def = $defptr{$dev};
|
||||
my $haverain = 0;
|
||||
|
||||
my @v;
|
||||
my @txt = ( "rain_raw", "rain", "wind", "humidity", "temperature",
|
||||
"israining", "unknown1", "unknown2", "unknown3");
|
||||
my @sfx = ( "(counter)", "(l/m2)", "(km/h)", "(%)", "(Celsius)",
|
||||
"(yes/no)", "","","");
|
||||
|
||||
# The next instr wont work for empty hashes, so we init it now
|
||||
$def->{READINGS}{$txt[0]}{VAL} = 0 if(!$def->{READINGS});
|
||||
my $r = $def->{READINGS};
|
||||
|
||||
$v[0] = hex("$a[28]$a[27]$a[26]");
|
||||
|
||||
#############################
|
||||
# My KS300 sends a (quite huge) "negative" rain, when the rain begins,
|
||||
# then the value is "normal" again. So we have to filter neg. rain out.
|
||||
# But if the KS300 is sending this value more than once, then accept it,
|
||||
# as the KS300 was probably reset
|
||||
|
||||
if($r->{rain_raw}{VAL}) {
|
||||
my ($rrv, undef) = split(" ", $r->{rain_raw}{VAL});
|
||||
$haverain = 1 if($v[0] != $rrv);
|
||||
if($v[0] < $rrv) {
|
||||
if($negcount++ < 3) {
|
||||
Log 3, "KS300 negative rain, ignoring it";
|
||||
$v[0] = $rrv;
|
||||
} else {
|
||||
Log 1, "KS300 was probably reset, accepting new rain value";
|
||||
}
|
||||
} else {
|
||||
$negcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$v[1] = sprintf("%0.1f", $v[0] * $def->{RAINUNIT} / 1000);
|
||||
$v[2] = sprintf("%0.1f", ("$a[25]$a[24].$a[23]"+0) * $def->{WINDUNIT});
|
||||
$v[3] = "$a[22]$a[21]" + 0;
|
||||
$v[4] = "$a[20]$a[19].$a[18]" + 0; $v[4] = "-$v[4]" if($a[17] eq "7");
|
||||
$v[4] = sprintf("%0.1f", $v[4]);
|
||||
|
||||
$v[5] = ((hex($a[17]) & 0x2) || $haverain) ? "yes" : "no";
|
||||
$v[6] = $a[29];
|
||||
$v[7] = $a[16];
|
||||
$v[8] = $a[17];
|
||||
|
||||
# Negative temp
|
||||
$v[4] = -$v[4] if($v[8] & 8);
|
||||
|
||||
my $tm = TimeNow();
|
||||
|
||||
Log GetLogLevel($def->{NAME},4), "KS300 $dev: $msg";
|
||||
|
||||
my $max = int(@v);
|
||||
|
||||
|
||||
for(my $i = 0; $i < $max; $i++) {
|
||||
$r->{$txt[$i]}{TIME} = $tm;
|
||||
my $val = "$v[$i] $sfx[$i]";
|
||||
$r->{$txt[$i]}{VAL} = $val;
|
||||
$def->{CHANGED}[$i] = "$txt[$i]: $val";
|
||||
}
|
||||
|
||||
# For logging/summary
|
||||
my $val = "T: $v[4] H: $v[3] W: $v[2] R: $v[1] IR: $v[5]";
|
||||
$def->{STATE} = $val;
|
||||
$def->{CHANGED}[$max++] = $val;
|
||||
|
||||
###################################
|
||||
# AVG computing
|
||||
if(!$r->{cum_day}) {
|
||||
|
||||
$r->{cum_day}{VAL} = "$tm T: 0 H: 0 W: 0 R: $v[1]";
|
||||
$r->{avg_day}{VAL} = "T: $v[4] H: $v[3] W: $v[2] R: $v[1]";
|
||||
|
||||
} else {
|
||||
|
||||
my @cv = split(" ", $r->{cum_day}{VAL});
|
||||
|
||||
my @cd = split("[ :-]", $r->{cum_day}{TIME});
|
||||
my $csec = 3600*$cd[3] + 60*$cd[4] + $cd[5]; # Sec of last reading
|
||||
|
||||
my @d = split("[ :-]", $tm);
|
||||
my $sec = 3600*$d[3] + 60*$d[4] + $d[5]; # Sec now
|
||||
|
||||
my @sd = split("[ :-]", "$cv[0] $cv[1]");
|
||||
my $ssec = 3600*$sd[3] + 60*$sd[4] + $sd[5]; # Sec at start of day
|
||||
|
||||
my $difft = $sec - $csec;
|
||||
$difft += 86400 if($d[2] != $cd[2]); # Sec since last reading
|
||||
|
||||
my $t = $cv[3] + $difft * $v[4];
|
||||
my $h = $cv[5] + $difft * $v[3];
|
||||
my $w = $cv[7] + $difft * $v[2];
|
||||
my $e = $cv[9];
|
||||
|
||||
$r->{cum_day}{VAL} = "$cv[0] $cv[1] T: $t H: $h W: $w R: $e";
|
||||
|
||||
$difft = $sec - $ssec;
|
||||
$difft += 86400 if($d[2] != $sd[2]); # Sec since last reading
|
||||
|
||||
$t /= $difft; $h /= $difft; $w /= $difft; $e = $v[1] - $cv[9];
|
||||
$r->{avg_day}{VAL} =
|
||||
sprintf("T: %.1f H: %d W: %.1f R: %.1f", $t, $h, $w, $e);
|
||||
|
||||
if($d[2] != $sd[2]) { # Day changed, report it
|
||||
|
||||
$def->{CHANGED}[$max++] = "avg_day $r->{avg_day}{VAL}";
|
||||
$r->{cum_day}{VAL} = "$tm T: 0 H: 0 W: 0 R: $v[1]";
|
||||
|
||||
if(!$r->{cum_month}) { # Check the month
|
||||
|
||||
$r->{cum_month}{VAL} = "1 $r->{avg_day}{VAL}";
|
||||
$r->{avg_month}{VAL} = $r->{avg_day}{VAL};
|
||||
|
||||
} else {
|
||||
|
||||
my @cmv = split(" ", $r->{cum_month}{VAL});
|
||||
$t += $cmv[2]; $w += $cmv[4]; $h += $cmv[6];
|
||||
|
||||
$cmv[0]++;
|
||||
$r->{cum_month}{VAL} =
|
||||
sprintf("%d T: %.1f H: %d W: %.1f R: %.1f",
|
||||
$cmv[0], $t, $h, $w, $cmv[8]+$e);
|
||||
$r->{avg_month}{VAL} =
|
||||
sprintf("T: %.1f H: %d W: %.1f R: %.1f",
|
||||
$t/$cmv[0], $h/$cmv[0], $w/$cmv[0], $cmv[8]+$e);
|
||||
|
||||
if($d[1] != $sd[1]) { # Month changed, report it
|
||||
|
||||
$def->{CHANGED}[$max++] = "avg_month $r->{avg_month}{VAL}";
|
||||
$r->{cum_month}{VAL} = "0 T: 0 H: 0 W: 0 R: 0";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$r->{cum_month}{TIME} = $r->{avg_month}{TIME} = $tm;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
$r->{cum_day}{TIME} = $r->{avg_day}{TIME} = $tm;
|
||||
# AVG computing
|
||||
###################################
|
||||
|
||||
return $def->{NAME};
|
||||
|
||||
} else {
|
||||
|
||||
Log 4, "KS300 detected: $msg";
|
||||
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
1;
|
113
fhem/FHEM/90_at.pm
Executable file
113
fhem/FHEM/90_at.pm
Executable file
@ -0,0 +1,113 @@
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IO::File;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
at_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "at_Define";
|
||||
$hash->{TimeFn} = "at_Exec";
|
||||
$hash->{AttrFn} = "at_Attr";
|
||||
$hash->{AttrList} = "disable:0,1 skip_next:0,1";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
at_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my ($name, undef, $tm, $command) = split("[ \t]+", $def, 4);
|
||||
|
||||
return "Usage: define <name> at <timespec> <command>" if(!$command);
|
||||
return "Wrong timespec, use \"[+][*[{count}]]<time or func>\""
|
||||
if($tm !~ m/^(\+)?(\*({\d+})?)?(.*)$/);
|
||||
my ($rel, $rep, $cnt, $tspec) = ($1, $2, $3, $4);
|
||||
my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($tspec);
|
||||
return $err if($err);
|
||||
|
||||
$rel = "" if(!defined($rel));
|
||||
$rep = "" if(!defined($rep));
|
||||
$cnt = "" if(!defined($cnt));
|
||||
|
||||
my $ot = time;
|
||||
my @lt = localtime($ot);
|
||||
my $nt = $ot;
|
||||
|
||||
$nt -= ($lt[2]*3600+$lt[1]*60+$lt[0]) # Midnight for absolute time
|
||||
if($rel ne "+");
|
||||
$nt += ($hr*3600+$min*60+$sec); # Plus relative time
|
||||
$nt += 86400 if($ot >= $nt);# Do it tomorrow...
|
||||
|
||||
@lt = localtime($nt);
|
||||
my $ntm = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
|
||||
|
||||
if($rep) { # Setting the number of repetitions
|
||||
$cnt =~ s/[{}]//g;
|
||||
return undef if($cnt eq "0");
|
||||
$cnt = 0 if(!$cnt);
|
||||
$cnt--;
|
||||
$hash->{REP} = $cnt;
|
||||
} else {
|
||||
$hash->{VOLATILE} = 1; # Write these entries to the statefile
|
||||
}
|
||||
$hash->{NTM} = $ntm if($rel eq "+" || $fn);
|
||||
$hash->{TRIGGERTIME} = $nt;
|
||||
$hash->{CMD} = SemicolonEscape($command);
|
||||
$nextat = $nt if(!$nextat || $nextat > $nt);
|
||||
|
||||
$hash->{STATE} = "Next: " . FmtTime($nt);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub
|
||||
at_Exec($)
|
||||
{
|
||||
my ($name) = @_;
|
||||
my ($skip, $disable);
|
||||
|
||||
if(defined($attr{$name})) {
|
||||
$skip = 1 if(defined($attr{$name}{skip_next}));
|
||||
$disable = 1 if(defined($attr{$name}{disable}));
|
||||
}
|
||||
|
||||
delete $attr{$name}{skip_next} if($skip);
|
||||
AnalyzeCommandChain(undef, $defs{$name}{CMD}) if(!$skip && !$disable);
|
||||
|
||||
my $count = $defs{$name}{REP};
|
||||
my $def = $defs{$name}{DEF};
|
||||
delete $defs{$name};
|
||||
|
||||
if($count) {
|
||||
$def =~ s/{\d+}/{$count}/ if($def =~ m/^\+?\*{/); # Replace the count }
|
||||
CommandDefine(undef, "$name at $def"); # Recompute the next TRIGGERTIME
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
at_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
my $do = 0;
|
||||
|
||||
if($a[0] eq "set" && $a[2] eq "disable") {
|
||||
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
||||
}
|
||||
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
||||
return if(!$do);
|
||||
|
||||
$defs{$a[1]}{STATE} = ($do == 1 ?
|
||||
"disabled" :
|
||||
"Next: " . FmtTime($defs{$a[1]}{TRIGGERTIME}));
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
88
fhem/FHEM/91_notify.pm
Executable file
88
fhem/FHEM/91_notify.pm
Executable file
@ -0,0 +1,88 @@
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IO::File;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
notify_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "notify_Define";
|
||||
$hash->{NotifyFn} = "notify_Exec";
|
||||
$hash->{AttrFn} = "notify_Attr";
|
||||
$hash->{AttrList} = "disable:0,1";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
notify_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my ($re, $command) = split("[ \t]+", $def, 2);
|
||||
|
||||
# Checking for misleading regexps
|
||||
eval { "Hallo" =~ m/^$re$/ };
|
||||
return "Bad regexp: $@" if($@);
|
||||
$hash->{CMD} = SemicolonEscape($command);
|
||||
$hash->{REGEXP} = $re;
|
||||
$hash->{STATE} = "active";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
notify_Exec($$)
|
||||
{
|
||||
my ($log, $dev) = @_;
|
||||
|
||||
my $ln = $log->{NAME};
|
||||
return if($attr{$ln} && $attr{$ln}{disable});
|
||||
|
||||
my $n = $dev->{NAME};
|
||||
my $re = $log->{REGEXP};
|
||||
my $max = int(@{$dev->{CHANGED}});
|
||||
|
||||
my $ret = "";
|
||||
for (my $i = 0; $i < $max; $i++) {
|
||||
my $s = $dev->{CHANGED}[$i];
|
||||
$s = "" if(!defined($s));
|
||||
if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/) {
|
||||
my $exec = $log->{CMD};
|
||||
|
||||
$exec =~ s/%%/____/g;
|
||||
$exec =~ s/%/$s/g;
|
||||
$exec =~ s/____/%/g;
|
||||
|
||||
$exec =~ s/@@/____/g;
|
||||
$exec =~ s/@/$n/g;
|
||||
$exec =~ s/____/@/g;
|
||||
|
||||
my $r = AnalyzeCommandChain(undef, $exec);
|
||||
$ret .= " $r" if($r);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
sub
|
||||
notify_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
my $do = 0;
|
||||
|
||||
if($a[0] eq "set" && $a[2] eq "disable") {
|
||||
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
||||
}
|
||||
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
||||
return if(!$do);
|
||||
|
||||
$defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active");
|
||||
return undef;
|
||||
}
|
||||
1;
|
120
fhem/FHEM/92_FileLog.pm
Executable file
120
fhem/FHEM/92_FileLog.pm
Executable file
@ -0,0 +1,120 @@
|
||||
##############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use IO::File;
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "FileLog_Define";
|
||||
$hash->{UndefFn} = "FileLog_Undef";
|
||||
$hash->{NotifyFn} = "FileLog_Log";
|
||||
$hash->{AttrFn} = "FileLog_Attr";
|
||||
$hash->{AttrList} = "disable:0,1 logtype";
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Define($@)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
my $fh;
|
||||
|
||||
return "wrong syntax: define <name> FileLog filename regexp" if(int(@a) != 4);
|
||||
|
||||
eval { "Hallo" =~ m/^$a[3]$/ };
|
||||
return "Bad regexp: $@" if($@);
|
||||
|
||||
my @t = localtime;
|
||||
my $f = ResolveDateWildcards($a[2], @t);
|
||||
$fh = new IO::File ">>$f";
|
||||
return "Can't open $f" if(!defined($fh));
|
||||
|
||||
$hash->{FH} = $fh;
|
||||
$hash->{REGEXP} = $a[3];
|
||||
$hash->{FILENAME} = $a[2];
|
||||
$hash->{CURRENT} = $f;
|
||||
$hash->{STATE} = "active";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
close($hash->{FH});
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####################################
|
||||
sub
|
||||
FileLog_Log($$)
|
||||
{
|
||||
# Log is my entry, Dev is the entry of the changed device
|
||||
my ($log, $dev) = @_;
|
||||
|
||||
my $ln = $log->{NAME};
|
||||
return if($attr{$ln} && $attr{$ln}{disable});
|
||||
|
||||
my $n = $dev->{NAME};
|
||||
my $re = $log->{REGEXP};
|
||||
my $max = int(@{$dev->{CHANGED}});
|
||||
|
||||
for (my $i = 0; $i < $max; $i++) {
|
||||
my $s = $dev->{CHANGED}[$i];
|
||||
$s = "" if(!defined($s));
|
||||
if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/) {
|
||||
my $t = TimeNow();
|
||||
$t = $dev->{CHANGETIME}[$i] if(defined($dev->{CHANGETIME}[$i]));
|
||||
$t =~ s/ /_/; # Makes it easier to parse with gnuplot
|
||||
|
||||
my $fh = $log->{FH};
|
||||
my @t = localtime;
|
||||
my $cn = ResolveDateWildcards($log->{FILENAME}, @t);
|
||||
|
||||
if($cn ne $log->{CURRENT}) { # New logfile
|
||||
$fh->close();
|
||||
$fh = new IO::File ">>$cn";
|
||||
if(!defined($fh)) {
|
||||
Log(0, "Can't open $cn");
|
||||
return;
|
||||
}
|
||||
$log->{CURRENT} = $cn;
|
||||
$log->{FH} = $fh;
|
||||
}
|
||||
|
||||
print $fh "$t $n $s\n";
|
||||
$fh->flush;
|
||||
$fh->sync;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub
|
||||
FileLog_Attr(@)
|
||||
{
|
||||
my @a = @_;
|
||||
my $do = 0;
|
||||
|
||||
if($a[0] eq "set" && $a[2] eq "disable") {
|
||||
$do = (!defined($a[3]) || $a[3]) ? 1 : 2;
|
||||
}
|
||||
$do = 2 if($a[0] eq "del" && (!$a[2] || $a[2] eq "disable"));
|
||||
return if(!$do);
|
||||
|
||||
$defs{$a[1]}{STATE} = ($do == 1 ? "disabled" : "active");
|
||||
|
||||
return undef;
|
||||
}
|
||||
1;
|
Loading…
x
Reference in New Issue
Block a user