Merging balloob/dev into dev.

This commit is contained in:
Ryan Kraus 2015-09-01 01:03:26 -04:00
commit df4afa5025
20 changed files with 352 additions and 19 deletions

View File

@ -31,6 +31,7 @@ omit =
homeassistant/components/browser.py homeassistant/components/browser.py
homeassistant/components/camera/* homeassistant/components/camera/*
homeassistant/components/device_tracker/actiontec.py homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/luci.py

View File

@ -17,7 +17,7 @@ Check out [the website](https://home-assistant.io) for [a demo][demo], installat
Examples of devices it can interface it: 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/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Efergy](https://efergy.com) plugs, [Edimax](http://www.edimax.com/) switches, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors * [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), and [Kodi (XBMC)](http://kodi.tv/) * [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), and [Kodi (XBMC)](http://kodi.tv/)
* 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/) * 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/)
* 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). * 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).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -10,6 +10,15 @@ import homeassistant.config as config_util
from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START from homeassistant.const import __version__, EVENT_HOMEASSISTANT_START
def validate_python():
""" Validate we're running the right Python version. """
major, minor = sys.version_info[:2]
if major < 3 or (major == 3 and minor < 4):
print("Home Assistant requires atleast Python 3.4")
sys.exit(1)
def ensure_config_path(config_dir): def ensure_config_path(config_dir):
""" Validates configuration directory. """ """ Validates configuration directory. """
@ -74,6 +83,8 @@ def get_arguments():
def main(): def main():
""" Starts Home Assistant. """ """ Starts Home Assistant. """
validate_python()
args = get_arguments() args = get_arguments()
config_dir = os.path.join(os.getcwd(), args.config) config_dir = os.path.join(os.getcwd(), args.config)
@ -86,6 +97,7 @@ def main():
}, config_dir=config_dir) }, config_dir=config_dir)
else: else:
config_file = ensure_config_file(config_dir) config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(config_file) hass = bootstrap.from_config_file(config_file)
if args.open_ui: if args.open_ui:

View File

@ -0,0 +1,154 @@
"""
homeassistant.components.device_tracker.aruba
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a Aruba Access Point for device
presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the Aruba tracker you will need to add something like the following
to your config/configuration.yaml. You also need to enable Telnet in the
configuration pages.
device_tracker:
platform: aruba
host: YOUR_ACCESS_POINT_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
"""
import logging
from datetime import timedelta
import re
import threading
import telnetlib
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
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__)
_DEVICES_REGEX = re.compile(
r'(?P<name>([^\s]+))\s+' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s+' +
r'(?P<os>([^\s]+))\s+' +
r'(?P<network>([^\s]+))\s+' +
r'(?P<ap>([^\s]+))\s+' +
r'(?P<channel>([^\s]+))\s+' +
r'(?P<type>([^\s]+))\s+' +
r'(?P<role>([^\s]+))\s+' +
r'(?P<signal>([^\s]+))\s+' +
r'(?P<speed>([^\s]+))')
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns a Aruba scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = ArubaDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class ArubaDeviceScanner(object):
""" This class queries a Aruba Acces Point for connected devices. """
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.lock = threading.Lock()
self.last_results = {}
# Test the router is accessible
data = self.get_aruba_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]
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
if not self.last_results:
return None
for client in self.last_results:
if client['mac'] == device:
return client['name']
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
""" Ensures the information from the Aruba Access Point is up to date.
Returns boolean if scanning successful. """
if not self.success_init:
return False
with self.lock:
data = self.get_aruba_data()
if not data:
return False
self.last_results = data.values()
return True
def get_aruba_data(self):
""" Retrieve data from Aruba Access Point and return parsed
result. """
try:
telnet = telnetlib.Telnet(self.host)
telnet.read_until(b'User: ')
telnet.write((self.username + '\r\n').encode('ascii'))
telnet.read_until(b'Password: ')
telnet.write((self.password + '\r\n').encode('ascii'))
telnet.read_until(b'#')
telnet.write(('show clients\r\n').encode('ascii'))
devices_result = telnet.read_until(b'#').split(b'\r\n')
telnet.write('exit\r\n'.encode('ascii'))
except EOFError:
_LOGGER.exception("Unexpected response from router")
return
except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router," +
" is telnet enabled?")
return
devices = {}
for device in devices_result:
match = _DEVICES_REGEX.search(device.decode('utf-8'))
if match:
devices[match.group('ip')] = {
'ip': match.group('ip'),
'mac': match.group('mac').upper(),
'name': match.group('name')
}
return devices

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "6558cad990c03dd96f9e8b1035495cb6" VERSION = "35ecb5457a9ff0f4142c2605b53eb843"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 5a103bf5e98848318a9550d066a9de2e9289314b Subproject commit b0b12e20e0f61df849c414c2dfbcf9923f784631

View File

@ -104,7 +104,7 @@ def setup(hass, config):
""" Sets up all groups found definded in the configuration. """ """ Sets up all groups found definded in the configuration. """
for name, entity_ids in config.get(DOMAIN, {}).items(): for name, entity_ids in config.get(DOMAIN, {}).items():
if isinstance(entity_ids, str): if isinstance(entity_ids, str):
entity_ids = entity_ids.split(",") entity_ids = [ent.strip() for ent in entity_ids.split(",")]
setup_group(hass, name, entity_ids) setup_group(hass, name, entity_ids)
return True return True

View File

@ -29,8 +29,11 @@ def setup(hass, config=None):
- Available components: - Available components:
https://home-assistant.io/components/ https://home-assistant.io/components/
- Chat room: - Troubleshooting your configuration:
https://gitter.im/balloob/home-assistant https://home-assistant.io/getting-started/troubleshooting-configuration.html
- Getting help:
https://home-assistant.io/help/
This message is generated by the introduction component. You can This message is generated by the introduction component. You can
disable it in configuration.yaml. disable it in configuration.yaml.

View File

@ -24,6 +24,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_TODAY_MWH = "today_mwh" ATTR_TODAY_MWH = "today_mwh"
ATTR_CURRENT_POWER_MWH = "current_power_mwh" ATTR_CURRENT_POWER_MWH = "current_power_mwh"
ATTR_SENSOR_STATE = "sensor_state"
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
@ -38,6 +39,7 @@ DISCOVERY_PLATFORMS = {
PROP_TO_ATTR = { PROP_TO_ATTR = {
'current_power_mwh': ATTR_CURRENT_POWER_MWH, 'current_power_mwh': ATTR_CURRENT_POWER_MWH,
'today_power_mw': ATTR_TODAY_MWH, 'today_power_mw': ATTR_TODAY_MWH,
'sensor_state': ATTR_SENSOR_STATE
} }
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -101,6 +103,16 @@ class SwitchDevice(ToggleEntity):
""" Today total power usage in mw. """ """ Today total power usage in mw. """
return None return None
@property
def is_standby(self):
""" Is the device in standby. """
return None
@property
def sensor_state(self):
""" Is the sensor on or off. """
return None
@property @property
def device_state_attributes(self): def device_state_attributes(self):
""" Returns device specific state attributes. """ """ Returns device specific state attributes. """

View File

@ -7,8 +7,9 @@ Support for WeMo switches.
import logging import logging
from homeassistant.components.switch import SwitchDevice from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_ON, STATE_OFF, STATE_STANDBY
REQUIREMENTS = ['pywemo==0.2'] REQUIREMENTS = ['pywemo==0.3']
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -39,6 +40,7 @@ class WemoSwitch(SwitchDevice):
def __init__(self, wemo): def __init__(self, wemo):
self.wemo = wemo self.wemo = wemo
self.insight_params = None self.insight_params = None
self.maker_params = None
@property @property
def unique_id(self): def unique_id(self):
@ -50,6 +52,16 @@ class WemoSwitch(SwitchDevice):
""" Returns the name of the switch if any. """ """ Returns the name of the switch if any. """
return self.wemo.name return self.wemo.name
@property
def state(self):
""" Returns the state. """
is_on = self.is_on
if not is_on:
return STATE_OFF
elif self.is_standby:
return STATE_STANDBY
return STATE_ON
@property @property
def current_power_mwh(self): def current_power_mwh(self):
""" Current power usage in mwh. """ """ Current power usage in mwh. """
@ -62,6 +74,40 @@ class WemoSwitch(SwitchDevice):
if self.insight_params: if self.insight_params:
return self.insight_params['todaymw'] return self.insight_params['todaymw']
@property
def is_standby(self):
""" Is the device on - or in standby. """
if self.insight_params:
standby_state = self.insight_params['state']
# Standby is actually '8' but seems more defensive
# to check for the On and Off states
if standby_state == '1' or standby_state == '0':
return False
else:
return True
@property
def sensor_state(self):
""" Is the sensor on or off. """
if self.maker_params and self.has_sensor:
# Note a state of 1 matches the WeMo app 'not triggered'!
if self.maker_params['sensorstate']:
return STATE_OFF
else:
return STATE_ON
@property
def switch_mode(self):
""" Is the switch configured as toggle(0) or momentary (1). """
if self.maker_params:
return self.maker_params['switchmode']
@property
def has_sensor(self):
""" Is the sensor present? """
if self.maker_params:
return self.maker_params['hassensor']
@property @property
def is_on(self): def is_on(self):
""" True if switch is on. """ """ True if switch is on. """
@ -78,5 +124,8 @@ class WemoSwitch(SwitchDevice):
def update(self): def update(self):
""" Update WeMo state. """ """ Update WeMo state. """
self.wemo.get_state(True) self.wemo.get_state(True)
if self.wemo.model.startswith('Belkin Insight'): if self.wemo.model_name == 'Insight':
self.insight_params = self.wemo.insight_params self.insight_params = self.wemo.insight_params
self.insight_params['standby_state'] = self.wemo.get_standby_state
elif self.wemo.model_name == 'Maker':
self.maker_params = self.wemo.maker_params

View File

@ -46,6 +46,7 @@ STATE_CLOSED = 'closed'
STATE_PLAYING = 'playing' STATE_PLAYING = 'playing'
STATE_PAUSED = 'paused' STATE_PAUSED = 'paused'
STATE_IDLE = 'idle' STATE_IDLE = 'idle'
STATE_STANDBY = 'standby'
# #### STATE AND EVENT ATTRIBUTES #### # #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event # Contains current time for a TIME_CHANGED event

View File

@ -89,7 +89,7 @@ pynetgear==0.3
netdisco==0.3 netdisco==0.3
# Wemo (switch.wemo) # Wemo (switch.wemo)
pywemo==0.2 pywemo==0.3
# Wink (*.wink) # Wink (*.wink)
https://github.com/balloob/python-wink/archive/c2b700e8ca866159566ecf5e644d9c297f69f257.zip https://github.com/balloob/python-wink/archive/c2b700e8ca866159566ecf5e644d9c297f69f257.zip

View File

@ -1,6 +1,6 @@
# If current pwd is scripts, go 1 up. echo "The update script has been deprecated since Home Assistant v0.7"
if [ ${PWD##*/} == "scripts" ]; then echo
cd .. echo "Home Assistant is now distributed via PyPi and can be installed and"
fi echo "upgraded by running: pip3 install --upgrade homeassistant"
echo
git pull echo "If you are developing a new feature for Home Assistant, run: git pull"

View File

@ -0,0 +1,96 @@
"""
tests.components.test_conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests Conversation component.
"""
# pylint: disable=too-many-public-methods,protected-access
import unittest
import homeassistant.components as core_components
import homeassistant.components.conversation as conversation
import homeassistant.components.demo as demo
import homeassistant.components.light as light
from common import get_test_home_assistant
class TestConversation(unittest.TestCase):
""" Test the conversation component. """
def setUp(self): # pylint: disable=invalid-name
""" Start up ha for testing """
self.hass = get_test_home_assistant(3)
demo.setup(self.hass, {demo.DOMAIN: {}})
core_components.setup(self.hass, {})
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_setup_and_turn_on(self):
""" Setup and perform good turn on requests """
self.assertTrue(
conversation.setup(self.hass, {conversation.DOMAIN: {}}))
light.turn_off(self.hass, 'light.kitchen_lights')
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights on'}
self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True)
self.assertTrue(
light.is_on(self.hass, 'light.kitchen_lights'))
def test_setup_and_turn_off(self):
""" Setup and perform good turn off requests """
self.assertTrue(
conversation.setup(self.hass, {conversation.DOMAIN: {}}))
light.turn_on(self.hass, 'light.kitchen_lights')
event_data = {conversation.ATTR_TEXT: 'turn kitchen lights off'}
self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True)
self.assertFalse(
light.is_on(self.hass, 'light.kitchen_lights'))
def test_setup_and_bad_request_format(self):
""" Setup and perform a badly formatted request """
self.assertTrue(
conversation.setup(self.hass, {conversation.DOMAIN: {}}))
event_data = {
conversation.ATTR_TEXT:
'what is the answer to the ultimate question of life, ' +
'the universe and everything'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
def test_setup_and_bad_request_entity(self):
""" Setup and perform requests with bad entity id """
self.assertTrue(
conversation.setup(self.hass, {conversation.DOMAIN: {}}))
event_data = {conversation.ATTR_TEXT: 'turn something off'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
def test_setup_and_bad_request_command(self):
""" Setup and perform requests with bad command """
self.assertTrue(
conversation.setup(self.hass, {conversation.DOMAIN: {}}))
event_data = {conversation.ATTR_TEXT: 'turn kitchen over'}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))
def test_setup_and_bad_request_notext(self):
""" Setup and perform requests with bad command with no text """
self.assertTrue(
conversation.setup(self.hass, {conversation.DOMAIN: {}}))
event_data = {}
self.assertTrue(self.hass.services.call(
conversation.DOMAIN, 'process', event_data, True))

View File

@ -199,7 +199,7 @@ class TestComponentsGroup(unittest.TestCase):
self.hass, self.hass,
{ {
group.DOMAIN: { group.DOMAIN: {
'second_group': self.group_entity_id + ',light.Bowl' 'second_group': 'light.Bowl, ' + self.group_entity_id
} }
})) }))
@ -207,6 +207,8 @@ class TestComponentsGroup(unittest.TestCase):
group.ENTITY_ID_FORMAT.format('second_group')) group.ENTITY_ID_FORMAT.format('second_group'))
self.assertEqual(STATE_ON, group_state.state) self.assertEqual(STATE_ON, group_state.state)
self.assertEqual(set((self.group_entity_id, 'light.bowl')),
set(group_state.attributes['entity_id']))
self.assertFalse(group_state.attributes[group.ATTR_AUTO]) self.assertFalse(group_state.attributes[group.ATTR_AUTO])
def test_groups_get_unique_names(self): def test_groups_get_unique_names(self):

View File

@ -51,6 +51,10 @@ def setUpModule(): # pylint: disable=invalid-name
# Start slave # Start slave
slave = remote.HomeAssistant(master_api) slave = remote.HomeAssistant(master_api)
bootstrap.setup_component(
slave, http.DOMAIN,
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
http.CONF_SERVER_PORT: 8130}})
slave.start() slave.start()