# Insteon_PLS.pm

# $Id$

=begin comment
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

File:
	Insteon_PLS.pm

Description:

	This is the base interface class for Insteon Power Line Serial Insterface (PLS)

	For more information regarding the technical details of the Insteon PLCs
        purchase the appropriate Insteon SDK:

               http://www.insteon.net/developers-kits.html


Author(s):
    Neil Cherry
    ncherry@linuxha.com

License:
    This free software is licensed under the terms of the GNU public license. GPLv2

Usage:
	Use these mh.ini parameters to enable this code:

        Insteon_PLS_Serial_Port = /dev/ttyS0 # push(@Generic_Devices, $1) if $parm =~ /^(.+?)_device$/;


    Example initialization:

		$myPLS = new Insteon_PLS("Insteon_PLS");

		#Turn Light Module ID L5 On
		$myPLS->send_plc_cmd();
		$myPLS->send_plc_cmd();
	
Notes:

Special Thanks to:
    Bruce Winter - MH

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
=cut
require Event_queue;

package Insteon_PLS;

#@Insteon_PLS::ISA = ('Serial_Item');
@Insteon_PLS::ISA = ('Insteon_PLC');

my %Insteon_PLS_Data;

my @event_queue = (); # An array of queue objects

#my $pstr = "PLS ";

### --------------------------------------------------------------------------
###

sub serial_startup {
    my ($instance) = @_;

    # mh.private.ini
    # Insteon_PLS_serial_port     = /dev/ttyM8
    # Insteon_PLS_serial_baudrate = 4800

    # log
    # &Insteon_PLS::serial_startup('Insteon_PLS')
    # PLS::Serial_startup called
    # PLS:Startup: nothing added

    my $port  = $::config_parms{$instance . "_serial_port"};

    $Insteon_PLS_Data{$instance}{'serial_port'} = $port;    
    &::serial_port_create($instance, $port, 4800, 'none', 'raw');

    if (1==scalar(keys %Insteon_PLS_Data)) {  # Add hooks on first call only
	&::MainLoop_pre_add_hook(\&Insteon_PLS::check_for_data, 1);
    }

    #
    # Insteon usually has an event, then a command. I really don't care
    # about the events unless I'm debugging code so i can queue them
    # up and toss them away. To this end I built a queue that can keep
    # the events around for reference.
    #
    my $e = new Event_queue();

    for(my $i = 0; $i < 10; $i++) {
	$event_queue[$i] = $e;
    }
}


### --------------------------------------------------------------------------
### add_hook - add the check_for_data only once to the MainLoop_pre_add_hook

sub add_hook {
    my ($instance) = @_;
    if (1==scalar(keys %Insteon_PLS_Data)) {  # Add hooks on first call only
	&::MainLoop_pre_add_hook(\&Insteon_PLS::check_for_data, 1);

	#
	# Insteon usually has an event, then a command. I really don't care
	# about the events unless I'm debugging code so i can queue them
	# up and toss them away. To this end I built a queue that can keep
	# the events around for reference.
	#
	my $e = new Event_queue();

	for(my $i = 0; $i < 10; $i++) {
	    $event_queue[$i] = $e;
	}
    }
}

### --------------------------------------------------------------------------
###

sub check_for_data {
    # We could have more than 1 Insteon device
    for my $port_name (keys %Insteon_PLS_Data) {
	&::check_for_generic_serial_data($port_name) if $::Serial_Ports{$port_name}{object};

	my $data = $::Serial_Ports{$port_name}{data};
	next if !$data;
	$::Serial_Ports{$port_name}{data} = undef;

	# Returns: Data: (1 2 0 0 0 0 0 0 2 69 8 0 0 0 0 0 1 2 0 0 0 0 0 0 1 74 0 0 0 0 0 0)
	my @buf = unpack('(C2)*', $data); # Convert raw to valid Perl 'string' ^A => 1

	# Parse out the commands
	$Insteon_PLS_Data{$port_name}{'object'}->parse_scmds("PLS", @buf); # I can get away with this for now but I may need to pull this back later
   }
}

### --------------------------------------------------------------------------
###

my @X10_Interface_Names=qw! Insteon_PLS !;

sub new {
   my ($class, $id, $state, $device_name) = @_;

   &::print_log("SDevice name = ($id($state) / $device_name)");

   $device_name = 'Insteon_PLS' if !$device_name;

   my $self = Device_Item->new($id, $state, $device_name);

   $$self{state}            = '';
   $$self{said}             = '';
   $$self{state_now}        = '';
   $$self{port_name}        = $device_name;
   $$self{last_command}     = '';
   $$self{xmit_in_progress} = 0; # Needed for X10 (takes 1/2 a second to send A1)

   @{$$self{command_stack}} = ();

   bless $self, $class;
   $Insteon_PLS_Data{$device_name}{'object'} = $self;
	
   return $self;
}


sub get_location {
    my ($self) = @_;

    my $key;
    my @arr;

    foreach $key (keys %$self) {
	&::print_log("$key: $$self{$key}");
    }
    return $self->{device_name};
}

sub add {
    my ($self,@p_objects) = @_;

    &::print_log("Insteon_PLS::add");

    my @l_objects;

    for my $l_object (@p_objects) {
	if ($l_object->isa('Group_Item') ) {
	    @l_objects = $$l_object{members};
	    for my $obj (@l_objects) {
		$self->add($obj);
	    }
	} else {
	    $self->add_item($l_object);
	}
    }
}

sub add_item {
    my ($self,$p_object) = @_;

    &::print_log("Insteon_PLS::add_item");

    push @{$$self{objects}}, $p_object;

    return $p_object;
}

sub send_plm_cmd {
    my ($self, $cmd) = @_;
    #queue any new commands
    unshift(@{$$self{command_stack}},$cmd) if defined $cmd;

    &::print_log("PLM Add Command:" . unpack("H*",$cmd) . ":XmitInProgress:" . $$self{xmit_in_progress} . ":" );
    #we dont transmit on top of another xmit
    if ($$self{xmit_in_progress} != 1) {
	$$self{xmit_in_progress} = 1;
	#TODO: Should start a timer just in case PLM is not responding and we need to clear xmit_in_progress after a while.
	#always send the oldest command first
	$cmd = pop(@{$$self{command_stack}});
	if (defined $cmd) {
	    #put the command back into the stack.. Its not our job to tamper with this array
	    push(@{$$self{command_stack}},$cmd);
	    return $self->_send_cmd($cmd);
	}
    } else {
	return;
    }
}

sub _send_cmd {
    my ($self, $cmd) = @_;
    my $instance = $$self{port_name};
    my $hex = unpack "H*", $cmd;

    &::print_log("PLM: Executing command:$hex:") unless $main::config_parms{no_log} =~/Insteon_PLM/;
    my $data = $cmd;
    $main::Serial_Ports{$instance}{object}->write($data);

    #X10 is sloooooow
    select(undef,undef,undef,0.5);
    $$self{'last_change'} = $main::Time;
}


return 1;
__END__
# $Log$
#

