The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#########################################################################################
# Package        HiPi::Interface::PCF8574
# Description  : Control NXP PCF8574 8-channel port extender
# Copyright    : Copyright (c) 2013-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::PCF8574;

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

use strict;
use warnings;
use parent qw( HiPi::Interface );
use HiPi qw( :i2c :rpi  );
use Carp;
use Time::HiRes qw( usleep );

__PACKAGE__->create_ro_accessors( qw(
    backend
) );

our $VERSION ='0.68';


sub new {
    my ($class, %userparams) = @_;
    
    my $pi = HiPi::RaspberryPi->new();
    
    my %params = (
        devicename      => ( $pi->board_type == RPI_BOARD_TYPE_1 ) ? '/dev/i2c-0' : '/dev/i2c-1',
        address         => 0x3f,
        device          => undef,
        backend         => 'i2c',
    );
    
    # get user params
    foreach my $key( keys (%userparams) ) {
        $params{$key} = $userparams{$key};
    }
        
    unless( defined($params{device}) ) {
        if ( $params{backend} eq 'bcm2835' ) {
            require HiPi::BCM2835::I2C;
            $params{device} = HiPi::BCM2835::I2C->new(
                address    => $params{address},
                peripheral => ( $params{devicename} eq '/dev/i2c-0' ) ? HiPi::BCM2835::I2C::BB_I2C_PERI_0() : HiPi::BCM2835::I2C::BB_I2C_PERI_1(),
            );
        } else {
            require HiPi::Device::I2C;
            $params{device} = HiPi::Device::I2C->new(
                devicename  => $params{devicename},
                address     => $params{address},
                busmode     => $params{backend},
            );
        }
    }
    
    my $self = $class->SUPER::new(%params);
    
    return $self;
}

sub read_byte {
    my ( $self ) = @_;
    my @bytes = $self->device->bus_read(undef, 1);
    return $bytes[0];
}

sub write_byte {
    my( $self, $byte) = @_;
    $self->device->bus_write($byte & 0xFF);
}

sub read_bits {
    my ( $self ) = @_;
    my $byte = $self->read_byte;
    my @bits;
    for ( my $i = 0; $i < 8; $i++ ) {
        push @bits, ( $byte >> $i ) & 1; 
    }
    return @bits;
}

sub write_bits {
    my( $self, @bits) = @_;
    my $bitcount = @bits;
    unless( $bitcount == 8 ) {
        warn qq(Only $bitcount bits provided in write_bits. Needs 8);
        return;
    }
    my $byte = 0;
    for ( my $i = 0; $i < 8; $i++ ) {
        $byte += ($bits[$i] & 1 ) << $i;
    }
    $self->write_byte( $byte );
}

sub set_bit {
    my( $self, $bit, $value) = @_;
    if($bit < 0 || $bit > 7) {
        warn qq(Bit argument must be between 0 and 7 in set_bit. You passed $bit);
        return;
    }
    
    if($value < 0 || $value > 1) {
        warn qq(Value argument muist be 1 or 0 in set_bit. You passed $value);
        return;
    }
    
    my @bits = $self->read_bits;
    $bits[$bit] = $value;
    $self->write_bits(@bits);
}

sub get_bit {
    my( $self, $bit ) = @_;
    if($bit < 0 || $bit > 7) {
        warn qq(Bit argument must be between 0 and 7 in get_bit. You passed $bit);
        return;
    }
    my @bits = $self->read_bits;
    return $bits[$bit];
}

sub set_port { set_bit( @_ ); }

sub get_port { get_bit( @_ ); }


1;

__END__