Skip to content

Instantly share code, notes, and snippets.

@stephenhouser
Forked from jceloria/telegraf-arris.py
Last active January 4, 2019 22:41
Show Gist options
  • Save stephenhouser/b042be2a0881c10e6a49661f079b1410 to your computer and use it in GitHub Desktop.
Save stephenhouser/b042be2a0881c10e6a49661f079b1410 to your computer and use it in GitHub Desktop.
Telegraf input plugin for Arris TM1602AP2
[[inputs.exec]]
commands = ["/usr/local/bin/telegraf-arris.py -u", "/usr/local/bin/telegraf-arris.py -d"]
data_format = "json"
name_override = "arris_modem"
tag_keys = ["channel_id", "stream"]
[[inputs.exec]]
commands = ["/usr/local/bin/telegraf-arris.py -s"]
data_format = "json"
name_override = "arris_system"
json_time_key = "time"
json_time_format = "unix"
json_string_fields = ["status", "uptime_format"]

Telegraf plugin to collect up/down/status from Arris Cable Modems.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
########################################################################################################################
"""
Telegraf input plugin for Arris TM1602AP2
Copyright © 2018 by John Celoria.
Status collection Copyright © 2018 by Stephen Houser.
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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
########################################################################################################################
# Library imports
from __future__ import print_function
from bs4 import BeautifulSoup
import argparse
import json
import re
import sys
from datetime import datetime
try:
# For Python 3.0 and later
from urllib.request import urlopen
except ImportError:
# Fall back to Python 2's urllib2
from urllib2 import urlopen
########################################################################################################################
# Set some defaults
__author__ = 'John Celoria'
__version__ = '0.1'
MODEM_URL = 'http://192.168.100.1/cgi-bin/status_cgi'
########################################################################################################################
def getUptimeInformation(html):
# Get uptime text and Convert to seconds
# System Uptime: 2 d: 0 h: 24 m
uptimeText = html.findAll("tr")[0].findAll("td", recursive=False)[1].text.strip()
(d, _, h, _, m, _) = uptimeText.split()
uptime = ((int(d) * 24 * 60) + (int(h) * 60) + int(m)) * 60
# Computers Detected: staticCPE(1), dynamicCPE(1)
computers = html.findAll("tr")[1].findAll("td", recursive=False)[1].text.strip()
staticCPE = 0
m = re.search(r'staticCPE\((\d+)\)', computers)
if m:
staticCPE = int(m.group(1))
dynamicCPE = 0
m = re.search(r'dynamicCPE\((\d+)\)', computers)
if m:
dynamicCPE = int(m.group(1))
# CM Status: OPERATIONAL
cmStatus = html.findAll("tr")[2].findAll("td", recursive=False)[1]
# Time and Date: Fri 2019-01-04 15:50:54
arrisTimeText = html.findAll("tr")[3].findAll("td", recursive=False)[1]
arrisTime = datetime.strptime(arrisTimeText.text.strip(), '%a %Y-%m-%d %H:%M:%S')
arrisTimeSeconds = int((arrisTime - datetime.fromtimestamp(0)).total_seconds())
uptimeInformation = {
"uptime": uptime,
"uptime_format": uptimeText,
"time": arrisTimeSeconds,
"status": cmStatus.text.strip(),
"static_cpe": staticCPE,
"dynamic_cpe": dynamicCPE
}
return uptimeInformation
def getUpstreamInformation(html, channels):
# Empty list and dictionary
AllChannelInfo = []
CurrentChannelInfo = {}
# Loop through all detected channels
for idx in range(0, channels - 1):
# Scrape information from HTML tables
ChannelID = html.findAll("tr")[idx + 2].findAll("td", recursive=False)[1]
Frequency = html.findAll("tr")[idx + 2].findAll("td", recursive=False)[2]
PowerLevel = html.findAll("tr")[idx + 2].findAll("td", recursive=False)[3]
SymbolRate = html.findAll("tr")[idx + 2].findAll("td", recursive=False)[5]
Modulation = html.findAll("tr")[idx + 2].findAll("td", recursive=False)[6]
# Put information from current channel into dictionary
CurrentChannelInfo = {
"Channel ID": ChannelID.text.strip(),
"Frequency": Frequency.text.strip(),
"Power Level": PowerLevel.text.strip(),
"Symbol Rate": SymbolRate.text.strip(),
"Modulation": Modulation.text.strip()
}
# Add dictionary to list
AllChannelInfo.append(CurrentChannelInfo)
return AllChannelInfo
def getDownstreamInformation(html, channels):
# Empty list and dictionary
AllChannelInfo = []
CurrentChannelInfo = {}
# Loop through all detected channels
for idx in range(0, channels):
# Scrape information from HTML tables
ChannelID = html.findAll("tr")[idx + 1].findAll("td", recursive=False)[1]
Frequency = html.findAll("tr")[idx + 1].findAll("td", recursive=False)[2]
PowerLevel = html.findAll("tr")[idx + 1].findAll("td", recursive=False)[3]
SNR = html.findAll("tr")[idx + 1].findAll("td", recursive=False)[4]
Modulation = html.findAll("tr")[idx + 1].findAll("td", recursive=False)[5]
CorrectableCodewords = html.findAll("tr")[idx + 1].findAll("td", recursive=False)[7]
UncorrectableCodewords = html.findAll("tr")[idx + 1].findAll("td", recursive=False)[8]
# Put information from current channel into dictionary
CurrentChannelInfo = {
"Channel ID": ChannelID.text.strip(),
"Frequency": Frequency.text.strip(),
"Power Level": PowerLevel.text.strip(),
"SNR": SNR.text.strip(),
"Modulation": Modulation.text.strip(),
"Correctable Codewords": CorrectableCodewords.text.strip(),
"Uncorrectable Codewords": UncorrectableCodewords.text.strip()
}
# Add dictionary to list
AllChannelInfo.append(CurrentChannelInfo)
return AllChannelInfo
def normalize(obj):
return [{k.replace(" ", "_").lower(): float(re.sub("[^0-9\.-]", "", re.sub("----", "0", v))) for k, v in d.items()} for d in obj]
def main(arguments):
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("-d", "--downstream", help="Return downstream information", action="store_true")
parser.add_argument("-u", "--upstream", help="Return upstream information", action="store_true")
parser.add_argument("-s", "--status", help="Return system status information (uptime)", action="store_true")
parser.add_argument("-r", "--raw", help="Display unmodified raw data", action="store_true")
args = parser.parse_args(arguments)
if not (args.downstream or args.upstream or args.status):
parser.error('Please specify one of -d, -u, or -s options')
# Request status_cgi and initialize BeautifulSoup
resp = urlopen(MODEM_URL)
body = resp.read()
soup = BeautifulSoup(body, 'html.parser')
# Parse HTML tables
ustream = soup.findAll("table")[3]
dstream = soup.findAll("table")[1]
# Get the number of channels in use by counting rows
uschan = len(ustream.findAll("tr")) - 1
dschan = len(dstream.findAll("tr")) - 1
if args.upstream:
upstream_list = getUpstreamInformation(ustream, uschan)
if not (args.raw):
upstream_list = normalize(upstream_list)
# Add telegraf tag
[d.update({'stream': 'upstream'}) for d in upstream_list]
# JSON-ify our lists
print(json.dumps(upstream_list, indent=2, sort_keys=True))
if args.downstream:
downstream_list = getDownstreamInformation(dstream, dschan)
if not (args.raw):
downstream_list = normalize(downstream_list)
# Add telegraf tag
[d.update({'stream': 'downstream'}) for d in downstream_list]
# JSON-ify our lists
print(json.dumps(downstream_list, indent=2, sort_keys=True))
if args.status:
uptime_info = getUptimeInformation(soup.findAll("table")[5])
print(json.dumps(uptime_info, indent=2, sort_keys=True))
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
########################################################################################################################
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment