http://www.perlmonks.org?node_id=600568

I've recently had to make a number of changes to my Internet connection (local cable company). More specifically, I've had to trade in the old reliable hulk of a modem I originally signed up with for a sleek new Motorola SURFboard due to service upgrades. The new modem is far less tolerant of the poor cable connection that brings the world to my door, and it's been the source of many help desk calls and now quite a few installer visits to locate problems in the cable.

It was during one of these frustrating moments that I googled the modem and discovered it has a built in web interface complete with status page of signal strength conditions. As I was explaining to the help desk person (again) about my loss of Internet, he explained that the modem's signal condition was logged at the office and indeed the modem had reset several thousand times during the last few days (you'd think they would be calling me!). That's when it hit me. Rather than waiting for my Internet service to die without explanation, I could actively monitor the modem for signs of life and get someone looking into the problem before it got so bad that I would have to spend hours (or days) waiting for the 'cable guy' to show up and fix it.

Thus was born modem.pl.

Now when my modem starts getting twitchy, I get an email which is my cue to call the help desk and get somebody working on it, usually before it gets so bad as to cause an Internet outage. I've also got a log file I can refer to which tells me exactly when things started going bad. The only thing sweeter would be to have the cable company do their own monitoring so the cable guy could be waiting at the door before I have to call. But that's beyond Perl's abilities, or is it? ;)

Update: tweaked expression matching for the SB5100E model. Cleaned up commments a little.
Update: Didn't quite get the SB5100E model right first time around :(.
This script is dog simple. It scrapes the modem's status page for the various signal strength values then outputs one line with the 3 most useful values. I have a cron job setup to run it every 5 minutes piping the output to the end of a log file. If there's a problem getting the values, or the values are outside reasonable operating conditions, the output is also sent to STDERR (warning or die) causing the cronjob to email root (or whoever gets notice of cron messages) with the error. I expect most modern cable modems have similar web interfaces and the code could probably be adapted to other brands/models as well.

I'd really appreciate any and all comments regarding this code. Errors, style, faster, smaller, it's all up for criticism.

#!/usr/bin/perl # # modem.pl - Report the status a Motorola 'SURFboard' cable modem # (models: SB5101, SB5100E) # # copyright(C) 2007 Scott Mazur <scott.AT.littlefish.ca> # GNU General Public License # -------------------------------------------------------------------- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, # Boston, MA 02111-1307 USA # requires: # Time::Local; # LWP::Simple; my $ver = '0.11'; # Version 0.11 # - now handles SB5100E model as well # The Motorola 'SURFboard' cable modem includes a built in web # interface that contains useful information like signal to noise # ratios and power levels. # # This script uses LWP:Simple to scrape the status page from the # modem and report the most useful information in a single line: # Down power, Signal to Noise, Up power # # If the script is not running interactively (like say from a cron # job) the output line is prefixed with a standard log time stamp # and Errors/warnings are duplicated to STDOUT and STDERR. This # is so that cron jobs can report errors while still sending # output to a log file use strict; use warnings; use Time::Local; use LWP::Simple; use constant SB5101_URL => 'http://192.168.100.1/RgSignal.asp'; use constant SB5100E_URL => 'http://192.168.100.1/signaldata.html'; # are we running from a shell or maybe crond? use constant I_am_interactive => (-t STDIN and -t STDOUT); # format current time into standard system log file format my $tm_str = localtime; $tm_str = sprintf("$1 % 2d $3", $2) if $tm_str =~ m/^\S+\s+(\S+)\s+(\S+)\s+(\S+)/; # print message in addition to die unless interactive # This lets you pipe STDOUT to a file yet still get # a message on the console (or from cron) sub My_Die { my $msg = shift; print "$tm_str $msg\n" unless I_am_interactive; die "$msg\n"; } # main script ############# # get the modem status page my $content = get(SB5101_URL) || get(SB5100E_URL) || My_Die("Couldn't get modem signal page!"); # clean up the html a bit for parsing $content =~ s/\n//g; # drop new lines $content =~ s!</?t[dr]>! !ig; # strip table tags $content =~ s/\s\s+/ /g; # reduce double spaces # check that the page has what we expect $content =~ m{ Frequency \s (\d+\s Hz) \s (Locked\s)? Signal \s To \s Noise \s Ratio \s ([\d.]+\s dB) \s Power \s Level \s ([\d.-]+\s dBmV) }xi or My_Die('content failed!'); # build a hash of signal strength values my %results = ( DownStream => {freq => $1, sn => $3, power => $4}, UpStream => {channel => '?', freq => '? Hz', power => '0 dBmV'} ); # get the upstream values $results{UpStream} = {channel => $1, freq => $2, power => $5} if $content =~ m/ID (\d+) Frequency (\d+ Hz)( Ranged)? Power( Leve +l)? ([\d.-]+ dBmV)/i; # create the result message my $msg = "Down Power: $results{DownStream}->{power}" . " S/N: $results{DownStream}->{sn}" . " Up Power: $results{UpStream}->{power}"; # check up power for reasonable range my $power = $results{UpStream}->{power}; # strip down to raw number my ($stripped) = $power =~ m/^([\d.-]+)/; my $status = ''; $status = 'Up Power high!' if $stripped > 54; $status = 'Up Power low!' if $stripped < 36; # better call the cable guy! if ($status) { warn "$status $power\n"; $msg .= " warning: $status"; } # check down power for reasonable range $power = $results{DownStream}->{power}; # strip down to raw number ($stripped) = $power =~ m/^([\d.-]+)/; $status = ''; $status = 'Down Power high!' if $stripped > 10; $status = 'Down Power low!' if $stripped < -10; # not good either! if ($status) { warn "$status $power\n"; $msg .= " warning: $status"; } $msg = "$tm_str $msg" unless I_am_interactive; print "$msg\n"; # debug dump all values # foreach (qw(DownStream UpStream)) { # print "$_ - "; # my $hash = $results{$_}; # foreach (sort keys %$hash) { # print " $_: $hash->{$_}"; # } # print "\n"; # } exit;

Replies are listed 'Best First'.
Re: Cable modem status
by rkrieger (Friar) on Feb 17, 2007 at 02:40 UTC

    > Now when my modem starts getting twitchy, I get an email which is my
    > cue to call the help desk and get somebody working on it [...]

    Definitely a good use for Perl. For similar monitoring purposes, we extended the script to also attempt to automatically put the help desk to work when switch ports hit their 8th MAC address and port security kicked in to block new MAC addresses.

    Since you mention your connection is still workable when you get a message, perhaps it's a nice touch to actively alert the helpdesk automatically ;)

    Next thing might be logging their responses and response times to determine their learning curve over the course of time.

      LOL!

      I've already learned I can drastically cut down the time spent determining the problem if I just start the cable guy at the entry point to the home rather than the modem itself. Otherwise the guy will be prone to blaming (and replacing) every wire and connector between the modem and the outlet first before accepting that the poor signal originates from outside the home.

      I've got an insider at the cable company. Maybe I'll just default modem error emails to him if the quality of my service doesn't pick up soon. What's that they say about the squeaky wheel?
Re: Cable modem status
by GrandFather (Saint) on Feb 17, 2007 at 02:52 UTC

    I'd put the license stuff into POD rather than a big (messy to edit) comment. The short comments describing most lines actually obscures the code - I'd put them at the end of the line and omit some of them altogether. Otherwise, good stuff and insperation to do something similar with my modem!


    DWIM is Perl's answer to Gödel
      One thing I didn't like doing was changing the first regex match:
      $content =~ m{ Frequency(\d+\sHz)\s Signal\sTo\sNoise\sRatio([\d.]+\sdB)\s Power\sLevel([\d.-]+\sdBmV) }xi or My_Die('content failed!');
      When I wrote it, it was a much simpler one line, no 'x' expression with spaces instead of '\s' like so:
      $content =~ m/Frequency(\d+ Hz) Signal To Noise Ratio([\d.]+ dB) Power + Level([\d.-]+ dBmV)/i or My_Die('content failed!');
      But the line was too long to fit the line length on Monks and wrapped (like here). So I massaged it to fit within the column limit, but I think the new format is much less readable than the original. Any ideas on how to improve that?

        well, the conventional wisdom is use a module to parse HTML and I was tempted to make that comment first time around. However the regex seemed sufficiently trivial that it didn't seem really to matter. Using something like HTML::TreeBuilder would sure make it easier to dig the data out for a lot of modems though - mine (which uses a table) for a start!

        If you are going to use /x then take advantage of it and use some white space to break up the different components of the regex so they are easier to parse by eye. Consider:

        $content =~ m{ Frequency(\d+\s Hz)\s Signal \s To \s Noise \s Ratio([\d.]+ \s dB)\s Power \s Level([\d.-]+ \s dBmV) }xi or My_Die('content failed!');

        DWIM is Perl's answer to Gödel
Re: Cable modem status
by qbxk (Friar) on Feb 17, 2007 at 16:13 UTC
    Very good ruzam, very good, indeed! Now let's take a look at your score then, shall we?

    Laziness: yep, doesn't want to wait on the phone, and definitely put in more energy on the front to save time later. But you aren't done yet, because the cable guys don't actually show up when they need to yet. I grade 8/10

    Impatience: well yeah, the entire purpose reeks of this. 10/10

    Hubris: you posted it here. and you're surely a ray of sunshine to your cable company's tech support. 10/10 and i'd go higher if i could.

    Total: 28/30, thanks ruzam!

    Let's see if my "media changer" (that changes my municipal fiber line into ethernet) has a web interface too...


    It's not what you look like, when you're doin' what you’re doin'.
    It's what you’re doin' when you’re doin' what you look like you’re doin'!
         - Charles Wright & the Watts 103rd Street Rhythm Band, Express yourself