mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Merging balloob/dev into dev.
This commit is contained in:
commit
df4afa5025
@ -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
|
||||
|
@ -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 |
@ -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:
|
||||
|
154
homeassistant/components/device_tracker/aruba.py
Normal file
154
homeassistant/components/device_tracker/aruba.py
Normal 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
|
@ -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
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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. """
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
96
tests/components/test_conversation.py
Normal file
96
tests/components/test_conversation.py
Normal 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))
|
@ -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):
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user