###############################################
# Module for KWB
# Currently works only for KWB ClassicFire
# Basics from: Markus Heberling <markus@tisoft.de>
# Aditional extentions: JamesB
# Date: 2017-09-32 V02

package main;

use strict;
use warnings;
use feature qw/switch/;
use Time::HiRes qw(gettimeofday);
use Device::SerialPort;

use LWP::Simple;                # From CPAN
use JSON qw( decode_json );     # From CPAN
use Data::Dumper;               # Perl core module

sub KWB_Read($);
sub KWB_Ready($);

#####################################
sub KWB_Initialize($) {
  my ($hash) = @_;

  require "$attr{global}{modpath}/FHEM/DevIo.pm";

  $hash->{ReadFn}  = "KWB_Read";
  $hash->{ReadyFn} = "KWB_Ready";
  $hash->{DefFn}   = "KWB_Define";
  $hash->{UndefFn} = "KWB_Undef";
  $hash->{SetFn}  = "KWB_Set";
  $hash->{AttrFn}  = "KWB_Attr";
  $hash->{AttrList}= "showAllReadings:0,1 ".
                     $readingFnAttributes;

}

#####################################
sub KWB_Define($$) {
  my ($hash, $def) = @_;
  my @a = split("[ \t][ \t]*", $def);

  return "wrong syntax: define <name> KWB [KWB Device Name]"
    if(@a != 3);

  DevIo_CloseDev($hash);
  my $name = $a[0];
  my $dev = $a[2];
  $hash->{DeviceName} = $dev;
  
  my $ret = DevIo_OpenDev($hash, 0, "KWB_InitFn");

  return $ret;
}


#####################################
sub KWB_Set($@) {
  my ($hash, $name, $cmd, @args) = @_;

  my $erg ="";
  if ($cmd eq 'DeleteOldReadings' ) {
    deleteOldReadings($hash);
    return undef;
  }
  #Aktuell wird kein SET angeboten
  my $list = "DeleteOldReadings";
  return return "Unknown argument $cmd, choose one of $list";
}

#####################################
sub KWB_Undef($$) {
  my ($hash, $arg) = @_;
  DevIo_CloseDev($hash);
  RemoveInternalTimer($hash);
  return undef;
}

#####################################
sub KWB_Attr(@) {
	my ( $cmd, $name, $attrName, $attrValue ) = @_;
  my $hash = $defs{$name};


  # $cmd  - Vorgangsart - kann die Werte "del" (löschen) oder "set" (setzen) annehmen
	# $name - Gerätename
	# $attrName/$attrValue sind Attribut-Name und Attribut-Wert

	if ($cmd eq "set") {
  	Log3 $name, 4, "KWB ($name) - Test 01 $cmd, $name, $attrName, $attrValue";
		if ($attrName eq "showAllReadings") {
      if ($attrValue eq "0") {
    	Log3 $name, 4, "KWB ($name) - Test 02 $cmd, $name, $attrName, $attrValue";
      deleteOldReadings($hash);
      }
		}
	}
	return undef;
}


#####################################
sub deleteOldReadings($){
	my ($hash) = @_;
	my $name = $hash->{NAME};

  foreach my $r ( keys %{$hash->{READINGS}} ) {
		Log3 $name, 4, "KWB ($name) - reading $r";
    delete $hash->{READINGS}{$r} if($r =~ m/.*_Char.*/ || $r =~ m/.*_Byte.*/ || $r =~ m/500_.*/ || $r =~ m/600_.*/ || $r =~ m/700_.*/ || $r =~ m/Lgg-01-Line.*/);
  }

}

#####################################
sub dec2bin {
    my $str = unpack("B16", pack("n", shift));
    return substr($str,8,8);
}

#####################################
sub bin2dec {
    return unpack("N", pack("B32", substr("0" x 32 . shift, -32)));
}

#####################################
sub BitGesetzt {
		my $data = shift;
		my $bitnummer = shift;

    my $bitmaske = 1 << $bitnummer;

		if (ord($data) & $bitmaske) {
			return "Ein"
		}	else {
	    return "Aus"
	 }
}

sub BitGesetzt2 {
		my $data = shift;
		my $bitnummer = shift;

    my $bitmaske = 1 << $bitnummer;

		if (ord($data) & $bitmaske) {
			return "Offen"
		}	else {
	    return "Zu"
	 }
}

sub Stoerung {
		my $data = shift;
		my $bitnummer = shift;

    my $bitmaske = 1 << $bitnummer;

		if (ord($data) & $bitmaske) {
			return "Keine"
		}	else {
	    return "Es ist ein Fehler aufgetreten!"
	 }
}

#####################################
sub Stoerung2 {
		my $data = shift;
		my $bitnummer1 = shift;
		my $bitnummer2 = shift;

    my $bitmaske1 = 1 << $bitnummer1;
    my $bitmaske2 = 1 << $bitnummer2;

		if ((ord($data) & $bitmaske1) || (ord($data) & $bitmaske2)) {
			return 0
		}	else {
	    return 1
	  }
}

#####################################
sub MischerInfo {
		my $mischer = ord(shift);
		my $bitnummer1 = shift;
		my $bitnummer2 = shift;

    my $bitmaske1 = 1 << $bitnummer1;
    my $bitmaske2 = 1 << $bitnummer2;

		if ($mischer & $bitmaske1 && $mischer & $bitmaske2) {
			return "Zu"
		}	elsif ($mischer & $bitmaske1 && !($mischer & $bitmaske2)) {
			return "Aus"
		}	elsif (!($mischer & $bitmaske1) && ($mischer & $bitmaske2)) {
			return "Auf"
		}	else {
	    return "Aus"
	 }
}

#####################################
sub KesseHeiztLog {
		my $data = ord(shift);

		if ($data > 0) {
			return 1;
		}	else {
	    return 0;
	 }
}


#####################################
sub KesseHeizt {
		my $data = ord(shift);

		if ($data > 0) {
			return "Kessel heizt";
		}	else {
	    return "Kessel aus";
	 }
}

sub HeizkreiseHeiztLog {
		my $hk0 = shift;
		my $hk1 = shift;
		my $hk2 = shift;

		if ($hk0 eq "Ein" || $hk1 eq "Ein" || $hk2 eq "Ein") {
			return 1;
		}	else {
	    return 0;
	 }
}

#####################################
sub HeizkreiseHeizt {
		my $hk0 = shift;
		my $hk1 = shift;
		my $hk2 = shift;

		my $ergebnis = "";

		if ($hk0 eq "Ein") {
			$ergebnis = "EG, ";
		}
		if ($hk1 eq "Ein") {
			$ergebnis = $ergebnis . "OG1, ";
		}
		if ($hk2 eq "Ein") {
			$ergebnis = $ergebnis . "OG2, ";
		}

		if ($ergebnis eq ""){
			$ergebnis = "Keiner, ";
		}

		$ergebnis = substr($ergebnis,0,length($ergebnis)-2);

	  return $ergebnis;
}


#####################################
sub HeizkreiseHeizt2 {
		my $hk0 = shift;
		my $hk1 = shift;
		my $hk2 = shift;

		my $ergebnis = "";

		if ($hk0 eq "Ein") {
			$ergebnis = "EG, ";
		}
		if ($hk1 eq "Ein") {
			$ergebnis = $ergebnis . "OG1, ";
		}
		if ($hk2 eq "Ein") {
			$ergebnis = $ergebnis . "OG2, ";
		}

		if ($ergebnis eq ""){
			$ergebnis = "Keiner, ";
		}

		$ergebnis = substr($ergebnis,0,length($ergebnis)-2);

	  if( $ergebnis=~ m/OG2/ && $ergebnis =~ m/OG1/ && $ergebnis =~ m/EG/) {
	    return "../images/FZ_control_heizung_2og_og1.svg";
	  } elsif( $ergebnis =~ m/OG2/ && $ergebnis =~ m/OG1/) {
	    return "../images/FZ_control_heizung_2og_1og_eg.svg";
	  } elsif( $ergebnis =~ m/OG1/ && $ergebnis =~ m/EG/ ) {
	    return "../images/FZ_control_heizung_1og_eg.svg";
	  } elsif( $ergebnis =~ m/OG2/ && $ergebnis =~ m/EG/) {
	    return "../images/FZ_control_heizung_2og_eg.svg";
	  } elsif( $ergebnis eq "OG2") {
	    return "../images/FZ_control_heizung_2og.svg";
	  } elsif( $ergebnis eq "OG1") {
	    return "../images/FZ_control_heizung_1og.svg";
	  } elsif( $ergebnis eq "EG") {
	    return "../images/FZ_control_heizung_eg.svg";
	  } elsif( $ergebnis eq "Keiner") {
	    return "../images/FZ_control_heizung_none.svg";
	  } else {
	    return $ergebnis;
	  }
}


#####################################
sub BolierPumpeLog {
    my $data = shift;

		if ($data eq "Ein") {
			return 1
		}	else {
	    return 0
	 }
}

#####################################
sub getCheckSum {
  my $hash   = $_[0];
  my $length = $_[1];
  my @data   = @{$_[2]};

  my $name = $hash->{NAME};

  my $i=0;
  my $crc = ord($data[0]);

  for ($i = 1; $i < $length; $i+=1){
    $crc = &rol($crc, 1);

    if ($crc + ord($data[$i]) > 255) {
      $crc = ($crc + ord($data[$i]) + 1) % 256;
    } else {
      $crc = $crc + ord($data[$i]);
    }
  }

  return $crc;
}


# Usage: &rol(number, n)
# Rotate 'number' by 'n' bits left
#####################################
sub rol {
    my $number = shift;
    my $bits2rotate = shift;
    for (1..$bits2rotate) {
        # Shift left 1 bit
        $number = $number << 1;
        my $rmb = 0;
        if ($number > 255) {
            my $rmb = 1;
            $number -= 255;
        }
        $number += $rmb;
    }
    return $number;
}

#####################################
# called from the global loop, when the select for hash->{FD} reports data
sub KWB_Read($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};
  my ($data, $crc);

  my $buf = DevIo_SimpleRead($hash);
  return "" if(!defined($buf));
  $buf = unpack('H*', $buf);
  #$buf = $hash->{PARTIAL} . $buf;
 	Log3 $name, 4, "buf Vorher: $buf";

  while($buf =~ m/(02(?!00)(?:..)+?)(02(?!00).+?)$/) {#packets start with 02XX where XX is NOT 00
    my $line = $1;
    $buf = $2;

		#Convert 0200 to 02 (Byte by Byte) (No regex solution found)
		my $lineNew = substr($line,0,2);
		for (my $i = 2; $i < length($line); $i += 2) {
     $lineNew = $lineNew . substr($line,$i,2);
     if (substr($line,$i,4) eq "0200"){
       $i+=2;
     }
		}
    $line = $lineNew;

    given($line) {
        when (/b405/) {
          Log3 $name, 2, "line bis Alarm: b405 $line";
        }
    }

    Log3 $name, 4, "line Nachher: $line";

    # We want the bytes back
    my @linebytes = map { pack('C', hex($_)) } ($line =~ /(..)/g);

    #given($line) {
    #    when (/^0202/) {
    #      #readingsSingleUpdate($hash,"000_01-Line",$line,1);
    #    }
    #    when (/^023d/) { #entspricht 02023d....
    #      #readingsSingleUpdate($hash,"000_01-Line",$line,1);
    #    }
    #    when (/^0215/)   {
    #      #readingsSingleUpdate($hash,"000_02-Line",$line,1);
    #    }
    #    default {
    #      my @linebytesT1 = @linebytes[ 0 .. $#linebytes ];
	  #      my @linebytesTmp = @linebytes[ 1 .. $#linebytes ]; #throw away the first 02, so that lenght is correct
    #      my $sizeTmp = @linebytesTmp;
    #      my $checksumTmp = ord($linebytesTmp[-1]);
    #
    #      if($checksumTmp==getCheckSum($hash, $sizeTmp, \@linebytesT1)){
    #        readingsSingleUpdate($hash,"Lgg-01-Line",$line,1);
    #      }
    #    }
    #}

    given($line) {
        when (/^0202/) {
	        #sense message
          #Vom Kesselsteuergerät an das Bediengerät. Beinhaltet Sensorinformationen.
				 	Log3 $name, 5, "Start : sense message";
          #Log3 $name, 4, "line Nachher: $line";
	        @linebytes = @linebytes[ 1 .. $#linebytes ]; #throw away the first 02, so that lenght is correct
          my $size = @linebytes;

          my $checksum = ord($linebytes[-1]); #last element
          my $lenght = ord($linebytes[1]);
          my $address = ord($linebytes[2]);
          my $count = ord($linebytes[3]);

          if($checksum==getCheckSum($hash, $size-1, \@linebytes)){
            Log3 $name, 5, "01-Lenght: $linebytes[1] $lenght";
            Log3 $name, 5, "01-Address: $linebytes[2] $address";
            Log3 $name, 5, "01-Count: $linebytes[3] $count";
            Log3 $name, 5, "01-Checksum: $linebytes[-1] $checksum";
            Log3 $name, 5, "01-Size: $size";
            Log3 $name, 5, "01-Byte 4-8 Sensse-Message: ".substr($line,4,16);

            my @values = ();
            my @fi = ();
            my @se = ();
            my @po = ();

            #starting with byte 9 we have temperature/percantage values
            # last 4 bytes are other stuff
            #for (my $i = 9; $i < $size-4; $i += 2) {
            for (my $i = 9; $i < $size; $i += 2) {
              my $first = ord($linebytes[$i]);
              my $second =ord($linebytes[$i+1]);

              if ($first >127) {
                  $first-=256;
              }

              my $value = ($first * 256 + $second)/10;

              push(@values, $value);
              push(@fi, ord($linebytes[$i]));
              push(@se, ord($linebytes[$i+1]));
              push(@po, $i);

              Log3 $name, 5, "Werte: Byte: ".unpack('H*', $linebytes[$i])." ".unpack('H*', $linebytes[$i+1])." $i: $first $second $value Index: $#values";
            }

            if($address == 16 && $count==66) {#only send message if count==66 to be less spammy
              #KWB Classicfire

              readingsBeginUpdate($hash);
              #readingsBulkUpdate($hash,"Lgg-01-Line",$line);

              #Für Schnellinfo (000 - 020)

              readingsBulkUpdate($hash,"005_Kesseltemperatur",$values[3]);
              readingsBulkUpdate($hash,"006_Puffertemperatur-1",$values[5]);
              readingsBulkUpdate($hash,"007_Puffertemperatur-2",$values[4]);
              readingsBulkUpdate($hash,"008_Boilertemperatur",$values[2]);

              #Kessel (030 - 050)
              readingsBulkUpdate($hash,"032_Rauchgastemperatur",$values[7]);
              readingsBulkUpdate($hash,"034_Rücklauftemperatur-Kessel",$values[1]);
              #Die Multiplikation des HighByte mit 150/153 liefert eine brauchbare Genauigkeit
              readingsBulkUpdate($hash,"045_Drehzahl-Saugzuggebläse", ($se[22]*153)+$fi[23]);
              readingsBulkUpdate($hash,"046_Drehzahl-Hauptgebläse", ($se[23]*150)+$fi[24]);

              #HK-0 (060 - 080)
              readingsBulkUpdate($hash,"062_HK-0_Vorlauftemperatur",$values[16]);
              readingsBulkUpdate($hash,"063_HK-0_Raumtemperatur",$values[17]);

              #HK-1 (090 - 110)
              readingsBulkUpdate($hash,"092_HK-1_Raumtemperatur",$values[9]);
              readingsBulkUpdate($hash,"093_HK-1_Vorlauftemperatur",$values[0]);

              #HK-2 (120 - 140)
              readingsBulkUpdate($hash,"122_HK-2_Raumtemperatur",$values[10]);
              readingsBulkUpdate($hash,"123_HK-2_Vorlauftemperatur",$values[11]);

              #Sonsiges (150 - 200)
              readingsBulkUpdate($hash,"150_Außentemperatur",$values[6]);
              readingsBulkUpdate($hash,"152_Stockerkanal",$values[12]);

              if(AttrVal($name,"showAllReadings",1)) {
                #Unbekannt  (300 - 400)
                #### [08] führt zu Fehler: Illegal octal digit '8' at ./FHEM/10_KWB.pm line 622, at end of line
                readingsBulkUpdate($hash,"300_Char-08",dec2bin($fi[8]) ." ". dec2bin($se[8]) ." ". $values[8]." ". $fi[8] ." ". $se[8] ." ". (($se[8]*256)+$fi[8]));
                readingsBulkUpdate($hash,"301_Char-13",dec2bin($fi[13]) ." ". dec2bin($se[13]) ." ". $values[13] ." ". $fi[13] ." ". $se[13] ." ". (($se[13]*256)+$fi[13]));
                readingsBulkUpdate($hash,"302_Char-14",dec2bin($fi[14]) ." ". dec2bin($se[14]) ." ". $values[14] ." ". $fi[14] ." ". $se[14] ." ". (($se[14]*256)+$fi[14]));
                readingsBulkUpdate($hash,"303_Byte-15",dec2bin($fi[15]) ." ". dec2bin($se[15]) ." ". $values[15] ." ". $fi[15] ." ". $se[15] ." ". (($se[15]*256)+$fi[15]));
                readingsBulkUpdate($hash,"304_Char-18",dec2bin($fi[18]) ." ". dec2bin($se[18]) ." ". $values[18] ." ". $fi[18] ." ". $se[18] ." ". (($se[18]*256)+$fi[18]));
                readingsBulkUpdate($hash,"305_Char-19",dec2bin($fi[19]) ." ". dec2bin($se[19]) ." ". $values[19] ." ". $fi[19] ." ". $se[19] ." ". (($se[19]*256)+$fi[19]));
                readingsBulkUpdate($hash,"306_Char-20",dec2bin($fi[20]) ." ". dec2bin($se[20]) ." ". $values[20] ." ". $fi[20] ." ". $se[20] ." ". (($se[20]*256)+$fi[20]));
                readingsBulkUpdate($hash,"307_Char-21",dec2bin($fi[21]) ." ". dec2bin($se[21]) ." ". $values[21] ." ". $fi[21] ." ". $se[21] ." ". (($se[21]*256)+$fi[21]));
                readingsBulkUpdate($hash,"308_Char-22",dec2bin($fi[22]) ." ". dec2bin($se[22]) ." ". $values[22] ." ". $fi[22] ." ". $se[22] ." ". (($se[22]*256)+$fi[22]));
                readingsBulkUpdate($hash,"309_Char-24",dec2bin($fi[24]) ." ". dec2bin($se[24]) ." ". $values[24] ." ". $fi[24] ." ". $se[24] ." ". (($se[24]*256)+$fi[24]));
                readingsBulkUpdate($hash,"310_Char-25",dec2bin($fi[25]) ." ". dec2bin($se[25]) ." ". $values[25] ." ". $fi[25] ." ". $se[25] ." ". (($se[25]*256)+$fi[25]));
              }

              Log3 $name, 5, "Unbekannt 300_Char-08: ".dec2bin($fi[8]) ." ". dec2bin($se[8]) ." ". $values[8];
              Log3 $name, 5, "Unbekannt 300_Char-13: ".$values[13];
              Log3 $name, 5, "Unbekannt 300_Char-14: ".$values[14];
              Log3 $name, 5, "Unbekannt 300_Char-15: ".$values[15];
              Log3 $name, 5, "Unbekannt 300_Char-18: ".$values[18];
              Log3 $name, 5, "Unbekannt 300_Char-19: ".dec2bin($fi[19]) ." ". dec2bin($se[19]) ." ". $values[19];
              Log3 $name, 5, "Unbekannt 300_Char-20: ".dec2bin($fi[20]) ." ". dec2bin($se[20]) ." ". $values[20];
              Log3 $name, 5, "Unbekannt 300_Char-25: ".$values[25];

              #DB-Logging
              readingsBulkUpdate($hash,"Lgg-Boilertemperatur",round($values[2]/5,1)*5);
              readingsBulkUpdate($hash,"Lgg-Puffertemperatur2",round($values[4]/5,1)*5);
              readingsBulkUpdate($hash,"Lgg-Puffertemperatur1",round($values[5]/5,1)*5);
              readingsBulkUpdate($hash,"Lgg-Rauchgastemperatur",round($values[7]/5,1)*5);
              readingsBulkUpdate($hash,"Lgg-DrehzahlHauptgeblaese",($se[23]*150)+$fi[24]);

              #Log3 $name, 2, "02-linebytes: @linebytes";
              #Log3 $name, 2, "02-size: $size";
              #readingsBulkUpdate($hash,"810_CheckSum",$checksum." // ".getCheckSum($hash, $size-1, \@linebytes));

              readingsEndUpdate($hash, 1);

            }
		    	}
        }
        when (/^0215/)   {
          #control message
          #Vom Bediengerät and das Kesselsteuergerät. Beinhaltet Steuerkommandos.

				 	Log3 $name, 5, "Start : control message";
          my @linebytesT = @linebytes[ 0 .. $#linebytes ];
	        @linebytes = @linebytes[ 1 .. $#linebytes ]; #throw away the first 02, so that lenght is correct
          my $size = @linebytes;
          #TODO verify line lenght
          Log3 $name, 5, "Array Lenght: $size";

          my $checksum = ord($linebytes[-1]);
          my $lenght = ord($linebytes[1]);
          my $address = ord($linebytes[2]);
          my $count = ord($linebytes[3]);

          if($checksum==getCheckSum($hash, $size, \@linebytesT)){

            Log3 $name, 5, "02-Lenght: $lenght";
            Log3 $name, 5, "02-Address: $address";
            Log3 $name, 5, "02-Count: $count";
            Log3 $name, 5, "02-Checksum: $checksum";
            if($count==88) {#only send message if count==88 to be less spammy
              #KWB Classicfire

              #Belegt:
              #Byte 4: 1 / 2. / 3. / 4 / 5. / 6. / 7.
              #Byte 5: 0. / 1. / 4. / 5. / 6. / 7.
              #Byte 6: 0. / 1. / 2. / 4. / 6.
              #Byte 7: ALLE (Kesselpumpe)
              #Byte 8: ALLE (Hauptgeblaese)
              #Byte 9: ALLE (Saugzuggeblaese)
              #Byte 10: (Hauptantrieb) 0 / 1 / 2 / 4 / 6
              #Byte 11: (Hauptantrieb) 4 / 5 / 6 / 7
              #Byte 12: (Hauptantrieb) 0 / 1 / 2 / 4 / 6

              readingsBeginUpdate($hash);
              #readingsBulkUpdate($hash,"Lgg-01-Line",$line);

              #Für Schnellinfo (000 - 020)
              readingsBulkUpdate($hash,"001_Anlagenstatus",KesseHeizt($linebytes[8]));
              readingsBulkUpdate($hash,"002_aktive-Heizkreise",HeizkreiseHeizt(BitGesetzt($linebytes[6], 0), BitGesetzt($linebytes[4], 7), BitGesetzt($linebytes[4], 6)));
              readingsBulkUpdate($hash,"002_aktive-Heizkreise2",HeizkreiseHeizt2(BitGesetzt($linebytes[6], 0), BitGesetzt($linebytes[4], 7), BitGesetzt($linebytes[4], 6)));
              readingsBulkUpdate($hash,"003_Kesselpumpe-[%]", ord($linebytes[7])/2.55);
              readingsBulkUpdate($hash,"004_Boilerpumpe", BitGesetzt($linebytes[4], 5));
              readingsBulkUpdate($hash,"018_Störung-1", Stoerung($linebytes[4], 3));
              readingsBulkUpdate($hash,"019_Störung-2", Stoerung($linebytes[4], 2));
              readingsBulkUpdate($hash,"020_Störung-Ges", Stoerung2($linebytes[4], 2, 3));

              #Kessel (030 - 050)
              readingsBulkUpdate($hash,"030_Hauptrelais", BitGesetzt($linebytes[6], 4));
              readingsBulkUpdate($hash,"040_Reinigung", BitGesetzt($linebytes[5], 1));
              readingsBulkUpdate($hash,"041_Ascheaustragung", BitGesetzt($linebytes[5], 0));
              readingsBulkUpdate($hash,"042_Raumaustragung", BitGesetzt($linebytes[6], 6));
              readingsBulkUpdate($hash,"044_Hauptgebläse-(nicht-linerare-Regelung) Byte8",ord($linebytes[8])." ".dec2bin(ord($linebytes[8])));
              readingsBulkUpdate($hash,"045_Saugzuggebläse-(nicht-linerare-Regelung) Byte9",ord($linebytes[9])." ".dec2bin(ord($linebytes[9])));
              readingsBulkUpdate($hash,"047_Brandschutzklappe",BitGesetzt2($linebytes[4], 1));
              readingsBulkUpdate($hash,"048_Rauchgassauger",BitGesetzt($linebytes[18], 0));
              readingsBulkUpdate($hash,"049_Leistungsausgang",BitGesetzt($linebytes[4], 4));

              #HK-0 (060 - 080)
              readingsBulkUpdate($hash,"060_Pumpe-0",BitGesetzt($linebytes[6], 0));
              readingsBulkUpdate($hash,"061_Mischer-0",MischerInfo($linebytes[6], 1, 2));

              #HK-1 (090 - 110)
              readingsBulkUpdate($hash,"090_Pumpe-1",BitGesetzt($linebytes[4], 7));
              readingsBulkUpdate($hash,"091_Mischer-1",MischerInfo($linebytes[5], 6, 7));

              #HK-2 (120 - 140)
              readingsBulkUpdate($hash,"120_Pumpe-2",BitGesetzt($linebytes[4], 6));
              readingsBulkUpdate($hash,"121_Mischer-2",MischerInfo($linebytes[5], 4, 5));

              #Sonsiges (150 - 200)

              if(AttrVal($name,"showAllReadings",1)) {
                #Unbekannt  (300 - 400)
                readingsBulkUpdate($hash,"400_Byte-04-offen-Bit-0,1,7",dec2bin(ord($linebytes[4])));
                readingsBulkUpdate($hash,"401_Byte-05-offen-Bit-2,3",dec2bin(ord($linebytes[5])));
                readingsBulkUpdate($hash,"402_Byte-06-offen-Bit-3,5,7",dec2bin(ord($linebytes[6])));
                readingsBulkUpdate($hash,"403_Byte-10",dec2bin(ord($linebytes[10])) ." ". ord($linebytes[10]));
                readingsBulkUpdate($hash,"404_Byte-11",dec2bin(ord($linebytes[11])) ." ". ord($linebytes[11]));
                readingsBulkUpdate($hash,"405_Byte-12",dec2bin(ord($linebytes[12])) ." ". ord($linebytes[12]));
                readingsBulkUpdate($hash,"406_Byte-13",dec2bin(ord($linebytes[13])) ." ". ord($linebytes[13]));
                readingsBulkUpdate($hash,"407_Byte-14",dec2bin(ord($linebytes[14])) ." ". ord($linebytes[14]));
                readingsBulkUpdate($hash,"408_Byte-15",dec2bin(ord($linebytes[15])) ." ". ord($linebytes[15]));
                readingsBulkUpdate($hash,"409_Byte-16",dec2bin(ord($linebytes[16])) ." ". ord($linebytes[16]));
                readingsBulkUpdate($hash,"410_Byte-17",dec2bin(ord($linebytes[17])) ." ". ord($linebytes[17]));
                readingsBulkUpdate($hash,"411_Byte-18",dec2bin(ord($linebytes[18])) ." ". ord($linebytes[18]));
                #Keine Bedeutung bekannt, es wechselt sehr schnell
                #readingsBulkUpdate($hash,"412_Byte-19",dec2bin(ord($linebytes[19])) ." ". ord($linebytes[19]));
              }

              Log3 $name, 4, "Unbekannt 400_Byte-04-offen-Bit-0,1,7: ".dec2bin(ord($linebytes[4]));
              Log3 $name, 4, "Unbekannt 401_Byte-05-offen-Bit-2,3: ".dec2bin(ord($linebytes[5]));
              Log3 $name, 4, "Unbekannt 402_Byte-06-offen-Bit-3,5,7: ".dec2bin(ord($linebytes[6]));
              Log3 $name, 4, "Unbekannt 403_Byte-10: ".dec2bin(ord($linebytes[10]));
              Log3 $name, 4, "Unbekannt 403_Byte-11: ".dec2bin(ord($linebytes[11]));
              Log3 $name, 4, "Unbekannt 403_Byte-12: ".dec2bin(ord($linebytes[12]));
              Log3 $name, 4, "Unbekannt 403_Byte-13: ".dec2bin(ord($linebytes[13]));
              Log3 $name, 4, "Unbekannt 403_Byte-14: ".dec2bin(ord($linebytes[14]));
              Log3 $name, 4, "Unbekannt 403_Byte-15: ".dec2bin(ord($linebytes[15]));

              #DB-Logging
              readingsBulkUpdate($hash,"Lgg-KesselHeizt", KesseHeiztLog($linebytes[8]));
              readingsBulkUpdate($hash,"Lgg-HeizkreisHeizt", HeizkreiseHeiztLog(BitGesetzt($linebytes[6], 0), BitGesetzt($linebytes[4], 7), BitGesetzt($linebytes[4], 6)));
              readingsBulkUpdate($hash,"Lgg-Raumaustragung", BitGesetzt($linebytes[6], 6));
              readingsBulkUpdate($hash,"Lgg-Boilerpumpe", BolierPumpeLog(BitGesetzt($linebytes[4], 5)));
              readingsBulkUpdate($hash,"Lgg-Stoerung_1", Stoerung($linebytes[4], 3));
              readingsBulkUpdate($hash,"Lgg-Stoerung_2", Stoerung($linebytes[4], 2));
              readingsBulkUpdate($hash,"Lgg-Byte-10-Teilw_Hauptantrieb",dec2bin(ord($linebytes[10])));
              readingsBulkUpdate($hash,"Lgg-Byte-11-Teilw_Hauptantrieb",dec2bin(ord($linebytes[11])));
              readingsBulkUpdate($hash,"Lgg-Byte-12-Teilw_Hauptantrieb",dec2bin(ord($linebytes[12])));

              #readingsBulkUpdate($hash,"820_CheckSum",$checksum." // ".getCheckSum($hash, $size, \@linebytesT));

              readingsEndUpdate($hash, 1);
            }
		      }

        }

        when (/^0240/)   {
          #control message

				 	Log3 $name, 5, "Start : message 2040";
          my @linebytesT = @linebytes[ 0 .. $#linebytes ];
	        @linebytes = @linebytes[ 1 .. $#linebytes ]; #throw away the first 02, so that lenght is correct
          my $size = @linebytes;
          #TODO verify line lenght
          Log3 $name, 5, "Array Lenght: $size";

          my $checksum = ord($linebytes[-1]);
          my $lenght = ord($linebytes[1]);
          my $address = ord($linebytes[2]);
          my $count = ord($linebytes[3]);

          if($checksum==getCheckSum($hash, $size, \@linebytesT)){

            Log3 $name, 5, "02-Lenght: $lenght";
            Log3 $name, 5, "02-Address: $address";
            Log3 $name, 5, "02-Count: $count";
            Log3 $name, 5, "02-Checksum: $checksum";

            readingsBeginUpdate($hash);

            if(AttrVal($name,"showAllReadings",1)) {
              #Unbekannt  (300 - 400)
              readingsBulkUpdate($hash,"500_Line",$line);
              readingsBulkUpdate($hash,"500_Length",$lenght);
              readingsBulkUpdate($hash,"500_Byte-01",dec2bin(ord($linebytes[1])) ." ". ord($linebytes[1]));
              readingsBulkUpdate($hash,"501_Byte-02",dec2bin(ord($linebytes[2])) ." ". ord($linebytes[2]));
              readingsBulkUpdate($hash,"502_Byte-03",dec2bin(ord($linebytes[3])) ." ". ord($linebytes[3]));
              readingsBulkUpdate($hash,"503_Byte-04",dec2bin(ord($linebytes[4])) ." ". ord($linebytes[4]));
              readingsBulkUpdate($hash,"504_Byte-04",dec2bin(ord($linebytes[5])) ." ". ord($linebytes[5]));
              readingsBulkUpdate($hash,"505_Byte-06",dec2bin(ord($linebytes[6])) ." ". ord($linebytes[6]));
              readingsBulkUpdate($hash,"506_Byte-07",dec2bin(ord($linebytes[7])) ." ". ord($linebytes[7]));
              readingsBulkUpdate($hash,"507_Byte-08",dec2bin(ord($linebytes[8])) ." ". ord($linebytes[8]));
              readingsBulkUpdate($hash,"508_Byte-09",dec2bin(ord($linebytes[9])) ." ". ord($linebytes[9]));
              readingsBulkUpdate($hash,"509_Byte-10",dec2bin(ord($linebytes[10])) ." ". ord($linebytes[10]));
              readingsBulkUpdate($hash,"510_Byte-11",dec2bin(ord($linebytes[11])) ." ". ord($linebytes[11]));
              readingsBulkUpdate($hash,"511_Byte-12",dec2bin(ord($linebytes[12])) ." ". ord($linebytes[12]));
              readingsBulkUpdate($hash,"512_Byte-13",dec2bin(ord($linebytes[13])) ." ". ord($linebytes[13]));
              readingsBulkUpdate($hash,"512_Byte-14",dec2bin(ord($linebytes[14])) ." ". ord($linebytes[14]));
              readingsBulkUpdate($hash,"512_Byte-15",dec2bin(ord($linebytes[15])) ." ". ord($linebytes[15]));
              readingsBulkUpdate($hash,"512_Byte-16",dec2bin(ord($linebytes[16])) ." ". ord($linebytes[16]));
              readingsBulkUpdate($hash,"512_Byte-17",dec2bin(ord($linebytes[17])) ." ". ord($linebytes[17]));
              readingsBulkUpdate($hash,"512_Byte-18",dec2bin(ord($linebytes[18])) ." ". ord($linebytes[18]));
              readingsBulkUpdate($hash,"512_Byte-19",dec2bin(ord($linebytes[19])) ." ". ord($linebytes[19]));
              readingsBulkUpdate($hash,"512_Byte-20",dec2bin(ord($linebytes[20])) ." ". ord($linebytes[20]));
              readingsBulkUpdate($hash,"512_Byte-21",dec2bin(ord($linebytes[21])) ." ". ord($linebytes[21]));
            }

            readingsEndUpdate($hash, 1);
		      }

        }

        when (/^0260/)   {
          #control message

				 	Log3 $name, 5, "Start : message 2060";
          my @linebytesT = @linebytes[ 0 .. $#linebytes ];
	        @linebytes = @linebytes[ 1 .. $#linebytes ]; #throw away the first 02, so that lenght is correct
          my $size = @linebytes;
          #TODO verify line lenght
          Log3 $name, 5, "Array Lenght: $size";

          my $checksum = ord($linebytes[-1]);
          my $lenght = ord($linebytes[1]);
          my $address = ord($linebytes[2]);
          my $count = ord($linebytes[3]);

          if($checksum==getCheckSum($hash, $size, \@linebytesT)){

            Log3 $name, 5, "02-Lenght: $lenght";
            Log3 $name, 5, "02-Address: $address";
            Log3 $name, 5, "02-Count: $count";
            Log3 $name, 5, "02-Checksum: $checksum";

            readingsBeginUpdate($hash);

            if(AttrVal($name,"showAllReadings",1)) {
              #Unbekannt  (300 - 400)
              readingsBulkUpdate($hash,"600_Line",$line);
              readingsBulkUpdate($hash,"600_Length",$lenght);
              readingsBulkUpdate($hash,"600_Byte-01",dec2bin(ord($linebytes[1])) ." ". ord($linebytes[1]));
              readingsBulkUpdate($hash,"601_Byte-02",dec2bin(ord($linebytes[2])) ." ". ord($linebytes[2]));
              readingsBulkUpdate($hash,"602_Byte-03",dec2bin(ord($linebytes[3])) ." ". ord($linebytes[3]));
              readingsBulkUpdate($hash,"603_Byte-04",dec2bin(ord($linebytes[4])) ." ". ord($linebytes[4]));
              readingsBulkUpdate($hash,"604_Byte-04",dec2bin(ord($linebytes[5])) ." ". ord($linebytes[5]));
              readingsBulkUpdate($hash,"605_Byte-06",dec2bin(ord($linebytes[6])) ." ". ord($linebytes[6]));
              readingsBulkUpdate($hash,"606_Byte-07",dec2bin(ord($linebytes[7])) ." ". ord($linebytes[7]));
              readingsBulkUpdate($hash,"607_Byte-08",dec2bin(ord($linebytes[8])) ." ". ord($linebytes[8]));
              readingsBulkUpdate($hash,"608_Byte-09",dec2bin(ord($linebytes[9])) ." ". ord($linebytes[9]));
              readingsBulkUpdate($hash,"609_Byte-10",dec2bin(ord($linebytes[10])) ." ". ord($linebytes[10]));
              readingsBulkUpdate($hash,"610_Byte-11",dec2bin(ord($linebytes[11])) ." ". ord($linebytes[11]));
              readingsBulkUpdate($hash,"611_Byte-12",dec2bin(ord($linebytes[12])) ." ". ord($linebytes[12]));
              readingsBulkUpdate($hash,"612_Byte-13",dec2bin(ord($linebytes[13])) ." ". ord($linebytes[13]));
            }

            readingsEndUpdate($hash, 1);
		      }
        }

        when (/^0270/)   {
          #control message

				 	Log3 $name, 5, "Start : message 2070";
          my @linebytesT = @linebytes[ 0 .. $#linebytes ];
	        @linebytes = @linebytes[ 1 .. $#linebytes ]; #throw away the first 02, so that lenght is correct
          my $size = @linebytes;
          #TODO verify line lenght
          Log3 $name, 5, "Array Lenght: $size";

          my $checksum = ord($linebytes[-1]);
          my $lenght = ord($linebytes[1]);
          my $address = ord($linebytes[2]);
          my $count = ord($linebytes[3]);

          if($checksum==getCheckSum($hash, $size, \@linebytesT)){

            Log3 $name, 5, "02-Lenght: $lenght";
            Log3 $name, 5, "02-Address: $address";
            Log3 $name, 5, "02-Count: $count";
            Log3 $name, 5, "02-Checksum: $checksum";

            readingsBeginUpdate($hash);

            if(AttrVal($name,"showAllReadings",1)) {
              #Unbekannt  (300 - 400)
              readingsBulkUpdate($hash,"700_Line",$line);
              readingsBulkUpdate($hash,"700_Length",$lenght);
              readingsBulkUpdate($hash,"700_Byte-01",dec2bin(ord($linebytes[1])) ." ". ord($linebytes[1]));
              readingsBulkUpdate($hash,"701_Byte-02",dec2bin(ord($linebytes[2])) ." ". ord($linebytes[2]));
              readingsBulkUpdate($hash,"702_Byte-03",dec2bin(ord($linebytes[3])) ." ". ord($linebytes[3]));
              readingsBulkUpdate($hash,"703_Byte-04",dec2bin(ord($linebytes[4])) ." ". ord($linebytes[4]));
              readingsBulkUpdate($hash,"704_Byte-04",dec2bin(ord($linebytes[5])) ." ". ord($linebytes[5]));
              readingsBulkUpdate($hash,"705_Byte-06",dec2bin(ord($linebytes[6])) ." ". ord($linebytes[6]));
              readingsBulkUpdate($hash,"706_Byte-07",dec2bin(ord($linebytes[7])) ." ". ord($linebytes[7]));
              readingsBulkUpdate($hash,"707_Byte-08",dec2bin(ord($linebytes[8])) ." ". ord($linebytes[8]));
              readingsBulkUpdate($hash,"708_Byte-09",dec2bin(ord($linebytes[9])) ." ". ord($linebytes[9]));
              readingsBulkUpdate($hash,"709_Byte-10",dec2bin(ord($linebytes[10])) ." ". ord($linebytes[10]));
              readingsBulkUpdate($hash,"710_Byte-11",dec2bin(ord($linebytes[11])) ." ". ord($linebytes[11]));
              readingsBulkUpdate($hash,"711_Byte-12",dec2bin(ord($linebytes[12])) ." ". ord($linebytes[12]));
              readingsBulkUpdate($hash,"712_Byte-13",dec2bin(ord($linebytes[13])) ." ". ord($linebytes[13]));
            }

            readingsEndUpdate($hash, 1);
		      }
        }


        default        {
   				Log3 $name, 5, "Unknown message: $line";
        }
    }
  }
  #$hash->{PARTIAL} = $buf;
}

#####################################
sub KWB_Ready($) {
  my ($hash) = @_;

  return DevIo_OpenDev($hash, 1, "KWB_InitFn")
                if($hash->{STATE} eq "disconnected");

  # This is relevant for windows/USB only
  my $po = $hash->{USBDev};
  my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  return ($InBytes>0);
}

#####################################
sub KWB_InitFn($) {
  my $hash = shift;
  my $name = $hash->{NAME};

  DevIo_SetHwHandshake($hash) if($hash->{USBDev});
  #$hash->{PARTIAL} = "";
  $hash->{STATE} = "Initialized";
  return undef;
}

1;

