package main;
use strict;
use warnings;

#available get cmds
my %AutoMower_gets = (
	"state" => ""
);

#available set cmds
my %AutoMower_sets = (
	"stop" => "",
	"home" => "",
	"auto" => "",
	"man" => ""
);

my $debug = 0;
my $interval = 0;
my $define = "";

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

    $hash->{DefFn}      = 'AutoMower_Define';
    $hash->{UndefFn}    = 'AutoMower_Undef';
    $hash->{SetFn}      = 'AutoMower_Set';
    $hash->{GetFn}      = 'AutoMower_Get';
    $hash->{AttrFn}     = 'AutoMower_Attr';
    $hash->{ReadFn}     = 'AutoMower_Read';
	$hash->{ShutdownFn} = 'AutoMower_Shutdown';
	$hash->{ReadyFn} 	= 'AutoMower_Ready';

    $hash->{AttrList} =	"IODev do_not_notify:1,0 ignore:0,1 showtime:1,0 " .
						"pollInterval:30,60,120,0 " .
						"debug:1,0 ";
}

#This method is called at define
#
sub AutoMower_Define($$) 
{
    my ($hash, $def) = @_;
	my @param = split("[ \t][ \t]*", $def);
	my $errStr;
	
	#this device is single-instance only. Check for any other running instance
	if (not ($define eq ""))
	{
		$errStr = "This device is single instance only. An instance is alreday defined. Name: $define";		
		debug ($errStr);
		Log (0, $errStr);
        return $errStr;			
	}
	
	#check for minimum length
    if(int(@param) < 3) 
	{
		$errStr = "too few parameters: define <name> AutoMower <port>";		
		debug ($errStr);
		Log (0, $errStr);
        return $errStr;		
    }
	
	#check for parameter debug
	for (my $i = 3; $i < int(@param); $i++)
	{
		my $par = $param[$i];
		
		if (($par eq "debug") or ($par eq "debug=1") or ($par eq "debug=true"))
		{
			$debug = 1;
			debug("debug enabled via definition");		
		}
	}

	#check for other params
	for (my $i = 0; $i < int(@param); $i++)
	{
		my $par = $param[$i];
		
		debug("supplied parameter no:$i, val: $par");		
	}
	    
	#assign name and port
    $hash->{NAME} = $param[0];
	debug ("name: $hash->{NAME}");
    $hash->{PORTNAME} = $param[2];
	debug ("port: $hash->{PORTNAME}");
	
	#try to open and init port
	$errStr = openPort ($hash);

	#error?
	if ($errStr)
	{
		#error
		debug ($errStr);
		Log (0, $errStr);	
	} else
	{
		#no error
		#use poll timer
		InternalTimer(gettimeofday() + $interval, "AutoMower_GetUpdate", $hash, 0) if ($interval > 0);
		#mark instance as already defined
		$define = $hash->{NAME};
	}
	
	readingsSingleUpdate($hash,"state","undef",1);
	readingsSingleUpdate($hash,"blades","undef",1);
	readingsSingleUpdate($hash,"error","undef",1);

    return $errStr;
}

#Destructor
#
sub AutoMower_Undef($$) 
{
    my ($hash, $arg) = @_; 
	my $name = $hash->{NAME};	
	my $errStr;	
	
	debug ("Called _Undef");
	
	#kill interval timer
	RemoveInternalTimer($hash);  
	
	#delete device from fhem
	foreach my $d (sort keys %defs) 
	{
		if(defined($defs{$d}) and defined($defs{$d}{IODev}) and $defs{$d}{IODev} == $hash)
		{
			my $lev = ($reread_active ? 4 : 2);
			delete $defs{$d}{IODev};
		}
	}  
  
	#close port
  	AutoMower_Shutdown ($hash);
	
    return $errStr;
}

#Shutdown
#
sub AutoMower_Shutdown($) 
{
  my ($hash) = @_;
  
  debug ("Called _Shutdown");
  
  closePort ($hash);
  
  return undef;
}

#This function is called from fhem from rime to time
#It executes a reconnect, if the state was disconnected due to an error
#
sub AutoMower_Ready($) 
{
	my ($hash) = @_;
	my $port = $hash->{PORT};
	my $state = $hash->{STATE};
	
	#not so helpful - spam
	#debug ("Called _Ready");

	if(!$port or ($state eq "disconnected"))
	{
		debug ("try to reconnect due to state disconnected");
		return openPort ($hash, 1);
	}
	
	#This is relevant for windows/USB only
	my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $port->status;
	return ($InBytes>0);
}

#Reacts on the get-cmd from fhem
#
sub AutoMower_Get($@) 
{
	my ($hash, @param) = @_;
	my $par;
	my $errMsg;
	
	debug ("Called _Get");
	
	#give up - only one argument accepted
	return "get $hash->{NAME} needs exactly one argument" if ((int(@param) < 2) or (int(@param) > 3));
	
	#easy reading
	$par = $param[1];

	#response for gui
	my $list = $AutoMower_gets{$par};
	return "Unknown argument $par, choose one of " . join(" ", " ", sort keys %AutoMower_gets) if(!defined($list));
	
	#get actual mower state
	if ($par eq "state")
	{
		#request status- results processed in read
		$errMsg = writeMsg ($hash, "12", "01");
		
		#quit HMI
		return "state requested successfully" if (!$errMsg);
	}
	
	return "failed to request actual state - $errMsg"; 
}

#Reacts on the set-cmd from fhem
#
sub AutoMower_Set($@) 
{
	my ($hash, @param) = @_;
	my $par;
	my $errMsg;
	my $i;
	
	debug ("Called _Set");
	
	#give up - only one argument accepted
	return "set $hash->{NAME} needs exactly one argument" if ((int(@param) < 2) or (int(@param) > 3));

	#easy reading
	$par = $param[1];
	
	#response for gui
	my $list = $AutoMower_sets{$par};
	return "Unknown argument $par, choose one of " . join(" ", " ", sort keys %AutoMower_sets) if(!defined($list));

	debug ("try to set command $par");
	
	#set the desired mower state
	if ($par eq "stop")
	{
		#send stop
		$errMsg = writeMsg ($hash, "0E", "00");
		
		#quit HMI
		return "$par requested successfully" if (!$errMsg);
	} elsif ($par eq "home")
	{
		#set to home
		$errMsg = writeMsg ($hash, "0E", "02");
		sleep (1);
		
		#if any error, quit with this error
		return "failed requesting $par - $errMsg" if ($errMsg);
		
		#not nice, but required...
		#wait max. 5 seconds for the ack.
		for ($i = 0; $i < 5; $i++)
		{
			if ($hash->{waitForAck})
			{
				AutoMower_Read($hash);				
				sleep (1);
			} else
			{
				$i = 6;
			}
		}
		#if time elapsed, quit with error
		return "failed requesting $par - no ack received" if ($i eq 5);
		
		#start moving to home
		$errMsg = writeMsg ($hash, "0E", "01");
		
		#quit HMI
		return "$par requested successfully" if (!$errMsg);
	}
	elsif ($par eq "auto")
	{
		#set to auto
		$errMsg = writeMsg ($hash, "0E", "04");
		sleep (1);
		
		#if any error, quit with this error
		return "failed requesting $par - $errMsg" if ($errMsg);
		
		#not nice, but required...
		#wait max. 5 seconds for the ack.
		for ($i = 0; $i < 5; $i++)
		{			
			if ($hash->{waitForAck})
			{				
				AutoMower_Read($hash);
				sleep (1);		
			} else
			{
				$i = 6;
			}
		}
		#if time elapsed, quit with error
		return "failed requesting $par - no ack received" if ($i eq 5);
		
		#start moving to home
		$errMsg = writeMsg ($hash, "0E", "01");
		
		#quit HMI
		return "$par requested successfully" if (!$errMsg);
	} elsif ($par eq "man")
	{
		#set to home
		$errMsg = writeMsg ($hash, "0E", "03");
		sleep (1);
		
		#if any error, quit with this error
		return "failed requesting $par - $errMsg" if ($errMsg);
		
		#not nice, but required...
		#wait max. 5 seconds for the ack.
		for ($i = 0; $i < 5; $i++)
		{
			if ($hash->{waitForAck})
			{				
				AutoMower_Read($hash);				
				sleep (1);
			} else
			{
				$i = 6;
			}
		}
		#if time elapsed, quit with error
		return "failed requesting $par - no ack received" if ($i eq 5);
		
		#start moving to home
		$errMsg = writeMsg ($hash, "0E", "01");
		
		#quit HMI
		return "$par requested successfully" if (!$errMsg);	
	}
	
	return "failed requesting $par - $errMsg" if ($errMsg);
}

#called on every mod of the attributes
#
sub AutoMower_Attr(@) 
{
	my ($cmd,$name,$attr_name,$attr_value) = @_;
	
	if($cmd eq "set") 
	{		
		if(($attr_name eq "debug") and (($attr_value eq "1") or ($attr_value eq "true")))
		{
			#debug enable
			$debug = 1;
			debug("debug enabled via attribute");
		} elsif (($attr_name eq "debug") and (($attr_value eq "0") or ($attr_value eq "false")))
		{
			#debug disable
			debug("debug disabled via attribute");		
			$debug = 0;			
		} elsif($attr_name eq "pollInterval")
		{			
			#poll-Interval
			if (($attr_value >= 5) and ($attr_value <= 3600))
			{
				$interval = $attr_value;
				debug("interval set to $interval seconds");		
			} else
			{
				my $errMsg = "interval must be a numeric value between 5 and 3600s";
				Log (3, $errMsg);
				debug ($errMsg);
				return $errMsg;
			}
			
		} 
	} elsif ($cmd eq "del")
	{
		if($attr_name eq "debug")
		{
			#debug
			debug("debug disabled via attribute");
			$debug = 0;
		} elsif($attr_name eq "pollInterval")
		{
			#poll-Interval
			$interval = 0;
			debug("interval set to $interval seconds");		
		}	
		
	}
	
	return undef;
}

#Called, if any new data is available at the serial port (handling by fhem)
#
sub AutoMower_Read($)
{
	my ($hash) = @_;

	debug ("new data available");
	
	my ($ack, $cmd, @dat) = readMsg($hash);	
	my $dataBytes = join ("", @dat);
	
	debug ("new data read and converted - ack: $ack cmd: $cmd data: $dataBytes");
	
	#new status message
	if (($cmd eq "12") and ($ack eq 1))
	{		
		#feed status
		readingsSingleUpdate($hash,"state","auto",1) if (($dat[1] eq "02") and ($dat[3] eq "06"));
		readingsSingleUpdate($hash,"state","man",1) if (($dat[1] eq "02") and ($dat[3] eq "07"));
		readingsSingleUpdate($hash,"state","search",1) if ($dat[1] eq "05");
		readingsSingleUpdate($hash,"state","park",1) if ($dat[1] eq "01");
		readingsSingleUpdate($hash,"state","charge",1) if ($dat[1] eq "04");
		readingsSingleUpdate($hash,"state","error",1) if ($dat[1] eq "07");
		
		#blades
		readingsSingleUpdate($hash,"blades","on",1) if ($dat[2] eq "01");
		readingsSingleUpdate($hash,"blades","on",1) if ($dat[2] eq "02");

		#errors
		if (($dat[3] eq "06") and ($dat[4] eq "01") and ($dat[5] eq "00"))
		{
			readingsSingleUpdate($hash,"error","mower stopped",1);
		} elsif (($dat[3] eq "06") and ($dat[4] eq "01") and ($dat[5] eq "27"))
		{
			readingsSingleUpdate($hash,"error","mower tilted",1);
		} elsif (($dat[3] eq "05") and ($dat[4] eq "22") and ($dat[5] eq "27"))
		{
			readingsSingleUpdate($hash,"error","mower lifted",1);
		} elsif (($dat[3] eq "07") and ($dat[4] eq "12") and ($dat[5] eq "00"))
		{
			readingsSingleUpdate($hash,"error","blades blocked",1);
		} elsif (($dat[3] eq "05") and (($dat[4] eq "12") or ($dat[5] eq "13")))
		{
			readingsSingleUpdate($hash,"error","acknowledged and stopped",1);
		} else
		{
			my $tmp = substr ($dataBytes, 2, 10);
			readingsSingleUpdate($hash,"error","errcode $tmp",1);
		}
	}
}

#Called on the interval timer, if enabled
#
sub AutoMower_GetUpdate($)
{
	my ($hash) = @_;
	my $name = $hash->{NAME};
	
	debug ("Called _GetUpdate");

	#get status	
	AutoMower_Get ($hash, "state");

	#reset timer
	InternalTimer(gettimeofday() + $interval, "AutoMower_GetUpdate", $hash, 0) if ($interval > 0);
}

#private Funktionen

#prints a debug message if global debug-flag is set. 
#
#input: message string.
#
#return: nothing
sub debug($)
{
	my ($msg) = @_;
  
	print "Debug AutoMower: $msg\n" if ($debug eq 1);
}

#Opens a serial port from a given name. Pushes the port into the hash, if its openen ducessfully.
#
#input: hash (containing portName, later on port), reconnect
#
#return String containing the error, undef if ok
sub openPort($$)
{
	my ($hash, $reopen) = @_;
	my $portName = $hash->{PORTNAME};
	my $name = $hash->{NAME};
	my $port = undef;
	my $errStr = undef;
	
	if ($reopen)
	{
		my $msg = "try to reconnect";
		debug ($msg);
		Log (2, $msg);
	}
  
	#different behaviour in Linux and windows
	#wrong port causes IO-error in Logfile
	if ($^O=~/Win/) 
	{
		require Win32::SerialPort;
		$port = new Win32::SerialPort ($portName);
    } else  
	{
		require Device::SerialPort;
		$port = new Device::SerialPort ($portName);
    }
	
	#port defined?
	if(!$port) 
	{
		#error, if not
		$errStr = "can't open $portName: $!";
		debug ($errStr);
		Log(0, $errStr);
		closePort ($hash);
		
		#inform fhem
		$readyfnlist{"$name.$portName"} = $hash;
		$hash->{STATE} = "disconnected";
		
		return $errStr;
    } else
	{
		my $msg = "port $portName aquired sucessfully";
		debug ($msg);
		$hash->{PORT} = $port;
		$hash->{STATE} = "connected";
		Log(3, $msg);
		
		#inform fhem
		if( $^O =~ /Win/ ) 
		{
			$readyfnlist{"$name.$portName"} = $hash;
		} else 
		{
			$hash->{FD} = $port->FILENO;
			delete($readyfnlist{"$name.$portName"});
			$selectlist{"$name.$portName"} = $hash;
		}
    }
	
	#do basic settings. They will never change.
	$port->baudrate(115200);
	$port->databits(8);
	$port->parity("none");
	$port->stopbits(1);	
	$port->purge_all();
	$port->rts_active(0);
	$port->dtr_active(0);	
	$port->handshake('none');

	# This part is for some Linux kernel versions which has strange default
	# settings.  Device::SerialPort is nice: if the flag is not defined for your
	# OS then it will be ignored.
	$port->stty_icanon(0);
	#$po->stty_parmrk(0); # The debian standard install does not have it
	$port->stty_icrnl(0);
	$port->stty_echoe(0);
	$port->stty_echok(0);
	$port->stty_echoctl(0);

	# Needed for some strange distros
	$port->stty_echo(0);
	$port->stty_icanon(0);
	$port->stty_isig(0);
	$port->stty_opost(0);
	$port->stty_icrnl(0);

	#write settings
    $port->write_settings;
	
	# empty, if OK
	return $errStr;
}

#Closes the serial port in the hash.
#
#input: hash (containing port)
#
#return always undef
sub closePort($)
{
	my ($hash) = @_;
	my $port = $hash->{PORT};
	my $portName = $hash->{PORTNAME};
	my $name = $hash->{NAME};
	
	my $msg = "closing port";
	debug ($msg);
	Log(3, $msg);
	
	#cleanup
	if ($hash->{PORT})
	{
		debug ("closing - delete port");
		delete($hash->{PORT});
	}
	if ($hash->{STATE})
	{
		debug ("closing - set state to disconnected");
		$hash->{STATE} = "disconnected";
	}
	if ($hash->{FD})
	{
		debug ("closing - delete FD");
		delete($hash->{FD});
	}
	if ($selectlist{"$name.$portName"})
	{
		debug ("closing - delete selectlist");
		delete($selectlist{"$name.$portName"});
	}
	if ($readyfnlist{"$name.$portName"})
	{
		debug ("closing - delete readylist");
		delete($readyfnlist{"$name.$portName"});	
	}
	
	#valid port?
	if (!$port)
	{
		debug ("closing - no port open");
		return undef;
	}
		
	#close it
	$port->close;
		
	debug ("closing - port closed");
	
	return undef;
}

#Executes a reset, if connection should be establish, but is not...
#
#input: hash (containing port)
#
#return nothing
sub disconnected($)
{
	my ($hash) = @_;
	my $portName = $hash->{PORTNAME};
	my $name = $hash->{NAME};

	#cannot be used, because fhem deletes invalid FD's automatically...
	#Properly closed - no action!
	#if(!defined($hash->{FD}))
	#{
	#	my $msg = "device properly closed - no reopen!";
	#	debug ($msg);
	#	Log (3, $msg);
	#	return;
	#}
	                 
	#Message - Try to reconnect
	my $msg = "Port disconnected. Waiting to reappear.";
	debug ($msg);
	Log (1, $msg);

	#Close properly
	closePort($hash);
	
	#start polling
	$readyfnlist{"$name.$portName"} = $hash;
	$hash->{STATE} = "disconnected";
	
	#reopen is done in ready!

	#Without the following sleep the open of the device causes a SIGSEGV,
	#and following opens block infinitely. Only a reboot helps.
	sleep(5);

	#inform fhem
	DoTrigger($name, "disconnected");
}

#Writes raw-data to the given port
#
#input: hash (containing port), data as Hex-string (Hello World = 48656c6c6f20576f726c64)
#
#return String containing the error, undef if ok
sub writeRaw($$)
{
	my ($hash, $data) = @_;
	my $port = $hash->{PORT};
	my $dataLen = 0;

	#no port given
	if (!$port)
	{
		my $errStr = "port not available -> closing";
		#closePort($hash);
		disconnected($hash);
		return $errStr;
	}
	
	#no data given
	if (!$data or !length($data))
	{
		my $errStr = "no data -> giving up";
		return $errStr;	
	}
	
	my $msg = "convert data (hex): $data";
	debug ($msg);	
	Log (3, $msg);	
		
	#convert hex-string
	#get string-length (half of the input-length)
	$dataLen = length($data) / 2;
	
	my $string = "";
	#loop over all positions	
	for (my $i = 0; $i < $dataLen; $i++) 
	{
		#get decimal value of each pair
		my $c = hex(substr($data, $i*2, 2));
		
		#if nul - string terminated
		#if ($c eq 0) 
		#{
		#	$i = $dataLen;
		#} 
		#append as character
		#else 
		#{
			$string .=  sprintf("%c", $c);
		#}		
	}
	
	#write
	my $writtenBytes = $port->write($string);
	
	#not all given bytes are written
	if (!$writtenBytes or !($dataLen eq $writtenBytes))
	{
		my $errStr = "write not complete -> closing";
		#closePort ($hash);
		disconnected($hash);
		return $errStr;
	}
		
	$msg = "wrote $writtenBytes bytes";
	debug ($msg);	
	Log (3, $msg);
	
	#empty, if ok
	return undef;
}

#Reads Raw-data from a serial port. Maximum is 255 bytes.
#
#input: hash (containing port)
#
#return Hex-String containing the data, undef if error
sub readRaw ($)
{
	my ($hash) = @_;
	my $port = $hash->{PORT};
	my $readBytes = undef;
	my $data = undef;
	
	#no port given
	if (!$port)
	{
		my $errStr = "port not available -> closing";
		debug ($errStr);
		Log(1, $errStr);
		#closePort($hash);
		disconnected($hash);
		return undef;
	}
	
	#read data - max 255 bytes
	($readBytes, $data) = $port->read(255);
		
	if ($readBytes > 0)
	{
		my $tmp;
		
		#convert to hex-string
		for (my $i = 0; $i < $readBytes; $i++)
		{
			$tmp .= sprintf("%02X", ord(substr($data, $i, 1)));
		}
		
		$data = $tmp;
		
		#does not work - for what reason ever
		#$data =~ s/(.)/sprintf("%02X",ord($1))/eg;

		my $msg = "received and converted data (hex): $data - $readBytes bytes";
		debug ($msg);
		Log (3, $msg);
	} else
	{
		Log (3, "no data received");
		$data = "";
	}
	
	return $data;
}

#Writes a message and marks the hash for the ack.
#Port check is done deeper in writeRaw.
#ack is processed in Read
#
#input: hash (containing port), cmd (one hex-byte NN), data (at least one hex-byte MM LL SS)
#
#return Error-String, undef if OK
sub writeMsg ($$$)
{
	my ($hash, $cmd, $data) = @_;
	my $txErr = undef;

	#check for port status is done in writeRaw
	
	#check for any other proceeding transaction
	if ($hash->{waitForAck} and !$hash->{blocked})
	{
		#busy - give up
		$hash->{blocked} = 1;
		my $errStr = "another message is transferred - this message is discarded";
		debug ($errStr);
		Log(2, $errStr);
		#delay, and give the system a chance to respond
		sleep (1);
		return $errStr;		
	} elsif ($hash->{blocked} and ($hash->{blocked} eq 1))
	{
		#busy 2nd-time - there will be no response any more...
		delete ($hash->{blocked});
		my $errStr = "a message could not be acknowledged - continuing";
		debug ($errStr);
		Log(3, $errStr);
	}

	debug ("try to write cmd $cmd data $data");

	#encode message
	my $txData = encodeMsg ($cmd, $data);
	#check for errors
	if (!$txData)
	{
		my $errStr = "processing not possible - see log for details and check input data";
		debug ($errStr);
		Log(1, $errStr);
		return $errStr;
	}
	
	#finally write	
	$txErr = writeRaw ($hash, $txData);
	#check for errors
	if ($txErr)
	{
		debug ($txErr);
		Log(1, $txErr);
		return $txErr;
	}
	
	#mark in hash - lookup ack in read
	$hash->{waitForAck} = $cmd;

	return undef;
}

#Reads and decodes a message.
#if it is a ack, it is marked as response.
#Port check is done deeper in readRaw.
#ack is processed in Read
#
#input: hash (containing port)
#
#return {ack}, {cmd} and {data}. Ack is numeric 1 == Answer OK, 0 == Answer without ack, -1 = ack different from request (NOK). 
#Undef if there was an error.
#Access: my ($ack, $cmd, @dat) = decodeMsg(readRaw ($hash));	
sub readMsg ($)
{
	my ($hash) = @_;
	my $rxErr = undef;	

	#get raw data
	my $answer = readRaw ($hash);
	
	#error while receiving?
	if (!$answer)
	{
		my $errStr = "receiving not possible - see log for details";
		debug ($errStr);
		Log(1, $errStr);
		return undef;
	}
	#answer received?
	if ($answer eq "")
	{
		my $errStr = "receiving not possible - data could be read, but there was no content (length == 0)";
		debug ($errStr);
		Log(3, $errStr);
		return undef;	
	}
	
	#decode message
	my ($cmd, @dat) = decodeMsg($answer);
	#error while decoding?
	if (!$cmd)
	{
		my $errStr = "decoding not possible - see log for details";
		debug ($errStr);
		Log(1, $errStr);
		return undef;
	}
	
	#check for ack
	my $ack = $hash->{waitForAck};
	if (!$ack)
	{
		my $errStr = "message received without prior sending - theoretically not possible";
		debug ($errStr);
		Log(3, $errStr);
		delete ($hash->{waitForAck});
		$ack = 0;	
	} elsif ($ack eq $cmd)
	{
		delete ($hash->{waitForAck});
		$ack = 1;
	} else
	{
		my $errStr = "message received and stored ack are different - this is not the answer!";
		debug ($errStr);
		Log(3, $errStr);
		delete ($hash->{waitForAck});
		$ack = -1;
	}

	return ($ack, $cmd, @dat);
}


#Calculates a checksum accoring dallas-1-wire-standard
#
#input: cmd (one byte in hex-format "NN"), data (string containing hex-data "AABBCC")
#
#return Hex-String containing the crc-data, undef if error
sub calcCrc ($$)
{
	my ($cmd, $value) = @_;
	my @data = undef;

	my $valueByteLen = length($value) / 2;
	my $dataByteLen = $valueByteLen + 2;
    my $crc = 0b00000000;	
	
	#assing cmd-byte
	$data[0] = hex($cmd);
	#assign len-byte
	$data[1] = $valueByteLen;
	
	#loop over all positions (aka data-bytes)
	for (my $i = 0; $i < $dataByteLen; $i++) 
	{
		#copy input data
		if ($i >= 2)		
		{
			#copy int-value of input-string to data-array
			$data[$i] = hex(substr($value, (($i - 2) * 2), 2));
		}
		
		my $byte = $data[$i];
		
		#loop over all bits
		for (my $j = 0; $j < 8; $j++) 
		{
			#get LSbit of byte by applying mask
			my $bit = $byte & 0b00000001;			
			#XOR the LS bit of the crc with the current data bit
			my $chk = $crc ^ $bit;
			#apply mask to get LS bit only
			$chk = $chk & 0b00000001;			
			
			#test if result was 1 or 0
			if ($chk) 
			{
				#result was 1 so need to XOR, right shift & make MS bit 1
				#XOR crc with 0b00011000
				$crc = $crc ^ 0b00011000;
				#right shift
				$crc = $crc >> 1;
				#make MS bit 1 by OR'ing with 0b10000000
				$crc = $crc | 0b10000000;
            } else 
			{
				#result was zero, so just right shift crc (MS bit will be 0) 
				$crc = $crc >> 1;
			}

			# shift data byte to right so next bit
			#   is in LS bit position
			$byte = ($byte >> 1);
		}
	}
				
	#convert to hexy-string			
	$crc = sprintf("%02X", $crc);
	
	debug ("crc calculated: $crc");
	
	return $crc;
}

#Encodes a message for the automower
#
#input: cmd (one byte in hex-format "NN"), data (string containing hex-data "AABBCC")
#
#return Hex-String containing the complete frame
sub encodeMsg ($$)
{
	my ($cmd, $value) = @_;
	my @data = undef;
	my $retVal = undef;

	my $valueByteLen = length($value) / 2;
	my $dataByteLen = $valueByteLen;
	
	#exactly one byte must be given
	if (!$cmd or !(length($cmd) eq 2))
	{
		my $errStr = "cmd null or not valid";
		debug ($errStr);
		Log(1, $errStr);
		return undef;	
	}
	
	#at least one data-byte must be given. There has to be a even number of  positions.
	if (!$value or (length($value) < 2) or !(length($value) eq ($valueByteLen * 2)))
	{
		my $errStr = "data null or not valid";
		debug ($errStr);
		Log(1, $errStr);
		return undef;		
	}
	
	#first byte alway 0x02
	$data[0] = "02";
	#cmd
	$data[1] = $cmd;
	#length	
	$data[2] = sprintf("%02X", $dataByteLen);	
	#data bytes
	for (my $i = 0; $i < $valueByteLen; $i++)
	{
		$data[$i + 3] = substr ($value, $i * 2, 2);
	}
	#crc
	$data[$dataByteLen + 4] = calcCrc ($cmd, $value);
	#last byte alway 0x03
	$data[$dataByteLen + 5] = "03";
	
	#join array, locally surpress warnings caused by 0x0
	{	
		no warnings 'uninitialized'; 
		$retVal = join("", @data);
	}	
	
	debug ("encoded: $retVal");
	
	return $retVal;
}

#Decodes a message from the automower
#
#input: data-frame (string containing hex-data "AABBCC")
#
#return {cmd} and {data}. Access: my ($cmd, @dat) = decodeMsg(readRaw ($hash));	
sub decodeMsg ($)
{
	my ($data) = @_;
	my @byteData = undef;
	my $retVal = undef;
	my $rawLen = length($data);
	my $dataByteLen = ($rawLen - 10) / 2;
	
	#at least one data-byte must be given. There has to be a even number of  positions.
	if (!$data or ($dataByteLen < 0))
	{
		debug ("message invalid - no full data supplied (< 5 byte)");
		return undef;		
	}	
	
	#First or last byte does not match
	if (!(substr($data, 0, 2) eq "02") or !(substr($data, $rawLen - 2, 2) eq "03"))
	{
		debug ("message invalid - first or last byte not correct");
		return undef;		
	}
	
	#check length
	if (not (hex(substr($data, 4, 2)) eq $dataByteLen))
	{
		debug ("message invalid - length does not match");
		return undef;		
	}
	
	my $cmd = substr($data, 2, 2);
	my $dataString = substr($data, 6, $dataByteLen * 2);
	my $crc = calcCrc($cmd, $dataString);
	
	#check crc
	if (not (substr($data, 6 + ($dataByteLen * 2), 2) eq $crc))
	{
		debug ("message invalid - crc does not match");
		return undef;		
	}	
	
	#loop over all data-bytes
	for (my $i = 0; $i < $dataByteLen; $i++) 
	{
		#get value of each pair
		$byteData[$i] = substr($dataString, $i * 2, 2);
	}
	
	debug ("decoded: cmd $cmd data $dataString");
	
	#$retVal->{cmd} = $cmd;
	#$retVal->{data} = \@byteData;
	
	#return $retVal;
	return ($cmd, @byteData)
}

1;

=pod
=begin html


<a name="Hello"></a>
<h3>Hello</h3>
<ul>
    <i>Hello</i> implements the classical "Hello World" as a starting point for module development. 
    You may want to copy 98_Hello.pm to start implementing a module of your very own. See 
    <a href="http://www.fhemwiki.de/wiki/DevelopmentModuleIntro">DevelopmentModuleIntro</a> for an 
    in-depth instruction to your first module.
    <br><br>
    <a name="Hellodefine"></a>
    <b>Define</b>
    <ul>
        <code>define <name> Hello <greet></code>
        <br><br>
        Example: <code>define HELLO Hello TurnUrRadioOn</code>
        <br><br>
        The "greet" parameter has no further meaning, it just demonstrates
        how to set a so called "Internal" value. See <a href="http://fhem.de/commandref.html#define">commandref#define</a> 
        for more info about the define command.
    </ul>
    <br>
    
    <a name="Helloset"></a>
    <b>Set</b><br>
    <ul>
        <code>set <name> <option> <value></code>
        <br><br>
        You can <i>set</i> any value to any of the following options. They're just there to 
        <i>get</i> them. See <a href="http://fhem.de/commandref.html#set">commandref#set</a> 
        for more info about the set command.
        <br><br>
        Options:
        <ul>
              <li><i>satisfaction</i><br>
                  Defaults to "no"</li>
              <li><i>whatyouwant</i><br>
                  Defaults to "can't"</li>
              <li><i>whatyouneed</i><br>
                  Defaults to "try sometimes"</li>
        </ul>
    </ul>
    <br>

    <a name="Helloget"></a>
    <b>Get</b><br>
    <ul>
        <code>get <name> <option></code>
        <br><br>
        You can <i>get</i> the value of any of the options described in 
        <a href="#Helloset">paragraph "Set" above</a>. See 
        <a href="http://fhem.de/commandref.html#get">commandref#get</a> for more info about 
        the get command.
    </ul>
    <br>
    
    <a name="Helloattr"></a>
    <b>Attributes</b>
    <ul>
        <code>attr <name> <attribute> <value></code>
        <br><br>
        See <a href="http://fhem.de/commandref.html#attr">commandref#attr</a> for more info about 
        the attr command.
        <br><br>
        Attributes:
        <ul>
            <li><i>formal</i> no|yes<br>
                When you set formal to "yes", all output of <i>get</i> will be in a
                more formal language. Default is "no".
            </li>
        </ul>
    </ul>
</ul>

=end html

=cut

