Skip to content

Commit

Permalink
Merge pull request home-assistant#506 from balloob/dev
Browse files Browse the repository at this point in the history
0.7.5rc1
  • Loading branch information
balloob committed Oct 10, 2015
2 parents 4d1dce2 + 6a18205 commit 853a9fd
Show file tree
Hide file tree
Showing 36 changed files with 925 additions and 298 deletions.
7 changes: 6 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py

homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py

homeassistant/components/ifttt.py
homeassistant/components/browser.py
homeassistant/components/camera/*
Expand All @@ -39,11 +42,13 @@ omit =
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/keyboard.py
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/itunes.py
Expand All @@ -60,6 +65,7 @@ omit =
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
Expand All @@ -71,7 +77,6 @@ omit =
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rfxtrx.py
homeassistant/components/sensor/rpi_gpio.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/swiss_public_transport.py
Expand Down
7 changes: 5 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
recursive-exclude tests *
recursive-include homeassistant services.yaml
include README.md
include LICENSE
graft homeassistant
prune homeassistant/components/frontend/www_static/home-assistant-polymer
recursive-exclude * *.py[co]
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ Check out [the website](https://home-assistant.io) for [a demo][demo], installat

Examples of devices it can interface it:

* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) and any SNMP capable Linksys WAP/WRT
*
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Edimax](http://www.edimax.com/) switches, [Efergy](https://efergy.com) energy monitoring, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), [Kodi (XBMC)](http://kodi.tv/), and iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api))
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), [Plex](https://plex.tv/), [Kodi (XBMC)](http://kodi.tv/), and iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api))
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [RFXtrx](http://www.rfxcom.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
* Interaction with [IFTTT](https://ifttt.com/)
* Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org).
* [See full list of supported devices](https://home-assistant.io/components/)

Expand All @@ -29,8 +31,8 @@ Built home automation on top of your devices:
* Turn on the lights when people get home after sun set
* Turn on lights slowly during sun set to compensate for less light
* Turn off all lights and devices when everybody leaves the house
* Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), and [Jabber (XMPP)](http://xmpp.org)
* Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects like [OwnTracks](http://owntracks.org/)
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), [Telegram](https://telegram.org/), and [Jabber (XMPP)](http://xmpp.org)

The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html).

Expand Down
3 changes: 3 additions & 0 deletions config/configuration.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ automation:
service: light.turn_off
entity_id: group.all_lights

# Sensors need to be added into the configuration.yaml as sensor:, sensor 2:, sensor 3:, etc.
# Each sensor label should be unique or your sensors might not load correctly.

sensor:
platform: systemmonitor
resources:
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/device_tracker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ def setup(hass, config):
os.remove(csv_path)

conf = config.get(DOMAIN, {})
if isinstance(conf, list):
conf = conf[0]
consider_home = timedelta(
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
DEFAULT_CONSIDER_HOME))
Expand Down
76 changes: 38 additions & 38 deletions homeassistant/components/device_tracker/ddwrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
_LOGGER = logging.getLogger(__name__)

_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')


# pylint: disable=unused-argument
Expand Down Expand Up @@ -77,7 +78,7 @@ def __init__(self, config):

self.last_results = {}

self.mac2name = None
self.mac2name = {}

# Test the router is accessible
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
Expand All @@ -98,30 +99,33 @@ def get_device_name(self, device):

with self.lock:
# if not initialised and not already scanned and not found
if self.mac2name is None or device not in self.mac2name:
if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)

if not data:
return
return None

dhcp_leases = data.get('dhcp_leases', None)
if dhcp_leases:
# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]

return self.mac2name.get(device, None)

if not dhcp_leases:
return None

# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]

return self.mac2name.get(device)

@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
Expand All @@ -141,29 +145,25 @@ def _update_info(self):
if not data:
return False

if data:
self.last_results = []
active_clients = data.get('active_wireless', None)
if active_clients:
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!
self.last_results = []

active_clients = data.get('active_wireless', None)
if not active_clients:
return False

# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!

num_clients = int(len(elements)/9)
for idx in range(0, num_clients):
# get every 9th element which is the MAC address
index = idx * 9
if index < len(elements):
self.last_results.append(elements[index])
# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")

return True
self.last_results.extend(item for item in elements
if _MAC_REGEX.match(item))

return False
return True

def get_ddwrt_data(self, url):
""" Retrieve data from DD-WRT and return parsed result. """
Expand Down
28 changes: 15 additions & 13 deletions homeassistant/components/device_tracker/nmap_tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,34 +117,36 @@ def _update_info(self):
scanner = PortScanner()

options = "-F --host-timeout 5"
exclude_targets = set()

if self.home_interval:
now = dt_util.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))
boundary = dt_util.now() - self.home_interval
last_results = [device for device in self.last_results
if device.last_update > boundary]
if last_results:
# Pylint is confused here.
# pylint: disable=no-member
options += " --exclude {}".format(",".join(device.ip for device
in last_results))
else:
last_results = []

try:
result = scanner.scan(hosts=self.hosts, arguments=options)
except PortScannerError:
return False

now = dt_util.now()
self.last_results = []
for ipv4, info in result['scan'].items():
if info['status']['state'] != 'up':
continue
name = info['hostnames'][0] if info['hostnames'] else ipv4
name = info['hostnames'][0]['name'] if info['hostnames'] else ipv4
# Mac address only returned if nmap ran as root
mac = info['addresses'].get('mac') or _arp(ipv4)
if mac is None:
continue
device = Device(mac.upper(), name, ipv4, now)
self.last_results.append(device)
self.last_results.extend(exclude_targets)
last_results.append(Device(mac.upper(), name, ipv4, now))

self.last_results = last_results

_LOGGER.info("nmap scan successful")
return True
119 changes: 119 additions & 0 deletions homeassistant/components/device_tracker/snmp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
homeassistant.components.device_tracker.snmp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports fetching WiFi associations
through SNMP.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.snmp.html
"""
import logging
from datetime import timedelta
import threading
import binascii

from homeassistant.const import CONF_HOST
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN

# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)

_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.2.5']

CONF_COMMUNITY = "community"
CONF_BASEOID = "baseoid"


# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns an snmp scanner """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]},
_LOGGER):
return None

scanner = SnmpScanner(config[DOMAIN])

return scanner if scanner.success_init else None


class SnmpScanner(object):
"""
This class queries any SNMP capable Acces Point for connected devices.
"""
def __init__(self, config):
self.host = config[CONF_HOST]
self.community = config[CONF_COMMUNITY]
self.baseoid = config[CONF_BASEOID]

self.lock = threading.Lock()

self.last_results = []

# Test the router is accessible
data = self.get_snmp_data()
self.success_init = data is not None

def scan_devices(self):
"""
Scans for new devices and return a list containing found device IDs.
"""

self._update_info()
return [client['mac'] for client in self.last_results]

# Supressing no-self-use warning
# pylint: disable=R0201
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
# We have no names
return None

@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the WAP is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False

with self.lock:
data = self.get_snmp_data()
if not data:
return False

self.last_results = data
return True

def get_snmp_data(self):
""" Fetch mac addresses from WAP via SNMP. """
from pysnmp.entity.rfc3413.oneliner import cmdgen

devices = []

snmp = cmdgen.CommandGenerator()
errindication, errstatus, errindex, restable = snmp.nextCmd(
cmdgen.CommunityData(self.community),
cmdgen.UdpTransportTarget((self.host, 161)),
cmdgen.MibVariable(self.baseoid)
)

if errindication:
_LOGGER.error("SNMPLIB error: %s", errindication)
return
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
errindex and restable[-1][int(errindex)-1]
or '?')
return

for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})
return devices
Loading

0 comments on commit 853a9fd

Please sign in to comment.