File Coverage

blib/lib/Device/TLSPrinter.pm
Criterion Covered Total %
statement 45 176 25.5
branch 6 74 8.1
condition 0 17 0.0
subroutine 11 32 34.3
pod 11 15 73.3
total 73 314 23.2


line stmt bran cond sub pod time code
1             package Device::TLSPrinter;
2 2     2   2736 use strict;
  2         6  
  2         81  
3 2     2   13 use Carp;
  2         4  
  2         164  
4 2     2   1975 use Class::Accessor;
  2         4733  
  2         17  
5 2     2   68 use Exporter ();
  2         4  
  2         50  
6              
7             {
8 2     2   10 no strict "vars";
  2         5  
  2         408  
9             $VERSION = '0.51';
10             @ISA = qw< Exporter Class::Accessor >;
11              
12             %EXPORT_TAGS = (
13             feedback => [qw<
14             FC_OK FC_SERIAL_TIMEOUT_ERROR FC_COMMAND_ERROR
15             FC_MEMORY_FULL_ERROR FC_IMAGE_ALREADY_EXISTS
16             FC_IMMEDIATE_COMMANDS_ENABLED FC_OUT_OF_LABELS FC_PRINTHEAD_OPEN
17             FC_OUT_OF_RIBBON FC_BATTERY_CELL_SHORTED FC_LOW_BATTERY
18             FC_PRINTING_COMPLETE FC_PRINTING_COMPLETE FC_NO_LABEL_FORMAT_ERROR
19             FC_MEMORY_READ_ERROR FC_MEDIA_CHANGED FC_PRINTHEAD_TOO_HOT
20             FC_LABEL_ERROR FC_FIELD_ERROR FC_FEED_TO_CUT_COMPLETE
21              
22             FC_UNDEF FC_IMMEDIATE_COMMANDS_DISABLED FC_NOT_IN_LABEL_EDIT_MODE
23              
24             ROTATION_NONE ROTATION_90 ROTATION_180 ROTATION_270
25              
26             TYPE_FONT TYPE_BARCODE_39 TYPE_BARCODE_39_WITH_CHECK
27             TYPE_BARCODE_128 TYPE_IMAGE
28             >],
29             ascii => [qw<
30             SOH STX CR
31             >],
32             );
33              
34             $EXPORT_TAGS{all} = [ @{$EXPORT_TAGS{feedback}}, @{$EXPORT_TAGS{ascii}} ];
35             @EXPORT = ( @{$EXPORT_TAGS{feedback}} );
36             @EXPORT_OK = ( @{$EXPORT_TAGS{ascii}} );
37             }
38              
39              
40             # ASCII constants
41             use constant {
42 2         404 SOH => "\x01",
43             STX => "\x02",
44             CR => "\x0D",
45             CRLF => "\x0D\x0A",
46             LF => "\x0A",
47 2     2   13 };
  2         6  
48              
49             use constant {
50             # standard feedback chars
51 2         2397 FC_OK => "0",
52             FC_SERIAL_TIMEOUT_ERROR => "1",
53             FC_COMMAND_ERROR => "2",
54             FC_MEMORY_FULL_ERROR => "3",
55             FC_IMAGE_ALREADY_EXISTS => "4",
56             FC_IMMEDIATE_COMMANDS_ENABLED => "5",
57             FC_OUT_OF_LABELS => "6",
58             FC_PRINTHEAD_OPEN => "7",
59             FC_OUT_OF_RIBBON => "8",
60             FC_BATTERY_CELL_SHORTED => "A",
61             FC_LOW_BATTERY => "B",
62             FC_PRINTING_COMPLETE => "C",
63             FC_NO_LABEL_FORMAT_ERROR => "D",
64             FC_MEMORY_READ_ERROR => "E",
65             FC_MEDIA_CHANGED => "F",
66             FC_PRINTHEAD_TOO_HOT => "G",
67             FC_LABEL_ERROR => "H",
68             FC_FIELD_ERROR => "I",
69             FC_FEED_TO_CUT_COMPLETE => "J",
70              
71             # custom feedback chars
72             FC_UNDEF => "~",
73             FC_IMMEDIATE_COMMANDS_DISABLED => ";",
74             FC_NOT_IN_LABEL_EDIT_MODE => ":",
75              
76             # rotations
77             ROTATION_NONE => 1,
78             ROTATION_90 => 2,
79             ROTATION_180 => 3,
80             ROTATION_270 => 4,
81              
82             # field types
83             TYPE_FONT => "9",
84             TYPE_BARCODE_39 => "a",
85             TYPE_BARCODE_39_WITH_CHECK => "b",
86             TYPE_BARCODE_128 => "c",
87             TYPE_IMAGE => "Y",
88 2     2   12 };
  2         3  
89              
90             # object fields and default values
91             my %object_fields = (
92             # internal parameters
93             _device => undef, # device string
94             _socket => undef, # IO::Socket::INET object
95             _serial => undef, # {Device,Win32}::SerialPort object
96             _timeout => 10, # timeout
97              
98             # public attributes
99             feedback_chars => 0, # are feedback characters enabled?
100             immediate_cmds => 0, # are immediate commands enabled?
101             label_edition => 0, # currently in label editing mode?
102             );
103              
104             # create accessors
105             __PACKAGE__->mk_accessors(keys %object_fields);
106              
107             # private variables
108             my $HEX = "[0-9A-Fa-f]";
109             my $HEXNUM = $HEX x 2;
110              
111              
112             #
113             # new()
114             # ---
115             sub new {
116 2     2 1 2251 my ($class, %args) = @_;
117              
118             # if missing, try to infer the type from the device param
119 2 50       9 if (not $args{type}) {
120 0 0       0 if (eval { $args{device}->isa("Device::SerialPort") } ) {
  0 0       0  
    0          
    0          
121 0         0 $args{type} = "serial"
122             }
123 0         0 elsif (eval { $args{device}->isa("Win32::SerialPort") } ) {
124 0         0 $args{type} = "serial"
125             }
126             elsif ($args{device} =~ m{^COM\d|^/dev/(?:term|tty)}) {
127 0         0 $args{type} = "serial"
128             }
129             elsif ($args{device} =~ m{^[a-zA-Z0-9.-]+:[0-9]+$}) {
130 0         0 $args{type} = "network"
131             }
132             }
133              
134             # enable debug mode?
135 2     0   22 { local $SIG{__WARN__} = sub {};
  2         15  
  0         0  
136 2 50   0   21 *DEBUG = $args{debug} ? \&_DEBUG : sub {};
  0         0  
137             }
138              
139             # check arguments
140 2 50       8 carp "warning: You should specify the connection type" unless exists $args{type};
141 2 100       239 croak "error: Missing required parameter: device" unless exists $args{device};
142              
143             # create the object and populate the attributes
144 1         10 my %fields = (
145             %object_fields, # default values
146             _type => $args{type},
147             _device => $args{device},
148             _timeout => $args{timeout},
149             );
150 1         13 my $self = __PACKAGE__->SUPER::new(\%fields);
151              
152             # initialize the backend driver
153 1         22 my ($driver) = $args{type} =~ /^(\w+)$/;
154 1         4 $class = __PACKAGE__."::".ucfirst($driver);
155 1 50       81 eval "require $class"
156             or croak "error: Could not load driver $class: no such module";
157 1         856 bless $self, $class; # rebless the object into the class of the driver
158 1         5 $self->init();
159              
160 1         531 return $self
161             }
162              
163              
164             #
165             # _DEBUG()
166             # ------
167             sub _DEBUG {
168 0     0     print STDERR @_, $/
169             }
170              
171              
172             #
173             # exec_command()
174             # ------------
175             sub exec_command {
176 0     0 1   my ($self, %args) = @_;
177 0           my ($rc, $answer, $n) = (FC_UNDEF, "", 0);
178              
179 0 0 0       carp "error: Missing required parameter: cmd" and return unless $args{cmd};
180              
181             # send the data
182 0           $n = $self->write(data => $args{cmd});
183              
184             # read the answer if any is expected
185 0 0         if ($args{expect}) {
186 0           my ($left_to_read, $read, $chunk);
187 0           $left_to_read = $args{expect};
188              
189 0           while ($left_to_read > 0) {
190 0           ($read, $chunk) = $self->read(expect => $left_to_read);
191 0           $answer .= $chunk;
192 0           $left_to_read -= $read;
193             }
194             }
195              
196             # read the feedback character if enabled
197 0 0 0       if ($args{feedback} and $self->feedback_chars) {
198 0           ($n, $rc) = $self->read(expect => 1);
199 0           DEBUG(" >>> exec_command(): feedback='$rc' (", ord($rc), ")");
200             }
201              
202 0           return ($rc, $answer)
203             }
204              
205              
206             # ========================================================================
207             # Immediate commands
208             #
209              
210             my %ic_cmds = (
211             ic_printer_reset => { string => "#", expect => 25 },
212             ic_printer_status => {
213             string => "A", expect => 9, filter => \&ic_filter_printer_status
214             },
215             ic_toggle_pause => { string => "B", expect => 0 },
216             ic_cancel_job => { string => "C", expect => 0 },
217             ic_batch_quantity => { string => "E", expect => 5 },
218             );
219              
220             for my $cmd (keys %ic_cmds) {
221 2     2   13 no strict 'refs';
  2         480  
  2         1247  
222             *$cmd = sub {
223 0     0     my ($self) = @_;
224 0           my ($rc, $raw, @data) = (FC_UNDEF);
225 0           DEBUG(" >>> $cmd()");
226              
227 0 0         if ($self->immediate_cmds) {
228 0           ($rc, $raw) = $self->exec_command(
229             cmd => SOH.$ic_cmds{$cmd}{string},
230             expect => $ic_cmds{$cmd}{expect},
231             feedback => 0,
232             );
233 0           $rc = FC_OK;
234              
235             # pass the raw result to the filter if it's defined
236 0 0 0       if (defined $raw and ref $ic_cmds{$cmd}{filter} eq "CODE") {
237 0           @data = $ic_cmds{$cmd}{filter}->($raw)
238             }
239             }
240             else {
241 0           $rc = FC_IMMEDIATE_COMMANDS_DISABLED
242             }
243              
244 0 0         return wantarray ? ($rc, $raw, @data) : $rc
245             }
246             }
247              
248              
249             #
250             # ic_disable_immediate_cmds()
251             # -------------------------
252             sub ic_disable_immediate_cmds {
253 0     0 1   my ($self) = @_;
254 0           $self->exec_command(cmd => SOH."D");
255 0           $self->immediate_cmds(0);
256             }
257              
258              
259             #
260             # ic_filter_printer_status()
261             # ------------------------
262             sub ic_filter_printer_status {
263 0     0 0   my ($raw) = @_;
264              
265             # decode the status
266 0           my @chars = split //, $raw;
267 0 0         my %status = (
    0          
    0          
    0          
    0          
    0          
    0          
    0          
268             printhead_open => $chars[0] eq "Y" ? 1 : 0,
269             out_of_labels => $chars[1] eq "Y" ? 1 : 0,
270             out_of_ribbon => $chars[2] eq "Y" ? 1 : 0,
271             printing_batch => $chars[3] eq "Y" ? 1 : 0,
272             busy_printing => $chars[4] eq "Y" ? 1 : 0,
273             printer_paused => $chars[5] eq "Y" ? 1 : 0,
274             touch_cell_error => $chars[6] eq "Y" ? 1 : 0,
275             low_battery => $chars[7] eq "Y" ? 1 : 0,
276             );
277              
278 0           return %status
279             }
280              
281              
282             # ========================================================================
283             # System commands
284             #
285              
286             my %sc_cmds = (
287             sc_heat_setting_offset => { string => "b%+02.2d" },
288             sc_disable_feed_to_cut_position => { string => "C" },
289             sc_enable_feed_to_cut_position => { string => "c" },
290             sc_quantity_for_stored_labels => { string => "E%04d" },
291             sc_form_feed => { string => "F" },
292             sc_set_form_stop_position => { string => "f%+02.2d" },
293             sc_print_last_label_format => { string => "G" },
294             sc_set_printer_to_metric => { string => "m" },
295             sc_set_printer_to_inches => { string => "n" },
296             sc_set_start_of_print_offset => { string => "O+02.2d" },
297             sc_set_horizontal_align_offset => { string => "o+02.2d" },
298             sc_set_continuous_label_length => { string => "P%04d" },
299             sc_clear_all_memory => { string => "Q" },
300             sc_set_continuous_label_spacing => { string => "S%04d" },
301             sc_print_test_label => { string => "T" },
302             sc_get_touch_cell_data_binary => {
303             string => "t", expect => 32
304             },
305             sc_replace_label_format_field => { string => "U%02d%s".CR },
306             sc_get_touch_cell_data_ascii => {
307             string => "V", expect => 32*2, filter => \&sc_filter_touch_cell_data_ascii
308             },
309             sc_firmware_version => {
310             string => "v", expect => 25, filter => \&sc_filter_chomp
311             },
312             sc_memory_information => {
313             string => "W%s", expect => 255, filter => \&sc_filter_memory_info
314             },
315             sc_delete_file => { string => "x%s%s" },
316             sc_pack_memory => { string => "z" },
317             );
318              
319             for my $cmd (keys %sc_cmds) {
320 2     2   13 no strict 'refs';
  2         5  
  2         2776  
321             *$cmd = sub {
322 0     0     my ($self, @args) = @_;
323 0           my @data;
324 0           DEBUG(" >>> $cmd(@args)");
325              
326             # execute the command
327 0           my ($rc, $raw) = $self->exec_command(
328             cmd => sprintf(STX.$sc_cmds{$cmd}{string}, @args),
329             expect => $sc_cmds{$cmd}{expect},
330             feedback => 1,
331             );
332              
333             # pass the raw result to the filter if it's defined
334 0 0 0       if (defined $raw and ref $sc_cmds{$cmd}{filter} eq "CODE") {
335 0           @data = $sc_cmds{$cmd}{filter}->($raw)
336             }
337              
338 0 0         return wantarray ? ($rc, $raw, @data) : $rc
339             }
340             }
341              
342              
343             #
344             # sc_filter_chomp()
345             # ---------------
346             sub sc_filter_chomp {
347 0     0 0   my ($raw) = @_;
348 0           $raw =~ s/[\012\015]$//g;
349 0           return $raw
350             }
351              
352              
353             #
354             # sc_filter_memory_info()
355             # ---------------------
356             sub sc_filter_memory_info {
357 0     0 0   my ($raw) = @_;
358 0           $raw =~ s/\b($HEX+)\s$/hex($1)/e;
  0            
359 0           return split CR, $raw
360             }
361              
362              
363             #
364             # sc_filter_touch_cell_data_ascii()
365             # -------------------------------
366             sub sc_filter_touch_cell_data_ascii {
367 0     0 0   my ($raw) = @_;
368              
369 0           my @values = map {hex} $raw =~ m{
  0            
370             ^ $HEXNUM ($HEXNUM $HEXNUM) $HEXNUM $HEXNUM # label quantity
371             ($HEXNUM) ($HEXNUM $HEXNUM) ($HEXNUM $HEXNUM) # bits field, offset X and Y
372             ($HEXNUM $HEXNUM) ($HEXNUM $HEXNUM) # width, length
373             }x;
374              
375 0           my %fields = (
376             remaining_labels => $values[0],
377             notched_material => $values[1] & 1,
378             offset_x => $values[2],
379             offset_y => $values[3],
380             label_width => $values[4],
381             label_length => $values[5],
382             );
383              
384 0           return %fields
385             }
386              
387              
388             #
389             # sc_disable_feedback_chars()
390             # -------------------------
391             ## @method string sc_disable_feedback_chars($self)
392             # @return feedback code
393             #
394             sub sc_disable_feedback_chars {
395 0     0 1   my ($self) = @_;
396 0           DEBUG(" >>> sc_disable_feedback_chars()");
397 0           $self->exec_command(cmd => STX."A", feedback => 0);
398 0           $self->feedback_chars(0);
399             }
400              
401              
402             #
403             # sc_enable_feedback_chars()
404             # ------------------------
405             ## @method string sc_enable_feedback_chars($self)
406             # @return feedback code
407             #
408             sub sc_enable_feedback_chars {
409 0     0 1   my ($self) = @_;
410 0           DEBUG(" >>> sc_enable_feedback_chars()");
411 0           $self->exec_command(cmd => STX."a", feedback => 0);
412 0           $self->feedback_chars(1);
413             }
414              
415              
416             #
417             # sc_enable_immediate_cmds()
418             # ------------------------
419             ## @method string sc_enable_immediate_cmds($self)
420             # @return feedback code
421             #
422             *ic_enable_immediate_cmds = \&sc_enable_immediate_cmds;
423             sub sc_enable_immediate_cmds {
424 0     0 1   my ($self) = @_;
425 0           DEBUG(" >>> sc_enable_immediate_cmds()");
426 0           my ($rc) = $self->exec_command(cmd => STX."H", feedback => 1);
427 0 0         $self->immediate_cmds(1) if $rc eq FC_IMMEDIATE_COMMANDS_ENABLED;
428 0           return $rc
429             }
430              
431              
432             #
433             # sc_input_image_data()
434             # -------------------
435             ## @method string sc_input_image_data($self, $data_type, $format, $image_name, @image_data)
436             # @param data_type string
437             # @param format string, image format designation
438             # @param image_name string, image name, up to 8 characters long
439             # @param image_data array or image data
440             # @return feedback code
441             #
442             sub sc_input_image_data {
443 0     0 1   my ($self, $data_type, $format, $image_name, @image_data) = @_;
444 0           DEBUG(" >>> sc_input_image_data($data_type, $format, $image_name)");
445              
446             # check arguments
447 0 0 0       carp "error: Invalid value for data type: '$data_type'"
448             and return if $data_type !~ /^[AB]$/;
449 0 0 0       carp "error: Invalid value for format designation: '$format'"
450             and return if $format !~ /^[BbPpU]$/;
451              
452             # first disable immediate commands if they were enabled
453 0           my $ic_enabled = $self->immediate_cmds;
454 0 0         $self->ic_disable_immediate_cmds if $ic_enabled;
455              
456             # then send the actual image command
457 0           my $cmd = sprintf STX."I%s%s%s".CR, $data_type, $format, $image_name;
458 0           my $data = join '', @image_data;
459 0           my ($rc) = $self->exec_command(cmd => $cmd.$data, feedback => 1);
460              
461             # finaly restore immediate commands
462 0 0         $self->sc_enable_immediate_cmds if $ic_enabled;
463              
464 0           return $rc
465             }
466              
467              
468             #
469             # sc_extended_system_cmds()
470             # -----------------------
471             ## @method string sc_extended_system_cmds($self)
472             # @return feedback code
473             #
474             sub sc_extended_system_cmds {
475 0     0 1   my ($self) = @_;
476 0           DEBUG(" >>> sc_extended_system_cmds()");
477 0           $self->exec_command(cmd => STX."K", feedback => 0);
478             }
479              
480              
481             #
482             # sc_enter_label_formatting_cmd()
483             # -----------------------------
484             ## @method string sc_enter_label_formatting_cmd($self)
485             # @return feedback code
486             #
487             sub sc_enter_label_formatting_cmd {
488 0     0 1   my ($self) = @_;
489 0           DEBUG(" >>> sc_enter_label_formatting_cmd()");
490              
491 0           my ($rc) = $self->exec_command(cmd => STX."L", feedback => 1);
492 0           $self->label_edition(1);
493              
494 0           return $rc
495             }
496              
497              
498             # ========================================================================
499             # Label formatting commands
500             #
501              
502             my %lc_cmds = (
503             lc_set_format_attribute => { string => "A%d".CR },
504             lc_set_column_offset => { string => "C%04d".CR },
505             lc_end_label_formatting_and_print => { string => "E".CR, end_mode => 1 },
506             lc_set_row_offset => { string => "C%04d".CR },
507             lc_end_label_formatting => { string => "X".CR, end_mode => 1 },
508             lc_increment_prev_numeric_field => { string => "+%s%02d".CR },
509             lc_decrement_prev_numeric_field => { string => "-%s%02d".CR },
510             lc_increment_prev_alphanum_field => { string => ">%s%02d".CR },
511             lc_decrement_prev_alphanum_field => { string => "<%s%02d".CR },
512             lc_set_count_by_amount => { string => "^%02d".CR },
513             lc_add_field => { string => "%d%s00%03d%04d%04d".CR },
514             );
515              
516             for my $cmd (keys %lc_cmds) {
517 2     2   15 no strict 'refs';
  2         4  
  2         1314  
518             *$cmd = sub {
519 0     0     my ($self, @args) = @_;
520 0           DEBUG(" >>> $cmd(@args)");
521              
522             # check that we're in label formatting mode
523 0 0         return FC_NOT_IN_LABEL_EDIT_MODE
524             unless $self->label_edition;
525              
526             # execute the command
527 0           my ($rc) = $self->exec_command(
528             cmd => sprintf($lc_cmds{$cmd}{string}, @args),
529             expect => $lc_cmds{$cmd}{expect},
530             feedback => 1,
531             );
532              
533             # end edition mode if needed
534 0 0         if ($lc_cmds{$cmd}{end_mode}) {
535 0           $self->label_edition(0)
536             }
537              
538 0           return $rc
539             }
540             }
541              
542              
543             # ========================================================================
544             # High-level commands
545             #
546              
547              
548             #
549             # hc_flush_input()
550             # --------------
551             ## @method string hc_flush_input($self)
552             # @return feedback code
553             #
554             sub hc_flush_input {
555 0     0 1   my ($self) = @_;
556 0           my ($n, $data) = (1, "");
557              
558             # read everything in the input buffer
559 0           while ($n) {
560 0     0     local $SIG{ALRM} = sub { $n = 0; die "read timeout\n" };
  0            
  0            
561 0           alarm 2;
562 0           ($n, $data) = eval { $self->read(expect => 20) };
  0            
563 0           alarm 0;
564 0   0       $n ||= 0;
565             }
566              
567 0           return FC_OK
568             }
569              
570              
571             #
572             # hc_upload_label()
573             # ---------------
574             ## @method string hc_upload_label($self, %params)
575             # @param lines arrayref of lines describing the label
576             # @param print_now boolean
577             # @return feedback code
578             #
579             sub hc_upload_label {
580 0     0 1   my ($self, %args) = @_;
581 0           DEBUG(" >>> hc_upload_label()");
582 0           my $rc;
583 0 0         croak "error: Missing required parameter: lines" unless exists $args{lines};
584 0 0         croak "error: Invalid value for parameter 'lines'" unless ref $args{lines} eq "ARRAY";
585              
586             # prepare the data to be sent
587 0           my @lines = @{ $args{lines} };
  0            
588 0           chomp @lines;
589              
590             # send the label data
591 0           $self->sc_enter_label_formatting_cmd;
592              
593 0           for my $line (@lines) {
594 0           ($rc) = $self->exec_command(cmd => $line.CR, feedback => 1);
595 0 0         last if $rc ne FC_OK;
596             }
597              
598             # in case of error, stop and return the last feedback char
599 0 0         if ($rc ne FC_OK) {
600 0           $self->lc_end_label_formatting;
601 0           return $rc
602             }
603              
604             # end the label edition mode
605 0 0         if ($args{print_now}) {
606 0           ($rc) = $self->lc_end_label_formatting_and_print
607             }
608             else {
609 0           ($rc) = $self->lc_end_label_formatting
610             }
611              
612 0           return $rc
613             }
614              
615              
616             1;
617              
618             __END__