mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
commit
dfc38b76a4
@ -7,35 +7,43 @@ https://home-assistant.io/components/apcupsd/
|
|||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import (CONF_HOST, CONF_PORT)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
DOMAIN = "apcupsd"
|
REQUIREMENTS = ['apcaccess==0.0.4']
|
||||||
REQUIREMENTS = ("apcaccess==0.0.4",)
|
|
||||||
|
|
||||||
CONF_HOST = "host"
|
_LOGGER = logging.getLogger(__name__)
|
||||||
CONF_PORT = "port"
|
|
||||||
CONF_TYPE = "type"
|
|
||||||
|
|
||||||
DEFAULT_HOST = "localhost"
|
CONF_TYPE = 'type'
|
||||||
|
|
||||||
|
DATA = None
|
||||||
|
DEFAULT_HOST = 'localhost'
|
||||||
DEFAULT_PORT = 3551
|
DEFAULT_PORT = 3551
|
||||||
|
DOMAIN = 'apcupsd'
|
||||||
|
|
||||||
KEY_STATUS = "STATUS"
|
KEY_STATUS = 'STATUS'
|
||||||
|
|
||||||
VALUE_ONLINE = "ONLINE"
|
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
|
||||||
|
|
||||||
DATA = None
|
VALUE_ONLINE = 'ONLINE'
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
|
DOMAIN: vol.Schema({
|
||||||
|
vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
}),
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Use config values to set up a function enabling status retrieval."""
|
"""Use config values to set up a function enabling status retrieval."""
|
||||||
global DATA
|
global DATA
|
||||||
|
conf = config[DOMAIN]
|
||||||
host = config[DOMAIN].get(CONF_HOST, DEFAULT_HOST)
|
host = conf.get(CONF_HOST)
|
||||||
port = config[DOMAIN].get(CONF_PORT, DEFAULT_PORT)
|
port = conf.get(CONF_PORT)
|
||||||
|
|
||||||
DATA = APCUPSdData(host, port)
|
DATA = APCUPSdData(host, port)
|
||||||
|
|
||||||
|
@ -4,23 +4,32 @@ Support for tracking the online status of a UPS.
|
|||||||
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/binary_sensor.apcupsd/
|
https://home-assistant.io/components/binary_sensor.apcupsd/
|
||||||
"""
|
"""
|
||||||
from homeassistant.components import apcupsd
|
import voluptuous as vol
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import (
|
||||||
|
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.components import apcupsd
|
||||||
|
|
||||||
|
DEFAULT_NAME = 'UPS Online Status'
|
||||||
DEPENDENCIES = [apcupsd.DOMAIN]
|
DEPENDENCIES = [apcupsd.DOMAIN]
|
||||||
DEFAULT_NAME = "UPS Online Status"
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Instantiate an OnlineStatus binary sensor entity."""
|
"""Setup an Online Status binary sensor."""
|
||||||
add_entities((OnlineStatus(config, apcupsd.DATA),))
|
add_entities((OnlineStatus(config, apcupsd.DATA),))
|
||||||
|
|
||||||
|
|
||||||
class OnlineStatus(BinarySensorDevice):
|
class OnlineStatus(BinarySensorDevice):
|
||||||
"""Represent UPS online status."""
|
"""Representation of an UPS online status."""
|
||||||
|
|
||||||
def __init__(self, config, data):
|
def __init__(self, config, data):
|
||||||
"""Initialize the APCUPSd device."""
|
"""Initialize the APCUPSd binary device."""
|
||||||
self._config = config
|
self._config = config
|
||||||
self._data = data
|
self._data = data
|
||||||
self._state = None
|
self._state = None
|
||||||
@ -29,7 +38,7 @@ class OnlineStatus(BinarySensorDevice):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the UPS online status sensor."""
|
"""Return the name of the UPS online status sensor."""
|
||||||
return self._config.get("name", DEFAULT_NAME)
|
return self._config.get(CONF_NAME)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self):
|
def is_on(self):
|
||||||
|
@ -16,7 +16,7 @@ from homeassistant.config import load_yaml_config_file
|
|||||||
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
|
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
|
||||||
ATTR_ENTITY_ID)
|
ATTR_ENTITY_ID)
|
||||||
|
|
||||||
REQUIREMENTS = ["ha-ffmpeg==0.8"]
|
REQUIREMENTS = ["ha-ffmpeg==0.9"]
|
||||||
|
|
||||||
SERVICE_RESTART = 'ffmpeg_restart'
|
SERVICE_RESTART = 'ffmpeg_restart'
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ from homeassistant.components.camera.mjpeg import extract_image_from_mjpeg
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.const import CONF_NAME
|
from homeassistant.const import CONF_NAME
|
||||||
|
|
||||||
REQUIREMENTS = ['ha-ffmpeg==0.8']
|
REQUIREMENTS = ['ha-ffmpeg==0.9']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -351,7 +351,7 @@ class ClimateDevice(Entity):
|
|||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the current state."""
|
"""Return the current state."""
|
||||||
return self.current_operation or STATE_UNKNOWN
|
return self.target_temperature or STATE_UNKNOWN
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
|
@ -80,6 +80,8 @@ class Thermostat(ClimateDevice):
|
|||||||
self.thermostat_index)
|
self.thermostat_index)
|
||||||
self._name = self.thermostat['name']
|
self._name = self.thermostat['name']
|
||||||
self.hold_temp = hold_temp
|
self.hold_temp = hold_temp
|
||||||
|
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
|
||||||
|
'heat', 'off']
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Get the latest state from the thermostat."""
|
"""Get the latest state from the thermostat."""
|
||||||
@ -124,11 +126,6 @@ class Thermostat(ClimateDevice):
|
|||||||
"""Return the upper bound temperature we try to reach."""
|
"""Return the upper bound temperature we try to reach."""
|
||||||
return int(self.thermostat['runtime']['desiredCool'] / 10)
|
return int(self.thermostat['runtime']['desiredCool'] / 10)
|
||||||
|
|
||||||
@property
|
|
||||||
def current_humidity(self):
|
|
||||||
"""Return the current humidity."""
|
|
||||||
return self.thermostat['runtime']['actualHumidity']
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def desired_fan_mode(self):
|
def desired_fan_mode(self):
|
||||||
"""Return the desired fan mode of operation."""
|
"""Return the desired fan mode of operation."""
|
||||||
@ -142,31 +139,26 @@ class Thermostat(ClimateDevice):
|
|||||||
else:
|
else:
|
||||||
return STATE_OFF
|
return STATE_OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_operation(self):
|
||||||
|
"""Return current operation."""
|
||||||
|
return self.operation_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def operation_list(self):
|
||||||
|
"""Return the operation modes list."""
|
||||||
|
return self._operation_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def operation_mode(self):
|
def operation_mode(self):
|
||||||
"""Return current operation ie. heat, cool, idle."""
|
"""Return current operation ie. heat, cool, idle."""
|
||||||
status = self.thermostat['equipmentStatus']
|
return self.thermostat['settings']['hvacMode']
|
||||||
if status == '':
|
|
||||||
return STATE_IDLE
|
|
||||||
elif 'Cool' in status:
|
|
||||||
return STATE_COOL
|
|
||||||
elif 'auxHeat' in status:
|
|
||||||
return STATE_HEAT
|
|
||||||
elif 'heatPump' in status:
|
|
||||||
return STATE_HEAT
|
|
||||||
else:
|
|
||||||
return status
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode(self):
|
def mode(self):
|
||||||
"""Return current mode ie. home, away, sleep."""
|
"""Return current mode ie. home, away, sleep."""
|
||||||
return self.thermostat['program']['currentClimateRef']
|
return self.thermostat['program']['currentClimateRef']
|
||||||
|
|
||||||
@property
|
|
||||||
def current_operation(self):
|
|
||||||
"""Return current hvac mode ie. auto, auxHeatOnly, cool, heat, off."""
|
|
||||||
return self.thermostat['settings']['hvacMode']
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_min_on_time(self):
|
def fan_min_on_time(self):
|
||||||
"""Return current fan minimum on time."""
|
"""Return current fan minimum on time."""
|
||||||
@ -176,11 +168,23 @@ class Thermostat(ClimateDevice):
|
|||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
# Move these to Thermostat Device and make them global
|
# Move these to Thermostat Device and make them global
|
||||||
|
status = self.thermostat['equipmentStatus']
|
||||||
|
operation = None
|
||||||
|
if status == '':
|
||||||
|
operation = STATE_IDLE
|
||||||
|
elif 'Cool' in status:
|
||||||
|
operation = STATE_COOL
|
||||||
|
elif 'auxHeat' in status:
|
||||||
|
operation = STATE_HEAT
|
||||||
|
elif 'heatPump' in status:
|
||||||
|
operation = STATE_HEAT
|
||||||
|
else:
|
||||||
|
operation = status
|
||||||
return {
|
return {
|
||||||
"humidity": self.current_humidity,
|
"humidity": self.thermostat['runtime']['actualHumidity'],
|
||||||
"fan": self.fan,
|
"fan": self.fan,
|
||||||
"mode": self.mode,
|
"mode": self.mode,
|
||||||
"operation_mode": self.current_operation,
|
"operation": operation,
|
||||||
"fan_min_on_time": self.fan_min_on_time
|
"fan_min_on_time": self.fan_min_on_time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,11 +35,21 @@ DEVICE_MAPPINGS = {
|
|||||||
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
|
REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120
|
||||||
}
|
}
|
||||||
|
|
||||||
ZXT_120_SET_TEMP = {
|
SET_TEMP_TO_INDEX = {
|
||||||
'Heat': 1,
|
'Heat': 1,
|
||||||
'Cool': 2,
|
'Cool': 2,
|
||||||
|
'Auto': 3,
|
||||||
|
'Aux Heat': 4,
|
||||||
|
'Resume': 5,
|
||||||
|
'Fan Only': 6,
|
||||||
|
'Furnace': 7,
|
||||||
'Dry Air': 8,
|
'Dry Air': 8,
|
||||||
'Auto Changeover': 10
|
'Moist Air': 9,
|
||||||
|
'Auto Changeover': 10,
|
||||||
|
'Heat Econ': 11,
|
||||||
|
'Cool Econ': 12,
|
||||||
|
'Away': 13,
|
||||||
|
'Unknown': 14
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -78,7 +88,6 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
self._current_swing_mode = None
|
self._current_swing_mode = None
|
||||||
self._swing_list = None
|
self._swing_list = None
|
||||||
self._unit = None
|
self._unit = None
|
||||||
self._index = None
|
|
||||||
self._zxt_120 = None
|
self._zxt_120 = None
|
||||||
self.update_properties()
|
self.update_properties()
|
||||||
# register listener
|
# register listener
|
||||||
@ -107,15 +116,17 @@ 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
|
# Set point
|
||||||
temps = []
|
|
||||||
for value in self._node.get_values(
|
for value in self._node.get_values(
|
||||||
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
|
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
|
||||||
self._unit = value.units
|
self._unit = value.units
|
||||||
temps.append(int(value.data))
|
if self.current_operation is not None:
|
||||||
if value.index == self._index:
|
if SET_TEMP_TO_INDEX.get(self._current_operation) \
|
||||||
self._target_temperature = int(value.data)
|
!= value.index:
|
||||||
self._target_temperature_high = max(temps)
|
continue
|
||||||
self._target_temperature_low = min(temps)
|
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():
|
||||||
@ -209,23 +220,25 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
|
|||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
for value in self._node.get_values(
|
for value in self._node.get_values(
|
||||||
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
|
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
|
||||||
if value.command_class != 67 and value.index != self._index:
|
if self.current_operation is not None:
|
||||||
continue
|
if SET_TEMP_TO_INDEX.get(self._current_operation) \
|
||||||
if self._zxt_120:
|
!= value.index:
|
||||||
# ZXT-120 does not support get setpoint
|
|
||||||
self._target_temperature = temperature
|
|
||||||
if ZXT_120_SET_TEMP.get(self._current_operation) \
|
|
||||||
!= value.index:
|
|
||||||
continue
|
continue
|
||||||
_LOGGER.debug("ZXT_120_SET_TEMP=%s and"
|
_LOGGER.debug("SET_TEMP_TO_INDEX=%s and"
|
||||||
" self._current_operation=%s",
|
" self._current_operation=%s",
|
||||||
ZXT_120_SET_TEMP.get(self._current_operation),
|
SET_TEMP_TO_INDEX.get(self._current_operation),
|
||||||
self._current_operation)
|
self._current_operation)
|
||||||
# ZXT-120 responds only to whole int
|
if self._zxt_120:
|
||||||
value.data = int(round(temperature, 0))
|
# ZXT-120 does not support get setpoint
|
||||||
|
self._target_temperature = temperature
|
||||||
|
# ZXT-120 responds only to whole int
|
||||||
|
value.data = int(round(temperature, 0))
|
||||||
|
else:
|
||||||
|
value.data = int(temperature)
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
value.data = int(temperature)
|
value.data = int(temperature)
|
||||||
break
|
break
|
||||||
|
|
||||||
def set_fan_mode(self, fan):
|
def set_fan_mode(self, fan):
|
||||||
"""Set new target fan mode."""
|
"""Set new target fan mode."""
|
||||||
|
@ -10,14 +10,19 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
from typing import Any, Sequence, Callable
|
||||||
|
|
||||||
from homeassistant.bootstrap import prepare_setup_platform
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.bootstrap import (
|
||||||
|
prepare_setup_platform, log_exception)
|
||||||
from homeassistant.components import group, zone
|
from homeassistant.components import group, zone
|
||||||
from homeassistant.components.discovery import SERVICE_NETGEAR
|
from homeassistant.components.discovery import SERVICE_NETGEAR
|
||||||
from homeassistant.config import load_yaml_config_file
|
from homeassistant.config import load_yaml_config_file
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import config_per_platform, discovery
|
from homeassistant.helpers import config_per_platform, discovery
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -27,8 +32,7 @@ from homeassistant.const import (
|
|||||||
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE,
|
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE,
|
||||||
DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME)
|
DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
|
DOMAIN = 'device_tracker'
|
||||||
DOMAIN = "device_tracker"
|
|
||||||
DEPENDENCIES = ['zone']
|
DEPENDENCIES = ['zone']
|
||||||
|
|
||||||
GROUP_NAME_ALL_DEVICES = 'all devices'
|
GROUP_NAME_ALL_DEVICES = 'all devices'
|
||||||
@ -38,21 +42,18 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
|||||||
|
|
||||||
YAML_DEVICES = 'known_devices.yaml'
|
YAML_DEVICES = 'known_devices.yaml'
|
||||||
|
|
||||||
CONF_TRACK_NEW = "track_new_devices"
|
CONF_TRACK_NEW = 'track_new_devices'
|
||||||
DEFAULT_CONF_TRACK_NEW = True
|
DEFAULT_TRACK_NEW = True
|
||||||
|
|
||||||
CONF_CONSIDER_HOME = 'consider_home'
|
CONF_CONSIDER_HOME = 'consider_home'
|
||||||
DEFAULT_CONSIDER_HOME = 180 # seconds
|
DEFAULT_CONSIDER_HOME = 180 # seconds
|
||||||
|
|
||||||
CONF_SCAN_INTERVAL = "interval_seconds"
|
CONF_SCAN_INTERVAL = 'interval_seconds'
|
||||||
DEFAULT_SCAN_INTERVAL = 12
|
DEFAULT_SCAN_INTERVAL = 12
|
||||||
|
|
||||||
CONF_AWAY_HIDE = 'hide_if_away'
|
CONF_AWAY_HIDE = 'hide_if_away'
|
||||||
DEFAULT_AWAY_HIDE = False
|
DEFAULT_AWAY_HIDE = False
|
||||||
|
|
||||||
CONF_HOME_RANGE = 'home_range'
|
|
||||||
DEFAULT_HOME_RANGE = 100
|
|
||||||
|
|
||||||
SERVICE_SEE = 'see'
|
SERVICE_SEE = 'see'
|
||||||
|
|
||||||
ATTR_MAC = 'mac'
|
ATTR_MAC = 'mac'
|
||||||
@ -62,23 +63,33 @@ ATTR_LOCATION_NAME = 'location_name'
|
|||||||
ATTR_GPS = 'gps'
|
ATTR_GPS = 'gps'
|
||||||
ATTR_BATTERY = 'battery'
|
ATTR_BATTERY = 'battery'
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_int, # seconds
|
||||||
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
_CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [
|
||||||
|
vol.Schema({
|
||||||
|
vol.Optional(CONF_TRACK_NEW): cv.boolean,
|
||||||
|
vol.Optional(CONF_CONSIDER_HOME): cv.positive_int # seconds
|
||||||
|
}, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
DISCOVERY_PLATFORMS = {
|
DISCOVERY_PLATFORMS = {
|
||||||
SERVICE_NETGEAR: 'netgear',
|
SERVICE_NETGEAR: 'netgear',
|
||||||
}
|
}
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
|
||||||
|
|
||||||
|
def is_on(hass: HomeAssistantType, entity_id: str=None):
|
||||||
def is_on(hass, entity_id=None):
|
|
||||||
"""Return the state if any or a specified device is home."""
|
"""Return the state if any or a specified device is home."""
|
||||||
entity = entity_id or ENTITY_ID_ALL_DEVICES
|
entity = entity_id or ENTITY_ID_ALL_DEVICES
|
||||||
|
|
||||||
return hass.states.is_state(entity, STATE_HOME)
|
return hass.states.is_state(entity, STATE_HOME)
|
||||||
|
|
||||||
|
|
||||||
def see(hass, mac=None, dev_id=None, host_name=None, location_name=None,
|
def see(hass: HomeAssistantType, mac: str=None, dev_id: str=None,
|
||||||
gps=None, gps_accuracy=None, battery=None):
|
host_name: str=None, location_name: str=None,
|
||||||
|
gps: GPSType=None, gps_accuracy=None,
|
||||||
|
battery=None): # pylint: disable=too-many-arguments
|
||||||
"""Call service to notify you see device."""
|
"""Call service to notify you see device."""
|
||||||
data = {key: value for key, value in
|
data = {key: value for key, value in
|
||||||
((ATTR_MAC, mac),
|
((ATTR_MAC, mac),
|
||||||
@ -91,27 +102,24 @@ def see(hass, mac=None, dev_id=None, host_name=None, location_name=None,
|
|||||||
hass.services.call(DOMAIN, SERVICE_SEE, data)
|
hass.services.call(DOMAIN, SERVICE_SEE, data)
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass: HomeAssistantType, config: ConfigType):
|
||||||
"""Setup device tracker."""
|
"""Setup device tracker."""
|
||||||
yaml_path = hass.config.path(YAML_DEVICES)
|
yaml_path = hass.config.path(YAML_DEVICES)
|
||||||
|
|
||||||
conf = config.get(DOMAIN, {})
|
try:
|
||||||
|
conf = _CONFIG_SCHEMA(config).get(DOMAIN, [])
|
||||||
# Config can be an empty list. In that case, substitute a dict
|
except vol.Invalid as ex:
|
||||||
if isinstance(conf, list):
|
log_exception(ex, DOMAIN, config)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
conf = conf[0] if len(conf) > 0 else {}
|
conf = conf[0] if len(conf) > 0 else {}
|
||||||
|
consider_home = timedelta(
|
||||||
|
seconds=conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME))
|
||||||
|
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
|
||||||
|
|
||||||
consider_home = timedelta(
|
devices = load_config(yaml_path, hass, consider_home)
|
||||||
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
|
|
||||||
DEFAULT_CONSIDER_HOME))
|
|
||||||
track_new = util.convert(conf.get(CONF_TRACK_NEW), bool,
|
|
||||||
DEFAULT_CONF_TRACK_NEW)
|
|
||||||
home_range = util.convert(conf.get(CONF_HOME_RANGE), int,
|
|
||||||
DEFAULT_HOME_RANGE)
|
|
||||||
|
|
||||||
devices = load_config(yaml_path, hass, consider_home, home_range)
|
tracker = DeviceTracker(hass, consider_home, track_new, devices)
|
||||||
tracker = DeviceTracker(hass, consider_home, track_new, home_range,
|
|
||||||
devices)
|
|
||||||
|
|
||||||
def setup_platform(p_type, p_config, disc_info=None):
|
def setup_platform(p_type, p_config, disc_info=None):
|
||||||
"""Setup a device tracker platform."""
|
"""Setup a device tracker platform."""
|
||||||
@ -170,30 +178,37 @@ def setup(hass, config):
|
|||||||
class DeviceTracker(object):
|
class DeviceTracker(object):
|
||||||
"""Representation of a device tracker."""
|
"""Representation of a device tracker."""
|
||||||
|
|
||||||
def __init__(self, hass, consider_home, track_new, home_range, devices):
|
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
|
||||||
|
track_new: bool, devices: Sequence) -> None:
|
||||||
"""Initialize a device tracker."""
|
"""Initialize a device tracker."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.devices = {dev.dev_id: dev for dev in devices}
|
self.devices = {dev.dev_id: dev for dev in devices}
|
||||||
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
|
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
|
||||||
|
for dev in devices:
|
||||||
|
if self.devices[dev.dev_id] is not dev:
|
||||||
|
_LOGGER.warning('Duplicate device IDs detected %s', dev.dev_id)
|
||||||
|
if dev.mac and self.mac_to_dev[dev.mac] is not dev:
|
||||||
|
_LOGGER.warning('Duplicate device MAC addresses detected %s',
|
||||||
|
dev.mac)
|
||||||
self.consider_home = consider_home
|
self.consider_home = consider_home
|
||||||
self.track_new = track_new
|
self.track_new = track_new
|
||||||
self.home_range = home_range
|
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
|
|
||||||
for device in devices:
|
for device in devices:
|
||||||
if device.track:
|
if device.track:
|
||||||
device.update_ha_state()
|
device.update_ha_state()
|
||||||
|
|
||||||
self.group = None
|
self.group = None # type: group.Group
|
||||||
|
|
||||||
def see(self, mac=None, dev_id=None, host_name=None, location_name=None,
|
def see(self, mac: str=None, dev_id: str=None, host_name: str=None,
|
||||||
gps=None, gps_accuracy=None, battery=None):
|
location_name: str=None, gps: GPSType=None, gps_accuracy=None,
|
||||||
|
battery: str=None):
|
||||||
"""Notify the device tracker that you see a device."""
|
"""Notify the device tracker that you see a device."""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if mac is None and dev_id is None:
|
if mac is None and dev_id is None:
|
||||||
raise HomeAssistantError('Neither mac or device id passed in')
|
raise HomeAssistantError('Neither mac or device id passed in')
|
||||||
elif mac is not None:
|
elif mac is not None:
|
||||||
mac = mac.upper()
|
mac = str(mac).upper()
|
||||||
device = self.mac_to_dev.get(mac)
|
device = self.mac_to_dev.get(mac)
|
||||||
if not device:
|
if not device:
|
||||||
dev_id = util.slugify(host_name or '') or util.slugify(mac)
|
dev_id = util.slugify(host_name or '') or util.slugify(mac)
|
||||||
@ -211,7 +226,7 @@ class DeviceTracker(object):
|
|||||||
# If no device can be found, create it
|
# If no device can be found, create it
|
||||||
dev_id = util.ensure_unique_string(dev_id, self.devices.keys())
|
dev_id = util.ensure_unique_string(dev_id, self.devices.keys())
|
||||||
device = Device(
|
device = Device(
|
||||||
self.hass, self.consider_home, self.home_range, self.track_new,
|
self.hass, self.consider_home, self.track_new,
|
||||||
dev_id, mac, (host_name or dev_id).replace('_', ' '))
|
dev_id, mac, (host_name or dev_id).replace('_', ' '))
|
||||||
self.devices[dev_id] = device
|
self.devices[dev_id] = device
|
||||||
if mac is not None:
|
if mac is not None:
|
||||||
@ -234,7 +249,7 @@ class DeviceTracker(object):
|
|||||||
self.group = group.Group(
|
self.group = group.Group(
|
||||||
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
|
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
|
||||||
|
|
||||||
def update_stale(self, now):
|
def update_stale(self, now: dt_util.dt.datetime):
|
||||||
"""Update stale devices."""
|
"""Update stale devices."""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
for device in self.devices.values():
|
for device in self.devices.values():
|
||||||
@ -246,19 +261,21 @@ class DeviceTracker(object):
|
|||||||
class Device(Entity):
|
class Device(Entity):
|
||||||
"""Represent a tracked device."""
|
"""Represent a tracked device."""
|
||||||
|
|
||||||
host_name = None
|
host_name = None # type: str
|
||||||
location_name = None
|
location_name = None # type: str
|
||||||
gps = None
|
gps = None # type: GPSType
|
||||||
gps_accuracy = 0
|
gps_accuracy = 0
|
||||||
last_seen = None
|
last_seen = None # type: dt_util.dt.datetime
|
||||||
battery = None
|
battery = None # type: str
|
||||||
|
|
||||||
# Track if the last update of this device was HOME.
|
# Track if the last update of this device was HOME.
|
||||||
last_update_home = False
|
last_update_home = False
|
||||||
_state = STATE_NOT_HOME
|
_state = STATE_NOT_HOME
|
||||||
|
|
||||||
def __init__(self, hass, consider_home, home_range, track, dev_id, mac,
|
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
|
||||||
name=None, picture=None, gravatar=None, away_hide=False):
|
track: bool, dev_id: str, mac: str, name: str=None,
|
||||||
|
picture: str=None, gravatar: str=None,
|
||||||
|
away_hide: bool=False) -> None:
|
||||||
"""Initialize a device."""
|
"""Initialize a device."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
|
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
|
||||||
@ -267,8 +284,6 @@ class Device(Entity):
|
|||||||
# detected anymore.
|
# detected anymore.
|
||||||
self.consider_home = consider_home
|
self.consider_home = consider_home
|
||||||
|
|
||||||
# Distance in meters
|
|
||||||
self.home_range = home_range
|
|
||||||
# Device ID
|
# Device ID
|
||||||
self.dev_id = dev_id
|
self.dev_id = dev_id
|
||||||
self.mac = mac
|
self.mac = mac
|
||||||
@ -287,13 +302,6 @@ class Device(Entity):
|
|||||||
|
|
||||||
self.away_hide = away_hide
|
self.away_hide = away_hide
|
||||||
|
|
||||||
@property
|
|
||||||
def gps_home(self):
|
|
||||||
"""Return if device is within range of home."""
|
|
||||||
distance = max(
|
|
||||||
0, self.hass.config.distance(*self.gps) - self.gps_accuracy)
|
|
||||||
return self.gps is not None and distance <= self.home_range
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the entity."""
|
"""Return the name of the entity."""
|
||||||
@ -329,26 +337,24 @@ class Device(Entity):
|
|||||||
"""If device should be hidden."""
|
"""If device should be hidden."""
|
||||||
return self.away_hide and self.state != STATE_HOME
|
return self.away_hide and self.state != STATE_HOME
|
||||||
|
|
||||||
def seen(self, host_name=None, location_name=None, gps=None,
|
def seen(self, host_name: str=None, location_name: str=None,
|
||||||
gps_accuracy=0, battery=None):
|
gps: GPSType=None, gps_accuracy=0, battery: str=None):
|
||||||
"""Mark the device as seen."""
|
"""Mark the device as seen."""
|
||||||
self.last_seen = dt_util.utcnow()
|
self.last_seen = dt_util.utcnow()
|
||||||
self.host_name = host_name
|
self.host_name = host_name
|
||||||
self.location_name = location_name
|
self.location_name = location_name
|
||||||
self.gps_accuracy = gps_accuracy or 0
|
self.gps_accuracy = gps_accuracy or 0
|
||||||
self.battery = battery
|
self.battery = battery
|
||||||
if gps is None:
|
self.gps = None
|
||||||
self.gps = None
|
if gps is not None:
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
self.gps = tuple(float(val) for val in gps)
|
self.gps = float(gps[0]), float(gps[1])
|
||||||
except ValueError:
|
except (ValueError, TypeError, IndexError):
|
||||||
_LOGGER.warning('Could not parse gps value for %s: %s',
|
_LOGGER.warning('Could not parse gps value for %s: %s',
|
||||||
self.dev_id, gps)
|
self.dev_id, gps)
|
||||||
self.gps = None
|
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def stale(self, now=None):
|
def stale(self, now: dt_util.dt.datetime=None):
|
||||||
"""Return if device state is stale."""
|
"""Return if device state is stale."""
|
||||||
return self.last_seen and \
|
return self.last_seen and \
|
||||||
(now or dt_util.utcnow()) - self.last_seen > self.consider_home
|
(now or dt_util.utcnow()) - self.last_seen > self.consider_home
|
||||||
@ -377,32 +383,30 @@ class Device(Entity):
|
|||||||
self.last_update_home = True
|
self.last_update_home = True
|
||||||
|
|
||||||
|
|
||||||
def load_config(path, hass, consider_home, home_range):
|
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
|
||||||
"""Load devices from YAML configuration file."""
|
"""Load devices from YAML configuration file."""
|
||||||
if not os.path.isfile(path):
|
|
||||||
return []
|
|
||||||
try:
|
try:
|
||||||
return [
|
return [
|
||||||
Device(hass, consider_home, home_range, 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(), 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))
|
||||||
for dev_id, device in load_yaml_config_file(path).items()]
|
for dev_id, device in load_yaml_config_file(path).items()]
|
||||||
except HomeAssistantError:
|
except (HomeAssistantError, FileNotFoundError):
|
||||||
# When YAML file could not be loaded/did not contain a dict
|
# When YAML file could not be loaded/did not contain a dict
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def setup_scanner_platform(hass, config, scanner, see_device):
|
def setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
|
||||||
|
scanner: Any, see_device: Callable):
|
||||||
"""Helper method to connect scanner-based platform to device tracker."""
|
"""Helper method to connect scanner-based platform to device tracker."""
|
||||||
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
|
interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||||
DEFAULT_SCAN_INTERVAL)
|
|
||||||
|
|
||||||
# Initial scan of each mac we also tell about host name for config
|
# Initial scan of each mac we also tell about host name for config
|
||||||
seen = set()
|
seen = set() # type: Any
|
||||||
|
|
||||||
def device_tracker_scan(now):
|
def device_tracker_scan(now: dt_util.dt.datetime):
|
||||||
"""Called when interval matches."""
|
"""Called when interval matches."""
|
||||||
for mac in scanner.scan_devices():
|
for mac in scanner.scan_devices():
|
||||||
if mac in seen:
|
if mac in seen:
|
||||||
@ -418,7 +422,7 @@ def setup_scanner_platform(hass, config, scanner, see_device):
|
|||||||
device_tracker_scan(None)
|
device_tracker_scan(None)
|
||||||
|
|
||||||
|
|
||||||
def update_config(path, dev_id, device):
|
def update_config(path: str, dev_id: str, device: Device):
|
||||||
"""Add device to YAML configuration file."""
|
"""Add device to YAML configuration file."""
|
||||||
with open(path, 'a') as out:
|
with open(path, 'a') as out:
|
||||||
out.write('\n')
|
out.write('\n')
|
||||||
@ -432,8 +436,8 @@ def update_config(path, dev_id, device):
|
|||||||
out.write(' {}: {}\n'.format(key, '' if value is None else value))
|
out.write(' {}: {}\n'.format(key, '' if value is None else value))
|
||||||
|
|
||||||
|
|
||||||
def get_gravatar_for_email(email):
|
def get_gravatar_for_email(email: str):
|
||||||
"""Return an 80px Gravatar for the given email address."""
|
"""Return an 80px Gravatar for the given email address."""
|
||||||
import hashlib
|
import hashlib
|
||||||
url = "https://www.gravatar.com/avatar/{}.jpg?s=80&d=wavatar"
|
url = 'https://www.gravatar.com/avatar/{}.jpg?s=80&d=wavatar'
|
||||||
return url.format(hashlib.md5(email.encode('utf-8').lower()).hexdigest())
|
return url.format(hashlib.md5(email.encode('utf-8').lower()).hexdigest())
|
||||||
|
@ -34,9 +34,9 @@ PLATFORM_SCHEMA = vol.All(
|
|||||||
vol.Required(CONF_USERNAME): cv.string,
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
vol.Optional(CONF_PASSWORD): cv.string,
|
vol.Optional(CONF_PASSWORD): cv.string,
|
||||||
vol.Optional(CONF_PROTOCOL, default='ssh'):
|
vol.Optional(CONF_PROTOCOL, default='ssh'):
|
||||||
vol.Schema(['ssh', 'telnet']),
|
vol.In(['ssh', 'telnet']),
|
||||||
vol.Optional(CONF_MODE, default='router'):
|
vol.Optional(CONF_MODE, default='router'):
|
||||||
vol.Schema(['router', 'ap']),
|
vol.In(['router', 'ap']),
|
||||||
vol.Optional(CONF_SSH_KEY): cv.isfile,
|
vol.Optional(CONF_SSH_KEY): cv.isfile,
|
||||||
vol.Optional(CONF_PUB_KEY): cv.isfile
|
vol.Optional(CONF_PUB_KEY): cv.isfile
|
||||||
}))
|
}))
|
||||||
|
@ -63,7 +63,7 @@ def setup_scanner(hass, config, see):
|
|||||||
# Load all known devices.
|
# Load all known devices.
|
||||||
# We just need the devices so set consider_home and home range
|
# We just need the devices so set consider_home and home range
|
||||||
# to 0
|
# to 0
|
||||||
for device in load_config(yaml_path, hass, 0, 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[:3].upper() == BLE_PREFIX:
|
||||||
if device.track:
|
if device.track:
|
||||||
|
@ -45,7 +45,7 @@ def setup_scanner(hass, config, see):
|
|||||||
# Load all known devices.
|
# Load all known devices.
|
||||||
# We just need the devices so set consider_home and home range
|
# We just need the devices so set consider_home and home range
|
||||||
# to 0
|
# to 0
|
||||||
for device in load_config(yaml_path, hass, 0, 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() == BT_PREFIX:
|
if device.mac and device.mac[:3].upper() == BT_PREFIX:
|
||||||
if device.track:
|
if device.track:
|
||||||
|
@ -129,7 +129,7 @@ CONFIG_SCHEMA = vol.Schema({
|
|||||||
vol.In(CONF_RESOLVENAMES_OPTIONS),
|
vol.In(CONF_RESOLVENAMES_OPTIONS),
|
||||||
vol.Optional(CONF_USERNAME, default="Admin"): cv.string,
|
vol.Optional(CONF_USERNAME, default="Admin"): cv.string,
|
||||||
vol.Optional(CONF_PASSWORD, default=""): cv.string,
|
vol.Optional(CONF_PASSWORD, default=""): cv.string,
|
||||||
vol.Optional(CONF_DELAY, default=0.5): cv.string,
|
vol.Optional(CONF_DELAY, default=0.5): vol.Coerce(float),
|
||||||
}),
|
}),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ def _system_callback_handler(hass, config, src, *args):
|
|||||||
for component_name, discovery_type in (
|
for component_name, discovery_type in (
|
||||||
('switch', DISCOVER_SWITCHES),
|
('switch', DISCOVER_SWITCHES),
|
||||||
('light', DISCOVER_LIGHTS),
|
('light', DISCOVER_LIGHTS),
|
||||||
('rollershutter', DISCOVER_COVER),
|
('cover', DISCOVER_COVER),
|
||||||
('binary_sensor', DISCOVER_BINARY_SENSORS),
|
('binary_sensor', DISCOVER_BINARY_SENSORS),
|
||||||
('sensor', DISCOVER_SENSORS),
|
('sensor', DISCOVER_SENSORS),
|
||||||
('climate', DISCOVER_CLIMATE)):
|
('climate', DISCOVER_CLIMATE)):
|
||||||
|
@ -7,9 +7,11 @@ https://home-assistant.io/components/light.flux_led/
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
import random
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_RGB_COLOR,
|
from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_RGB_COLOR,
|
||||||
|
ATTR_EFFECT, EFFECT_RANDOM,
|
||||||
SUPPORT_BRIGHTNESS,
|
SUPPORT_BRIGHTNESS,
|
||||||
SUPPORT_RGB_COLOR, Light)
|
SUPPORT_RGB_COLOR, Light)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
@ -125,10 +127,15 @@ class FluxLight(Light):
|
|||||||
|
|
||||||
rgb = kwargs.get(ATTR_RGB_COLOR)
|
rgb = kwargs.get(ATTR_RGB_COLOR)
|
||||||
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
brightness = kwargs.get(ATTR_BRIGHTNESS)
|
||||||
|
effect = kwargs.get(ATTR_EFFECT)
|
||||||
if rgb:
|
if rgb:
|
||||||
self._bulb.setRgb(*tuple(rgb))
|
self._bulb.setRgb(*tuple(rgb))
|
||||||
elif brightness:
|
elif brightness:
|
||||||
self._bulb.setWarmWhite255(brightness)
|
self._bulb.setWarmWhite255(brightness)
|
||||||
|
elif effect == EFFECT_RANDOM:
|
||||||
|
self._bulb.setRgb(random.randrange(0, 255),
|
||||||
|
random.randrange(0, 255),
|
||||||
|
random.randrange(0, 255))
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
"""Turn the specified or all lights off."""
|
"""Turn the specified or all lights off."""
|
||||||
|
@ -97,7 +97,6 @@ SERVICE_TO_METHOD = {
|
|||||||
SERVICE_MEDIA_STOP: 'media_stop',
|
SERVICE_MEDIA_STOP: 'media_stop',
|
||||||
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
|
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
|
||||||
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
|
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
|
||||||
SERVICE_SELECT_SOURCE: 'select_source',
|
|
||||||
SERVICE_CLEAR_PLAYLIST: 'clear_playlist'
|
SERVICE_CLEAR_PLAYLIST: 'clear_playlist'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,25 +40,25 @@ volume_down:
|
|||||||
description: Name(s) of entities to turn volume down on
|
description: Name(s) of entities to turn volume down on
|
||||||
example: 'media_player.living_room_sonos'
|
example: 'media_player.living_room_sonos'
|
||||||
|
|
||||||
mute_volume:
|
volume_mute:
|
||||||
description: Mute a media player's volume
|
description: Mute a media player's volume
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entities to mute
|
description: Name(s) of entities to mute
|
||||||
example: 'media_player.living_room_sonos'
|
example: 'media_player.living_room_sonos'
|
||||||
mute:
|
is_volume_muted:
|
||||||
description: True/false for mute/unmute
|
description: True/false for mute/unmute
|
||||||
example: true
|
example: true
|
||||||
|
|
||||||
set_volume_level:
|
volume_set:
|
||||||
description: Set a media player's volume level
|
description: Set a media player's volume level
|
||||||
|
|
||||||
fields:
|
fields:
|
||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entities to set volume level on
|
description: Name(s) of entities to set volume level on
|
||||||
example: 'media_player.living_room_sonos'
|
example: 'media_player.living_room_sonos'
|
||||||
volume:
|
volume_level:
|
||||||
description: Volume level to set
|
description: Volume level to set
|
||||||
example: 60
|
example: 60
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ media_seek:
|
|||||||
entity_id:
|
entity_id:
|
||||||
description: Name(s) of entities to seek media on
|
description: Name(s) of entities to seek media on
|
||||||
example: 'media_player.living_room_chromecast'
|
example: 'media_player.living_room_chromecast'
|
||||||
position:
|
seek_position:
|
||||||
description: Position to seek to. The format is platform dependent.
|
description: Position to seek to. The format is platform dependent.
|
||||||
example: 100
|
example: 100
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant.components.notify import (
|
|||||||
ATTR_TITLE, DOMAIN, BaseNotificationService)
|
ATTR_TITLE, DOMAIN, BaseNotificationService)
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
|
|
||||||
REQUIREMENTS = ['sendgrid==3.1.10']
|
REQUIREMENTS = ['sendgrid==3.2.10']
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,31 +10,46 @@ from email.mime.multipart import MIMEMultipart
|
|||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.mime.image import MIMEImage
|
from email.mime.image import MIMEImage
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components.notify import (
|
from homeassistant.components.notify import (
|
||||||
ATTR_TITLE, ATTR_DATA, DOMAIN, BaseNotificationService)
|
ATTR_TITLE, ATTR_DATA, PLATFORM_SCHEMA, BaseNotificationService)
|
||||||
from homeassistant.helpers import validate_config
|
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_IMAGES = 'images' # optional embedded image file attachments
|
ATTR_IMAGES = 'images' # optional embedded image file attachments
|
||||||
|
|
||||||
|
CONF_STARTTLS = 'starttls'
|
||||||
|
CONF_SENDER = 'sender'
|
||||||
|
CONF_RECIPIENT = 'recipient'
|
||||||
|
CONF_DEBUG = 'debug'
|
||||||
|
CONF_SERVER = 'server'
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Required(CONF_RECIPIENT): cv.string,
|
||||||
|
vol.Optional(CONF_SERVER, default='localhost'): cv.string,
|
||||||
|
vol.Optional(CONF_PORT, default=25): cv.port,
|
||||||
|
vol.Optional(CONF_SENDER): cv.string,
|
||||||
|
vol.Optional(CONF_STARTTLS, default=False): cv.boolean,
|
||||||
|
vol.Optional(CONF_USERNAME): cv.string,
|
||||||
|
vol.Optional(CONF_PASSWORD): cv.string,
|
||||||
|
vol.Optional(CONF_DEBUG, default=False): cv.boolean,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config):
|
||||||
"""Get the mail notification service."""
|
"""Get the mail notification service."""
|
||||||
if not validate_config({DOMAIN: config},
|
|
||||||
{DOMAIN: ['recipient']},
|
|
||||||
_LOGGER):
|
|
||||||
return None
|
|
||||||
|
|
||||||
mail_service = MailNotificationService(
|
mail_service = MailNotificationService(
|
||||||
config.get('server', 'localhost'),
|
config.get(CONF_SERVER),
|
||||||
int(config.get('port', '25')),
|
config.get(CONF_PORT),
|
||||||
config.get('sender', None),
|
config.get(CONF_SENDER),
|
||||||
int(config.get('starttls', 0)),
|
config.get(CONF_STARTTLS),
|
||||||
config.get('username', None),
|
config.get(CONF_USERNAME),
|
||||||
config.get('password', None),
|
config.get(CONF_PASSWORD),
|
||||||
config.get('recipient', None),
|
config.get(CONF_RECIPIENT),
|
||||||
config.get('debug', 0))
|
config.get(CONF_DEBUG))
|
||||||
|
|
||||||
if mail_service.connection_is_valid():
|
if mail_service.connection_is_valid():
|
||||||
return mail_service
|
return mail_service
|
||||||
@ -65,7 +80,7 @@ class MailNotificationService(BaseNotificationService):
|
|||||||
mail = smtplib.SMTP(self._server, self._port, timeout=5)
|
mail = smtplib.SMTP(self._server, self._port, timeout=5)
|
||||||
mail.set_debuglevel(self.debug)
|
mail.set_debuglevel(self.debug)
|
||||||
mail.ehlo_or_helo_if_needed()
|
mail.ehlo_or_helo_if_needed()
|
||||||
if self.starttls == 1:
|
if self.starttls:
|
||||||
mail.starttls()
|
mail.starttls()
|
||||||
mail.ehlo()
|
mail.ehlo()
|
||||||
if self.username and self.password:
|
if self.username and self.password:
|
||||||
|
@ -11,7 +11,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN
|
|||||||
from homeassistant.helpers import validate_config
|
from homeassistant.helpers import validate_config
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
REQUIREMENTS = ['TwitterAPI==2.4.1']
|
REQUIREMENTS = ['TwitterAPI==2.4.2']
|
||||||
|
|
||||||
CONF_CONSUMER_KEY = "consumer_key"
|
CONF_CONSUMER_KEY = "consumer_key"
|
||||||
CONF_CONSUMER_SECRET = "consumer_secret"
|
CONF_CONSUMER_SECRET = "consumer_secret"
|
||||||
|
@ -6,10 +6,16 @@ https://home-assistant.io/components/sensor.apcupsd/
|
|||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components import apcupsd
|
from homeassistant.components import apcupsd
|
||||||
from homeassistant.const import TEMP_CELSIUS
|
from homeassistant.const import (TEMP_CELSIUS, CONF_RESOURCES)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEPENDENCIES = [apcupsd.DOMAIN]
|
DEPENDENCIES = [apcupsd.DOMAIN]
|
||||||
|
|
||||||
SENSOR_PREFIX = 'UPS '
|
SENSOR_PREFIX = 'UPS '
|
||||||
@ -92,14 +98,17 @@ INFERRED_UNITS = {
|
|||||||
' C': TEMP_CELSIUS,
|
' C': TEMP_CELSIUS,
|
||||||
}
|
}
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Required(CONF_RESOURCES, default=[]):
|
||||||
|
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Setup the APCUPSd sensors."""
|
"""Setup the APCUPSd sensors."""
|
||||||
entities = []
|
entities = []
|
||||||
|
|
||||||
for resource in config['resources']:
|
for resource in config[CONF_RESOURCES]:
|
||||||
sensor_type = resource.lower()
|
sensor_type = resource.lower()
|
||||||
|
|
||||||
if sensor_type not in SENSOR_TYPES:
|
if sensor_type not in SENSOR_TYPES:
|
||||||
@ -109,7 +118,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
if sensor_type.upper() not in apcupsd.DATA.status:
|
if sensor_type.upper() not in apcupsd.DATA.status:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
'Sensor type: "%s" does not appear in the APCUPSd status '
|
'Sensor type: "%s" does not appear in the APCUPSd status '
|
||||||
'output.', sensor_type)
|
'output', sensor_type)
|
||||||
|
|
||||||
entities.append(APCUPSdSensor(apcupsd.DATA, sensor_type))
|
entities.append(APCUPSdSensor(apcupsd.DATA, sensor_type))
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ CONF_SECOND = 'second'
|
|||||||
CONF_MINUTE = 'minute'
|
CONF_MINUTE = 'minute'
|
||||||
CONF_HOUR = 'hour'
|
CONF_HOUR = 'hour'
|
||||||
CONF_DAY = 'day'
|
CONF_DAY = 'day'
|
||||||
|
CONF_SERVER_ID = 'server_id'
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'ping': ['Ping', 'ms'],
|
'ping': ['Ping', 'ms'],
|
||||||
'download': ['Download', 'Mbit/s'],
|
'download': ['Download', 'Mbit/s'],
|
||||||
@ -38,6 +39,7 @@ SENSOR_TYPES = {
|
|||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Required(CONF_MONITORED_CONDITIONS):
|
vol.Required(CONF_MONITORED_CONDITIONS):
|
||||||
vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES.keys()))]),
|
vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES.keys()))]),
|
||||||
|
vol.Optional(CONF_SERVER_ID): cv.positive_int,
|
||||||
vol.Optional(CONF_SECOND, default=[0]):
|
vol.Optional(CONF_SECOND, default=[0]):
|
||||||
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]),
|
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]),
|
||||||
vol.Optional(CONF_MINUTE, default=[0]):
|
vol.Optional(CONF_MINUTE, default=[0]):
|
||||||
@ -131,6 +133,7 @@ class SpeedtestData(object):
|
|||||||
def __init__(self, hass, config):
|
def __init__(self, hass, config):
|
||||||
"""Initialize the data object."""
|
"""Initialize the data object."""
|
||||||
self.data = None
|
self.data = None
|
||||||
|
self._server_id = config.get(CONF_SERVER_ID)
|
||||||
track_time_change(hass, self.update,
|
track_time_change(hass, self.update,
|
||||||
second=config.get(CONF_SECOND),
|
second=config.get(CONF_SECOND),
|
||||||
minute=config.get(CONF_MINUTE),
|
minute=config.get(CONF_MINUTE),
|
||||||
@ -143,9 +146,12 @@ class SpeedtestData(object):
|
|||||||
|
|
||||||
_LOGGER.info('Executing speedtest')
|
_LOGGER.info('Executing speedtest')
|
||||||
try:
|
try:
|
||||||
|
args = [sys.executable, speedtest_cli.__file__, '--simple']
|
||||||
|
if self._server_id:
|
||||||
|
args = args + ['--server', str(self._server_id)]
|
||||||
|
|
||||||
re_output = _SPEEDTEST_REGEX.split(
|
re_output = _SPEEDTEST_REGEX.split(
|
||||||
check_output([sys.executable, speedtest_cli.__file__,
|
check_output(args).decode("utf-8"))
|
||||||
'--simple']).decode("utf-8"))
|
|
||||||
except CalledProcessError as process_error:
|
except CalledProcessError as process_error:
|
||||||
_LOGGER.error('Error executing speedtest: %s', process_error)
|
_LOGGER.error('Error executing speedtest: %s', process_error)
|
||||||
return
|
return
|
||||||
|
@ -101,7 +101,10 @@ class WUndergroundSensor(Entity):
|
|||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
if self.rest.data and self._condition in self.rest.data:
|
if self.rest.data and self._condition in self.rest.data:
|
||||||
return self.rest.data[self._condition]
|
if self._condition == 'relative_humidity':
|
||||||
|
return int(self.rest.data[self._condition][:-1])
|
||||||
|
else:
|
||||||
|
return self.rest.data[self._condition]
|
||||||
else:
|
else:
|
||||||
return STATE_UNKNOWN
|
return STATE_UNKNOWN
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# Add static devices from the config file.
|
# Add static devices from the config file.
|
||||||
devices.extend((address, None)
|
devices.extend((address, None)
|
||||||
for address in config.get(DOMAIN, {}).get(CONF_STATIC))
|
for address in config.get(DOMAIN, {}).get(CONF_STATIC, []))
|
||||||
|
|
||||||
for address, device in devices:
|
for address, device in devices:
|
||||||
port = pywemo.ouimeaux_device.probe_wemo(address)
|
port = pywemo.ouimeaux_device.probe_wemo(address)
|
||||||
|
@ -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.0'
|
__version__ = '0.27.1'
|
||||||
REQUIRED_PYTHON_VER = (3, 4)
|
REQUIRED_PYTHON_VER = (3, 4)
|
||||||
|
|
||||||
PLATFORM_FORMAT = '{}.{}'
|
PLATFORM_FORMAT = '{}.{}'
|
||||||
|
@ -1,24 +1,13 @@
|
|||||||
"""Typing Helpers for Home-Assistant."""
|
"""Typing Helpers for Home-Assistant."""
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any, Tuple
|
||||||
|
|
||||||
# NOTE: NewType added to typing in 3.5.2 in June, 2016; Since 3.5.2 includes
|
import homeassistant.core
|
||||||
# security fixes everyone on 3.5 should upgrade "soon"
|
|
||||||
try:
|
|
||||||
from typing import NewType
|
|
||||||
except ImportError:
|
|
||||||
NewType = None
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
if NewType:
|
|
||||||
ConfigType = NewType('ConfigType', Dict[str, Any])
|
|
||||||
|
|
||||||
# Custom type for recorder Queries
|
GPSType = Tuple[float, float]
|
||||||
QueryType = NewType('QueryType', Any)
|
ConfigType = Dict[str, Any]
|
||||||
|
HomeAssistantType = homeassistant.core.HomeAssistant
|
||||||
|
|
||||||
# Duplicates for 3.5.1
|
# Custom type for recorder Queries
|
||||||
# pylint: disable=invalid-name
|
QueryType = Any
|
||||||
else:
|
|
||||||
ConfigType = Dict[str, Any] # type: ignore
|
|
||||||
|
|
||||||
# Custom type for recorder Queries
|
|
||||||
QueryType = Any # type: ignore
|
|
||||||
|
@ -12,7 +12,7 @@ import string
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
|
||||||
from typing import Any, Optional, TypeVar, Callable, Sequence
|
from typing import Any, Optional, TypeVar, Callable, Sequence, KeysView, Union
|
||||||
|
|
||||||
from .dt import as_local, utcnow
|
from .dt import as_local, utcnow
|
||||||
|
|
||||||
@ -63,8 +63,8 @@ def convert(value: T, to_type: Callable[[T], U],
|
|||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def ensure_unique_string(preferred_string: str,
|
def ensure_unique_string(preferred_string: str, current_strings:
|
||||||
current_strings: Sequence[str]) -> str:
|
Union[Sequence[str], KeysView[str]]) -> str:
|
||||||
"""Return a string that is not present in current_strings.
|
"""Return a string that is not present in current_strings.
|
||||||
|
|
||||||
If preferred string exists will append _2, _3, ..
|
If preferred string exists will append _2, _3, ..
|
||||||
|
@ -23,7 +23,7 @@ PyMata==2.12
|
|||||||
SoCo==0.11.1
|
SoCo==0.11.1
|
||||||
|
|
||||||
# homeassistant.components.notify.twitter
|
# homeassistant.components.notify.twitter
|
||||||
TwitterAPI==2.4.1
|
TwitterAPI==2.4.2
|
||||||
|
|
||||||
# homeassistant.components.http
|
# homeassistant.components.http
|
||||||
Werkzeug==0.11.10
|
Werkzeug==0.11.10
|
||||||
@ -104,7 +104,7 @@ gps3==0.33.2
|
|||||||
|
|
||||||
# homeassistant.components.binary_sensor.ffmpeg
|
# homeassistant.components.binary_sensor.ffmpeg
|
||||||
# homeassistant.components.camera.ffmpeg
|
# homeassistant.components.camera.ffmpeg
|
||||||
ha-ffmpeg==0.8
|
ha-ffmpeg==0.9
|
||||||
|
|
||||||
# homeassistant.components.mqtt.server
|
# homeassistant.components.mqtt.server
|
||||||
hbmqtt==0.7.1
|
hbmqtt==0.7.1
|
||||||
@ -422,7 +422,7 @@ schiene==0.17
|
|||||||
scsgate==0.1.0
|
scsgate==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.notify.sendgrid
|
# homeassistant.components.notify.sendgrid
|
||||||
sendgrid==3.1.10
|
sendgrid==3.2.10
|
||||||
|
|
||||||
# homeassistant.components.notify.slack
|
# homeassistant.components.notify.slack
|
||||||
slacker==0.9.24
|
slacker==0.9.24
|
||||||
|
@ -7,7 +7,7 @@ from io import StringIO
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant import core as ha, loader
|
from homeassistant import core as ha, loader
|
||||||
from homeassistant.bootstrap import _setup_component
|
from homeassistant.bootstrap import setup_component
|
||||||
from homeassistant.helpers.entity import ToggleEntity
|
from homeassistant.helpers.entity import ToggleEntity
|
||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
from homeassistant.util.unit_system import METRIC_SYSTEM
|
||||||
import homeassistant.util.dt as date_util
|
import homeassistant.util.dt as date_util
|
||||||
@ -137,15 +137,15 @@ def mock_http_component(hass):
|
|||||||
hass.config.components.append('http')
|
hass.config.components.append('http')
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('homeassistant.components.mqtt.MQTT')
|
def mock_mqtt_component(hass):
|
||||||
def mock_mqtt_component(hass, mock_mqtt):
|
|
||||||
"""Mock the MQTT component."""
|
"""Mock the MQTT component."""
|
||||||
_setup_component(hass, mqtt.DOMAIN, {
|
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_mqtt:
|
||||||
mqtt.DOMAIN: {
|
setup_component(hass, mqtt.DOMAIN, {
|
||||||
mqtt.CONF_BROKER: 'mock-broker',
|
mqtt.DOMAIN: {
|
||||||
}
|
mqtt.CONF_BROKER: 'mock-broker',
|
||||||
})
|
}
|
||||||
return mock_mqtt
|
})
|
||||||
|
return mock_mqtt
|
||||||
|
|
||||||
|
|
||||||
class MockModule(object):
|
class MockModule(object):
|
||||||
|
@ -110,16 +110,19 @@ class TestDemoClimate(unittest.TestCase):
|
|||||||
|
|
||||||
def test_set_operation_bad_attr(self):
|
def test_set_operation_bad_attr(self):
|
||||||
"""Test setting operation mode without required attribute."""
|
"""Test setting operation mode without required attribute."""
|
||||||
self.assertEqual("Cool", self.hass.states.get(ENTITY_CLIMATE).state)
|
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||||
|
self.assertEqual("Cool", state.attributes.get('operation_mode'))
|
||||||
climate.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
|
climate.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual("Cool", self.hass.states.get(ENTITY_CLIMATE).state)
|
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||||
|
self.assertEqual("Cool", state.attributes.get('operation_mode'))
|
||||||
|
|
||||||
def test_set_operation(self):
|
def test_set_operation(self):
|
||||||
"""Test setting of new operation mode."""
|
"""Test setting of new operation mode."""
|
||||||
climate.set_operation_mode(self.hass, "Heat", ENTITY_CLIMATE)
|
climate.set_operation_mode(self.hass, "Heat", ENTITY_CLIMATE)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
self.assertEqual("Heat", self.hass.states.get(ENTITY_CLIMATE).state)
|
state = self.hass.states.get(ENTITY_CLIMATE)
|
||||||
|
self.assertEqual("Heat", state.attributes.get('operation_mode'))
|
||||||
|
|
||||||
def test_set_away_mode_bad_attr(self):
|
def test_set_away_mode_bad_attr(self):
|
||||||
"""Test setting the away mode without required attribute."""
|
"""Test setting the away mode without required attribute."""
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"""The tests for the device tracker component."""
|
"""The tests for the device tracker component."""
|
||||||
# pylint: disable=protected-access,too-many-public-methods
|
# pylint: disable=protected-access,too-many-public-methods
|
||||||
|
import logging
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from homeassistant.loader import get_component
|
from homeassistant.loader import get_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -12,14 +12,23 @@ from homeassistant.const import (
|
|||||||
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN,
|
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, ATTR_HIDDEN,
|
||||||
STATE_HOME, STATE_NOT_HOME, CONF_PLATFORM)
|
STATE_HOME, STATE_NOT_HOME, CONF_PLATFORM)
|
||||||
import homeassistant.components.device_tracker as device_tracker
|
import homeassistant.components.device_tracker as device_tracker
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
get_test_home_assistant, fire_time_changed, fire_service_discovered)
|
get_test_home_assistant, fire_time_changed, fire_service_discovered,
|
||||||
|
patch_yaml_files)
|
||||||
|
|
||||||
|
TEST_PLATFORM = {device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestComponentsDeviceTracker(unittest.TestCase):
|
class TestComponentsDeviceTracker(unittest.TestCase):
|
||||||
"""Test the Device tracker."""
|
"""Test the Device tracker."""
|
||||||
|
|
||||||
|
hass = None # HomeAssistant
|
||||||
|
yaml_devices = None # type: str
|
||||||
|
|
||||||
def setUp(self): # pylint: disable=invalid-name
|
def setUp(self): # pylint: disable=invalid-name
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
self.hass = get_test_home_assistant()
|
self.hass = get_test_home_assistant()
|
||||||
@ -48,27 +57,28 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
|
|
||||||
def test_reading_broken_yaml_config(self): # pylint: disable=no-self-use
|
def test_reading_broken_yaml_config(self): # pylint: disable=no-self-use
|
||||||
"""Test when known devices contains invalid data."""
|
"""Test when known devices contains invalid data."""
|
||||||
with tempfile.NamedTemporaryFile() as fpt:
|
files = {'empty.yaml': '',
|
||||||
# file is empty
|
'bad.yaml': '100',
|
||||||
assert device_tracker.load_config(fpt.name, None, False, 0) == []
|
'ok.yaml': 'my_device:\n name: Device'}
|
||||||
|
with patch_yaml_files(files):
|
||||||
fpt.write('100'.encode('utf-8'))
|
# File is empty
|
||||||
fpt.flush()
|
assert device_tracker.load_config('empty.yaml', None, False) == []
|
||||||
|
# File contains a non-dict format
|
||||||
# file contains a non-dict format
|
assert device_tracker.load_config('bad.yaml', None, False) == []
|
||||||
assert device_tracker.load_config(fpt.name, None, False, 0) == []
|
# A file that works fine
|
||||||
|
assert len(device_tracker.load_config('ok.yaml', None, False)) == 1
|
||||||
|
|
||||||
def test_reading_yaml_config(self):
|
def test_reading_yaml_config(self):
|
||||||
"""Test the rendering of the YAML configuration."""
|
"""Test the rendering of the YAML configuration."""
|
||||||
dev_id = 'test'
|
dev_id = 'test'
|
||||||
device = device_tracker.Device(
|
device = device_tracker.Device(
|
||||||
self.hass, timedelta(seconds=180), 0, True, dev_id,
|
self.hass, timedelta(seconds=180), True, dev_id,
|
||||||
'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture',
|
'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture',
|
||||||
away_hide=True)
|
away_hide=True)
|
||||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||||
device.consider_home, 0)[0]
|
device.consider_home)[0]
|
||||||
self.assertEqual(device.dev_id, config.dev_id)
|
self.assertEqual(device.dev_id, config.dev_id)
|
||||||
self.assertEqual(device.track, config.track)
|
self.assertEqual(device.track, config.track)
|
||||||
self.assertEqual(device.mac, config.mac)
|
self.assertEqual(device.mac, config.mac)
|
||||||
@ -76,12 +86,44 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
self.assertEqual(device.away_hide, config.away_hide)
|
self.assertEqual(device.away_hide, config.away_hide)
|
||||||
self.assertEqual(device.consider_home, config.consider_home)
|
self.assertEqual(device.consider_home, config.consider_home)
|
||||||
|
|
||||||
|
@patch('homeassistant.components.device_tracker._LOGGER.warning')
|
||||||
|
def test_track_with_duplicate_mac_dev_id(self, mock_warning): \
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
"""Test adding duplicate MACs or device IDs to DeviceTracker."""
|
||||||
|
devices = [
|
||||||
|
device_tracker.Device(self.hass, True, True, 'my_device', 'AB:01',
|
||||||
|
'My device', None, None, False),
|
||||||
|
device_tracker.Device(self.hass, True, True, 'your_device',
|
||||||
|
'AB:01', 'Your device', None, None, False)]
|
||||||
|
device_tracker.DeviceTracker(self.hass, False, True, devices)
|
||||||
|
_LOGGER.debug(mock_warning.call_args_list)
|
||||||
|
assert mock_warning.call_count == 1, \
|
||||||
|
"The only warning call should be duplicates (check DEBUG)"
|
||||||
|
args, _ = mock_warning.call_args
|
||||||
|
assert 'Duplicate device MAC' in args[0], \
|
||||||
|
'Duplicate MAC warning expected'
|
||||||
|
|
||||||
|
mock_warning.reset_mock()
|
||||||
|
devices = [
|
||||||
|
device_tracker.Device(self.hass, True, True, 'my_device',
|
||||||
|
'AB:01', 'My device', None, None, False),
|
||||||
|
device_tracker.Device(self.hass, True, True, 'my_device',
|
||||||
|
None, 'Your device', None, None, False)]
|
||||||
|
device_tracker.DeviceTracker(self.hass, False, True, devices)
|
||||||
|
|
||||||
|
_LOGGER.debug(mock_warning.call_args_list)
|
||||||
|
assert mock_warning.call_count == 1, \
|
||||||
|
"The only warning call should be duplicates (check DEBUG)"
|
||||||
|
args, _ = mock_warning.call_args
|
||||||
|
assert 'Duplicate device IDs' in args[0], \
|
||||||
|
'Duplicate device IDs warning expected'
|
||||||
|
|
||||||
def test_setup_without_yaml_file(self):
|
def test_setup_without_yaml_file(self):
|
||||||
"""Test with no YAML file."""
|
"""Test with no YAML file."""
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
def test_adding_unknown_device_to_config(self): \
|
||||||
def test_adding_unknown_device_to_config(self):
|
# pylint: disable=invalid-name
|
||||||
"""Test the adding of unknown devices to configuration file."""
|
"""Test the adding of unknown devices to configuration file."""
|
||||||
scanner = get_component('device_tracker.test').SCANNER
|
scanner = get_component('device_tracker.test').SCANNER
|
||||||
scanner.reset()
|
scanner.reset()
|
||||||
@ -90,7 +132,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
self.assertTrue(device_tracker.setup(self.hass, {
|
self.assertTrue(device_tracker.setup(self.hass, {
|
||||||
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
||||||
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||||
timedelta(seconds=0), 0)
|
timedelta(seconds=0))
|
||||||
assert len(config) == 1
|
assert len(config) == 1
|
||||||
assert config[0].dev_id == 'dev1'
|
assert config[0].dev_id == 'dev1'
|
||||||
assert config[0].track
|
assert config[0].track
|
||||||
@ -99,7 +141,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
"""Test the Gravatar generation."""
|
"""Test the Gravatar generation."""
|
||||||
dev_id = 'test'
|
dev_id = 'test'
|
||||||
device = device_tracker.Device(
|
device = device_tracker.Device(
|
||||||
self.hass, timedelta(seconds=180), 0, True, dev_id,
|
self.hass, timedelta(seconds=180), True, dev_id,
|
||||||
'AB:CD:EF:GH:IJ', 'Test name', gravatar='test@example.com')
|
'AB:CD:EF:GH:IJ', 'Test name', gravatar='test@example.com')
|
||||||
gravatar_url = ("https://www.gravatar.com/avatar/"
|
gravatar_url = ("https://www.gravatar.com/avatar/"
|
||||||
"55502f40dc8b7c769880b10874abc9d0.jpg?s=80&d=wavatar")
|
"55502f40dc8b7c769880b10874abc9d0.jpg?s=80&d=wavatar")
|
||||||
@ -109,7 +151,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
"""Test that Gravatar overrides picture."""
|
"""Test that Gravatar overrides picture."""
|
||||||
dev_id = 'test'
|
dev_id = 'test'
|
||||||
device = device_tracker.Device(
|
device = device_tracker.Device(
|
||||||
self.hass, timedelta(seconds=180), 0, True, dev_id,
|
self.hass, timedelta(seconds=180), True, dev_id,
|
||||||
'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture',
|
'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture',
|
||||||
gravatar='test@example.com')
|
gravatar='test@example.com')
|
||||||
gravatar_url = ("https://www.gravatar.com/avatar/"
|
gravatar_url = ("https://www.gravatar.com/avatar/"
|
||||||
@ -122,8 +164,8 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
|
|
||||||
with patch.dict(device_tracker.DISCOVERY_PLATFORMS, {'test': 'test'}):
|
with patch.dict(device_tracker.DISCOVERY_PLATFORMS, {'test': 'test'}):
|
||||||
with patch.object(scanner, 'scan_devices') as mock_scan:
|
with patch.object(scanner, 'scan_devices') as mock_scan:
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {
|
self.assertTrue(device_tracker.setup(self.hass,
|
||||||
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
TEST_PLATFORM))
|
||||||
fire_service_discovered(self.hass, 'test', {})
|
fire_service_discovered(self.hass, 'test', {})
|
||||||
self.assertTrue(mock_scan.called)
|
self.assertTrue(mock_scan.called)
|
||||||
|
|
||||||
@ -139,9 +181,9 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
with patch('homeassistant.components.device_tracker.dt_util.utcnow',
|
with patch('homeassistant.components.device_tracker.dt_util.utcnow',
|
||||||
return_value=register_time):
|
return_value=register_time):
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {
|
self.assertTrue(device_tracker.setup(self.hass, {
|
||||||
'device_tracker': {
|
device_tracker.DOMAIN: {
|
||||||
'platform': 'test',
|
CONF_PLATFORM: 'test',
|
||||||
'consider_home': 59,
|
device_tracker.CONF_CONSIDER_HOME: 59,
|
||||||
}}))
|
}}))
|
||||||
|
|
||||||
self.assertEqual(STATE_HOME,
|
self.assertEqual(STATE_HOME,
|
||||||
@ -165,11 +207,11 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
picture = 'http://placehold.it/200x200'
|
picture = 'http://placehold.it/200x200'
|
||||||
|
|
||||||
device = device_tracker.Device(
|
device = device_tracker.Device(
|
||||||
self.hass, timedelta(seconds=180), 0, True, dev_id, None,
|
self.hass, timedelta(seconds=180), True, dev_id, None,
|
||||||
friendly_name, picture, away_hide=True)
|
friendly_name, picture, away_hide=True)
|
||||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||||
|
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
|
|
||||||
attrs = self.hass.states.get(entity_id).attributes
|
attrs = self.hass.states.get(entity_id).attributes
|
||||||
|
|
||||||
@ -181,15 +223,14 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
dev_id = 'test_entity'
|
dev_id = 'test_entity'
|
||||||
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
|
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
|
||||||
device = device_tracker.Device(
|
device = device_tracker.Device(
|
||||||
self.hass, timedelta(seconds=180), 0, True, dev_id, None,
|
self.hass, timedelta(seconds=180), True, dev_id, None,
|
||||||
away_hide=True)
|
away_hide=True)
|
||||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||||
|
|
||||||
scanner = get_component('device_tracker.test').SCANNER
|
scanner = get_component('device_tracker.test').SCANNER
|
||||||
scanner.reset()
|
scanner.reset()
|
||||||
|
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
|
||||||
|
|
||||||
self.assertTrue(self.hass.states.get(entity_id)
|
self.assertTrue(self.hass.states.get(entity_id)
|
||||||
.attributes.get(ATTR_HIDDEN))
|
.attributes.get(ATTR_HIDDEN))
|
||||||
@ -199,15 +240,14 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
dev_id = 'test_entity'
|
dev_id = 'test_entity'
|
||||||
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
|
entity_id = device_tracker.ENTITY_ID_FORMAT.format(dev_id)
|
||||||
device = device_tracker.Device(
|
device = device_tracker.Device(
|
||||||
self.hass, timedelta(seconds=180), 0, True, dev_id, None,
|
self.hass, timedelta(seconds=180), True, dev_id, None,
|
||||||
away_hide=True)
|
away_hide=True)
|
||||||
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
device_tracker.update_config(self.yaml_devices, dev_id, device)
|
||||||
|
|
||||||
scanner = get_component('device_tracker.test').SCANNER
|
scanner = get_component('device_tracker.test').SCANNER
|
||||||
scanner.reset()
|
scanner.reset()
|
||||||
|
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}}))
|
|
||||||
|
|
||||||
state = self.hass.states.get(device_tracker.ENTITY_ID_ALL_DEVICES)
|
state = self.hass.states.get(device_tracker.ENTITY_ID_ALL_DEVICES)
|
||||||
self.assertIsNotNone(state)
|
self.assertIsNotNone(state)
|
||||||
@ -217,40 +257,31 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
|
|
||||||
@patch('homeassistant.components.device_tracker.DeviceTracker.see')
|
@patch('homeassistant.components.device_tracker.DeviceTracker.see')
|
||||||
def test_see_service(self, mock_see):
|
def test_see_service(self, mock_see):
|
||||||
"""Test the see service."""
|
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
|
||||||
mac = 'AB:CD:EF:GH'
|
|
||||||
dev_id = 'some_device'
|
|
||||||
host_name = 'example.com'
|
|
||||||
location_name = 'Work'
|
|
||||||
gps = [.3, .8]
|
|
||||||
|
|
||||||
device_tracker.see(self.hass, mac, dev_id, host_name, location_name,
|
|
||||||
gps)
|
|
||||||
|
|
||||||
self.hass.pool.block_till_done()
|
|
||||||
|
|
||||||
mock_see.assert_called_once_with(
|
|
||||||
mac=mac, dev_id=dev_id, host_name=host_name,
|
|
||||||
location_name=location_name, gps=gps)
|
|
||||||
|
|
||||||
@patch('homeassistant.components.device_tracker.DeviceTracker.see')
|
|
||||||
def test_see_service_unicode_dev_id(self, mock_see):
|
|
||||||
"""Test the see service with a unicode dev_id and NO MAC."""
|
"""Test the see service with a unicode dev_id and NO MAC."""
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
params = {
|
params = {
|
||||||
'dev_id': chr(233), # e' acute accent from icloud
|
'dev_id': 'some_device',
|
||||||
'host_name': 'example.com',
|
'host_name': 'example.com',
|
||||||
'location_name': 'Work',
|
'location_name': 'Work',
|
||||||
'gps': [.3, .8]
|
'gps': [.3, .8]
|
||||||
}
|
}
|
||||||
device_tracker.see(self.hass, **params)
|
device_tracker.see(self.hass, **params)
|
||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
assert mock_see.call_count == 1
|
||||||
mock_see.assert_called_once_with(**params)
|
mock_see.assert_called_once_with(**params)
|
||||||
|
|
||||||
def test_not_write_duplicate_yaml_keys(self):
|
mock_see.reset_mock()
|
||||||
|
params['dev_id'] += chr(233) # e' acute accent from icloud
|
||||||
|
|
||||||
|
device_tracker.see(self.hass, **params)
|
||||||
|
self.hass.pool.block_till_done()
|
||||||
|
assert mock_see.call_count == 1
|
||||||
|
mock_see.assert_called_once_with(**params)
|
||||||
|
|
||||||
|
def test_not_write_duplicate_yaml_keys(self): \
|
||||||
|
# pylint: disable=invalid-name
|
||||||
"""Test that the device tracker will not generate invalid YAML."""
|
"""Test that the device tracker will not generate invalid YAML."""
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
|
|
||||||
device_tracker.see(self.hass, 'mac_1', host_name='hello')
|
device_tracker.see(self.hass, 'mac_1', host_name='hello')
|
||||||
device_tracker.see(self.hass, 'mac_2', host_name='hello')
|
device_tracker.see(self.hass, 'mac_2', host_name='hello')
|
||||||
@ -258,15 +289,46 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||||||
self.hass.pool.block_till_done()
|
self.hass.pool.block_till_done()
|
||||||
|
|
||||||
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||||
timedelta(seconds=0), 0)
|
timedelta(seconds=0))
|
||||||
assert len(config) == 2
|
assert len(config) == 2
|
||||||
|
|
||||||
def test_not_allow_invalid_dev_id(self):
|
def test_not_allow_invalid_dev_id(self): # pylint: disable=invalid-name
|
||||||
"""Test that the device tracker will not allow invalid dev ids."""
|
"""Test that the device tracker will not allow invalid dev ids."""
|
||||||
self.assertTrue(device_tracker.setup(self.hass, {}))
|
self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM))
|
||||||
|
|
||||||
device_tracker.see(self.hass, dev_id='hello-world')
|
device_tracker.see(self.hass, dev_id='hello-world')
|
||||||
|
|
||||||
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||||
timedelta(seconds=0), 0)
|
timedelta(seconds=0))
|
||||||
assert len(config) == 0
|
assert len(config) == 0
|
||||||
|
|
||||||
|
@patch('homeassistant.components.device_tracker._LOGGER.warning')
|
||||||
|
def test_see_failures(self, mock_warning):
|
||||||
|
"""Test that the device tracker see failures."""
|
||||||
|
tracker = device_tracker.DeviceTracker(
|
||||||
|
self.hass, timedelta(seconds=60), 0, [])
|
||||||
|
|
||||||
|
# MAC is not a string (but added)
|
||||||
|
tracker.see(mac=567, host_name="Number MAC")
|
||||||
|
|
||||||
|
# No device id or MAC(not added)
|
||||||
|
with self.assertRaises(HomeAssistantError):
|
||||||
|
tracker.see()
|
||||||
|
assert mock_warning.call_count == 0
|
||||||
|
|
||||||
|
# Ignore gps on invalid GPS (both added & warnings)
|
||||||
|
tracker.see(mac='mac_1_bad_gps', gps=1)
|
||||||
|
tracker.see(mac='mac_2_bad_gps', gps=[1])
|
||||||
|
tracker.see(mac='mac_3_bad_gps', gps='gps')
|
||||||
|
config = device_tracker.load_config(self.yaml_devices, self.hass,
|
||||||
|
timedelta(seconds=0))
|
||||||
|
assert mock_warning.call_count == 3
|
||||||
|
|
||||||
|
assert len(config) == 4
|
||||||
|
|
||||||
|
@patch('homeassistant.components.device_tracker.log_exception')
|
||||||
|
def test_config_failure(self, mock_ex):
|
||||||
|
"""Test that the device tracker see failures."""
|
||||||
|
device_tracker.setup(self.hass, {device_tracker.DOMAIN: {
|
||||||
|
device_tracker.CONF_CONSIDER_HOME: -1}})
|
||||||
|
assert mock_ex.call_count == 1
|
||||||
|
@ -8,17 +8,19 @@ import requests
|
|||||||
from homeassistant import bootstrap, const
|
from homeassistant import bootstrap, const
|
||||||
import homeassistant.components.device_tracker as device_tracker
|
import homeassistant.components.device_tracker as device_tracker
|
||||||
import homeassistant.components.http as http
|
import homeassistant.components.http as http
|
||||||
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant, get_test_instance_port
|
from tests.common import get_test_home_assistant, get_test_instance_port
|
||||||
|
|
||||||
SERVER_PORT = get_test_instance_port()
|
SERVER_PORT = get_test_instance_port()
|
||||||
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
|
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
|
||||||
|
|
||||||
hass = None
|
hass = None # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
|
||||||
def _url(data={}):
|
def _url(data=None):
|
||||||
"""Helper method to generate URLs."""
|
"""Helper method to generate URLs."""
|
||||||
|
data = data or {}
|
||||||
data = "&".join(["{}={}".format(name, value) for
|
data = "&".join(["{}={}".format(name, value) for
|
||||||
name, value in data.items()])
|
name, value in data.items()])
|
||||||
return "{}{}locative?{}".format(HTTP_BASE_URL, const.URL_API, data)
|
return "{}{}locative?{}".format(HTTP_BASE_URL, const.URL_API, data)
|
||||||
@ -26,7 +28,7 @@ def _url(data={}):
|
|||||||
|
|
||||||
def setUpModule(): # pylint: disable=invalid-name
|
def setUpModule(): # pylint: disable=invalid-name
|
||||||
"""Initalize a Home Assistant server."""
|
"""Initalize a Home Assistant server."""
|
||||||
global hass
|
global hass # pylint: disable=invalid-name
|
||||||
|
|
||||||
hass = get_test_home_assistant()
|
hass = get_test_home_assistant()
|
||||||
bootstrap.setup_component(hass, http.DOMAIN, {
|
bootstrap.setup_component(hass, http.DOMAIN, {
|
||||||
@ -38,7 +40,7 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
# Set up device tracker
|
# Set up device tracker
|
||||||
bootstrap.setup_component(hass, device_tracker.DOMAIN, {
|
bootstrap.setup_component(hass, device_tracker.DOMAIN, {
|
||||||
device_tracker.DOMAIN: {
|
device_tracker.DOMAIN: {
|
||||||
'platform': 'locative'
|
CONF_PLATFORM: 'locative'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
"""The tests for the MQTT device tracker platform."""
|
"""The tests for the MQTT device tracker platform."""
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from homeassistant.bootstrap import _setup_component
|
from homeassistant.bootstrap import _setup_component
|
||||||
@ -9,6 +11,8 @@ from homeassistant.const import CONF_PLATFORM
|
|||||||
from tests.common import (
|
from tests.common import (
|
||||||
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message)
|
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestComponentsDeviceTrackerMQTT(unittest.TestCase):
|
class TestComponentsDeviceTrackerMQTT(unittest.TestCase):
|
||||||
"""Test MQTT device tracker platform."""
|
"""Test MQTT device tracker platform."""
|
||||||
@ -25,6 +29,27 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase):
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def test_ensure_device_tracker_platform_validation(self): \
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
"""Test if platform validation was done."""
|
||||||
|
def mock_setup_scanner(hass, config, see):
|
||||||
|
"""Check that Qos was added by validation."""
|
||||||
|
self.assertTrue('qos' in config)
|
||||||
|
|
||||||
|
with patch('homeassistant.components.device_tracker.mqtt.'
|
||||||
|
'setup_scanner', side_effect=mock_setup_scanner) as mock_sp:
|
||||||
|
|
||||||
|
dev_id = 'paulus'
|
||||||
|
topic = '/location/paulus'
|
||||||
|
self.hass.config.components = ['mqtt', 'zone']
|
||||||
|
assert _setup_component(self.hass, device_tracker.DOMAIN, {
|
||||||
|
device_tracker.DOMAIN: {
|
||||||
|
CONF_PLATFORM: 'mqtt',
|
||||||
|
'devices': {dev_id: topic}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert mock_sp.call_count == 1
|
||||||
|
|
||||||
def test_new_message(self):
|
def test_new_message(self):
|
||||||
"""Test new message."""
|
"""Test new message."""
|
||||||
dev_id = 'paulus'
|
dev_id = 'paulus'
|
||||||
|
@ -22,12 +22,12 @@ def setUpModule(): # pylint: disable=invalid-name
|
|||||||
"""Write a device tracker known devices file to be used."""
|
"""Write a device tracker known devices file to be used."""
|
||||||
device_tracker.update_config(
|
device_tracker.update_config(
|
||||||
KNOWN_DEV_YAML_PATH, 'device_1', device_tracker.Device(
|
KNOWN_DEV_YAML_PATH, 'device_1', device_tracker.Device(
|
||||||
None, None, None, True, 'device_1', 'DEV1',
|
None, None, True, 'device_1', 'DEV1',
|
||||||
picture='http://example.com/dev1.jpg'))
|
picture='http://example.com/dev1.jpg'))
|
||||||
|
|
||||||
device_tracker.update_config(
|
device_tracker.update_config(
|
||||||
KNOWN_DEV_YAML_PATH, 'device_2', device_tracker.Device(
|
KNOWN_DEV_YAML_PATH, 'device_2', device_tracker.Device(
|
||||||
None, None, None, True, 'device_2', 'DEV2',
|
None, None, True, 'device_2', 'DEV2',
|
||||||
picture='http://example.com/dev2.jpg'))
|
picture='http://example.com/dev2.jpg'))
|
||||||
|
|
||||||
|
|
||||||
@ -83,7 +83,8 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertTrue(light.is_on(self.hass))
|
self.assertTrue(light.is_on(self.hass))
|
||||||
|
|
||||||
def test_lights_turn_off_when_everyone_leaves(self):
|
def test_lights_turn_off_when_everyone_leaves(self): \
|
||||||
|
# pylint: disable=invalid-name
|
||||||
"""Test lights turn off when everyone leaves the house."""
|
"""Test lights turn off when everyone leaves the house."""
|
||||||
light.turn_on(self.hass)
|
light.turn_on(self.hass)
|
||||||
|
|
||||||
@ -99,7 +100,8 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertFalse(light.is_on(self.hass))
|
self.assertFalse(light.is_on(self.hass))
|
||||||
|
|
||||||
def test_lights_turn_on_when_coming_home_after_sun_set(self):
|
def test_lights_turn_on_when_coming_home_after_sun_set(self): \
|
||||||
|
# pylint: disable=invalid-name
|
||||||
"""Test lights turn on when coming home after sun set."""
|
"""Test lights turn on when coming home after sun set."""
|
||||||
light.turn_off(self.hass)
|
light.turn_off(self.hass)
|
||||||
ensure_sun_set(self.hass)
|
ensure_sun_set(self.hass)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user