##############################################
# $Id: 75_LGTV_RS232.pm 9954 2015-11-21 13:58:15Z markusbloch $
#
# created by Markus Bloch (Notausstieg0309@googlemail.com)
#
# This modules controls LG Smart TV's which are connected via
# a USB to Serial converter to FHEM.
#
# Detailed Information about the hardware setup and more possible control commands: http://www.lgrs232.com/
#
# This modules is able to switch the input channels, which
# is not possible via the ethernet interface.
#
# Define:  define TV LGTV_RS232 /dev/ttyUSB0
#
# Set: statusRequest input:hmdi1,hdmi2, ...
#
#
# 

package main;

use strict;
use warnings;
use Time::HiRes qw(gettimeofday);
use DevIo;

my %LGTV_RS232_response = (

"a" =>  {   
            "power" => {"00" => "off",
                        "01" => "on"
                       },
            "state" => {"00" => "off",
                        "01" => "on"
                       }            
        },
"b" =>  {
            "input" => {"00" => "digitalTV",
                        "01"  => "analogueTV",
                        "02" => "video1",
                        "03" => "video2",
                        "04" => "component1",
                        "05" => "component2",
                        "06" => "rgbDTV",
                        "07" => "rgbPC",
                        "08" => "hdmi1",
                        "09" => "hdmi2"
                        }
            
        },
"c" =>  {   
            "aspectRatio" => {  "01" => "normal",
                                "02" => "wide",
                                "03" => "horizon",
                                "04" => "zoom1",
                                "05" => "zoom2",
                                "06" => "auto",
                                "07" => "14:9",
                                "08" => "full",
                                "09" => "justScan",
                                "0a" => "zoom3",
                                "0b" => "fullWide",
                                "10" => "cinemaZoom1",
                                "11" => "cinemaZoom16"
                             }
        },
"d" =>  {
            "screenMute"    => {"00" => "off",
                                "01" => "on"
                               },
            "videoOutMute"  => {"00" => "off",
                                "10" => "on"
                               }
        },        
"e" =>  {
            "volumeMute" => {"00" => "on",
                             "01" => "off"
                            }
        }
);

my %LGTV_RS232_set = (
"input" => {
            "digitalTV" => "kb 01 00",
            "video1"    => "kb 01 02",
            "video2"    => "kb 01 03",
            "component" => "kb 01 04",
            "hdmi1"     => "kb 01 08",
            "hdmi2"     => "kb 01 09",
           },
"power" => {
	    "on"        => "ka 01 01",
	    "off"       => "ka 01 00",
           }
);



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

  $hash->{DefFn}    = "LGTV_RS232_Define";
  $hash->{UndefFn}  = "LGTV_RS232_Undef";
  $hash->{SetFn}    = "LGTV_RS232_Set";
  $hash->{ReadFn}   = "LGTV_RS232_Read";
  $hash->{ReadyFn}  = "LGTV_RS232_Ready";
  $hash->{AttrList} = " ".$readingFnAttributes;
}

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



  my $name = $a[0];
  my $dev = $a[2];


  
  $hash->{helper}{RECEIVE_BUFFER} = "";
  
  $dev .= "\@9600" if(not $dev =~ m/\@\d+/);
  
  $hash->{DeviceName} = $dev;
  
  DevIo_CloseDev($hash);
  
  my $ret = DevIo_OpenDev($hash, 0, undef);
  
  delete($hash->{PARTIAL});
  RemoveInternalTimer($hash);
  LGTV_RS232_GetStatus($hash);
  return undef;
}

#####################################
sub
LGTV_RS232_Undef($$)
{
  my ($hash, $arg) = @_;
  
  DevIo_CloseDev($hash); 
  return undef;
}


#####################################
sub
LGTV_RS232_Set($@)
{
    my ($hash, @a) = @_;

    my $what = $a[1];
    my $usage = "Unknown argument $what, choose one of statusRequest";

    foreach my $cmd (sort keys %LGTV_RS232_set)
    {
       $usage .= " $cmd:".join(",", sort keys %{$LGTV_RS232_set{$cmd}});
    }

    if($what eq "statusRequest")
    {
        LGTV_RS232_GetStatus($hash, 1);
    }
    elsif(exists($LGTV_RS232_set{$what}) and exists($LGTV_RS232_set{$what}{$a[2]}))
    {
        DevIo_SimpleWrite($hash, $LGTV_RS232_set{$what}{$a[2]}."\n", 0);
    }
    else
    {
      return $usage;
    }
   
}


#####################################
# called from the global loop, when the select for hash->{FD} reports data
sub
LGTV_RS232_Read($)
{
    my ($hash) = @_;

    my $buf = DevIo_SimpleRead($hash);
    return "" if(!defined($buf));
    my $name = $hash->{NAME};

    my $partial = $hash->{helper}{RECEIVE_BUFFER};

    Log3 $name, 5, "LGTV_RS232 ($name) - ".($partial ne "" ? "(buffer contains: $partial) " : "")."received: $buf"; 
   
    $partial .= $buf;

    while($partial =~ /(\w\s\d{2}\s[0-9a-zA-Z]+?x)(.*)/)
    {
        my $msg = $1;
        $partial = $2;
        $msg =~ s/x$//;

        LGTV_RS232_ParseResponse($hash, $msg);
    }
    
    $hash->{helper}{RECEIVE_BUFFER} = $partial;
    
    RemoveInternalTimer($hash);
    InternalTimer(gettimeofday()+15, "LGTV_RS232_GetStatus", $hash, 0);
}


#####################################
# receives incoming data
sub
LGTV_RS232_Ready($)
{
  my ($hash) = @_;

  return DevIo_OpenDev($hash, 1, undef) if($hash->{STATE} eq "disconnected");
}

sub
LGTV_RS232_ParseResponse($$)
{
    my ($hash, $msg) = @_;
    my $name = $hash->{NAME};
    
    Log3 $name, 4, "LGTV_RS232 ($name) - processing response: ".$msg;

    my ($code, $glue, $result, $val) = unpack("A2 A3 A2 A*", $msg);
    
    Log3 $name, 5, "LGTV_RS232 ($name) - processed code: $code - glue: $glue - result: $result - val: $val";
    
    readingsBeginUpdate($hash);
    
    if($result eq "OK")
    {
         readingsBulkUpdate($hash, "CommandAccepted", "yes");
    }
    elsif($result eq "NG")
    {
         readingsBulkUpdate($hash, "CommandAccepted", "no");    
         return;
         
    }
    
    foreach my $reading (keys %{$LGTV_RS232_response{$code}})
    {
        if(exists($LGTV_RS232_response{$code}{$reading}{$val}))
        {
            readingsBulkUpdate($hash, $reading, $LGTV_RS232_response{$code}{$reading}{$val});
        }
    }
    
    if($code eq "f")
    {
        readingsBulkUpdate($hash, "volume", hex($val)." %");
    }
    readingsBulkUpdate($hash, "presence", "present");
    readingsEndUpdate($hash, 1);

}

#####################################
# request the current state
sub
LGTV_RS232_GetStatus($;$)
{
    my ($hash, $local) = @_;
    
    DevIo_SimpleWrite($hash, "ka 01 ff\n", 0);
    DevIo_SimpleWrite($hash, "kb 01 ff\n", 0);
    DevIo_SimpleWrite($hash, "kc 01 ff\n", 0);
    DevIo_SimpleWrite($hash, "kd 01 ff\n", 0);
    DevIo_SimpleWrite($hash, "kf 01 ff\n", 0);
    DevIo_SimpleWrite($hash, "ke 01 ff\n", 0);

    RemoveInternalTimer($hash);
    InternalTimer(gettimeofday()+2, "LGTV_RS232_TimeOut", $hash, 0);

}

#####################################
# Is executed when a request via serial connection was not answered
sub 
LGTV_RS232_TimeOut($)
{
    my ($hash) = @_;
    readingsBeginUpdate($hash);
    readingsBulkUpdate($hash, "presence", "absent");
    readingsBulkUpdate($hash, "state", "off");
    readingsEndUpdate($hash, 1);
    RemoveInternalTimer($hash);
    InternalTimer(gettimeofday()+15, "LGTV_RS232_GetStatus", $hash, 0);
}

1;