FHEM_Hargassner/20_HGNanopk.pm

485 lines
14 KiB
Perl

# 20_HGNanopk.pm
# HGNanopk Device (readings only)
#
# based on 20_HARGASSNER.pm from:
# (c) 2016 Alexander Kneer <alexander.kneer@gmx.de>
# 2020 tb. (modify readings)
#
# 20_HGNanopk.pm
package main;
use Net::Telnet;
use List::Util 'first';
use strict;
use warnings;
use HttpUtils;
use Unicode::String;
sub HGNanopk_UpdateValue($$$);
my %gets = (
"Fehlerdaten" => "Fehlerdaten",
);
#################################################################################
sub HGNanopk_Initialize($) {
#################################################################################
my ($hash) = @_;
# Normal devices
$hash->{UndefFn} = "HGNanopk_Undef";
$hash->{DefFn} = "HGNanopk_Define";
$hash->{AttrFn} = "HGNanopk_Attr";
$hash->{NOTIFYDEV} = "global";
# $hash->{AttrList} = "dummy:1,0 ";
# $hash->{AttrList} = $readingFnAttributes;
# $hash->{GetFn} = "HGNanopk_Get";
$hash->{DbLog_splitFn} = "HGNanopk_DbLog_split";
}
#################################################################################
sub HGNanopk_Define($$) {
#################################################################################
my ($hash, $def) = @_;
my @a = split("[ \t]+", $def);
if(@a <= 3) {
return "wrong syntax: define <name> HGNanopk <hostname> <interval> <DAQ_Basename>";
}
my $name= $a[0];
my $hostname= $a[2];
my $interval= $a[3];
my $filename;
if ($a[4] eq "") {
$filename = "DAQ00001.DAQ";
print "using default filename $filename\n";
} else {
$filename = $a[4];
print "set filename to given argument $filename\n";
}
$hash->{DeviceName} = $name;
$hash->{Device} = $hostname;
$hash->{STATE} = 'Initialized';
$hash->{interval} = $interval;
$hash->{filename} = $filename;
Log 4,"HGNanopk_Define -> $name at $hostname (Update every $interval seconds)";
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{interval}, "HGNanopk_GetUpdate", $hash, 0);
return undef;
}
#################################################################################
sub HGNanopk_Undef($@) {
#################################################################################
my ( $hash, $arg ) = @_;
RemoveInternalTimer($hash);
return undef;
}
##################################################################################
sub HGNanopk_DbLog_split($$) {
#################################################################################
my ( $event, $device_name ) = @_;
my ( $reading, $value, $unit) = split / /, $event;
$reading =~ s/://;
#$reading = substr($reading, 4);
return ( $reading, $value, $unit );
}
##################################################################################
sub HGNanopk_GetUpdate($)
#################################################################################
{
my ($hash) = @_;
my $name = $hash->{NAME};
my $hginfofile = $hash->{filename};
my @buffer = split(/\./,$hginfofile);
my $hgtransfile;
my $hgstatefile;
$hgtransfile = "$buffer[0]" . ".translation";
$hgstatefile = "$buffer[0]" . ".states";
sub query_bit($$) {
my ( $bitarray, $bit ) = @_;
$bitarray = hex("0x".$bitarray);
return ( ( $bitarray & 2**$bit ) ? 1 : 0);
}
Log3 $name, 4, "HGNanopk ($name) - Get Update";
my $telnet = Net::Telnet->new(Timeout => 30, Errmode => 'die');
if (!$telnet) {
Log3 $name, 4, "HGNanopk ($name) - telnet bad";
}
else
{
Log3 $name, 4, "HGNanopk ($name) - telnet ok";
if($telnet->open($hash->{Device}))
{
Log3 $name, 4, "HGNanopk ($name) - connected to pellet oven via telnet";
my $line1 = $telnet->getline();
my $line2 = $telnet->getline();
$telnet->close;
Log3 $name, 4, "HGNanopk ($name) - line ($line2)";
my @array = split(/ /, $line2);
readingsBeginUpdate($hash);
if ($array[0] eq "pm") {
#will contain the data readed from DAQ00001.translation
#First a hash (key = name of DAQ00001.DAQ, Value = Translation from DAQ00001.translation)
my %HG_TRANSLATION_HASH;
#second a hash (key = reading name, as used in fhem, Value is 0 or 1 for not displayed or displayed)
my %HG_ACTIVE_HASH;
my %HG_STATUS_CODES;
my $translation=1;
my $states=1;
my @HG_HEADER_ANALOG;
my @HG_HEADER_DIGITAL;
my $NAME;
my @HG_Reading_Names;
#my @array;
my @databuff;
sub query_bit($$) {
my ( $bitarray, $bit ) = @_;
$bitarray = hex("0x" . $bitarray);
return ( ( $bitarray & 2**$bit ) ? 1 : 0);
}
if ( -e "$hgtransfile" ) {
my @filebuffer;
open(TRANSLATE,'<',$hgtransfile) or $translation=0;
while(<TRANSLATE>) {
push(@filebuffer,$_);
}
close(TRANSLATE);
chomp(@filebuffer);
foreach(@filebuffer) {
my @data=split /;/,$_;
$HG_TRANSLATION_HASH{$data[0]} = $data[1];
if ( $data[1] ne "" ) {
$HG_ACTIVE_HASH{$data[1]} = $data[2];
} else {
$HG_ACTIVE_HASH{$data[0]} = $data[2];
}
}
} else {
$translation=0;
}
if ( -e "$hgstatefile" ) {
open(STATUSCODE,"<",$hgstatefile ) or $states=0
} else {
$states=0;
}
if ( $states eq "1" ) {
my @filebuffer;
while (<STATUSCODE>) {
push(@filebuffer,$_);
}
close(STATUSCODE);
chomp(@filebuffer);
foreach(@filebuffer) {
my @data=split /;/,$_;
my @codes=split /,/,$data[1];
if ( $HG_TRANSLATION_HASH{$data[0]} ) {
$HG_STATUS_CODES{$HG_TRANSLATION_HASH{$data[0]}} = [ @codes ];
} else {
$HG_STATUS_CODES{$data[0]} = [ @codes ];
}
}
}
open(HGDATA,'<',$hginfofile) or die "File $hginfofile not found in FHEM Root dir";
while (<HGDATA>) {
push(@databuff,$_);
}
close(HGDATA);
chomp(@databuff);
my @data = split /</,$databuff[1];
foreach(@data) {
$_ = Unicode::String::latin1 ( $_ );
my @buffer;
if ( ( $_ =~ /unit/ ) or ( $_ =~ /dop/ ) ) {
$_ =~ s/^CHANNEL id=\'//;
$_ =~ s/\' name='/;/;
$_ =~ s/\' unit='/;/;
$_ =~ s/\' dop='/;/;
$_ =~ s/\'\/>//;
push(@HG_HEADER_ANALOG,$_);
@buffer = split /;/,$_;
$buffer[1] =~ s/ /_/g;
if ($HG_TRANSLATION_HASH{$buffer[1]}) {
push(@HG_Reading_Names,$HG_TRANSLATION_HASH{$buffer[1]})
} else {
push(@HG_Reading_Names,$buffer[1]);
}
}
if ( $_ =~ /bit/ ) {
$_ =~ s/^CHANNEL id=\'//;
$_ =~ s/\' bit='/;/;
$_ =~ s/' name='/;/;
$_ =~ s/\'\/>//;
push(@HG_HEADER_DIGITAL,$_);
@buffer = split /;/,$_;
$buffer[2] =~ s/ /_/g;
$buffer[2] =~ s/^/BIT-/;
if ($HG_TRANSLATION_HASH{$buffer[2]}) {
push(@HG_Reading_Names,$HG_TRANSLATION_HASH{$buffer[2]})
} else {
push(@HG_Reading_Names,$buffer[2]);
}
}
}
chomp(@HG_HEADER_ANALOG);
chomp(@HG_HEADER_DIGITAL);
my @readingList = sort keys %{$hash->{READINGS}};
foreach(@readingList) {
my $found=0;
my $Read=$_;
foreach(@HG_Reading_Names) {
if ( $Read eq $_ && $HG_ACTIVE_HASH{$_} == 1 ) {
$found=1;
}
}
if ( $found eq "0" ) {
readingsDelete($hash, $_);
print "$hash->{NAME}: Reading $_ deleted\n";
}
}
if ( $translation eq "0" ) {
open(TRANSLATE,'>',$hgtransfile) or die "could not open new translation file ($hgtransfile)for Hargassner Module\n";
foreach(@HG_HEADER_ANALOG) {
my @data=split /;/,$_;
if ( $data[1] ne "DUMMY" ) {
$data[1] =~ s/ /_/g;
print(TRANSLATE $data[1] . ";;1\n");
}
}
foreach(@HG_HEADER_DIGITAL) {
my @data=split /;/,$_;
if ( $data[2] ne "DUMMY" ) {
$data[2] =~ s/ /_/g;
$data[2] =~ s/^/BIT-/;
print(TRANSLATE $data[2] . ";\n");
}
}
close(TRANSLATE);
}
my $LastID=0;
foreach(@HG_HEADER_ANALOG) {
my @data = split /;/,$_;
if ( $data[1] ne "DUMMY" ) {
$data[1] =~ s/ /_/g;
if ( $HG_TRANSLATION_HASH{$data[1]} ) {
if ( $HG_ACTIVE_HASH{$HG_TRANSLATION_HASH{$data[1]}} == 1 ) {
if ( $HG_STATUS_CODES{$HG_TRANSLATION_HASH{$data[1]}}[$array[$data[0]+1]] ) {
readingsBulkUpdate($hash, $HG_TRANSLATION_HASH{$data[1]}, $HG_STATUS_CODES{$HG_TRANSLATION_HASH{$data[1]}}[$array[$data[0]+1]].' ') if( ReadingsVal($name, $HG_TRANSLATION_HASH{$data[1]}, '') ne $HG_STATUS_CODES{$HG_TRANSLATION_HASH{$data[1]}}[$array[$data[0]+1]].' ');
} else {
readingsBulkUpdate($hash, $HG_TRANSLATION_HASH{$data[1]}, $array[$data[0]+1].' ') if( ReadingsVal($name, $HG_TRANSLATION_HASH{$data[1]}, '') ne $array[$data[0]+1].' ');
}
}
} else {
if ($HG_ACTIVE_HASH{$data[1]} == 1 ) {
if ( $HG_STATUS_CODES{$data[1]}[$array[$data[0]+1]] ) {
readingsBulkUpdate($hash, $data[1], $HG_STATUS_CODES{$data[1]}[$array[$data[0]+1]] .' ') if( ReadingsVal($name, $data[1], '') ne $HG_STATUS_CODES{$data[1]}[$array[$data[0]+1]-1].' ');
} else {
readingsBulkUpdate($hash, $data[1], $array[$data[0]+1].' ') if( ReadingsVal($name, $data[1], '') ne $array[$data[0]+1].' ');
}
}
}
}
$LastID=$data[0];
}
foreach(@HG_HEADER_DIGITAL) {
my @data = split /;/,$_;
if ( $data[2] ne "DUMMY" ) {
$data[2] =~ s/ /_/g;
$data[2] =~ s/^/BIT-/;
if ( $HG_TRANSLATION_HASH{$data[2]} ) {
if ( $HG_ACTIVE_HASH{$HG_TRANSLATION_HASH{$data[2]}} == 1 ) {
readingsBulkUpdate($hash, $HG_TRANSLATION_HASH{$data[2]}, query_bit($array[$data[0]+$LastID+1], $data[1])) if( ReadingsVal($name, $HG_TRANSLATION_HASH{$data[2]}, '') ne query_bit($array[$data[0]+$LastID+1], $data[1]));
}
} else {
if ( $HG_ACTIVE_HASH{$data[2]} == 1 ) {
readingsBulkUpdate($hash, $data[2], query_bit($array[$data[0]+$LastID+1], $data[1])) if( ReadingsVal($name, $data[2], '') ne query_bit($array[$data[0]+$LastID+1], $data[1]));
}
}
}
}
}
readingsEndUpdate($hash, 1);
$hash->{STATE} = 'Updated';
}
else
{
Log3 $name, 4, "HGNanopk ($name) - open telnet failed";
$hash->{STATE} = 'Failed';
}
}
RemoveInternalTimer($hash);
InternalTimer(gettimeofday()+$hash->{interval}, "HGNanopk_GetUpdate", $hash, 0);
}
##################################################################################
sub HGNanopk_Get($@) {
##################################################################################
my ($hash, @a) = @_;
return "\"get HGNanopk\" needs only one parameter" if(@a != 2);
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %gets)
if(!defined($gets{$a[1]}));
my ($fn, $arg) = split(" ", $gets{$a[1]});
my $v = join(" ", @a);
my $name = $hash->{NAME};
Log3 $name, 4, "HGNanopk get $v";
#HGNanopk_GetParameter($hash, $fn);
return "";
}
##################################################################################
sub HGNanopk_Attr($$$) {
##################################################################################
my ($cmd, $name, $attrName, $attrVal) = @_;
my $orig = $attrVal;
$attrVal = int($attrVal) if($attrName eq "interval");
$attrVal = 60 if($attrName eq "interval" && $attrVal < 60 && $attrVal != 0);
my $hash = $defs{$name};
# if( $attrName eq 'disable' ) {
# if( $cmd eq "set" && $attrVal ) {
# plex_stopTimelineListener($hash);
# plex_stopDiscovery($hash);
# foreach my $ip ( keys %{$hash->{clients}} ) {
# $hash->{clients}{$ip}{online} = 0;
# }
# } else {
# $attr{$name}{$attrName} = 0;
# plex_startDiscovery($hash);
# plex_startTimelineListener($hash);
# }
#
# } elsif( $attrName eq 'responder' ) {
# if( $cmd eq "set" && $attrVal ) {
# $attr{$name}{$attrName} = 1;
# plex_startDiscovery($hash);
#
# } else {
# $attr{$name}{$attrName} = 0;
# plex_startDiscovery($hash);
#
# }
# }
if( $cmd eq "set" ) {
if( $attrVal && $orig ne $attrVal ) {
$attr{$name}{$attrName} = $attrVal;
return $attrName ." set to ". $attrVal if( $init_done );
}
}
return;
}
#################################################################################
1;
=pod
=begin html
<a name="HGNanopk"></a>
<h3>HGNanopk</h3>
<ul>
This module connects to the Hargassner Pellet Heating.
<br /><br />
<a name="HGNanopk_define"></a>
<b>Define</b>
<ul>
<code>define &lt;name&gt; HGNanopk &lt;hostname&gt; &lt;interval&gt; &lbrack;&lt;filename;&gt;&rbrack;</code>
<br />
<br />
&lt;hostname&gt; is the name or ip address of the HGNanopk Pellet Heating.
<br />
<br />
&lt;interval&gt; is the interval in seconds between fhem try to get new data from heating. (e.g. 60)
<br />
<br />
&lt;filename&gt; is a optional parameter for the filename of DAQ00001.DAQ file. <br />If not given the defaultname will be used, else the path/name of this argument. <br /> Attention: there should be no "." char in the pathnames (just one in filename)
<br /><br />
<b>installation</b>
<br />
make sure this is running on a Linux/Unix System, and on the system ist the recode command installed.
<br />
Insert an SD Card into your Hargassner pellets oven (left Touchdisplay blow the rubber cover)
<br />
then enable in display setup the storage of logdata, let it run vor a few seconds, disable it
<br />
and remove the SD Card. Insert the Card into your PC, and copy the file named DAQ00001.DAQ from this card
<br />
into the root of your fhem installation.
This file will contain a header line, which define all the readings for this module
<br />
after the first run of the module you will find in your fhem root another file, named "DAQ00001.tranlations"
<br />
with all the named of the radings from hargassner. You can add you own interpretation of this names after each parameter.
<br />
when ready delete each configured HGNanopk device, and redefine it - you will now get your translated names for the radings.
<br />
Example:
<ul>
<code>define Pelletoven HGNanopk 192.168.1.69 60</code>
</ul>
</ul>
<br />
<b>Readings</b>
Actual readings vary with the oven and the Software version.
<br />
the readings are dynamically generated based on the <code>DAQ00001.DAQ</code> file. per default this
<br /> must be installed in the root of your fhem installation. Alternativly use the optional
<br /> filename argument in the definition
<br />
</ul>
</ul>
=end html
=cut