DslMon

Power Cycle your DSL modem automatically whenever the net goes down.

I love DSL -- You are reading this page right now from a site I have hosted in my home using a 1.5Mbit down/768kbit uplink that I have from Speakeasy for about $100/month. Unfortunately, every provider I have had so far has the same problem with the modems. Every so often, the connection to the gateway server gets interrupted, and the only way to restore it is to turn the DSL modem off and on. I am convinced this is just their way of making me buy a business account instead of a consumer grade account. I have called customer support a few times at both companies, and each time they make some noise about how the modem should not do that, or my line has noise in it, or some other reason that does not address the problem. I have replaced modems, had my line inspected, the whole nine yards. It became clear to me that the real reason I am frustrated is because I can see a fifteen dollar solution right in front of me that these large companies do not see. Short of rewriting the crappy firmware in the modem, why not just power cycle the modem automatically when the net goes down?

My solution is to add some simple hardware to the modem's power supply cable to create a parallel port interface to trigger a relay that will cut the 12v dc power into the modem via software. The software is simply a Perl script that pings the gateway every few seconds, and determines when is a good time to turn the modem off and on to restore the connection, as well as log the results to see how well it's doing.

First, the hardware mods. I got the idea from the Linux Coffee HOWTO. My original goal was to make a circuit that could turn off and on any AC power load, like making a light switch that operates from the state of a pin on a serial port. I soon discovered that it is much easier to use the parallel port, since it is already at TTL levels. However, I got sidetracked into wanting to make this as cheap and compact as possible, and was trying to use a TRIAC so that I would not need any external wall wart power supply. Well, I wasted a few days trying out different TRIAC circuits, and could never get it to work flawlessly, and I attributed that to the fact that the load is mostly inductive, since it is going into a power transformer. I also am not great at circuit design, so I decided to change my assumptions to make this thing simpler.

I next decided that besides me not being able to get the TRIAC circuit working right, it also has the problem that in default state when there is no voltage on the gate pin (the one being powered by the parallel port from the computer) the switch is off. This might cause problems while the computer is booting, since the DSL modem will get powered down any time the detect computer is shut down or rebooted, and not come back on until a 1 is written to the port. I preferred that my circuit be normally on all the time, and only switched off when the parallel port goes true. It is at this point that I went full force into the 12v DC relay option. Another nice thing about the relay in the normally on position is that it will only consume power during the small amount of time that the computer is holding the DSL modem power off, which is better than the triac solution, which wastes power during the normally-on time. Still one nicer thing is that I can hear a slight audible click when the power is turned off or on, so I know when a power cycle is happening if I am in the room.

As it turned out, Radio Shack in Redwood City has a much better selection of project cases, cheap relays, diodes and resistors that Fry's in Palo Alto, and the line is shorter. I still had the issue of not wanting another wall wart on my power strip, and that's when I got the idea of using the DSL modem's own 12V DC power supply to power the relay circuit. This involved cutting the cord, which at first I was against, but since I've grown to hate DSL modems so much, I decided that it deserved it. Plus, I have so many 12v DC power supplies in my closet, if I wreck this one, who cares?

The other problem with using the DSL's own 12v dc lifeline to power my relay is that this circuit will not work for arbitrary appliances or voltages -- it will need to be customized for each person's modem, and therefore does not make for an easily shrink-wrapped product. Oh well. At this point, I only really care about my own personal modem's functionality -- if this works, maybe later I'll generalize it for the mass public.

Here is the circuit I used, taken pretty much from the Coffee HOWTO:

                              normally closed
             Relay switch   +-------------------> to DSL Modem
                 +----------o                    +12V Power In
+12V             |             
From DSL         |          +---> No connect  
Power   >--------+-------+    normally open
Supply         __|__     |     
         Diode  /^\    Relay   
        1N4002 /---\    Coil              
                 |       |     
                 +-------+     
                     |                     
parallel             | / 
port       4.7K    B |/  C  NPN Transistor
Pin 1   >-\/\/\/\/---|      BC547A or 2N2222A 
                     |\  E        
parallel               | V    
port                   |
Ground  >--------------+
                       |
Ground                 |
From DSL               |
Power   >--------------+------------------------> to DSL Modem
Supply                                            Gnd Power In

Now I needed a way to turn pin 1 of the parallel port on and off. Luckily, I don't have my printer hooked up to the parallel port, as it is a USB printer. In fact, my printer is on my Windows machine, and I am putting my server stuff on my Linux server, so there are now two reasons why the parallel port goes unused. If you were using your parallel port for a printer, I'm sure there are some pins that are not used by the printer that you could finangle to turn off and on via software control. But since the whole port in unused for me at the moment, I am going for the most obvious choice -- data pin 1.

Since I am using Linux for this job, I need a linux program that can write to the port hardware. I found one here written by Tomi Engdahl. The only trick to this program is that you have to run it with root privileges. Here the complete program I used.

/*
 *  * Simple parallel port output control program for Linux
 *   * Written and copyright by Tomi Engdahl 1998
 *    * (e-mail: tomi.engdahl@hut.fi)
 *     *
 *      * The program output the data value to PC parallel port data pins
 *       * (default lpt1 I/O address 0x378). The data values are given as the
 *        * command line parameter to the program. The number can be
 *         * in decimal (0..255) or hexadecimal format (0x00..0xFF).
 *          *
 *           */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <asm/io.h>

#define base 0x378           /* printer port base address */

main(int argc, char **argv)
{                    
    int value;

    if (argc!=2) fprintf(stderr, 
        "Error: Wrong number of arguments. This program needs one "
        "argument which is number between 0 and 255.\n"), exit(1);
    if (sscanf(argv[1],"%i",&value)!=1) fprintf(stderr, 
        "Error: Parameter is not a number.\n"), exit(1);
    if ((value<0) || (value>255)) fprintf(stderr, 
        "Error: Invalid numeric value. The parameter number must be "
        "between 0 and 255\n"), exit(1);
    if (ioperm(base,1,1)) fprintf(stderr, 
        "Error: Couldn't get the port at %x\n", base), exit(1);

    outb((unsigned char)value, base);

    return 0;
}                                            

Compile this with a line such as:

gcc -O lptout.c -o lptout

Now, I can test that the circuit is working by sending

lptout 1

to trigger the relay to turn off the modem and

lptout 0

to turn it back on. After I verified that this worked, I went on to write this perl script:

#! /usr/bin/perl

use Net::Ping;


# DslMon.pl
# (c) 2002 Tom Wuttke -- consider this to be public domain
# http://schmail.com/dslmon
# perl script to power cycle DSL modem when net goes down



# All time in seconds

$timeToCheckStatus = 10; 
$timeToLogDown = 1;
$timeToReboot = 10;
$timeToKeepPowerOff = 10;
$timeToRebootAgain = 60 * 10;



$modemOff = "/usr/local/bin/lptout 1"; # change these paths for your system!!
$modemOn = "/usr/local/bin/lptout 0"; # change these paths for your system!!
$mygateway = "123.123.123.123"; # put your real gateway IP here!!!
$mylogfile = "/var/log/dslmon.log"; # change this to suit your needs


sub mylog
{
    my $status = shift(@_);
    my $shift = shift(@_);

    if (open(LOG,">> $mylogfile"))
    {
       $timeString = localtime(time() - $shift);
 
       print LOG "$status $timeString \n";
       close LOG;
    }
};


for(;;)
{
    $p = Net::Ping->new("icmp", 1);
    if ($p->ping($mygateway))
    {
        mylog("UP:)") if (!$started || $downTime > $timeToLogDown);
        $downTime = 0;
        $upTime += $pollInterval;
        sleep($pollInterval);
    }
    else
    {
        mylog("DOWN", $downTime) if (!$started || $downTime == $timeToLogDown);
        $upTime = 0;
        $downTime++;
        if ($downTime == $timeToRebootAgain)
        {
            $downTime = 1;
        }
        if ($downTime == $timeToReboot)
        {
            mylog("POFF");
            system($modemOff) == 0 or die "system $modemOff failed: $?"  
        }
        if ($downTime == $timeToReboot + $timeToKeepPowerOff)
        {
            mylog("PON ");
            system($modemOn) == 0 or die "system $modemOn failed: $?"  
        }
    }
    $started = 1;
    $p->close();
}

I save this as /usr/local/bin/dslmon.pl, making sure to give it execute permissions with chmod. Also, make sure that the gateway and all the hard paths are set to proper values.

I run this script as part of my rc.local script during startup. If I want to run it interactively in a term window, I use this command:

nohup /usr/local/bin/dslmon.pl &

The ampersand puts the job in the background, and nohup prevents the job from shutting down when I log out of the term window.

The logic in this script can be tweaked for your own desired behavior. I have found that polling every 1 second, and waiting until ten consecutive failures have occurred seems to be a good balance of responsiveness vs avoiding false alarms. My modem seems to need restarting zero to four times a day. Sometimes it goes several days with no reported problems.

To easily view the recent status of my DSL modem activity log, I made this apache/php web page to check that from any web browser:

<html>
<body>

Dsl Status: <br>

<pre>
<?php
system("tail -n50 /var/log/dslmon.log | tac");
?>
</pre>

</body>
</html>

And that's it. Here is a sample log of my DSL activity:

Dsl Status: 

UP:) Mon Sep 23 13:33:50 2002 
DOWN Mon Sep 23 13:33:48 2002 
UP:) Mon Sep 23 00:21:18 2002 
PON  Mon Sep 23 00:20:47 2002 
POFF Mon Sep 23 00:20:37 2002 
DOWN Mon Sep 23 00:20:28 2002 
UP:) Sun Sep 22 21:21:21 2002 
DOWN Sun Sep 22 21:21:20 2002 
UP:) Sun Sep 22 10:43:36 2002 
DOWN Sun Sep 22 10:43:35 2002 
UP:) Sun Sep 22 10:43:34 2002 
DOWN Sun Sep 22 10:43:33 2002 
UP:) Sat Sep 21 23:15:06 2002 
PON  Sat Sep 21 23:14:34 2002 
POFF Sat Sep 21 23:14:24 2002 
DOWN Sat Sep 21 23:14:15 2002 
UP:) Sat Sep 21 23:14:04 2002 
PON  Sat Sep 21 23:13:33 2002 
POFF Sat Sep 21 23:13:23 2002 
DOWN Sat Sep 21 23:13:14 2002 
UP:) Sat Sep 21 23:13:11 2002 
DOWN Sat Sep 21 23:13:09 2002 
UP:) Sat Sep 21 23:10:54 2002 
DOWN Sat Sep 21 23:10:53 2002 
UP:) Sat Sep 21 23:10:44 2002 
DOWN Sat Sep 21 23:10:43 2002 
UP:) Sat Sep 21 23:10:37 2002 
DOWN Sat Sep 21 23:10:36 2002 
UP:) Sat Sep 21 23:10:26 2002 
PON  Sat Sep 21 23:09:55 2002 
POFF Sat Sep 21 23:09:45 2002 
DOWN Sat Sep 21 23:09:36 2002 
UP:) Sat Sep 21 23:09:00 2002 
DOWN Sat Sep 21 23:08:59 2002 
UP:) Sat Sep 21 23:07:49 2002 
DOWN Sat Sep 21 23:07:48 2002 
UP:) Sat Sep 21 23:07:44 2002 
PON  Sat Sep 21 23:05:58 2002 
POFF Sat Sep 21 23:05:48 2002 
DOWN Sat Sep 21 23:05:39 2002 
UP:) Sat Sep 21 23:05:34 2002 
PON  Sat Sep 21 23:04:45 2002 
POFF Sat Sep 21 23:04:35 2002 
DOWN Sat Sep 21 23:04:26 2002 
UP:) Sat Sep 21 15:01:42 2002 
DOWN Sat Sep 21 15:01:41 2002 
UP:) Sat Sep 21 00:34:22 2002 
DOWN Sat Sep 21 00:34:21 2002 
UP:) Fri Sep 20 20:15:22 2002 
DOWN Fri Sep 20 20:15:21 2002 

Please let me know if you find this page useful, or have any comments/corrections.