* Host should be optional for apcupsd component (#3072)

* Zwave climate Bugfix: if some setpoints have different units, we should fetch the o… (#3078)

* Bugfix: if some setpoints have different units, we should fetch the one that are active.

* Move order of population for first time detection

* Default to config if None unit_of_measurement

* unit fix (#3083)

* humidity slider (#3088)

* If device was off target temp was null. Default to Heating setpoint (#3091)

* Fix for BLE device tracker (#3019)

* Bug fix tracked devices
* Added scan_duration configuration parameter

* fix homematic climate implementation (#3114)

* Allow 'None' MAC to be loaded from known_devices (#3102)

* Climate and cover bugfix (#3097)

* Avoid None comparison for zwave cover.

* Just rely on unit from config for unit_of_measurement

* Explicit return None

* Mqtt (#11)

* Explicit return None

* Missing service and wrong service name defined

* Mqtt state was inverted, and never triggering

* Fixed Homematic cover (#3116)

* Add missing docstrings (fix PEP257 issues) (#3098)

* Add missing docstrings (fix PEP257 issues)

* Finish sentence

* Merge pull request #3130 from turbokongen/zwave_fixes

Bugfix. climate and covermqt

* Back out insteon hub and fan changes (#3062)

* Bump version

* Special frontend build for 0.27.2
pull/3272/head^2 0.27.2
Robbie Trencheny 2016-09-03 15:59:20 -07:00 committed by GitHub
parent dfc38b76a4
commit 64cc4a47ec
35 changed files with 141 additions and 297 deletions

View File

@ -32,7 +32,7 @@ VALUE_ONLINE = 'ONLINE'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
}),
}, extra=vol.ALLOW_EXTRA)

View File

@ -181,7 +181,7 @@ class Thermostat(ClimateDevice):
else:
operation = status
return {
"humidity": self.thermostat['runtime']['actualHumidity'],
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
"fan": self.fan,
"mode": self.mode,
"operation": operation,

View File

@ -101,7 +101,7 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
for mode, state in HM_STATE_MAP.items():
if state == operation_mode:
code = getattr(self._hmdevice, mode, 0)
self._hmdevice.STATE = code
self._hmdevice.MODE = code
@property
def min_temp(self):

View File

@ -12,7 +12,7 @@ from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
from homeassistant.components import zwave
from homeassistant.const import (TEMP_FAHRENHEIT, TEMP_CELSIUS)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
_LOGGER = logging.getLogger(__name__)
@ -59,11 +59,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.debug("No discovery_info=%s or no NETWORK=%s",
discovery_info, zwave.NETWORK)
return
temp_unit = hass.config.units.temperature_unit
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
value.set_change_verified(False)
add_devices([ZWaveClimate(value)])
add_devices([ZWaveClimate(value, temp_unit)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
discovery_info, zwave.NETWORK)
@ -73,7 +73,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Represents a ZWave Climate device."""
# pylint: disable=too-many-public-methods, too-many-instance-attributes
def __init__(self, value):
def __init__(self, value, temp_unit):
"""Initialize the zwave climate device."""
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
@ -87,7 +87,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._fan_list = None
self._current_swing_mode = None
self._swing_list = None
self._unit = None
self._unit = temp_unit
_LOGGER.debug("temp_unit is %s", self._unit)
self._zxt_120 = None
self.update_properties()
# register listener
@ -115,18 +116,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def update_properties(self):
"""Callback on data change for the registered node/value pair."""
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
self._unit = value.units
if self.current_operation is not None:
if SET_TEMP_TO_INDEX.get(self._current_operation) \
!= value.index:
continue
if self._zxt_120:
continue
self._target_temperature = int(value.data)
# Operation Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
@ -140,6 +129,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._unit = value.units
# Fan Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
@ -158,6 +148,17 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
if self.current_operation is not None and \
self.current_operation != 'Off':
if SET_TEMP_TO_INDEX.get(self._current_operation) \
!= value.index:
continue
if self._zxt_120:
continue
self._target_temperature = int(value.data)
@property
def should_poll(self):
@ -187,14 +188,12 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
unit = self._unit
if unit == 'C':
if self._unit == 'C':
return TEMP_CELSIUS
elif unit == 'F':
elif self._unit == 'F':
return TEMP_FAHRENHEIT
else:
_LOGGER.exception("unit_of_measurement=%s is not valid",
unit)
return self._unit
@property
def current_temperature(self):

View File

@ -61,6 +61,7 @@ SERVICE_TO_METHOD = {
SERVICE_STOP_COVER: {'method': 'stop_cover'},
SERVICE_OPEN_COVER_TILT: {'method': 'open_cover_tilt'},
SERVICE_CLOSE_COVER_TILT: {'method': 'close_cover_tilt'},
SERVICE_STOP_COVER_TILT: {'method': 'stop_cover_tilt'},
SERVICE_SET_COVER_TILT_POSITION: {
'method': 'set_cover_tilt_position',
'schema': COVER_SET_COVER_TILT_POSITION_SCHEMA},

View File

@ -11,7 +11,7 @@ properly configured.
import logging
from homeassistant.const import STATE_UNKNOWN
from homeassistant.components.cover import CoverDevice,\
ATTR_CURRENT_POSITION
ATTR_POSITION
import homeassistant.components.homematic as homematic
_LOGGER = logging.getLogger(__name__)
@ -41,16 +41,16 @@ class HMCover(homematic.HMDevice, CoverDevice):
None is unknown, 0 is closed, 100 is fully open.
"""
if self.available:
return int((1 - self._hm_get_state()) * 100)
return int(self._hm_get_state() * 100)
return None
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if self.available:
if ATTR_CURRENT_POSITION in kwargs:
position = float(kwargs[ATTR_CURRENT_POSITION])
if ATTR_POSITION in kwargs:
position = float(kwargs[ATTR_POSITION])
position = min(100, max(0, position))
level = (100 - position) / 100.0
level = position / 100.0
self._hmdevice.set_level(level, self._channel)
@property

View File

@ -97,12 +97,16 @@ class MqttCover(CoverDevice):
hass, value_template, payload)
if payload == self._state_open:
self._state = False
_LOGGER.warning("state=%s", int(self._state))
self.update_ha_state()
elif payload == self._state_closed:
self._state = True
self.update_ha_state()
elif payload.isnumeric() and 0 <= int(payload) <= 100:
self._state = int(payload)
if int(payload) > 0:
self._state = False
else:
self._state = True
self._position = int(payload)
self.update_ha_state()
else:
@ -129,11 +133,7 @@ class MqttCover(CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
return self._state
@property
def current_cover_position(self):

View File

@ -96,6 +96,8 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is None:
return None
if self.current_cover_position > 0:
return False
else:

View File

@ -388,7 +388,8 @@ def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
try:
return [
Device(hass, consider_home, device.get('track', False),
str(dev_id).lower(), str(device.get('mac')).upper(),
str(dev_id).lower(), None if device.get('mac') is None
else str(device.get('mac')).upper(),
device.get('name'), device.get('picture'),
device.get('gravatar'),
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))

View File

@ -2,16 +2,19 @@
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.components.device_tracker import (
YAML_DEVICES,
CONF_TRACK_NEW,
CONF_SCAN_INTERVAL,
DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA,
load_config,
)
import homeassistant.util as util
import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -19,6 +22,11 @@ REQUIREMENTS = ['gattlib==0.20150805']
BLE_PREFIX = 'BLE_'
MIN_SEEN_NEW = 5
CONF_SCAN_DURATION = "scan_duration"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_DURATION, default=10): cv.positive_int
})
def setup_scanner(hass, config, see):
@ -51,12 +59,13 @@ def setup_scanner(hass, config, see):
"""Discover Bluetooth LE devices."""
_LOGGER.debug("Discovering Bluetooth LE devices")
service = DiscoveryService()
devices = service.discover(10)
devices = service.discover(duration)
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
return devices
yaml_path = hass.config.path(YAML_DEVICES)
duration = config.get(CONF_SCAN_DURATION)
devs_to_track = []
devs_donot_track = []
@ -65,11 +74,13 @@ def setup_scanner(hass, config, see):
# to 0
for device in load_config(yaml_path, hass, 0):
# check if device is a valid bluetooth device
if device.mac and device.mac[:3].upper() == BLE_PREFIX:
if device.mac and device.mac[:4].upper() == BLE_PREFIX:
if device.track:
devs_to_track.append(device.mac[3:])
_LOGGER.debug("Adding %s to BLE tracker", device.mac)
devs_to_track.append(device.mac[4:])
else:
devs_donot_track.append(device.mac[3:])
_LOGGER.debug("Adding %s to BLE do not track", device.mac)
devs_donot_track.append(device.mac[4:])
# if track new devices is true discover new devices
# on every scan.
@ -96,7 +107,7 @@ def setup_scanner(hass, config, see):
if track_new:
for address in devs:
if address not in devs_to_track and \
address not in devs_donot_track:
address not in devs_donot_track:
_LOGGER.info("Discovered Bluetooth LE device %s", address)
see_device(address, devs[address], new_device=True)

View File

@ -1,66 +0,0 @@
"""
Support for Insteon FanLinc.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.insteon/
"""
import logging
from homeassistant.components.fan import (FanEntity, SUPPORT_SET_SPEED,
SPEED_OFF, SPEED_LOW, SPEED_MED,
SPEED_HIGH)
from homeassistant.components.insteon_hub import (InsteonDevice, INSTEON,
filter_devices)
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
DEVICE_CATEGORIES = [
{
'DevCat': 1,
'SubCat': [46]
}
]
DEPENDENCIES = ['insteon_hub']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Insteon Hub fan platform."""
devs = []
for device in filter_devices(INSTEON.devices, DEVICE_CATEGORIES):
devs.append(InsteonFanDevice(device))
add_devices(devs)
class InsteonFanDevice(InsteonDevice, FanEntity):
"""Represet an insteon fan device."""
def __init__(self, node: object) -> None:
"""Initialize the device."""
super(InsteonFanDevice, self).__init__(node)
self.speed = STATE_UNKNOWN # Insteon hub can't get state via REST
def turn_on(self, speed: str=None):
"""Turn the fan on."""
self.set_speed(speed if speed else SPEED_MED)
def turn_off(self):
"""Turn the fan off."""
self.set_speed(SPEED_OFF)
def set_speed(self, speed: str) -> None:
"""Set the fan speed."""
if self._send_command('fan', payload={'speed', speed}):
self.speed = speed
@property
def supported_features(self) -> int:
"""Get the supported features for device."""
return SUPPORT_SET_SPEED
@property
def speed_list(self) -> list:
"""Get the available speeds for the fan."""
return [SPEED_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH]

View File

@ -2,7 +2,7 @@
FINGERPRINTS = {
"core.js": "1fd10c1fcdf56a61f60cf861d5a0368c",
"frontend.html": "88c97d278de3320278da6c32fe9e7d61",
"frontend.html": "610cc799225ede933a9894b64bb35717",
"mdi.html": "710b84acc99b32514f52291aba9cd8e8",
"panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b",
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 670ba0292bfca2b65aeca70804c0856b6cabf10e
Subproject commit 659ec6552f761ff4779dd52ee35d26f7be5e111f

File diff suppressed because one or more lines are too long

View File

@ -8,93 +8,37 @@ import logging
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config, discovery
from homeassistant.helpers.entity import Entity
DOMAIN = 'insteon_hub' # type: str
REQUIREMENTS = ['insteon_hub==0.5.0'] # type: list
INSTEON = None # type: Insteon
DEVCAT = 'DevCat' # type: str
SUBCAT = 'SubCat' # type: str
DEVICE_CLASSES = ['light', 'fan'] # type: list
DOMAIN = "insteon_hub"
REQUIREMENTS = ['insteon_hub==0.4.5']
INSTEON = None
_LOGGER = logging.getLogger(__name__)
def _is_successful(response: dict) -> bool:
"""Check http response for successful status."""
return 'status' in response and response['status'] == 'succeeded'
def setup(hass, config):
"""Setup Insteon Hub component.
def filter_devices(devices: list, categories: list) -> list:
"""Filter insteon device list by category/subcategory."""
categories = (categories
if isinstance(categories, list)
else [categories])
matching_devices = []
for device in devices:
if any(
device.DevCat == c[DEVCAT] and
(SUBCAT not in c or device.SubCat in c[SUBCAT])
for c in categories):
matching_devices.append(device)
return matching_devices
def setup(hass, config: dict) -> bool:
"""Setup Insteon Hub component."""
This will automatically import associated lights.
"""
if not validate_config(
config,
{DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]},
_LOGGER):
return False
from insteon import Insteon
import insteon
username = config[DOMAIN][CONF_USERNAME]
password = config[DOMAIN][CONF_PASSWORD]
api_key = config[DOMAIN][CONF_API_KEY]
global INSTEON
INSTEON = Insteon(username, password, api_key)
INSTEON = insteon.Insteon(username, password, api_key)
if INSTEON is None:
_LOGGER.error('Could not connect to Insteon service.')
_LOGGER.error("Could not connect to Insteon service.")
return
for device_class in DEVICE_CLASSES:
discovery.load_platform(hass, device_class, DOMAIN, {}, config)
discovery.load_platform(hass, 'light', DOMAIN, {}, config)
return True
class InsteonDevice(Entity):
"""Represents an insteon device."""
def __init__(self: Entity, node: object) -> None:
"""Initialize the insteon device."""
self._node = node
def update(self: Entity) -> None:
"""Update state of the device."""
pass
@property
def name(self: Entity) -> str:
"""Name of the insteon device."""
return self._node.DeviceName
@property
def unique_id(self: Entity) -> str:
"""Unique identifier for the device."""
return self._node.DeviceID
@property
def supported_features(self: Entity) -> int:
"""Supported feature flags."""
return 0
def _send_command(self: Entity, command: str, level: int=None,
payload: dict=None) -> bool:
"""Send command to insteon device."""
resp = self._node.send_command(command, payload=payload, level=level,
wait=True)
return _is_successful(resp)

View File

@ -4,76 +4,74 @@ Support for Insteon Hub lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/insteon_hub/
"""
from homeassistant.components.insteon_hub import (INSTEON, InsteonDevice)
from homeassistant.components.insteon_hub import INSTEON
from homeassistant.components.light import (ATTR_BRIGHTNESS,
SUPPORT_BRIGHTNESS, Light)
SUPPORT_INSTEON_HUB = SUPPORT_BRIGHTNESS
DEPENDENCIES = ['insteon_hub']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Insteon Hub light platform."""
devs = []
for device in INSTEON.devices:
if device.DeviceCategory == "Switched Lighting Control":
devs.append(InsteonLightDevice(device))
devs.append(InsteonToggleDevice(device))
if device.DeviceCategory == "Dimmable Lighting Control":
devs.append(InsteonDimmableDevice(device))
devs.append(InsteonToggleDevice(device))
add_devices(devs)
class InsteonLightDevice(InsteonDevice, Light):
"""A representation of a light device."""
class InsteonToggleDevice(Light):
"""An abstract Class for an Insteon node."""
def __init__(self, node: object) -> None:
def __init__(self, node):
"""Initialize the device."""
super(InsteonLightDevice, self).__init__(node)
self.node = node
self._value = 0
def update(self) -> None:
"""Update state of the device."""
resp = self._node.send_command('get_status', wait=True)
@property
def name(self):
"""Return the the name of the node."""
return self.node.DeviceName
@property
def unique_id(self):
"""Return the ID of this insteon node."""
return self.node.DeviceID
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._value / 100 * 255
def update(self):
"""Update state of the sensor."""
resp = self.node.send_command('get_status', wait=True)
try:
self._value = resp['response']['level']
except KeyError:
pass
@property
def is_on(self) -> None:
def is_on(self):
"""Return the boolean response if the node is on."""
return self._value != 0
def turn_on(self, **kwargs) -> None:
"""Turn device on."""
if self._send_command('on'):
self._value = 100
def turn_off(self, **kwargs) -> None:
"""Turn device off."""
if self._send_command('off'):
self._value = 0
class InsteonDimmableDevice(InsteonLightDevice):
"""A representation for a dimmable device."""
@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return round(self._value / 100 * 255, 0) # type: int
@property
def supported_features(self) -> int:
def supported_features(self):
"""Flag supported features."""
return SUPPORT_INSTEON_HUB
def turn_on(self, **kwargs) -> None:
def turn_on(self, **kwargs):
"""Turn device on."""
level = 100 # type: int
if ATTR_BRIGHTNESS in kwargs:
level = round(kwargs[ATTR_BRIGHTNESS] / 255 * 100, 0) # type: int
self._value = kwargs[ATTR_BRIGHTNESS] / 255 * 100
self.node.send_command('on', self._value)
else:
self._value = 100
self.node.send_command('on')
if self._send_command('on', level=level):
self._value = level
def turn_off(self, **kwargs):
"""Turn device off."""
self.node.send_command('off')

View File

@ -1,7 +1,7 @@
# coding: utf-8
"""Constants used by Home Assistant components."""
__version__ = '0.27.1'
__version__ = '0.27.2'
REQUIRED_PYTHON_VER = (3, 4)
PLATFORM_FORMAT = '{}.{}'
@ -244,7 +244,7 @@ SERVICE_OPEN_COVER = 'open_cover'
SERVICE_OPEN_COVER_TILT = 'open_cover_tilt'
SERVICE_SET_COVER_POSITION = 'set_cover_position'
SERVICE_SET_COVER_TILT_POSITION = 'set_cover_tilt_position'
SERVICE_STOP_COVER = 'stop'
SERVICE_STOP_COVER = 'stop_cover'
SERVICE_STOP_COVER_TILT = 'stop_cover_tilt'
SERVICE_MOVE_UP = 'move_up'

View File

@ -204,7 +204,7 @@ https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0
influxdb==3.0.0
# homeassistant.components.insteon_hub
insteon_hub==0.5.0
insteon_hub==0.4.5
# homeassistant.components.media_player.kodi
jsonrpc-requests==0.3

View File

@ -1,73 +0,0 @@
"""Tests for the insteon hub fan platform."""
import unittest
from homeassistant.const import (STATE_OFF, STATE_ON)
from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_HIGH,
ATTR_SPEED)
from homeassistant.components.fan.insteon_hub import (InsteonFanDevice,
SUPPORT_SET_SPEED)
class Node(object):
"""Fake insteon node."""
def __init__(self, name, id, dev_cat, sub_cat):
"""Initialize fake insteon node."""
self.DeviceName = name
self.DeviceID = id
self.DevCat = dev_cat
self.SubCat = sub_cat
self.response = None
def send_command(self, command, payload, level, wait):
"""Send fake command."""
return self.response
class TestInsteonHubFanDevice(unittest.TestCase):
"""Test around insteon hub fan device methods."""
_NODE = Node('device', '12345', '1', '46')
def setUp(self):
"""Initialize test data."""
self._DEVICE = InsteonFanDevice(self._NODE)
def tearDown(self):
"""Tear down test data."""
self._DEVICE = None
def test_properties(self):
"""Test basic properties."""
self.assertEqual(self._NODE.DeviceName, self._DEVICE.name)
self.assertEqual(self._NODE.DeviceID, self._DEVICE.unique_id)
self.assertEqual(SUPPORT_SET_SPEED, self._DEVICE.supported_features)
for speed in [STATE_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH]:
self.assertIn(speed, self._DEVICE.speed_list)
def test_turn_on(self):
"""Test the turning on device."""
self._NODE.response = {
'status': 'succeeded'
}
self.assertEqual(STATE_OFF, self._DEVICE.state)
self._DEVICE.turn_on()
self.assertEqual(STATE_ON, self._DEVICE.state)
self._DEVICE.turn_on(SPEED_MED)
self.assertEqual(STATE_ON, self._DEVICE.state)
self.assertEqual(SPEED_MED, self._DEVICE.state_attributes[ATTR_SPEED])
def test_turn_off(self):
"""Test turning off device."""
self._NODE.response = {
'status': 'succeeded'
}
self.assertEqual(STATE_OFF, self._DEVICE.state)
self._DEVICE.turn_on()
self.assertEqual(STATE_ON, self._DEVICE.state)
self._DEVICE.turn_off()
self.assertEqual(STATE_OFF, self._DEVICE.state)

View File

@ -1,3 +1,4 @@
"""The tests for the emulated Hue component."""
import time
import json
import threading
@ -11,8 +12,7 @@ import homeassistant.components as core_components
from homeassistant.components import emulated_hue, http, light, mqtt
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.emulated_hue import (
HUE_API_STATE_ON, HUE_API_STATE_BRI
)
HUE_API_STATE_ON, HUE_API_STATE_BRI)
from tests.common import get_test_instance_port, get_test_home_assistant
@ -27,6 +27,7 @@ mqtt_broker = None
def setUpModule():
"""Setup things to be run when tests are started."""
global mqtt_broker
mqtt_broker = MQTTBroker('127.0.0.1', MQTT_BROKER_PORT)
@ -34,12 +35,14 @@ def setUpModule():
def tearDownModule():
"""Stop everything that was started."""
global mqtt_broker
mqtt_broker.stop()
def setup_hass_instance(emulated_hue_config):
"""Setup the Home Assistant instance to test."""
hass = get_test_home_assistant()
# We need to do this to get access to homeassistant/turn_(on,off)
@ -55,15 +58,19 @@ def setup_hass_instance(emulated_hue_config):
def start_hass_instance(hass):
"""Start the Home Assistant instance to test."""
hass.start()
time.sleep(0.05)
class TestEmulatedHue(unittest.TestCase):
"""Test the emulated Hue component."""
hass = None
@classmethod
def setUpClass(cls):
"""Setup the class."""
cls.hass = setup_hass_instance({
emulated_hue.DOMAIN: {
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT
@ -73,9 +80,11 @@ class TestEmulatedHue(unittest.TestCase):
@classmethod
def tearDownClass(cls):
"""Stop the class."""
cls.hass.stop()
def test_description_xml(self):
"""Test the description."""
import xml.etree.ElementTree as ET
result = requests.get(
@ -91,6 +100,7 @@ class TestEmulatedHue(unittest.TestCase):
self.fail('description.xml is not valid XML!')
def test_create_username(self):
"""Test the creation of an username."""
request_json = {'devicetype': 'my_device'}
result = requests.post(
@ -107,6 +117,7 @@ class TestEmulatedHue(unittest.TestCase):
self.assertTrue('username' in success_json['success'])
def test_valid_username_request(self):
"""Test request with a valid username."""
request_json = {'invalid_key': 'my_device'}
result = requests.post(
@ -117,8 +128,11 @@ class TestEmulatedHue(unittest.TestCase):
class TestEmulatedHueExposedByDefault(unittest.TestCase):
"""Test class for emulated hue component."""
@classmethod
def setUpClass(cls):
"""Setup the class."""
cls.hass = setup_hass_instance({
emulated_hue.DOMAIN: {
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT,
@ -177,9 +191,11 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
@classmethod
def tearDownClass(cls):
"""Stop the class."""
cls.hass.stop()
def test_discover_lights(self):
"""Test the discovery of lights."""
result = requests.get(
BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5)
@ -194,6 +210,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
self.assertTrue('light.kitchen_light' not in result_json)
def test_get_light_state(self):
"""Test the getting of light state."""
# Turn office light on and set to 127 brightness
self.hass.services.call(
light.DOMAIN, const.SERVICE_TURN_ON,
@ -229,6 +246,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
self.assertEqual(kitchen_result.status_code, 404)
def test_put_light_state(self):
"""Test the seeting of light states."""
self.perform_put_test_on_office_light()
# Turn the bedroom light on first
@ -264,6 +282,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
self.assertEqual(kitchen_result.status_code, 404)
def test_put_with_form_urlencoded_content_type(self):
"""Test the form with urlencoded content."""
# Needed for Alexa
self.perform_put_test_on_office_light(
'application/x-www-form-urlencoded')
@ -278,6 +297,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
self.assertEqual(result.status_code, 400)
def test_entity_not_found(self):
"""Test for entity which are not found."""
result = requests.get(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}'.format("not.existant_entity")),
@ -293,6 +313,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
self.assertEqual(result.status_code, 404)
def test_allowed_methods(self):
"""Test the allowed methods."""
result = requests.get(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format("light.office_light")))
@ -313,6 +334,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
self.assertEqual(result.status_code, 405)
def test_proper_put_state_request(self):
"""Test the request to set the state."""
# Test proper on value parsing
result = requests.put(
BRIDGE_URL_BASE.format(
@ -334,6 +356,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
def perform_put_test_on_office_light(self,
content_type='application/json'):
"""Test the setting of a light."""
# Turn the office light off first
self.hass.services.call(
light.DOMAIN, const.SERVICE_TURN_OFF,
@ -361,6 +384,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
self.assertEqual(office_light.attributes[light.ATTR_BRIGHTNESS], 56)
def perform_get_light_state(self, entity_id, expected_status):
"""Test the gettting of a light state."""
result = requests.get(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}'.format(entity_id)), timeout=5)
@ -377,6 +401,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
def perform_put_light_state(self, entity_id, is_on, brightness=None,
content_type='application/json'):
"""Test the setting of a light state."""
url = BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format(entity_id))
@ -432,6 +457,7 @@ class MQTTBroker(object):
self._thread.join()
def _run_loop(self):
"""Run the loop."""
asyncio.set_event_loop(self._loop)
self._loop.run_until_complete(self._broker_coroutine())
@ -442,4 +468,5 @@ class MQTTBroker(object):
@asyncio.coroutine
def _broker_coroutine(self):
"""The Broker coroutine."""
yield from self._broker.start()