###############################################
# Module for KWB
# Currently works only for KWB ClassicFire
# Author: Markus Heberling <markus@tisoft.de>
# Date: 2014-01-08

package main;

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

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

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->{AttrList}= "loglevel:0,1,2,3,4,5,6 ".
                     "do_not_notify:1,0 ignore:1,0 dummy:1,0 showtime:1,0 ".
                     $readingFnAttributes;
}

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

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

  DevIo_CloseDev($hash);
  my $name = $a[0];
  my $dev = $a[2];

  if($dev eq "none") {
    Log 1, "KWB device is none, commands will be echoed only";
    return undef;
  }
  
  $hash->{PARTIAL} = "";
  $hash->{DeviceName} = $dev;
  my $ret = DevIo_OpenDev($hash, 0, "KWB_InitFn");
  return $ret;
}


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

#####################################
sub
KWB_Set($@)
{
  #no write support currently
  return undef;
}


#####################################
# 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 $ll5 = GetLogLevel($name,5);

  my $buf = DevIo_SimpleRead($hash);
  return "" if(!defined($buf));

  $buf = unpack('H*', $buf);
#  Log $ll5, "RAW: $buf";

  $buf = $hash->{PARTIAL} . $buf;
  while($buf =~ m/^(?:..)+?(02(?!00)(?:..)+?)(02(?!00).+?)$/) {#packets start with 02XX where XX is NOT 00
    my $line = $1;
    $buf = $2;
    ######################################
    # We are inside a packet any remaining 0200 should be converted to 02
    #TODO check for full bytes, other wise 802008 would be mangled to 8008 which is wrong
    $line =~ s/0200/02/g;
    Log $ll5, "LINE: $line";
    # We want the bytes back
    my @linebytes = map { pack('C', hex($_)) } ($line =~ /(..)/g);

    given($line) {
        when (/^0202/) { 
            #sense message
            @linebytes = @linebytes[ 1 .. $#linebytes ]; #throw away the first 02, so that lenght is correct
            
            my $size = @linebytes;
            #TODO verify line lenght
            Log $ll5, "Array Lenght: $size";

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

            Log $ll5, "Lenght: $lenght";
            Log $ll5, "Address: $address";
            Log $ll5, "Count: $count";
            Log $ll5, "Checksum: $checksum";
            
            #bytes 4-8 are some bitfields
            
            my @values = ();
            #starting with byte 9 we have temperature/percantage values
            # last 4 bytes are other stuff
            for (my $i = 9; $i < $size-4; $i += 2) {
            	my $first =ord(@linebytes[$i]);
            	if ($first >127) {
            	    $first-=256;
            	} 
            	my $second =ord(@linebytes[$i+1]); 
            	my $value=$first*25.5+$second/10;
            	push(@values, $value);
	            Log $ll5, "$i: $first $second $value";
                #first = message.data[i] if message.data[i] <= 127 else message.data[i] - 256 
                #temperature.append(first*25.5+message.data[i+1]/10)
            }
            
			if($address == 48 && $count==66) {#only send message if count==66 to be less spammy
                #KWB Classicfire
                
            readingsBeginUpdate($hash);
#temperature_name[0]="° Rücklauftemperatur 2?"
#temperature_name[1]="° Rücklauftemperatur 1"
            readingsBulkUpdate($hash,"Boilertemperatur",@values[2]);
            readingsBulkUpdate($hash,"Kesseltemperatur",@values[3]);
            readingsBulkUpdate($hash,"Puffertemperatur1",@values[5]);
            readingsBulkUpdate($hash,"Puffertemperatur2",@values[4]);
            readingsBulkUpdate($hash,"Puffertemperatur3",@values[12]);
            readingsBulkUpdate($hash,"Sauerstoffgehalt",@values[13]);
            readingsBulkUpdate($hash,"Rauchgastemperatur",@values[17]);
            readingsBulkUpdate($hash,"Aussentemperatur",@values[18]);
#temperature_name[18]="° Aussentemperatur HK0"
#temperature_name[20]="° Vorlauf Ist HK0"
            readingsEndUpdate($hash, 1); 
            }           
        }
        when (/^02/)   {
            #control message 
        }
        default        { 
            #unknown packet typ
        }
    }
  }
  $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;

