mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 02:07:09 +00:00
0.27.2 (#3151)
* 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
This commit is contained in:
parent
dfc38b76a4
commit
64cc4a47ec
@ -32,7 +32,7 @@ VALUE_ONLINE = 'ONLINE'
|
|||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: 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,
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
}),
|
}),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
@ -181,7 +181,7 @@ class Thermostat(ClimateDevice):
|
|||||||
else:
|
else:
|
||||||
operation = status
|
operation = status
|
||||||
return {
|
return {
|
||||||
"humidity": self.thermostat['runtime']['actualHumidity'],
|
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
|
||||||
"fan": self.fan,
|
"fan": self.fan,
|
||||||
"mode": self.mode,
|
"mode": self.mode,
|
||||||
"operation": operation,
|
"operation": operation,
|
||||||
|
@ -101,7 +101,7 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
|
|||||||
for mode, state in HM_STATE_MAP.items():
|
for mode, state in HM_STATE_MAP.items():
|
||||||
if state == operation_mode:
|
if state == operation_mode:
|
||||||
code = getattr(self._hmdevice, mode, 0)
|
code = getattr(self._hmdevice, mode, 0)
|
||||||
self._hmdevice.STATE = code
|
self._hmdevice.MODE = code
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_temp(self):
|
def min_temp(self):
|
||||||
|
@ -12,7 +12,7 @@ from homeassistant.components.climate import ClimateDevice
|
|||||||
from homeassistant.components.zwave import (
|
from homeassistant.components.zwave import (
|
||||||
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
|
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
|
||||||
from homeassistant.components import zwave
|
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__)
|
_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",
|
_LOGGER.debug("No discovery_info=%s or no NETWORK=%s",
|
||||||
discovery_info, zwave.NETWORK)
|
discovery_info, zwave.NETWORK)
|
||||||
return
|
return
|
||||||
|
temp_unit = hass.config.units.temperature_unit
|
||||||
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
|
||||||
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
value = node.values[discovery_info[ATTR_VALUE_ID]]
|
||||||
value.set_change_verified(False)
|
value.set_change_verified(False)
|
||||||
add_devices([ZWaveClimate(value)])
|
add_devices([ZWaveClimate(value, temp_unit)])
|
||||||
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
|
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
|
||||||
discovery_info, zwave.NETWORK)
|
discovery_info, zwave.NETWORK)
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
"""Represents a ZWave Climate device."""
|
"""Represents a ZWave Climate device."""
|
||||||
|
|
||||||
# pylint: disable=too-many-public-methods, too-many-instance-attributes
|
# 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."""
|
"""Initialize the zwave climate device."""
|
||||||
from openzwave.network import ZWaveNetwork
|
from openzwave.network import ZWaveNetwork
|
||||||
from pydispatch import dispatcher
|
from pydispatch import dispatcher
|
||||||
@ -87,7 +87,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
self._fan_list = None
|
self._fan_list = None
|
||||||
self._current_swing_mode = None
|
self._current_swing_mode = None
|
||||||
self._swing_list = 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._zxt_120 = None
|
||||||
self.update_properties()
|
self.update_properties()
|
||||||
# register listener
|
# register listener
|
||||||
@ -115,18 +116,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
|
|
||||||
def update_properties(self):
|
def update_properties(self):
|
||||||
"""Callback on data change for the registered node/value pair."""
|
"""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
|
# Operation Mode
|
||||||
for value in self._node.get_values(
|
for value in self._node.get_values(
|
||||||
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
|
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
|
||||||
@ -140,6 +129,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
|
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
|
||||||
if value.label == 'Temperature':
|
if value.label == 'Temperature':
|
||||||
self._current_temperature = int(value.data)
|
self._current_temperature = int(value.data)
|
||||||
|
self._unit = value.units
|
||||||
# Fan Mode
|
# Fan Mode
|
||||||
for value in self._node.get_values(
|
for value in self._node.get_values(
|
||||||
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).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._swing_list=%s", self._swing_list)
|
||||||
_LOGGER.debug("self._current_swing_mode=%s",
|
_LOGGER.debug("self._current_swing_mode=%s",
|
||||||
self._current_swing_mode)
|
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
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -187,14 +188,12 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
"""Return the unit of measurement."""
|
"""Return the unit of measurement."""
|
||||||
unit = self._unit
|
if self._unit == 'C':
|
||||||
if unit == 'C':
|
|
||||||
return TEMP_CELSIUS
|
return TEMP_CELSIUS
|
||||||
elif unit == 'F':
|
elif self._unit == 'F':
|
||||||
return TEMP_FAHRENHEIT
|
return TEMP_FAHRENHEIT
|
||||||
else:
|
else:
|
||||||
_LOGGER.exception("unit_of_measurement=%s is not valid",
|
return self._unit
|
||||||
unit)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_temperature(self):
|
def current_temperature(self):
|
||||||
|
@ -61,6 +61,7 @@ SERVICE_TO_METHOD = {
|
|||||||
SERVICE_STOP_COVER: {'method': 'stop_cover'},
|
SERVICE_STOP_COVER: {'method': 'stop_cover'},
|
||||||
SERVICE_OPEN_COVER_TILT: {'method': 'open_cover_tilt'},
|
SERVICE_OPEN_COVER_TILT: {'method': 'open_cover_tilt'},
|
||||||
SERVICE_CLOSE_COVER_TILT: {'method': 'close_cover_tilt'},
|
SERVICE_CLOSE_COVER_TILT: {'method': 'close_cover_tilt'},
|
||||||
|
SERVICE_STOP_COVER_TILT: {'method': 'stop_cover_tilt'},
|
||||||
SERVICE_SET_COVER_TILT_POSITION: {
|
SERVICE_SET_COVER_TILT_POSITION: {
|
||||||
'method': 'set_cover_tilt_position',
|
'method': 'set_cover_tilt_position',
|
||||||
'schema': COVER_SET_COVER_TILT_POSITION_SCHEMA},
|
'schema': COVER_SET_COVER_TILT_POSITION_SCHEMA},
|
||||||
|
@ -11,7 +11,7 @@ properly configured.
|
|||||||
import logging
|
import logging
|
||||||
from homeassistant.const import STATE_UNKNOWN
|
from homeassistant.const import STATE_UNKNOWN
|
||||||
from homeassistant.components.cover import CoverDevice,\
|
from homeassistant.components.cover import CoverDevice,\
|
||||||
ATTR_CURRENT_POSITION
|
ATTR_POSITION
|
||||||
import homeassistant.components.homematic as homematic
|
import homeassistant.components.homematic as homematic
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -41,16 +41,16 @@ class HMCover(homematic.HMDevice, CoverDevice):
|
|||||||
None is unknown, 0 is closed, 100 is fully open.
|
None is unknown, 0 is closed, 100 is fully open.
|
||||||
"""
|
"""
|
||||||
if self.available:
|
if self.available:
|
||||||
return int((1 - self._hm_get_state()) * 100)
|
return int(self._hm_get_state() * 100)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_cover_position(self, **kwargs):
|
def set_cover_position(self, **kwargs):
|
||||||
"""Move the cover to a specific position."""
|
"""Move the cover to a specific position."""
|
||||||
if self.available:
|
if self.available:
|
||||||
if ATTR_CURRENT_POSITION in kwargs:
|
if ATTR_POSITION in kwargs:
|
||||||
position = float(kwargs[ATTR_CURRENT_POSITION])
|
position = float(kwargs[ATTR_POSITION])
|
||||||
position = min(100, max(0, position))
|
position = min(100, max(0, position))
|
||||||
level = (100 - position) / 100.0
|
level = position / 100.0
|
||||||
self._hmdevice.set_level(level, self._channel)
|
self._hmdevice.set_level(level, self._channel)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -97,12 +97,16 @@ class MqttCover(CoverDevice):
|
|||||||
hass, value_template, payload)
|
hass, value_template, payload)
|
||||||
if payload == self._state_open:
|
if payload == self._state_open:
|
||||||
self._state = False
|
self._state = False
|
||||||
|
_LOGGER.warning("state=%s", int(self._state))
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
elif payload == self._state_closed:
|
elif payload == self._state_closed:
|
||||||
self._state = True
|
self._state = True
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
elif payload.isnumeric() and 0 <= int(payload) <= 100:
|
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._position = int(payload)
|
||||||
self.update_ha_state()
|
self.update_ha_state()
|
||||||
else:
|
else:
|
||||||
@ -129,11 +133,7 @@ class MqttCover(CoverDevice):
|
|||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
"""Return if the cover is closed."""
|
"""Return if the cover is closed."""
|
||||||
if self.current_cover_position is not None:
|
return self._state
|
||||||
if self.current_cover_position > 0:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_cover_position(self):
|
def current_cover_position(self):
|
||||||
|
@ -96,6 +96,8 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
|
|||||||
@property
|
@property
|
||||||
def is_closed(self):
|
def is_closed(self):
|
||||||
"""Return if the cover is closed."""
|
"""Return if the cover is closed."""
|
||||||
|
if self.current_cover_position is None:
|
||||||
|
return None
|
||||||
if self.current_cover_position > 0:
|
if self.current_cover_position > 0:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
@ -388,7 +388,8 @@ def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
|
|||||||
try:
|
try:
|
||||||
return [
|
return [
|
||||||
Device(hass, consider_home, device.get('track', False),
|
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('name'), device.get('picture'),
|
||||||
device.get('gravatar'),
|
device.get('gravatar'),
|
||||||
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
|
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
|
||||||
|
@ -2,16 +2,19 @@
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
from homeassistant.helpers.event import track_point_in_utc_time
|
from homeassistant.helpers.event import track_point_in_utc_time
|
||||||
from homeassistant.components.device_tracker import (
|
from homeassistant.components.device_tracker import (
|
||||||
YAML_DEVICES,
|
YAML_DEVICES,
|
||||||
CONF_TRACK_NEW,
|
CONF_TRACK_NEW,
|
||||||
CONF_SCAN_INTERVAL,
|
CONF_SCAN_INTERVAL,
|
||||||
DEFAULT_SCAN_INTERVAL,
|
DEFAULT_SCAN_INTERVAL,
|
||||||
|
PLATFORM_SCHEMA,
|
||||||
load_config,
|
load_config,
|
||||||
)
|
)
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -19,6 +22,11 @@ REQUIREMENTS = ['gattlib==0.20150805']
|
|||||||
|
|
||||||
BLE_PREFIX = 'BLE_'
|
BLE_PREFIX = 'BLE_'
|
||||||
MIN_SEEN_NEW = 5
|
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):
|
def setup_scanner(hass, config, see):
|
||||||
@ -51,12 +59,13 @@ def setup_scanner(hass, config, see):
|
|||||||
"""Discover Bluetooth LE devices."""
|
"""Discover Bluetooth LE devices."""
|
||||||
_LOGGER.debug("Discovering Bluetooth LE devices")
|
_LOGGER.debug("Discovering Bluetooth LE devices")
|
||||||
service = DiscoveryService()
|
service = DiscoveryService()
|
||||||
devices = service.discover(10)
|
devices = service.discover(duration)
|
||||||
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
|
_LOGGER.debug("Bluetooth LE devices discovered = %s", devices)
|
||||||
|
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
yaml_path = hass.config.path(YAML_DEVICES)
|
yaml_path = hass.config.path(YAML_DEVICES)
|
||||||
|
duration = config.get(CONF_SCAN_DURATION)
|
||||||
devs_to_track = []
|
devs_to_track = []
|
||||||
devs_donot_track = []
|
devs_donot_track = []
|
||||||
|
|
||||||
@ -65,11 +74,13 @@ def setup_scanner(hass, config, see):
|
|||||||
# to 0
|
# to 0
|
||||||
for device in load_config(yaml_path, hass, 0):
|
for device in load_config(yaml_path, hass, 0):
|
||||||
# check if device is a valid bluetooth device
|
# 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:
|
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:
|
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
|
# if track new devices is true discover new devices
|
||||||
# on every scan.
|
# on every scan.
|
||||||
@ -96,7 +107,7 @@ def setup_scanner(hass, config, see):
|
|||||||
if track_new:
|
if track_new:
|
||||||
for address in devs:
|
for address in devs:
|
||||||
if address not in devs_to_track and \
|
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)
|
_LOGGER.info("Discovered Bluetooth LE device %s", address)
|
||||||
see_device(address, devs[address], new_device=True)
|
see_device(address, devs[address], new_device=True)
|
||||||
|
|
||||||
|
@ -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]
|
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
FINGERPRINTS = {
|
FINGERPRINTS = {
|
||||||
"core.js": "1fd10c1fcdf56a61f60cf861d5a0368c",
|
"core.js": "1fd10c1fcdf56a61f60cf861d5a0368c",
|
||||||
"frontend.html": "88c97d278de3320278da6c32fe9e7d61",
|
"frontend.html": "610cc799225ede933a9894b64bb35717",
|
||||||
"mdi.html": "710b84acc99b32514f52291aba9cd8e8",
|
"mdi.html": "710b84acc99b32514f52291aba9cd8e8",
|
||||||
"panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b",
|
"panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b",
|
||||||
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",
|
"panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169",
|
||||||
|
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1 @@
|
|||||||
Subproject commit 670ba0292bfca2b65aeca70804c0856b6cabf10e
|
Subproject commit 659ec6552f761ff4779dd52ee35d26f7be5e111f
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@ -8,93 +8,37 @@ import logging
|
|||||||
|
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.helpers import validate_config, discovery
|
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__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _is_successful(response: dict) -> bool:
|
def setup(hass, config):
|
||||||
"""Check http response for successful status."""
|
"""Setup Insteon Hub component.
|
||||||
return 'status' in response and response['status'] == 'succeeded'
|
|
||||||
|
|
||||||
|
This will automatically import associated lights.
|
||||||
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."""
|
|
||||||
if not validate_config(
|
if not validate_config(
|
||||||
config,
|
config,
|
||||||
{DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]},
|
{DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]},
|
||||||
_LOGGER):
|
_LOGGER):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from insteon import Insteon
|
import insteon
|
||||||
|
|
||||||
username = config[DOMAIN][CONF_USERNAME]
|
username = config[DOMAIN][CONF_USERNAME]
|
||||||
password = config[DOMAIN][CONF_PASSWORD]
|
password = config[DOMAIN][CONF_PASSWORD]
|
||||||
api_key = config[DOMAIN][CONF_API_KEY]
|
api_key = config[DOMAIN][CONF_API_KEY]
|
||||||
|
|
||||||
global INSTEON
|
global INSTEON
|
||||||
INSTEON = Insteon(username, password, api_key)
|
INSTEON = insteon.Insteon(username, password, api_key)
|
||||||
|
|
||||||
if INSTEON is None:
|
if INSTEON is None:
|
||||||
_LOGGER.error('Could not connect to Insteon service.')
|
_LOGGER.error("Could not connect to Insteon service.")
|
||||||
return
|
return
|
||||||
|
|
||||||
for device_class in DEVICE_CLASSES:
|
discovery.load_platform(hass, 'light', DOMAIN, {}, config)
|
||||||
discovery.load_platform(hass, device_class, DOMAIN, {}, config)
|
|
||||||
return True
|
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)
|
|
||||||
|
@ -4,76 +4,74 @@ Support for Insteon Hub lights.
|
|||||||
For more details about this platform, please refer to the documentation at
|
For more details about this platform, please refer to the documentation at
|
||||||
https://home-assistant.io/components/insteon_hub/
|
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,
|
from homeassistant.components.light import (ATTR_BRIGHTNESS,
|
||||||
SUPPORT_BRIGHTNESS, Light)
|
SUPPORT_BRIGHTNESS, Light)
|
||||||
|
|
||||||
SUPPORT_INSTEON_HUB = SUPPORT_BRIGHTNESS
|
SUPPORT_INSTEON_HUB = SUPPORT_BRIGHTNESS
|
||||||
|
|
||||||
DEPENDENCIES = ['insteon_hub']
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the Insteon Hub light platform."""
|
"""Setup the Insteon Hub light platform."""
|
||||||
devs = []
|
devs = []
|
||||||
for device in INSTEON.devices:
|
for device in INSTEON.devices:
|
||||||
if device.DeviceCategory == "Switched Lighting Control":
|
if device.DeviceCategory == "Switched Lighting Control":
|
||||||
devs.append(InsteonLightDevice(device))
|
devs.append(InsteonToggleDevice(device))
|
||||||
if device.DeviceCategory == "Dimmable Lighting Control":
|
if device.DeviceCategory == "Dimmable Lighting Control":
|
||||||
devs.append(InsteonDimmableDevice(device))
|
devs.append(InsteonToggleDevice(device))
|
||||||
add_devices(devs)
|
add_devices(devs)
|
||||||
|
|
||||||
|
|
||||||
class InsteonLightDevice(InsteonDevice, Light):
|
class InsteonToggleDevice(Light):
|
||||||
"""A representation of a light device."""
|
"""An abstract Class for an Insteon node."""
|
||||||
|
|
||||||
def __init__(self, node: object) -> None:
|
def __init__(self, node):
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
super(InsteonLightDevice, self).__init__(node)
|
self.node = node
|
||||||
self._value = 0
|
self._value = 0
|
||||||
|
|
||||||
def update(self) -> None:
|
@property
|
||||||
"""Update state of the device."""
|
def name(self):
|
||||||
resp = self._node.send_command('get_status', wait=True)
|
"""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:
|
try:
|
||||||
self._value = resp['response']['level']
|
self._value = resp['response']['level']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> None:
|
def is_on(self):
|
||||||
"""Return the boolean response if the node is on."""
|
"""Return the boolean response if the node is on."""
|
||||||
return self._value != 0
|
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
|
@property
|
||||||
def brightness(self) -> int:
|
def supported_features(self):
|
||||||
"""Return the brightness of this light between 0..255."""
|
|
||||||
return round(self._value / 100 * 255, 0) # type: int
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self) -> int:
|
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
return SUPPORT_INSTEON_HUB
|
return SUPPORT_INSTEON_HUB
|
||||||
|
|
||||||
def turn_on(self, **kwargs) -> None:
|
def turn_on(self, **kwargs):
|
||||||
"""Turn device on."""
|
"""Turn device on."""
|
||||||
level = 100 # type: int
|
|
||||||
if ATTR_BRIGHTNESS in kwargs:
|
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):
|
def turn_off(self, **kwargs):
|
||||||
self._value = level
|
"""Turn device off."""
|
||||||
|
self.node.send_command('off')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
|
|
||||||
__version__ = '0.27.1'
|
__version__ = '0.27.2'
|
||||||
REQUIRED_PYTHON_VER = (3, 4)
|
REQUIRED_PYTHON_VER = (3, 4)
|
||||||
|
|
||||||
PLATFORM_FORMAT = '{}.{}'
|
PLATFORM_FORMAT = '{}.{}'
|
||||||
@ -244,7 +244,7 @@ SERVICE_OPEN_COVER = 'open_cover'
|
|||||||
SERVICE_OPEN_COVER_TILT = 'open_cover_tilt'
|
SERVICE_OPEN_COVER_TILT = 'open_cover_tilt'
|
||||||
SERVICE_SET_COVER_POSITION = 'set_cover_position'
|
SERVICE_SET_COVER_POSITION = 'set_cover_position'
|
||||||
SERVICE_SET_COVER_TILT_POSITION = 'set_cover_tilt_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_STOP_COVER_TILT = 'stop_cover_tilt'
|
||||||
|
|
||||||
SERVICE_MOVE_UP = 'move_up'
|
SERVICE_MOVE_UP = 'move_up'
|
||||||
|
@ -204,7 +204,7 @@ https://github.com/wokar/pylgnetcast/archive/v0.2.0.zip#pylgnetcast==0.2.0
|
|||||||
influxdb==3.0.0
|
influxdb==3.0.0
|
||||||
|
|
||||||
# homeassistant.components.insteon_hub
|
# homeassistant.components.insteon_hub
|
||||||
insteon_hub==0.5.0
|
insteon_hub==0.4.5
|
||||||
|
|
||||||
# homeassistant.components.media_player.kodi
|
# homeassistant.components.media_player.kodi
|
||||||
jsonrpc-requests==0.3
|
jsonrpc-requests==0.3
|
||||||
|
@ -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)
|
|
@ -1,3 +1,4 @@
|
|||||||
|
"""The tests for the emulated Hue component."""
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import threading
|
import threading
|
||||||
@ -11,8 +12,7 @@ import homeassistant.components as core_components
|
|||||||
from homeassistant.components import emulated_hue, http, light, mqtt
|
from homeassistant.components import emulated_hue, http, light, mqtt
|
||||||
from homeassistant.const import STATE_ON, STATE_OFF
|
from homeassistant.const import STATE_ON, STATE_OFF
|
||||||
from homeassistant.components.emulated_hue import (
|
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
|
from tests.common import get_test_instance_port, get_test_home_assistant
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ mqtt_broker = None
|
|||||||
|
|
||||||
|
|
||||||
def setUpModule():
|
def setUpModule():
|
||||||
|
"""Setup things to be run when tests are started."""
|
||||||
global mqtt_broker
|
global mqtt_broker
|
||||||
|
|
||||||
mqtt_broker = MQTTBroker('127.0.0.1', MQTT_BROKER_PORT)
|
mqtt_broker = MQTTBroker('127.0.0.1', MQTT_BROKER_PORT)
|
||||||
@ -34,12 +35,14 @@ def setUpModule():
|
|||||||
|
|
||||||
|
|
||||||
def tearDownModule():
|
def tearDownModule():
|
||||||
|
"""Stop everything that was started."""
|
||||||
global mqtt_broker
|
global mqtt_broker
|
||||||
|
|
||||||
mqtt_broker.stop()
|
mqtt_broker.stop()
|
||||||
|
|
||||||
|
|
||||||
def setup_hass_instance(emulated_hue_config):
|
def setup_hass_instance(emulated_hue_config):
|
||||||
|
"""Setup the Home Assistant instance to test."""
|
||||||
hass = get_test_home_assistant()
|
hass = get_test_home_assistant()
|
||||||
|
|
||||||
# We need to do this to get access to homeassistant/turn_(on,off)
|
# 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):
|
def start_hass_instance(hass):
|
||||||
|
"""Start the Home Assistant instance to test."""
|
||||||
hass.start()
|
hass.start()
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
|
||||||
class TestEmulatedHue(unittest.TestCase):
|
class TestEmulatedHue(unittest.TestCase):
|
||||||
|
"""Test the emulated Hue component."""
|
||||||
|
|
||||||
hass = None
|
hass = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
"""Setup the class."""
|
||||||
cls.hass = setup_hass_instance({
|
cls.hass = setup_hass_instance({
|
||||||
emulated_hue.DOMAIN: {
|
emulated_hue.DOMAIN: {
|
||||||
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT
|
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT
|
||||||
@ -73,9 +80,11 @@ class TestEmulatedHue(unittest.TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
|
"""Stop the class."""
|
||||||
cls.hass.stop()
|
cls.hass.stop()
|
||||||
|
|
||||||
def test_description_xml(self):
|
def test_description_xml(self):
|
||||||
|
"""Test the description."""
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
result = requests.get(
|
result = requests.get(
|
||||||
@ -91,6 +100,7 @@ class TestEmulatedHue(unittest.TestCase):
|
|||||||
self.fail('description.xml is not valid XML!')
|
self.fail('description.xml is not valid XML!')
|
||||||
|
|
||||||
def test_create_username(self):
|
def test_create_username(self):
|
||||||
|
"""Test the creation of an username."""
|
||||||
request_json = {'devicetype': 'my_device'}
|
request_json = {'devicetype': 'my_device'}
|
||||||
|
|
||||||
result = requests.post(
|
result = requests.post(
|
||||||
@ -107,6 +117,7 @@ class TestEmulatedHue(unittest.TestCase):
|
|||||||
self.assertTrue('username' in success_json['success'])
|
self.assertTrue('username' in success_json['success'])
|
||||||
|
|
||||||
def test_valid_username_request(self):
|
def test_valid_username_request(self):
|
||||||
|
"""Test request with a valid username."""
|
||||||
request_json = {'invalid_key': 'my_device'}
|
request_json = {'invalid_key': 'my_device'}
|
||||||
|
|
||||||
result = requests.post(
|
result = requests.post(
|
||||||
@ -117,8 +128,11 @@ class TestEmulatedHue(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
||||||
|
"""Test class for emulated hue component."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
"""Setup the class."""
|
||||||
cls.hass = setup_hass_instance({
|
cls.hass = setup_hass_instance({
|
||||||
emulated_hue.DOMAIN: {
|
emulated_hue.DOMAIN: {
|
||||||
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT,
|
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT,
|
||||||
@ -177,9 +191,11 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
|
"""Stop the class."""
|
||||||
cls.hass.stop()
|
cls.hass.stop()
|
||||||
|
|
||||||
def test_discover_lights(self):
|
def test_discover_lights(self):
|
||||||
|
"""Test the discovery of lights."""
|
||||||
result = requests.get(
|
result = requests.get(
|
||||||
BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5)
|
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)
|
self.assertTrue('light.kitchen_light' not in result_json)
|
||||||
|
|
||||||
def test_get_light_state(self):
|
def test_get_light_state(self):
|
||||||
|
"""Test the getting of light state."""
|
||||||
# Turn office light on and set to 127 brightness
|
# Turn office light on and set to 127 brightness
|
||||||
self.hass.services.call(
|
self.hass.services.call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_ON,
|
light.DOMAIN, const.SERVICE_TURN_ON,
|
||||||
@ -229,6 +246,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
self.assertEqual(kitchen_result.status_code, 404)
|
self.assertEqual(kitchen_result.status_code, 404)
|
||||||
|
|
||||||
def test_put_light_state(self):
|
def test_put_light_state(self):
|
||||||
|
"""Test the seeting of light states."""
|
||||||
self.perform_put_test_on_office_light()
|
self.perform_put_test_on_office_light()
|
||||||
|
|
||||||
# Turn the bedroom light on first
|
# Turn the bedroom light on first
|
||||||
@ -264,6 +282,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
self.assertEqual(kitchen_result.status_code, 404)
|
self.assertEqual(kitchen_result.status_code, 404)
|
||||||
|
|
||||||
def test_put_with_form_urlencoded_content_type(self):
|
def test_put_with_form_urlencoded_content_type(self):
|
||||||
|
"""Test the form with urlencoded content."""
|
||||||
# Needed for Alexa
|
# Needed for Alexa
|
||||||
self.perform_put_test_on_office_light(
|
self.perform_put_test_on_office_light(
|
||||||
'application/x-www-form-urlencoded')
|
'application/x-www-form-urlencoded')
|
||||||
@ -278,6 +297,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
self.assertEqual(result.status_code, 400)
|
self.assertEqual(result.status_code, 400)
|
||||||
|
|
||||||
def test_entity_not_found(self):
|
def test_entity_not_found(self):
|
||||||
|
"""Test for entity which are not found."""
|
||||||
result = requests.get(
|
result = requests.get(
|
||||||
BRIDGE_URL_BASE.format(
|
BRIDGE_URL_BASE.format(
|
||||||
'/api/username/lights/{}'.format("not.existant_entity")),
|
'/api/username/lights/{}'.format("not.existant_entity")),
|
||||||
@ -293,6 +313,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
self.assertEqual(result.status_code, 404)
|
self.assertEqual(result.status_code, 404)
|
||||||
|
|
||||||
def test_allowed_methods(self):
|
def test_allowed_methods(self):
|
||||||
|
"""Test the allowed methods."""
|
||||||
result = requests.get(
|
result = requests.get(
|
||||||
BRIDGE_URL_BASE.format(
|
BRIDGE_URL_BASE.format(
|
||||||
'/api/username/lights/{}/state'.format("light.office_light")))
|
'/api/username/lights/{}/state'.format("light.office_light")))
|
||||||
@ -313,6 +334,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
self.assertEqual(result.status_code, 405)
|
self.assertEqual(result.status_code, 405)
|
||||||
|
|
||||||
def test_proper_put_state_request(self):
|
def test_proper_put_state_request(self):
|
||||||
|
"""Test the request to set the state."""
|
||||||
# Test proper on value parsing
|
# Test proper on value parsing
|
||||||
result = requests.put(
|
result = requests.put(
|
||||||
BRIDGE_URL_BASE.format(
|
BRIDGE_URL_BASE.format(
|
||||||
@ -334,6 +356,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
|
|
||||||
def perform_put_test_on_office_light(self,
|
def perform_put_test_on_office_light(self,
|
||||||
content_type='application/json'):
|
content_type='application/json'):
|
||||||
|
"""Test the setting of a light."""
|
||||||
# Turn the office light off first
|
# Turn the office light off first
|
||||||
self.hass.services.call(
|
self.hass.services.call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_OFF,
|
light.DOMAIN, const.SERVICE_TURN_OFF,
|
||||||
@ -361,6 +384,7 @@ class TestEmulatedHueExposedByDefault(unittest.TestCase):
|
|||||||
self.assertEqual(office_light.attributes[light.ATTR_BRIGHTNESS], 56)
|
self.assertEqual(office_light.attributes[light.ATTR_BRIGHTNESS], 56)
|
||||||
|
|
||||||
def perform_get_light_state(self, entity_id, expected_status):
|
def perform_get_light_state(self, entity_id, expected_status):
|
||||||
|
"""Test the gettting of a light state."""
|
||||||
result = requests.get(
|
result = requests.get(
|
||||||
BRIDGE_URL_BASE.format(
|
BRIDGE_URL_BASE.format(
|
||||||
'/api/username/lights/{}'.format(entity_id)), timeout=5)
|
'/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,
|
def perform_put_light_state(self, entity_id, is_on, brightness=None,
|
||||||
content_type='application/json'):
|
content_type='application/json'):
|
||||||
|
"""Test the setting of a light state."""
|
||||||
url = BRIDGE_URL_BASE.format(
|
url = BRIDGE_URL_BASE.format(
|
||||||
'/api/username/lights/{}/state'.format(entity_id))
|
'/api/username/lights/{}/state'.format(entity_id))
|
||||||
|
|
||||||
@ -432,6 +457,7 @@ class MQTTBroker(object):
|
|||||||
self._thread.join()
|
self._thread.join()
|
||||||
|
|
||||||
def _run_loop(self):
|
def _run_loop(self):
|
||||||
|
"""Run the loop."""
|
||||||
asyncio.set_event_loop(self._loop)
|
asyncio.set_event_loop(self._loop)
|
||||||
self._loop.run_until_complete(self._broker_coroutine())
|
self._loop.run_until_complete(self._broker_coroutine())
|
||||||
|
|
||||||
@ -442,4 +468,5 @@ class MQTTBroker(object):
|
|||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _broker_coroutine(self):
|
def _broker_coroutine(self):
|
||||||
|
"""The Broker coroutine."""
|
||||||
yield from self._broker.start()
|
yield from self._broker.start()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user