Merge pull request #50 from andythigpen/feature/variable-nmap-tracker

Variable nmap tracker
This commit is contained in:
Paulus Schoutsen 2015-03-08 14:01:31 -07:00
commit af6407c1df

View File

@ -1,6 +1,6 @@
""" Supports scanning using nmap. """ """ Supports scanning using nmap. """
import logging import logging
from datetime import timedelta from datetime import timedelta, datetime
import threading import threading
from collections import namedtuple from collections import namedtuple
import subprocess import subprocess
@ -11,7 +11,7 @@ from libnmap.parser import NmapParser, NmapParserException
from homeassistant.const import CONF_HOSTS from homeassistant.const import CONF_HOSTS
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
from homeassistant.util import Throttle from homeassistant.util import Throttle, convert
from homeassistant.components.device_tracker import DOMAIN from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago # Return cached results if last scan was less then this time ago
@ -19,6 +19,9 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
def get_scanner(hass, config): def get_scanner(hass, config):
""" Validates config and returns a Nmap scanner. """ """ Validates config and returns a Nmap scanner. """
@ -30,7 +33,7 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "name"]) Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
def _arp(ip_address): def _arp(ip_address):
@ -53,6 +56,8 @@ class NmapDeviceScanner(object):
self.lock = threading.Lock() self.lock = threading.Lock()
self.hosts = config[CONF_HOSTS] self.hosts = config[CONF_HOSTS]
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
self.home_interval = timedelta(minutes=minutes)
self.success_init = True self.success_init = True
self._update_info() self._update_info()
@ -77,23 +82,12 @@ class NmapDeviceScanner(object):
else: else:
return None return None
@Throttle(MIN_TIME_BETWEEN_SCANS) def _parse_results(self, stdout):
def _update_info(self): """ Parses results from an nmap scan.
""" Scans the network for devices. Returns True if successful, False otherwise. """
Returns boolean if scanning successful. """
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
nmap = NmapProcess(targets=self.hosts, options="-F")
nmap.run()
if nmap.rc == 0:
try: try:
results = NmapParser.parse(nmap.stdout) results = NmapParser.parse(stdout)
now = datetime.now()
self.last_results = [] self.last_results = []
for host in results.hosts: for host in results.hosts:
if host.is_up(): if host.is_up():
@ -106,16 +100,43 @@ class NmapDeviceScanner(object):
else: else:
mac = _arp(host.ipv4) mac = _arp(host.ipv4)
if mac: if mac:
device = Device(mac, name) device = Device(mac, name, host.ipv4, now)
self.last_results.append(device) self.last_results.append(device)
_LOGGER.info("nmap scan successful") _LOGGER.info("nmap scan successful")
return True return True
except NmapParserException as parse_exc: except NmapParserException as parse_exc:
_LOGGER.error("failed to parse nmap results: %s", _LOGGER.error("failed to parse nmap results: %s", parse_exc.msg)
parse_exc.msg)
self.last_results = [] self.last_results = []
return False return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Scans the network for devices.
Returns boolean if scanning successful. """
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Scanning")
options = "-F"
exclude_targets = set()
if self.home_interval:
now = datetime.now()
for host in self.last_results:
if host.last_update + self.home_interval > now:
exclude_targets.add(host)
if len(exclude_targets) > 0:
target_list = [t.ip for t in exclude_targets]
options += " --exclude {}".format(",".join(target_list))
nmap = NmapProcess(targets=self.hosts, options=options)
nmap.run()
if nmap.rc == 0:
if self._parse_results(nmap.stdout):
self.last_results.extend(exclude_targets)
else: else:
self.last_results = [] self.last_results = []
_LOGGER.error(nmap.stderr) _LOGGER.error(nmap.stderr)