# 20_HGNanopk.pm # HGNanopk Device (readings only) # # based on 20_HARGASSNER.pm from: # (c) 2016 Alexander Kneer # 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 HGNanopk "; } 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() { 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 () { 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 () { push(@databuff,$_); } close(HGDATA); chomp(@databuff); my @data = split ///; 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

HGNanopk

    This module connects to the Hargassner Pellet Heating.

    Define
      define <name> HGNanopk <hostname> <interval> [<filename;>]

      <hostname> is the name or ip address of the HGNanopk Pellet Heating.

      <interval> is the interval in seconds between fhem try to get new data from heating. (e.g. 60)

      <filename> is a optional parameter for the filename of DAQ00001.DAQ file.
      If not given the defaultname will be used, else the path/name of this argument.
      Attention: there should be no "." char in the pathnames (just one in filename)

      installation
      make sure this is running on a Linux/Unix System, and on the system ist the recode command installed.
      Insert an SD Card into your Hargassner pellets oven (left Touchdisplay blow the rubber cover)
      then enable in display setup the storage of logdata, let it run vor a few seconds, disable it
      and remove the SD Card. Insert the Card into your PC, and copy the file named DAQ00001.DAQ from this card
      into the root of your fhem installation. This file will contain a header line, which define all the readings for this module
      after the first run of the module you will find in your fhem root another file, named "DAQ00001.tranlations"
      with all the named of the radings from hargassner. You can add you own interpretation of this names after each parameter.
      when ready delete each configured HGNanopk device, and redefine it - you will now get your translated names for the radings.
      Example:
        define Pelletoven HGNanopk 192.168.1.69 60

    Readings Actual readings vary with the oven and the Software version.
    the readings are dynamically generated based on the DAQ00001.DAQ file. per default this
    must be installed in the root of your fhem installation. Alternativly use the optional
    filename argument in the definition
=end html =cut