485 lines
14 KiB
Perl
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 <name> HGNanopk <hostname> <interval> [<filename;>]</code>
|
|
<br />
|
|
<br />
|
|
<hostname> is the name or ip address of the HGNanopk Pellet Heating.
|
|
<br />
|
|
<br />
|
|
<interval> is the interval in seconds between fhem try to get new data from heating. (e.g. 60)
|
|
<br />
|
|
<br />
|
|
<filename> 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
|