File Coverage

blib/lib/Device/CableModem/Motorola/SB4200.pm
Criterion Covered Total %
statement 39 216 18.0
branch 0 42 0.0
condition 0 18 0.0
subroutine 13 33 39.3
pod 11 11 100.0
total 63 320 19.6


}xmsi ) {
line stmt bran cond sub pod time code
1             package Device::CableModem::Motorola::SB4200;
2             $Device::CableModem::Motorola::SB4200::VERSION = '0.14';
3 1     1   67473 use strict;
  1         10  
  1         30  
4 1     1   5 use warnings;
  1         2  
  1         32  
5 1     1   5 use constant DEFAULT_IP => '192.168.100.1';
  1         2  
  1         125  
6 1     1   7 use constant RE_BUTTON_RESTART => qr{\QRestart Cable Modem\E}xmsi;
  1         2  
  1         76  
7 1     1   7 use constant RE_BUTTON_RESET => qr{\QReset All Defaults\E}xmsi;
  1         2  
  1         71  
8 1     1   6 use constant RE_404 => qr{ File \s Not \s Found }xmsi;
  1         2  
  1         96  
9 1     1   8 use constant UA_TIMEOUT => 5;
  1         1  
  1         43  
10 1     1   650 use LWP::UserAgent;
  1         49154  
  1         37  
11 1     1   613 use HTML::TableParser;
  1         11593  
  1         34  
12 1     1   572 use HTML::Form;
  1         17461  
  1         51  
13 1     1   688 use Data::Dumper;
  1         6238  
  1         74  
14 1     1   16 use Carp qw( croak );
  1         2  
  1         76  
15             use Exception::Class (
16 1         20 'HTTP::Error',
17             'HTTP::Error::NotFound' => {
18             isa => 'HTTP::Error',
19             description => 'The content not found on the machine',
20             },
21             'HTTP::Error::Connection' => {
22             isa => 'HTTP::Error',
23             description => 'Unable to get a result from the server',
24             },
25             'Modem::Error::Command' => {
26             description => 'Unable to get execute a modem command',
27             },
28 1     1   559 );
  1         8468  
29              
30             my $AGENT = sprintf q{%s/%s}, __PACKAGE__, __PACKAGE__->VERSION;
31             my %PAGE = (
32             status => 'startupdata.html',
33             signal => 'signaldata.html',
34             addr => 'addressdata.html',
35             conf => 'configdata.html',
36             logs => 'logsdata.html',
37             help => 'mainhelpdata.html',
38             );
39              
40             sub new {
41 0     0 1   my($class, @args) = @_;
42 0 0         my %opt = (
43             ip => DEFAULT_IP,
44             ( @args % 2 ? () : @args )
45             );
46              
47 0           $opt{base_url} = sprintf 'http://%s/', $opt{ip};
48 0           foreach my $name ( keys %PAGE ) {
49 0           my $id = 'page_' . $name;
50 0 0         next if $opt{ $id }; # user-defined?
51 0           $opt{ $id } = $opt{base_url} . $PAGE{ $name };
52             }
53              
54 0           my $self = bless { %opt }, $class;
55 0           return $self;
56             }
57              
58             sub restart {
59 0     0 1   my $self = shift;
60 0           my $raw = $self->_get( $self->{page_conf} );
61 0           my $form = HTML::Form->parse( $raw, $self->{page_conf} );
62              
63 0           foreach my $e ( $form->inputs ) {
64 0 0         next if $e->type ne 'submit';
65 0 0         if ( $e->value =~ RE_BUTTON_RESTART ) {
66 0   0       my $req = $e->click( $form )
67             || Modem::Error::Command->throw( 'Restart failed' );
68 0           $req->uri( $self->{page_conf} );
69 0           my $response = $self->_req( $req );
70 0           return;
71             }
72             }
73              
74 0           return Modem::Error::Command->throw(
75             'Restart failed: the required button can not be found'
76             );
77             }
78              
79             sub reset { ## no critic (ProhibitBuiltinHomonyms)
80 0     0 1   my $self = shift;
81 0           my $raw = $self->_get( $self->{page_conf} );
82 0           my $form = HTML::Form->parse( $raw, $self->{page_conf} );
83              
84 0           foreach my $e ( $form->inputs ) {
85 0 0         next if $e->type ne 'submit';
86 0 0         if ( $e->value =~ RE_BUTTON_RESET ) {
87 0   0       my $req = $e->click( $form )
88             || Modem::Error::Command->throw( 'Reset failed' );
89 0           $req->uri( $self->{page_conf} );
90 0           my $response = $self->_req( $req );
91 0           return;
92             }
93             }
94              
95 0           return Modem::Error::Command->throw(
96             'Reset failed: the required button can not be found'
97             );
98             }
99              
100             sub config {
101 0     0 1   my $self = shift;
102 0           my $raw = $self->_get( $self->{page_conf} );
103 0           my $form = HTML::Form->parse( $raw, $self->{page_conf} );
104 0           my %rv;
105 0           foreach my $e ( $form->inputs ) {
106 0 0         next if $e->type eq 'submit';
107 0           $rv{ $e->name } = $e->value;
108             }
109 0           return %rv;
110             }
111              
112             sub set_config {
113 0     0 1   my $self = shift;
114 0   0       my $name = shift || croak 'Config name not present';
115 0           my $value = shift;
116 0 0         croak 'Config value not present' if not defined $value;
117 0           my $raw = $self->_get( $self->{page_conf} );
118 0           my $form = HTML::Form->parse( $raw, $self->{page_conf} );
119              
120 0           my $input;
121 0           foreach my $e ( @{ $form->inputs } ) {
  0            
122 0 0 0       next if $e->type eq 'submit' || $e->name ne $name;
123 0 0         if ( my @possible = $e->possible_values ) {
124 0 0         my %valid = map { ( (defined $_ ? $_ : 0), 1 ) } @possible;
  0            
125 0 0         if ( ! $valid{ $value } ) {
126 0           croak "The value ($value) for $name is not valid. "
127             .'You should select one of these: ' . join q{ }, keys %valid;
128             }
129             }
130 0           $input = $e;
131 0           last;
132             }
133              
134 0 0         croak "$name is not a valid configuration option" if ! $input;
135              
136             # good to go
137 0           $input->value($value);
138 0   0       my $req = $form->click() || croak "Saving $name=$value failed";
139 0           $req->uri( $self->{page_conf} );
140 0           my $response = $self->_req( $req );
141 0           return;
142             }
143              
144             sub addresses {
145 0     0 1   my $self = shift;
146 0           my $raw = $self->_get( $self->{page_addr} );
147              
148 0           my(%list, @mac);
149              
150             my $list = sub {
151 0     0     my ( $id, $line, $cols, $udata ) = @_;
152 0           (my $name = lc $cols->[0]) =~ tr/ /_/;
153 0           $list{ $name } = $cols->[1];
154 0           return;
155 0           };
156              
157             my $mac = sub {
158 0     0     my ( $id, $line, $cols, $udata ) = @_;
159 0           my($num, $addr, $status) = @{ $cols };
  0            
160 0           push @mac, { address => $addr, status => $status };
161 0           return;
162 0           };
163              
164 0           HTML::TableParser->new(
165             [
166             { id => 1.4, row => $list },
167             { id => 1.5, row => $mac },
168             ],
169             { Decode => 1, Trim => 1, Chomp => 1 },
170             )->parse( $raw );
171              
172 0           my $di = $list{dhcp_information};
173 0           $list{dhcp_information} = {};
174 0           foreach my $info ( split m{ \r?\n }xmsi, $di ) {
175 0           my($name, $value) = split m{ : \s+ }xms, $info;
176 0           my($num, $type, $other) = split m{ \s+ }xms, $value;
177 0   0       my $has_type = defined $num && defined $type && ! defined $other;
178 0 0         $list{dhcp_information}->{ $name } = $has_type
179             ? { value => $num, type => $type }
180             : { value => $value }
181             ;
182             }
183              
184 0           my %rv = (
185             %list,
186             known_cpe_mac_addresses => [ @mac ],
187             );
188              
189 0           return %rv;
190             }
191              
192             sub signal {
193 0     0 1   my $self = shift;
194 0           my $raw = $self->_get( $self->{page_signal} );
195              
196             # remove junk info, otherwise it will not be parsed correctly
197 0           $raw =~ s{
198            
199             .+?
200             \QThe Downstream Power Level reading is\E
201             .+?
202            
203             }{}xmsi;
204              
205 0           my(%down, %up);
206              
207             my $down_row = sub {
208 0     0     my ( $id, $line, $cols, $udata ) = @_;
209 0           (my $name = lc $cols->[0]) =~ tr/ /_/;
210 0           $down{ $name } = $cols->[1];
211 0           return;
212 0           };
213              
214             my $up_row = sub {
215 0     0     my ( $id, $line, $cols, $udata ) = @_;
216 0           (my $name = lc $cols->[0]) =~ tr/ /_/;
217 0           $up{ $name } = $cols->[1];
218 0           return;
219 0           };
220              
221 0           HTML::TableParser->new(
222             [
223             { id => 1.4, row => $down_row },
224             { id => 1.5, row => $up_row },
225             ],
226             { Decode => 1, Trim => 1, Chomp => 1 },
227             )->parse( $raw );
228              
229 0           foreach my $v (
230             \@up{ qw( frequency power_level symbol_rate ) },
231             \@down{ qw( frequency power_level signal_to_noise_ratio ) },
232             ) {
233 0           my($value, $unit, $status) = split m{\s+}xms, ${$v};
  0            
234 0           ${$v} = {
  0            
235             value => $value,
236             unit => $unit,
237             };
238 0 0         ${$v}->{status} = $status if defined $status;
  0            
239             }
240              
241 0           my %rv = (
242             upstream => { %up },
243             downstream => { %down },
244             );
245              
246 0           return %rv;
247             }
248              
249             sub status {
250 0     0 1   my $self = shift;
251 0           my $raw = $self->_get( $self->{page_status} );
252 0           my %rv;
253              
254             my $cb_row = sub {
255 0     0     my ( $id, $line, $cols, $udata ) = @_;
256 0           (my $name = lc $cols->[0]) =~ tr/ /_/;
257 0           $rv{ $name } = $cols->[1];
258 0           return;
259 0           };
260              
261 0           HTML::TableParser->new(
262             [
263             { id => 1.4, row => $cb_row },
264             { id => 1 , cols => qr/(?:Task|Status)/xmsi },
265             ],
266             { Decode => 1, Trim => 1, Chomp => 1 },
267             )->parse( $raw );
268              
269 0           return %rv;
270             }
271              
272             sub logs {
273 0     0 1   my $self = shift;
274 0           my $raw = $self->_get( $self->{page_logs} );
275 0           my @logs;
276              
277             my $cb_row = sub {
278 0     0     my ( $id, $line, $cols, $udata ) = @_;
279             push @logs, {
280 0           time => shift @{ $cols },
281 0           priority => shift @{ $cols },
282 0           code => shift @{ $cols },
283 0           message => shift @{ $cols },
  0            
284             };
285 0           my $cur = $logs[-1];
286 0           my($pn,$ps) = split m/\-/xms, $cur->{priority};
287             $cur->{priority} = {
288 0           code => $pn,
289             string => $ps,
290             };
291 0 0         $cur->{time} = undef if $cur->{time} eq '************';
292 0           return;
293 0           };
294              
295 0           HTML::TableParser->new(
296             [
297             { id => 1.4, row => $cb_row },
298             { id => 1 , cols => qr/(?:Time|Priority|Code|Message)/xmsi },
299             ],
300             { Decode => 1, Trim => 1, Chomp => 1 },
301             )->parse( $raw );
302              
303 0           return @logs;
304             }
305              
306             sub versions {
307 0     0 1   my $self = shift;
308 0           my $raw = $self->_get( $self->{page_help} );
309 0           my $v;
310 0 0         if ( $raw =~ m{(.+?version.+?)
311 0           ($v = $1) =~ s{
}{}xmsig;
312             }
313             else {
314 0           croak "Can not get version from $self->{page_help} output: $raw"
315             };
316 0           my %rv;
317 0           foreach my $vs ( split m/ \r? \n /xms, $self->_trim( $v ) ) {
318 0           my($name, $value) = split m/ : \s+ /xms, $vs;
319 0           ($name, undef) = split m/ \s+ /xms, $name;
320 0           $rv{ lc $name } = $value;
321             }
322 0           my @soft = split m/ \- /xms, $rv{software};
323             $rv{software} = {
324 0           model => shift @soft,
325             version => shift @soft,
326             string => join( q{-}, @soft ),
327             };
328 0           return %rv;
329             }
330              
331             sub _trim {
332 0     0     my $self = shift;
333 0           my $s = shift;
334 0           $s =~ s{ \A \s+ }{}xmsg;
335 0           $s =~ s{ \s+ \z }{}xmsg;
336 0           return $s;
337             }
338              
339             sub agent {
340 0     0 1   my $self = shift;
341 0           my $ua = LWP::UserAgent->new;
342 0           $ua->agent($AGENT);
343 0           $ua->timeout( UA_TIMEOUT );
344 0           return $ua;
345             }
346              
347             sub _get {
348 0     0     my $self = shift;
349 0           my $url = shift;
350 0           my $r = $self->agent->get($url);
351              
352 0 0         if ( $r->is_success ) {
353 0           my $raw = $r->decoded_content;
354 0 0         HTTP::Error::NotFound->throw(
355             "The address $url is invalid. Server returned a 404 error"
356             ) if $raw =~ RE_404;
357 0           return $raw;
358             }
359              
360 0           return HTTP::Error::Connection->throw(
361             'GET request failed: ' . $r->as_string
362             );
363             }
364              
365             sub _req {
366 0     0     my $self = shift;
367 0           my $req = shift;
368 0           my $r = $self->agent->request($req);
369              
370 0 0         if ( $r->is_success ) {
371 0           my $raw = $r->decoded_content;
372 0 0         HTTP::Error::NotFound->throw(
373             'The request is invalid. Server returned a 404 error'
374             ) if $raw =~ RE_404;
375 0           return $raw;
376             }
377              
378 0           return HTTP::Error::Connection->throw(
379             'HTTP::Request failed: ' . $r->as_string
380             );
381             }
382              
383             1;
384              
385             __END__