#########################################################################################
# Package        HiPi::Interface::LCDBackpackPCF8574
# Description  : PCF8574 Backpack LCD Controller
# Copyright    : Copyright (c) 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::Interface::LCDBackpackPCF8574;

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

use strict;
use warnings;
use parent qw( HiPi::Interface::Common::HD44780 );
use Carp;
use HiPi qw( :rpi :lcd :i2c );
use HiPi::RaspberryPi;
use HiPi::Interface::PCF8574;

our $VERSION ='0.74';

__PACKAGE__->create_accessors( qw( address devicename backend _backlight ) );

use constant {
    LCD_CLEARDISPLAY => 0x01,
    LCD_RETURNHOME => 0x02,
    LCD_ENTRYMODESET => 0x04,
    LCD_DISPLAYCONTROL => 0x08,
    LCD_CURSORSHIFT => 0x10,
    LCD_FUNCTIONSET => 0x20,
    LCD_SETCGRAMADDR => 0x40,
    LCD_SETDDRAMADDR => 0x80,

    ##  for display entry mode
    LCD_ENTRYRIGHT => 0x00,
    LCD_ENTRYLEFT => 0x02,
    LCD_ENTRYSHIFTINCREMENT => 0x01,
    LCD_ENTRYSHIFTDECREMENT => 0x00,

    ##  for display on/off control
    LCD_DISPLAYON => 0x04,
    LCD_DISPLAYOFF => 0x00,
    LCD_CURSORON => 0x02,
    LCD_CURSOROFF => 0x00,
    LCD_BLINKON => 0x01,
    LCD_BLINKOFF => 0x00,

    ##  for display/cursor shift
    LCD_DISPLAYMOVE => 0x08,
    LCD_CURSORMOVE => 0x00,
    LCD_MOVERIGHT => 0x04,
    LCD_MOVELEFT => 0x00,

    ##  for function set
    LCD_8BITMODE => 0x10,
    LCD_4BITMODE => 0x00,
    LCD_2LINE => 0x08,
    LCD_1LINE => 0x00,
    LCD_5x10DOTS => 0x04,
    LCD_5x8DOTS => 0x00,
    
    ##
    SEND_MODE_CMD  => 0,
    SEND_MODE_DATA => 1,
    SEND_ENABLE    => 4,
    SEND_DISABLE   => 0,
    SEND_BACKLIGHT => 8,
};

sub new {
    my( $class, %userparams)  = @_;
    	
    my $pi = HiPi::RaspberryPi->new();
    
    my %params = (
        # LCD
        width            =>  undef,
        lines            =>  undef,
        backlightcontrol =>  1,
        device           =>  undef,
        positionmap      =>  undef,
        serialbuffermode =>  0,
        
        # i2c params
        address           => 0x3F,
	    devicename    => ( $pi->board_type == RPI_BOARD_TYPE_1 ) ? '/dev/i2c-0' : '/dev/i2c-1',
        backend       => 'i2c',
    );
    
    # get user params
    foreach my $key( keys (%userparams) ) {
        $params{$key} = $userparams{$key};
    }
	
    unless( defined($params{device}) ) {
        $params{device} = HiPi::Interface::PCF8574->new(
            devicename   => $params{devicename},
            address      => $params{address},
            backend      => $params{backend},
        );
    }
    
    my $self = $class->SUPER::new(%params);
    return $self;
}

sub send_text {
    my($self, $text) = @_;
    for my $p ( split(//, $text) ) {
        $self->_send_data( ord($p) );
    }
}

sub send_command {
    my($self, $command) = @_;
    my $lsb = $command & 0x0F;
    my $msb = ( $command >> 4 ) & 0x0F;
    $self->_write_command_part($msb);
    $self->_write_command_part($lsb);
}

sub _send_data {
    my($self, $data) = @_;
    my $lsb = $data & 0x0F;
    my $msb = ( $data >> 4 ) & 0x0F;
    $self->_write_data_part($msb);
    $self->_write_data_part($lsb);
}

sub backlight {
    my($self, $brightness) = @_;
    
    $brightness = 0 if $brightness < 0;
    $brightness = 100 if $brightness > 100;
    
    # $brightness = 0 to 100
    # we translate to 0 - 250
    
    return unless $self->backlightcontrol;
    
    my $bset = int( 2.5 * $brightness);
    $self->_backlight( $bset );
    $self->_write_to_bus(0x00, SEND_MODE_DATA, SEND_DISABLE );
}

sub update_baudrate {
    my $self = shift;
    # not handled
    return;
}

sub update_geometry {
    my $self = shift;
    # not handled
    return;
}

sub _write_command_part {
    my($self, $data) = @_;
    $self->_write_to_bus( $data, SEND_MODE_CMD, SEND_ENABLE );
    $self->delayMicroseconds(1);
    $self->_write_to_bus( $data, SEND_MODE_CMD, SEND_DISABLE );
    $self->delayMicroseconds(40);
}

sub _write_data_part {
    my($self, $data) = @_;
    $self->_write_to_bus( $data, SEND_MODE_DATA, SEND_ENABLE );
    $self->delayMicroseconds(1);
    $self->_write_to_bus( $data, SEND_MODE_DATA, SEND_DISABLE );
    $self->delayMicroseconds(40);
}

sub _write_to_bus {
    my($self, $data, $mode, $onoff) = @_;
    my $byte = $data << 4;
    $byte |= ( $mode & 1 );
    $byte |= SEND_ENABLE if $onoff;
    $byte |= SEND_BACKLIGHT if $self->_backlight;
    $self->device->write_byte( $byte ); 
}

sub init_display {
    my $self = shift;
    
    $self->_write_to_bus(0, 0, 0);
    $self->delay(50); 
    
    # put the LCD into 4 bit mode according to the hitachi HD44780 datasheet figure 26, pg 47
    $self->_write_command_part(0x03);
    $self->delayMicroseconds(4500); 
    $self->_write_command_part(0x03);
    $self->delayMicroseconds(4500); 
    $self->_write_command_part(0x03);
    $self->delayMicroseconds(150); 
    $self->_write_command_part(0x02);
    
    $self->send_command( HD44780_CURSOR_OFF );
}

1;

__END__
