File Coverage

blib/lib/Device/Modbus/Unit/Route.pm
Criterion Covered Total %
statement 49 49 100.0
branch 18 18 100.0
condition 9 9 100.0
subroutine 12 12 100.0
pod 0 4 0.0
total 88 92 95.6


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