# Insteon_PLU.pm

# $Id: Insteon_PLU.pm,v 1.1 2007/09/29 03:10:05 njc Exp njc $

# $Log: Insteon_PLU.pm,v $
# Revision 1.1  2007/09/29 03:10:05  njc
# Initial revision
#
=begin comment
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

File:
	Insteon_PLU.pm

Description:

	This is the base interface class for Insteon Power Line USB Insterface (PLU)

	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
Original author of the Insteon_PLM.pm (which this work is based on)
    Jason Sharpee
    jason@sharpee.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_PLM_serial_port=/dev/iplc1
        Insteon_PLU_device     = /dev/iplc1 # push(@Generic_Devices, $1) if $parm =~ /^(.+?)_device$/;


    Example initialization:

		$myPLU = new Insteon_PLU("Insteon_PLU");

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

Special Thanks to:
    Bruce Winter - MH

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
=cut

use strict;
use POSIX qw(strftime);

require Event_queue;

package Insteon_PLU;

use Insteon_PLC;
@Insteon_PLU::ISA = ('Insteon_PLC');

my %Insteon_PLU_Data;

my @event_queue = (); # An array of queue objects
#my $pstr = "PLU ";

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

sub startup {
    &::print_log("Insteon_PLU::startup");
}

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

sub add_hook {
   if (1==scalar(keys %Insteon_PLU_Data)) {  # Add hooks on first call only
       &::print_log("Insteon_PLU add hook");
       &::MainLoop_pre_add_hook(\&Insteon_PLU::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_PLU_Data) {
       &::check_for_generic_device_data($port_name) if $::Generic_Devices{$port_name}{object};

       my $data = $::Generic_Devices{$port_name}{data};
       next if !$data;
       $::Generic_Devices{$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

       # Remove the framing from the incoming data
       my @d = $Insteon_PLU_Data{$port_name}{'object'}->deframe(@buf);

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

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

my @X10_Interface_Names=qw! Insteon_PLU !;

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

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

   $device_name = 'Insteon_PLU' if !$device_name;

   #$main::Serial_Ports{iplcs}{object};
   #$::$Generic_Devices{$name}{handle}

   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_PLU_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 set {
    my ($self,$p_state,$p_setby,$p_response) = @_;

    my ($package, $filename, $line) = caller;
    &::print_log("PLM xmit:" , $p_setby->{object_name} . ":$p_setby:$p_state:$package:$filename:$line");
        
    #identify the type of device that sent the request
    if ($p_setby->isa("X10_Item") or $p_setby->isa("X10_Switchlinc")) {
	&::print_log("PLU: XSW $p_state,$p_setby");
	#$self->_xlate_mh_x10($p_state,$p_setby);
    } elsif ($p_setby->isa("Insteon_Device")) {
	&::print_log("PLU: IPLD: $p_state");
	#$self->send_plm_cmd(pack("H*",'0262' . $p_state));
    }
}

# this sets the interface through which we will send X10 data when asked to do so
sub set_interface {
    my ($self, $interface, $id) = @_;
    my $localDebug=0;


    # if an interface is specified, then we need to search through the
    # possible interface modules until we find one that will work with it
    if ($interface) {
    &::print_log("PLU: X10_Item: Interface: ($interface)");

        if (X10_Interface->supports($interface)) {
            print "for id $id, x10 interface supplied ($interface) and supported by X10_Interface\n" if $localDebug;
            $self->{interface}=new X10_Interface(undef,undef,$interface);
        } else {
            # we can't find a real interface, so use a Dummy_Interface
            print "PLU: warning, using dummy interface for id $id and supplied interface $interface\n" if $localDebug;
            $self->{interface}=new Dummy_Interface($id, undef, $interface);
        }
    } else {
        # an interface wasn't specified, we'll use the first one that we find
        if ($interface=X10_Interface->lookup_interface){
            print "PLU: for id $id, x10 interface not supplied, supported by X10_Interface $interface\n" if $localDebug;
            $self->{interface}=new X10_Interface(undef,undef,$interface);
        } else {
            # we can't find a real interface, so use a Dummy_Interface
            print "PLU: warning, using dummy interface for id $id\n" if $localDebug;
            $self->{interface}=new Dummy_Interface($id);
        }
    }
    # tell our "generic" interface object the name of the actual interface to use
    # we could also call set_interface without an interface name but it would
    # just repeat the same search that we just did
    $self->{interface}->set_interface($interface);

    # Set a placeholder object name for our contained interface class
    # This is to provide a more friendly log message when X10 data is received
    # It starts with a '#' so that we can identify these contained objects in
    # code/common/mh_control.pl and suppress them if desired.
    $self->{interface}->{object_name}='#'.ref($self->{interface}).' for '.ref($self);
}

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

sub send_plu_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; # ???
    &::print_log("main::Serial_Ports{\$instance}{object}->write(" . $data . ")");
    #

    ### Dont overrun the controller.. Its easy, so lets wait a bit
    #       select(undef,undef,undef,0.15);
    # X10 is sloooooow
    select(undef,undef,undef,0.5);
    $$self{'last_change'} = $main::Time;
#   $self->_poll();
}

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

sub add_id_state {
    my ($self,$id,$state) = @_;
    &::print_log("PLM: add_id_state:$id:$state");
}

### --------------------------------------------------------------------------
### x10.mht
### Type    Address   Name   Groups     Other Info
### X10I,   E3,       Hutch, Test|E,		
###
### x10.mhp
### $Hutch                               =  new X10_Item('E3', );
### $Test                                -> add($Hutch);
### $E                                   -> add($Hutch);
###
### --------------------------------------------------------------------------
###

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

    print "Insteon_PLU->add called with self $self\n";

    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) = @_;

#   $p_object->tie_items($self);
    push @{$$self{objects}}, $p_object;
    #request an initial state from the device
    if (! $p_object->isa('UPB_Link') ) {       
#       $p_object->set("status_request");
    }
    return $p_object;
}

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

return 1;           # for require
__END__

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

=begin comment
#> > You'll have to pardon me, while I kind of understand OOPs I've not
#> > applied it to Perl. I've only done Java and C++ (ick) so I'll be
#> > asking lots of questions. Can I see you're sample code?
#
# Sure!  The basic premise for inheritance for PERL is that if a method can't
# be found within the current package, then it searches @ISA for other
# packages until it finds a method.
#
# Here's some code snipped from X10_Interface, which adds X10 functionality to

# Device_Item:
#
# package X10_Interface;
#
# use Device_Item;
#
# @X10_Interface::ISA=('Device_Item');
#
# sub new {
#     my ($class, $id, $state, $device_name)=@_;
#
#     my $self = Device_Item->new($id, $state, $device_name);
#     bless ($self, $class);
#     return $self;
# }
#
# The key lines are "use Device_Item", setting @X10_Interface::ISA and then
# creating $self using the Device_Item constructor.
#
# Here's how I use Device_Item for my tests:
#
# mh.ini:
# test_device=/tmp/testpipe
# test_device_datatype=record
# test_device_mode=rw
#
# code:
#
# $testDevice= new Device_Item(undef,undef,'test');
#
# if ($state = $testDevice->said) {
#    &print_log("test device said $state");
# }
#
# ----------------------------------------------------------------------------

##
## X10
##

# 09/25/07 12:08:01 PM PLU:B:buf  (2 69 8 2 74)
# 09/25/07 12:08:01 PM PLU:B:oldd ()
# 09/25/07 12:08:01 PM plc_event (2 69 8 2 74)
# 09/25/07 12:08:01 PM PLU: Event: X10 Receive (2 69 8)
# 09/25/07 12:08:01 PM PLU:A:Oldd ()
# 09/25/07 12:08:01 PM PLU:B:buf  (2 74)
# 09/25/07 12:08:01 PM PLU:B:oldd ()
# 09/25/07 12:08:01 PM PLU:B:buf  (2 74 0 37)
# 09/25/07 12:08:01 PM PLU:B:oldd (2 74)
# 09/25/07 12:08:01 PM plc_rx10 (2 74 0 37)

# 09/25/07 12:08:01 PM PLU: X10: (2 74 0 37) XC

# 09/25/07 12:08:01 PM PLU:A:Oldd ()
# 09/25/07 12:08:01 PM PLU:A:OLDD ()
# 09/25/07 12:08:01 PM XC7: xC7 manual
# 09/25/07 12:08:01 PM XC7: xCR_Cisco manual
# 09/25/07 12:08:01 PM PLU:B:buf  (2 69 8 2)
# 09/25/07 12:08:01 PM PLU:B:oldd ()
# 09/25/07 12:08:01 PM plc_event (2 69 8 2)
# 09/25/07 12:08:01 PM PLU: Event: X10 Receive (2 69 8)
# 09/25/07 12:08:01 PM PLU:A:Oldd ()
# 09/25/07 12:08:01 PM PLU:B:buf  (2)
# 09/25/07 12:08:01 PM PLU:B:oldd ()
# 09/25/07 12:08:01 PM PLU:B:buf  (2 74 1 35)
# 09/25/07 12:08:01 PM PLU:B:oldd (2)
# 09/25/07 12:08:01 PM plc_rx10 (2 74 1 35)
# 09/25/07 12:08:01 PM XC7CK: xC7 off
# 09/25/07 12:08:01 PM XC7CK: xCR_Cisco off

# 09/25/07 12:08:01 PM PLU: X10: (2 74 1 35) XC7CK

# 09/25/07 12:08:01 PM PLU:A:Oldd ()
# 09/25/07 12:08:01 PM PLU:A:OLDD ()
# 09/25/07 12:08:02 PM XC7CK: xC7 off
# 09/25/07 12:08:02 PM XC7CK: xCR_Cisco off
# 09/25/07 12:09:55 PM PLU:B:buf  (2 69 11)
# 09/25/07 12:09:55 PM PLU:B:oldd ()

##
## Linking
##

# 09/25/07 07:31:47 PM PLU:B:buf  (2)
# 09/25/07 07:31:47 PM PLU:B:buf  (2 69 8 2 74 1 35)
# 09/25/07 07:31:47 PM plc_event (2 69 8 2 74 1 35)
# 09/25/07 07:31:47 PM PLU: Event: X10 Receive (2 69 8)
# 09/25/07 07:31:47 PM PLU:A:Oldd ()
# 09/25/07 07:31:47 PM PLU:B:buf  (2 74 1 35)
# 09/25/07 07:31:47 PM plc_rx10 (2 74 1 35)
# 09/25/07 07:31:47 PM X10: Unmatched incoming data=XCK
# 09/25/07 07:31:47 PM PLU: X10: (2 74 1 35) (XCK/)
# 09/25/07 07:31:47 PM PLU:A:Oldd ()
# 09/25/07 07:31:47 PM PLU:A:OLDD ()
# 09/25/07 07:32:37 PM Status request on undefined state: data=JSTATUS event_data=X
# 09/25/07 07:32:37 PM X10: Unmatched incoming data=XJSTATUS
# 09/25/07 07:32:46 PM PLU:B:buf  (2 69 11)
# 09/25/07 07:32:46 PM plc_event (2 69 11)
# 09/25/07 07:32:46 PM PLU: Event: Button Hold (2 69 11)
# 09/25/07 07:32:46 PM PLU:A:Oldd ()
# 09/25/07 07:32:46 PM PLU:A:OLDD ()
# 09/25/07 07:32:55 PM PLU:B:buf  (2 65 20 76 105 110 107 105 110 103 32 83 116 97)
# 09/25/07 07:32:55 PM plc_msg_fixed (2 65 20 76 105 110 107 105 110 103 32 83 116 97)
# Buf  short
# 09/25/07 07:32:55 PM PLU:A:Oldd ()
# 09/25/07 07:32:55 PM PLU:A:OLDD ()
# 09/25/07 07:32:55 PM PLU:B:buf  (114 116 101 100 13 10 1 3 0 2 69 76)
# Not STX (114 116 101 100 13 10 1 3 0 2 69 76)
# 09/25/07 07:32:55 PM PLU:A:Oldd ()
# 09/25/07 07:32:55 PM PLU:B:buf  (2 69 76)
# 09/25/07 07:32:55 PM plc_event (2 69 76)
# 09/25/07 07:32:55 PM PLU: Event: Unknown 4C (2 69 76)
# 09/25/07 07:32:55 PM PLU:A:Oldd ()
# 09/25/07 07:32:55 PM PLU:A:OLDD ()
# 09/25/07 07:32:56 PM PLU:B:buf  (2 69 3 2)
# 09/25/07 07:32:56 PM plc_event (2 69 3 2)
# 09/25/07 07:32:56 PM PLU: Event: Insteon message (hop dup) (2 69 3)
# 09/25/07 07:32:56 PM PLU:A:Oldd ()
# 09/25/07 07:32:56 PM PLU:B:buf  (2)
# 09/25/07 07:32:56 PM PLU:B:buf  (2 69 3 2 69 3)
# 09/25/07 07:32:56 PM plc_event (2 69 3 2 69 3)
# 09/25/07 07:32:56 PM PLU: Event: Insteon message (hop dup) (2 69 3)
# 09/25/07 07:32:56 PM PLU:A:Oldd ()
# 09/25/07 07:32:56 PM PLU:B:buf  (2 69 3)
# 09/25/07 07:32:56 PM plc_event (2 69 3)
# 09/25/07 07:32:56 PM PLU: Event: Insteon message (hop dup) (2 69 3)
# 09/25/07 07:32:56 PM PLU:A:Oldd ()
# 09/25/07 07:32:56 PM PLU:A:OLDD ()
# 09/25/07 07:32:57 PM PLU:B:buf  (2 69 12)
# 09/25/07 07:32:57 PM plc_event (2 69 12)
# 09/25/07 07:32:57 PM PLU: Event: Button Release (2 69 12)
# 09/25/07 07:32:57 PM PLU:A:Oldd ()
# 09/25/07 07:32:57 PM PLU:A:OLDD ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 69 7)
# 09/25/07 07:33:02 PM plc_event (2 69 7)
# 09/25/07 07:33:02 PM PLU: Event: Enroll (2 69 7)
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:A:OLDD ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 69 3)
# 09/25/07 07:33:02 PM plc_event (2 69 3)
# 09/25/07 07:33:02 PM PLU: Event: Insteon message (hop dup) (2 69 3)
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:A:OLDD ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 79 7 1 5 85 2 9 35 143 1 0)
# 09/25/07 07:33:02 PM plc_rinsteon (2 79 7 1 5 85 2 9 35 143 1 0)
# 09/25/07 07:33:02 PM Event: 'Enroll', src = 1.5.85, dst = 2.9.35, Cmd = 1/0
# 09/25/07 07:33:02 PM Event: (143) Standard pkt
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:A:OLDD ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 79 3 1 5 85 2 9)
# 09/25/07 07:33:02 PM plc_rinsteon (2 79 3 1 5 85 2 9)
# Buf  short 1
# 09/25/07 07:33:02 PM PLU:A:Oldd (2 79 3 1 5 85 2 9)
# 09/25/07 07:33:02 PM PLU:A:OLDD (2 79 3 1 5 85 2 9)
# 09/25/07 07:33:02 PM PLU:B:buf  (2 79 3 1 5 85 2 9 35 135 1 0)
# 09/25/07 07:33:02 PM plc_rinsteon (2 79 3 1 5 85 2 9 35 135 1 0)
# 09/25/07 07:33:02 PM Event: 'Insteon message (hop dup)', src = 1.5.85, dst = 2.9.35, Cmd = 1/0
# 09/25/07 07:33:02 PM Event: (135) Standard pkt
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:A:OLDD ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 69 3)
# 09/25/07 07:33:02 PM plc_event (2 69 3)
# 09/25/07 07:33:02 PM PLU: Event: Insteon message (hop dup) (2 69 3)
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:A:OLDD ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 79 3 7 247 70 1 5 85 1 1 1 2)
# 09/25/07 07:33:02 PM plc_rinsteon (2 79 3 7 247 70 1 5 85 1 1 1 2)
# 09/25/07 07:33:02 PM Event: 'Insteon message (hop dup)', src = 7.247.70, dst = 1.5.85, Cmd = 1/1
# 09/25/07 07:33:02 PM Event: (1) Standard pkt
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2)
# 09/25/07 07:33:02 PM PLU:B:buf  (2 69 4 2 69 3)
# 09/25/07 07:33:02 PM plc_event (2 69 4 2 69 3)
# 09/25/07 07:33:02 PM PLU: Event: ACK (2 69 4)
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 69 3)
# 09/25/07 07:33:02 PM plc_event (2 69 3)
# 09/25/07 07:33:02 PM PLU: Event: Insteon message (hop dup) (2 69 3)
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:A:OLDD ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 79 2 1 5 85 7)
# 09/25/07 07:33:02 PM plc_rinsteon (2 79 2 1 5 85 7)
# Buf  short 1
# 09/25/07 07:33:02 PM PLU:A:Oldd (2 79 2 1 5 85 7)
# 09/25/07 07:33:02 PM PLU:A:OLDD (2 79 2 1 5 85 7)
# 09/25/07 07:33:02 PM PLU:B:buf  (2 79 2 1 5 85 7 247 70 37 1 1 2 79 3 1 5 85 7 247 70 33 1 1)
# 09/25/07 07:33:02 PM plc_rinsteon (2 79 2 1 5 85 7 247 70 37 1 1 2 79 3 1 5 85 7 247 70 33 1 1)
# 09/25/07 07:33:02 PM Event: 'Insteon message', src = 1.5.85, dst = 7.247.70, Cmd = 1/1
# 09/25/07 07:33:02 PM Event: (37) Standard pkt
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:B:buf  (2 79 3 1 5 85 7 247 70 33 1 1)
# 09/25/07 07:33:02 PM plc_rinsteon (2 79 3 1 5 85 7 247 70 33 1 1)
# 09/25/07 07:33:02 PM Event: 'Insteon message (hop dup)', src = 1.5.85, dst = 7.247.70, Cmd = 1/1
# 09/25/07 07:33:02 PM Event: (33) Standard pkt
# 09/25/07 07:33:02 PM PLU:A:Oldd ()
# 09/25/07 07:33:02 PM PLU:A:OLDD ()

=cut
