File Coverage

blib/lib/Lab/Moose/Instrument/OI_ITC503.pm
Criterion Covered Total %
statement 26 191 13.6
branch 0 24 0.0
condition 0 21 0.0
subroutine 9 37 24.3
pod 15 28 53.5
total 50 301 16.6


line stmt bran cond sub pod time code
1             package Lab::Moose::Instrument::OI_ITC503;
2             $Lab::Moose::Instrument::OI_ITC503::VERSION = '3.881';
3             #ABSTRACT: Oxford Instruments ITC503 Intelligent Temperature Control
4              
5 1     1   2299 use v5.20;
  1         5  
6              
7 1     1   7 use Moose;
  1         3  
  1         9  
8 1     1   7433 use Moose::Util::TypeConstraints qw/enum/;
  1         2  
  1         12  
9 1     1   528 use MooseX::Params::Validate;
  1         4  
  1         11  
10 1         69 use Lab::Moose::Instrument qw/
11 1     1   537 validated_getter validated_setter setter_params /;
  1         3  
12 1     1   6 use Lab::Moose::Instrument::Cache;
  1         4  
  1         9  
13 1     1   709 use Lab::Moose::Countdown 'countdown';
  1         3  
  1         48  
14 1     1   9 use Carp;
  1         2  
  1         52  
15 1     1   8 use namespace::autoclean;
  1         2  
  1         9  
16              
17             extends 'Lab::Moose::Instrument';
18              
19             has empty_buffer_count =>
20             ( is => 'ro', isa => 'Lab::Moose::PosInt', default => 1 );
21             has auto_pid => ( is => 'ro', isa => 'Bool', default => 1 );
22              
23             has high_temp_sensor =>
24             ( is => 'ro', isa => enum( [qw/1 2 3/] ), default => 3 );
25             has low_temp_sensor =>
26             ( is => 'ro', isa => enum( [qw/1 2 3/] ), default => 2 );
27              
28             # currently used sensor
29             has t_sensor => ( is => 'rw', isa => enum( [qw/1 2 3/] ), default => 3 );
30              
31             # most function names should be backwards compatible with the
32             # Lab::Instrument::OI_ITC503 driver
33              
34             sub BUILD {
35 0     0 0   my $self = shift;
36              
37 0           warn "The ITC driver is work in progress. You have been warned\n";
38              
39             # Unlike modern GPIB equipment, this device does not assert the EOI
40             # at end of message. The controller shell stop reading when receiving the
41             # eos byte.
42              
43 0           $self->connection->set_termchar( termchar => "\r" );
44 0           $self->connection->enable_read_termchar();
45 0           $self->clear();
46              
47             # Dont clear the instrument since that may make it unresponsive.
48             # Instead, set the communication protocol to "Normal", which should
49             # also clear all communication buffers.
50 0           $self->write( command => "Q0\r" ); # why not use set_control ???
51 0           $self->set_control( value => 3 );
52              
53 0 0         if ( $self->auto_pid ) {
54 0           warn "setting PID to AUTO\n";
55 0           $self->itc_set_PID_auto( value => 1 );
56             }
57              
58             }
59              
60              
61             # query wrapper with error checking
62             around query => sub {
63             my $orig = shift;
64             my $self = shift;
65             my %args = @_;
66              
67             my $result = $self->$orig(@_);
68              
69             chomp $result;
70             my $cmd = $args{command};
71             my $cmd_char = substr( $cmd, 0, 1 );
72              
73             # ITC query answers always start with the command character
74             # if successful with a question mark and the command char on failure
75             my $status = substr( $result, 0, 1 );
76             if ( $status eq '?' ) {
77             croak "ITC503 returned error '$result' on command '$cmd'";
78             }
79             elsif ( defined $cmd_char and ( $status ne $cmd_char ) ) {
80             croak
81             "ITC503 returned unexpected answer. Expected '$cmd_char' prefix,
82             received '$status' on command '$cmd'";
83             }
84             return substr( $result, 1 );
85             };
86              
87              
88             sub set_control {
89 0     0 1   my ( $self, $value, %args ) = validated_setter(
90             \@_,
91             value => { isa => enum( [qw/0 1 2 3/] ) },
92             );
93 0           my $result = $self->query( command => "C$value\r", %args );
94 0           sleep(1);
95 0           return $result;
96             }
97              
98              
99             sub itc_set_communications_protocol {
100 0     0 1   my ( $self, $value, %args ) = validated_setter(
101             \@_,
102             value => { isa => enum( [qw/0 2/] ) }
103             );
104 0           return $self->query( command => "Q$value\r" );
105             }
106              
107             # For Lab::Moose::Sweep interface
108             sub set_T {
109 0     0 0   my $self = shift;
110 0           $self->itc_set_T(@_);
111             }
112              
113              
114             sub itc_set_T {
115 0     0 1   my ( $self, $value, %args ) = validated_setter(
116             \@_,
117             value => { isa => 'Lab::Moose::PosNum' },
118             );
119 0           my $t_sensor = $self->t_sensor;
120 0           my $high_temp_sensor = $self->high_temp_sensor;
121 0           my $low_temp_sensor = $self->low_temp_sensor;
122              
123 0 0 0       if ( $value < 1.5 && $t_sensor != $low_temp_sensor ) {
    0 0        
124 0           $t_sensor = $low_temp_sensor;
125             }
126             elsif ( $value >= 1.5 && $t_sensor != $high_temp_sensor ) {
127 0           $t_sensor = $high_temp_sensor;
128             }
129 0           $self->itc_set_heater_auto( value => 0 );
130 0           $self->itc_set_heater_sensor( value => $t_sensor );
131 0           $self->itc_set_heater_auto( value => 1 );
132 0           $self->itc_T_set_point( value => $value );
133              
134 0           warn "Set temperature $value with sensor $t_sensor\n";
135 0           $self->t_sensor($t_sensor);
136             }
137              
138              
139             sub get_value {
140 0     0 1   my ( $self, %args ) = validated_getter( \@_ );
141 0           my $t_sensor = $self->t_sensor();
142 0           my $high_temp_sensor = $self->high_temp_sensor();
143 0           my $low_temp_sensor = $self->low_temp_sensor();
144 0           my $temp = $self->itc_read_parameter( param => $t_sensor );
145 0           $temp = $self->itc_read_parameter( param => $t_sensor );
146 0           $temp = $self->itc_read_parameter( param => $t_sensor );
147 0 0 0       if ( $temp < 1.5 && $t_sensor != $low_temp_sensor ) {
    0 0        
148 0           $t_sensor = $low_temp_sensor;
149 0           $temp = $self->itc_read_parameter( param => $t_sensor );
150 0           $temp = $self->itc_read_parameter( param => $t_sensor );
151 0           $temp = $self->itc_read_parameter( param => $t_sensor );
152 0           warn "Switching to sensor $t_sensor at temperature $temp\n";
153             }
154             elsif ( $temp >= 1.5 && $t_sensor != $high_temp_sensor ) {
155 0           $t_sensor = $high_temp_sensor;
156 0           $temp = $self->itc_read_parameter( param => $t_sensor );
157 0           $temp = $self->itc_read_parameter( param => $t_sensor );
158 0           $temp = $self->itc_read_parameter( param => $t_sensor );
159 0           warn "Switching to sensor $t_sensor at temperature $temp\n";
160             }
161 0           warn "Read temperature $temp with sensor $t_sensor\n";
162 0           $self->t_sensor($t_sensor);
163 0           return $temp;
164             }
165              
166              
167             sub get_T {
168 0     0 1   my $self = shift;
169 0           return $self->get_value(@_);
170             }
171              
172              
173             sub itc_read_parameter {
174 0     0 1   my ( $self, %args ) = validated_getter(
175             \@_,
176             param => { isa => enum( [qw/0 1 2 3 4 5 6 7 8 9 10 11 12 13/] ) },
177             );
178 0           my $param = delete $args{param};
179 0           my $result = $self->query( command => "R$param\r", %args );
180 0           return sprintf( "%e", $result );
181             }
182              
183              
184             sub itc_set_wait {
185 0     0 1   my ( $self, $value, %args ) = validated_setter(
186             \@_,
187             value => { isa => 'Lab::Moose::PosInt' }
188             );
189 0           $self->query( command => "W$value\r" );
190             }
191              
192              
193             sub itc_examine {
194 0     0 1   my ( $self, %args ) = validated_getter( \@_ );
195 0           return $self->query( command => "X\r", %args );
196             }
197              
198             # for XPRESS compatiblity
199             sub set_heatercontrol {
200 0     0 0   my $self = shift;
201 0           my $mode = shift;
202              
203 0 0         if ( $mode eq 'MAN' ) {
    0          
204 0           $self->itc_set_heater_auto( value => 0 );
205             }
206             elsif ( $mode eq 'AUTO' ) {
207 0           $self->itc_set_heater_auto( value => 1 );
208             }
209             else {
210 0           warn "set_heatercontrol received an invalid parameter: $mode\n";
211             }
212              
213             }
214              
215              
216             sub itc_set_heater_auto {
217 0     0 1   my ( $self, $value, %args ) = validated_setter(
218             \@_,
219             value => { isa => enum( [qw/0 1 2 3/] ) }
220             );
221 0           return $self->query( command => "A$value\r", %args );
222             }
223              
224             # for XPRESS compatibility
225             sub set_PID {
226 0     0 0   my $self = shift;
227 0           my $p = shift;
228 0           my $i = shift;
229 0           my $d = shift;
230 0           $self->itc_set_PID( p => $p, i => $i, d => $d );
231             }
232              
233              
234             sub itc_set_PID {
235 0     0 1   my ( $self, %args ) = validated_getter(
236             \@_,
237             p => { isa => 'Num' },
238             i => { isa => 'Num' },
239             d => { isa => 'Num' },
240             );
241 0           $self->itc_set_proportional_value( value => $args{p} );
242 0           $self->itc_set_integral_value( value => $args{i} );
243 0           $self->itc_set_derivative_value( value => $args{d} );
244             }
245              
246             sub itc_set_proportional_value {
247 0     0 0   my ( $self, $value, %args ) = validated_setter(
248             \@_,
249             value => { isa => 'Lab::Moose::PosNum' }
250             );
251 0           $self->itc_set_PID_auto( value => 0 );
252 0           return $self->query( command => "P$value\r", %args );
253             }
254              
255             sub itc_set_integral_value {
256 0     0 0   my ( $self, $value, %args ) = validated_setter(
257             \@_,
258             value => { isa => 'Lab::Moose::PosNum' }
259             );
260 0           $self->itc_set_PID_auto( value => 0 );
261 0           return $self->query( command => "I$value\r", %args );
262             }
263              
264             sub itc_set_derivative_value {
265 0     0 0   my ( $self, $value, %args ) = validated_setter(
266             \@_,
267             value => { isa => 'Lab::Moose::PosNum' }
268             );
269 0           $self->itc_set_PID_auto( value => 0 );
270 0           return $self->query( command => "D$value\r", %args );
271             }
272              
273              
274             sub itc_set_heater_sensor {
275 0     0 1   my ( $self, $value, %args ) = validated_setter(
276             \@_,
277             value => { isa => enum( [ 1, 2, 3 ] ) }
278             );
279 0           return $self->query( command => "H$value\r", %args );
280             }
281              
282              
283             sub itc_set_PID_auto {
284 0     0 1   my ( $self, $value, %args ) = validated_setter(
285             \@_,
286             value => { isa => enum( [qw/0 1/] ) }
287             );
288 0           return $self->query( command => "L$value\r", %args );
289             }
290              
291              
292             # in 0.1 V
293             # 0 dynamical varying limit
294             sub itc_set_max_heater_voltage {
295 0     0 1   my ( $self, $value, %args ) = validated_setter(
296             \@_,
297             value => { isa => 'Lab::Moose::PosNum' }
298             );
299 0           return $self->query( command => "M$value\r", %args );
300             }
301              
302             # from 0 to 0.999
303             # 0 dynamical varying limit
304              
305              
306             sub itc_set_heater_output {
307 0     0 1   my ( $self, $value, %args ) = validated_setter( \@_ );
308 0           $value = sprintf( "%d", 1000 * $value );
309 0           return $self->query( command => "O$value\r", %args );
310             }
311              
312              
313             sub itc_T_set_point {
314 0     0 1   my ( $self, $value, %args ) = validated_setter(
315             \@_,
316             value => { isa => 'Lab::Moose::PosNum' }
317             );
318 0           $value = sprintf( "%.3f", $value );
319 0           return $self->query( command => "T$value\r" );
320             }
321              
322             #
323             #
324             # subs below need some more care
325             #
326             #
327              
328             sub itc_sweep {
329 0     0 0   my ( $self, $value, %args ) = validated_setter(
330             \@_,
331             value => { isa => 'Lab::Moose::PosInt' },
332             );
333 0 0         if ( $value > 32 ) {
334 0           croak "value argument of itc_sweep must be in the range 0..32";
335             }
336 0           return $self->query( command => "S$value\r", %args );
337             }
338              
339             sub itc_set_pointer {
340              
341             # Sets Pointer in internal ITC memory
342 0     0 0   my $self = shift;
343 0           my $x = shift;
344 0           my $y = shift;
345 0 0 0       if ( $x < 0 or $x > 128 ) {
346 0           printf "x=$x no valid ITC Pointer value\n";
347 0           die;
348             }
349 0 0 0       if ( $y < 0 or $y > 128 ) {
350 0           printf "y=$y no valid ITC Pointer value\n";
351 0           die;
352             }
353 0           my $cmd = sprintf( "x%d\r", $x );
354 0           $self->query( command => $cmd );
355 0           $cmd = sprintf( "y%d\r", $y );
356 0           $self->query( command => $cmd );
357             }
358              
359             sub itc_program_sweep_table {
360 0     0 0   my $self = shift;
361 0           my $setpoint = shift; #K Sweep Stop Point
362 0           my $sweeptime = shift; #Min. Total Sweep Time
363 0           my $holdtime = shift; #sec. Hold Time
364              
365 0 0 0       if ( $setpoint < 0. or $setpoint > 9.9 ) {
366 0           printf "Cannot reach setpoint: $setpoint\n";
367 0           die;
368             }
369              
370 0           $self->itc_set_pointer( 1, 1 );
371 0           $setpoint = sprintf( "%1.4f", $setpoint );
372 0           $self->query( command => "s$setpoint\r" );
373              
374 0           $self->itc_set_pointer( 1, 2 );
375 0           $sweeptime = sprintf( "%.4f", $sweeptime );
376 0           $self->query( command => "s$sweeptime\r" );
377              
378 0           $self->itc_set_pointer( 1, 3 );
379 0           $holdtime = sprintf( "%.4f", $holdtime );
380 0           $self->query( command => "s$holdtime\r" );
381              
382 0           $self->itc_set_pointer( 0, 0 );
383             }
384              
385             sub itc_read_sweep_table {
386              
387             # Clears Sweep Program Table
388 0     0 0   my $self = shift;
389 0           $self->query( command => "r\r" );
390             }
391              
392             sub itc_clear_sweep_table {
393              
394             # Clears Sweep Program Table
395 0     0 0   my $self = shift;
396 0           $self->query( command => "w\r" );
397             }
398              
399              
400             sub heat_sorb {
401 0     0 0   my $self = shift;
402             my (
403 0           $max_temp, $max_temp_time, $middle_temp, $middle_temp_time,
404             $target_temp, $sorb_sensor
405             )
406             = validated_list(
407             \@_,
408             max_temp => { isa => 'Lab::Moose::PosNum', default => 30 },
409             max_temp_time => { isa => 'Lab::Moose::PosNum', default => 20 * 60 },
410             middle_temp => { isa => 'Lab::Moose::PosNum', default => 10 },
411             middle_temp_time => { isa => 'Lab::Moose::PosNum', default => 200 },
412             target_temp => { isa => 'Lab::Moose::PosNum', default => 0.3 },
413             sorb_sensor => { isa => enum( [ 1, 2, 3 ] ), default => 1 },
414             );
415 0           warn "Heating sorb\n";
416 0           $self->itc_set_heater_auto( value => 0 );
417 0           $self->itc_set_PID_auto( value => 1 );
418              
419 0           $self->itc_set_heater_output( value => 0 );
420 0           $self->itc_set_heater_sensor( value => $sorb_sensor );
421 0           $self->itc_T_set_point( value => $middle_temp );
422              
423 0           $self->itc_set_heater_auto( value => 1 );
424              
425 0           warn "Sorb setpoint set to $middle_temp K\n";
426 0           countdown( $middle_temp_time, "Waiting for $middle_temp_time seconds: " );
427              
428 0           $self->itc_T_set_point( value => $max_temp );
429 0           $self->itc_set_heater_auto( value => 1 );
430 0           warn "Sorb setpoint set to $max_temp K\n";
431 0           countdown( $max_temp_time, "Waiting for $max_temp_time seconds: " );
432 0           warn "He3 should be condensated. Switching off heater\n";
433              
434 0           $self->itc_set_heater_auto( value => 0 );
435 0           $self->itc_set_heater_output( value => 0 );
436 0           $self->itc_set_PID_auto( value => 1 );
437 0           warn "Waiting until target temperature $target_temp is reached\n";
438 0           while (1) {
439 0           my $temp = $self->get_value();
440 0           warn "temp: $temp\n";
441 0 0         if ( $temp <= $target_temp ) {
442 0           warn "reached target temperature\n";
443 0           last;
444             }
445 0           sleep(5);
446             }
447             }
448              
449              
450             __PACKAGE__->meta()->make_immutable();
451              
452             1;
453              
454             __END__
455              
456             =pod
457              
458             =encoding UTF-8
459              
460             =head1 NAME
461              
462             Lab::Moose::Instrument::OI_ITC503 - Oxford Instruments ITC503 Intelligent Temperature Control
463              
464             =head1 VERSION
465              
466             version 3.881
467              
468             =head1 SYNOPSIS
469              
470             use Lab::Moose;
471              
472             # Constructor
473             my $itc = instrument(
474             type => 'OI_ITC503',
475             connection_type => 'LinuxGPIB',
476             connection_options => {pad => 10},
477             );
478              
479              
480             # Get temperature
481             say "Temperature: ", $itc->get_value();
482              
483             # Set heater to AUTO
484             $itc->itc_set_heater_auto( value => 0 );
485              
486             # Set PID to AUTO
487             $itc->itc_set_PID_auto( value => 1 );
488              
489             =head1 DESCRIPTION
490              
491             By default, two temperature sensors are used: Sensor 2 for temperatures below
492             1.5K and sensor 3 for temperatures above 1.5K. The used sensors can be set in
493             the constructor, e.g.
494              
495             my $itc = instrument(
496             ...
497             high_temp_sensor => 2,
498             low_temp_sensor => 3
499             );
500              
501             The L</get_value> and L</set_T> functions will dynamically choose the proper sensor.
502              
503             =head1 METHODS
504              
505             =head2 set_control
506              
507             $itc->set_control(value => 1);
508              
509             Set device local/remote mode (0, 1, 2, 3)
510              
511             =head2 itc_set_communications_protocol
512              
513             $itc->itc_set_communications_protocol(value => 0); # 0 or 2
514              
515             =head2 itc_set_T
516              
517             $itc->itc_set_T(value => 0.5);
518              
519             Set target temperature.
520              
521             =head2 get_value
522              
523             my $temp = $itc->get_value();
524              
525             Get current temperature value.
526              
527             =head2 get_T
528              
529             Alias for L</get_value>.
530              
531             =head2 itc_read_parameter
532              
533             my $value = $itc->itc_read_parameter(param => 1);
534              
535             Allowed values for C<param> are 0..13
536              
537             =head2 itc_set_wait
538              
539             $itc->itc_set_wait(value => $milli_seconds);
540              
541             =head2 itc_examine
542              
543             my $status = $itc->itc_examine();
544              
545             =head2 itc_set_heater_auto
546              
547             $itc->itc_set_heater_auto(value => 0);
548              
549             Allowed values:
550             0 Heater Manual, Gas Manual;
551             1 Heater Auto, Gas Manual
552             2 Heater Manual, Gas Auto
553             3 Heater Auto, Gas Auto
554              
555             =head2 itc_set_PID
556              
557             $itc->itc_set_PID(
558             p => $p,
559             i => $i,
560             d => $d
561             );
562              
563             =head2 itc_set_heater_sensor
564              
565             $itc->itc_set_heater_sensor( value => 1 );
566              
567             Value must be one of 1, 2, or 3.
568              
569             =head2 itc_set_PID_auto
570              
571             $itc->itc_set_PID_auto(value => 1); # enable
572             $itc->itc_set_PID_auto(value => 0); # disable
573              
574             =head2 itc_set_max_heater_voltage
575              
576             $itc->itc_set_max_heater_voltage(value => $voltage);
577              
578             =head2 itc_set_heater_output
579              
580             $itc->itc_set_heater_output(value => $output); # value from 0 to 0.999
581              
582             =head2 itc_T_set_point
583              
584             $itc->itc_T_set_point(value => $temp);
585              
586             =head2
587              
588             $itc->heat_sorb(
589             max_temp => $max_temp, # default: 30 K
590             max_temp_time => ..., # default: 20 * 60 seconds
591             middle_temp => ..., # default: 20 K
592             middle_temp_time => ..., # default: 200 seconds
593             target_time => ..., # default: 0.3 K
594             sorb_sensor => ..., # default: 1
595             sample_sensor => ..., # default: 2
596             );
597              
598             Heat the sorb of a 3-He cryostat (like OI HelioxVL). The sorb temperature is
599             first set to C<middle_temp> for C<middle_temp_time> seconds, then to
600             C<max_temp> for C<max_temp_time> seconds. Then the heater is switched off and
601             the routine returns when the temperature at C<sample_sensor> has dropped below C<target_time>.
602              
603             =head2 Consumed Roles
604              
605             This driver consumes the following roles:
606              
607             =over
608              
609             =back
610              
611             =head1 COPYRIGHT AND LICENSE
612              
613             This software is copyright (c) 2023 by the Lab::Measurement team; in detail:
614              
615             Copyright 2019 Simon Reinhardt
616             2020 Andreas K. Huettel
617             2022 Mia Schambeck
618              
619              
620             This is free software; you can redistribute it and/or modify it under
621             the same terms as the Perl 5 programming language system itself.
622              
623             =cut