A while back now, I had the idea to write a tool to observe the status of my
cable modem and came up with the following python script. It should work with
the Scientific Atlanta cable modem, given to my by Unitymedia. It provides a
system page, where no login credentials are required. The values read from the
status page are stored in a rrd database and allows you to create nice graphs
using rrdtool. I also prepared a little demo
graph, where you can see, that I have been experiencing connection problems, by
looking at the SNR1). You can also see, when they start
fixing my port2).
#!/usr/bin/env python import urllib2 from BeautifulSoup import BeautifulSoup from lxml import etree from StringIO import StringIO import sys, time import rrdtool import signal # URL to the sytem status frame URL = 'http://192.168.100.1/system.asp' is_looping = False def rrdNew(filename): ret = rrdtool.create(filename, "--step", "300", "--start", '0', "DS:rpl:GAUGE:600:U:U", "DS:tpl:GAUGE:600:U:U", "DS:snr:GAUGE:600:U:U", "RRA:MIN:0.5:1:600", "RRA:MIN:0.5:6:700", "RRA:MIN:0.5:24:775", "RRA:MIN:0.5:288:797", "RRA:AVERAGE:0.5:1:600", "RRA:AVERAGE:0.5:6:700", "RRA:AVERAGE:0.5:24:775", "RRA:AVERAGE:0.5:288:797", "RRA:MAX:0.5:1:600", "RRA:MAX:0.5:6:700", "RRA:MAX:0.5:24:775", "RRA:MAX:0.5:444:797") if ret: print 'rrdNew:', rrdtool.error() def rrdGraph(imgfile, rrdfile): print 're-generating graph...' rrdtool.graph(imgfile, "--start", "-1d", "--vertical-label=dBmV\dB", "--width", "600", "DEF:receive=%s:rpl:AVERAGE" %rrdfile, "DEF:transmit=%s:tpl:AVERAGE" %rrdfile, "DEF:signal=%s:snr:AVERAGE" %rrdfile, "AREA:receive#00FF00:Receive Power Level", "LINE1:transmit#0000FF:Transmit Power Level", "LINE1:signal#FF0000:Signal to Noise Ratio\\r", "CDEF:rpldbmv=receive,1,*", "CDEF:tpldbmv=transmit,1,*", "CDEF:snrdb=signal,1,*", "COMMENT:\\n", "GPRINT:rpldbmv:AVERAGE:Avg Receive Power Level\: %6.2lf dBmV", "GPRINT:rpldbmv:MAX:Max Receive Power Level\: %6.2lf dBmV\\r", "GPRINT:rpldbmv:MIN:Min Receive Power Level\: %6.2lf dBmV\\r", "COMMENT:\\n", "GPRINT:tpldbmv:AVERAGE:Avg Transmit Power Level\: %6.2lf dBmV", "GPRINT:tpldbmv:MAX:Max Transmit Power Level\: %6.2lf dBmV\\r", "GPRINT:tpldbmv:MIN:Min Transmit Power Level\: %6.2lf dBmV\\r", "COMMENT:\\n", "GPRINT:snrdb:AVERAGE:Avg Signal to Noise Level\: %6.2lf dB", "GPRINT:snrdb:MAX:Max Avg Signal to Noise Level\: %6.2lf dB\\r", "GPRINT:snrdb:MIN:Min Avg Signal to Noise Level\: %6.2lf dB\\r") def rrdUpdate(rrdfile, data): print 'update data:', data ret = rrdtool.update(rrdfile, data) if ret: print 'rrdUpdate:', rrdtool.error() def refresh(): print 'refreshing...' try: page = urllib2.urlopen(URL) except: # Poor mans exception; no infos, just skip the processing print 'refresh aborted.' return soup = BeautifulSoup(page) # Remove markups, because they break the XML parser. f = StringIO(str(soup.findAll('tbody')[0]).replace(' ', '')) dom = etree.parse(f) nodes = dom.xpath('//font') # We take every value we can get, but do not use them, yet modemvalues = [] for i in xrange(0, len(nodes), 2): modemvalues.append((nodes[i].text, nodes[i+1].text.strip())) # Prepare the values and cut of the string at the end rpl = float(modemvalues[4][1].split(' ')[0]) # dBmV tpl = float(modemvalues[5][1].split(' ')[0]) # dBmV snr = float(modemvalues[6][1].split(' ')[0]) # dB rrdUpdate('modem.rrd', 'N:%f:%f:%f' %(rpl, tpl, snr)) def handler(signum, frame): global is_looping if signum == signal.SIGHUP: rrdGraph('modem.png', 'modem.rrd') elif signum == signal.SIGQUIT: print 'Graceful shutdown received. Please wait...' is_looping = False elif signum == signal.SIGUSR1: print 'Premature status update...' refresh() if '-n' in sys.argv: print 'resetting database...' rrdNew('modem.rrd') sys.exit(0) if '-g' in sys.argv: rrdGraph('modem.png', 'modem.rrd') sys.exit(0) if '-l' in sys.argv: signal.signal(signal.SIGHUP, handler) signal.signal(signal.SIGQUIT, handler) signal.signal(signal.SIGUSR1, handler) print 'SIGHUP handler registered: Will draw the graph when signaled.' print 'SIGQUIT handler registered: Graceful shutdown.' print 'SIGUSR1 handler registered: Force premature status update.' is_looping = True while is_looping: refresh() print 'Sleeping for 300 seconds...' time.sleep(300) else: refresh()
Commandline switches:
./modemstatd.py -<Switch>
Switch Description ========= ================================================================= -n Create a new "modem.rrd" file. Overwrites the old one! -g Render the graph and stores the image as "modem.png". -l The script will loop and update the data every 300 seconds.
Signals (when looping):
kill -<SIGNAL> <pid>
SIGNAL Description ========= ================================================================= HUP Draws the graph and stores it as "modem.png". QUIT Graceful shutdown, terminate the loop after the sleep finished. USR1 Premature update of modem status.