Added support for Netgear wireless routers (tested with R6300)
This commit is contained in:
parent
9518a2a0b7
commit
e3b00ffc71
3 changed files with 147 additions and 5 deletions
|
@ -16,7 +16,13 @@ It is currently able to do the following things:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
It currently works with any wireless router running [Tomato firmware](http://www.polarcloud.com/tomato) in combination with [Philips Hue](http://meethue.com) and the [Google Chromecast](http://www.google.com/intl/en/chrome/devices/chromecast). The system is built modular so support for other wireless routers, other devices or actions can be implemented easily.
|
Current compatible devices:
|
||||||
|
* Wireless router running [Tomato firmware](http://www.polarcloud.com/tomato)
|
||||||
|
* Netgear wireless routers (tested with R6300)
|
||||||
|
* [Philips Hue](http://meethue.com)
|
||||||
|
* [Google Chromecast](http://www.google.com/intl/en/chrome/devices/chromecast)
|
||||||
|
|
||||||
|
The system is built modular so support for other wireless routers, other devices or actions can be implemented easily.
|
||||||
|
|
||||||
Installation instructions
|
Installation instructions
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
|
@ -42,12 +42,24 @@ def from_config_file(config_path):
|
||||||
statusses.append(("Device Scanner - Tomato",
|
statusses.append(("Device Scanner - Tomato",
|
||||||
device_scanner.success_init))
|
device_scanner.success_init))
|
||||||
|
|
||||||
if not device_scanner.success_init:
|
elif config.has_option('netgear', 'host') and \
|
||||||
device_scanner = None
|
config.has_option('netgear', 'username') and \
|
||||||
|
config.has_option('netgear', 'password'):
|
||||||
|
|
||||||
|
device_scanner = device.NetgearDeviceScanner(
|
||||||
|
config.get('netgear', 'host'),
|
||||||
|
config.get('netgear', 'username'),
|
||||||
|
config.get('netgear', 'password'))
|
||||||
|
|
||||||
|
statusses.append(("Device Scanner - Netgear",
|
||||||
|
device_scanner.success_init))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
device_scanner = None
|
device_scanner = None
|
||||||
|
|
||||||
|
if device_scanner and not device_scanner.success_init:
|
||||||
|
device_scanner = None
|
||||||
|
|
||||||
# Device Tracker
|
# Device Tracker
|
||||||
if device_scanner:
|
if device_scanner:
|
||||||
device.DeviceTracker(bus, statemachine, device_scanner)
|
device.DeviceTracker(bus, statemachine, device_scanner)
|
||||||
|
|
|
@ -33,7 +33,7 @@ STATE_HOME = 'device_home'
|
||||||
TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(minutes=1)
|
TIME_SPAN_FOR_ERROR_IN_SCANNING = timedelta(minutes=1)
|
||||||
|
|
||||||
# Return cached results if last scan was less then this time ago
|
# Return cached results if last scan was less then this time ago
|
||||||
TOMATO_MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
|
||||||
|
|
||||||
# Filename to save known devices to
|
# Filename to save known devices to
|
||||||
KNOWN_DEVICES_FILE = "known_devices.csv"
|
KNOWN_DEVICES_FILE = "known_devices.csv"
|
||||||
|
@ -324,7 +324,7 @@ class TomatoDeviceScanner(object):
|
||||||
|
|
||||||
# if date_updated is None or the date is too old we scan for new data
|
# if date_updated is None or the date is too old we scan for new data
|
||||||
if (not self.date_updated or datetime.now() - self.date_updated >
|
if (not self.date_updated or datetime.now() - self.date_updated >
|
||||||
TOMATO_MIN_TIME_BETWEEN_SCANS):
|
MIN_TIME_BETWEEN_SCANS):
|
||||||
|
|
||||||
self.logger.info("Tomato:Scanning")
|
self.logger.info("Tomato:Scanning")
|
||||||
|
|
||||||
|
@ -389,3 +389,127 @@ class TomatoDeviceScanner(object):
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class NetgearDeviceScanner(object):
|
||||||
|
""" This class queries a Netgear wireless router.
|
||||||
|
|
||||||
|
Tested with the Netgear R6300.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, host, username, password):
|
||||||
|
self.req = requests.Request('GET',
|
||||||
|
'http://{}/DEV_device.htm'.format(host),
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
username, password)).prepare()
|
||||||
|
|
||||||
|
self.req_main_page = requests.Request('GET',
|
||||||
|
'http://{}/start.htm'.format(host),
|
||||||
|
auth=requests.auth.HTTPBasicAuth(
|
||||||
|
username, password)).prepare()
|
||||||
|
|
||||||
|
|
||||||
|
self.parse_api_pattern = re.compile(r'ttext">(.*?)<')
|
||||||
|
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
|
self.date_updated = None
|
||||||
|
self.last_results = []
|
||||||
|
|
||||||
|
self.success_init = self._update_info()
|
||||||
|
|
||||||
|
def scan_devices(self):
|
||||||
|
""" Scans for new devices and return a
|
||||||
|
list containing found device ids. """
|
||||||
|
|
||||||
|
self._update_info()
|
||||||
|
|
||||||
|
return [item[2] for item in self.last_results]
|
||||||
|
|
||||||
|
def get_device_name(self, device):
|
||||||
|
""" Returns the name of the given device or None if we don't know. """
|
||||||
|
|
||||||
|
# Make sure there are results
|
||||||
|
if not self.date_updated:
|
||||||
|
self._update_info()
|
||||||
|
|
||||||
|
filter_named = [item[1] for item in self.last_results
|
||||||
|
if item[2] == device]
|
||||||
|
|
||||||
|
if len(filter_named) == 0 or filter_named[0] == "--":
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return filter_named[0]
|
||||||
|
|
||||||
|
def _update_info(self):
|
||||||
|
""" Retrieves latest information from the Netgear router.
|
||||||
|
Returns boolean if scanning successful. """
|
||||||
|
|
||||||
|
self.lock.acquire()
|
||||||
|
|
||||||
|
# if date_updated is None or the date is too old we scan for new data
|
||||||
|
if (not self.date_updated or datetime.now() - self.date_updated >
|
||||||
|
MIN_TIME_BETWEEN_SCANS):
|
||||||
|
|
||||||
|
self.logger.info("Netgear:Scanning")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.Session().send(self.req, timeout=3)
|
||||||
|
|
||||||
|
# Netgear likes us to hit the main page first
|
||||||
|
# So first 401 we get we will first hit main page
|
||||||
|
if response.status_code == 401:
|
||||||
|
response = requests.Session().send(self.req_main_page, timeout=3)
|
||||||
|
response = requests.Session().send(self.req, timeout=3)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
|
||||||
|
entries = self.parse_api_pattern.findall(response.text)
|
||||||
|
|
||||||
|
if len(entries) % 3 != 0:
|
||||||
|
self.logger.error("Netgear:Failed to parse response")
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.last_results = [entries[i:i+3] for i
|
||||||
|
in xrange(0, len(entries), 3)]
|
||||||
|
|
||||||
|
self.date_updated = datetime.now()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif response.status_code == 401:
|
||||||
|
# Authentication error
|
||||||
|
self.logger.exception((
|
||||||
|
"Netgear:Failed to authenticate, "
|
||||||
|
"please check your username and password"))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
# We get this if we could not connect to the router or
|
||||||
|
# an invalid http_id was supplied
|
||||||
|
self.logger.exception((
|
||||||
|
"Netgear:Failed to connect to the router"
|
||||||
|
" or invalid http_id supplied"))
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
# We get this if we could not connect to the router or
|
||||||
|
# an invalid http_id was supplied
|
||||||
|
self.logger.exception(
|
||||||
|
"Netgear:Connection to the router timed out")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self.lock.release()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# We acquired the lock before the IF check,
|
||||||
|
# release it before we return True
|
||||||
|
self.lock.release()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
Loading…
Add table
Reference in a new issue