mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Merge pull request #5384 from home-assistant/release-0-36-1
Release 0 36 1
This commit is contained in:
commit
4d96b12424
@ -17,7 +17,7 @@ from homeassistant.const import (
|
|||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
REQUIREMENTS = ['python-eq3bt==0.1.2']
|
REQUIREMENTS = ['python-eq3bt==0.1.4']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ ATTR_STATE_WINDOW_OPEN = "window_open"
|
|||||||
ATTR_STATE_VALVE = "valve"
|
ATTR_STATE_VALVE = "valve"
|
||||||
ATTR_STATE_LOCKED = "is_locked"
|
ATTR_STATE_LOCKED = "is_locked"
|
||||||
ATTR_STATE_LOW_BAT = "low_battery"
|
ATTR_STATE_LOW_BAT = "low_battery"
|
||||||
|
ATTR_STATE_AWAY_END = "away_end"
|
||||||
|
|
||||||
DEVICE_SCHEMA = vol.Schema({
|
DEVICE_SCHEMA = vol.Schema({
|
||||||
vol.Required(CONF_MAC): cv.string,
|
vol.Required(CONF_MAC): cv.string,
|
||||||
@ -152,6 +153,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
|
|||||||
ATTR_STATE_LOW_BAT: self._thermostat.low_battery,
|
ATTR_STATE_LOW_BAT: self._thermostat.low_battery,
|
||||||
ATTR_STATE_VALVE: self._thermostat.valve_state,
|
ATTR_STATE_VALVE: self._thermostat.valve_state,
|
||||||
ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open,
|
ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open,
|
||||||
|
ATTR_STATE_AWAY_END: self._thermostat.away_end,
|
||||||
}
|
}
|
||||||
|
|
||||||
return dev_specific
|
return dev_specific
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
"""Tracking for bluetooth low energy devices."""
|
"""Tracking for bluetooth low energy devices."""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import voluptuous as vol
|
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, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
|
YAML_DEVICES, CONF_TRACK_NEW, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
|
||||||
PLATFORM_SCHEMA, load_config, DEFAULT_TRACK_NEW
|
PLATFORM_SCHEMA, load_config
|
||||||
)
|
)
|
||||||
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
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
@ -86,14 +84,13 @@ def setup_scanner(hass, config, see):
|
|||||||
|
|
||||||
# if track new devices is true discover new devices
|
# if track new devices is true discover new devices
|
||||||
# on every scan.
|
# on every scan.
|
||||||
track_new = util.convert(config.get(CONF_TRACK_NEW), bool,
|
track_new = config.get(CONF_TRACK_NEW)
|
||||||
DEFAULT_TRACK_NEW)
|
|
||||||
if not devs_to_track and not track_new:
|
if not devs_to_track and not track_new:
|
||||||
_LOGGER.warning("No Bluetooth LE devices to track!")
|
_LOGGER.warning("No Bluetooth LE devices to track!")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
interval = util.convert(config.get(CONF_SCAN_INTERVAL), int,
|
interval = config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||||
DEFAULT_SCAN_INTERVAL)
|
|
||||||
|
|
||||||
def update_ble(now):
|
def update_ble(now):
|
||||||
"""Lookup Bluetooth LE devices and update status."""
|
"""Lookup Bluetooth LE devices and update status."""
|
||||||
@ -113,8 +110,7 @@ def setup_scanner(hass, config, see):
|
|||||||
_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)
|
||||||
|
|
||||||
track_point_in_utc_time(hass, update_ble,
|
track_point_in_utc_time(hass, update_ble, now + interval)
|
||||||
now + timedelta(seconds=interval))
|
|
||||||
|
|
||||||
update_ble(dt_util.utcnow())
|
update_ble(dt_util.utcnow())
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""Tracking for bluetooth devices."""
|
"""Tracking for bluetooth devices."""
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -83,8 +82,7 @@ def setup_scanner(hass, config, see):
|
|||||||
see_device((mac, result))
|
see_device((mac, result))
|
||||||
except bluetooth.BluetoothError:
|
except bluetooth.BluetoothError:
|
||||||
_LOGGER.exception('Error looking up bluetooth device!')
|
_LOGGER.exception('Error looking up bluetooth device!')
|
||||||
track_point_in_utc_time(hass, update_bluetooth,
|
track_point_in_utc_time(hass, update_bluetooth, now + interval)
|
||||||
now + timedelta(seconds=interval))
|
|
||||||
|
|
||||||
update_bluetooth(dt_util.utcnow())
|
update_bluetooth(dt_util.utcnow())
|
||||||
|
|
||||||
|
@ -74,8 +74,11 @@ class UPCDeviceScanner(DeviceScanner):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
raw = yield from self._async_ws_function(CMD_DEVICES)
|
raw = yield from self._async_ws_function(CMD_DEVICES)
|
||||||
xml_root = ET.fromstring(raw)
|
if raw is None:
|
||||||
|
_LOGGER.warning("Can't read device from %s", self.host)
|
||||||
|
return
|
||||||
|
|
||||||
|
xml_root = ET.fromstring(raw)
|
||||||
return [mac.text for mac in xml_root.iter('MACAddr')]
|
return [mac.text for mac in xml_root.iter('MACAddr')]
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -94,7 +97,8 @@ class UPCDeviceScanner(DeviceScanner):
|
|||||||
"http://{}/common_page/login.html".format(self.host)
|
"http://{}/common_page/login.html".format(self.host)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.token = self._async_get_token()
|
yield from response.text()
|
||||||
|
self.token = response.cookies['sessionToken'].value
|
||||||
|
|
||||||
# login
|
# login
|
||||||
data = yield from self._async_ws_function(CMD_LOGIN, {
|
data = yield from self._async_ws_function(CMD_LOGIN, {
|
||||||
@ -144,7 +148,7 @@ class UPCDeviceScanner(DeviceScanner):
|
|||||||
|
|
||||||
# load data, store token for next request
|
# load data, store token for next request
|
||||||
raw = yield from response.text()
|
raw = yield from response.text()
|
||||||
self.token = self._async_get_token()
|
self.token = response.cookies['sessionToken'].value
|
||||||
|
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
@ -155,10 +159,3 @@ class UPCDeviceScanner(DeviceScanner):
|
|||||||
finally:
|
finally:
|
||||||
if response is not None:
|
if response is not None:
|
||||||
yield from response.release()
|
yield from response.release()
|
||||||
|
|
||||||
def _async_get_token(self):
|
|
||||||
"""Extract token from cookies."""
|
|
||||||
cookie_manager = self.websession.cookie_jar.filter_cookies(
|
|
||||||
"http://{}".format(self.host))
|
|
||||||
|
|
||||||
return cookie_manager.get('sessionToken')
|
|
||||||
|
@ -37,7 +37,7 @@ def setup_scanner(hass, config, see):
|
|||||||
config.get(CONF_USERNAME),
|
config.get(CONF_USERNAME),
|
||||||
config.get(CONF_PASSWORD))
|
config.get(CONF_PASSWORD))
|
||||||
|
|
||||||
interval = max(MIN_TIME_BETWEEN_SCANS.seconds,
|
interval = max(MIN_TIME_BETWEEN_SCANS,
|
||||||
config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
|
config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
|
||||||
|
|
||||||
def _see_vehicle(vehicle):
|
def _see_vehicle(vehicle):
|
||||||
@ -91,8 +91,7 @@ def setup_scanner(hass, config, see):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
finally:
|
finally:
|
||||||
track_point_in_utc_time(hass, update,
|
track_point_in_utc_time(hass, update, now + interval)
|
||||||
now + timedelta(seconds=interval))
|
|
||||||
|
|
||||||
_LOGGER.info('Logging in to service')
|
_LOGGER.info('Logging in to service')
|
||||||
return update(utcnow())
|
return update(utcnow())
|
||||||
|
@ -72,6 +72,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
continue
|
continue
|
||||||
device['name'] = device['id'] + " " + ipaddr
|
device['name'] = device['id'] + " " + ipaddr
|
||||||
device[ATTR_MODE] = 'rgbw'
|
device[ATTR_MODE] = 'rgbw'
|
||||||
|
device[CONF_PROTOCOL] = None
|
||||||
light = FluxLight(device)
|
light = FluxLight(device)
|
||||||
if light.is_valid:
|
if light.is_valid:
|
||||||
lights.append(light)
|
lights.append(light)
|
||||||
|
@ -20,7 +20,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
REQUIREMENTS = [
|
REQUIREMENTS = [
|
||||||
'http://github.com/technicalpickles/python-nest'
|
'http://github.com/technicalpickles/python-nest'
|
||||||
'/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip' # nest-cam branch
|
'/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip' # nest-cam branch
|
||||||
'#python-nest==3.0.3']
|
'#python-nest==3.0.2']
|
||||||
|
|
||||||
DOMAIN = 'nest'
|
DOMAIN = 'nest'
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config):
|
def get_service(hass, config, discovery_info=None):
|
||||||
"""Get the Lannouncer notification service."""
|
"""Get the Lannouncer notification service."""
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
port = config.get(CONF_PORT)
|
port = config.get(CONF_PORT)
|
||||||
|
@ -22,6 +22,7 @@ REQUIREMENTS = ['myusps==1.0.1']
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
COOKIE = 'usps_cookies.pickle'
|
||||||
CONF_UPDATE_INTERVAL = 'update_interval'
|
CONF_UPDATE_INTERVAL = 'update_interval'
|
||||||
ICON = 'mdi:package-variant-closed'
|
ICON = 'mdi:package-variant-closed'
|
||||||
STATUS_DELIVERED = 'delivered'
|
STATUS_DELIVERED = 'delivered'
|
||||||
@ -39,8 +40,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
|||||||
"""Setup the USPS platform."""
|
"""Setup the USPS platform."""
|
||||||
import myusps
|
import myusps
|
||||||
try:
|
try:
|
||||||
|
cookie = hass.config.path(COOKIE)
|
||||||
session = myusps.get_session(config.get(CONF_USERNAME),
|
session = myusps.get_session(config.get(CONF_USERNAME),
|
||||||
config.get(CONF_PASSWORD))
|
config.get(CONF_PASSWORD),
|
||||||
|
cookie_path=cookie)
|
||||||
except myusps.USPSError:
|
except myusps.USPSError:
|
||||||
_LOGGER.exception('Could not connect to My USPS')
|
_LOGGER.exception('Could not connect to My USPS')
|
||||||
return False
|
return False
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 36
|
MINOR_VERSION = 36
|
||||||
PATCH_VERSION = '0'
|
PATCH_VERSION = '1'
|
||||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||||
REQUIRED_PYTHON_VER = (3, 4, 2)
|
REQUIRED_PYTHON_VER = (3, 4, 2)
|
||||||
|
@ -60,7 +60,7 @@ def load_yaml(fname: str) -> Union[List, Dict]:
|
|||||||
with open(fname, encoding='utf-8') as conf_file:
|
with open(fname, encoding='utf-8') as conf_file:
|
||||||
# If configuration file is empty YAML returns None
|
# If configuration file is empty YAML returns None
|
||||||
# We convert that to an empty dict
|
# We convert that to an empty dict
|
||||||
return yaml.load(conf_file, Loader=SafeLineLoader) or {}
|
return yaml.load(conf_file, Loader=SafeLineLoader) or OrderedDict()
|
||||||
except yaml.YAMLError as exc:
|
except yaml.YAMLError as exc:
|
||||||
_LOGGER.error(exc)
|
_LOGGER.error(exc)
|
||||||
raise HomeAssistantError(exc)
|
raise HomeAssistantError(exc)
|
||||||
|
@ -178,7 +178,7 @@ hikvision==0.4
|
|||||||
# http://github.com/adafruit/Adafruit_Python_DHT/archive/310c59b0293354d07d94375f1365f7b9b9110c7d.zip#Adafruit_DHT==1.3.0
|
# http://github.com/adafruit/Adafruit_Python_DHT/archive/310c59b0293354d07d94375f1365f7b9b9110c7d.zip#Adafruit_DHT==1.3.0
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
http://github.com/technicalpickles/python-nest/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip#python-nest==3.0.3
|
http://github.com/technicalpickles/python-nest/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip#python-nest==3.0.2
|
||||||
|
|
||||||
# homeassistant.components.switch.dlink
|
# homeassistant.components.switch.dlink
|
||||||
https://github.com/LinuxChristian/pyW215/archive/v0.3.7.zip#pyW215==0.3.7
|
https://github.com/LinuxChristian/pyW215/archive/v0.3.7.zip#pyW215==0.3.7
|
||||||
@ -476,7 +476,7 @@ pysnmp==4.3.2
|
|||||||
python-digitalocean==1.10.1
|
python-digitalocean==1.10.1
|
||||||
|
|
||||||
# homeassistant.components.climate.eq3btsmart
|
# homeassistant.components.climate.eq3btsmart
|
||||||
python-eq3bt==0.1.2
|
python-eq3bt==0.1.4
|
||||||
|
|
||||||
# homeassistant.components.sensor.darksky
|
# homeassistant.components.sensor.darksky
|
||||||
python-forecastio==1.3.5
|
python-forecastio==1.3.5
|
||||||
|
@ -37,11 +37,9 @@ class AiohttpClientMocker:
|
|||||||
content = b''
|
content = b''
|
||||||
if params:
|
if params:
|
||||||
url = str(yarl.URL(url).with_query(params))
|
url = str(yarl.URL(url).with_query(params))
|
||||||
if cookies:
|
|
||||||
self._cookies.update(cookies)
|
|
||||||
|
|
||||||
self._mocks.append(AiohttpClientMockResponse(
|
self._mocks.append(AiohttpClientMockResponse(
|
||||||
method, url, status, content, exc))
|
method, url, status, content, cookies, exc))
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
def get(self, *args, **kwargs):
|
||||||
"""Register a mock get request."""
|
"""Register a mock get request."""
|
||||||
@ -68,10 +66,6 @@ class AiohttpClientMocker:
|
|||||||
"""Number of requests made."""
|
"""Number of requests made."""
|
||||||
return len(self.mock_calls)
|
return len(self.mock_calls)
|
||||||
|
|
||||||
def filter_cookies(self, host):
|
|
||||||
"""Return hosts cookies."""
|
|
||||||
return self._cookies
|
|
||||||
|
|
||||||
def clear_requests(self):
|
def clear_requests(self):
|
||||||
"""Reset mock calls."""
|
"""Reset mock calls."""
|
||||||
self._mocks.clear()
|
self._mocks.clear()
|
||||||
@ -97,7 +91,7 @@ class AiohttpClientMocker:
|
|||||||
class AiohttpClientMockResponse:
|
class AiohttpClientMockResponse:
|
||||||
"""Mock Aiohttp client response."""
|
"""Mock Aiohttp client response."""
|
||||||
|
|
||||||
def __init__(self, method, url, status, response, exc=None):
|
def __init__(self, method, url, status, response, cookies=None, exc=None):
|
||||||
"""Initialize a fake response."""
|
"""Initialize a fake response."""
|
||||||
self.method = method
|
self.method = method
|
||||||
self._url = url
|
self._url = url
|
||||||
@ -107,6 +101,14 @@ class AiohttpClientMockResponse:
|
|||||||
self.response = response
|
self.response = response
|
||||||
self.exc = exc
|
self.exc = exc
|
||||||
|
|
||||||
|
self._cookies = {}
|
||||||
|
|
||||||
|
if cookies:
|
||||||
|
for name, data in cookies.items():
|
||||||
|
cookie = mock.MagicMock()
|
||||||
|
cookie.value = data
|
||||||
|
self._cookies[name] = cookie
|
||||||
|
|
||||||
def match_request(self, method, url, params=None):
|
def match_request(self, method, url, params=None):
|
||||||
"""Test if response answers request."""
|
"""Test if response answers request."""
|
||||||
if method.lower() != self.method.lower():
|
if method.lower() != self.method.lower():
|
||||||
@ -140,6 +142,11 @@ class AiohttpClientMockResponse:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cookies(self):
|
||||||
|
"""Return dict of cookies."""
|
||||||
|
return self._cookies
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def read(self):
|
def read(self):
|
||||||
"""Return mock response."""
|
"""Return mock response."""
|
||||||
@ -160,6 +167,10 @@ class AiohttpClientMockResponse:
|
|||||||
"""Mock release."""
|
"""Mock release."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Mock close."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def mock_aiohttp_client():
|
def mock_aiohttp_client():
|
||||||
@ -173,6 +184,4 @@ def mock_aiohttp_client():
|
|||||||
setattr(instance, method,
|
setattr(instance, method,
|
||||||
functools.partial(mocker.match_request, method))
|
functools.partial(mocker.match_request, method))
|
||||||
|
|
||||||
instance.cookie_jar.filter_cookies = mocker.filter_cookies
|
|
||||||
|
|
||||||
yield mocker
|
yield mocker
|
||||||
|
@ -74,6 +74,12 @@ class TestYaml(unittest.TestCase):
|
|||||||
doc = yaml.yaml.safe_load(file)
|
doc = yaml.yaml.safe_load(file)
|
||||||
assert doc["key"] == "value"
|
assert doc["key"] == "value"
|
||||||
|
|
||||||
|
with patch_yaml_files({'test.yaml': None}):
|
||||||
|
conf = 'key: !include test.yaml'
|
||||||
|
with io.StringIO(conf) as file:
|
||||||
|
doc = yaml.yaml.safe_load(file)
|
||||||
|
assert doc["key"] == {}
|
||||||
|
|
||||||
@patch('homeassistant.util.yaml.os.walk')
|
@patch('homeassistant.util.yaml.os.walk')
|
||||||
def test_include_dir_list(self, mock_walk):
|
def test_include_dir_list(self, mock_walk):
|
||||||
"""Test include dir list yaml."""
|
"""Test include dir list yaml."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user