TP-Link TD-W8961N

November 16th, 2015

  • Product Link
  • Don't buy one
  • If you're too lazy to replace it, and you want to monitor your ADSL stats when it rains...

import telnetlib
import re
import time

class TPW8961N(object):

    def __init__(self, host): = host

    def __enter__(self):
        self.con = telnetlib.Telnet(, 23, 5)
        self.con.read_until('Password: ')
        self.con.read_until('TP-LINK> ')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

    def send_cmd(self, cmd):
        self.con.write(cmd.get_cmd() + '\n')
        response = self.con.read_until('TP-LINK> ')
        return cmd.parse(response)

class Command(object):
    def __init__(self):

    def get_cmd(self):

    def parse(self, string):

class StatusCommand(Command):
    def get_cmd(self):
        return 'wan adsl status'

    def parse(self, response):
        match ='current modem status: (.*?)\r\n', response)
        if match is not None and == 'up':
            return 'up'
            return 'down'

class RateCommand(Command):
    def get_cmd(self):
        return 'wan adsl c'

    def parse(self, response):
        dl_match ='near-end interleaved channel bit rate: (.*?) kbps\r\n', response)
        ul_match ='far-end interleaved channel bit rate: (.*?) kbps\r\n', response)

        if dl_match is None or ul_match is None:
            return {'dl_rate': 0, 'ul_rate': 0}

        return {'dl_rate': int(, 'ul_rate': int(}

class QualityCommand(Command):
    def __init__(self, direction):
        if direction not in ['downstream', 'upstream']:
            raise Exception('Unknown direction, must be "downstream" or "upstream"')
        self.direction = direction
        super(Command, self).__init__()

    def get_cmd(self):
        if self.direction == 'downstream':
            return 'wan adsl l n'
        return 'wan adsl l f'

    def parse(self, response):
        noise_match ='noise margin ' + self.direction + ': (.*?) db\r\n', response)
        attenuation_match ='attenuation ' + self.direction + ': (.*?) db\r\n', response)

        if noise_match is None or attenuation_match is None:
            return {'noise': 0.0, 'attenuation': 0.0}

        return {'noise': float(, 'attenuation': float(}

if __name__ == '__main__':

    while True:
            print time.strftime('%Y-%m-%d %H:%M-%S')
            with TPW8961N('') as modem:
                print modem.send_cmd(StatusCommand())
                print modem.send_cmd(RateCommand())
                print modem.send_cmd(QualityCommand('downstream'))
                print modem.send_cmd(QualityCommand('upstream'))
        except KeyboardInterrupt:
        except e:
            print e