#!/usr/bin/perl
#
# read_ws2000 from device (elv art-Nr. 390-61) and provides socket service (like XPORT),
#  or act as client for this service
# licence GPL2
# Thomas Dressler 2008
# $Id: ws2000_reader.pl,v 1.2 2010-08-01 13:33:13 rudolfkoenig Exp $

use Switch;
use strict;
use IO::Socket;
use IO::Select;

our $PortObj;
my $server;
our $xport;
our $sel;
our $clients;
our %sockets;
my ($datum,$data,$lastdate,$lastdata);
$SIG{INT}=$SIG{TERM}=\&signalhandler;
$|=1;

PortOpen($ARGV[0]);

my $ServerPort=$ARGV[1];
if ($ServerPort) {
    $server=IO::Socket::INET->new(LocalPort=>$ServerPort,
                                 ReuseAddr=>1,
                                 Listen=>10,
                                 timeout=>1,
                                 blocking=>0
                                 );
    die "ServerStart-Error: $@ \n" if !$server;
    $clients=IO::Select->new($server);
}


MAINLOOP:
    for(;;) {
        if($server) {
            #only for server mode
            my %message=undef;
            my $fh;
            while(my @ready=$clients->can_read(1)) {
                foreach $fh (@ready) {
                    if ($fh==$server) {
                        #new connection waiting
                        my $new=$server->accept;
                        $new->autoflush(1);
                        $new->blocking(0);
                        $new->timeout(1);
                        $clients->add($new);
                        my $host=$new->peerhost;
                        $sockets{$new}=$host;
                        print "\nNew Client:".$host."\n";
                    #}else{
                    #    my $out=undef;
                    #    $fh->read($out,1);
                    #    $message{$fh}=$out;
                    }
                }
            }
            #check living connections
            foreach my $fh($clients->handles) {
                   next if $fh==$server;
                   next if ($fh->peerhost);
                   print "Terminate ".$sockets{$fh}. " by Check1\n";
                    Client_disconnect($fh);                
            }
            
        }
        $data=get_data();
        next if ! $data;
        $datum = time();
        next if ($data eq $lastdata) && (($datum - $lastdate) < 30);
        my $result=decode_data($data);
        print scalar localtime().":".$result."\n";
        $lastdata = $data;
        $lastdate = $datum;
    }
PortClose();
exit 0;
#--------SUBs -----------------------------
sub Clientdisconnect{
        my $fh=shift;
        print "\n Client ".$sockets{$fh}." disconnected!\n";
        $clients->remove($fh);
        delete $sockets{$fh};
        $fh->shutdown(2);
        $fh->close;
}
sub PortOpen{
    my $PortName=shift;
    my $quiet=shift ||undef;
    
    if ($PortName=~/^\/dev|^COM/) {
    #normal devices (/dev)
        my $OS=$^O;
	if ($OS eq 'MSWin32') {
		eval ("use Win32::SerialPort;");
		die "$@\n" if ($@);
		$PortObj = new Win32::SerialPort ($PortName, $quiet)
	       || die "Can't open $PortName: $^E\n";    # $quiet is optional
	} else {
		eval ("use Device::SerialPort;");
		die "$@\n" if ($@);
		$PortObj = new Device::SerialPort ($PortName, $quiet)
		|| die "Can't open $PortName: $^E\n";    # $quiet is optional
        
	}
#Parameter 19200,8,2,Odd,None
        $PortObj->baudrate(19200);
	$PortObj->databits(8);
	$PortObj->parity("odd");
	$PortObj->stopbits(2);
	$PortObj->handshake("none");
	if (! $PortObj->write_settings) {
		undef $PortObj;
		die "Write Settings failed!\n";
	}
        $sel=IO::Select->new($PortObj->{FD} );
    }elsif($PortName=~/([\w.]+):(\d{1,5})/){
    #Sockets(hostname:port)
        my $host=$1;
        my $port=$2;
        $xport=IO::Socket::INET->new(PeerAddr=>$host,
                                     PeerPort=>$port,
                                     timeout=>1,
                                     blocking=>0
                                     );
        die "Cannot connect to $PortName -> $@ ( $!) \n" if ! $xport;
        $xport->autoflush(1);
        $sel=IO::Select->new($xport);
    }else{
        die "$PortName is no device and not implemented!\n";
    }
}
sub PortClose {
    $PortObj->close if ($PortObj);
    if ($xport) {
        $clients->remove($xport) if $clients;
        $xport->shutdown(2);
        $xport->close;
    }
    if ($clients) {
        foreach my $socket($clients->handles) {
                Clientdisconnect($socket);
         }
    }
}
sub signalhandler {
    my $signal=shift;
    PortClose();
    print "\nTerminated by Signal $signal!\n";
    exit;
}
    
sub get_data {
    
    my $STX=2;
    my $ETX=3;
    my $retval='';
    my $status='';
    my $out=undef;
    my $message;
    my $byte;
    for(;;) {
        #sleep 1 if(!defined($out) || length($out) == 0);
        $out=undef;
        if ($xport) {
           
          #my @readable=$select->can_read(1);
          #next if $#readable<0;
          
          
          #my $fh;
          #foreach $fh (@readable) {
              
          next if ! $sel->can_read(1);
          $xport->read($out,1);
          #if ($xport->eof) {
          #  print "Xport eof\n";
          #  print "Server disconnected, terminate!\n";
          #  PortClose();
           # exit 1;
            
          #}
            
            
                  
        }elsif($PortObj) {
            $out = $PortObj->read(1);
        }
        next if(!defined($out) || length($out) == 0) ;
          
          
        $byte=ord($out);
        if($byte eq $STX) {
        	#Log 4, "M232: return value \'" . $retval . "\'";
        	$status= "STX";
                $message=$out;
        } elsif($byte eq $ETX) {
                $status= "ETX";
                $message .=$out;
        } elsif ($status eq "STX"){
            $byte=$byte & 0x7F;
            $retval .= chr($byte);
            $message .=$out;
        }
        last if($status eq "ETX");
	
    }
    if ($server) {
        foreach my $client($clients->can_write(1)) {
                next if $client == $server;
                if (!$client->send($message)){
                    Clientdisconnect($client);
                    next;
                 }
            
        }
    }
    return $retval;
}


sub decode_data {
    my ($sensor,$daten1,$einheit1,$daten2,$einheit2,$daten3,$einheit3,$result);
    my $data=shift;
    my ($typ,$w1,$w2,$w3,$w4,$w5)=unpack("U*",$data);
    my $group = ($typ & 0x70)/16 ;#/slash for komodo syntax checker!
    my $snr = $typ % 16;
    
    
    switch ( $group ){
        case 7 {
                $sensor = "Fernbedienung";
                $daten1 = $w1 * 10000 + $w2 * 1000 + $w3 * 100 + $w4 * 10 + $w5;
                $result = $sensor . "=" . $daten1 . $einheit1;
            }
        case 0 {
                if ($snr < 8) {
                        $sensor = "Temperatursensor V1.1(" . $snr . ")";
                }else{
                        $sensor = "Temperatursensor V1.2(" .($snr - 8). ")";
                }
                if ($w1 >= 64) {
                    $daten1 = ((255 - $w1 - $w2) / 10) * (-1);
                }else{
                    $daten1 = (($w1 * 128 + $w2) / 10);
                }
                $einheit1 = " °C";
                $result = $sensor . "=" . $daten1 . $einheit1;
            }
        case 1 {
            if ($snr <8) {
                $sensor = "Temperatursensor mit Feuchte V1.1(" . $snr . ")";
            }else{
                $sensor = "Temperatursensor mit Feuchte V1.2(" . ($snr - 8) . ")";
            }
            if ($w1 >= 64) {
                $daten1 = ((255 - $w1 - $w2) / 10) * (-1);
            }else{
                $daten1 = (($w1 * 128 + $w2) / 10);
            }
      
            $daten2 = $w3;
            $daten3 = 0;
            $einheit1 = " °C";
            $einheit2 = " %";
      
            $result = $sensor . "=" . $daten1 . $einheit1 . " " . $daten2 .$einheit2;
        }
        case 2 {
            if ( $snr < 8 ) {
                $sensor = "Regensensor V1.1(" . $snr . ")";
            }else{
                $sensor = "Regensensor V1.2(" . ($snr - 8) . ")"
            }
            $daten1 = ($w1 * 128 + $w2) * 0.36;
            $einheit1 = " l/m˛";
            $result = $sensor . "=" . $daten1 . $einheit1;
        }
        case 3 {
            if ($snr < 8) {
                $sensor = "Windsensor V1.1(" . $snr . ")";
            }else{
                $sensor = "Windsensor V1.2(" & ($snr - 8) . ")";
            }
            switch( $w3) {
                case 0 { $einheit3 = "±0 °";}
                case 1 { $einheit3 = "± 22,5 °";}
                case 2 { $einheit3 = "± 45 °";}
                case 3 { $einheit3 = "± 67,5 °";}
            }
            $daten1 = ($w1 * 128 + $w2) / 10;
            $daten2 = $w4 * 128 + $w5;
            $einheit1 = " km/h";
            $einheit2 = " °";
            $result = $sensor . "=" . $daten1 . $einheit1 . " " . $daten2 . $einheit2 . " " . $einheit3;
        }
        case 4 {
            if ($snr < 8) {
                $sensor = "Innensensor V1.1(" . $snr . ")";
            }else{
                $sensor = "Innensensor V1.2(" . ($snr - 8) . ")";
            }
            if ($w1 >= 64) {
                $daten1 = ((255 - $w1 - $w2) / 10) * (-1);
            }else{
                $daten1 = (($w1 * 128 + $w2) / 10);
            }
            $daten2 = $w3;
            $daten3 = $w4 * 128 + $w5;
            $einheit1 = " °C";
            $einheit2 = " %";
            $einheit3 = " hPa";
            $result = $sensor . "=" . $daten1 . $einheit1 . " " . $daten2 . $einheit2 . " " . $daten3 . $einheit3;
        }
        case 5 {
            $sensor = "Helligkeitssensor V1.2(" . ($snr - 8) . ")";
            switch ($w3) {
                case 0 {$daten1 = 1;}
                case 1 {$daten1 = 10;}
                case 2 {$daten1 = 100;}
                case 3 {$daten1 = 1000;}
            }
            $daten1 = $daten1 * ($w1 * 128 + $w2);
            $einheit1 = "Lux";
            $result = $sensor . "=" . $daten1 . $einheit1;
        }
        case 6 {
            $sensor = "Pyranometer V1.2(" . ($snr - 8) . ")";
            switch ($w3) {
                case 0 {$daten1 = 1;}
                case 1 {$daten1 = 10;}
                case 2 {$daten1 = 100;}
                case 3 {$daten1 = 1000;}
            }
            $daten1 = $daten1 * ($w1 * 128 + $w2);
            $einheit1 = " W/m˛";
            $result = $sensor . "=" . $daten1 . $einheit1;
        }
        else {
            $sensor = "Störung";
            $daten1 = $typ;
            $result = $sensor . "(Group:" . $group . "/Typ:" . $typ . ")";
        }#switch else
        
    }#switch
    return $result;
}
