#################################################################################
# 46_TRX_SECURITY.pm
#
# FHEM module for X10, KD101, Visonic
#
# Copyright (C) 2012 Willi Herzig
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# The GNU General Public License may also be found at http://www.gnu.org/licenses/gpl-2.0.html .
#
##################################
#
# values for "set global verbose"
# 4: log unknown protocols
# 5: log decoding hexlines for debugging
#
# $Id$
package main;

use strict;
use warnings;

# Debug this module? YES = 1, NO = 0
my $TRX_SECURITY_debug = 0;

my $time_old = 0;

my $TRX_SECURITY_type_default = "ds10a";

my $DOT = q{_};

sub
TRX_SECURITY_Initialize($)
{
  my ($hash) = @_;

  $hash->{Match}     = "^..(20).*";
  $hash->{DefFn}     = "TRX_SECURITY_Define";
  $hash->{UndefFn}   = "TRX_SECURITY_Undef";
  $hash->{ParseFn}   = "TRX_SECURITY_Parse";
  $hash->{AttrList}  = "IODev ignore:1,0 event-on-update-reading event-on-change-reading do_not_notify:1,0 loglevel:0,1,2,3,4,5,6";

}

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

  my $a = int(@a);

  if(int(@a) != 5 && int(@a) != 7) {
	Log 1,"TRX_SECURITY wrong syntax '@a'. \nCorrect syntax is  'define <name> TRX_SECURITY <type> <deviceid> <devicelog> [<deviceid2> <devicelog2>]'";
	return "wrong syntax: define <name> TRX_SECURITY <type> <deviceid> <devicelog> [<deviceid2> <devicelog2>]";
  }
	

  my $name = $a[0];

  my $type = lc($a[2]);
  my $deviceid = $a[3];
  my $devicelog = $a[4];


  $type = uc($type);

  my $device_name = "TRX".$DOT.$type.$DOT.$deviceid;

  if ($type ne "DS10A" && $type ne "SD90" && $type ne "MS10A" && $type ne "MS14A" && $type ne "KR18" && $type ne "KD101" && $type ne "VISONIC_WINDOW" & $type ne "VISONIC_MOTION" & $type ne "VISONIC_REMOTE") {
  	Log 1,"TRX_SECURITY define: wrong type: $type";
  	return "TRX_SECURITY: wrong type: $type";
  }

  $hash->{TRX_SECURITY_deviceid} = $deviceid;
  $hash->{TRX_SECURITY_devicelog} = $devicelog;
  $hash->{TRX_SECURITY_type} = $type;
  #$hash->{TRX_SECURITY_CODE} = $deviceid;
  $modules{TRX_SECURITY}{defptr}{$device_name} = $hash;


  if (int(@a) == 7) {
	# there is a second deviceid:
	#
  	my $deviceid2 = $a[5];
  	my $devicelog2 = $a[6];

  	my $device_name2 = "TRX_SECURITY".$DOT.$deviceid2;

  	$hash->{TRX_SECURITY_deviceid2} = $deviceid2;
  	$hash->{TRX_SECURITY_devicelog2} = $devicelog2;
  	#$hash->{TRX_SECURITY_CODE2} = $deviceid2;
  	$modules{TRX_SECURITY}{defptr2}{$device_name2} = $hash;
  }

  AssignIoPort($hash);

  return undef;
}

#####################################
sub
TRX_SECURITY_Undef($$)
{
  my ($hash, $name) = @_;
  delete($modules{TRX_SECURITY}{defptr}{$name});
  return undef;
}



#####################################
sub TRX_SECURITY_parse_X10Sec {
  my $bytes = shift;

  my $error;

  my $subtype = $bytes->[1];

  my $device;
  if ($subtype >= 3) {
	$device = sprintf '%02x%02x%02x', $bytes->[3], $bytes->[4], $bytes->[5];
  } else {
	# that's how we do it on 43_RFXX10REC.pm
	$device = sprintf '%02x%02x', $bytes->[5], $bytes->[3];
  }

  my %security_devtype =
    (	# HEXSTRING => 
	0x00 => [ "DS10A", "Window" ], 			# X10 security door/window sensor
	0x01 => [ "MS10A", "motion" ], 			# X10 security motion sensor
	0x02 => [ "KR18", "key" ],			# X10 security remote (no alive packets)
	0x03 => [ "KD101", "smoke" ],			# KD101 (no alive packets)
	0x04 => [ "VISONIC_WINDOW", "window" ],	# Visonic PowerCode door/window sensor – primary contact (with alive packets)
	0x05 => [ "VISONIC_MOTION", "motion" ],	# Visonic PowerCode motion sensor (with alive packets)
	0x06 => [ "VISONIC_REMOTE", "key" ],	# Visonic CodeSecure (no alive packets)
	0x07 => [ "VISONIC_WINDOW", "window" ],	# Visonic PowerCode door/window sensor – auxiliary contact (no alive packets)
  );

  my $dev_type;
  my $dev_reading;
  if (exists $security_devtype{$subtype}) {
    my $rec = $security_devtype{$subtype};
    if (ref $rec) {
      ($dev_type, $dev_reading ) = @$rec;
    } else {
	$error = "TRX_SECURITY: x10_devtype wrong for subtype=$subtype";
	Log 1, $error;
  	return "";
    }
  } else {
 	$error = "TRX_SECURITY: error undefined subtype=$subtype";
	Log 1, $error;
  	return "";
  }

  #--------------
  my $device_name = "TRX".$DOT.$dev_type.$DOT.$device;
  Log 4, "device_name=$device_name";

  my $firstdevice = 1;
  my $def = $modules{TRX_SECURITY}{defptr}{$device_name};
  if(!$def) {
  	$firstdevice = 0;
	$def = $modules{TRX_SECURITY}{defptr2}{$device_name};
	if (!$def) {
	Log 1, "UNDEFINED $device_name TRX_SECURITY $dev_type $device $dev_reading";
        	Log 3, "TRX_SECURITY: TRX_SECURITY Unknown device $device_name, please define it";
       		return "";
	}
  }

  # Use $def->{NAME}, because the device may be renamed:
  my $name = $def->{NAME};
  return "" if(IsIgnored($name));

  my $data = $bytes->[6];

  my $hexdata = sprintf '%02x', $data;

  my %x10_security =
    (
	0x00 => ['X10Sec', 'normal', 'min_delay', ''],
	0x01 => ['X10Sec', 'normal', 'max_delay', ''],

	0x02 => ['X10Sec', 'alert', 'min_delay', ''],
	0x03 => ['X10Sec', 'alert', 'max_delay', ''],

	0x04 => ['X10Sec', 'alert', '', ''],
	0x05 => ['X10Sec', 'normal', '', ''],

	0x06 => ['X10Sec', 'alert', '', ''],
	0x07 => ['X10Sec', 'normal', '', ''],

	0x08 => ['X10Sec', 'tamper', '', ''],

	0x09 => ['X10Sec', 'Security-Arm_Away', 'min_delay', ''], # kr18
	0x0a => ['X10Sec', 'Security-Arm_Away', 'max_delay', ''], # kr18
	0x0b => ['X10Sec', 'Security-Arm_Home', 'min_delay', ''], # kr18
	0x0c => ['X10Sec', 'Security-Arm_Home', 'max_delay', ''], # kr18
	0x0d => ['X10Sec', 'Security-Disarm', 'min_delay', ''], # kr18

	0x10 => ['X10Sec', 'ButtonA-on', '', ''], # kr18
	0x11 => ['X10Sec', 'ButtonA-off', '', ''], # kr18
	0x12 => ['X10Sec', 'ButtonB-on', '', ''], # kr18
	0x13 => ['X10Sec', 'ButtonB-off', '', ''], # kr18

	0x14 => ['X10Sec', 'dark', '', ''],
	0x15 => ['X10Sec', 'light', '', ''],
	0x16 => ['X10Sec', 'normal', '', 'batt_low'],

	0x17 => ['X10Sec', 'pair KD101', '', ''],

    );

  my $command = "";
  my $type = "";
  my $delay = "";
  my $battery = "";
  my @res;
  if (exists $x10_security{$data}) {
    my $rec = $x10_security{$data};
    if (ref $rec) {
      ($type, $command, $delay, $battery) = @$rec;
    } else {
      $command = $rec;
    }
  } else {
    Log 1, "TRX_SECURITY: undefined command cmd=$data device-nr=$device, hex=$hexdata";
    return "";
  }

  my $battery_level = $bytes->[7] & 0x0f;
  if (($battery eq "") && ($dev_type ne "kd101")) {
	if ($battery_level == 0x9) { $battery = 'batt_ok'}
	elsif ($battery_level == 0x0) { $battery = 'batt_low'}
	else {
		Log 1,"TRX_SECURITY: X10Sec unkown battery_level=$battery_level";
	}
  }

  my $current = "";

  Log 1, "TRX_SECURITY: $name devn=$device_name first=$firstdevice subtype=$subtype command=$command, delay=$delay, batt=$battery cmd=$hexdata" if ($TRX_SECURITY_debug == 1);

  my $n = 0;
  my $tm = TimeNow();
  my $val = "";

  my $device_type = $def->{TRX_SECURITY_type};

  my $sensor = "";

  if ($device_type eq "sd90") {
	$sensor = $firstdevice == 1 ? $def->{TRX_SECURITY_devicelog} : $def->{TRX_SECURITY_devicelog2};
  } else {
  	$sensor = $def->{TRX_SECURITY_devicelog};
  } 

  $current =$command;
  if (($device_type eq "DS10A") || ($device_type eq "VISONIC_WINDOW")) {
	$current = "Error";
	$current = "Open" if ($command eq "alert");
	$current = "Closed" if ($command eq "normal");
  }

  readingsBeginUpdate($def);

  if (($dev_type ne "kr18") || ($dev_type ne "VISONIC_REMOTE")) {  
  	if ($firstdevice == 1) {
		$val .= $current;
  	}
	readingsUpdate($def, $sensor, $current);

  	if (($def->{STATE} ne $val)) {
		$sensor = "statechange";
		readingsUpdate($def, $sensor, $current);
  	}
  } else {
	# kr18 remote control or VISONIC_REMOTE
	$current = $command;

	#$sensor = $def->{TRX_SECURITY_devicelog};
	$val = $current;
	readingsUpdate($def, $sensor, $current);

	my @cmd_split = split(/-/, $command);
	$sensor = $cmd_split[0];
	$current = $cmd_split[1];
	readingsUpdate($def, $sensor, $current);
  }

  if ($battery ne "") {
	$sensor = "battery";
	$current = "Error";
	$current = "ok" if ($battery eq "batt_ok");
	$current = "low" if ($battery eq "batt_low");
	readingsUpdate($def, $sensor, $current);
  }

  if ($delay ne '') {
	$sensor = "delay";
	$current = "Error";
	$current = "min" if ($delay eq "min_delay");
	$current = "max" if ($delay eq "max_delay");
	readingsUpdate($def, $sensor, $current);
  }

  if (($firstdevice == 1) && $val) {
  	$def->{STATE} = $val;
	readingsUpdate($def, "state", $val);
  }

  readingsEndUpdate($def, 1);

  return $name;
}


sub
TRX_SECURITY_Parse($$)
{
  my ($iohash, $hexline) = @_;

  my $time = time();
  # convert to binary
  my $msg = pack('H*', $hexline);
  if ($time_old ==0) {
  	Log 5, "TRX_SECURITY: decoding delay=0 hex=$hexline";
  } else {
  	my $time_diff = $time - $time_old ;
  	Log 5, "TRX_SECURITY: decoding delay=$time_diff hex=$hexline";
  }
  $time_old = $time;

  # convert string to array of bytes. Skip length byte
  my @rfxcom_data_array = ();
  foreach (split(//, substr($msg,1))) {
    push (@rfxcom_data_array, ord($_) );
  }

  my $num_bytes = ord($msg);

  if ($num_bytes < 3) {
    return "";
  }

  my $type = $rfxcom_data_array[0];

  #Log 1, "TRX_SECURITY: X10Sec num_bytes=$num_bytes hex=$hexline type=$type" if ($TRX_SECURITY_debug == 1);
  my $res = "";
  if ($type == 0x20) {
	Log 1, "TRX_SECURITY: X10Sec num_bytes=$num_bytes hex=$hexline" if ($TRX_SECURITY_debug == 1);
        $res = TRX_SECURITY_parse_X10Sec(\@rfxcom_data_array);
  	Log 1, "TRX_SECURITY: unsupported hex=$hexline" if ($res eq "");
	return $res;
  } else {
	Log 0, "TRX_SECURITY: not implemented num_bytes=$num_bytes hex=$hexline";
  }

  return "";
}

1;
