mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 15:17:35 +00:00
commit
38da81c308
@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
})
|
})
|
||||||
|
|
||||||
REQUIREMENTS = ['python-miio==0.3.5']
|
REQUIREMENTS = ['python-miio==0.3.6']
|
||||||
|
|
||||||
ATTR_TEMPERATURE = 'temperature'
|
ATTR_TEMPERATURE = 'temperature'
|
||||||
ATTR_HUMIDITY = 'humidity'
|
ATTR_HUMIDITY = 'humidity'
|
||||||
|
@ -83,9 +83,9 @@ NODE_FILTERS = {
|
|||||||
},
|
},
|
||||||
'fan': {
|
'fan': {
|
||||||
'uom': [],
|
'uom': [],
|
||||||
'states': ['on', 'off', 'low', 'medium', 'high'],
|
'states': ['off', 'low', 'medium', 'high'],
|
||||||
'node_def_id': ['FanLincMotor'],
|
'node_def_id': ['FanLincMotor'],
|
||||||
'insteon_type': ['1.46.']
|
'insteon_type': []
|
||||||
},
|
},
|
||||||
'cover': {
|
'cover': {
|
||||||
'uom': ['97'],
|
'uom': ['97'],
|
||||||
@ -99,7 +99,7 @@ NODE_FILTERS = {
|
|||||||
'node_def_id': ['DimmerLampSwitch', 'DimmerLampSwitch_ADV',
|
'node_def_id': ['DimmerLampSwitch', 'DimmerLampSwitch_ADV',
|
||||||
'DimmerSwitchOnly', 'DimmerSwitchOnly_ADV',
|
'DimmerSwitchOnly', 'DimmerSwitchOnly_ADV',
|
||||||
'DimmerLampOnly', 'BallastRelayLampSwitch',
|
'DimmerLampOnly', 'BallastRelayLampSwitch',
|
||||||
'BallastRelayLampSwitch_ADV', 'RelayLampSwitch',
|
'BallastRelayLampSwitch_ADV',
|
||||||
'RemoteLinc2', 'RemoteLinc2_ADV'],
|
'RemoteLinc2', 'RemoteLinc2_ADV'],
|
||||||
'insteon_type': ['1.']
|
'insteon_type': ['1.']
|
||||||
},
|
},
|
||||||
@ -431,7 +431,10 @@ class ISYDevice(Entity):
|
|||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
"""Get the unique identifier of the device."""
|
"""Get the unique identifier of the device."""
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
return self._node._id
|
if hasattr(self._node, '_id'):
|
||||||
|
return self._node._id
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
|
@ -237,7 +237,6 @@ class LightTemplate(Light):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
def async_update(self):
|
||||||
"""Update the state from the template."""
|
"""Update the state from the template."""
|
||||||
print("ASYNC UPDATE")
|
|
||||||
if self._template is not None:
|
if self._template is not None:
|
||||||
try:
|
try:
|
||||||
state = self._template.async_render().lower()
|
state = self._template.async_render().lower()
|
||||||
@ -262,7 +261,7 @@ class LightTemplate(Light):
|
|||||||
self._state = None
|
self._state = None
|
||||||
|
|
||||||
if 0 <= int(brightness) <= 255:
|
if 0 <= int(brightness) <= 255:
|
||||||
self._brightness = brightness
|
self._brightness = int(brightness)
|
||||||
else:
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
'Received invalid brightness : %s' +
|
'Received invalid brightness : %s' +
|
||||||
|
@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
})
|
})
|
||||||
|
|
||||||
REQUIREMENTS = ['python-miio==0.3.5']
|
REQUIREMENTS = ['python-miio==0.3.6']
|
||||||
|
|
||||||
# The light does not accept cct values < 1
|
# The light does not accept cct values < 1
|
||||||
CCT_MIN = 1
|
CCT_MIN = 1
|
||||||
|
@ -370,7 +370,8 @@ class PlexClient(MediaPlayerDevice):
|
|||||||
self._is_player_available = False
|
self._is_player_available = False
|
||||||
self._media_position = self._session.viewOffset
|
self._media_position = self._session.viewOffset
|
||||||
self._media_content_id = self._session.ratingKey
|
self._media_content_id = self._session.ratingKey
|
||||||
self._media_content_rating = self._session.contentRating
|
self._media_content_rating = getattr(
|
||||||
|
self._session, 'contentRating', None)
|
||||||
|
|
||||||
self._set_player_state()
|
self._set_player_state()
|
||||||
|
|
||||||
|
@ -12,8 +12,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
def purge_old_data(instance, purge_days):
|
def purge_old_data(instance, purge_days):
|
||||||
"""Purge events and states older than purge_days ago."""
|
"""Purge events and states older than purge_days ago."""
|
||||||
from .models import States, Events
|
from .models import States, Events
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import func
|
||||||
from sqlalchemy.sql import exists
|
|
||||||
|
|
||||||
purge_before = dt_util.utcnow() - timedelta(days=purge_days)
|
purge_before = dt_util.utcnow() - timedelta(days=purge_days)
|
||||||
|
|
||||||
@ -21,18 +20,10 @@ def purge_old_data(instance, purge_days):
|
|||||||
# For each entity, the most recent state is protected from deletion
|
# For each entity, the most recent state is protected from deletion
|
||||||
# s.t. we can properly restore state even if the entity has not been
|
# s.t. we can properly restore state even if the entity has not been
|
||||||
# updated in a long time
|
# updated in a long time
|
||||||
states_alias = orm.aliased(States, name='StatesAlias')
|
protected_states = session.query(func.max(States.state_id)) \
|
||||||
protected_states = session.query(States.state_id, States.event_id)\
|
.group_by(States.entity_id).all()
|
||||||
.filter(~exists()
|
|
||||||
.where(States.entity_id ==
|
|
||||||
states_alias.entity_id)
|
|
||||||
.where(states_alias.last_updated >
|
|
||||||
States.last_updated))\
|
|
||||||
.all()
|
|
||||||
|
|
||||||
protected_state_ids = tuple((state[0] for state in protected_states))
|
protected_state_ids = tuple((state[0] for state in protected_states))
|
||||||
protected_event_ids = tuple((state[1] for state in protected_states
|
|
||||||
if state[1] is not None))
|
|
||||||
|
|
||||||
deleted_rows = session.query(States) \
|
deleted_rows = session.query(States) \
|
||||||
.filter((States.last_updated < purge_before)) \
|
.filter((States.last_updated < purge_before)) \
|
||||||
@ -45,6 +36,13 @@ def purge_old_data(instance, purge_days):
|
|||||||
# Otherwise, if the SQL server has "ON DELETE CASCADE" as default, it
|
# Otherwise, if the SQL server has "ON DELETE CASCADE" as default, it
|
||||||
# will delete the protected state when deleting its associated
|
# will delete the protected state when deleting its associated
|
||||||
# event. Also, we would be producing NULLed foreign keys otherwise.
|
# event. Also, we would be producing NULLed foreign keys otherwise.
|
||||||
|
protected_events = session.query(States.event_id) \
|
||||||
|
.filter(States.state_id.in_(protected_state_ids)) \
|
||||||
|
.filter(States.event_id.isnot(None)) \
|
||||||
|
.all()
|
||||||
|
|
||||||
|
protected_event_ids = tuple((state[0] for state in protected_events))
|
||||||
|
|
||||||
deleted_rows = session.query(Events) \
|
deleted_rows = session.query(Events) \
|
||||||
.filter((Events.time_fired < purge_before)) \
|
.filter((Events.time_fired < purge_before)) \
|
||||||
.filter(~Events.event_id.in_(
|
.filter(~Events.event_id.in_(
|
||||||
|
@ -21,7 +21,7 @@ from homeassistant.const import (
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
REQUIREMENTS = ['python-miio==0.3.5']
|
REQUIREMENTS = ['python-miio==0.3.6']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -126,6 +126,8 @@ class SQLSensor(Entity):
|
|||||||
except sqlalchemy.exc.SQLAlchemyError as err:
|
except sqlalchemy.exc.SQLAlchemyError as err:
|
||||||
_LOGGER.error("Error executing query %s: %s", self._query, err)
|
_LOGGER.error("Error executing query %s: %s", self._query, err)
|
||||||
return
|
return
|
||||||
|
finally:
|
||||||
|
sess.close()
|
||||||
|
|
||||||
for res in result:
|
for res in result:
|
||||||
_LOGGER.debug(res.items())
|
_LOGGER.debug(res.items())
|
||||||
@ -141,5 +143,3 @@ class SQLSensor(Entity):
|
|||||||
data, None)
|
data, None)
|
||||||
else:
|
else:
|
||||||
self._state = data
|
self._state = data
|
||||||
|
|
||||||
sess.close()
|
|
||||||
|
@ -7,7 +7,6 @@ https://home-assistant.io/components/sensor.yr/
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from random import randrange
|
from random import randrange
|
||||||
from xml.parsers.expat import ExpatError
|
from xml.parsers.expat import ExpatError
|
||||||
|
|
||||||
@ -22,16 +21,17 @@ from homeassistant.const import (
|
|||||||
ATTR_ATTRIBUTION, CONF_NAME)
|
ATTR_ATTRIBUTION, CONF_NAME)
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (async_track_utc_time_change,
|
||||||
async_track_point_in_utc_time, async_track_utc_time_change)
|
async_call_later)
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
REQUIREMENTS = ['xmltodict==0.11.0']
|
REQUIREMENTS = ['xmltodict==0.11.0']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_ATTRIBUTION = "Weather forecast from yr.no, delivered by the Norwegian " \
|
CONF_ATTRIBUTION = "Weather forecast from met.no, delivered " \
|
||||||
"Meteorological Institute and the NRK."
|
"by the Norwegian Meteorological Institute."
|
||||||
|
# https://api.met.no/license_data.html
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'symbol': ['Symbol', None],
|
'symbol': ['Symbol', None],
|
||||||
@ -91,11 +91,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
|||||||
async_add_devices(dev)
|
async_add_devices(dev)
|
||||||
|
|
||||||
weather = YrData(hass, coordinates, forecast, dev)
|
weather = YrData(hass, coordinates, forecast, dev)
|
||||||
# Update weather on the hour, spread seconds
|
async_track_utc_time_change(hass, weather.updating_devices, minute=31)
|
||||||
async_track_utc_time_change(
|
yield from weather.fetching_data()
|
||||||
hass, weather.async_update, minute=randrange(1, 10),
|
|
||||||
second=randrange(0, 59))
|
|
||||||
yield from weather.async_update()
|
|
||||||
|
|
||||||
|
|
||||||
class YrSensor(Entity):
|
class YrSensor(Entity):
|
||||||
@ -153,50 +150,49 @@ class YrData(object):
|
|||||||
self._url = 'https://aa015h6buqvih86i1.api.met.no/'\
|
self._url = 'https://aa015h6buqvih86i1.api.met.no/'\
|
||||||
'weatherapi/locationforecast/1.9/'
|
'weatherapi/locationforecast/1.9/'
|
||||||
self._urlparams = coordinates
|
self._urlparams = coordinates
|
||||||
self._nextrun = None
|
|
||||||
self._forecast = forecast
|
self._forecast = forecast
|
||||||
self.devices = devices
|
self.devices = devices
|
||||||
self.data = {}
|
self.data = {}
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self, *_):
|
def fetching_data(self, *_):
|
||||||
"""Get the latest data from yr.no."""
|
"""Get the latest data from yr.no."""
|
||||||
import xmltodict
|
import xmltodict
|
||||||
|
|
||||||
def try_again(err: str):
|
def try_again(err: str):
|
||||||
"""Retry in 15 minutes."""
|
"""Retry in 15 to 20 minutes."""
|
||||||
_LOGGER.warning("Retrying in 15 minutes: %s", err)
|
minutes = 15 + randrange(6)
|
||||||
self._nextrun = None
|
_LOGGER.error("Retrying in %i minutes: %s", minutes, err)
|
||||||
nxt = dt_util.utcnow() + timedelta(minutes=15)
|
async_call_later(self.hass, minutes*60, self.fetching_data)
|
||||||
if nxt.minute >= 15:
|
try:
|
||||||
async_track_point_in_utc_time(self.hass, self.async_update,
|
websession = async_get_clientsession(self.hass)
|
||||||
nxt)
|
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||||
|
resp = yield from websession.get(
|
||||||
if self._nextrun is None or dt_util.utcnow() >= self._nextrun:
|
self._url, params=self._urlparams)
|
||||||
try:
|
if resp.status != 200:
|
||||||
websession = async_get_clientsession(self.hass)
|
try_again('{} returned {}'.format(resp.url, resp.status))
|
||||||
with async_timeout.timeout(10, loop=self.hass.loop):
|
|
||||||
resp = yield from websession.get(
|
|
||||||
self._url, params=self._urlparams)
|
|
||||||
if resp.status != 200:
|
|
||||||
try_again('{} returned {}'.format(resp.url, resp.status))
|
|
||||||
return
|
|
||||||
text = yield from resp.text()
|
|
||||||
|
|
||||||
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
|
||||||
try_again(err)
|
|
||||||
return
|
return
|
||||||
|
text = yield from resp.text()
|
||||||
|
|
||||||
try:
|
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
|
||||||
self.data = xmltodict.parse(text)['weatherdata']
|
try_again(err)
|
||||||
model = self.data['meta']['model']
|
return
|
||||||
if '@nextrun' not in model:
|
|
||||||
model = model[0]
|
try:
|
||||||
self._nextrun = dt_util.parse_datetime(model['@nextrun'])
|
self.data = xmltodict.parse(text)['weatherdata']
|
||||||
except (ExpatError, IndexError) as err:
|
except (ExpatError, IndexError) as err:
|
||||||
try_again(err)
|
try_again(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
yield from self.updating_devices()
|
||||||
|
async_call_later(self.hass, 60*60, self.fetching_data)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def updating_devices(self, *_):
|
||||||
|
"""Find the current data from self.data."""
|
||||||
|
if not self.data:
|
||||||
|
return
|
||||||
|
|
||||||
now = dt_util.utcnow()
|
now = dt_util.utcnow()
|
||||||
forecast_time = now + dt_util.dt.timedelta(hours=self._forecast)
|
forecast_time = now + dt_util.dt.timedelta(hours=self._forecast)
|
||||||
|
@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
})
|
})
|
||||||
|
|
||||||
REQUIREMENTS = ['python-miio==0.3.5']
|
REQUIREMENTS = ['python-miio==0.3.6']
|
||||||
|
|
||||||
ATTR_POWER = 'power'
|
ATTR_POWER = 'power'
|
||||||
ATTR_TEMPERATURE = 'temperature'
|
ATTR_TEMPERATURE = 'temperature'
|
||||||
|
@ -19,7 +19,7 @@ from homeassistant.const import (
|
|||||||
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON)
|
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
REQUIREMENTS = ['python-miio==0.3.5']
|
REQUIREMENTS = ['python-miio==0.3.6']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 63
|
MINOR_VERSION = 63
|
||||||
PATCH_VERSION = '2'
|
PATCH_VERSION = '3'
|
||||||
__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)
|
||||||
|
@ -922,7 +922,7 @@ python-juicenet==0.0.5
|
|||||||
# homeassistant.components.remote.xiaomi_miio
|
# homeassistant.components.remote.xiaomi_miio
|
||||||
# homeassistant.components.switch.xiaomi_miio
|
# homeassistant.components.switch.xiaomi_miio
|
||||||
# homeassistant.components.vacuum.xiaomi_miio
|
# homeassistant.components.vacuum.xiaomi_miio
|
||||||
python-miio==0.3.5
|
python-miio==0.3.6
|
||||||
|
|
||||||
# homeassistant.components.media_player.mpd
|
# homeassistant.components.media_player.mpd
|
||||||
python-mpd2==0.5.5
|
python-mpd2==0.5.5
|
||||||
|
@ -586,7 +586,7 @@ class TestTemplateLight:
|
|||||||
state = self.hass.states.get('light.test_template_light')
|
state = self.hass.states.get('light.test_template_light')
|
||||||
assert state is not None
|
assert state is not None
|
||||||
|
|
||||||
assert state.attributes.get('brightness') == '42'
|
assert state.attributes.get('brightness') == 42
|
||||||
|
|
||||||
def test_friendly_name(self):
|
def test_friendly_name(self):
|
||||||
"""Test the accessibility of the friendly_name attribute."""
|
"""Test the accessibility of the friendly_name attribute."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user