# $Date: 2006-09-26 03:39:02 +0200 (Tue, 26 Sep 2006) $
# $Revision: 910 $

use strict;

package Serial_Item;

use X10_Interface;

our $mainHash=\%::Serial_Ports;

@Serial_Item::ISA = ('X10_Interface');
our @supported_interfaces=qw!cm11 BX24 Homevision HomeBase Stargate HouseLinc
	Marrick cm17 Lynx10PLC weeder wish iplcs!;

sub new {
	my ($class, $id, $state, $device_name) = @_;
	my $self = X10_Interface->new($id, $state, $device_name);
	bless ($self, $class);
	$self->{mainHash}=$mainHash;
	$self->set_standard_config;
	$self->set_interface($device_name) if $id and $id =~ /^X/;
	$self->state_overload('off'); # By default, do not process ~;: strings as substate/multistate

	return $self;
}

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

	return &::serial_port_open($self->{device_name});
}

sub stop {
    my ($self) = @_;
    my $port_name = $self->{port_name};
    my $sp_object = $main::Serial_Ports{$port_name}{object};
    if ($sp_object) {

        my $port = $main::Serial_Ports{$port_name}{port};
#       &Win32::SerialPort::debug(1);
        if ($sp_object->close) {
            print "Port $port_name on port $port was closed\n";
        }
        else {
            print "Serial_Item stop failed for port $port_name\n";
        }
                                # Delete the ports, even if it didn't close, so we can do
                                # starts again without a 'port reuse' message.
        delete $main::Serial_Ports{$port_name}{object};
        delete $main::Serial_Ports{object_by_port}{$port};
#       &Win32::SerialPort::debug(0);
    }
    else {
        print "Error in Serial_Item stop for port $port_name: Port is not started\n";
    }
}

sub set_dtr {
    my ($self, $state) = @_;
    my $port_name = $self->{port_name};
    if (my $serial_port = $main::Serial_Ports{$port_name}{object}) {
        $main::Serial_Ports{$port_name}{object}->dtr_active($state);
        print "Serial_port $port_name dtr set to $state\n" if $main::Debug{serial};
    }
    else {
        print "Error, serial port set_dtr for $port_name failed, port has not been set\n";
    }
}
sub set_rts {
    my ($self, $state) = @_;
    my $port_name = $self->{port_name};
    if (my $serial_port = $main::Serial_Ports{$port_name}{object}) {
        $main::Serial_Ports{$port_name}{object}->rts_active($state);
        print "Serial_port $port_name rts set to $state\n" if $main::Debug{serial};
    }
    else {
        print "Error, serial port set_rts for $port_name failed, port has not been set\n";
    }
}

sub write_data {
	my ($self, $serial_data) = @_;

	&send_serial_data($self->{device_name}, $serial_data);
}

sub send_serial_data {
	my ($port_name, $serial_data)=@_;

    return if &main::proxy_send($port_name, 'send_serial_data', $serial_data);

                                # The ncpuxa code works on a socket, not a serial port
                                # but may be called as a Serial_Item
    unless ($main::Serial_Ports{$port_name}{object} or lc $port_name eq 'ncpuxa') {
        print "Error, serial port for $port_name has not been set: data=$serial_data\n";
        return;
    }

    if (lc $port_name eq 'homevision') {
        print "Using homevision to send: $serial_data\n";
        &Homevision::send($main::Serial_Ports{Homevision}{object}, $serial_data);
    }
    elsif (lc $port_name eq 'ncpuxa') {
        &main::print_log("Using ncpuxa to send: $serial_data");
        &ncpuxa_mh::send($main::config_parms{ncpuxa_port}, $serial_data);
    }
    else {
		my $datatype  = $main::Serial_Ports{$port_name}{datatype};
		my $prefix    = $main::Serial_Ports{$port_name}{prefix};

		$serial_data = $prefix . $serial_data if $prefix and $prefix ne '';
		$serial_data .= "\r" unless $datatype and $datatype eq 'raw';

        my $results = $main::Serial_Ports{$port_name}{object}->write($serial_data);

#      &main::print_log("serial port=$port_name out=$serial_data results=$results") if $main::Debug{serial};
        print "serial  port=$port_name out=$serial_data results=$results\n" if $main::Debug{serial};
    }
}

my $x10_save_unit;

sub send_x10_data {
	# This function can either be called as a class method or a library function
	# If being called as a member function, then pull the object ref off the
	# argument list.
	my $self=undef;
	if (ref($_[0])) {
		$self=shift @_;
	}
    my ($interface, $serial_data, $module_type) = @_;
    my ($isfunc);

                                # Use proxy mh if present (avoids mh pauses for slow X10 xmits)
    return if &main::proxy_send($interface, 'send_x10_data', $serial_data, $module_type);
# This function can either be called as a class method or a library function

    if ($serial_data =~ /^X[A-P][1-9A-G]$/) {
        $isfunc = 0;
        $x10_save_unit = $serial_data;
    }
    else {
        $isfunc = 1;
    }
    print "X10: interface=$interface isfunc=$isfunc save_unit=$x10_save_unit data=$serial_data\n" if $main::Debug{x10};

    if ($interface eq 'cm11') {
                                # CM11 wants individual codes without X
        print "db1 CM11: Sending x10 data: $serial_data\n" if $main::Debug{cm11};

				# Standard 1-cm11 code
	if (!$main::config_parms{cm11_bak_port}) {
	    &ControlX10::CM11::send($main::Serial_Ports{cm11}{object},
				    substr($serial_data, 1));
	}
				# Dual cm11 code
	else {
				# if both units are active then
                                #   use the one with the most time left on the counter as it was the most recently found to be active
				# otherwise use the main one if it's active or the backup if it's active
	    if (($main::cm11_objects{active}->state() eq 'on') && ($main::cm11_objects{bak_active}->state() eq 'on')) {
		if ($main::cm11_objects{timer}->seconds_remaining() >= $main::cm11_objects{bak_timer}->seconds_remaining()) {
		    print "db CM11: using primary cm11\n" if $main::Debug{cm11};
		    &ControlX10::CM11::send($main::Serial_Ports{cm11}{object},substr($serial_data, 1));
		} else {
		    print "db CM11: using backup cm11\n" if $main::Debug{cm11};
		    &ControlX10::CM11::send($main::Serial_Ports{cm11_bak}{object},substr($serial_data, 1));
		}
	    } elsif ($main::cm11_objects{active}->state() eq 'on') {
		print "db CM11: using primary cm11\n" if $main::Debug{cm11};
		&ControlX10::CM11::send($main::Serial_Ports{cm11}{object},substr($serial_data, 1));
	    } elsif ($main::cm11_objects{bak_active}->state() eq 'on') {
		print "db CM11: using backup cm11\n" if $main::Debug{cm11};
		&ControlX10::CM11::send($main::Serial_Ports{cm11_bak}{object},substr($serial_data, 1));
	    } else {
		print "db CM11: Error - no cm11's are working ...\n" if $main::Debug{cm11};
	    }
	}
    }


    elsif ($interface eq 'ti103') {
                                # TI103 wants individual codes without X
        print "db1 TI103: Sending x10 data: $serial_data\n" if $main::Debug{ti103};
        &ControlX10::TI103::send($main::Serial_Ports{ti103}{object}, substr($serial_data, 1));
    }

    elsif ($interface eq 'bx24') {
                                # BX24 wants individual codes without X
        &X10_BX24::SendX10($serial_data);
    }

    elsif ($interface eq 'lynx10plc') {
                                # marrick PLC wants XA1AK
        &Lynx10PLC::send_plc($main::Serial_Ports{Lynx10PLC}{object},
                             $serial_data, $module_type);
    }
    elsif ($interface eq 'cm17') {
                                # cm17 wants A1K, not XA1AK
        &ControlX10::CM17::send($main::Serial_Ports{cm17}{object},
                                substr($x10_save_unit, 1) . substr($serial_data, 2)) if $isfunc;
    }
    elsif ($interface eq 'homevision') {
                                # homevision wants XA1AK
        if ($isfunc) {
            print "Using homevision to send: " .
                $x10_save_unit . substr($serial_data, 1) . "\n";
            &Homevision::send($main::Serial_Ports{Homevision}{object},
                              $x10_save_unit . substr($serial_data, 1));
        }
    }
    elsif ($interface eq 'homebase') {
                                # homebase wants individual codes without X
        print "Using homebase to send: $serial_data\n";
        &HomeBase::send_X10($main::Serial_Ports{HomeBase}{object}, substr($serial_data, 1));
    }
    elsif ($interface eq 'stargate') {
                                # Stargate wants individual codes without X
        print "Using stargate to send: $serial_data\n";
        &Stargate::send_X10($main::Serial_Ports{Stargate}{object}, substr($serial_data, 1));
    }
    elsif ($interface eq 'houselinc') {
                                # houselinc wants XA1AK
        if ($isfunc) {
            print "Using houselinc to send: " .
                $x10_save_unit . substr($serial_data, 1) . "\n";
            &HouseLinc::send_X10($main::Serial_Ports{HouseLinc}{object},
                                 $x10_save_unit . substr($serial_data, 1));
        }
    }
    elsif ($interface eq 'marrick') {
                                # marrick wants XA1AK
        if ($isfunc) {
            print "Using marrick to send: " .
                $x10_save_unit . substr($serial_data, 1) . "\n";
            &Marrick::send_X10($main::Serial_Ports{Marrick}{object},
                               $x10_save_unit . substr($serial_data, 1));
        }
    }
    elsif ($interface eq 'ncpuxa') {
                                # ncpuxa wants individual codes with X
        &main::print_log("Using ncpuxa to send: $serial_data");
        &ncpuxa_mh::send($main::config_parms{ncpuxa_port}, $serial_data);
    }
    elsif ($interface eq 'weeder') {
                                # Weeder wants XA1AK or XA1ALALAL
        my ($device, $house, $command) = $serial_data =~ /^X(\S\S)(\S)(\S+)/;

                                # Allow for +-xx%
        my $dim_amount = 3;
        if ($command =~ /[\+\-]\d+/) {
            $dim_amount = int(10 * abs($command) / 100); # about 10 levels to 100%
            $command = ($command > 0) ? 'L' : 'M';
        }
                                # Weeder table does not match what we defined in CM11,CM17,X10_Items.pm
                                #  - Dim -> L, Bright -> M,  AllOn -> I, AllOff -> H
        if ($command eq 'M') {
            $command =  'L' . (($house . 'L') x $dim_amount);
        }
        elsif ($command eq 'L') {
            $command =  'M' . (($house . 'M') x $dim_amount);
        }
        elsif ($command eq 'O') {
            $command =  'I';
        }
        elsif ($command eq 'P') {
            $command =  'H';
        }
        $serial_data = 'X' . $device . $house . $command;

        $main::Serial_Ports{weeder}{object}->write($serial_data);

				# Give weeder a chance to do the previous command
				# Surely there must be a better way!
        select undef, undef, undef, 1.2;
    }
    elsif ($interface eq 'wish') {
                                # wish wants individual codes without X
        &main::print_log("Using wish to send: $serial_data");
        &X10_Wish::send(substr($serial_data, 1));
    }
    elsif ($interface eq 'iplcs') {
                                # ncpuxa wants individual codes with X
        &main::print_log("Using iplcs to send: $serial_data");
        &iplcs::send($main::Serial_Ports{iplcs}{object}, $serial_data);
    }
    elsif ($interface eq 'iplcu') {
                                # ncpuxa wants individual codes with X
        &main::print_log("Using iplcu to send: $serial_data");
        &iplcs::send($main::config_parms{iplcu_port}, $serial_data);
    }
    else {
        print "\nError, X10 interface not found: interface=$interface, data=$serial_data\n";
    }

}

sub get_supported_interfaces {
	my ($self)=@_;
	return \@supported_interfaces;
}

sub serial_items_by_id {
	return &Device_Item::items_by_id(@_);
}
sub serial_item_by_id {
	return &Device_Item::item_by_id(@_);
}

1;