#########################################################################
# fhem Modul für Victron BMV 600 Batteriemonitor
# Die Beschreibung und Kommentierung dieses Moduls ist in deutsch gehalten,
# um anderen das erstellen eigener Module zu erleichtern.
# Als Basis diente das Modul 00_TAHR.pm und 00_WHR962.pm
#
#										# Kommentarbereich
package main;

use strict;                          #
use warnings;                        #
use Time::HiRes qw(gettimeofday);    #

sub BMV600_Read($);                  #
sub BMV600_Ready($);                 #
sub BMV600_setbits($$);              #
sub BMV600_SetReading($$$$);         #

my $buf1 = "";                       # Hilfsvariable für auslesen des Buffers
my $Zeit = 0;                        # Hilfsvariable Zeit
my $Umin = 15;                       #Hilfsvariable Minimale Batteriespannung (um ungültige Werte abzufangen) in V
my $Umax = 38;                      #dito, nur uMax

my %BMV600_sets = (    		# Hier werden Befehle an den Batterie Monitor geschickt

);


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

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

	$hash->{ReadFn}  = "BMV600_Read";
	$hash->{ReadyFn} = "BMV600_Ready";
	$hash->{DefFn}   = "BMV600_Define";
	$hash->{UndefFn} = "BMV600_Undef";
	$hash->{SetFn}   = "BMV600_Set";
	$hash->{AttrList} =
	  "do_not_notify:1,0 loglevel:0,1,2,3,4,5,6 " . $readingFnAttributes;
}

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

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

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

	if ( $dev eq "none" )
	{
		Log3 undef, 1, "BMV600 device is none, commands will be echoed only";
		return undef;
	}

	$hash->{DeviceName} = $dev;
	my $ret = DevIo_OpenDev( $hash, 0, "BMV600_Poll" );
	return $ret;
}

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

#########################################################################
sub BMV600_Set($@)
{
	my ( $hash, @a ) = @_;
	my $name = $hash->{NAME};
	return "\"set BMV600\" needs at least an argument" if ( @a < 2 );

	my $cmd = $BMV600_sets{ $a[1] };
	return "Unknown argument $a[1], choose one of "
	  . join( " ", sort keys %BMV600_sets )
	  if ( !defined($cmd) );

	Log3 $name, 3, "DevIo_SimpleWrite: $hash $cmd";
	DevIo_SimpleWrite( $hash, $cmd, 1 );
	return undef;
}


sub BMV600_Read($)
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	my ( $data, $crc );
	my $buf = DevIo_SimpleRead($hash);
	my $tn  = TimeNow();
	my ( $key, $val ) = ( "key", "val" );
  my $Power = 0;  
  
	###### Daten der seriellen Schnittstelle holen und an $buf1 anhaengen

	return "" if ( !defined($buf) );
	$buf1 .= $buf;

	#Log3 $name, 5, "Current buffer content: "  $buf1;

        my $pos_cs0 = index($buf1,"\nChecksum");			#sucht nach neuer Zeile mit Checksum
        my $pos_cs1 = -1;
	if($pos_cs0 >= 0)
	{
	        $pos_cs1 = index($buf1,"\nChecksum",$pos_cs0+1);
	}
	if(($pos_cs0 >= 0) && ($pos_cs1 >= 0))				#wenn 2mal Checksum dann beginnt er das lesen
	{
		readingsBeginUpdate($hash);
      	        my @e = split("\n",$buf1);				#Splittet die Daten bei einer neuen Zeile auf
		my $V_first = index($e[1],"V") >= 0;			#schaut in die Schlaufe und fängt bei V an
		for my $i (0 .. $#e)
		{
			
      my @e_ = split(" ",$e[$i]);
			if($e_[0] eq "Checksum")
			{
				next;
			}
      if($e_[0] eq "V")
			{
				if(($e_[1] /1000 ) > $Umin && ($e_[1] /1000 ) < $Umax ) #nur Werte übernehmen die im gültigen bereich liegen
        {
          #readingsBulkUpdate($hash,"_V",($e_[1] )); 
          readingsBulkUpdate($hash,"Spannung",sprintf("%.1f",$e_[1]/1000));
          $Power = $e_[1] / 1000;  			#für Leistungsberechnung als Reading
        }
			}
		
		   
      if($e_[0] eq "I")
			{
	#readingsBulkUpdate($hash,"_I",($e_[1]));  # aktueller Strom in mA
        readingsBulkUpdate($hash,"Strom",sprintf("%.1f",$e_[1] / 1000)); # aktueller Strom in A
        $Power = ($Power * $e_[1]) / 1000;           	#für Leistungsberechnung als Reading
			}
      
      if($e_[0] eq "SOC")
			{
				if(($e_[1]) > -1 && ($e_[1]) < 1001 )
        {
        #readingsBulkUpdate($hash,"_SOC",$e_[1]);  # Ladezustand in %
        readingsBulkUpdate($hash,"Ladezustand",sprintf("%.2f",($e_[1]) / 10));  # Ladezustand in %
        }
			}
      if($e_[0] eq "TTG")
			{
	#readingsBulkUpdate($hash,"_TTG",$e_[1]);   #Restlaufzeit in Minuten
        readingsBulkUpdate($hash,"Restlaufzeit_h",sprintf("%.2f",($e_[1]) / 60));   #Restlaufzeit in stunden
			}
      if($e_[0] eq "VS")
			{
	#readingsBulkUpdate($hash,"_VS",$e_[1]);
        readingsBulkUpdate($hash,"Starterbatteriespannung",sprintf("%.2f",($e_[1]) / 1000)); #Spannung der Starterbatterie
			}
      if($e_[0] eq "CE")
			{
	#readingsBulkUpdate($hash,"_CE",$e_[1]);  #entnommene Kapazität in Ah
        readingsBulkUpdate($hash,"Amperestunden",sprintf("%.1f",$e_[1] /1000 ));
			}
      if($e_[0] eq "H1")    # mAh Wert der bisher tiefsten Entladung 
			{
	#readingsBulkUpdate($hash,"_H1",$e_[1]);
        readingsBulkUpdate($hash,"Entladung_tiefste_Ah",sprintf("%.2f",$e_[1] /1000 ));
			}
      if($e_[0] eq "H2")     # mAh Wert der letzten Entladung
			{
	#readingsBulkUpdate($hash,"_H2",$e_[1]);
        readingsBulkUpdate($hash,"Entladung_letzte_Ah",sprintf("%.2f",$e_[1] / 1000));
			}
      if($e_[0] eq "H3")   # mAh Wert der durchschnittlichen Entladung
			{
	#readingsBulkUpdate($hash,"_H3",$e_[1]);
        readingsBulkUpdate($hash,"Entladung_Durchschnitt_Ah",sprintf("%.2f",$e_[1] / 1000));
			}
      if($e_[0] eq "H4")  # Anzahl der Ladezyklen
			{
	#readingsBulkUpdate($hash,"_H4",$e_[1]);
        readingsBulkUpdate($hash,"Ladezyklen_Anzahl",$e_[1]);
			}
      if($e_[0] eq "H5") # Anzahl der völligen Entladungen 
			{
	#readingsBulkUpdate($hash,"_H5",$e_[1]);
        readingsBulkUpdate($hash,"Entladung_voellige_Anzahl",$e_[1]);
			}
      if($e_[0] eq "H6")  # mAh Gesamtwert (kumuliert) der bisher entnommenen Ah
			{
	#readingsBulkUpdate($hash,"_H6",$e_[1]);
        readingsBulkUpdate($hash,"Entladung_gesamt_Ah",sprintf("%.2f",$e_[1] / 1000));
			}
      if($e_[0] eq "H7")  # mV Minimalwert der Batteriespannung 
			{
	#readingsBulkUpdate($hash,"_H7",$e_[1]);
        readingsBulkUpdate($hash,"Spannung_minmal",sprintf("%.2f",$e_[1] / 1000));
			}
      if($e_[0] eq "H8")  # mV Maximalwert der Batteriespannung 
			{
	#readingsBulkUpdate($hash,"_H8",$e_[1]);
        readingsBulkUpdate($hash,"Spannung_maximal",sprintf("%.2f",$e_[1] / 1000));
			}
      if($e_[0] eq "H9")  # Sekunden  Anzahl der Tage seit der letzten Volladung
			{
	#readingsBulkUpdate($hash,"_H9",$e_[1]);
        readingsBulkUpdate($hash,"Volladung_letzte_vor_Stunden",sprintf("%.2f",$e_[1] / 3600));
			} 
      if($e_[0] eq "H10") # Anzahl der automatisch durchgeführten Synchronistionen 
			{
	#readingsBulkUpdate($hash,"_H10",$e_[1]);
        readingsBulkUpdate($hash,"autom_Synchronisationen_Anzahl",$e_[1]);
			}
      if($e_[0] eq "H11") # Anzahl der Unterspannungs Alarme
			{
	#readingsBulkUpdate($hash,"_H11",$e_[1]);
        readingsBulkUpdate($hash,"Alarm_Unterspannung_Anzahl",$e_[1]);
			}
      if($e_[0] eq "H12") # Anzahl der Überspannungs Alarme.
			{
	#readingsBulkUpdate($hash,"_H12",$e_[1]);
        readingsBulkUpdate($hash,"Alarm_Ueberspannungs_Anzahl",$e_[1]);
			}
      if($e_[0] eq "H13") # Anzahl der Alarme für leere Starterbatterie.  nur beim BMV-602
			{
	#readingsBulkUpdate($hash,"_H13",$e_[1]);
        readingsBulkUpdate($hash,"Alarm_leere_Starterbatterie_Anzahl",$e_[1]);
			}
      if($e_[0] eq "H14") # Anzahl der Alarme bezüglich der Zahl der Überspannungsalarme nur beim BMV-602 
			{
	#readingsBulkUpdate($hash,"_H14",$e_[1]);
        readingsBulkUpdate($hash,"Alarm_Ueberspannung_Starterbatterie_Anzahl",$e_[1]);
			}
      if($e_[0] eq "H15") # Minimale Starter-Batterie-Spannung.  nur beim BMV-602 
			{
	#readingsBulkUpdate($hash,"_H15",$e_[1]);
        readingsBulkUpdate($hash,"Starterbatteriespannung_minimal",sprintf("%.2f",$e_[1] / 1000));
			}
      if($e_[0] eq "H16") # Maximale Starterbatteriespannung  nur beim BMV-602
			{
	#readingsBulkUpdate($hash,"_H16",$e_[1]);
        readingsBulkUpdate($hash,"Starterbatteriespannung_maximal",sprintf("%.2f",$e_[1] / 1000));
			} 
      if($e_[0] eq "Alarm")
			{
	#readingsBulkUpdate($hash,"_Alarm",$e_[1]);
        readingsBulkUpdate($hash,"Alarmzustand",$e_[1]);
			}
      if($e_[0] eq "AR")
			{
				 # Alarm Reason :
                                   #Low Voltage                 1 
                                   #High Voltage                2 
                                   #Low SOC                     4 
                                   #Low Starter Voltage         8 
                                   #High Starter Voltage        16
        if($e_[1] eq "0"){readingsBulkUpdate($hash,"Alarm_Reason","-");}
        elsif($e_[1] eq "1"){readingsBulkUpdate($hash,"Alarm_Reason","Low Voltage");}
        elsif($e_[1] eq "2"){readingsBulkUpdate($hash,"Alarm_Reason","High Voltage");}
        elsif($e_[1] eq "4"){readingsBulkUpdate($hash,"Alarm_Reason","Low SOC");}
        elsif($e_[1] eq "8"){readingsBulkUpdate($hash,"Alarm_Reason","Low Starter Voltage");}
        elsif($e_[1] eq "16"){readingsBulkUpdate($hash,"Alarm_Reason","High Starter Voltage");}
        else{readingsBulkUpdate($hash,"Alarm_Reason","Multi-Alarm");}
        #readingsBulkUpdate($hash,"_AR",$e_[1]);
			}
      if($e_[0] eq "FW")
			{
	#readingsBulkUpdate($hash,"_FW",$e_[1]);
        readingsBulkUpdate($hash,"BMV_Firmware",$e_[1]);
			}
      if($e_[0] eq "BMV")
			{
	#readingsBulkUpdate($hash,"_BMV",$e_[1]); 
        readingsBulkUpdate($hash,"BMV_Modell",$e_[1]);
			}
      
    } 
    readingsBulkUpdate($hash,"Power",sprintf("%.2f",$Power));
    #readingsBulkUpdate($hash,"Umin(System)",$Umin);
    #readingsBulkUpdate($hash,"Umax(System)",$Umax);
		readingsEndUpdate( $hash, 1 );
		$buf1 = "";
	}
}

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

	return DevIo_OpenDev( $hash, 1, undef )
	  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 BMV600_Poll($)
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	push @{ $hash->{SENDBUFFER} }, $BMV600_sets{"logmode"};
	DevIo_SimpleWrite( $hash, "07F0009B01014A070F", 1 );
	Log3 $name, 5, "RS232 Modus PC-Master";
	return undef;
}

#########################################################################
sub BMV600_SetReading($$$$)
{
	my ( $hash, $tn, $key, $val ) = @_;
	my $name = $hash->{NAME};
	Log3 $name, 4, "$name: $key $val";
	$hash->{READINGS}{$key}{TIME} = $tn;
	$hash->{READINGS}{$key}{VAL}  = $val;
	DoTrigger( $name, "$key: $val" );
}


1;
