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