File Coverage

blib/lib/Device/Modbus/Unit/Route.pm
Criterion Covered Total %
statement 47 47 100.0
branch 18 18 100.0
condition 9 9 100.0
subroutine 11 11 100.0
pod 0 4 0.0
total 85 89 95.5


line stmt bran cond sub pod time code
1             package Device::Modbus::Unit::Route;
2              
3 7     7   28835 use Carp;
  7         17  
  7         434  
4 7     7   27 use strict;
  7         7  
  7         119  
5 7     7   18 use warnings;
  7         12  
  7         3387  
6              
7             sub new {
8 42     42 0 7042 my ($class, %args) = @_;
9            
10 42         134 my %valid_zone = (
11             discrete_coils => 'rw',
12             discrete_inputs => 'ro',
13             input_registers => 'ro',
14             holding_registers => 'rw',
15             );
16              
17 42         64 foreach my $field (qw/address quantity zone read_write routine/) {
18             croak "Missing required arguments: $field"
19 200 100       811 unless exists $args{$field};
20             }
21             croak "Invalid Modbus model type: zone '$args{zone}' does not exist"
22 37 100       264 unless exists $valid_zone{$args{zone}};
23             croak "Modbus zone '$args{zone}' is read-only"
24 36 100 100     370 if $args{read_write} eq 'write' && $valid_zone{$args{zone}} eq 'ro';
25             croak "Parameter read_write must be either 'read' or 'write'"
26 34 100       230 unless $args{read_write} =~ /^read|write$/;
27             croak "The routine for an address must be a code reference"
28 33 100 100     322 unless ref $args{routine} && ref $args{routine} eq 'CODE';
29              
30 31         47 $args{route_tests} = _build_tests($args{address});
31 31         57 $args{qty_tests} = _build_tests($args{quantity});
32              
33 31         99 my $self = bless \%args, $class;
34             }
35              
36             sub routine {
37 12     12 0 1465 my $self = shift;
38 12         35 return $self->{routine};
39             }
40              
41             # Receives a route string and converts it into an array reference of
42             # anonymous subroutines. Each subroutine will test if a given value
43             # matches a part of the route.
44             sub _build_tests {
45 62     62   60 my $route = shift;
46              
47             # Star matches always
48 62 100   12   128 return [ sub { 1 } ] if $route =~ /\*/;
  12         64  
49              
50 60         84 $route =~ s/\s+//g;
51 60         107 my @atoms = split /,/, $route;
52 60         41 my @tests;
53 60         63 foreach my $atom (@atoms) {
54             # Range test
55 77 100       116 if ($atom =~ /^(\d+)\-(\d+)$/) {
56 11         26 my ($min, $max) = ($1, $2);
57 11   100 109   27 push @tests, sub { my $val = shift; return $val >= $min && $val <= $max; };
  109         88  
  109         540  
58             }
59             # Equality test
60             else {
61 66     229   168 push @tests, sub { return shift == $atom; };
  229         586  
62             }
63             }
64              
65 60         125 return \@tests;
66             }
67              
68             # Tests an address
69             sub test_route {
70 93     93 0 16628 my ($self, $value) = @_;
71 93         88 foreach my $test (@{$self->{route_tests}}) {
  93         157  
72 181 100       200 return 1 if $test->($value);
73             }
74 47         151 return 0;
75             }
76              
77             # Tests a quantity
78             sub test_quantity {
79 80     80 0 14988 my ($self, $value) = @_;
80 80         71 foreach my $test (@{$self->{qty_tests}}) {
  80         118  
81 169 100       189 return 1 if $test->($value);
82             }
83 35         131 return 0;
84             }
85              
86             1;