#########################################################################################
# Package HiPi::Controller::ENER314_RT
# Description: Control Energenie ENER314-RT board
# Copyright: Copyright (c) 2016 Mark Dootson
# Licence: This work is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or any later
# version.
#########################################################################################
package HiPi::Controller::ENER314_RT;
#########################################################################################
use strict;
use warnings;
use parent qw( HiPi::Controller );
use Carp;
use HiPi::Device::SPI qw( :spi );
use HiPi::Constant qw( :raspberry );
use Time::HiRes qw( usleep );
__PACKAGE__->create_accessors( qw( devicename led_green led_red reset_gpio
led_on _green_pin _red_pin _reset_pin backend repeat ) );
our $VERSION = '0.20';
our @EXPORT = ();
our @EXPORT_OK = ();
our %EXPORT_TAGS = ( all => \@EXPORT_OK );
use constant {
EG_DEFAULT_REPEAT => 15,
EG_SEED_PID => 0x01,
EG_MESSAGE_BUF_SIZE => 66,
EG_MAX_FIFO_SIZE => 66,
EG_ADDR_FIFO => 0x00,
EG_ADDR_OPMODE => 0x01,
EG_ADDR_REGDATAMODUL => 0x02,
EG_ADDR_BITRATEMSB => 0x03,
EG_ADDR_BITRATELSB => 0x04,
EG_ADDR_FDEVMSB => 0x05,
EG_ADDR_FDEVLSB => 0x06,
EG_ADDR_FRMSB => 0x07,
EG_ADDR_FRMID => 0x08,
EG_ADDR_FRLSB => 0x09,
EG_ADDR_AFCCTRL => 0x0B,
EG_ADDR_LNA => 0x18,
EG_ADDR_RXBW => 0x19,
EG_ADDR_AFCFEI => 0x1E,
EG_ADDR_IRQFLAGS1 => 0x27,
EG_ADDR_IRQFLAGS2 => 0x28,
EG_ADDR_RSSITHRESH => 0x29,
EG_ADDR_PREAMBLELSB => 0x2D,
EG_ADDR_SYNCCONFIG => 0x2E,
EG_ADDR_SYNCVALUE1 => 0x2F,
EG_ADDR_SYNCVALUE2 => 0x30,
EG_ADDR_SYNCVALUE3 => 0x31,
EG_ADDR_SYNCVALUE4 => 0x32,
EG_ADDR_PACKETCONFIG1 => 0x37,
EG_ADDR_PAYLOADLEN => 0x38,
EG_ADDR_NODEADDRESS => 0x39,
EG_ADDR_FIFOTHRESH => 0x3C,
EG_MASK_REGDATAMODUL_OOK => 0x08,
EG_MASK_REGDATAMODUL_FSK => 0x00,
EG_MASK_WRITE_DATA => 0x80,
EG_MASK_MODEREADY => 0x80,
EG_MASK_FIFONOTEMPTY => 0x40,
EG_MASK_FIFOLEVEL => 0x20,
EG_MASK_FIFOOVERRUN => 0x10,
EG_MASK_PACKETSENT => 0x08,
EG_MASK_TXREADY => 0x20,
EG_MASK_PACKETMODE => 0x60,
EG_MASK_MODULATION => 0x18,
EG_MASK_PAYLOADRDY => 0x04,
EG_PARAM_JOIN => 0x6A,
EG_PARAM_POWER => 0x70,
EG_PARAM_REACTIVE_P => 0x71,
EG_PARAM_VOLTAGE => 0x76,
EG_PARAM_CURRENT => 0x69,
EG_PARAM_FREQUENCY => 0x66,
EG_PARAM_TEST => 0xAA,
EG_PARAM_SW_STATE => 0x73,
EG_PARAM_CRC => 0x00,
EG_SIZE_MSGLEN => 1,
EG_SIZE_MANUFACT_ID => 1,
EG_SIZE_PRODUCT_ID => 1,
EG_SIZE_ENCRYPTPIP => 2,
EG_SIZE_SENSORID => 3,
EG_SIZE_DATA_PARAMID => 1,
EG_SIZE_DATA_TYPEDESC => 1,
EG_SIZE_CRC => 2,
EG_MODE_STANDBY => 0x04, # Standby
EG_MODE_TRANSMITER => 0x0C, # Transmiter
EG_MODE_RECEIVER => 0x10, # Receiver
EG_VAL_REGDATAMODUL_FSK => 0x00, # Modulation scheme FSK
EG_VAL_REGDATAMODUL_OOK => 0x08, # Modulation scheme OOK
EG_VAL_FDEVMSB30 => 0x01, # frequency deviation 5kHz => 0x0052 -> 30kHz => 0x01EC
EG_VAL_FDEVLSB30 => 0xEC, # frequency deviation 5kHz => 0x0052 -> 30kHz => 0x01EC
EG_VAL_FRMSB434 => 0x6C, # carrier freq -> 434.3MHz => 0x6C9333
EG_VAL_FRMID434 => 0x93, # carrier freq -> 434.3MHz => 0x6C9333
EG_VAL_FRLSB434 => 0x33, # carrier freq -> 434.3MHz => 0x6C9333
EG_VAL_FRMSB433 => 0x6C, # carrier freq -> 433.92MHz => 0x6C7AE1
EG_VAL_FRMID433 => 0x7A, # carrier freq -> 433.92MHz => 0x6C7AE1
EG_VAL_FRLSB433 => 0xE1, # carrier freq -> 433.92MHz => 0x6C7AE1
EG_VAL_AFCCTRLS => 0x00, # standard AFC routine
EG_VAL_AFCCTRLI => 0x20, # improved AFC routine
EG_VAL_LNA50 => 0x08, # LNA input impedance 50 ohms
EG_VAL_LNA50G => 0x0E, # LNA input impedance 50 ohms, LNA gain -> 48db
EG_VAL_LNA200 => 0x88, # LNA input impedance 200 ohms
EG_VAL_RXBW60 => 0x43, # channel filter bandwidth 10kHz -> 60kHz page:26
EG_VAL_RXBW120 => 0x41, # channel filter bandwidth 120kHz
EG_VAL_AFCFEIRX => 0x04, # AFC is performed each time RX mode is entered
EG_VAL_RSSITHRESH220 => 0xDC, # RSSI threshold => 0xE4 -> => 0xDC (220)
EG_VAL_PREAMBLELSB3 => 0x03, # preamble size LSB 3
EG_VAL_PREAMBLELSB5 => 0x05, # preamble size LSB 5
EG_VAL_SYNCCONFIG2 => 0x88, # Size of the Synch word = 2 (SyncSize + 1)
EG_VAL_SYNCCONFIG4 => 0x98, # Size of the Synch word = 4 (SyncSize + 1)
EG_VAL_SYNCVALUE1FSK => 0x2D, # 1st byte of Sync word
EG_VAL_SYNCVALUE2FSK => 0xD4, # 2nd byte of Sync word
EG_VAL_SYNCVALUE1OOK => 0x80, # 1nd byte of Sync word
EG_VAL_PACKETCONFIG1FSK => 0xA2, # Variable length, Manchester coding, Addr must match NodeAddress
EG_VAL_PACKETCONFIG1FSKNO => 0xA0, # Variable length, Manchester coding
EG_VAL_PACKETCONFIG1OOK => 0, # Fixed length, no Manchester coding
EG_VAL_PAYLOADLEN255 => 0xFF, # max Length in RX, not used in Tx
EG_VAL_PAYLOADLEN66 => 66, # max Length in RX, not used in Tx
EG_VAL_PAYLOADLEN_OOK => 13 + 8 * 17, # Payload Length
EG_VAL_NODEADDRESS01 => 0x01, # Node address used in address filtering
EG_VAL_NODEADDRESS04 => 0x04, # Node address used in address filtering
EG_VAL_FIFOTHRESH1 => 0x81, # Condition to start packet transmission: at least one byte in FIFO
EG_VAL_FIFOTHRESH30 => 0x1E, # Condition to start packet transmission: wait for 30 bytes in FIFO
EG_STATE_MSGLEN => 1,
EG_STATE_MANUFACT_ID => 2,
EG_STATE_PRODUCT_ID => 3,
EG_STATE_ENCRYPTPIP => 4,
EG_STATE_SENSORID => 5,
EG_STATE_DATA_PARAMID => 6,
EG_STATE_DATA_TYPEDESC => 7,
EG_STATE_DATA_VAL => 8,
EG_STATE_CRC => 9,
EG_STATE_FINISH => 10,
};
# compound consts
use constant {
EG_NON_CRC => EG_SIZE_MSGLEN + EG_SIZE_MANUFACT_ID + EG_SIZE_PRODUCT_ID + EG_SIZE_ENCRYPTPIP,
EG_SEED_PID_POSITION => EG_SIZE_MSGLEN + EG_SIZE_MANUFACT_ID + EG_SIZE_PRODUCT_ID,
};
# configuration blocks
my $_config_ook = [
[ EG_ADDR_REGDATAMODUL, EG_VAL_REGDATAMODUL_OOK ],# modulation scheme OOK
[ EG_ADDR_FDEVMSB, 0 ], # frequency deviation -> 0kHz
[ EG_ADDR_FDEVLSB, 0 ], # frequency deviation -> 0kHz
[ EG_ADDR_FRMSB, EG_VAL_FRMSB433 ], # carrier freq -> 433.92MHz 0x6C7AE1
[ EG_ADDR_FRMID, EG_VAL_FRMID433 ], # carrier freq -> 433.92MHz 0x6C7AE1
[ EG_ADDR_FRLSB, EG_VAL_FRLSB433 ], # carrier freq -> 433.92MHz 0x6C7AE1
[ EG_ADDR_RXBW, EG_VAL_RXBW120 ], # channel filter bandwidth 120kHz
[ EG_ADDR_BITRATEMSB, 0x40 ], # 1938b/s
[ EG_ADDR_BITRATELSB, 0x80 ], # 1938b/s
#[ EG_ADDR_BITRATEMSB, 0x1A ], # 4800b/s
#[ EG_ADDR_BITRATELSB, 0x0B ], # 4800b/s
[ EG_ADDR_PREAMBLELSB, 0 ], # preamble size LSB 3
[ EG_ADDR_SYNCCONFIG, EG_VAL_SYNCCONFIG4 ], # Size of the Synch word = 4 (SyncSize + 1)
[ EG_ADDR_SYNCVALUE1, EG_VAL_SYNCVALUE1OOK ], # sync value 1
[ EG_ADDR_SYNCVALUE2, 0 ], # sync value 2
[ EG_ADDR_SYNCVALUE3, 0 ], # sync value 3
[ EG_ADDR_SYNCVALUE4, 0 ], # sync value 4
[ EG_ADDR_PACKETCONFIG1, EG_VAL_PACKETCONFIG1OOK ], # Fixed length, no Manchester coding, OOK
[ EG_ADDR_PAYLOADLEN, EG_VAL_PAYLOADLEN_OOK ], # Payload Length
[ EG_ADDR_FIFOTHRESH, EG_VAL_FIFOTHRESH30 ], # Condition to start packet transmission: wait for 30 bytes in FIFO
[ EG_ADDR_OPMODE, EG_MODE_TRANSMITER ], # Transmitter mode
];
my $_config_fsk = [
[ EG_ADDR_REGDATAMODUL, EG_VAL_REGDATAMODUL_FSK ], # modulation scheme FSK
[ EG_ADDR_FDEVMSB, EG_VAL_FDEVMSB30 ], # frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
[ EG_ADDR_FDEVLSB, EG_VAL_FDEVLSB30 ], # frequency deviation 5kHz 0x0052 -> 30kHz 0x01EC
[ EG_ADDR_FRMSB, EG_VAL_FRMSB434 ], # carrier freq -> 434.3MHz 0x6C9333
[ EG_ADDR_FRMID, EG_VAL_FRMID434 ], # carrier freq -> 434.3MHz 0x6C9333
[ EG_ADDR_FRLSB, EG_VAL_FRLSB434 ], # carrier freq -> 434.3MHz 0x6C9333
[ EG_ADDR_AFCCTRL, EG_VAL_AFCCTRLS ], # standard AFC routine
[ EG_ADDR_LNA, EG_VAL_LNA50 ], # 200ohms, gain by AGC loop -> 50ohms
[ EG_ADDR_RXBW, EG_VAL_RXBW60 ], # channel filter bandwidth 10kHz -> 60kHz page:26
[ EG_ADDR_BITRATEMSB, 0x1A ], # 4800b/s
[ EG_ADDR_BITRATELSB, 0x0B ], # 4800b/s
#{ADDR_AFCFEI, VAL_AFCFEIRX}, # AFC is performed each time rx mode is entered
#{ADDR_RSSITHRESH, VAL_RSSITHRESH220 ], # RSSI threshold 0xE4 -> 0xDC (220)
#{ADDR_PREAMBLELSB, VAL_PREAMBLELSB5 ], # preamble size LSB set to 5
[ EG_ADDR_SYNCCONFIG, EG_VAL_SYNCCONFIG2 ], # Size of the Synch word = 2 (SyncSize + 1)
[ EG_ADDR_SYNCVALUE1, EG_VAL_SYNCVALUE1FSK ], # 1st byte of Sync word
[ EG_ADDR_SYNCVALUE2, EG_VAL_SYNCVALUE2FSK ], # 2nd byte of Sync word
#{ADDR_PACKETCONFIG1, VAL_PACKETCONFIG1FSK ], # Variable length, Manchester coding, Addr must match NodeAddress
[ EG_ADDR_PACKETCONFIG1, EG_VAL_PACKETCONFIG1FSKNO ], # Variable length, Manchester coding
[ EG_ADDR_PAYLOADLEN, EG_VAL_PAYLOADLEN66 ], # max Length in RX, not used in Tx
#{ADDR_NODEADDRESS, VAL_NODEADDRESS01}, # Node address used in address filtering
[ EG_ADDR_NODEADDRESS, 0x06 ], # Node address used in address filtering
[ EG_ADDR_FIFOTHRESH, EG_VAL_FIFOTHRESH1 ], # Condition to start packet transmission: at least one byte in FIFO
[ EG_ADDR_OPMODE, EG_MODE_RECEIVER ], # Operating mode to Receiv
];
sub new {
my( $class, %userparams ) = @_;
my %params = (
devicename => '/dev/spidev0.1',
backend => 'spi',
speed => 9600000, # 9.6 mhz
bitsperword => 8,
delay => 0,
device => undef,
led_green => RPI_PIN_13,
led_red => RPI_PIN_15,
led_on => 1,
reset_gpio => RPI_PIN_22,
repeat => EG_DEFAULT_REPEAT,
);
foreach my $key (sort keys(%userparams)) {
$params{$key} = $userparams{$key};
}
unless( defined($params{device}) ) {
if ( $params{backend} && $params{backend} =~ /^spi$/ ) {
require HiPi::Device::SPI;
$params{device} = HiPi::Device::SPI->new(
speed => $params{speed},
bitsperword => $params{bitsperword},
delay => $params{delay},
devicename => $params{devicename},
);
#} elsif ( $params{backend} && $params{backend} eq 'bcm2835') {
# require HiPi::BCM2835::SPI;
# $params{device} = HiPi::BCM2835::SPI->new(
#
# );
} else {
croak q(invalid backend specified);
}
}
my $self = $class->SUPER::new(%params);
# setup defaults
$self->hrf_reset();
# set default mode
$self->hrf_config_fsk();
return $self;
}
sub hrf_config_ook {
my $self = shift;
# write registers
for my $msgref ( @$_config_ook ) {
$self->hrf_reg_write(@$msgref);
}
# wait until ready after mode switch
$self->hrf_wait_for(EG_ADDR_IRQFLAGS1, EG_MASK_MODEREADY, 1);
return;
}
sub hrf_config_fsk {
my $self = shift;
# write registers
for my $msgref ( @$_config_fsk ) {
$self->hrf_reg_write(@$msgref);
}
# wait until ready after mode switch
$self->hrf_wait_for(EG_ADDR_IRQFLAGS1, EG_MASK_MODEREADY, 1);
return;
}
sub hrf_wait_for {
my( $self, $addr, $mask, $true) = @_;
my $counter = 0;
my $maxcount = 4000000;
while ( $counter < $maxcount ) {
my $ret = $self->hrf_reg_read( $addr );
last if( ( $ret & $mask ) == ( $true ? $mask : 0 ) );
$counter ++;
}
if ( $counter >= $maxcount ) {
croak qq(timeout inside wait loop with addr $addr);
}
return;
}
sub hrf_assert_reg_val {
my($self, $addr, $mask, $true, $desc) = @_;
my @data = ( $addr, 0 );
my @buf = unpack('C2', $self->device->transfer( pack('C2', @data) ));
if ($true){
if (($buf[1] & $mask) != $mask) {
croak sprintf("ASSERTION FAILED: addr:%02x, expVal:%02x(mask:%02x) != val:%02x, desc: %s", $addr, $true, $mask, $buf[1], $desc);
}
} else {
if (($buf[1] & $mask) != 0) {
croak sprintf("ASSERTION FAILED: addr:%02x, expVal:%02x(mask:%02x) != val:%02x, desc: %s", $addr, $true, $mask, $buf[1], $desc);
}
}
return;
}
# wrapper for 1 byte writes
sub hrf_reg_write { shift->hrf_reg_write_bytes( @_ ); }
sub hrf_reg_write_bytes {
my( $self, @data ) = @_;
# address is first byte
$data[0] |= EG_MASK_WRITE_DATA;
my $packcount = scalar( @data );
my $packfmt = 'C' . $packcount;
$self->device->bus_transfer( pack($packfmt, @data ) );
return;
}
# wrapper to read 1 byte from a register
sub hrf_reg_read {
my( $self, $addr ) = @_;
my ( $rval ) = $self->hrf_reg_read_bytes($addr, 1);
return $rval;
}
sub hrf_reg_read_bytes {
my( $self, $addr, $numbytes ) = @_;
my $packcount = 1 + $numbytes;
my $packfmt = 'C' . $packcount;
my @data = ( 0 ) x $packcount;
$data[0] = $addr;
my @rvals = unpack($packfmt, $self->device->bus_transfer( pack($packfmt, @data)));
shift @rvals; # get rid of address
return @rvals;
}
sub hrf_make_ook_message {
my($self, $groupid, $data) = @_;
# preamble
my @msgbytes = ( 0x80, 0x00, 0x00, 0x00 );
# encode the group id
push @msgbytes, ( $self->hrf_encode_data( ( $groupid & 0x0f0000 ) >> 16, 4 ) );
push @msgbytes, ( $self->hrf_encode_data( ( $groupid & 0x00ff00 ) >> 8, 8 ) );
push @msgbytes, ( $self->hrf_encode_data( ( $groupid & 0x0000ff ), 8 ) );
# encode the databits
push @msgbytes, ( $self->hrf_encode_data( ( $data & 0x00000f ), 4 ) );
return @msgbytes;
}
sub broadcast_ook_message {
my($self, $groupid, $data, $repeat ) = @_;
$repeat ||= $self->repeat;
$self->hrf_green_led( 1 );
# $groupid = 20 bit controller id for your energenie ENER314-RT - you can vary this
# as you wish so you can control multiple groups of 4 devices each.
# $address is therefore any number between 0x1 and 0xFFFFF
# $data = the 4 bits you want to send as defined in Energenie docs for your switch
# order here is d0,d1,d2,d3 as defined in docs for your device
# therefore data is a number between 0 and 15
# switch to OOK mode
$self->hrf_config_ook;
my $regaddr = 0;
my @sendbytes = $self->hrf_make_ook_message( $groupid, $data );
$self->hrf_wait_for (EG_ADDR_IRQFLAGS1, EG_MASK_MODEREADY | EG_MASK_TXREADY, 1); # wait for ModeReady + TX ready
# send first without sync bytes
$self->hrf_reg_write_bytes($regaddr, @sendbytes[4..15] );
# repeated resend with sync bytes
for (my $i = 0; $i < $repeat; $i++) {
$self->hrf_wait_for(EG_ADDR_IRQFLAGS2, EG_MASK_FIFOLEVEL, 0);
$self->hrf_reg_write_bytes($regaddr, @sendbytes);
}
$self->hrf_wait_for (EG_ADDR_IRQFLAGS2, EG_MASK_PACKETSENT, 1); #wait for Packet sent
$self->hrf_assert_reg_val(EG_ADDR_IRQFLAGS2, EG_MASK_FIFONOTEMPTY | EG_MASK_FIFOOVERRUN, 0, q(are all bytes sent?));
# return to default mode
$self->hrf_config_fsk;
$self->hrf_wait_for (EG_ADDR_IRQFLAGS1, EG_MASK_MODEREADY, 1); # wait for ModeReady
$self->hrf_green_led( 0 );
}
# mask for bit encoding
my @_encoding_mask = ( 0x88, 0x8E, 0xE8, 0xEE );
sub hrf_encode_data {
my($self, $data, $number ) = @_;
my @encoded = ();
my $shift = $number - 2;
while ( $shift >= 0 ) {
my $encindex = ($data >> $shift) & 0x03;
push @encoded, $_encoding_mask[$encindex];
$shift -= 2;
}
return @encoded;
}
sub _get_gpio_pin {
my( $self, $pinid ) = @_;
my $pin;
if ( $self->backend eq 'spi' ) {
require HiPi::Device::GPIO;
my $gpio = HiPi::Device::GPIO->new;
$pin = $gpio->export_pin( $pinid, 'gpio' );
$pin->mode(RPI_PINMODE_OUTP);
$pin->value(0);
}
return $pin;
}
sub get_red_pin {
my $self = shift;
return $self->_red_pin if $self->_red_pin;
my $pin = $self->_get_gpio_pin( $self->led_red );
$self->_red_pin( $pin );
return $self->_red_pin;
}
sub get_green_pin {
my $self = shift;
return $self->_green_pin if $self->_green_pin;
my $pin = $self->_get_gpio_pin( $self->led_green );
$self->_green_pin( $pin );
return $self->_green_pin;
}
sub get_reset_pin {
my $self = shift;
return $self->_reset_pin if $self->_reset_pin;
my $pin = $self->_get_gpio_pin( $self->reset_gpio );
$self->_reset_pin( $pin );
return $self->_reset_pin;
}
sub hrf_red_led {
my ($self, $value) = @_;
return unless $self->led_on;
$self->get_red_pin->value( $value );
return;
}
sub hrf_green_led {
my ($self, $value) = @_;
return unless $self->led_on;
$self->get_green_pin->value( $value );
return;
}
sub hrf_reset {
my $self = shift;
my $pin = $self->get_reset_pin;
$pin->value(1);
usleep( 100000 ); # 0.1 secs
$pin->value(0);
$self->get_red_pin->value( 0 );
$self->get_green_pin->value( 0 );
}
#-------------------------------------------------------
# ENER002 common handlers
#-------------------------------------------------------
sub ener002_pair_socket {
my($self, $groupid, $data, $repeat) = @_;
$self->broadcast_ook_message( $groupid, $data, $repeat );
}
sub ener002_switch_socket {
my($self, $groupid, $data, $repeat) = @_;
$self->broadcast_ook_message( $groupid, $data, $repeat );
}
1;
__END__