Group
Extension

Lab-Measurement/lib/Lab/Moose/Instrument/Bluefors_Temp.pm

package Lab::Moose::Instrument::Bluefors_Temp;
$Lab::Moose::Instrument::Bluefors_Temp::VERSION = '3.931';
#ABSTRACT: Bluefors temperature control

use v5.20;

use strict;
use Moose;
use Moose::Util::TypeConstraints qw/enum/;
use MooseX::Params::Validate;
use Lab::Moose::Instrument qw/
    validated_getter
    validated_setter
    validated_no_param_setter
    setter_params
    /;
use Carp;
use namespace::autoclean;
use Time::HiRes qw/time usleep/;

# HTTP request JSON stuff
use DateTime;
use JSON::PP;
use Data::Dumper;


extends 'Lab::Moose::Instrument';

has default_channel => (
    is  => 'ro',
    isa => 'Num',
    default => 1,
);

has default_heater => (
    is  => 'ro',
    isa => 'Num',
    default => 1,
);

has default_time => (
    is  => 'ro',
    isa => 'Num',
    default => 90,
);

has json => (
    is => 'ro',
    isa => 'Any',
    builder => '_build_json',
);
sub _build_json {
    return JSON::PP->new;
}

around default_connection_options => sub {
	my $orig = shift;
	my $self = shift;
	my $options = $self->$orig();

	$options->{HTTP}{port} = 5001;
	return $options;
};


sub get_measurement {
    my ( $self, %args ) = validated_hash(
        \@_,
        channel_nr => { isa => enum([ (1..12) ]) },
        time => { isa => 'Lab::Moose::PosNum' },
        fields => { isa => 'ArrayRef' },
    );
    my $channel_nr = $args{'channel_nr'};
    my $time = $args{'time'};
    my $fields = $args{'fields'};

    my $time_stop  = DateTime->now."Z"; 
    my $time_start = DateTime->from_epoch(epoch => time()-$time)."Z";

    my %hash_json = ( 'channel_nr'  => $channel_nr,
                      'start_time' => $time_start,
                      'stop_time'  => $time_stop,
                      'fields'     => $fields,
                    );
    my $json = $self->json->encode(\%hash_json);

    my $endpoint = '/channel/historical-data';

    my $response = $self->write( endpoint =>  $endpoint, body => $json);
    my $hashref = $self->json->decode($response->content);

    return $hashref->{'measurements'};
}


sub get_heater_measurement {
    my ( $self, %args ) = validated_hash(
        \@_,
        heater_nr => { isa => enum([ (1..4) ])},
        time => { isa => 'Lab::Moose::PosNum' },
        fields => { isa => 'ArrayRef' },
    );
    my $heater_nr = delete $args{'heater_nr'};
    my $time      = delete $args{'time'};
    my $fields    = delete $args{'fields'};

    my $time_stop  = DateTime->now."Z"; 
    my $time_start = DateTime->from_epoch(epoch => time()-$time)."Z";

    my %hash_json = ( 'heater_nr'  => $heater_nr,
                      'start_time' => $time_start,
                      'stop_time'  => $time_stop,
                      'fields'     => $fields,
                    );
    my $json = $self->json->encode(\%hash_json);

    my $endpoint = '/heater/historical-data';

    my $response = $self->write( endpoint =>  $endpoint, body => $json);
    my $hashref = $self->json->decode($response->content);

    return $hashref->{'measurements'};
}


sub set_heater {
    my ($self, %args) = validated_hash(
        \@_,
        heater_nr                   => { isa => enum([ (1..4) ])                  },
        active                      => { isa => enum( [qw/0 1/]),   optional => 1 },
        pid_mode                    => { isa => enum( [qw/0 1/]),   optional => 1 },
        resistance                  => { isa => 'Num',              optional => 1 },
        power                       => { isa => 'Num',              optional => 1 },
        target_temperature          => { isa => 'Num',              optional => 1 },
        control_algorithm_settings  => { isa => 'HashRef',          optional => 1 },
        setpoint                    => { isa => 'Num',              optional => 1 }, 
    );
    if (exists $args{'active'} and defined $args{'active'}) {
        if ( $args{"active"} eq 1 ) {
            $args{"active"} = JSON::PP::true;
        } else {
            $args{"active"} = JSON::PP::false;
        }
    }
    my $json = $self->json->encode(\%args);

    my $endpoint = '/heater/update';
    my $response = $self->write( endpoint =>  $endpoint, body => $json);
    my $hashref = $self->json->decode($response->content);

    return $hashref;
}


sub set_channel {
    my ($self, %args) = validated_hash(
        \@_,
        channel_nr => { isa => enum([ (1..12) ]) },
        active     => { isa => enum( [qw/0 1/] ), optional => 1 },
        excitation_mode       => { isa => enum( [qw/0 1 2/] ), optional => 1 },
        excitation_current_range => { isa => enum([ (1..22) ]), optional => 1 },
        excitation_cmn_range  => { isa => enum( [qw/1 2/] ), optional => 1 },
        excitation_vmax_range => { isa => enum( [qw/1 2/] ), optional => 1 },
        use_non_default_timeconstants => { isa => enum( [qw/0 1/] ), optional => 1 },
        wait_time  => { isa => 'Num', optional => 1 },
        meas_time  => { isa => 'Num', optional => 1 },
    );
    if (exists $args{'active'} and defined $args{'active'}) {
        if ( $args{"active"} eq 1 ) {
            $args{"active"} = JSON::PP::true;
        } else {
            $args{"active"} = JSON::PP::false;
        }
    }
    my $json = $self->json->encode(\%args);

    my $endpoint = '/channel/update';
    my $response = $self->write( endpoint =>  $endpoint, body => $json);
    my $hashref = $self->json->decode($response->content);

    return $hashref;
}


sub set_statemachine {
    my ($self, %args) = validated_hash(
        \@_,
        wait_time => { isa => enum([ (1..100) ]) },
        meas_time => { isa => enum([ (1..100) ]) },
    );

    my $json = $self->json->encode(\%args);

    my $endpoint = '/statemachine/update';
    my $response = $self->write( endpoint =>  $endpoint, body => $json);
    my $hashref = $self->json->decode($response->content);

    return $hashref;
}


sub idn {
    my $self = shift;

    my $endpoint = '/system';
    my $response = $self->read( endpoint => $endpoint );
    my $hashref = $self->json->decode($response->content);

    my $ret = '';
    $ret = $ret . $hashref->{api_version} . ":";
    $ret = $ret . $hashref->{type} . ":";
    $ret = $ret . $hashref->{serial} . ":";
    $ret = $ret . $hashref->{label} . ":";
    $ret = $ret . $hashref->{addinfo} . ":";
    $ret = $ret . $hashref->{software_version};

    return $ret;
}


sub set_network_config {
    my ($self, %args) = validated_hash(
        \@_,
        ip_configuration => { isa => 'Str' },
        ip_address       => { isa => 'Str', optional => 1 },
        netmask          => { isa => 'Str', optional => 1 },
    );
    my $json = $self->json->encode(\%args);
    say $json;

    my $endpoint = '/system/network/update';
    my $response = $self->write( endpoint => $endpoint, body => $json);
    my $hashref = $self->json->decode($response->content);

    return $hashref;
}


sub set_channel_heater_relation {
        my ($self, %args) = validated_hash(
        \@_,
        channel_nr => { isa => enum([ (0..12) ]) },
        heater_nr  => { isa => enum([ (0..4)  ]) },
    );
    my $json = $self->json->encode(\%args);

    my $endpoint = '/channel/heater/update';
    my $response = $self->write( endpoint => $endpoint, body => $json);
    my $hashref = $self->json->decode($response->content);

    return $hashref;
}


# High level functions for sweeps


sub get_T {
    my ($self, %args) = validated_hash(
        \@_,
        channel_nr => { isa => enum([ (1..12) ]), optional => 1 },
    );

    my $channel;
    if( exists $args{'channel_nr'} and defined $args{'channel_nr'} ) {
        $channel = delete $args{'channel_nr'};
    } else {
        $channel = $self->default_channel;
    }

    my $response = $self->get_measurement( channel_nr => $channel,
                            time => $self->default_time,
                            fields => [ "temperature" ] );
    return $response->{'temperature'}->[-1];

}


sub set_T {
    my ($self, %args) = validated_hash(
        \@_,
        value => { isa => 'Num' },
        heater_nr => { isa => enum([ (1..4) ]), optional => 1 },
    );

    my $heater;
    if( exists $args{'heater_nr'} and defined $args{'heater_nr'} ) {
        $heater = delete $args{'heater_nr'};
    } else {
        $heater = $self->default_heater;
    }
    my $value = delete $args{'value'};

    return $self->set_heater( heater_nr => $heater, active => 1, setpoint => $value );
}

__PACKAGE__->meta->make_immutable;

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Lab::Moose::Instrument::Bluefors_Temp - Bluefors temperature control

=head1 VERSION

version 3.931

=head1 SYNOPSIS

 use Lab::Moose;

 # Constructor
 my $tc = instrument(
     type            => 'Bluefors_Temp',
     connection_type => 'HTTP',
     connection_options => {
         ip => '192.32.14.24',
     },
 );

=head1 METHODS

=head2 get_measurement

 my $measurement = $tc->get_measurement( channel_nr => (1..12), time => 60, fields => [ 'temperature', 'resistance' ] );

The get_measurement() function returns the measurements of a specific channel in the specified time.
The option "time" controls how far back the measurements are fetched from the device. 
In the example "time => 60" gets the measurements done in the last 60 seconds.
The last option "fields" controls which measurements are fetched. A full list can be found in the API reference.

This function returns a hash reference with array references of all measurements.
The latest temperature measurement can be retrieved using $measurement->{'temperature'}[-1].

=head2 get_heater_measurement

 my $heater_meas = $tc->get_heater_measurement( heater_nr => (1..4), time => 60, fields => [ 'power', 'current' ] );

The get_heater_measurement() function returns the measurements of a specific heater.
It works the same as the get_measurement() function.

=head2 set_heater

 $tc->set_heater( heater_nr => (1..4), active => 1, power => 0.03, ... );

The set_heater() function controls the settings of a specific heater.
Supported options are:
 - heater_nr
 - active                    
 - pid_mode                  
 - resistance                
 - power                     
 - target_temperature        
 - control_algorithm_settings
 - setpoint

=head2 set_channel

 $tc->set_channel( channel_nr => (1..12), active => 1, ... );

The set_channel() function controls the settings of a specific channel.
Supported options are:
  - channel_nr
  - active
  - excitation_mode
  - excitation_current_range
  - excitation_cmn_range
  - excitation_vmax_range
  - use_non_default_timeconstants
  - wait_time
  - meas_time

=head2 set_statemachine

 my $state = $tc->set_statemachine( wait_time => 2, meas_time => 2 );

The set_statemachine() function controls the wait time after changing channel
and the measurement time. Both values are given in seconds.
The response is a hash reference with all information about the statemachine
described in the API reference.

=head2 idn

 say $tc->idn();

Returns all available system information concatenated as a string.

=head2 set_network_config

 my $configuration = $tc->set_network_config( ip_configuration => 'static', ip_address => '192.168.0.12' );

Supported options:
 - ip_configuration
 - ip_address      
 - netmask

=head2 set_channel_heater_relation

 my $relation = $tc->set_channel_heater_relation( channel_nr => 8, heater_nr => 2 );

Updates the relation between a channel and a heater.

=head2 get_T

 my $temp = $tc->get_T();

Returns the latest temperature measurement of the default channel using the default time parameter.
High level function for the built-in sweep environment.
Use the get_measurement function for all other use cases.

=head2 set_T

 $tc->set_T( value => 1.2 );

Sets the target temperature for the default heater and sets the active state to ON.
High level function for the in-built sweep environment.
Use the set_heater function for all other use cases.

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2025 by the Lab::Measurement team; in detail:

  Copyright 2023       Andreas K. Huettel, Mia Schambeck
            2024       Ed J


This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.