#!/usr/bin/perl

# simple write operation against waterkotte with control 'resümat cd4 (8126)'
# (c) 2009 alexander.philipp.lintenhofer
#

use strict;
use Device::SerialPort;
use POSIX qw(floor);

#####################################################################################################

if ($#ARGV != 1) { die "Usage: $0 <attribute> <value> (special case: $0 Zeit sync)\n"; }
my $changeAttr = $ARGV[0];
my $changeVal  = $ARGV[1]; 

use constant TYPE_UNDEF     => 0;
use constant TYPE_DATE      => 1;
use constant TYPE_TIME      => 2;
use constant TYPE_DATETIME  => 3;
use constant TYPE_DEC       => 4;
use constant TYPE_FLOAT1    => 5;
use constant TYPE_FLOAT3    => 6;
use constant TYPE_BIN       => 7;
use constant TYPE_BOOL      => 8;
use constant TYPE_SPECI     => 9;

use constant DLE         => 0x10;
use constant STX         => 0x02;
use constant ETX         => 0x03;

my  @frame;
our %wp_memory;

#####################################################################################################

require('WPmemaddr.inc.pl');
require('WPhelperfunctions.inc.pl');

my $logfile      = 'logs/wp_setval.log';
my $getValScript = 'WPgetVal.pl';

#####################################################################################################
open(STDOUT, ">>$logfile")
    || die "failed to re-open STDOUT to $logfile";
open(STDERR, ">&STDOUT")
    || die "failed to re-open STDERR to STDOUT";

#####################################################################################################
#####################################################################################################
# main
#

@frame = buildFrame($changeAttr,$changeVal);

if ((scalar(@frame) > 10))
{
    for (my $attempt=0; $attempt<5;$attempt++)
    {
        if (my $serial = Device::SerialPort->new("/dev/ttyr00",0,'/tmp/wp_op.lock'))
        {
            $serial->baudrate(9600);
            $serial->parity("none");
            $serial->databits(8);
            $serial->stopbits(1);
            $serial->write_settings || undef $serial;

            my $exitVal = 1;

            # frame contains bytes, but for serial-> write we need a string
            if (($serial->write(pack("C*",@frame)))==scalar(@frame))
            {
                my @comdump;

                sleep 1;

                # expect FF 10 02 00 11 00 10 03 66 00 FF
                my ($count,$retVal) = $serial->read(11);

                if ($count > 0)
                {
                    # we are sure that no 0xff appears in data section of frame
                    for(my $i=0, my $currByte = ord(substr($retVal,0,1));
                        (($currByte == 255) || ($i < length($retVal)));
                        $i++,$currByte = ord(substr($retVal,$i,1)))
                    {
                        $comdump[$i] = $currByte;
                    }

                    my @retVal = fetchFrameData(@comdump);
                    if ((@retVal)&&(scalar(@retVal)==1)&&($retVal[0]==0))
                    { $exitVal = 0; }
                    # success not sure...
                    else
                    {
                        $serial->close() || print STDERR (localtime().": Close failed\n");

                        my $newVal = `$getValScript $changeAttr`;
                        if ($changeVal == $newVal) { $exitVal = 0; }
                    }
                    if ($exitVal == 0) { print STDOUT (localtime().": $changeAttr = $changeVal\n"); }
                }
                else { print STDERR (localtime().": Received invalid frame\n"); }
            }
            else { print STDERR (localtime().": Error writing to rs232\n"); }

            $serial->close() || print STDERR (localtime().": tty already closed\n");
            exit $exitVal;
        }
        sleep 4;
    }
    print STDERR (localtime().": Open of device failed\n"); exit 1;
}
else { print STDERR (" ".$changeAttr." not set to ".$changeVal."\n"); exit 1; }

#####################################################################################################
#####################################################################################################
# functions
#

#####################################################################################################
#####################################################################################################
# buildFrame()
# - builds command-frame according to selected attribute and value
#

sub buildFrame
{
    my $changeAttr = $_[0];
    my $changeVal  = $_[1];
    my @writeCmd   = ($changeVal eq 'sync')?(0x01,0x14):(0x01,0x13);

    if ((defined($wp_memory{$changeAttr}))&&($wp_memory{$changeAttr}{acl} eq 'rw'))
    {
        ###########################################################################################
        # 1.) frame header, delimiter, start text
        @frame = (0xff,DLE,STX);

        ###########################################################################################
        # 2.) <cmdcode>
        push(@frame,@writeCmd);

        ###########################################################################################
        # 3.) <addr>
        push(@frame,(hex2bytearray($wp_memory{$changeAttr}{addr},2)));

        ###########################################################################################
        # 4.) <value>

        my @bytes;
        my $datatype = $wp_memory{$changeAttr}{type};
        my $seize    = $wp_memory{$changeAttr}{bytes};
        my $minVal   = (defined($wp_memory{$changeAttr}{minVal}))?$wp_memory{$changeAttr}{minVal}:0;
        my $maxVal   = (defined($wp_memory{$changeAttr}{maxVal}))?$wp_memory{$changeAttr}{maxVal}:0;

        # special case: time sync
        # set $changeVal with results of localtime()
        if($changeVal eq 'sync')
        {
            my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);

            if ($changeAttr eq 'Zeit')
            {
                $changeVal = sprintf("%02d",$day).'.'.sprintf("%02d",($month+1)).'.'.sprintf("%02d",($year-100)).',';
                $changeVal.= sprintf("%02d",$hour).':'.sprintf("%02d",$min).':'.sprintf("%02d",$sec);
            }
            else { print STDERR (localtime().": Sync only time (date and time)."); return 0; }
        }

        if    ($datatype == TYPE_BOOL) { @bytes = ((int($changeVal)>0?1:0)); }
        elsif ($datatype == TYPE_DEC)
        {
            $changeVal = int($changeVal);
            if (($changeVal>=$minVal)&&($changeVal<=$maxVal)) { @bytes = ($changeVal); }
            else { print STDERR (localtime().": Value out of range [".$minVal."-".$maxVal."]."); return 0; }
        }
        elsif ($datatype == TYPE_FLOAT1)
        {
            $changeVal = sprintf("%0.1f", $changeVal);
            if (($changeVal>=$minVal)&&($changeVal<=$maxVal)) { @bytes = (hex2bytearray(hex(float2hex($changeVal)),$seize)); }
            else { print STDERR (localtime().": Value out of range [".$minVal."-".$maxVal."]."); return 0; }
        }
        elsif ($datatype == TYPE_DATETIME)
        {
            if ($changeVal =~ /^[0-3][0-9]\.[0,1][0-9]\.[0,1][0-9]\,[0-2][0-9]\:[0-5][0-9]\:[0-5][0-9]$/)
            {
                @bytes = (int(substr($changeVal,15,2)),int(substr($changeVal,12,2)),int(substr($changeVal,9,2))%24,
                          int(substr($changeVal,0,2))%31,int(substr($changeVal,3,2)),int(substr($changeVal,6,2)));
            }
            else { print STDERR (localtime().": No valid format (dd.mm.yy,hh:mm:ss)."); return 0; }
        }
        else { print STDERR (localtime().": Missing type definition."); return 0; }

        # byte stuffing...
        my @stuffedBytes;
        for (my $i=0, my $j=0; $i<scalar(@bytes); $i++,$j++)
        {
            if ($bytes[$i]==16) { $stuffedBytes[$j]=16; $stuffedBytes[++$j]=16; }
            else { $stuffedBytes[$j]=$bytes[$i]; }
        }

        push(@frame,@stuffedBytes);

        ###########################################################################################
        # 5.) delimiter, end text
        push(@frame,(DLE,ETX));

        ###########################################################################################
        # 6.) CRC
        push(@frame,(0,0,0xff));
        my @crc = hex2bytearray(GetCRC16(@frame),2);
        @frame[(scalar(@frame)-3)] = $crc[0];
        @frame[(scalar(@frame)-2)] = $crc[1];

        # for debugging purposes...
        #printDebuggingInfo(\@bytes,\@frame);

        return @frame;
    }
    else { print STDERR (localtime().": Attribute not defined or not writeable."); return 0 }
}

#####################################################################################################
#####################################################################################################
# printDebuggingInfo()
# - display result human readable for control purposes
#

sub printDebuggingInfo
{
    my ($bytes, $frame) = @_;

    print "Input: Change ".$changeAttr." to value ".$changeVal." (type ".$wp_memory{$changeAttr}{type}."):\n";
    printf("Storing following value(s) to address 0x%04x\n",$wp_memory{$changeAttr}{addr});
    print "Hex  : "; foreach (@$bytes) { printf("0x%02x ",$_) }; print "\n";
    print "Dec  : "; foreach (@$bytes) { printf(" %03d ",$_) }; print "\n";
    if ($wp_memory{$changeAttr}{type} == TYPE_FLOAT1)
    {
        print "Float: ";
        my $val = 0;
        for (my $i=(scalar(@$bytes)-1); $i>=0;$i--) { $val += @$bytes[scalar(@$bytes)-1-$i]*(256**$i)." "; };
        print hex2float1(sprintf("%08x", $val))."\n";
    }
    print "[ "; foreach (@$frame) { printf("%02x ",$_) }; print "]\n";
}
