mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-03 16:56:54 +00:00
309 lines
7.6 KiB
Perl
Executable File
309 lines
7.6 KiB
Perl
Executable File
##############################################
|
|
package main;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
my %readings;
|
|
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->{Category} = "DEV";
|
|
|
|
$hash->{Match} = "^810.04..402.a001";
|
|
$hash->{SetFn} = "KS300_Set";
|
|
$hash->{GetFn} = "KS300_Get";
|
|
$hash->{StateFn} = "KS300_SetState";
|
|
$hash->{ListFn} = "KS300_List";
|
|
$hash->{DefFn} = "KS300_Define";
|
|
$hash->{UndefFn} = "KS300_Undef";
|
|
$hash->{ParseFn} = "KS300_Parse";
|
|
}
|
|
|
|
###################################
|
|
sub
|
|
KS300_Set($@)
|
|
{
|
|
my ($hash, @a) = @_;
|
|
return "No set function implemented";
|
|
}
|
|
|
|
###################################
|
|
sub
|
|
KS300_Get($@)
|
|
{
|
|
my ($hash,@a) = @_;
|
|
return "No get function implemented";
|
|
}
|
|
|
|
#####################################
|
|
sub
|
|
KS300_SetState($$$$)
|
|
{
|
|
my ($hash, $tim, $vt, $val) = @_;
|
|
|
|
my $n = $hash->{CODE};
|
|
if(!$readings{$n}{$vt} || $readings{$n}{$vt}{TIM} lt $tim) {
|
|
$readings{$n}{$vt}{TIM} = $tim;
|
|
$readings{$n}{$vt}{VAL} = $val;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
#####################################
|
|
sub
|
|
KS300_List($)
|
|
{
|
|
my ($hash) = @_;
|
|
my $str = "";
|
|
|
|
my $n = $hash->{CODE};
|
|
if(!defined($readings{$n})) {
|
|
$str .= "No information about " . $hash->{NAME} . "\n";
|
|
} else {
|
|
foreach my $m (keys %{ $readings{$n} }) {
|
|
$str .= sprintf("%-19s %-15s %s\n",
|
|
$readings{$n}{$m}{TIM}, $m, $readings{$n}{$m}{VAL});
|
|
}
|
|
}
|
|
return $str;
|
|
|
|
}
|
|
|
|
#####################################
|
|
sub
|
|
KS300_Define($@)
|
|
{
|
|
my ($hash, @a) = @_;
|
|
|
|
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
|
|
$readings{$dev}{$txt[0]}{VAL} = 0 if(!$readings{$dev});
|
|
my $r = $readings{$dev};
|
|
|
|
$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 4, "KS300 $dev: $msg";
|
|
|
|
my $max = int(@v);
|
|
|
|
|
|
for(my $i = 0; $i < $max; $i++) {
|
|
$r->{$txt[$i]}{TIM} = $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}{TIM});
|
|
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}{TIM} = $r->{avg_month}{TIM} = $tm;
|
|
|
|
}
|
|
|
|
}
|
|
$r->{cum_day}{TIM} = $r->{avg_day}{TIM} = $tm;
|
|
# AVG computing
|
|
###################################
|
|
|
|
return $def->{NAME};
|
|
|
|
} else {
|
|
|
|
Log 4, "KS300 detected: $msg";
|
|
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
1;
|