Telnet modem stats (TP-Link Archer VR600 v2.0)

I wanted to share my appdaemon script for getting data from TP-Link Archer VR600 modem using telnet.
This device has a snmp built in but there is no useful information on the snmp agent. It is also not possible to extend the snmp agent. I couldn’t find any other way other than telnet querying the modem to get dsl line values.
If anyone can improve upon it, feel free, I am not really a good programmer.

import appdaemon.plugins.hass.hassapi as hass
import sys, datetime, time, telnetlib

class TelnetDSL(hass.Hass):
    def initialize(self):
        runtime = datetime.time(0, 0, 0)
        self.run_minutely(self.getdata, runtime)
    def getdata(self, kwargs):
        HOST = "192.168.0.1"
        password = "pass"
        port = 23
        try:
            self.log('Opening Telnet Connection to Modem.', level = "INFO")
            with telnetlib.Telnet(HOST,port,timeout=10) as tn:
                tn.read_until(b'password:', 10)
                self.log('Sending password to Modem.', level = "INFO")
                tn.write((password + '\r\n').encode('ascii'))
                time.sleep(1)
                tn.read_until(b'(conf)#', 10)
                self.log('Querying for DSL Line Values!', level = "INFO")
                tn.write(('adsl show info' + '\r\n').encode('ascii'))
                time.sleep(1)
                try:
                    rawdata = tn.read_until(b'cmd:SUCC', 10).decode('utf-8').replace('\r','').splitlines()
                    rem_items = {'{','}','','cmd:SUCC','adsl show info'}
                    newlist = [ele for ele in rawdata if ele not in rem_items]
                    alldict = dict([x.split('=') for x in newlist])
                    keys = ['status','upstreamCurrRate','downstreamCurrRate','upstreamMaxRate','downstreamMaxRate','upstreamNoiseMargin','downstreamNoiseMargin','upstreamAttenuation','downstreamAttenuation']
                    datadict = {x:alldict[x] for x in keys}
                    datadict['upstreamNoiseMargin'] = float(datadict['upstreamNoiseMargin'])/10
                    datadict['downstreamNoiseMargin'] = float(datadict['downstreamNoiseMargin'])/10
                    datadict['upstreamAttenuation'] = float(datadict['upstreamAttenuation'])/10
                    datadict['downstreamAttenuation'] = float(datadict['downstreamAttenuation'])/10
                except:
                    self.log("Telnet data cannot be proccessed!!!", level = "ERROR")
                    self.set_unv()
                    return
                try:
                    self.set_state("sensor.modem_status", state=datadict['status'], attributes={"friendly_name":"Modem Status", "icon":"mdi:circle-outline"})
                    self.set_state("sensor.modem_current_rate_upload", state=datadict['upstreamCurrRate'], attributes={"friendly_name":"Modem Current Rate Upload", "icon":"mdi:mdi:phone-outgoing", "unit_of_measurement":"kbps"})
                    self.set_state("sensor.modem_current_rate_download", state=datadict['downstreamCurrRate'], attributes={"friendly_name":"Modem Current Rate Download", "icon":"mdi:phone-incoming", "unit_of_measurement":"kbps"})
                    self.set_state("sensor.modem_max_rate_upload", state=datadict['upstreamMaxRate'], attributes={"friendly_name":"Modem Max Rate Upload", "icon":"mdi:phone-outgoing-outline", "unit_of_measurement":"kbps"})
                    self.set_state("sensor.modem_max_rate_download", state=datadict['downstreamMaxRate'], attributes={"friendly_name":"Modem MAX Rate Download", "icon":"mdi:mdi:phone-incoming-outline", "unit_of_measurement":"kbps"})
                    self.set_state("sensor.modem_snr_margin_upload", state=datadict['upstreamNoiseMargin'], attributes={"friendly_name":"Modem SNR Upload", "icon":"mdi:phone-missed-outline", "device_class":"signal_strength","unit_of_measurement":"dB"})
                    self.set_state("sensor.modem_snr_margin_download", state=datadict['downstreamNoiseMargin'], attributes={"friendly_name":"Modem SNR Download", "icon":"mdi:phone-missed-outline", "device_class":"signal_strength","unit_of_measurement":"dB"})
                    self.set_state("sensor.modem_line_attenuation_upload", state=datadict['upstreamAttenuation'], attributes={"friendly_name":"Modem Line Attenuation Upload", "icon":"mdi:phone-plus", "device_class":"signal_strength","unit_of_measurement":"dB"})
                    self.set_state("sensor.modem_line_attenuation_download", state=datadict['downstreamAttenuation'], attributes={"friendly_name":"Modem Line Attenuation Download", "icon":"mdi:phone-minus", "device_class":"signal_strength","unit_of_measurement":"dB"})                      
                except:
                    self.log("Telnet sensors cannot be updated", level = "ERROR")
                    self.set_unv()
                self.log(datadict, level = "INFO")
        except EOFError:
            self.log("Telnet: Unexpected response from Modem", level = "ERROR")
            self.set_unv()
        except ConnectionRefusedError:
            self.log("Telnet connection refused by Modem. Telnet enabled?", level = "ERROR")
            self.set_unv()
        except:
            self.log("Telnet Error", level = "ERROR")
            self.set_unv()
    
    def set_unv(self):
        st = 'Unavailable'
        self.set_state("sensor.modem_status", state=st, attributes={"friendly_name":"Modem Status", "icon":"mdi:circle-outline"})
        self.set_state("sensor.modem_current_rate_upload", state=st, attributes={"friendly_name":"Modem Current Rate Upload", "icon":"mdi:mdi:phone-outgoing", "unit_of_measurement":"kbps"})
        self.set_state("sensor.modem_current_rate_download", state=st, attributes={"friendly_name":"Modem Current Rate Download", "icon":"mdi:phone-incoming", "unit_of_measurement":"kbps"})
        self.set_state("sensor.modem_max_rate_upload", state=st, attributes={"friendly_name":"Modem Max Rate Upload", "icon":"mdi:phone-outgoing-outline", "unit_of_measurement":"kbps"})
        self.set_state("sensor.modem_max_rate_download", state=st, attributes={"friendly_name":"Modem MAX Rate Download", "icon":"mdi:mdi:phone-incoming-outline", "unit_of_measurement":"kbps"})
        self.set_state("sensor.modem_snr_margin_upload", state=st, attributes={"friendly_name":"Modem SNR Upload", "icon":"mdi:phone-missed-outline", "device_class":"signal_strength","unit_of_measurement":"dB"})
        self.set_state("sensor.modem_snr_margin_download", state=st, attributes={"friendly_name":"Modem SNR Download", "icon":"mdi:phone-missed-outline", "device_class":"signal_strength","unit_of_measurement":"dB"})
        self.set_state("sensor.modem_line_attenuation_upload", state=st, attributes={"friendly_name":"Modem Line Attenuation Upload", "icon":"mdi:phone-plus", "device_class":"signal_strength","unit_of_measurement":"dB"})
        self.set_state("sensor.modem_line_attenuation_download", state=st, attributes={"friendly_name":"Modem Line Attenuation Download", "icon":"mdi:phone-minus", "device_class":"signal_strength","unit_of_measurement":"dB"})                      
3 Likes

Trying this now. After some false starts it has started collecting data. Thanks for writing this!

Thanks, it works fine, have you think in a switch to reboot the device?

Hi,

can you tell me how do i implement this? I’m not very experienced wiht scripting in HA and i don’t know where to start.

Ah, okay - i found some starting point: You have to add the Add-on “AppDaemon”. After that you must open the file editor to paste the code in a new file like “ModemTelnet.py”

Thank you - best regards, machnetz

I did implement a reboot swith using a “wake on lan” switch of HA.

This is the wol switch I created.

  #TP-LINK MODEM
- platform: wake_on_lan
  name: "TPLINK Modem WoL"
  mac: "B0:4E:26:E2:54:3E"
  host: "192.168.0.1"
  turn_off:
    service: script.modem_reboot_appdaemon_dummy

I also created a dummy script
modem_reboot_appdaemon_dummy
It is just a script without any action or code in it. Hope it helps.

Then I created the appdaemon program:

import appdaemon.plugins.hass.hassapi as hass
import sys, telnetlib, time

class TelnetReboot(hass.Hass):
    def initialize(self):
        self.listen_event(self.reboot, event="call_service", service="modem_reboot_appdaemon_dummy")
    def reboot(self, event_name, data, kwargs):
        HOST = "192.168.0.1"
        password = "11111111"
        port = 23
        try:
            self.log('Opening Telnet Connection to Modem.', level = "INFO")
            with telnetlib.Telnet(HOST,port,timeout=10) as tn:
                tn.read_until(b'password:', 10)
                self.log('Sending password to Modem.', level = "INFO")
                tn.write((password + '\r\n').encode('ascii'))
                time.sleep(1)
                tn.read_until(b'(conf)#', 10)
                self.log('Rebooting the Modem!!!', level = "WARNING")
                tn.write(('dev reboot' + '\r\n').encode('ascii'))
                time.sleep(5)
                
        except EOFError:
            self.log("Telnet: Unexpected response from Modem", level = "ERROR")
        except ConnectionRefusedError:
            self.log("Telnet connection refused by Modem. Telnet enabled?", level = "ERROR")
        except:
            self.log("Telnet Error", level = "ERROR")