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/camera/*
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.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:
* 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/)
* 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).

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
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):
""" Validates configuration directory. """
@ -74,6 +83,8 @@ def get_arguments():
def main():
""" Starts Home Assistant. """
validate_python()
args = get_arguments()
config_dir = os.path.join(os.getcwd(), args.config)
@ -86,6 +97,7 @@ def main():
}, config_dir=config_dir)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(config_file)
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 """
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. """
for name, entity_ids in config.get(DOMAIN, {}).items():
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)
return True

View File

@ -29,8 +29,11 @@ def setup(hass, config=None):
- Available components:
https://home-assistant.io/components/
- Chat room:
https://gitter.im/balloob/home-assistant
- Troubleshooting your configuration:
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
disable it in configuration.yaml.

View File

@ -24,6 +24,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_TODAY_MWH = "today_mwh"
ATTR_CURRENT_POWER_MWH = "current_power_mwh"
ATTR_SENSOR_STATE = "sensor_state"
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
@ -38,6 +39,7 @@ DISCOVERY_PLATFORMS = {
PROP_TO_ATTR = {
'current_power_mwh': ATTR_CURRENT_POWER_MWH,
'today_power_mw': ATTR_TODAY_MWH,
'sensor_state': ATTR_SENSOR_STATE
}
_LOGGER = logging.getLogger(__name__)
@ -101,6 +103,16 @@ class SwitchDevice(ToggleEntity):
""" Today total power usage in mw. """
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
def device_state_attributes(self):
""" Returns device specific state attributes. """

View File

@ -7,8 +7,9 @@ Support for WeMo switches.
import logging
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
@ -39,6 +40,7 @@ class WemoSwitch(SwitchDevice):
def __init__(self, wemo):
self.wemo = wemo
self.insight_params = None
self.maker_params = None
@property
def unique_id(self):
@ -50,6 +52,16 @@ class WemoSwitch(SwitchDevice):
""" Returns the name of the switch if any. """
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
def current_power_mwh(self):
""" Current power usage in mwh. """
@ -62,6 +74,40 @@ class WemoSwitch(SwitchDevice):
if self.insight_params:
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
def is_on(self):
""" True if switch is on. """
@ -78,5 +124,8 @@ class WemoSwitch(SwitchDevice):
def update(self):
""" Update WeMo state. """
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['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_PAUSED = 'paused'
STATE_IDLE = 'idle'
STATE_STANDBY = 'standby'
# #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event

View File

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

View File

@ -1,6 +1,6 @@
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
git pull
echo "The update script has been deprecated since Home Assistant v0.7"
echo
echo "Home Assistant is now distributed via PyPi and can be installed and"
echo "upgraded by running: pip3 install --upgrade homeassistant"
echo
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,
{
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'))
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])
def test_groups_get_unique_names(self):

View File

@ -51,6 +51,10 @@ def setUpModule(): # pylint: disable=invalid-name
# Start slave
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()