NAME

Lab::Moose::Developer - Developer tutorial.

Writing drivers

The driver infrastructure is based on Moose. For Moose basics see e.g.

  • The Modern Perl book by chromatic, available online for free, contains a chapter on Moose.

  • For full details, see the very well written Moose::Manual.

Code example

We start with a section of code from the Lab::Moose::Instrument::SR830 LIA driver and discuss the most important steps. This explains the most important parts of a driver, i.e. initialization, getters, setters, caching, ...

package Lab::Moose::Instrument::SR830;

use 5.010;
use Moose;
use MooseX::Params::Validate;
use Lab::Moose::Instrument qw/validated_getter validated_setter/;
use Lab::Moose::Instrument::Cache;
use Carp;
use namespace::autoclean;

our $VERSION = '3.520';

# (1)
extends 'Lab::Moose::Instrument';

# (2)
with qw/Lab::Moose::Instrument::Common/;

# (3)
sub BUILD {
    my $self = shift;
    $self->clear();
    $self->cls();
}

# (4)
cache amplitude => (getter => 'get_amplitude');

# (5)
sub get_amplitude {
    my ($self, %args) = validated_getter(\@_);

    # (6)
    return $self->cached_amplitude(
        $self->query( command => 'SLVL?', %args ) );
}

sub set_amplitude {
    # (7)
    my ( $self, $value, %args ) = validated_setter(
        \@_,
        value => { isa => 'Num' }
    );
    
    # (8)
    $self->write( command => "SLVL $value", %args );
    $self->cached_amplitude($value);
}

# (9)
__PACKAGE__->meta()->make_immutable();
1;

Explanations

  • extends 'Lab::Moose::Instrument';

    All drivers inherit from Lab::Moose::Instrument. This base class provides the access to the underlying connection via methods like write, query and clear.

  • with qw/Lab::Moose::Instrument::Common/;

    The Lab::Moose::Instrument::Common role contains methods for GPIB common commands like *IDN, *RST, *CLS, ...

  • sub BUILD {
        my $self = shift;
        $self->clear();
        $self->cls();
    }

    This BUILD method (see Moose::Manual::Construction) executes a device clear and clears the error status.

  • cache amplitude => (getter => 'get_amplitude');

    Accessing instruments settings by querying them from the device can be slow. It is mutch faster if the setters and getters store the setting in an object attribute (See Moose::Manual::Attributes). This line creates an attribute for storing the amplitude value of the LIA's reference output.

    The attribute can then be accessed with the cached_amplitude method.

    If the getter is called while the attributes is unset, the get_amplitude method will be called under the hood to initialize the attribute.

    What if the initialization should call the get_amplitude method with additional arguments, say timeout => 10? This can be done by overwriting the builder method of the attribute in your driver:

    sub cached_amplitude_builder {
        my $self = shift;
        return $self->get_amplitude( timeout => 10 );
    }

    See Lab::Moose::Instrument::Cache for full details.

  • sub get_amplitude {
        my ($self, %args) = validated_getter(\@_);

    We come to the getter function, which queries the amplitude from the LIA. The validated_getter function (from Lab::Moose::Instrument) allows the user to pass additional options, like a timeout, to the underlying connection:

    my $amplitude = $self->get_amplitude(timeout => 10);
    my $amplitude = $self->get_amplitude(); # Use default timeout.
  • return $self->cached_amplitude(
            $self->query( command => 'SLVL?', %args ) );

    We read the amplitude from the instrument and store it's value into the cache. Don't forget the %args argument to query!

  • sub set_amplitude {
        my ( $self, $value, %args ) = validated_setter(
            \@_,
            value => { isa => 'Num' }
        );

    The validated_setter function parses the arguments of the setter method. We require that the value argument is a number.

  • $self->write( command => "SLVL $value", %args );
    $self->cached_amplitude($value);

    We pass the new amplitude to the instrument and update the cache.

  • __PACKAGE__->meta()->make_immutable();

    Every Moose class shell end this way. See Moose::Manual::BestPractices.

Building automated instrument tests

Lab::Moose has extensive support to build automated tests for instrument drivers and this is what enables long-term maintainability of the code base.

If you are new to automated testing with Perl, consider the Modern Perl book, which covers all of the basics.

A test file t/Moose/Instrument/SR830.t for the above driver could look like this:

#!perl

use warnings;
use strict;

use lib 't';

use Lab::Test import => [qw/set_get_test/];
use Moose::Instrument::MockTest qw/mock_instrument/;
use MooseX::Params::Validate;

use File::Spec::Functions 'catfile';
my $log_file = catfile(qw/t Moose Instrument SR830.yml/);

my $lia = mock_instrument(
    type     => 'SR830',
    log_file => $log_file,
);

isa_ok( $lia, 'Lab::Moose::Instrument::SR830' );

$lia->rst( timeout => 10 );

# Test the amplitude getter and setter
set_get_test(
    instr  => $lia,
    values => [qw/0.004 1 2 3 5/],
    getter => 'get_amplitude',
    setter => 'set_amplitude',
    cache  => 'cached_amplitude',
);

$lia->rst();
done_testing();

First thing, we need to run the test with the real instrument, to create the connection log file t/Moose/Instrument/SR830.yml:

$ perl -Ilib t/Moose/Instrument/SR830.t --connection=LinuxGPIB --connection_options='{pad: 1}'

where the argument of --connection_options is given as a YAML hash.

You can always print a help screen of command line options supported by the test:

$ perl -Ilib t/Moose/Instrument/SR830.t --help

Now with the connection log in place, you can run the test without the real instrument connected:

$ prove -lv t/Moose/Instrument/SR830.t

If set_get_test is not enough for you, take a look at the other test routines in t/Lab/Test.pm which offer

  • Floating point number comparison (absolute error, relative error, ...)

  • File comparison, including basic filtering options.

Roles for SCPI instruments

TODO

Writing connections

TODO

Data files and plotting

TODO