#  You may distribute under the terms of either the GNU General Public License
#  or the Artistic License (the same terms as Perl itself)
#
#  (C) Paul Evans, 2015 -- leonerd@leonerd.org.uk

package Device::Chip::Adapter;

use strict;
use warnings;

use utf8;

our $VERSION = '0.02';

use Carp;

=encoding UTF-8

=head1 NAME

C<Device::Chip::Adapter> - an abstraction of a hardware communication device

=head1 DESCRIPTION

This package describes an interfaces tha classes can use to implement a driver
to provide access to some means of connecting an electronic chip or hardware
module to a computer. An instance implementing this interface provides some
means to send electrical signals to a connected chip or module, and receive
replies back from it; this device is called the I<adapter>. This is provided
as a service to some instance of the related interface, L<Device::Chip>.

It is suggested that a driver for a particular adapter provides a concrete
class named within the C<Device::Chip::Adapter> heirarchy, adding the basic
name of the product or means of communication as a suffix; for example the
driver for communication device based on the I<FDTI> range of devices would
be called:

   package Device::Chip::Adapter::FTDI;

This package provides a base class that such a specific implementation class
could use as a superclass, but it is not required to. The important detail is
that it provides the interface described by this documentation.

=cut

=head1 UTILITY CONSTRUCTOR

=cut

=head2 new_from_description

   $adapter = Device::Chip::Adapter->new_from_description( $DESC )

This utility method is provided to allow end-user programs a convenient way to
construct a useable C<Device::Chip::Adapter> instance from a given single
string value. This string takes the form of a the main name of the adapter
class (minus the leading C<Device::Chip::Adapter::> prefix), optionally
followed by a single colon and some comma-separated options. Each option takes
the form of a name and value, separated by equals sign. For example:

   FTDI
   FTDI:
   FTDI:product=0x0601
   BusPirate:serial=/dev/ttyUSB3,baud=57600

This utility method splits off the base name from the optional suffix, and
splits the options into an even-sized name/value list. It loads the class
implied by the base name and invokes a method called C<new_from_description>
on it. This is passed the even-sized name/value list obtained by splitting
the option string. Any option named without a value will be passed having the
value C<1>, as a convenience for options that are simple boolean flags.

If the class does not provide the C<new_from_description> method (and of
course, simply inheriting the base class one from here does not count), then
if there are no other options given, the plain C<new> constructor is invoked
instead. If this is not possible because there are user-specified options that
must be honoured, then an exception is thrown instead.

Note for example that in the case above, the C<product> option would be passed
to the C<FTDI> adapter class still as a string value; it is likely that this
class would want to implement a C<new_from_description> method to parse that
using the C<oct> operator into a plain integer.

It is intended that this method is used for creating an adapter that a
standalone program can use from a description string specified by the user;
likely in a commandline option or environment variable.

=cut

sub new_from_description
{
   shift;
   my ( $description ) = @_;

   my ( $basename, $opts ) = $description =~ m/^([^:]+)(?::(.*))?$/ or
      croak "Malformed adapter description - $description";

   # Not a hash, in case the same option is given more than once
   my @opts = map { m/^([^=]+)=(.*)$/ ? ( $1 => $2 ) : ( $_ => 1 ) }
              split m/,/, $opts // "";

   my $class = "Device::Chip::Adapter::$basename";
   ( my $file = "$class.pm" ) =~ s{::}{/}g;

   require $file;

   my $code = $class->can( "new_from_description" );
   if( $code != \&new_from_description ) {
      return $class->$code( @opts );
   }
   elsif( !@opts ) {
      # Fall back on plain ->new
      return $class->new();
   }

   croak "$class does not provide a ->new_from_description and we cannot fallback on ->new with options";
}

=head1 METHODS

The following methods documented with a trailing call to C<< ->get >> return
L<Future> instances.

=cut

=head2 make_protocol

   $protocol = $adapter->make_protocol( $pname )->get

Returns an object that satisfies one of the interfaces documented below in
L</PROTOCOLS>, depending on the protocol name given by I<$pname>. This should
be one of the following values:

   SPI
   I2C

It is unspecified what class these objects should belong to. In particular, it
is permitted that an adapter could even return itself as the protocol
implementation, provided it has the methods to satisfy the interface for that
particular protocol. This is especially convenient in the case that the
adapter is only capable of one kind of protocol.

=cut

# A default implementation that uses some reflection to simplify
# implementations
sub make_protocol
{
   my $self = shift;
   my ( $pname ) = @_;

   if( my $code = $self->can( "make_protocol_$pname" ) ) {
      return $code->( $self );
   }
   else {
      croak "Unrecognised protocol name $pname";
   }
}

=head2 shutdown

   $adapter->shutdown

Shuts down the adapter in whatever manner is appropriate at the end of the
lifetime of the containing program; or at least, at the point when the program
has finished using the connected device.

This method is allowed to block; it does not yield a L<Future>. It is suitable
to call from a C<DESTROY> method or C<END> block.

=cut

=head1 PROTOCOLS

The following methods are common to all protocol instances:

=head2 configure

   $protocol->configure( %args )->get

Sets configuration options for the protocol. The actual set of options
available will depend on the type of the protocol.

Chip drivers should attempt to bundle their changes together into as few
C<configure> calls as possible, because adapters may find it most efficient to
apply multiple changes in one go.

=head2 power

   $protocol->power( $on )->get

Switches on or off the power to the actual chip or module, if such ability is
provided by the adapter.

=cut

=head1 SPI PROTOCOL

=head2 Configuration Options

The following configuration options are recognised:

=over 4

=item mode => 0 | 1 | 2 | 3

The numbered SPI mode used to communicate with the chip.

=item max_bitrate => INT

The highest speed, in bits per second, that the chip can accept. The adapter
must pick a rate that is no higher than this. Note specifically that not all
adapters are able to choose a rate arbitrarily, and so the actually
communication may happen at some rate slower than this.

=back

=head2 readwrite

   $bytes_in = $spi->readwrite( $bytes_out )->get

Performs a complete SPI transaction; synchronously clocking the data given by
the plain byte string I<$bytes_out> out of the MOSI pin of the adapter, while
simultaneously capturing the data coming in to the MISO pin, which is
eventually returned as the result of the returned future.

=head2 write

   $spi->write( $bytes )->get

A variant of C<readwrite> where the caller does not intend to make use of the
data returned by the device, and so the adapter does not need to return it.
This may or may not make a material difference to the actual communication
with the adapter or device; it could be implemented simply by calling the
C<readwrite> method and ignoring the return value.

=cut

=head1 I2C PROTOCOL

=head2 Configuration Options

The following configuration options are recognised:

=over 4

=item addr => INT

The (7-bit) slave address for the chip this protocol is communicating with.

=item max_bitrate => INT

The highest speed, in bits per second, that the chip can accept. The adapter
must pick a rate that is no higher than this. Note specifically that not all
adapters are able to choose a rate arbitrarily, and so the actually
communication may happen at some rate slower than this.

=back

=head2 write

    $i2c->write( $bytes_out )->get

Performs a complete I²C transaction to send the given bytes to the slave chip.
This includes the start condition, sending the addressing byte (which is
implied; it should I<not> be included in C<$bytes_out>) and ending in the stop
condition.

=head2 write_then_read

    $bytes_in = $i2c->write_then_read( $bytes_out, $len_in )->get

Performs a complete I²C transaction to first send the given bytes to the slave
chip then reads the give number of bytes back, returning them. These two
operations must be performed within a single I²C transaction using a repeated
start condition.

=cut

=head1 AUTHOR

Paul Evans <leonerd@leonerd.org.uk>

=cut

0x55AA;
