2022-06-14 10:42:49 +00:00
# 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 ) ;
2023-01-03 13:38:56 +00:00
if ( @ a <= 3 ) {
return "wrong syntax: define <name> HGNanopk <hostname> <interval> <DAQ_Basename>" ;
2022-06-14 10:42:49 +00:00
}
my $ name = $ a [ 0 ] ;
my $ hostname = $ a [ 2 ] ;
my $ interval = $ a [ 3 ] ;
2023-01-03 13:38:56 +00:00
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" ;
}
2022-06-14 10:42:49 +00:00
$ hash - > { DeviceName } = $ name ;
$ hash - > { Device } = $ hostname ;
$ hash - > { STATE } = 'Initialized' ;
$ hash - > { interval } = $ interval ;
2023-01-03 13:38:56 +00:00
$ hash - > { filename } = $ filename ;
2022-06-14 10:42:49 +00:00
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 } ;
2023-01-03 13:38:56 +00:00
my $ hginfofile = $ hash - > { filename } ;
my @ buffer = split ( /\./ , $ hginfofile ) ;
my $ hgtransfile ;
my $ hgstatefile ;
$ hgtransfile = "$buffer[0]" . ".translation" ;
$ hgstatefile = "$buffer[0]" . ".states" ;
2022-06-14 10:42:49 +00:00
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 ) ;
}
2023-01-03 13:38:56 +00:00
if ( - e "$hgtransfile" ) {
2022-06-14 10:42:49 +00:00
my @ filebuffer ;
2023-01-03 13:38:56 +00:00
open ( TRANSLATE , '<' , $ hgtransfile ) or $ translation = 0 ;
2022-06-14 10:42:49 +00:00
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 ;
}
2023-01-03 13:38:56 +00:00
if ( - e "$hgstatefile" ) {
open ( STATUSCODE , "<" , $ hgstatefile ) or $ states = 0
2022-06-14 10:42:49 +00:00
} 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 ] ;
}
}
}
2023-01-03 13:38:56 +00:00
open ( HGDATA , '<' , $ hginfofile ) or die "File $hginfofile not found in FHEM Root dir" ;
2022-06-14 10:42:49 +00:00
while ( <HGDATA> ) {
push ( @ databuff , $ _ ) ;
}
close ( HGDATA ) ;
chomp ( @ databuff ) ;
my @ data = split /</ , $ databuff [ 1 ] ;
foreach ( @ data ) {
$ _ = Unicode::String:: latin1 ( $ _ ) ;
my @ buffer ;
2023-06-15 16:58:00 +00:00
if ( ( $ _ =~ /unit/ ) or ( $ _ =~ /dop/ ) ) {
2022-06-14 10:42:49 +00:00
$ _ =~ s/^CHANNEL id=\'// ;
$ _ =~ s/\' name='/;/ ;
$ _ =~ s/\' unit='/;/ ;
2023-06-15 16:58:00 +00:00
$ _ =~ s/\' dop='/;/ ;
2022-06-14 10:42:49 +00:00
$ _ =~ 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" ) {
2023-01-03 13:38:56 +00:00
open ( TRANSLATE , '>' , $ hgtransfile ) or die "could not open new translation file ($hgtransfile)for Hargassner Module\n" ;
2022-06-14 10:42:49 +00:00
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>
2023-01-03 13:38:56 +00:00
<code> define & lt ; name & gt ; HGNanopk & lt ; hostname & gt ; & lt ; interval & gt ; & lbrack ; & lt ; filename ; & gt ; & rbrack ; </code>
2022-06-14 10:42:49 +00:00
< 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 )
2023-01-03 13:38:56 +00:00
< 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 )
2022-06-14 10:42:49 +00:00
< 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 / >
2023-01-03 13:38:56 +00:00
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
2022-06-14 10:42:49 +00:00
< br / >
</ul>
</ul>
= end html
= cut