#########################################################################################
# Package        HiPi::Energenie
# Description:   Control Energenie devices
# Copyright    : Copyright (c) 2016-2017 Mark Dootson
# License      : This is free software; you can redistribute it and/or modify it under
#                the same terms as the Perl 5 programming language system itself.
#########################################################################################

package HiPi::Energenie;

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

use strict;
use warnings;
use parent qw( HiPi::Interface );
use HiPi qw( :energenie :openthings );
use Time::HiRes qw( usleep );
use HiPi::RF::OpenThings::Message;

use Carp;

__PACKAGE__->create_accessors( qw( backend repeat can_rx ) );

our $VERSION ='0.61';

# OOK Switch Data
# $data = $switchmask->[$socketnum - 1]->[$offon];
# where $socketnum == 0 | 1 | 2 | 3 | 4 and $offon == 0|1;
# when  $socketnum == 0 then $offon is applied to all sockets

my $_ook_switchdata = [
    [ 0b1100, 0b1101 ], # off / on all sockets
    [ 0b1110, 0b1111 ], # off / on  socket 1
    [ 0b0110, 0b0111 ], # off / on  socket 2
    [ 0b1010, 0b1011 ], # off / on  socket 3
    [ 0b0010, 0b0011 ], # off / on  socket 4
];

sub new {
    my( $class, %userparams ) = @_;
    
    my %params = (
        backend      => 'ENER314_RT',
        device       => undef,
        can_rx       => 1,
        devicename   => '/dev/spidev0.1',
    );
    
    foreach my $key (sort keys(%userparams)) {
        $params{$key} = $userparams{$key};
    }
    
    unless( defined($params{device}) ) {
        
        if ( $params{backend} eq 'ENER314_RT' ) {
            
            # Two way configurable board
            require HiPi::Energenie::ENER314_RT;
            $params{repeat} //= ENERGENIE_TXOOK_REPEAT_RATE;
            my $dev = HiPi::Energenie::ENER314_RT->new(
                led_on      => 0,
                devicename  => '/dev/spidev0.1',
            );
            $params{device} = $dev;
            
        } elsif( $params{backend} eq 'ENER314' ) { 
            # simple 1 way single group board
            require HiPi::Energenie::ENER314;
            my $dev = HiPi::Energenie::ENER314->new();
            $params{device} = $dev;
            $params{can_rx} = 0,
        } else {
            croak qq(Invalid backend $params{backend} specified);
        }
        
    }
    
    my $self = $class->SUPER::new( %params );
    return $self;
}

sub pair_socket {
    my($self, $group_id, $socket, $seconds) = @_;
    croak(qq(Invalid socket $socket)) unless $socket =~ /^1|2|3|4$/;
    $seconds ||= 10;
    
    # broadcast for $seconds seconds;
    my $endtime = time() + $seconds;
    my $data = $_ook_switchdata->[$socket]->[0]; # broadcast 'off' message for socket
    
    while ( $endtime >= time() ) {
        $self->device->switch_ook_socket( $group_id, $data, $self->repeat );
    }
    
    return;
}

sub switch_socket {
    my($self, $socket, $offon) = @_;
    croak(qq(Invalid socket $socket)) unless $socket =~ /^0|1|2|3|4$/;
    $offon = ( $offon ) ? 1 : 0;
    my $data = $_ook_switchdata->[$socket]->[$offon];
    $self->device->switch_ook_socket( $self->groupid, $data, $self->repeat );
    return;
}

# test what we actually send 
sub dump_message {
    my($self, $socket, $offon) = @_;
    croak(q(Method requires backend 'ENER314_RT')) if $self->backend ne 'ENER314_RT';
    croak(qq(Invalid socket $socket)) unless $socket =~ /^0|1|2|3|4$/;
    $offon = ( $offon ) ? 1 : 0;
    my $data = $_ook_switchdata->[$socket]->[$offon];
    my @tvals = $self->device->make_ook_message( $self->groupid, $data );
    
    # print preamble
    print sprintf("preamble : 0x%x, 0x%x, 0x%x, 0x%x\n", @tvals[0..3]);
    # print group id
    print sprintf("group id : 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", @tvals[4..13]);
    # print data
    print sprintf("set data : 0x%x, 0x%x\n", @tvals[14..15]);
    return;
}

sub main_loop {
    my( $self, %params ) = @_;
    
    $params{manufacturer_id} //= ENERGENIE_MANUFACTURER_ID;
    
    # keep a hash of switches we have 'registered'
    my $switch_states = {};
    
    # registered devices we are waiting for confirmation
    my $pending_reg = {};
    
    my $switchtime = time() + 7;
    
    my $continue = 1;
    
    while ( $continue ) {
        my $msg = $self->device->receive_fsk_message( ENERGENIE_DEFAULT_CRYPTSEED );
        
        my $messagefromswitch = 0;
        my $switchstate = 0;
        my $sensorkey = 'NOT KNOWN';
        if ( $msg ) {
            my $received = scalar(localtime($msg->epoch));
            print qq(\nMessage recieved $received\n) if $params{debug};
            
            $sensorkey = $msg->sensor_key;
            
            printf(qq(\tManufacturer ID: 0x%04x, Product ID: 0x%04x, Sensor ID: 0x%06x, Encrypt PIP: 0x%04x\n), $msg->manufacturer_id, $msg->product_id, $msg->sensor_id, $msg->encrypt_pip) if $params{debug};
            printf(qq(\tSensor Key: %s\n), $msg->sensor_key);
            if( $msg->manufacturer_id == $params{manufacturer_id} ) {
                my $productname = $self->device->map_product_to_name( $msg->product_id );
                print qq(\tEnergenie : $productname\n) if $params{debug};
            }
            my @joinrecords = ();
            if ( $msg->ok ) {
                
                for my $record ( @{ $msg->records } ) {
                    printf(qq(\t%s = %s ( command bit %s)\n), $record->name, $record->value . $record->units, $record->wr || '0') if $params{debug};
                    if ( $record->id == OPENTHINGS_PARAM_JOIN && $record->wr ) {
                        push @joinrecords, $record;
                    } elsif( $record->id == OPENTHINGS_PARAM_SWITCH_STATE ) {
                        $messagefromswitch = 1;
                        $switchstate = $record->value;
                    }
                }
                
                if(exists($pending_reg->{$sensorkey})) {
                    delete($pending_reg->{$sensorkey});
                    # is this a switch we just registered
                    if( $messagefromswitch ) {
                        $switch_states->{$sensorkey} = {
                            mid => $msg->manufacturer_id,
                            pid => $msg->product_id,
                            sid => $msg->sensor_id,
                            state => $switchstate,
                        }
                    }
                }
                
                for my $jreq( @joinrecords ) {
                    
                    delete($switch_states->{$sensorkey}) if exists ($switch_states->{$sensorkey});
                    
                    my $outmsg = HiPi::RF::OpenThings::Message->new(
                        cryptseed => ENERGENIE_DEFAULT_CRYPTSEED,
                        mid => $msg->manufacturer_id,
                        pid => $msg->product_id,
                        sid => $msg->sensor_id,
                        pip => ENERGENIE_DEFAULT_CRYPTPIP,
                    );
                    
                    $outmsg->add_record(
                        id      => OPENTHINGS_PARAM_JOIN,
                        wr      => 0,
                        typeid  => OPENTHINGS_UINT,
                        value   => undef,
                    );
                    
                    $outmsg->encode_buffer;
                    $self->device->send_fsk_message( $outmsg );
                    $outmsg->decode_buffer;
                    
                    if ( $outmsg->ok ) {
                        print qq(\n\n\tMessage Sent Decoded OK\n);
                        for my $record ( @{ $outmsg->records } ) {
                            printf(qq(\t%s = %s ( command bit %s)\n), $record->name, $record->value . $record->units, $record->wr || '0') if $params{debug};
                        }
                        $pending_reg->{$sensorkey} = 1;
                    } else {
                        print qq(\n\n\tMessage Decoded With Errors\n) if $params{debug};
                        while ( my $error = $outmsg->shift_error ) {
                            print qq(\tERROR: $error\n) if $params{debug};
                        }
                    }   
                }
            } else {
                while ( my $error = $msg->shift_error ) {
                    print qq(\tERROR: $error\n) if $params{debug};
                }
            }
        }
        
        # process any switch states
        if( $messagefromswitch && exists($switch_states->{$sensorkey}) ) {
            $switch_states->{$sensorkey}->{state} = $switchstate;
        }
        
        # send switch messages every 7 seconds
        if( time > $switchtime) {
            for my $skey ( keys %$switch_states ) {
                my $switchmsg = HiPi::RF::OpenThings::Message->new(
                    cryptseed => ENERGENIE_DEFAULT_CRYPTSEED,
                    mid => $switch_states->{$skey}->{mid},
                    pid => $switch_states->{$skey}->{pid},
                    sid => $switch_states->{$skey}->{sid},
                    pip => ENERGENIE_DEFAULT_CRYPTPIP,
                );
            
                my $newstate = ( $switch_states->{$skey}->{state} ) ? 0 : 1;
                
                $switchmsg->add_record(
                    id      => OPENTHINGS_PARAM_SWITCH_STATE,
                    wr      => 1,
                    typeid  => OPENTHINGS_UINT,
                    value   => $newstate,
                );
                
                $switchmsg->encode_buffer;
                $self->device->send_fsk_message( $switchmsg );
                print qq(\nSent switch message to sensor $skey to set state $newstate\n) if $params{debug};
                $switchmsg->decode_buffer;
                    
                if ( $switchmsg->ok ) {
                    for my $record ( @{ $switchmsg->records } ) {
                        printf(qq(\t%s = %s ( command bit %s)\n), $record->name, $record->value . $record->units, $record->wr || '0') if $params{debug};
                    }
                }
            }
            
            $switchtime = time() + 7;
        }
        
        usleep( 10000 );
    }
}

1;

__END__